C's command.c 317-338 has a when '+' arm — in the main command
switch, under #ifdef MASTER — that toggles wizard mode. The port had no '+' anywhere, so the key fell through dispatchKey's default to illcom
and answered "illegal command '+'".
The password half of that arm was dropped deliberately (wizard mode is ROGUE_WIZARD configuration) and is recorded in ARCHITECTURE.md §9.
The leave half was lost silently, and it is a different decision: it does
not touch the password machinery. The substantive part of it is turn_see(TRUE), not the flag — wizard sight draws every monster the hero
cannot see, so without the re-hide there is no way back to normal
visibility, and clearing the flag alone would leave the screen lying.
What changed
game/command.go: new wizardToggleCommand. After = false; in wizard
mode it clears Wizard, calls g.turnSee(true) and prints "not wizard any more"; otherwise "sorry".
game/tables.go: '+' registered in commandHandlers between '^' and Escape — C's own switch order. Because C's arm is in the main switch
rather than the if (wizard) switch (ch) sub-switch that wizardCommand
ports, it is reachable whether or not wizard is set; commandHandlers
is the faithful home, and the non-wizard case was a divergence too.
The non-wizard choice, stated in the function's doc comment and in
§9: a password check that no longer exists can never succeed, so the
else arm is exactly what C did on a wrong answer — "sorry", with no
prompt (nothing typed into one could change the outcome) and none of the noscore/turn_see(FALSE) bookkeeping of C's unreachable success branch.
game/wizard_test.go: two tests, both driving '+' through g.dispatch.
ARCHITECTURE.md §9: the password row now names the '+' enter arm
as the dropped part, and a new paragraph records that the leave arm is
ported in full.
TODO.md: Completed Steps entry. Next Step deliberately not rotated
(out-of-band issue work).
C sources each claim was verified against
Read via git show; c-master was not checked out or modified.
origin/c-master:command.c 317-338 — the #ifdef MASTER / when '+' / #endif block, both arms, and its position immediately before otherwise: in the main switch (the wizard sub-switch starts at 369).
The issue cited 316-336; cosmetic drift, everything else in it held.
origin/c-master:potions.c 267-306 — turn_see. The turn_off arm
redraws t_oldch at every monster the hero cannot see and clears SEEMONST, and reaches no rnd call (only the turn-on arm does). origin/c-master:rogue.h:691 for the declaration.
origin/c-master:wizard.c 233-257 — passwd(): it prompts, reads a
line, and compares md_crypt(buf, "mT") against PASSWD. An empty
answer returns FALSE, which is the branch this port is permanently on.
Message strings taken verbatim from those sources: "not wizard any more"
and "sorry".
Verification
make check green on the branch: fmt-check clean, golangci-lint 0 issues, make test (-timeout 30s -race -cover) ok for all
packages. The only linter output beyond that is the known gomodguard
deprecation warning (#29).
Lint ran against a private GOLANGCI_LINT_CACHE in a fresh empty
directory, not the shared host cache; the run named no path outside the
branch's own worktree.
make test re-run with the Go test cache bypassed, so the green is not a
cache hit.
Mutation-proved: deleting the turnSee(true) call fails TestWizardToggleOffRehidesSensedMonsters on all three visibility
assertions — SenseMonsters still set, cell still 'P', cell still in
standout — which is exactly what a flag-only test would have missed.
RNG order untouched: no rnd call added, and TestSeedCompatItemTables
passes against the golden, which was not regenerated or edited.
.golangci.yml unmodified (sha256 still 021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb).
Work was done in a throwaway worktree; the shared clone stayed on main
and clean.
Closes #11.
C's `command.c` 317-338 has a `when '+'` arm — in the **main** command
switch, under `#ifdef MASTER` — that toggles wizard mode. The port had no
`'+'` anywhere, so the key fell through `dispatchKey`'s default to `illcom`
and answered "illegal command '+'".
The password half of that arm was dropped deliberately (wizard mode is
`ROGUE_WIZARD` configuration) and is recorded in ARCHITECTURE.md §9.
The leave half was lost silently, and it is a different decision: it does
not touch the password machinery. The substantive part of it is
`turn_see(TRUE)`, not the flag — wizard sight draws every monster the hero
cannot see, so without the re-hide there is no way back to normal
visibility, and clearing the flag alone would leave the screen lying.
## What changed
- `game/command.go`: new `wizardToggleCommand`. `After = false`; in wizard
mode it clears `Wizard`, calls `g.turnSee(true)` and prints
`"not wizard any more"`; otherwise `"sorry"`.
- `game/tables.go`: `'+'` registered in `commandHandlers` between `'^'` and
`Escape` — C's own switch order. Because C's arm is in the main switch
rather than the `if (wizard) switch (ch)` sub-switch that `wizardCommand`
ports, it is reachable whether or not `wizard` is set; `commandHandlers`
is the faithful home, and the non-wizard case was a divergence too.
- The non-wizard choice, stated in the function's doc comment and in
§9: a password check that no longer exists can never succeed, so the
else arm is exactly what C did on a wrong answer — `"sorry"`, with no
prompt (nothing typed into one could change the outcome) and none of the
`noscore`/`turn_see(FALSE)` bookkeeping of C's unreachable success branch.
- `game/wizard_test.go`: two tests, both driving `'+'` through `g.dispatch`.
- `ARCHITECTURE.md` §9: the password row now names the `'+'` enter arm
as the dropped part, and a new paragraph records that the leave arm is
ported in full.
- `TODO.md`: Completed Steps entry. `Next Step` deliberately not rotated
(out-of-band issue work).
## C sources each claim was verified against
Read via `git show`; `c-master` was not checked out or modified.
- `origin/c-master:command.c` 317-338 — the `#ifdef MASTER` / `when '+'` /
`#endif` block, both arms, and its position immediately before
`otherwise:` in the main switch (the wizard sub-switch starts at 369).
The issue cited 316-336; cosmetic drift, everything else in it held.
- `origin/c-master:potions.c` 267-306 — `turn_see`. The `turn_off` arm
redraws `t_oldch` at every monster the hero cannot see and clears
`SEEMONST`, and reaches no `rnd` call (only the turn-on arm does).
`origin/c-master:rogue.h:691` for the declaration.
- `origin/c-master:wizard.c` 233-257 — `passwd()`: it prompts, reads a
line, and compares `md_crypt(buf, "mT")` against `PASSWD`. An empty
answer returns `FALSE`, which is the branch this port is permanently on.
- Message strings taken verbatim from those sources: `"not wizard any more"`
and `"sorry"`.
## Verification
- `make check` green on the branch: `fmt-check` clean, `golangci-lint`
**0 issues**, `make test` (`-timeout 30s -race -cover`) ok for all
packages. The only linter output beyond that is the known `gomodguard`
deprecation warning (#29).
- Lint ran against a private `GOLANGCI_LINT_CACHE` in a fresh empty
directory, not the shared host cache; the run named no path outside the
branch's own worktree.
- `make test` re-run with the Go test cache bypassed, so the green is not a
cache hit.
- **Mutation-proved**: deleting the `turnSee(true)` call fails
`TestWizardToggleOffRehidesSensedMonsters` on all three visibility
assertions — `SenseMonsters` still set, cell still `'P'`, cell still in
standout — which is exactly what a flag-only test would have missed.
- RNG order untouched: no `rnd` call added, and `TestSeedCompatItemTables`
passes against the golden, which was not regenerated or edited.
- `.golangci.yml` unmodified (sha256 still
`021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb`).
- Work was done in a throwaway worktree; the shared clone stayed on `main`
and clean.
C's command.c 317-338 has a `when '+'` arm in the main command switch,
under #ifdef MASTER, that toggles wizard mode. The port had no '+' at
all, so the key fell through dispatchKey's default to illcom and
answered "illegal command '+'".
The password half of that arm was dropped deliberately (wizard mode is
ROGUE_WIZARD configuration) and is recorded in ARCHITECTURE.md section
9. The leave half was lost silently, and it is a different decision: it
does not touch the password machinery. The substantive part of it is
turn_see(TRUE) rather than the flag -- wizard sight draws every monster
the hero cannot see, so without the re-hide there is no way back to
normal visibility, and clearing the flag alone would leave the screen
lying.
New wizardToggleCommand, registered in commandHandlers between '^' and
Escape, which is C's own switch order. Because C's arm sits in the main
switch rather than the `if (wizard) switch (ch)` sub-switch that
wizardCommand ports, it is reachable whether or not wizard is set, so
the non-wizard case was a divergence too. It resolves the way the
dropped passwd() forces: a password check that no longer exists can
never succeed, so the else arm is what C did on a wrong answer -- the
message "sorry", with no prompt, since nothing typed into one could
change the outcome, and none of the noscore/turn_see(FALSE) bookkeeping
of C's unreachable success branch. The choice is stated in the doc
comment and in section 9.
Two tests drive '+' through g.dispatch. The wizard one spawns a phantom
(ISINVIS straight from the monster table, so seeMonst is false and it is
on screen only because wizard sight put it there), asserts the
precondition, then asserts the flag cleared, SenseMonsters cleared, the
cell restored to the map char under the monster with standout off, the
exact message text, and After false. Deleting the turnSee(true) call
fails it on all three visibility assertions. The other pins "sorry".
No RNG call is added: the turn_off arm of turn_see never reaches rnd.
TestSeedCompatItemTables is green against the untouched golden.
clawbot
self-assigned this 2026-08-09 10:22:39 +02:00
One commit, c95f98f, on fix/wizard-toggle-off off main @ 630038e.
game/command.go — new wizardToggleCommand():
After = false unconditionally, as C's arm does first.
In wizard mode: g.Wizard = false, then g.turnSee(true), then g.msg("not wizard any more") — C's order exactly.
Otherwise: g.msg("sorry").
The doc comment carries the (command.c command) breadcrumb and states
the dropped-password reduction in full.
game/tables.go — '+': (*RogueGame).wizardToggleCommand in commandHandlers, placed between '^' and Escape, which is where the arm
sits in C's switch.
ARCHITECTURE.md §9 — the "DES crypt wizard password" row now reads
"DES crypt wizard password (passwd(), the '+' enter arm)", and a new
paragraph after the table records that the leave arm is ported in full as wizardToggleCommand, that turnSee(true) is why, and that '+' outside
wizard mode reduces to "sorry".
TODO.md — Completed Steps entry in the same commit. Next Step was
deliberately not rotated: this is out-of-band issue work, matching the
convention the last four issue entries use.
Where the issue was right, and the one place it drifted
Every substantive premise checked out. Two refinements:
Line numbers: the block is command.c317-338 (#ifdef MASTER 317, when '+': 318, #endif 338), not 316-336. Cosmetic.
Placement, which does change the code: '+' is in C's main command
switch, immediately before otherwise:, not in the if (wizard) switch (ch) sub-switch (that one starts at line 369) which wizardCommand/wizardDebugCommand port. Under MASTER, C therefore
reaches '+' whether or not wizard is set. So the faithful home is commandHandlers, not wizardCommand — and the non-wizard path was a
divergence too, answering "illegal command '+'" where C answers "sorry".
The issue's DoD item 2 anticipated this; it is worth naming because
putting the handler in wizardCommand would have looked right and left
half the bug in place.
C sources each claim was verified against
All read with git show; c-master and modern-rogue were not checked out
or touched.
Claim
Source
the '+' arm exists, both halves, in main switch
origin/c-master:command.c 317-338, otherwise: at 366
the wizard sub-switch is separate
origin/c-master:command.c 369-405
turn_see semantics and no rnd on the off arm
origin/c-master:potions.c 267-306
turn_see declaration
origin/c-master:rogue.h:691
passwd() is a DES compare that can only fail
origin/c-master:wizard.c 233-257
Message strings are byte-for-byte from command.c: "not wizard any more"
and "sorry". They are asserted raw against MessageLine.Huh, which is
captured before endmsg's display-time capitalization, so the test pins the
contract text rather than the rendered text.
How it was tested
game/wizard_test.go gains two tests, both t.Parallel(), in a file that
already carries the approved //nolint:testpackage header.
TestWizardToggleOffRehidesSensedMonsters — the substantive one. It spawns
a phantom next to the hero; a phantom carries Invisible straight from the
monster table, so seeMonst is false for it and it is on screen only
because wizard sight put it there. The test asserts its preconditions before
asserting anything else (monster is genuinely unseeable, the map char under
it differs from its glyph, turnSee(false) set SenseMonsters and drew 'P' in standout at its cell), then drives g.dispatch('+') and checks: Wizard cleared, SenseMonsters cleared, the cell restored to the
monster's OldCh, standout off, message "not wizard any more", After
false.
TestWizardToggleWithoutWizardSaysSorry — '+' with Wizard false: wizard
mode stays off, SenseMonsters is untouched, message "sorry", After
false.
Mutation proof. Deleting the g.turnSee(true) call makes the first test
fail on all three visibility assertions and on none of the others:
--- FAIL: TestWizardToggleOffRehidesSensedMonsters
wizard_test.go:481: '+' left SenseMonsters set: turn_see(TRUE) was not performed
wizard_test.go:485: monster still on screen after leaving wizard mode: cell is 'P', want the map char under it, '.'
wizard_test.go:490: cell left in standout after leaving wizard mode
That is the half a flag-only test would have missed, which is why the issue
insisted on it.
Gate
make check green: fmt-check clean, golangci-lint0 issues, make test (-timeout 30s -race -cover) ok on all three packages.
The lint run used GOLANGCI_LINT_CACHE pointed at a fresh empty directory
created for this branch, not the shared host cache, so the 0 cannot be a
stale shared-cache artifact. Its output named no path outside this
branch's own worktree. The only extra output was the known gomodguard
deprecation warning tracked as #29, untouched here.
make test was also re-run with the Go test cache bypassed, so the green
is a real execution rather than a cache hit.
TestSeedCompatItemTables passes against game/testdata/item_tables.golden
exactly as committed — the golden was neither edited nor regenerated, and
no rnd call was added (the turn_off arm of turn_see reaches none).
.golangci.yml untouched; sha256 still 021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb.
All work happened in a throwaway git worktree, removed after the push; the
shared clone stayed on main and clean throughout.
## What is in this branch
One commit, `c95f98f`, on `fix/wizard-toggle-off` off `main` @ `630038e`.
**`game/command.go`** — new `wizardToggleCommand()`:
- `After = false` unconditionally, as C's arm does first.
- In wizard mode: `g.Wizard = false`, then `g.turnSee(true)`, then
`g.msg("not wizard any more")` — C's order exactly.
- Otherwise: `g.msg("sorry")`.
- The doc comment carries the `(command.c command)` breadcrumb and states
the dropped-password reduction in full.
**`game/tables.go`** — `'+': (*RogueGame).wizardToggleCommand` in
`commandHandlers`, placed between `'^'` and `Escape`, which is where the arm
sits in C's switch.
**`ARCHITECTURE.md` §9** — the "DES crypt wizard password" row now reads
"DES crypt wizard password (`passwd()`, the `'+'` enter arm)", and a new
paragraph after the table records that the leave arm is ported in full as
`wizardToggleCommand`, that `turnSee(true)` is why, and that `'+'` outside
wizard mode reduces to "sorry".
**`TODO.md`** — Completed Steps entry in the same commit. `Next Step` was
deliberately **not** rotated: this is out-of-band issue work, matching the
convention the last four issue entries use.
## Where the issue was right, and the one place it drifted
Every substantive premise checked out. Two refinements:
1. Line numbers: the block is `command.c` **317-338** (`#ifdef MASTER` 317,
`when '+':` 318, `#endif` 338), not 316-336. Cosmetic.
2. Placement, which does change the code: `'+'` is in C's **main** command
switch, immediately before `otherwise:`, not in the
`if (wizard) switch (ch)` sub-switch (that one starts at line 369) which
`wizardCommand`/`wizardDebugCommand` port. Under `MASTER`, C therefore
reaches `'+'` whether or not `wizard` is set. So the faithful home is
`commandHandlers`, not `wizardCommand` — and the non-wizard path was a
divergence too, answering "illegal command '+'" where C answers "sorry".
The issue's DoD item 2 anticipated this; it is worth naming because
putting the handler in `wizardCommand` would have looked right and left
half the bug in place.
## C sources each claim was verified against
All read with `git show`; `c-master` and `modern-rogue` were not checked out
or touched.
| Claim | Source |
| ------------------------------------------------ | -------------------------------------------------------- |
| the `'+'` arm exists, both halves, in main switch | `origin/c-master:command.c` 317-338, `otherwise:` at 366 |
| the wizard sub-switch is separate | `origin/c-master:command.c` 369-405 |
| `turn_see` semantics and no `rnd` on the off arm | `origin/c-master:potions.c` 267-306 |
| `turn_see` declaration | `origin/c-master:rogue.h:691` |
| `passwd()` is a DES compare that can only fail | `origin/c-master:wizard.c` 233-257 |
Message strings are byte-for-byte from `command.c`: `"not wizard any more"`
and `"sorry"`. They are asserted raw against `MessageLine.Huh`, which is
captured before `endmsg`'s display-time capitalization, so the test pins the
contract text rather than the rendered text.
## How it was tested
`game/wizard_test.go` gains two tests, both `t.Parallel()`, in a file that
already carries the approved `//nolint:testpackage` header.
`TestWizardToggleOffRehidesSensedMonsters` — the substantive one. It spawns
a phantom next to the hero; a phantom carries `Invisible` straight from the
monster table, so `seeMonst` is false for it and it is on screen **only**
because wizard sight put it there. The test asserts its preconditions before
asserting anything else (monster is genuinely unseeable, the map char under
it differs from its glyph, `turnSee(false)` set `SenseMonsters` and drew
`'P'` in standout at its cell), then drives `g.dispatch('+')` and checks:
`Wizard` cleared, `SenseMonsters` cleared, the cell restored to the
monster's `OldCh`, standout off, message `"not wizard any more"`, `After`
false.
`TestWizardToggleWithoutWizardSaysSorry` — `'+'` with `Wizard` false: wizard
mode stays off, `SenseMonsters` is untouched, message `"sorry"`, `After`
false.
**Mutation proof.** Deleting the `g.turnSee(true)` call makes the first test
fail on all three visibility assertions and on none of the others:
```
--- FAIL: TestWizardToggleOffRehidesSensedMonsters
wizard_test.go:481: '+' left SenseMonsters set: turn_see(TRUE) was not performed
wizard_test.go:485: monster still on screen after leaving wizard mode: cell is 'P', want the map char under it, '.'
wizard_test.go:490: cell left in standout after leaving wizard mode
```
That is the half a flag-only test would have missed, which is why the issue
insisted on it.
## Gate
- `make check` green: `fmt-check` clean, `golangci-lint` **0 issues**,
`make test` (`-timeout 30s -race -cover`) ok on all three packages.
- The lint run used `GOLANGCI_LINT_CACHE` pointed at a fresh empty directory
created for this branch, not the shared host cache, so the 0 cannot be a
stale shared-cache artifact. Its output named no path outside this
branch's own worktree. The only extra output was the known `gomodguard`
deprecation warning tracked as #29, untouched here.
- `make test` was also re-run with the Go test cache bypassed, so the green
is a real execution rather than a cache hit.
- `TestSeedCompatItemTables` passes against `game/testdata/item_tables.golden`
exactly as committed — the golden was neither edited nor regenerated, and
no `rnd` call was added (the `turn_off` arm of `turn_see` reaches none).
- `.golangci.yml` untouched; sha256 still
`021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb`.
- All work happened in a throwaway git worktree, removed after the push; the
shared clone stayed on `main` and clean throughout.
Review of PR #30 — fix/wizard-toggle-off @ c95f98f (base main @ 630038e)
Verdict: PASS.
Independent review; reviewer did not author the change. All C claims were
re-verified from scratch via git show origin/c-master:...; c-master and modern-rogue were not checked out. Work was done in a throwaway worktree,
which has been removed; the shared clone was left on main and clean.
Adjudication of the author's correction to issue #11
The author is right, and the issue was wrong. Verified independently:
origin/c-master:rogue.h:52-53 — #define when break;case and #define otherwise break;default. The when macro claim holds.
origin/c-master:command.c — #ifdef MASTER at 317, when '+': at 318, #endif at 338. The issue's 316-336 is off by one line at each
end. Cosmetic; the author's numbers are the correct ones.
The block sits in the main command switch: when '^': at 300 precedes it, when ESCAPE: at 339 follows it, otherwise: is at 366, and the if (wizard) switch (ch) sub-switch that wizardCommand/wizardDebugCommand
port opens at 369 (inside #ifdef MASTER at 368, #endif at 425). '+'
is not in that sub-switch.
Consequence confirmed: under MASTER, C reaches '+' regardless of the wizard flag. commandHandlers is therefore the faithful home, and the
not-in-wizard-mode path was a live divergence too ("illegal command '+'" vs
C's "sorry"). Had the handler gone into wizardCommand per the issue's
framing, half the bug would have survived while looking fixed.
One prose inaccuracy in the PR description and the issue comment, non-blocking
and not present in the repo: both say the arm sits "immediately before otherwise:". It does not — six arms (ESCAPE, 'm', ')', ']', '=', '@') sit between #endif at 338 and otherwise: at 366. The load-bearing
claim (main switch, not the wizard sub-switch) is unaffected, and the code,
the doc comment, TODO.md and ARCHITECTURE.md all describe the placement
correctly ("between '^' and Escape", which is exactly C's order).
Itemized verification
Message text.command.c:324 is msg("not wizard any more") and command.c:336 is msg("sorry"). Both strings in game/command.go:419,428 match byte-for-byte, including the two-word
"any more" and the lowercase "sorry". Both are asserted raw against MessageLine.Huh, which game/io.go:57-60 captures from the buffer in End() before the display-time capitalization at io.go:67-71 — so the
tests pin the contract text, not the rendered text. Correct choice.
Order of operations on the leave arm.command.c:322-324 is wizard = FALSE; → turn_see(TRUE); → msg(...). game/command.go:424-428 is g.Wizard = false → g.turnSee(true)
→ g.msg(...). Identical, and the order matters: turnSee calls seeMonst, so clearing the flag first is what makes the re-hide see the
post-wizard world.
turnSee(true) vs turn_see(TRUE).origin/c-master:potions.c:270-306
against game/potions.go:295-317: same loop over the monster list, same move then can_see = see_monst(mp), same if (!can_see) addch(t_oldch)
on the off arm, same SEEMONST clear after the loop. The re-hide is real.
Pre-existing code, unchanged by this PR.
Password-path reduction. Verified against origin/c-master:wizard.c:233-258. passwd() prints "wizard's Password:", reads a line, returns FALSE on an empty answer and
otherwise compares md_crypt(buf, "mT") against PASSWD. C's wrong-answer
path then falls to command.c:335-336, which prints "sorry" and does
nothing else — no state change, and wizard was already assigned FALSE
from passwd(). So the reduction to a bare "sorry" is exactly C's
reachable behaviour. C does prompt first; dropping the prompt is right,
because with no stored hash there is nothing the input could be compared
against and the outcome is fixed before the read. Dropping noscore/turn_see(FALSE) is right for the same reason — both belong to
the unreachable success branch — and separately noscore is not lost from
the port at all: game/command.go:119-121 (turnUpkeep) already sets g.NoScore = true every turn while g.Wizard. The doc comment at game/command.go:405-414 states the reduction and its reasoning in full,
with the ROGUE_WIZARD/§9 pointer. Adequate.
After = false.command.c:319 sets after = FALSE before the if (wizard), i.e. on both arms. game/command.go:416 sets it
unconditionally at the top. Matches; '+' consumes no turn, so daemon and
fuse sequencing is unchanged. Both tests assert it after presetting g.After = true.
Dispatch-table placement.game/tables.go:805 adds '+': (*RogueGame).wizardToggleCommand between '^' and Escape — C's own
switch order. commandHandlers is a map, so the position is stylistic only.
No key is shadowed or displaced: '+' had no prior entry, wizardCommand
and wizardDebugCommand contain no '+' case, and the only other '+'
literals in non-test code are the Door glyph (game/types.go:48) and the
blessing prompt (game/wizard.go:84,96), neither of which is a command key.
The diff touches no other handler.
Tests — mutation reproduced. Deleting g.turnSee(true) from game/command.go:427 and running GOFLAGS=-count=1 make test, the failure
is exactly as claimed and confined to one test:
--- FAIL: TestWizardToggleOffRehidesSensedMonsters
wizard_test.go:481: '+' left SenseMonsters set: turn_see(TRUE) was not performed
wizard_test.go:485: monster still on screen after leaving wizard mode: cell is 'P', want the map char under it, '.'
wizard_test.go:490: cell left in standout after leaving wizard mode
No other test fails, including TestSeedCompatItemTables. Two further
mutations confirm the rest is non-vacuous: removing g.After = false fails wizard_test.go:498 and :529 in both tests; changing the strings to "not wizard anymore" / "Sorry" fails wizard_test.go:494 and :525.
The visibility half is genuinely load-bearing — a flag-only test would have
passed all three mutations that matter. The preconditions
(wizard_test.go:437-462) are asserted with t.Fatal before the act, so
the test cannot silently degrade into asserting nothing: it proves the
phantom is Invisible, that seeMonst is false for it, that OldCh differs
from Type, and that turnSee(false) actually drew it in standout.
RNG order.potions.c reaches rnd only on the turn-on arm
(rnd(26) + 'A', the hallucinating draw); the turn_off arm has no rnd
call, and the Go mirror keeps that split (showSensed holds the turn-on
arm). No rnd call is added anywhere in the diff, nothing under game/testdata/ is touched, and TestSeedCompatItemTables passes against
the committed golden.
ARCHITECTURE.md §9. Accurate. The password row now reads
"DES crypt wizard password (passwd(), the '+' enter arm)", which is
precisely the dropped half, and the new paragraph correctly states that the
leave arm is ported in full, why turnSee(true) is the substantive part,
and what the else arm reduces to. Table column alignment is intact (all rows
68 chars in column 1).
Gate
make check green on c95f98f with GOLANGCI_LINT_CACHE pointed at a fresh
empty directory outside the shared host cache: fmt-check clean (gofmt and
prettier), golangci-lint0 issues, tests ok for all three packages. The
only extra output was the known gomodguard deprecation warning (#29), which
matches the main @ 630038e calibration.
make test (-timeout 30s -race -cover) re-run three times with GOFLAGS=-count=1: green each time, no data race.
.golangci.yml not in the diff; sha256 still 021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb.
Mergeable: git merge-tree against main @ 630038e (current head) is
conflict-free.
No CI status on the head commit because the repo has no workflow files at
all; nothing red, and outside this PR's scope.
No Dockerfile / CI / script/ changes. No new //nolint directives; the
test file's //nolint:testpackage header is pre-existing and both new tests
call t.Parallel().
Commit title is Port the '+' wizard-mode toggle-off (closes #11). Author
and committer are sneak <sneak@sneak.berlin>.
No Claude or Anthropic reference anywhere in the diff, the commit message, or
the author identity; no attribution trailers.
git diff --check clean; no scripted-edit artifacts.
TODO.md: Completed Steps entry added in the same commit; Next Step
("Broaden unit test coverage...") not rotated, as intended for out-of-band
issue work.
Scope: 5 files, +185/-1, all germane. No unrelated behaviour changed.
Non-blocking observations
The "immediately before otherwise:" phrasing in the PR body and in the
issue #11 comment is wrong (see above). Repo content is unaffected; no
change requested.
wizardToggleCommand never enters wizard mode, so the name is slightly
broader than the behaviour. It is the right name for C's command, the doc
comment resolves it immediately, and it is consistent with the neighbouring identifyTrapCommand / wizardCommand. No change requested.
## Review of PR #30 — `fix/wizard-toggle-off` @ `c95f98f` (base `main` @ `630038e`)
**Verdict: PASS.**
Independent review; reviewer did not author the change. All C claims were
re-verified from scratch via `git show origin/c-master:...`; `c-master` and
`modern-rogue` were not checked out. Work was done in a throwaway worktree,
which has been removed; the shared clone was left on `main` and clean.
### Adjudication of the author's correction to issue #11
**The author is right, and the issue was wrong.** Verified independently:
- `origin/c-master:rogue.h:52-53` — `#define when break;case` and
`#define otherwise break;default`. The `when` macro claim holds.
- `origin/c-master:command.c` — `#ifdef MASTER` at **317**, `when '+':` at
**318**, `#endif` at **338**. The issue's 316-336 is off by one line at each
end. Cosmetic; the author's numbers are the correct ones.
- The block sits in the **main** command switch: `when '^':` at 300 precedes it,
`when ESCAPE:` at 339 follows it, `otherwise:` is at 366, and the
`if (wizard) switch (ch)` sub-switch that `wizardCommand`/`wizardDebugCommand`
port opens at **369** (inside `#ifdef MASTER` at 368, `#endif` at 425). `'+'`
is not in that sub-switch.
- Consequence confirmed: under `MASTER`, C reaches `'+'` regardless of the
`wizard` flag. `commandHandlers` is therefore the faithful home, and the
not-in-wizard-mode path was a live divergence too ("illegal command '+'" vs
C's "sorry"). Had the handler gone into `wizardCommand` per the issue's
framing, half the bug would have survived while looking fixed.
One prose inaccuracy in the PR description and the issue comment, non-blocking
and not present in the repo: both say the arm sits "immediately before
`otherwise:`". It does not — six arms (`ESCAPE`, `'m'`, `')'`, `']'`, `'='`,
`'@'`) sit between `#endif` at 338 and `otherwise:` at 366. The load-bearing
claim (main switch, not the wizard sub-switch) is unaffected, and the code,
the doc comment, `TODO.md` and ARCHITECTURE.md all describe the placement
correctly ("between `'^'` and `Escape`", which is exactly C's order).
### Itemized verification
1. **Message text.** `command.c:324` is `msg("not wizard any more")` and
`command.c:336` is `msg("sorry")`. Both strings in
`game/command.go:419,428` match byte-for-byte, including the two-word
"any more" and the lowercase "sorry". Both are asserted raw against
`MessageLine.Huh`, which `game/io.go:57-60` captures from the buffer in
`End()` before the display-time capitalization at `io.go:67-71` — so the
tests pin the contract text, not the rendered text. Correct choice.
2. **Order of operations on the leave arm.** `command.c:322-324` is
`wizard = FALSE;` → `turn_see(TRUE);` → `msg(...)`.
`game/command.go:424-428` is `g.Wizard = false` → `g.turnSee(true)`
→ `g.msg(...)`. Identical, and the order matters: `turnSee` calls
`seeMonst`, so clearing the flag first is what makes the re-hide see the
post-wizard world.
3. **`turnSee(true)` vs `turn_see(TRUE)`.** `origin/c-master:potions.c:270-306`
against `game/potions.go:295-317`: same loop over the monster list, same
`move` then `can_see = see_monst(mp)`, same `if (!can_see) addch(t_oldch)`
on the off arm, same `SEEMONST` clear after the loop. The re-hide is real.
Pre-existing code, unchanged by this PR.
4. **Password-path reduction.** Verified against
`origin/c-master:wizard.c:233-258`. `passwd()` prints
`"wizard's Password:"`, reads a line, returns `FALSE` on an empty answer and
otherwise compares `md_crypt(buf, "mT")` against `PASSWD`. C's wrong-answer
path then falls to `command.c:335-336`, which prints `"sorry"` and does
nothing else — no state change, and `wizard` was already assigned `FALSE`
from `passwd()`. So the reduction to a bare `"sorry"` is exactly C's
reachable behaviour. C does prompt first; dropping the prompt is right,
because with no stored hash there is nothing the input could be compared
against and the outcome is fixed before the read. Dropping
`noscore`/`turn_see(FALSE)` is right for the same reason — both belong to
the unreachable success branch — and separately `noscore` is not lost from
the port at all: `game/command.go:119-121` (`turnUpkeep`) already sets
`g.NoScore = true` every turn while `g.Wizard`. The doc comment at
`game/command.go:405-414` states the reduction and its reasoning in full,
with the `ROGUE_WIZARD`/§9 pointer. Adequate.
5. **`After = false`.** `command.c:319` sets `after = FALSE` before the
`if (wizard)`, i.e. on both arms. `game/command.go:416` sets it
unconditionally at the top. Matches; `'+'` consumes no turn, so daemon and
fuse sequencing is unchanged. Both tests assert it after presetting
`g.After = true`.
6. **Dispatch-table placement.** `game/tables.go:805` adds
`'+': (*RogueGame).wizardToggleCommand` between `'^'` and `Escape` — C's own
switch order. `commandHandlers` is a map, so the position is stylistic only.
No key is shadowed or displaced: `'+'` had no prior entry, `wizardCommand`
and `wizardDebugCommand` contain no `'+'` case, and the only other `'+'`
literals in non-test code are the `Door` glyph (`game/types.go:48`) and the
blessing prompt (`game/wizard.go:84,96`), neither of which is a command key.
The diff touches no other handler.
7. **Tests — mutation reproduced.** Deleting `g.turnSee(true)` from
`game/command.go:427` and running `GOFLAGS=-count=1 make test`, the failure
is exactly as claimed and confined to one test:
```
--- FAIL: TestWizardToggleOffRehidesSensedMonsters
wizard_test.go:481: '+' left SenseMonsters set: turn_see(TRUE) was not performed
wizard_test.go:485: monster still on screen after leaving wizard mode: cell is 'P', want the map char under it, '.'
wizard_test.go:490: cell left in standout after leaving wizard mode
```
No other test fails, including `TestSeedCompatItemTables`. Two further
mutations confirm the rest is non-vacuous: removing `g.After = false` fails
`wizard_test.go:498` and `:529` in both tests; changing the strings to
`"not wizard anymore"` / `"Sorry"` fails `wizard_test.go:494` and `:525`.
The visibility half is genuinely load-bearing — a flag-only test would have
passed all three mutations that matter. The preconditions
(`wizard_test.go:437-462`) are asserted with `t.Fatal` before the act, so
the test cannot silently degrade into asserting nothing: it proves the
phantom is `Invisible`, that `seeMonst` is false for it, that `OldCh` differs
from `Type`, and that `turnSee(false)` actually drew it in standout.
8. **RNG order.** `potions.c` reaches `rnd` only on the turn-on arm
(`rnd(26) + 'A'`, the hallucinating draw); the `turn_off` arm has no `rnd`
call, and the Go mirror keeps that split (`showSensed` holds the turn-on
arm). No `rnd` call is added anywhere in the diff, nothing under
`game/testdata/` is touched, and `TestSeedCompatItemTables` passes against
the committed golden.
9. **ARCHITECTURE.md §9.** Accurate. The password row now reads
"DES crypt wizard password (`passwd()`, the `'+'` enter arm)", which is
precisely the dropped half, and the new paragraph correctly states that the
leave arm is ported in full, why `turnSee(true)` is the substantive part,
and what the else arm reduces to. Table column alignment is intact (all rows
68 chars in column 1).
### Gate
- `make check` green on `c95f98f` with `GOLANGCI_LINT_CACHE` pointed at a fresh
empty directory outside the shared host cache: `fmt-check` clean (gofmt and
prettier), `golangci-lint` **0 issues**, tests ok for all three packages. The
only extra output was the known `gomodguard` deprecation warning (#29), which
matches the `main` @ `630038e` calibration.
- `make test` (`-timeout 30s -race -cover`) re-run three times with
`GOFLAGS=-count=1`: green each time, no data race.
- `.golangci.yml` not in the diff; sha256 still
`021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb`.
- Mergeable: `git merge-tree` against `main` @ `630038e` (current head) is
conflict-free.
- No CI status on the head commit because the repo has no workflow files at
all; nothing red, and outside this PR's scope.
- No Dockerfile / CI / `script/` changes. No new `//nolint` directives; the
test file's `//nolint:testpackage` header is pre-existing and both new tests
call `t.Parallel()`.
- Commit title is `Port the '+' wizard-mode toggle-off (closes #11)`. Author
and committer are `sneak <sneak@sneak.berlin>`.
- No Claude or Anthropic reference anywhere in the diff, the commit message, or
the author identity; no attribution trailers.
- `git diff --check` clean; no scripted-edit artifacts.
- `TODO.md`: Completed Steps entry added in the same commit; `Next Step`
("Broaden unit test coverage...") not rotated, as intended for out-of-band
issue work.
- Scope: 5 files, +185/-1, all germane. No unrelated behaviour changed.
### Non-blocking observations
- The "immediately before `otherwise:`" phrasing in the PR body and in the
issue #11 comment is wrong (see above). Repo content is unaffected; no
change requested.
- `wizardToggleCommand` never enters wizard mode, so the name is slightly
broader than the behaviour. It is the right name for C's command, the doc
comment resolves it immediately, and it is consistent with the neighbouring
`identifyTrapCommand` / `wizardCommand`. No change requested.
Manager notes (the review is in its own comment above).
Verdict accepted: PASS. Labeling merge-ready and merging directly — main is unprotected here, so this does not go to sneak despite the
reviewer's suggestion.
My issue was wrong in a way that would have produced a half-fix. I said '+' belongs in wizardCommand/wizardDebugCommand. It does not: it sits in
C's main command switch (when '^': at 300, when '+': at 318, when ESCAPE: at 339), while the if (wizard) switch (ch) sub-switch that
those functions port does not open until 369.
The consequence is the part I would have missed entirely: under MASTER, C
reaches '+'regardless of whether wizard is set, so the non-wizard path
was a second live divergence — Go answered "illegal command '+'" where C
answers "sorry". Implementing my version would have fixed the wizard arm,
looked correct, and left the other half broken with nothing to catch it.
The reviewer verified the correction from primary sources rather than
adjudicating between two agents' assertions, which is what made this
conclusive: rogue.h:52-53 confirms #define when break;case, and the
line-by-line reading of command.c places the block precisely. It also
corrected the author's line numbers (317-338, not my 316-336).
Three mutations, not one. The author proved deleting g.turnSee(true)
fails the visibility test; the reviewer reproduced that and added two more —
removing g.After = false fails the turn-consumption assertions, and altering
the message strings fails the text assertions. Each mutation fails only its
own assertions. That is a materially stronger claim than "the test passes",
and it is the standard I want on this repo after #26.
Two details worth recording because they show the check went past the obvious:
The message assertions pin contract text, not display text. They assert
against MessageLine.Huh as captured before the display capitalization in game/io.go:67-71. Asserting post-capitalization would have silently
tolerated a change to the stored string.
noscore is not lost. C's unreachable success branch sets it; the
reviewer confirmed the port already sets g.NoScore = true every turn while g.Wizard in turnUpkeep, so dropping that bookkeeping from the reduction
costs nothing. That is exactly the sort of "the reduction drops something
that matters" risk I wanted checked.
The one inaccuracy found is confined to the PR body and my issue comment,
not the repo: both say the arm sits "immediately before otherwise:" when six
arms sit between. The code, doc comment, TODO.md, and ARCHITECTURE.md §9
all describe the placement correctly, so nothing false lands in the tree.
Correctly non-blocking.
Follow-up filed as #31: nothing has ever checked C's command switch against
Go's dispatch table, and '+' was found by accident rather than by audit. A
missing dispatch entry is uniquely invisible in a function-by-function port —
no dangling reference, no compile error, fails only when a player presses the
key. That issue asks for a regression test, not just a sweep.
Manager notes (the review is in its own comment above).
**Verdict accepted: PASS. Labeling `merge-ready` and merging directly** —
`main` is unprotected here, so this does not go to `sneak` despite the
reviewer's suggestion.
**My issue was wrong in a way that would have produced a half-fix.** I said
`'+'` belongs in `wizardCommand`/`wizardDebugCommand`. It does not: it sits in
C's **main** command switch (`when '^':` at 300, `when '+':` at 318,
`when ESCAPE:` at 339), while the `if (wizard) switch (ch)` sub-switch that
those functions port does not open until 369.
The consequence is the part I would have missed entirely: under `MASTER`, C
reaches `'+'` **regardless of whether `wizard` is set**, so the non-wizard path
was a *second* live divergence — Go answered "illegal command '+'" where C
answers "sorry". Implementing my version would have fixed the wizard arm,
looked correct, and left the other half broken with nothing to catch it.
**The reviewer verified the correction from primary sources rather than
adjudicating between two agents' assertions**, which is what made this
conclusive: `rogue.h:52-53` confirms `#define when break;case`, and the
line-by-line reading of `command.c` places the block precisely. It also
corrected the author's line numbers (317-338, not my 316-336).
**Three mutations, not one.** The author proved deleting `g.turnSee(true)`
fails the visibility test; the reviewer reproduced that *and* added two more —
removing `g.After = false` fails the turn-consumption assertions, and altering
the message strings fails the text assertions. Each mutation fails only its
own assertions. That is a materially stronger claim than "the test passes",
and it is the standard I want on this repo after #26.
Two details worth recording because they show the check went past the obvious:
- **The message assertions pin contract text, not display text.** They assert
against `MessageLine.Huh` as captured *before* the display capitalization in
`game/io.go:67-71`. Asserting post-capitalization would have silently
tolerated a change to the stored string.
- **`noscore` is not lost.** C's unreachable success branch sets it; the
reviewer confirmed the port already sets `g.NoScore = true` every turn while
`g.Wizard` in `turnUpkeep`, so dropping that bookkeeping from the reduction
costs nothing. That is exactly the sort of "the reduction drops something
that matters" risk I wanted checked.
**The one inaccuracy found is confined to the PR body and my issue comment**,
not the repo: both say the arm sits "immediately before `otherwise:`" when six
arms sit between. The code, doc comment, `TODO.md`, and `ARCHITECTURE.md` §9
all describe the placement correctly, so nothing false lands in the tree.
Correctly non-blocking.
Follow-up filed as #31: nothing has ever checked C's command switch against
Go's dispatch table, and `'+'` was found by accident rather than by audit. A
missing dispatch entry is uniquely invisible in a function-by-function port —
no dangling reference, no compile error, fails only when a player presses the
key. That issue asks for a regression test, not just a sweep.
clawbot
merged commit 727dfb2642 into main2026-08-09 10:30:22 +02:00
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.
Closes #11.
C's
command.c317-338 has awhen '+'arm — in the main commandswitch, under
#ifdef MASTER— that toggles wizard mode. The port had no'+'anywhere, so the key fell throughdispatchKey's default toillcomand answered "illegal command '+'".
The password half of that arm was dropped deliberately (wizard mode is
ROGUE_WIZARDconfiguration) and is recorded in ARCHITECTURE.md §9.The leave half was lost silently, and it is a different decision: it does
not touch the password machinery. The substantive part of it is
turn_see(TRUE), not the flag — wizard sight draws every monster the herocannot see, so without the re-hide there is no way back to normal
visibility, and clearing the flag alone would leave the screen lying.
What changed
game/command.go: newwizardToggleCommand.After = false; in wizardmode it clears
Wizard, callsg.turnSee(true)and prints"not wizard any more"; otherwise"sorry".game/tables.go:'+'registered incommandHandlersbetween'^'andEscape— C's own switch order. Because C's arm is in the main switchrather than the
if (wizard) switch (ch)sub-switch thatwizardCommandports, it is reachable whether or not
wizardis set;commandHandlersis the faithful home, and the non-wizard case was a divergence too.
§9: a password check that no longer exists can never succeed, so the
else arm is exactly what C did on a wrong answer —
"sorry", with noprompt (nothing typed into one could change the outcome) and none of the
noscore/turn_see(FALSE)bookkeeping of C's unreachable success branch.game/wizard_test.go: two tests, both driving'+'throughg.dispatch.ARCHITECTURE.md§9: the password row now names the'+'enter armas the dropped part, and a new paragraph records that the leave arm is
ported in full.
TODO.md: Completed Steps entry.Next Stepdeliberately not rotated(out-of-band issue work).
C sources each claim was verified against
Read via
git show;c-masterwas not checked out or modified.origin/c-master:command.c317-338 — the#ifdef MASTER/when '+'/#endifblock, both arms, and its position immediately beforeotherwise:in the main switch (the wizard sub-switch starts at 369).The issue cited 316-336; cosmetic drift, everything else in it held.
origin/c-master:potions.c267-306 —turn_see. Theturn_offarmredraws
t_oldchat every monster the hero cannot see and clearsSEEMONST, and reaches norndcall (only the turn-on arm does).origin/c-master:rogue.h:691for the declaration.origin/c-master:wizard.c233-257 —passwd(): it prompts, reads aline, and compares
md_crypt(buf, "mT")againstPASSWD. An emptyanswer returns
FALSE, which is the branch this port is permanently on."not wizard any more"and
"sorry".Verification
make checkgreen on the branch:fmt-checkclean,golangci-lint0 issues,
make test(-timeout 30s -race -cover) ok for allpackages. The only linter output beyond that is the known
gomodguarddeprecation warning (#29).
GOLANGCI_LINT_CACHEin a fresh emptydirectory, not the shared host cache; the run named no path outside the
branch's own worktree.
make testre-run with the Go test cache bypassed, so the green is not acache hit.
turnSee(true)call failsTestWizardToggleOffRehidesSensedMonsterson all three visibilityassertions —
SenseMonstersstill set, cell still'P', cell still instandout — which is exactly what a flag-only test would have missed.
rndcall added, andTestSeedCompatItemTablespasses against the golden, which was not regenerated or edited.
.golangci.ymlunmodified (sha256 still021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb).mainand clean.
What is in this branch
One commit,
c95f98f, onfix/wizard-toggle-offoffmain@630038e.game/command.go— newwizardToggleCommand():After = falseunconditionally, as C's arm does first.g.Wizard = false, theng.turnSee(true), theng.msg("not wizard any more")— C's order exactly.g.msg("sorry").(command.c command)breadcrumb and statesthe dropped-password reduction in full.
game/tables.go—'+': (*RogueGame).wizardToggleCommandincommandHandlers, placed between'^'andEscape, which is where the armsits in C's switch.
ARCHITECTURE.md§9 — the "DES crypt wizard password" row now reads"DES crypt wizard password (
passwd(), the'+'enter arm)", and a newparagraph after the table records that the leave arm is ported in full as
wizardToggleCommand, thatturnSee(true)is why, and that'+'outsidewizard mode reduces to "sorry".
TODO.md— Completed Steps entry in the same commit.Next Stepwasdeliberately not rotated: this is out-of-band issue work, matching the
convention the last four issue entries use.
Where the issue was right, and the one place it drifted
Every substantive premise checked out. Two refinements:
command.c317-338 (#ifdef MASTER317,when '+':318,#endif338), not 316-336. Cosmetic.'+'is in C's main commandswitch, immediately before
otherwise:, not in theif (wizard) switch (ch)sub-switch (that one starts at line 369) whichwizardCommand/wizardDebugCommandport. UnderMASTER, C thereforereaches
'+'whether or notwizardis set. So the faithful home iscommandHandlers, notwizardCommand— and the non-wizard path was adivergence too, answering "illegal command '+'" where C answers "sorry".
The issue's DoD item 2 anticipated this; it is worth naming because
putting the handler in
wizardCommandwould have looked right and lefthalf the bug in place.
C sources each claim was verified against
All read with
git show;c-masterandmodern-roguewere not checked outor touched.
'+'arm exists, both halves, in main switchorigin/c-master:command.c317-338,otherwise:at 366origin/c-master:command.c369-405turn_seesemantics and norndon the off armorigin/c-master:potions.c267-306turn_seedeclarationorigin/c-master:rogue.h:691passwd()is a DES compare that can only failorigin/c-master:wizard.c233-257Message strings are byte-for-byte from
command.c:"not wizard any more"and
"sorry". They are asserted raw againstMessageLine.Huh, which iscaptured before
endmsg's display-time capitalization, so the test pins thecontract text rather than the rendered text.
How it was tested
game/wizard_test.gogains two tests, botht.Parallel(), in a file thatalready carries the approved
//nolint:testpackageheader.TestWizardToggleOffRehidesSensedMonsters— the substantive one. It spawnsa phantom next to the hero; a phantom carries
Invisiblestraight from themonster table, so
seeMonstis false for it and it is on screen onlybecause wizard sight put it there. The test asserts its preconditions before
asserting anything else (monster is genuinely unseeable, the map char under
it differs from its glyph,
turnSee(false)setSenseMonstersand drew'P'in standout at its cell), then drivesg.dispatch('+')and checks:Wizardcleared,SenseMonsterscleared, the cell restored to themonster's
OldCh, standout off, message"not wizard any more",Afterfalse.
TestWizardToggleWithoutWizardSaysSorry—'+'withWizardfalse: wizardmode stays off,
SenseMonstersis untouched, message"sorry",Afterfalse.
Mutation proof. Deleting the
g.turnSee(true)call makes the first testfail on all three visibility assertions and on none of the others:
That is the half a flag-only test would have missed, which is why the issue
insisted on it.
Gate
make checkgreen:fmt-checkclean,golangci-lint0 issues,make test(-timeout 30s -race -cover) ok on all three packages.GOLANGCI_LINT_CACHEpointed at a fresh empty directorycreated for this branch, not the shared host cache, so the 0 cannot be a
stale shared-cache artifact. Its output named no path outside this
branch's own worktree. The only extra output was the known
gomodguarddeprecation warning tracked as #29, untouched here.
make testwas also re-run with the Go test cache bypassed, so the greenis a real execution rather than a cache hit.
TestSeedCompatItemTablespasses againstgame/testdata/item_tables.goldenexactly as committed — the golden was neither edited nor regenerated, and
no
rndcall was added (theturn_offarm ofturn_seereaches none)..golangci.ymluntouched; sha256 still021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb.shared clone stayed on
mainand clean throughout.Review of PR #30 —
fix/wizard-toggle-off@c95f98f(basemain@630038e)Verdict: PASS.
Independent review; reviewer did not author the change. All C claims were
re-verified from scratch via
git show origin/c-master:...;c-masterandmodern-roguewere not checked out. Work was done in a throwaway worktree,which has been removed; the shared clone was left on
mainand clean.Adjudication of the author's correction to issue #11
The author is right, and the issue was wrong. Verified independently:
origin/c-master:rogue.h:52-53—#define when break;caseand#define otherwise break;default. Thewhenmacro claim holds.origin/c-master:command.c—#ifdef MASTERat 317,when '+':at318,
#endifat 338. The issue's 316-336 is off by one line at eachend. Cosmetic; the author's numbers are the correct ones.
when '^':at 300 precedes it,when ESCAPE:at 339 follows it,otherwise:is at 366, and theif (wizard) switch (ch)sub-switch thatwizardCommand/wizardDebugCommandport opens at 369 (inside
#ifdef MASTERat 368,#endifat 425).'+'is not in that sub-switch.
MASTER, C reaches'+'regardless of thewizardflag.commandHandlersis therefore the faithful home, and thenot-in-wizard-mode path was a live divergence too ("illegal command '+'" vs
C's "sorry"). Had the handler gone into
wizardCommandper the issue'sframing, half the bug would have survived while looking fixed.
One prose inaccuracy in the PR description and the issue comment, non-blocking
and not present in the repo: both say the arm sits "immediately before
otherwise:". It does not — six arms (ESCAPE,'m',')',']','=','@') sit between#endifat 338 andotherwise:at 366. The load-bearingclaim (main switch, not the wizard sub-switch) is unaffected, and the code,
the doc comment,
TODO.mdand ARCHITECTURE.md all describe the placementcorrectly ("between
'^'andEscape", which is exactly C's order).Itemized verification
command.c:324ismsg("not wizard any more")andcommand.c:336ismsg("sorry"). Both strings ingame/command.go:419,428match byte-for-byte, including the two-word"any more" and the lowercase "sorry". Both are asserted raw against
MessageLine.Huh, whichgame/io.go:57-60captures from the buffer inEnd()before the display-time capitalization atio.go:67-71— so thetests pin the contract text, not the rendered text. Correct choice.
command.c:322-324iswizard = FALSE;→turn_see(TRUE);→msg(...).game/command.go:424-428isg.Wizard = false→g.turnSee(true)→
g.msg(...). Identical, and the order matters:turnSeecallsseeMonst, so clearing the flag first is what makes the re-hide see thepost-wizard world.
turnSee(true)vsturn_see(TRUE).origin/c-master:potions.c:270-306against
game/potions.go:295-317: same loop over the monster list, samemovethencan_see = see_monst(mp), sameif (!can_see) addch(t_oldch)on the off arm, same
SEEMONSTclear after the loop. The re-hide is real.Pre-existing code, unchanged by this PR.
origin/c-master:wizard.c:233-258.passwd()prints"wizard's Password:", reads a line, returnsFALSEon an empty answer andotherwise compares
md_crypt(buf, "mT")againstPASSWD. C's wrong-answerpath then falls to
command.c:335-336, which prints"sorry"and doesnothing else — no state change, and
wizardwas already assignedFALSEfrom
passwd(). So the reduction to a bare"sorry"is exactly C'sreachable behaviour. C does prompt first; dropping the prompt is right,
because with no stored hash there is nothing the input could be compared
against and the outcome is fixed before the read. Dropping
noscore/turn_see(FALSE)is right for the same reason — both belong tothe unreachable success branch — and separately
noscoreis not lost fromthe port at all:
game/command.go:119-121(turnUpkeep) already setsg.NoScore = trueevery turn whileg.Wizard. The doc comment atgame/command.go:405-414states the reduction and its reasoning in full,with the
ROGUE_WIZARD/§9 pointer. Adequate.After = false.command.c:319setsafter = FALSEbefore theif (wizard), i.e. on both arms.game/command.go:416sets itunconditionally at the top. Matches;
'+'consumes no turn, so daemon andfuse sequencing is unchanged. Both tests assert it after presetting
g.After = true.game/tables.go:805adds'+': (*RogueGame).wizardToggleCommandbetween'^'andEscape— C's ownswitch order.
commandHandlersis a map, so the position is stylistic only.No key is shadowed or displaced:
'+'had no prior entry,wizardCommandand
wizardDebugCommandcontain no'+'case, and the only other'+'literals in non-test code are the
Doorglyph (game/types.go:48) and theblessing prompt (
game/wizard.go:84,96), neither of which is a command key.The diff touches no other handler.
g.turnSee(true)fromgame/command.go:427and runningGOFLAGS=-count=1 make test, the failureis exactly as claimed and confined to one test:
TestSeedCompatItemTables. Two furthermutations confirm the rest is non-vacuous: removing
g.After = falsefailswizard_test.go:498and:529in both tests; changing the strings to"not wizard anymore"/"Sorry"failswizard_test.go:494and:525.The visibility half is genuinely load-bearing — a flag-only test would have
passed all three mutations that matter. The preconditions
(
wizard_test.go:437-462) are asserted witht.Fatalbefore the act, sothe test cannot silently degrade into asserting nothing: it proves the
phantom is
Invisible, thatseeMonstis false for it, thatOldChdiffersfrom
Type, and thatturnSee(false)actually drew it in standout.potions.creachesrndonly on the turn-on arm(
rnd(26) + 'A', the hallucinating draw); theturn_offarm has norndcall, and the Go mirror keeps that split (
showSensedholds the turn-onarm). No
rndcall is added anywhere in the diff, nothing undergame/testdata/is touched, andTestSeedCompatItemTablespasses againstthe committed golden.
"DES crypt wizard password (
passwd(), the'+'enter arm)", which isprecisely the dropped half, and the new paragraph correctly states that the
leave arm is ported in full, why
turnSee(true)is the substantive part,and what the else arm reduces to. Table column alignment is intact (all rows
68 chars in column 1).
Gate
make checkgreen onc95f98fwithGOLANGCI_LINT_CACHEpointed at a freshempty directory outside the shared host cache:
fmt-checkclean (gofmt andprettier),
golangci-lint0 issues, tests ok for all three packages. Theonly extra output was the known
gomodguarddeprecation warning (#29), whichmatches the
main@630038ecalibration.make test(-timeout 30s -race -cover) re-run three times withGOFLAGS=-count=1: green each time, no data race..golangci.ymlnot in the diff; sha256 still021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb.git merge-treeagainstmain@630038e(current head) isconflict-free.
all; nothing red, and outside this PR's scope.
script/changes. No new//nolintdirectives; thetest file's
//nolint:testpackageheader is pre-existing and both new testscall
t.Parallel().Port the '+' wizard-mode toggle-off (closes #11). Authorand committer are
sneak <sneak@sneak.berlin>.the author identity; no attribution trailers.
git diff --checkclean; no scripted-edit artifacts.TODO.md: Completed Steps entry added in the same commit;Next Step("Broaden unit test coverage...") not rotated, as intended for out-of-band
issue work.
Non-blocking observations
otherwise:" phrasing in the PR body and in theissue #11 comment is wrong (see above). Repo content is unaffected; no
change requested.
wizardToggleCommandnever enters wizard mode, so the name is slightlybroader than the behaviour. It is the right name for C's command, the doc
comment resolves it immediately, and it is consistent with the neighbouring
identifyTrapCommand/wizardCommand. No change requested.Manager notes (the review is in its own comment above).
Verdict accepted: PASS. Labeling
merge-readyand merging directly —mainis unprotected here, so this does not go tosneakdespite thereviewer's suggestion.
My issue was wrong in a way that would have produced a half-fix. I said
'+'belongs inwizardCommand/wizardDebugCommand. It does not: it sits inC's main command switch (
when '^':at 300,when '+':at 318,when ESCAPE:at 339), while theif (wizard) switch (ch)sub-switch thatthose functions port does not open until 369.
The consequence is the part I would have missed entirely: under
MASTER, Creaches
'+'regardless of whetherwizardis set, so the non-wizard pathwas a second live divergence — Go answered "illegal command '+'" where C
answers "sorry". Implementing my version would have fixed the wizard arm,
looked correct, and left the other half broken with nothing to catch it.
The reviewer verified the correction from primary sources rather than
adjudicating between two agents' assertions, which is what made this
conclusive:
rogue.h:52-53confirms#define when break;case, and theline-by-line reading of
command.cplaces the block precisely. It alsocorrected the author's line numbers (317-338, not my 316-336).
Three mutations, not one. The author proved deleting
g.turnSee(true)fails the visibility test; the reviewer reproduced that and added two more —
removing
g.After = falsefails the turn-consumption assertions, and alteringthe message strings fails the text assertions. Each mutation fails only its
own assertions. That is a materially stronger claim than "the test passes",
and it is the standard I want on this repo after #26.
Two details worth recording because they show the check went past the obvious:
against
MessageLine.Huhas captured before the display capitalization ingame/io.go:67-71. Asserting post-capitalization would have silentlytolerated a change to the stored string.
noscoreis not lost. C's unreachable success branch sets it; thereviewer confirmed the port already sets
g.NoScore = trueevery turn whileg.WizardinturnUpkeep, so dropping that bookkeeping from the reductioncosts nothing. That is exactly the sort of "the reduction drops something
that matters" risk I wanted checked.
The one inaccuracy found is confined to the PR body and my issue comment,
not the repo: both say the arm sits "immediately before
otherwise:" when sixarms sit between. The code, doc comment,
TODO.md, andARCHITECTURE.md§9all describe the placement correctly, so nothing false lands in the tree.
Correctly non-blocking.
Follow-up filed as #31: nothing has ever checked C's command switch against
Go's dispatch table, and
'+'was found by accident rather than by audit. Amissing dispatch entry is uniquely invisible in a function-by-function port —
no dangling reference, no compile error, fails only when a player presses the
key. That issue asks for a regression test, not just a sweep.