Files
UnrealPrototyping/CLAUDE.md
T
Moto 0e61a77346 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
2026-09-15 17:54:00 +03:00

8.3 KiB

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); 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 (the notation and the twelve cross-cutting rules) and 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. 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. Ideas from the two earlier projects that are not built are in 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.

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.