Files
UnrealPrototyping/Docs/Steps.md
T
Rainer LeitandClaude Fable 5.1 4f2c55cd2a Step 1: the Salty project, two modules, tests and the gym
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>
2026-09-16 20:10:43 +03:00

20 KiB
Raw Blame History

Steps

The order of work. Not a roadmap: there are no phases, no dates and no milestones, only a ladder of small proofs where each rung stands on the one below. The next few steps are written in full. The rest are sketches, and a sketch is detailed only when the step before it closes; anything written further ahead than that would be a plan pretending not to be one.

The Spec/ documents specify; this document schedules. A step links the sections it builds and never restates them. If a step and its spec disagree, the spec wins and this file is corrected.

The rules of the ladder

  • A step is closed by a proof. A test passes, a checklist is ticked, a person performs the sentence. "Implemented" closes nothing.
  • A step lands with its docs. The spec's "what was built, and where it differs" section, a decision-log line if a decision was taken, a worklog line, and the status here, all in the same change.
  • A step that cannot be closed by one proof is two steps. A step under half a day belongs inside a neighbour.
  • Ids are stable. A dropped step is struck through with a reason, never renumbered.
  • Every replicated step is proved with a dedicated server in the editor and network emulation at 100 ms and 5 % loss. See Networking.md.

Status: ☐ not started · ◐ in progress · ☑ done · ⊘ dropped · ⏸ blocked (names the blocker)

The ladder

# Step Proves Status
1 The project, two modules, tests, a dedicated server in the editor It builds, tests run headless, and the server posture is real from the first commit ◐
2 The telemetry seam Every later step can emit into something ☐
3 A body, a camera, an input map and the gym You can walk, in both camera modes, on a server ☐
4 The feel pass The written checklist passes with a person at the keyboard ☐
5 The ability foundation and the stat block, proved on dodge, blink and mud Abilities predict cleanly, and every body has the one set of numbers every effect changes ☐
6 Interaction and carrying One system touches every prop; things you hold are real ☐
7 The swing One damage funnel, one weapon, one dummy, and it feels good to hit ☐
8 The goblin An enemy with a tell, going down, being revived, a wipe ☐
9 Kits and the action bar Two classes with a signature each, and the bar tells the truth about keys ☐
10 The second pair of kits Parity: four kits finish the same fight ☐
11 Projectiles Aim matters at range ☐
12 Crafting data and rules The pure rules pass their tests; the reference example composes ☐
13 The bench You assemble a sword, equip it, and its quality changes the damage number ☐
14 Substances and the forge Ore becomes ingot through a bed you tend, and walking away is not an exploit ☐
15 The anvil and enchanting Ore to ingot to blade to a sword of embers, all physical, all server-validated ☐

What comes after fifteen is decided when fifteen closes. The candidates are in Ideas.md.


1 · The project, two modules, tests, a dedicated server in the editor

Build: The .uproject at the repository root; runtime modules <Project>Core and <Project> with the folder layout from Architecture; the Server build target; plugins enabled: Gameplay Abilities, Gameplay Tags, Enhanced Input, CommonUI, StateTree; Config/Tags/ with the top-level namespaces; the asset manager configured for the definition types; Scripts/run-tests.sh and Scripts/build.sh; .gitattributes with Git LFS for binary assets; one placeholder automation test in Core; an empty L_Gym. Spec: Architecture.md (modules, content, tags, testing, conventions). Depends: the open decisions on engine version and project name (Decisions.md, OD-01, OD-02). Done when: the editor opens the project with no warnings about the modules; Scripts/run-tests.sh runs the placeholder test headless and reports green; Play In Editor with two clients and "Run Dedicated Server" puts two default pawns in the empty gym; git lfs ls-files lists the first .uasset. Notes: The launcher build of the engine cannot compile a packaged server; the editor's dedicated-server option is the everyday test and is enough until a packaged build is wanted. Do not create a third module, a plugin or an editor module here; each arrives when something needs it.

2 · The telemetry seam

Build: ITelemetrySink with the null, log and JSON Lines sinks; FTelemetryEvent and the envelope; the event-name constants; UTelemetrySubsystem on the game instance; app_started, session_started, session_ended, cheat_used; the session id minted by the game mode and adopted by clients through the game state. Spec: Telemetry.md. Depends: 1. Done when: launching the gym with -telemetry produces Saved/Telemetry/session_*.jsonl with app_started then session_started, every line parses, and a client's file carries the server's session id; the sink test passes; bs.TelemetryTest emits an event and sets cheats_used. Notes: Before the character, deliberately. Adding emit calls to a system that already exists means reading it again and guessing what mattered; adding them as it is written costs a line.

3 · A body, a camera, an input map and the gym

Build: ABaseCharacter, APlayerCharacter, UExtendedCharacterMovement with the sprint flag in the saved move, UMovementTuning and DA_Tuning_Player; IMC_Gameplay and every IA_* from the layout table, bound by asset; FLookModel in Core with its tests; UCameraModeDefinition for both modes and UCameraModeComponent with the stabilised head socket, the hidden local head, the spring arm; RemoteHeadYaw; the mannequin with the template's animation blueprint; the gym greyboxed to the section table; the first movement_sample, jump, land and camera_mode_changed events; the Settings widget's comfort sliders (FOV, stabilisation, bob, motion scale) and the rebind rows through the engine's user settings. Spec: Movement.md (input, the character, the look model, camera modes, the gym), UI.md (the menu and settings). Depends: 2. Done when: the whole gym can be walked in both camera modes; the five look-model tests pass; two clients and a dedicated server at 100 ms and 5 % show smooth remote bodies and no visible local correction with p.NetShowCorrections 1; a rebind of Interact survives a restart; every setting change emits settings_changed. Notes: Sprint is a compressed flag, not an ability. Coyote time and the jump buffer are in this step because they are the movement component's business; the feel numbers are guesses until step 4. GetMaxSpeed already multiplies by the MoveSpeed attribute, which reads as one until the stat block exists in step 5. The head is hidden by bone on the local pawn only; this is the rigging rule that everything head-worn must parent to the head bone.

4 · The feel pass

Build: Nothing new. The tuning asset's numbers, the gym's sections adjusted where they lied, the three gym functional tests (FT_Gym_Stairs, FT_Gym_Slopes, FT_Gym_Gaps), and a worklog entry with the numbers that made the checklist pass and the ones that did not. Spec: Movement.md, The feel checklist. Depends: 3. Done when: every line of the checklist is ticked in both camera modes by a person at the keyboard, the three functional tests pass, and DA_Tuning_Player is committed with the values that did it. Notes: This is the step most likely to be called done early. It is not done while any line says "mostly". Q1 in Movement (the default camera mode) is not decided here; both modes must pass.

Build: UExtendedAbilitySystemComponent on the player state with input-tag routing, ApplyDefaults and ApplyStatus; InitAbilityActorInfo on both sides; UStatBlockAttributeSet with every attribute in the table and its clamps, FStatBlockDefaults and GE_InitStats; the movement component reading MoveSpeed and JumpPower; GE_Status_Slow, GE_Status_Haste, GE_Immune_Slow, GE_Cleanse with their cues and the Status.*, Immune.* and Surface.* tags; AEffectVolume; UPhysicalMaterialWithTags, the ground trace and the gym's mud and ice patches; UKitAbility; GA_Dodge (constant-force root motion, State.Invulnerable for its duration, a cooldown effect) and GA_Blink (move-to-force along the aim, flattened); IA_Dodge and IA_Ability1 routed by tag; the HUD's status icon row; ability_used, status_applied, status_blocked, status_removed. Spec: Stats.md, Combat.md (abilities and kits), Movement.md, Hooks for other systems. Depends: 4. Done when: dodge and blink activate on their keys, predict locally and show no visible correction at 100 ms and 5 %; a dodge through a debug damage volume takes no damage during its i-frames and damage after; walking onto the mud slows the body to the surface's magnitude and the icon appears, walking off restores it; a slow volume over the mud does not stack with it but a haste multiplies against it; bs.ApplyStatus Slow 0.5 10 then bs.GrantImmunity Slow shows the blocked cue on the next application; the resistance and carry-factor tests and the attribute clamp test pass; bs.GrantAbility, bs.ApplyStatus, bs.GrantImmunity and bs.Cleanse exist. Notes: GAS proves itself on movement before combat depends on it, because a movement ability that snaps is the most visible thing prediction can get wrong. The stat block lands here rather than in combat so that the first debuff, the first patch of ground and the carry penalty in step 6 all arrive on a system that exists, instead of each growing a field of its own. UAbilitySystemGlobals::InitGlobalData is called from the asset manager's startup or target data will not serialise; it is the classic first-day GAS bug.

6 · Interaction and carrying

Build: IInteractable, FInteractionPrompt, UInteractionSubsystem with the server funnel, UInteractionComponent with the camera sweep, the highlight sweep and hold-to-confirm; the Interactable and Carryable trace channels; UInteractionHighlightComponent and the outline post-process; WBP_InteractionPrompt reading the live glyph; ICarryable, UCarryableComponent, UCarryComponent with hands, pick up, drop, throw (G tap and hold) and hand over; State.Carrying tags blocking the right verbs; GE_Encumbered from CarryMath::SpeedFactor against CarryCapacity; two test props in the gym (a lever that toggles a light, a crate that is two-handed); all six interaction events. Spec: Interaction.md. Depends: 5. Done when: the lever's prompt appears only in reach and greys with a reason from too far; a request forged from beyond reach is refused and emits interaction_refused; the crate blocks the lever with "hands full"; a throw lands within tolerance on two clients; the outline shows which of two crates the press will take; the interaction functional test passes. Notes: Before combat, because a downed teammate is a prop and reviving must not invent a mechanism. Pick-up is routed by the interaction component but is not an interaction; the boundary is in the spec and it is load-bearing.

7 · The swing

Build: DamageMath and its tests; UDamageExecution, GE_Damage and the friendly-fire gate through IGenericTeamAgentInterface; FWeaponProfile, UWeaponDefinition, AWeaponActor on the hand socket; GA_MeleeAttack with the montage, ANS_HitWindow, the server box sweep and the line-of-sight check; the hit cues: impact, client-side hit stop, camera kick, the procedural hit reaction; GE_FallDamage from Landed; a training dummy in the arena with a health number over it; the health HUD; player_damaged, enemy_damaged. Spec: Combat.md (the damage funnel, weapons and the melee swing). Depends: 6. Done when: the dummy takes exactly the profile's damage once per swing and none through a wall; a swing at another player moves them and costs no health, and the functional test asserts both; a 500 cm drop damages; hit stop never runs on the server; and a person says hitting the dummy feels good and writes the numbers down. Notes: Combat feel is the honest unknown of the whole ladder. Budget iteration here and do not move on until one dummy is satisfying. The box after the wind-up is the shipped shape; a blade sweep is the fallback in Q4.

8 · The goblin

Build: UEnemyDefinition with its BaseStats and DA_Enemy_Goblin; AEnemyCharacter with its own ability system and the same stat block, so the mud from step 5 slows it; AEnemyController with perception, the StateTree (idle, chase, attack with wind-up, flee, leash) and FThreatTable; UCombatantComponent with the downed state, GA_Downed, GE_BleedOut, the revive prop, GA_Revive, the solo-down-is-a-wipe rule; UEncounterSubsystem spawning from spawn points in the arena with the sublinear table; respawn on wipe; every remaining combat event. Spec: Combat.md (health, down, revive, wipe; enemies). Depends: 7. Done when: three goblins fight one player and the tells can be read and stepped out of; a goblin chasing across the mud is slowed exactly as the player is; a downed player is revived through the interaction path by a second client; alone, a down is an immediate wipe and the arena resets; the goblin flees for its flee time when an ally dies; the threat table tests pass. Notes: The enemy uses the same melee ability class as the player on its own component. Numbers are the earlier project's guesses (30 health, 8 damage every 1.2 s with a 0.35 s wind-up) until they are not.

9 · Kits and the action bar

Build: UClassKitDefinition with BaseStats, GrantKit; the Warrior (Taunt as Status.Debuff.Taunted, Shoulder charge) and the Cleric (Mass heal, Mend with the overheal launch, faster revive); bs.SetClass; cooldown effects per ability; WBP_ActionBar with live glyphs and cooldowns; grief_action from the charge and the launch. Spec: Combat.md, Abilities and kits, UI.md, The HUD. Depends: 8. Done when: a taunt pulls every goblin in range off a teammate; a charge damages goblins and launches a teammate without damaging them; an overheal launches; the bar shows the rebound key after a rebind; the kit validation test refuses a second signature. Notes: Class lives on the player state and survives a body dying. The recoverability contract applies to the charge and the launch from the first day they exist: the victim's recovery is standing up and walking back.

10 · The second pair of kits

Build: The Rogue (Backstab, Shadowstep) and the Mage's Blink promoted into a kit with a placeholder second ability; the parity measurement: enemy_killed.killer_class against time_to_kill_s over a scripted arena wave. Spec: Combat.md, Abilities and kits. Depends: 9. Done when: four kits each clear the same three-goblin wave solo within a spread the worklog records; no ability writes health directly (grep the abilities for the attribute setter and find nothing). Notes: Sketch. Detail when 9 closes.

11 · Projectiles

Build: AProjectileActor, the local cosmetic copy on activation, GA_Volley and the bow profile, GA_Fireball for the Mage; the Ranger kit (Mark, Volley). Spec: Combat.md, Projectiles. Depends: 10. Done when: an arrow leaves the hand without a visible round trip at 100 ms, drops over distance, and damages through the funnel with Damage.Source.Projectile; a marked goblin takes more from a teammate's sword. Notes: Sketch.

12 · Crafting data and rules

Build: Every definition class, FPieceInstance, FAssembledItem, FSubstanceInstance, UCraftingConfig; the Item.*, Piece.*, Substance.*, Theme.*, Domain.* and Craft.Reason.* tags; every pure rule in CraftingRules with its named tests; DA_Family_Sword with the four piece types, four characteristics, iron and oak, two enchantments; bs.SpawnPiece. Spec: Crafting.md (data, runtime state, the pure rules). Depends: 6 (for the carryable piece actor). Can run in parallel with 7 to 11 in the core module. Done when: the reference example composes to Blunt Mithril Sword of Embers; every rejection reason has a passing named test; a guardless sword validates; the durability case computes; MakeWeaponProfile lands a mid-quality crafted sword inside the authored sword's numbers. Notes: Sketch. The rules exist before a bench does, on purpose: word salad in names is the risk that surfaces earliest this way.

13 · The bench

Build: AStationActor, AAssemblyBench with staging, the live preview on a world-space panel laid out by the family's grid, and the commit through ServerAssembleItem; AAssembledItemActor built from resolved attach chains on named sockets, tinted by substance; the Equip verb and the crafted weapon in hand; item_assembled, assembly_rejected, weapon_equipped. Spec: Crafting.md, Stations, From item to weapon. Depends: 12, 7. Done when: three spawned pieces are inserted, the panel shows the name and the synergy segment, the commit produces the sword on the bench, a second client sees the blade on the handle's socket, equipping it changes the damage number on the dummy in proportion to coherence, and a piece the slot rejects shows the reason. Notes: Sketch. This is the step where crafting and fighting meet; it is why both exist.

14 · Substances and the forge

Build: ASubstanceActor as a carryable; the raw-to-processed pipeline; UActivityRuntime, UActivityDefinition, FActivityResult; AForgeStation with the 5×5 bed, the near simulation at 10 Hz, the far lumped model, the collapse and expand, the equivalence harness; place, rake, fuel and bellows inputs; the bellows as a second operator; the substance spawner cheat. Spec: Crafting.md, Substances, Activities. Depends: 13. Done when: iron ore in the bed becomes an ingot with a quality that reflects time in the working band; a second client on the bellows raises the ceiling; walking away and coming back yields the same ingot as staying, within the harness's tolerance; the output buffer full stalls the forge and says so. Notes: Sketch. The heat harness is written with the first heat code, not after.

15 · The anvil and enchanting

Build: AAnvilStation and the strike activity with the growing zone, the temperature gate, the timestamped strike, two strikers alternating; characteristic discovery on the player state; an enchanting station and ServerApplyEnchantment; the enchantment reaching the weapon profile's damage type and granted tags. Spec: Crafting.md, Activities, Enchanting. Depends: 14. Done when: ore to ingot to blade to a sword of embers, all physical objects, all server-validated, in one session with two clients; a strike at 150 ms of emulated latency scores the same as one at 0 ms pressed at the same moment; with no input the anvil completes at the baseline score; the enchanted sword's hit carries Item.Enchant.Fire.Penetration into the funnel. Notes: Sketch. When this closes, the three feels exist, and what to build next is a decision taken then, from Ideas.md, with the telemetry of fifteen steps to take it with.