Show the fight that was already happening #43

Merged
icub3d merged 1 commit from 9-outcome-playback into main 2026-09-14 00:39:17 +00:00
Owner

CLAUDE.md § Separation of Simulation and Presentation promised that resolution computes a
whole outcome and animation catches up afterwards. The first half has been true since #7.
This is the second.

It corrects something I had recorded wrong

I wrote in CLAUDE.md that "a unit teleports to where it walked". It does not. Nothing
ever moved a unit's Transform when its GridPos changed, so a walk was invisible —
the hostiles have been closing across the board since #11 and every screenshot this session
showed them standing exactly where they spawned. Playback is not polish here; it is the
only thing that moves a body.

The dependency points one way and is made to show it

src/playback/ is its own module rather than a corner of combat or world: it reads
both and neither reads it. Combat waits on StillShowing, which combat owns and playback
sets
, so a mission run with no playback behaves exactly as it did before.

Two tests hold that down:

  • showing_a_mission_does_not_change_it fights the same mission with and without the
    plugin and compares the reports.
  • playback_never_writes_what_the_simulation_owns runs frames in which only playback is
    live and asserts no cell and no hit point moved.

Acceptance criteria

  • Nothing in this module writes simulation state — it reads outcomes and moves
    Transforms, asserted by the test above.
  • With playback disabled or skipped the mission still resolves — every other
    headless test in the repo runs without the plugin and still passes; PlaybackSpeed(0)
    skips instantly, and a test uses it.
  • Animation speed is a single tunable value — theme::tempo::STEP_SECONDS is how
    long a unit takes to cross one tile, and everything else derives from it.
    PlaybackSpeed scales the lot.
  • Damage text uses theme typography — type_scale::SUBHEADING and
    semantic::WARNING.

What it shows

A walk interpolates along the route resolution already chose — the whole path is in the
outcome, so playback follows it rather than guessing. A hit floats its damage above the
target and fades. A unit that falls settles where it stood and stays there, because nothing
despawns the fallen and #12 leaves them on the map.

Also

Restores the src/game/ section of § Current Status, which #12 should have carried — the
script that wrote it aborted on a later edit before saving, and I only noticed while adding
playback beside it.

Verification

cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo test —
205 passing, up from 196.

On a Pixel Tablet: an enemy cone has advanced from the back row onto the centre platform.
Every earlier screenshot in this session had both cones frozen at their spawns.

Closes #9

🤖 Generated with Claude Code

https://claude.ai/code/session_01TA4hJHkRSU3XBxZYtMKXdh

`CLAUDE.md` § Separation of Simulation and Presentation promised that resolution computes a whole outcome and animation catches up afterwards. The first half has been true since #7. This is the second. ## It corrects something I had recorded wrong I wrote in `CLAUDE.md` that "a unit teleports to where it walked". It does not. Nothing ever moved a unit's `Transform` when its `GridPos` changed, so **a walk was invisible** — the hostiles have been closing across the board since #11 and every screenshot this session showed them standing exactly where they spawned. Playback is not polish here; it is the only thing that moves a body. ## The dependency points one way and is made to show it `src/playback/` is its own module rather than a corner of `combat` or `world`: it reads both and neither reads it. Combat waits on `StillShowing`, which **combat owns and playback sets**, so a mission run with no playback behaves exactly as it did before. Two tests hold that down: - `showing_a_mission_does_not_change_it` fights the same mission with and without the plugin and compares the reports. - `playback_never_writes_what_the_simulation_owns` runs frames in which only playback is live and asserts no cell and no hit point moved. ## Acceptance criteria - [x] **Nothing in this module writes simulation state** — it reads outcomes and moves `Transform`s, asserted by the test above. - [x] **With playback disabled or skipped the mission still resolves** — every other headless test in the repo runs without the plugin and still passes; `PlaybackSpeed(0)` skips instantly, and a test uses it. - [x] **Animation speed is a single tunable value** — `theme::tempo::STEP_SECONDS` is how long a unit takes to cross one tile, and everything else derives from it. `PlaybackSpeed` scales the lot. - [x] **Damage text uses `theme` typography** — `type_scale::SUBHEADING` and `semantic::WARNING`. ## What it shows A walk interpolates along the route resolution already chose — the whole path is in the outcome, so playback follows it rather than guessing. A hit floats its damage above the target and fades. A unit that falls settles where it stood and stays there, because nothing despawns the fallen and #12 leaves them on the map. ## Also Restores the `src/game/` section of § Current Status, which #12 should have carried — the script that wrote it aborted on a later edit before saving, and I only noticed while adding playback beside it. ## Verification `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`, `cargo test` — **205 passing**, up from 196. On a Pixel Tablet: an enemy cone has advanced from the back row onto the centre platform. Every earlier screenshot in this session had both cones frozen at their spawns. Closes #9 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01TA4hJHkRSU3XBxZYtMKXdh
`CLAUDE.md` § Separation of Simulation and Presentation promised that resolution
computes a whole outcome and animation catches up afterwards. The first half has
been true since #7. This is the second.

It corrects something I had written down wrong. A unit's `GridPos` changed and
nothing ever moved its `Transform`, so a walk was not a teleport — it was
invisible. The hostiles have been closing across the board since #11 and every
screenshot showed them standing exactly where they spawned.

`src/playback/` is its own module rather than a corner of `combat` or `world`,
because the dependency has to point one way and be seen to: playback reads both
and neither reads it. Combat waits on `StillShowing`, which combat owns and
playback sets, so a mission run with no playback at all behaves exactly as it did
before — which is every headless test, and which one test asserts directly by
fighting the same mission with and without playback and comparing the reports.
Another runs frames in which only playback is live and asserts no cell and no hit
point moved.

Timing comes from one number. `theme::tempo::STEP_SECONDS` is how long a unit
takes to cross a tile and everything else is derived from it, so making the game
feel quicker is a single edit rather than a hunt through durations.
`PlaybackSpeed` scales all of it and zero means instant, which is how a test
watches a mission resolve without waiting for it.

A walk interpolates along the route resolution already chose — the whole path is
in the outcome, so playback follows it rather than guessing at it. A hit floats
its damage above the target in `theme` type and fades. A unit that falls settles
where it stood and stays there, because nothing despawns the fallen and #12
leaves them on the map.

Also restores the `src/game/` section of § Current Status, which #12 should have
carried: the script that wrote it aborted on a later edit and I only noticed
while adding playback beside it.

Closes #9

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TA4hJHkRSU3XBxZYtMKXdh
icub3d merged commit c7eef4358e into main 2026-09-14 00:39:17 +00:00
icub3d deleted branch 9-outcome-playback 2026-09-14 00:39:18 +00:00
Sign in to join this conversation.
No description provided.