The roadmap was actively misleading: an earlier stage renumbering left Stage 2's completed work sitting under a "Stage 3 -- todo" heading, and the inventory table orphaned with no heading at all. Since that file is the primary handoff document, a fresh session would have started by re-implementing accounts and characters. Rewritten. Captured in full, from the original brief rather than from memory, the two stages not yet built: - Stage 3 (inventory and loot): 4 slots, potions rare from trash and guaranteed from bosses, world-shared loot, and the player-instanced food item -- with a note that two loot visibilities must exist from the start, because proving the instanced path works is the food item's entire purpose. - Stage 4 (upgrades): every upgrade with its exact stated effect, plus the two constraints it will collide with -- sniper's 2x bullet speed against the tunnelling threshold, and per-player bullet travel against the interest radius that test_interest.gd currently derives from static content. Ten open questions are listed as explicitly do-not-guess, seven of them blocking Stage 4. ARCHITECTURE.md still claimed "the arena is a rectangle" and "no tilemap collision", both untrue since Stage 1. Rewritten around MapGrid, with what the grid costs (axis-aligned, 32px-quantised, bullets under a tile per tick) rather than only what it buys. Added a "verification traps" section to WORKFLOW.md recording six mistakes made during this work, each of which cost a round trip of reporting something fixed that was not: verifying the artefact rather than the behaviour, dumping the wrong channel, measuring a configuration where the bug cannot exist, a test whose setup silently invalidated it, git checkout reverting real work alongside a probe, and a pattern edit matching in two files. They are specific enough to be actionable. Also documented the diagnostics in CLAUDE.md -- they were undiscoverable -- and recorded the current verification surface so "everything passes" has a stated meaning. 206 tests, 15 smoke assertions, both diagnostics pass.
This commit is contained in:
+62
-2
@@ -106,10 +106,16 @@ Runs the simulation with no transport and no rendering and prints milliseconds
|
||||
per tick. Current numbers, 4 players, 60s of play:
|
||||
|
||||
```
|
||||
boss fight 0.237 ms/tick peak 352 bullets headroom x70
|
||||
trash wave 0.179 ms/tick peak 145 bullets headroom x93
|
||||
boss fight 0.26 ms/tick peak ~300 bullets headroom x64
|
||||
trash wave 0.16 ms/tick peak ~82 bullets headroom x103
|
||||
```
|
||||
|
||||
Note what this does *not* cover: it measures the simulation only, with no
|
||||
transport. Per-peer snapshot encoding was measured separately (95.6us for four
|
||||
filtered snapshots against 24.9us for one shared, or 0.032 ms/tick amortised).
|
||||
Quoting a bench number for something the bench does not exercise is its own
|
||||
version of the trap below.
|
||||
|
||||
A 60 Hz tick has a 16.6 ms budget, so one instance uses ~1.4% of one core. That
|
||||
is the measurement that says a single server process can host dozens of
|
||||
concurrent dungeons, and it took ten seconds to get because the simulation has
|
||||
@@ -164,6 +170,60 @@ auto-reload racing against agent file writes is a known source of stale state.
|
||||
The honest recommendation: **start with the headless CLI loop, add the MCP when
|
||||
scene inspection becomes the bottleneck.** It is one command to add later.
|
||||
|
||||
## Verification traps, learned the hard way
|
||||
|
||||
Every entry below cost at least one round trip of reporting something as fixed
|
||||
when it was not. They are specific to this setup, and they generalise.
|
||||
|
||||
### Verify the behaviour, not the artefact you produced
|
||||
|
||||
Bullet sprites were reported fixed three times before they were. Each time the
|
||||
*atlas* was checked and found correct. The actual fault was one layer down: a
|
||||
`MultiMeshInstance2D` cannot display a sub-region of a texture, so the renderer
|
||||
was structurally incapable of showing one cell no matter what the atlas held.
|
||||
Checking the input to a broken stage will confirm the input forever.
|
||||
|
||||
Ask what the *player* would see, and find a way to check that.
|
||||
|
||||
### An ASCII dump only proves what you dumped
|
||||
|
||||
The same sprites were "verified" by rendering their alpha channel. The pack
|
||||
animates as a colour shimmer over a fixed silhouette, so identical-looking
|
||||
frames proved nothing at all. The check was real; it was measuring the wrong
|
||||
channel.
|
||||
|
||||
### Measure the configuration that has the bug
|
||||
|
||||
The client-prediction gap was measured at 0.1px on a listen server and reported
|
||||
fixed. A listen server runs both halves in one process on one physics tick, so
|
||||
the quantity that had drifted — two independent clocks — was constant *by
|
||||
construction*. The bug needed two processes to exist at all.
|
||||
|
||||
If a bug is described as appearing "after a while", ask what accumulates, and
|
||||
make sure your harness lets it accumulate.
|
||||
|
||||
### Have the test tell you the setup was valid
|
||||
|
||||
`test_firing_is_reported_even_when_the_bullet_dies_instantly` asserts that no
|
||||
`BULLET_SPAWN` event occurred, purely to prove the interesting condition was
|
||||
actually reached. The first version placed a turret as the victim — and turrets
|
||||
shoot, so the assertion passed on the turret's own bullets while testing
|
||||
nothing. A setup check inside the test caught it.
|
||||
|
||||
### `git checkout` to clean up a probe reverts real work too
|
||||
|
||||
A one-file revert to remove a temporary debug hook also discarded a fix made to
|
||||
the same file earlier in the session, and it had already been verified and
|
||||
reported. Only `git status` showing the file *missing* from the staged set
|
||||
caught it. Prefer editing the probe back out, or stash.
|
||||
|
||||
### A pattern-based edit can match twice
|
||||
|
||||
A `replace()` intended for the snapshot decoder also matched inside the
|
||||
character decoder, which then read a field its encoder never wrote. Both are
|
||||
codecs and both had the same trailing lines. Check the match count when patching
|
||||
by pattern, not by line.
|
||||
|
||||
## CI
|
||||
|
||||
`.github/workflows/ci.yml` runs check → test → smoke on a container image with
|
||||
|
||||
Reference in New Issue
Block a user