WIP: restore lost C behaviors (schtick message, forced redraw, greeting)

Incomplete checkpoint, preserved after the implementing session hit a
capacity limit partway through mutation verification. NOT reviewed and NOT
gate-clean: make fmt has not been run, so fmt-check currently fails on
ARCHITECTURE.md and TODO.md.

Work still outstanding before this is fit for review:
  - run make fmt and fold the result in
  - finish mutation-proving each of the three behaviors
  - verify every message string byte-for-byte against the C sources
  - confirm TestSeedCompatItemTables passes with its golden untouched

Refs #13.
This commit is contained in:
2026-08-09 09:55:38 +00:00
parent 727dfb2642
commit 3f0a14c5e6
15 changed files with 401 additions and 9 deletions

52
TODO.md
View File

@@ -34,6 +34,58 @@ wizard commands).
# Completed Steps
- 2026-08-09 Three small lost C behaviors (`fix/lost-c-behaviors`, closes #13):
grouped because each is a few lines and all are "restore something the port
dropped silently". (1) **"what a bizarre schtick!"**, `sticks.c` 237 — the
`otherwise` arm that closes `do_zap`'s switch, which `doZap` had turned into
doing nothing at all. Two things about it are easy to get wrong and are why
the fix is not one line. It is under `#ifdef MASTER`, **not** under a
`wizard` test, so in the MASTER build this port is it printed for every
player — gating it on `g.Wizard` would be issue #11's trap in reverse. And
`WS_NOP` is a case of that switch in its own right (`when WS_NOP: break;`),
so "no handler ran" cannot be the trigger: the wand of nothing does nothing
_quietly_, and only a kind C had no case for is bizarre. Since C's switch
covers all 14 `WS_` values, its `otherwise` is reachable only for an
`o_which` outside the table, which is exactly what `Object.hasValidWhich`
already screens for — so the split needed no new state, just a three-way
switch on handler / valid-Which / neither. All three arms fall through to
`obj.Charges--`, as C's do: even the bizarre schtick costs a charge. Replaces
the deferral comment PR #20 left there. (2) **`CTRL('R')` now actually
redraws.** C is `after = FALSE; clearok(curscr, TRUE); wrefresh(curscr);`
(`command.c` 288-291); the port called `g.refresh()`, the ordinary diffing
blit, **which cannot fix the only situation the command exists for** — a
screen corrupted by something else's output leaves the game's record of it
still correct, so the diff sends nothing and the corruption stays. New
`Terminal.Repaint` (tcell `Screen.Sync`, which discards tcell's record of the
terminal instead of diffing against it), `Screen.Repaint`, `g.repaint()`;
three implementations to update, the same shape as PR #26's `ReadChar`
change, so no split was needed. Named for the curses operation, not for
tcell: the interface is the game's abstraction. It repaints what was last
rendered — C repainted `curscr`, not `stdscr` — so it takes no window, and
the arm drops the `refresh()` C never had there (`command` refreshes before
the next key read anyway). (3) **The startup greeting**, `main.c` 107-113,
which existed nowhere in the tree. New `game.Greeting`, printed by
`cmd/rogue/main.go` before `term.New()` — the port's `initscr()`. Only the
wizard wording is `#ifdef MASTER`; the other is unconditional. The `%d` is
`dnum`, which `main.c` has just assigned to `seed`, so it is `Params.Seed`.
Two placement details the issue did not mention and the tests now pin: the
printf sits **after** `parse_opts`, so a ROGUEOPTS `name=` is what the player
is greeted by and the account name is only the fallback (`Greeting` re-runs
`ParseOpts`, which does nothing but assign into fields — no RNG, no screen);
and it sits after the `-s`/`-d` handling and after `restore()`, which never
returns, so a resumed game does not announce that a dungeon is being dug
(`digsNewDungeon`). No RNG call is added on any path and nothing under
`game/testdata/` moved; `TestSeedCompatItemTables` is green against the
untouched golden. Mutation-proved, each new behaviour deleted in turn and
only its own test failing: dropping the message arm fails
`TestZapUnhandledWandSaysBizarreSchtick`; extending it to `WandNothing` fails
`TestZapWandOfNothingIsSilent`; putting `g.refresh()` back fails
`TestRedrawCommandForcesFullRepaint`; swapping the two wordings, or the
ROGUEOPTS name for the account name, fails `TestGreeting`; greeting on the
restore path fails `TestDigsNewDungeon`. ARCHITECTURE.md §5.3 gains `Repaint`
and the paragraph on why a blit cannot substitute for it. `Next Step`
deliberately not rotated: out-of-band issue work.
- 2026-08-09 The `'+'` wizard-mode toggle (`fix/wizard-toggle-off`, closes #11):
C's `command.c` 317-338 has a `when '+'` arm that leaves wizard mode —
`wizard = FALSE`, `turn_see(TRUE)`, `msg("not wizard any more")` — and the