Salty.uproject (UE 5.8) from the Third Person template with class redirects, the SaltyCore and Salty modules, the three build targets, USaltyAssetManager calling InitGlobalData, Config/Tags with the 23 root namespaces, Scripts/run-tests.sh, build.sh and Authoring/create_gym.py, L_Gym, Git LFS attributes and the placeholder test. Template variants kept as reference (D-41). The four Fab packs stay out of the repository for now (~10 GB). The editor serves the engine MCP plugin on 127.0.0.1:8000 through DefaultEditorPerProjectUserSettings.ini. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Specifications
These documents are written for an implementer that reads carefully and lifts code, which today means Claude and
whoever reviews its pull requests. They are not the human summary; that is ../Design.md. Read the
summary first, then Architecture.md, then the document for the system you are touching.
Each spec is pseudocode plus the reasoning behind it. The pseudocode is C++-shaped Unreal code and it is meant to be
implemented, not admired: a class skeleton here is the class we expect to find in Source/, with the same name, the
same members and the same comments about why. Where a spec says // pure, that is a promise about testability the
real code is expected to keep.
Documents
| Doc | Owns | Read when |
|---|---|---|
| Architecture.md | Modules, lifetimes, authority, data assets, gameplay tags, the C++/Blueprint boundary, testing, conventions | Before writing any code |
| Stats.md | The one stat block every body carries, effects and their sources (abilities, areas, surfaces, items, the world), stacking, counters | Step 5, and anything that changes a number on a body |
| Movement.md | The character, the movement component, the look model, camera modes, input, the gym level | Steps 3 to 5 |
| Interaction.md | The one interaction system every prop uses, carrying, throwing | Step 6, and any prop |
| Combat.md | Attributes, the one damage funnel, melee hit detection, enemies, abilities, kits, downed and revive | Steps 7 to 11 |
| Crafting.md | Families, parts, pieces, traits, substances, the pure rules, stations, activities, the link back to combat | Steps 12 to 15 |
| Networking.md | The authority model, the responsiveness tiers, prediction posture, persistence and identity seams | Any replicated feature |
| Telemetry.md | The sink, the envelope, the event catalogue | Any feature (every feature emits) |
| UI.md | HUD, prompts, the theme asset, world-space text, localisation | Any screen or prop text |
Notation
Server*(...) runs on the authority only. Validates the instigator and re-checks every precondition,
then mutates. A UFUNCTION(Server, Reliable) RPC or a plain method guarded by HasAuthority().
Get* / Preview* side-effect-free query. Safe to call from UI every frame, on any peer.
// pure deterministic, no world access, unit-tested, reused by the client preview and the server verdict.
On* (delegate) a multicast delegate a service raises. UI subscribes; UI never polls.
*_Cosmetic a BlueprintAssignable hook a designer may wire. Never load-bearing.
UFooDefinition a UPrimaryDataAsset, authored content.
FFooInstance runtime state with identity (an FGuid).
FFooRecord the persisted snapshot of an instance. Separate type from the instance on purpose.
TAG_Foo_Bar a native gameplay tag, "Foo.Bar", declared once in code. Never a string literal at a call site.
Status markers inside a spec:
[DECIDED] settled, with the reason. Logged in ../Decisions.md.
[PROPOSED] the recommended shape, not yet built on. Becomes [DECIDED] when the step that builds it closes.
[SALVAGED] an idea carried over from the two earlier projects, reshaped for this one. See ../Ideas.md.
Qn an open question, collected at the end of each document.
Cross-cutting rules
Every spec obeys these. They are stated once here so no spec has to restate them, and any spec that appears to break one is wrong.
- The server decides. The client asks, and predicts only its own body. Every mutation has an explicit instigator and is re-validated on the authority, even when the game is being played alone. Standalone and listen-server play are development conveniences; the dedicated server is the real target and nothing may assume it is absent. See Networking.md.
- One formula, many consumers. A preview and the real thing call the same pure function. Damage, coherence, naming, assembly validation, spec matching and interaction prompts all follow this. If a UI-side "fast copy" of a rule ever appears, it is a bug.
- One funnel per kind of consequence. All damage goes through one execution. All interactions go through one service. All activity quality goes through one result type. A second path is where rules quietly diverge.
- Rules are plain C++ in the core module; actors and components adapt them to the world. The core module knows
no
AActor. This is what makes rules testable without a level and runnable on a headless server. - Content is data, addressed by gameplay tag or primary asset id, never by string. A new sword, enemy or substance is an asset, not a code change. If adding a variant needs code, the model is wrong.
- Every rejection carries a reason a player can read. A refused interaction, an illegal assembly, a blocked ability: each returns a reason tag that the prompt or panel shows. Silent refusal is a failed step.
- Nothing is destroyed silently. Dropped things persist on the floor, full containers refuse with a reason, thrown things land where they land. The rare deliberate deletion is announced before it happens.
- Bonuses, never locks. A class, a level or a piece of gear changes speed, quality or numbers. It never changes what a station accepts or what an assembly permits, and it never grants another class's signature ability.
- Solo viability is a gate, not a mode. Every rule is checked against one player. Where it costs a solo player something, the spec says so and names the tuning knob.
- Derived state is recomputed on load, never trusted from storage. A rebalance re-scores old items everywhere.
- Telemetry from the first line. Every feature emits through the injected sink into a catalogue that exists before there is anyone to measure. Retrofitting emit calls is the tax this avoids.
- Determinism where it is promised. Anything generated from a seed uses its own
FRandomStream, integer coordinates and ordered collections, and never reads physics, time or frame counts. Nothing today is generated from a seed; the rule exists so the first thing that is inherits it. - One stat block; every outside influence is an effect on it. Every body carries the same attribute set. A buff, a debuff, ground, weight, gear and base values are all gameplay effects applied to it; counters are tags and attributes on the receiver. No system keeps its own multiplier. See Stats.md.
How a spec relates to the steps
../Steps.md schedules; these documents specify. A step links to the sections it builds and states
what proves it done. If a step and its spec disagree, the spec wins and the step is corrected. A spec that turns out
wrong in the building is corrected in the same pull request as the code, under a What was built, and where it
differs heading at the end of the affected document, so the next reader is not misled by the sketch.