Test the wands, staffs and bolt geometry of sticks.c (closes #6)
game/sticks.go was the largest under-tested file in the repo: 534 lines, 23 functions and a single test. It now has two test files, both written against the C reference (git show origin/c-master:sticks.c) rather than against the current Go code, so they can catch divergence instead of recording it. game/sticks_test.go covers every zap handler that had none — light in a room and in a corridor, drain-life's too-weak refusal (which returns before o_charges--), drain's hit-point split and its kill arm, drainReaches for all three of C's clauses, invisibility and the flytrap release, polymorph's detach/re-attach dance with the pack, under- character and delta-clobbering it does on the way, cancellation, both teleport wands, magic missile, haste/slow in both directions, fix_stick's damage and charge formulas, and charge_str. game/bolt_test.go covers fire_bolt: dirch for all eight directions, boltBounces including the door the hero stands on, an end-to-end flight asserting the path and resting square, bounces off both wall orientations, off a corner and diagonally off a wall (which pins C's rule that a bounce negates both components rather than reflecting), a bounced bolt striking the hero who fired it, the strike and miss arms, and the dragon that shrugs off a flame but not a lightning bolt. The tests read the flight path off the screen: fire_bolt paints its trail and then paints chat() back over every square it recorded, so on an otherwise blank screen the non-blank cells are exactly the squares the bolt occupied, and the walls it bounced off are absent because C undoes the record before the mvaddch. Determinism comes from a pinRng helper that searches for a seed whose next draw is the wanted value, and from a level the tests carve themselves with the generator's own drawRoom. The hero is fortified wherever a bolt can reach him, since death exits the process. No divergence from C was found. Two notes are recorded in the test comments: fire_bolt's "ch != 'M'" guard is a tautology, because winat is t_disguise whenever a monster stands there, and the door-under-hero exception can only be tested by the fact that the run terminates.
This commit is contained in:
35
TODO.md
35
TODO.md
@@ -34,6 +34,41 @@ wizard commands).
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-08-09 Wands and staffs under test (`test/sticks-coverage`, closes #6):
|
||||
the second of the three thin spots the Next Step names. `game/sticks.go` was
|
||||
the largest under-tested file in the repo — 534 lines, 23 functions, one test
|
||||
— and now has `game/sticks_test.go` (the zap handlers, `drain`, `fix_stick`,
|
||||
`charge_str`) and `game/bolt_test.go` (the `fire_bolt` geometry). Every
|
||||
expectation was read out of `sticks.c` rather than off the Go code; **no
|
||||
divergence from C was found**, and three things worth knowing came out of the
|
||||
reading. (1) **The bolt trail is the test instrument.** `fire_bolt` paints
|
||||
each square with `dirch` and then paints `chat()` back over every square it
|
||||
recorded, so on a screen nothing else has drawn on, the non-blank cells
|
||||
afterwards are exactly the squares the bolt occupied — and the walls it
|
||||
bounced off are absent, because C undoes the record with `c1--` and `break`s
|
||||
before the `mvaddch`. That gives an exact assertion of the path and the
|
||||
resting place without touching game code, and it is why the tests fire from a
|
||||
square that is not the hero's (which is what `chase.c` does for dragon
|
||||
breath): with the hero off the ray the run produces one message and the screen
|
||||
stays readable. (2) **A bounce reverses both components of the direction, not
|
||||
one.** A bolt entering a wall at 45 degrees goes back the way it came instead
|
||||
of reflecting off the surface, so the diagonal-into-a-vertical-wall case is
|
||||
the one that separates C's rule from the plausible wrong one, and it is
|
||||
tested. (3) **The `ch != 'M'` guard on the miss message is a tautology.** `ch`
|
||||
comes from `winat`, and `winat` _is_ `t_disguise` when a monster stands there
|
||||
(`rogue.h` 57), so `ch == 'M'` implies `t_disguise == 'M'` and the arm can
|
||||
never go quiet; it is vestigial from when 'M' was the mimic, and the test pins
|
||||
the port to speaking, so nobody "tidies" it into a real silence. The
|
||||
door-under-hero exception has no assertion of its own because it cannot have
|
||||
one: without it the bolt bounces on the hero's own square forever, recording
|
||||
nothing, and `fire_bolt` never returns — the test for it hangs rather than
|
||||
fails, which the comment on it says. Determinism comes from a `pinRng` helper
|
||||
that searches for a seed whose next draw is the wanted value (running the real
|
||||
`Rng`, never predicting it) and from a level the tests carve themselves
|
||||
through `drawRoom`, since bounce geometry and `drain`'s room/passage/door
|
||||
reach only mean something against known walls and a known passage number. All
|
||||
21 mutations tried against the new tests were caught.
|
||||
|
||||
- 2026-08-09 Command dispatch audit (`audit/command-switch-coverage`, closes
|
||||
#31): checked every case label in C's `command.c` against this port's
|
||||
dispatch, and left the audit behind as a standing test
|
||||
|
||||
Reference in New Issue
Block a user