Add comprehensive design and specification documentation (#1)
* Write the Unreal documentation set: design, steps, ideas, decisions and specs Rewrites the design and engineering docs of the two earlier Unity projects (Adventurer Guild, Project Malleable) for an Unreal Engine 5 prototype that builds and proves three things in order: a movement controller, fighting and crafting. Neither earlier core loop is carried; their ideas are catalogued as features with what each needs. - Docs/Design.md: the human summary (pillars, fixed decisions, glossary) - Docs/Steps.md: a ladder of fifteen proofs, the first six in full - Docs/Ideas.md: salvaged ideas from both projects, none scheduled - Docs/Decisions.md: D-01..D-36, open decisions OD-01..OD-08, deferrals - Docs/Spec/: notation, Architecture, Movement, Interaction, Combat, Crafting, Networking, Telemetry, UI, written as Unreal C++ skeletons - CLAUDE.md and README.md for the repository * Add the stat block: one attribute set on every body, every influence an effect Every body (player, enemy, NPC) carries the same UStatBlockAttributeSet, and every outside influence on it is a gameplay effect: base values, buffs, debuffs, ground surfaces, carried weight, gear, being downed. Counters are tags and attributes on the receiver: immunity blocks by tag, resistance scales by attribute, cleanse removes by tag. Same status from several sources: strongest wins; different statuses multiply. - Docs/Spec/Stats.md: the block, effects and their five sources (abilities, areas, surfaces, items, the world), stacking, counters, readers, tests - Movement: the movement component's tagged speed-multiplier map is removed; speed is tuning times the MoveSpeed attribute; the ground surface trace turns mud into an effect; the gym gets mud and ice patches - Combat: the attribute set moves to Stats; enemies and kits carry FStatBlockDefaults; the funnel reads MagicResist and Immune.Damage tags; taunt and mark are statuses - Interaction: carried weight is GE_Encumbered against CarryCapacity - Crafting: class bonuses are the WorkSpeed and WorkQuality attributes; enchantments may grant a holder effect - Steps: step 5 builds the block with mud, a volume and an immunity - Decisions D-37 and D-38; design summary, CLAUDE.md, indexes, worklog
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
# Unreal prototype
|
||||
|
||||
An Unreal Engine 5 project, C++ first, that builds and proves three things in order: a movement controller,
|
||||
fighting, and crafting. The engine version and the project name are open decisions (OD-01, OD-02 in
|
||||
[`Docs/Decisions.md`](Docs/Decisions.md)); pin both here in step 1. Until the `.uproject` exists, `<Project>`
|
||||
in the docs stands for its name.
|
||||
|
||||
## Before you start
|
||||
|
||||
Read [`Docs/Spec/README.md`](Docs/Spec/README.md) (the notation and the twelve cross-cutting rules) and
|
||||
[`Docs/Spec/Architecture.md`](Docs/Spec/Architecture.md) before writing or changing any code. Nearly every wiring
|
||||
decision follows from them.
|
||||
|
||||
For *what* to build next, read [`Docs/Steps.md`](Docs/Steps.md). It is a ladder, not a roadmap: each rung says
|
||||
what exists afterwards, links the spec sections that define it, and states the proof that closes it. The spec
|
||||
docs under `Docs/Spec/` are the specification; each is C++-shaped pseudocode plus the reasoning, and the
|
||||
pseudocode is meant to be implemented rather than admired. If a step and its spec disagree, the spec wins and the
|
||||
step is corrected.
|
||||
|
||||
The human summary is [`Docs/Design.md`](Docs/Design.md). Ideas from the two earlier projects that are not built
|
||||
are in [`Docs/Ideas.md`](Docs/Ideas.md); do not build one inside a step about something else.
|
||||
|
||||
## Where the project actually is
|
||||
|
||||
Nothing is built. The repository holds the documentation set and a stock `.gitignore`. Step 1 (the project, two
|
||||
modules, tests, a dedicated server in the editor) is next and waits on OD-01 and OD-02. Update this section when a
|
||||
step closes: what exists, what the next step is.
|
||||
|
||||
## Layout
|
||||
|
||||
Planned; created by step 1. The `.uproject` sits at the repository root, which is what the `.gitignore` assumes.
|
||||
|
||||
| Path | What it is |
|
||||
| --- | --- |
|
||||
| `<Project>.uproject`, `Source/<Project>.Target.cs`, `<Project>Editor.Target.cs`, `<Project>Server.Target.cs` | The project and its three build targets |
|
||||
| `Source/<Project>Core/` | Rules, data types, tags, the telemetry contract. Knows no `AActor`. Tests in `Tests/` |
|
||||
| `Source/<Project>/` | Gameplay: `Core/`, `Stats/`, `Movement/`, `Interaction/`, `Combat/`, `Crafting/`, `UI/`, one folder per feature |
|
||||
| `Content/<Feature>/` | Assets per feature: `Definitions/` for data assets, Blueprints, meshes, montages |
|
||||
| `Content/Maps/L_Gym` | The movement test level. Never leaves the project |
|
||||
| `Content/Tests/` | Functional test maps |
|
||||
| `Config/Tags/<Feature>.ini` | Gameplay tag source of truth, one file per top-level namespace |
|
||||
| `Scripts/` | `run-tests.sh`, `build.sh`, and `Authoring/` for editor Python scripts. Run by hand; there is no CI |
|
||||
| `Docs/` | The documentation set. Update it when you change how something works |
|
||||
| `Docs/Worklog.md` | One terse line per step closed or wall hit |
|
||||
|
||||
Module dependency is one way: `<Project>` depends on `<Project>Core`, never the reverse. If the core module seems
|
||||
to need an actor, the thing it needs is data.
|
||||
|
||||
## Conventions
|
||||
|
||||
- **The server decides.** Every mutation is a `Server*` path that validates its instigator and re-checks its
|
||||
preconditions, even in standalone play. The client predicts its own movement and its own animation, nothing
|
||||
else. Never write a listen-server assumption or an "is this the host" branch.
|
||||
- **Rules are pure C++ in the core module; actors and components adapt them.** A preview and the real thing call
|
||||
the same function. Every pure rule has automation tests, one per rejection reason.
|
||||
- **One funnel per consequence.** All damage through `UDamageExecution`; all interactions through
|
||||
`UInteractionSubsystem`; all activity quality through `FActivityResult`. Do not add a second path.
|
||||
- **One stat block; everything is an effect on it.** Every body has `UStatBlockAttributeSet`; every buff,
|
||||
debuff, surface, weight, gear and base value is a gameplay effect applied through `ApplyStatus` or
|
||||
`ApplyDefaults`. Never add a multiplier, a modifier list or a status timer to a component. Counters are
|
||||
`Immune.*` tags and resist attributes on the receiver. See `Docs/Spec/Stats.md`.
|
||||
- **Content is data.** `UPrimaryDataAsset` subclasses, addressed by gameplay tag or primary asset id, art through
|
||||
soft references. No string ids. If a variant needs code, the model is wrong.
|
||||
- **Gameplay Tags are the vocabulary.** Defined in `Config/Tags/`; tags code references are declared natively
|
||||
with `UE_DECLARE_GAMEPLAY_TAG_EXTERN` / `UE_DEFINE_GAMEPLAY_TAG`. No string literal tag at a call site.
|
||||
- **C++ owns rules, replicated properties and RPCs. Blueprints own composition, tuning and cosmetics.** A
|
||||
Blueprint is a child of a C++ class that sets assets and numbers and wires `*_Cosmetic` hooks.
|
||||
- **Lifetimes are the engine's.** Game-instance subsystems for the application, world subsystems for a map,
|
||||
the player state for a player across bodies, the pawn for a body. Nothing about a player that would matter
|
||||
tomorrow lives on the character actor.
|
||||
- **No world searches in gameplay code.** No `GetAllActorsOfClass`, no static gameplay singletons. Subsystems are
|
||||
the registries; actors register in `BeginPlay` and unregister in `EndPlay`.
|
||||
- **Every rejection carries a reason tag** a player can read. **Nothing is destroyed silently.**
|
||||
- **Emit telemetry through `UTelemetrySubsystem` with names from `TelemetryEvents`**, never a literal, never a
|
||||
static helper. Every feature emits; the catalogue in `Docs/Spec/Telemetry.md` and the feature spec agree.
|
||||
- **Naming is Unreal's:** `A`/`U`/`F`/`E`/`I` prefixes, no project prefix on classes, `PascalCase` members, asset
|
||||
prefixes `BP_ DA_ DT_ IA_ IMC_ GA_ GE_ GC_ ABP_ AM_ SK_ SM_ M_ MI_ WBP_ T_ L_`. Placeholders carry `_Proto`.
|
||||
- **Units are centimetres and seconds.** `FText` for anything a player reads. `TObjectPtr` for object properties.
|
||||
- **The design word "material" is "substance"** in code and docs. `UMaterial` is the engine's.
|
||||
- Do not add an interface, a module or an abstraction for one implementation with no test double. The two source
|
||||
projects were each trimmed of speculative abstraction once already; the interfaces that exist here are the ones
|
||||
with several implementations named in the specs.
|
||||
|
||||
## Commands
|
||||
|
||||
Created in step 1; the shapes are fixed now so the docs can refer to them.
|
||||
|
||||
```bash
|
||||
Scripts/run-tests.sh # headless: UnrealEditor-Cmd <Project>.uproject -ExecCmds="Automation RunTests <Project>; Quit" -unattended -nopause -NullRHI
|
||||
Scripts/run-tests.sh Core # a filter: <Project>.Core.*
|
||||
Scripts/build.sh Win64 Development # UAT BuildCookRun for the game target; add -server for the server target once the engine is a source build
|
||||
```
|
||||
|
||||
There is no CI, deliberately, and there will not be until there is a reason. Run the tests yourself before saying
|
||||
a step is done. Every replicated step is also played in the editor with two clients, "Run Dedicated Server" on, and
|
||||
network emulation at 100 ms and 5 % loss with `p.NetShowCorrections 1`.
|
||||
|
||||
## Working with the editor
|
||||
|
||||
- Prefer editing C++ and `.ini` files directly. Blueprints and maps are binary; keep them thin and keep logic out
|
||||
of them so a diff can be reviewed.
|
||||
- Batch authoring (a folder of definition assets, a greybox level from a table) goes through the editor's Python
|
||||
API in `Scripts/Authoring/`, idempotent, kept so it can be rerun. Do not hand-edit `.uasset` files.
|
||||
- Blueprint-side wiring an authoring script cannot express is done once in the editor and recorded in the step's
|
||||
worklog line so it can be redone.
|
||||
- Watch for the compile trap: a C++ error that stops the editor launching is fixed in the files, with the build
|
||||
output, not by guessing. The editor's Live Coding is fine for iteration and unreliable for header changes; a
|
||||
header change means a full rebuild.
|
||||
- Every `.uasset` and `.umap` goes through Git LFS; `.gitattributes` is created in step 1. Run `git lfs install`
|
||||
once per machine. Never commit `Saved/`, `Intermediate/`, `DerivedDataCache/` or `Binaries/`; the `.gitignore`
|
||||
covers them.
|
||||
|
||||
## Documentation upkeep
|
||||
|
||||
- A step that lands updates: its status in `Docs/Steps.md`; a **What was built, and where it differs** section at
|
||||
the end of the spec it built from; a line in `Docs/Worklog.md`; a line in `Docs/Decisions.md` if a decision was
|
||||
taken; and the "Where the project actually is" section above.
|
||||
- A design change that contradicts a decision names the decision it supersedes in the log. Never delete a log
|
||||
line.
|
||||
- Do not turn `Docs/Steps.md` into a roadmap. Detail the next step when the current one closes, not before.
|
||||
Reference in New Issue
Block a user