game/sticks.go is the largest under-tested file in the repo: 534 lines,
23 functions, and exactly one test (TestZapSlowMonster). Every other zap
effect, the bolt-bouncing geometry, and the level-drain logic are untested.
Second of three units splitting the TODO.md Next Step into commit-sized
pieces.
fireBolt — bolt propagation, the highest-risk code here.
boltDirChar / boltBounces — the /, \, |, - glyph selection and
the wall-bounce reflection rules. Pure geometry over a direction vector;
cheap to test exhaustively and easy to get subtly wrong.
boltStrikesMonster / boltStrikesHero — including the case where a
bounced bolt hits the player who fired it.
drain / drainReaches — which monsters are in range for drain-life,
including the in-passage and cross-room cases.
fixStick, chargeStr — charge accounting and display.
Definition of done
A new game/sticks_test.go covering, at minimum:
boltDirChar for all eight direction vectors.
boltBounces for the reflection cases in both wall orientations,
including a corner.
At least one end-to-end fireBolt through a constructed level asserting
the path taken and the final resting position.
A bounced bolt striking the hero who fired it.
drainReaches for: same room, different room, in-passage.
Each remaining zap handler asserting its characteristic state change
(e.g. zapPolymorph replaces the monster type; zapCancellation clears
the monster's special flags; zapTeleport moves the target).
fixStick charge accounting and chargeStr output for a known and an
unknown stick.
Deterministic: fixed seeds for anything touching the RNG, following the
existing tests' pattern.
make check fully green.
TODO.md updated in the same commit (narrow the Next Step; do not mark it
complete — the wizard-command third remains).
Commit title ends with (closes #N).
Implementation requirements
Cross-check expected behavior against the C reference
(git show origin/c-master:sticks.c). Do NOT check out or modify origin/c-master. Do not simply assert current Go behavior — the value of
these tests is catching divergence from C, and a test written by reading the
Go code proves nothing.
If you find a real divergence from C, stop and report it. Do not fix
gameplay in a test-coverage commit and do not encode the bug as expected.
Beware the process-exit constraint: a bolt can kill the hero, and death
calls os.Exit, which would kill the test binary. Use the existing fortify()-style HP-pinning pattern from the playtest tests where needed.
Tests must call t.Parallel(); keep the lint run at 0 issues.
White-box tests need the //nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
header.
make targets only. Do NOT modify .golangci.yml. No game behavior changes.
If the file grows unwieldy, splitting bolt-geometry tests into their own
file is acceptable — but keep it to this one commit.
Never mention Claude or Anthropic anywhere.
## Problem
`game/sticks.go` is the largest under-tested file in the repo: **534 lines,
23 functions, and exactly one test** (`TestZapSlowMonster`). Every other zap
effect, the bolt-bouncing geometry, and the level-drain logic are untested.
Second of three units splitting the `TODO.md` Next Step into commit-sized
pieces.
## Untested surface
Per-effect handlers, all unexercised except speed/slow:
`zapLight`, `zapDrainLife`, `zapInvisibility`, `zapPolymorph`,
`zapCancellation`, `zapTeleport`, `zapMagicMissile`, `zapBolt`.
Supporting logic, all unexercised:
- `fireBolt` — bolt propagation, the highest-risk code here.
- `boltDirChar` / `boltBounces` — the `/`, `\`, `|`, `-` glyph selection and
the wall-bounce reflection rules. Pure geometry over a direction vector;
cheap to test exhaustively and easy to get subtly wrong.
- `boltStrikesMonster` / `boltStrikesHero` — including the case where a
bounced bolt hits the player who fired it.
- `drain` / `drainReaches` — which monsters are in range for drain-life,
including the in-passage and cross-room cases.
- `fixStick`, `chargeStr` — charge accounting and display.
## Definition of done
1. A new `game/sticks_test.go` covering, at minimum:
- `boltDirChar` for **all eight** direction vectors.
- `boltBounces` for the reflection cases in both wall orientations,
including a corner.
- At least one end-to-end `fireBolt` through a constructed level asserting
the path taken and the final resting position.
- A bounced bolt striking the hero who fired it.
- `drainReaches` for: same room, different room, in-passage.
- Each remaining zap handler asserting its characteristic state change
(e.g. `zapPolymorph` replaces the monster type; `zapCancellation` clears
the monster's special flags; `zapTeleport` moves the target).
- `fixStick` charge accounting and `chargeStr` output for a known and an
unknown stick.
2. Deterministic: fixed seeds for anything touching the RNG, following the
existing tests' pattern.
3. `make check` fully green.
4. `TODO.md` updated in the same commit (narrow the Next Step; do not mark it
complete — the wizard-command third remains).
5. Commit title ends with ` (closes #N)`.
## Implementation requirements
- **Cross-check expected behavior against the C reference**
(`git show origin/c-master:sticks.c`). Do NOT check out or modify
`origin/c-master`. Do not simply assert current Go behavior — the value of
these tests is catching divergence from C, and a test written by reading the
Go code proves nothing.
- If you find a real divergence from C, **stop and report it**. Do not fix
gameplay in a test-coverage commit and do not encode the bug as expected.
- Beware the process-exit constraint: a bolt can kill the hero, and death
calls `os.Exit`, which would kill the test binary. Use the existing
`fortify()`-style HP-pinning pattern from the playtest tests where needed.
- Tests must call `t.Parallel()`; keep the lint run at 0 issues.
- White-box tests need the
`//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)`
header.
- `make` targets only. Do NOT modify `.golangci.yml`. No game behavior changes.
- If the file grows unwieldy, splitting bolt-geometry tests into their own
file is acceptable — but keep it to this one commit.
- Never mention Claude or Anthropic anywhere.
Implemented in PR #35 (branch test/sticks-coverage, commit dda90bf).
game/sticks_test.go and game/bolt_test.go cover every item in the definition of done: boltDirChar for all eight directions, boltBounces in both wall orientations plus a corner (and diagonally off a wall, which is what separates C's negate-both-components rule from a surface reflection), an end-to-end fireBolt asserting the path and resting square, a bounced bolt striking the hero who fired it, drainReaches for same-room / other-room / in-passage / door-under-hero, every remaining zap handler with its characteristic state change, and fixStick / chargeStr. TODO.md has the Completed Steps entry; the Next Step was not rotated, since the wizard-command third (#7) remains.
Expectations were read from git show origin/c-master:sticks.c, not from the Go code. No divergence from C was found, so no game code changed. Two pieces of C archaeology are recorded in the test comments: fire_bolt's ch != 'M' guard on the miss message is a tautology (winatist_disguise when a monster stands there, rogue.h 57), and zapCancellation's t_disguise = t_type is an identity for every monster a zap ray can stop on, because a xeroc's item disguise is step_ok and the ray walks past it.
Verification: make check green (fmt-check, golangci-lint 0 issues with a private lint cache, go test -timeout 30s -race -cover), and 27 mutations of game/sticks.go were each shown to make the targeted test fail — including the door-under-hero guard, whose loss makes the test hang rather than fail, which is the only signal it can have. The full breakdown is in the PR comment.
Implemented in PR #35 (branch `test/sticks-coverage`, commit `dda90bf`).
`game/sticks_test.go` and `game/bolt_test.go` cover every item in the definition of done: `boltDirChar` for all eight directions, `boltBounces` in both wall orientations plus a corner (and diagonally off a wall, which is what separates C's negate-both-components rule from a surface reflection), an end-to-end `fireBolt` asserting the path and resting square, a bounced bolt striking the hero who fired it, `drainReaches` for same-room / other-room / in-passage / door-under-hero, every remaining zap handler with its characteristic state change, and `fixStick` / `chargeStr`. `TODO.md` has the Completed Steps entry; the `Next Step` was **not** rotated, since the wizard-command third (#7) remains.
Expectations were read from `git show origin/c-master:sticks.c`, not from the Go code. **No divergence from C was found**, so no game code changed. Two pieces of C archaeology are recorded in the test comments: `fire_bolt`'s `ch != 'M'` guard on the miss message is a tautology (`winat` _is_ `t_disguise` when a monster stands there, `rogue.h` 57), and `zapCancellation`'s `t_disguise = t_type` is an identity for every monster a zap ray can stop on, because a xeroc's item disguise is `step_ok` and the ray walks past it.
Verification: `make check` green (fmt-check, golangci-lint 0 issues with a private lint cache, `go test -timeout 30s -race -cover`), and 27 mutations of `game/sticks.go` were each shown to make the targeted test fail — including the door-under-hero guard, whose loss makes the test hang rather than fail, which is the only signal it can have. The full breakdown is in the PR comment.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
game/sticks.gois the largest under-tested file in the repo: 534 lines,23 functions, and exactly one test (
TestZapSlowMonster). Every other zapeffect, the bolt-bouncing geometry, and the level-drain logic are untested.
Second of three units splitting the
TODO.mdNext Step into commit-sizedpieces.
Untested surface
Per-effect handlers, all unexercised except speed/slow:
zapLight,zapDrainLife,zapInvisibility,zapPolymorph,zapCancellation,zapTeleport,zapMagicMissile,zapBolt.Supporting logic, all unexercised:
fireBolt— bolt propagation, the highest-risk code here.boltDirChar/boltBounces— the/,\,|,-glyph selection andthe wall-bounce reflection rules. Pure geometry over a direction vector;
cheap to test exhaustively and easy to get subtly wrong.
boltStrikesMonster/boltStrikesHero— including the case where abounced bolt hits the player who fired it.
drain/drainReaches— which monsters are in range for drain-life,including the in-passage and cross-room cases.
fixStick,chargeStr— charge accounting and display.Definition of done
game/sticks_test.gocovering, at minimum:boltDirCharfor all eight direction vectors.boltBouncesfor the reflection cases in both wall orientations,including a corner.
fireBoltthrough a constructed level assertingthe path taken and the final resting position.
drainReachesfor: same room, different room, in-passage.(e.g.
zapPolymorphreplaces the monster type;zapCancellationclearsthe monster's special flags;
zapTeleportmoves the target).fixStickcharge accounting andchargeStroutput for a known and anunknown stick.
existing tests' pattern.
make checkfully green.TODO.mdupdated in the same commit (narrow the Next Step; do not mark itcomplete — the wizard-command third remains).
(closes #N).Implementation requirements
(
git show origin/c-master:sticks.c). Do NOT check out or modifyorigin/c-master. Do not simply assert current Go behavior — the value ofthese tests is catching divergence from C, and a test written by reading the
Go code proves nothing.
gameplay in a test-coverage commit and do not encode the bug as expected.
calls
os.Exit, which would kill the test binary. Use the existingfortify()-style HP-pinning pattern from the playtest tests where needed.t.Parallel(); keep the lint run at 0 issues.//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)header.
maketargets only. Do NOT modify.golangci.yml. No game behavior changes.file is acceptable — but keep it to this one commit.
Implemented in PR #35 (branch
test/sticks-coverage, commitdda90bf).game/sticks_test.goandgame/bolt_test.gocover every item in the definition of done:boltDirCharfor all eight directions,boltBouncesin both wall orientations plus a corner (and diagonally off a wall, which is what separates C's negate-both-components rule from a surface reflection), an end-to-endfireBoltasserting the path and resting square, a bounced bolt striking the hero who fired it,drainReachesfor same-room / other-room / in-passage / door-under-hero, every remaining zap handler with its characteristic state change, andfixStick/chargeStr.TODO.mdhas the Completed Steps entry; theNext Stepwas not rotated, since the wizard-command third (#7) remains.Expectations were read from
git show origin/c-master:sticks.c, not from the Go code. No divergence from C was found, so no game code changed. Two pieces of C archaeology are recorded in the test comments:fire_bolt'sch != 'M'guard on the miss message is a tautology (winatist_disguisewhen a monster stands there,rogue.h57), andzapCancellation'st_disguise = t_typeis an identity for every monster a zap ray can stop on, because a xeroc's item disguise isstep_okand the ray walks past it.Verification:
make checkgreen (fmt-check, golangci-lint 0 issues with a private lint cache,go test -timeout 30s -race -cover), and 27 mutations ofgame/sticks.gowere each shown to make the targeted test fail — including the door-under-hero guard, whose loss makes the test hang rather than fail, which is the only signal it can have. The full breakdown is in the PR comment.