Restore three small C behaviors: bizarre-schtick message, forced CTRL-R repaint, startup greeting #13

Closed
opened 2026-08-09 03:44:58 +02:00 by clawbot · 2 comments
Collaborator

Problem

Three small, self-contained behaviors from 5.4.4 are missing. Each is a
few lines; grouping them into one commit because they are all "restore a lost
C behavior" and each is too small to justify its own PR.

1. "what a bizarre schtick!" is gone

C sticks.c:230-232 ends the zap dispatch with
otherwise: msg("what a bizarre schtick!"). Go's doZap
(game/sticks.go:34-38) silently does nothing for an unhandled wand kind.

Note WandNothing (C WS_NOP) correctly has no handler and still decrements
charges, matching C — so the fix must distinguish "deliberately does nothing"
from "unhandled kind", and only the latter gets the message.

2. CTRL('R') redraw is weaker than C

C does clearok(curscr, TRUE); wrefresh(curscr) — a forced full repaint,
which is the entire point of the command. Go's handler (game/tables.go,
CTRL('R') entry) calls g.refresh(), the ordinary diffing refresh. If the
screen is actually corrupted, the Go version will not fix it, because the
diff engine believes the screen already matches.

This will need a forced-repaint primitive on the Terminal interface
(tcell's Screen.Sync() is the equivalent of clearok+wrefresh).

3. Startup greeting missing

C main.c:107-113 prints, before initscr():

  • wizard: "Hello %s, welcome to dungeon #%d"
  • normal: "Hello %s, just a moment while I dig the dungeon..."

Neither string exists anywhere in the Go tree (grep -rn 'Hello\|dig the dungeon'
→ no hits). Both are user-visible and part of the game's texture.

Definition of done

  1. Unhandled wand kinds emit C's exact message; WandNothing still does not.
  2. CTRL('R') forces a full repaint through a new Terminal method
    (implemented via tcell Sync() in term/tcell.go, and in the headless
    test terminal).
  3. The startup greeting is emitted at the right point in the sequence, with
    both wizard and normal wording, and the dungeon seed number matching C's.
  4. Tests: the schtick message is asserted for an unhandled kind and asserted
    absent for WandNothing; the greeting is asserted for both modes; the
    forced-repaint method is asserted to be called by the CTRL('R') handler.
  5. make check fully green.
  6. TODO.md updated in the same commit.
  7. Commit title ends with (closes #N).

Implementation requirements

  • Read git show origin/c-master:sticks.c, …:main.c, and the CTRL('R')
    arm of …:command.c first. Do NOT check out or modify origin/c-master.
  • Exact C message text, including original spelling and punctuation
    ("schtick", the trailing ...). Message text is contract here — the repo
    already keeps C's "missle" typo deliberately.
  • Emitting the greeting must not perturb RNG call order. The seed-compat test
    (TestSeedCompatItemTables) will catch it if it does — make sure that test
    still passes, and do not regenerate its golden to make it pass.
  • Adding a method to the Terminal interface means updating both
    implementations (term/tcell.go and the headless testTerm in
    game/term_test.go).
  • Tests need t.Parallel() and the //nolint:testpackage header.
  • make targets only. Do NOT modify .golangci.yml.
  • Never mention Claude or Anthropic anywhere.

Splitting

If item 2 (the Terminal interface change) turns out to be more invasive than
it looks, split it into its own follow-up issue and deliver 1 and 3 — say so
in the PR rather than silently dropping it.

## Problem Three small, self-contained behaviors from 5.4.4 are missing. Each is a few lines; grouping them into one commit because they are all "restore a lost C behavior" and each is too small to justify its own PR. ### 1. `"what a bizarre schtick!"` is gone C `sticks.c:230-232` ends the zap dispatch with `otherwise: msg("what a bizarre schtick!")`. Go's `doZap` (`game/sticks.go:34-38`) silently does nothing for an unhandled wand kind. Note `WandNothing` (C `WS_NOP`) correctly has no handler and still decrements charges, matching C — so the fix must distinguish "deliberately does nothing" from "unhandled kind", and only the latter gets the message. ### 2. `CTRL('R')` redraw is weaker than C C does `clearok(curscr, TRUE); wrefresh(curscr)` — a **forced full repaint**, which is the entire point of the command. Go's handler (`game/tables.go`, `CTRL('R')` entry) calls `g.refresh()`, the ordinary diffing refresh. If the screen is actually corrupted, the Go version will not fix it, because the diff engine believes the screen already matches. This will need a forced-repaint primitive on the `Terminal` interface (tcell's `Screen.Sync()` is the equivalent of `clearok`+`wrefresh`). ### 3. Startup greeting missing C `main.c:107-113` prints, before `initscr()`: - wizard: `"Hello %s, welcome to dungeon #%d"` - normal: `"Hello %s, just a moment while I dig the dungeon..."` Neither string exists anywhere in the Go tree (`grep -rn 'Hello\|dig the dungeon'` → no hits). Both are user-visible and part of the game's texture. ## Definition of done 1. Unhandled wand kinds emit C's exact message; `WandNothing` still does not. 2. `CTRL('R')` forces a full repaint through a new `Terminal` method (implemented via tcell `Sync()` in `term/tcell.go`, and in the headless test terminal). 3. The startup greeting is emitted at the right point in the sequence, with both wizard and normal wording, and the dungeon seed number matching C's. 4. Tests: the schtick message is asserted for an unhandled kind and asserted **absent** for `WandNothing`; the greeting is asserted for both modes; the forced-repaint method is asserted to be called by the `CTRL('R')` handler. 5. `make check` fully green. 6. `TODO.md` updated in the same commit. 7. Commit title ends with ` (closes #N)`. ## Implementation requirements - Read `git show origin/c-master:sticks.c`, `…:main.c`, and the `CTRL('R')` arm of `…:command.c` first. Do NOT check out or modify `origin/c-master`. - **Exact** C message text, including original spelling and punctuation ("schtick", the trailing `...`). Message text is contract here — the repo already keeps C's "missle" typo deliberately. - Emitting the greeting must not perturb RNG call order. The seed-compat test (`TestSeedCompatItemTables`) will catch it if it does — make sure that test still passes, and do not regenerate its golden to make it pass. - Adding a method to the `Terminal` interface means updating both implementations (`term/tcell.go` and the headless `testTerm` in `game/term_test.go`). - Tests need `t.Parallel()` and the `//nolint:testpackage` header. - `make` targets only. Do NOT modify `.golangci.yml`. - Never mention Claude or Anthropic anywhere. ## Splitting If item 2 (the `Terminal` interface change) turns out to be more invasive than it looks, split it into its own follow-up issue and deliver 1 and 3 — say so in the PR rather than silently dropping it.
Author
Collaborator

C verification first

Read git show origin/c-master:sticks.c, :main.c, :command.c, :rogue.h
(remembering #define when break;case / #define otherwise break;default).
All three behaviours are real. Four corrections to the issue text:

  1. The message is at sticks.c:237, not 230-232, and — the part that matters —
    the otherwise arm is inside #ifdef MASTER, not gated on the runtime
    wizard flag:

    	when WS_NOP:
    	    break;
    #ifdef MASTER
    	otherwise:
    	    msg("what a bizarre schtick!");
    #endif
        }
        obj->o_charges--;
    

    This port compiles as MASTER (precedent: the '+' arm from #11), so the
    message is unconditional here. It must not be wrapped in if g.Wizard
    — that is exactly the #11 trap in reverse.

  2. C's switch covers all 14 WS_ values (0..13), so otherwise is reachable
    only for an o_which outside the table. That is precisely what
    Object.hasValidWhich() already screens for, so the "unhandled kind" vs.
    WandNothing split needs no new state.

  3. main.c:107-113: only the wizard arm is #ifdef MASTER; the normal
    greeting is unconditional. Verbatim:

    #ifdef MASTER
        if (wizard)
    	printf("Hello %s, welcome to dungeon #%d", whoami, dnum);
        else
    #endif
    	printf("Hello %s, just a moment while I dig the dungeon...", whoami);
        fflush(stdout);
    

    No trailing newline in either. dnum is the dungeon/seed number (seed = dnum a few lines up) = Params.Seed.

  4. Two placement details the issue does not mention. The printf sits after
    parse_opts(env), so whoami reflects a ROGUEOPTS name= setting, not
    just the username; and it sits after the -s/-d handling (both exit()
    first) and after restore() (which never returns), so only a new-dungeon
    start greets.

command.c:288-291 confirms item 2 exactly as described: after = FALSE; clearok(curscr,TRUE); wrefresh(curscr); — no refresh() of stdscr, and the
command loop refreshes before the next key read anyway (command.go:113).

Plan

  1. game/sticks.go doZap — replace the if h != nil with a three-way
    switch: handler present, else hasValidWhich() (WS_NOP, deliberately
    silent), else g.msg("what a bizarre schtick!"). All three fall through to
    obj.Charges--, as C's switch does. Replaces PR #20's deferral comment.

  2. Forced repaint. Add Repaint() to the Terminal interface
    (game/screen.go) — named for what curses does, since the interface is the
    game's abstraction, not tcell's — plus Screen.Repaint and a g.repaint()
    wrapper. term.Tcell.Repaint is t.screen.Sync(). Test terminals:
    testTerm (counts calls), blockingTerm. Three implementations total, so
    the change is no more invasive than PR #26's ReadChar signature change:
    no split expected. tables.go's CTRL('R') arm drops g.refresh() for
    g.repaint(), matching C.

  3. Greeting. game.Greeting(Params) string next to New in
    game/game.go (message text belongs with the rest of the game text), which
    resolves whoami through ParseOpts the way C's ordering demands.
    cmd/rogue/main.go prints it before term.New() — the Go equivalent of
    "before initscr()" — gated on a small digsNewDungeon(deathDemo, args)
    helper so -s, -d and restore stay silent, as in C.

Tests

  • game/effects_test.go: schtick asserted present for a malformed wand
    (malformed(KindWand)) and asserted absent for WandNothing; charge
    spent in both.
  • new game/command_test.go: CTRL('R') bumps testTerm's repaint count and
    leaves After false.
  • new game/greeting_test.go: both wordings byte-for-byte, the seed in the
    wizard one, ROGUEOPTS name= honoured, no trailing newline.
  • cmd/rogue/main_test.go: digsNewDungeon truth table.

No RNG call is added on any path (Greeting runs before New, and
ParseOpts never reaches rnd), so TestSeedCompatItemTables stays green
against the untouched golden. TODO.md gets a Completed Steps entry in the
same commit; Next Step deliberately not rotated (out-of-band issue work).
ARCHITECTURE.md 5.3's Terminal listing gains the new method.

## C verification first Read `git show origin/c-master:sticks.c`, `:main.c`, `:command.c`, `:rogue.h` (remembering `#define when break;case` / `#define otherwise break;default`). All three behaviours are real. Four corrections to the issue text: 1. The message is at `sticks.c:237`, not 230-232, and — the part that matters — the `otherwise` arm is inside `#ifdef MASTER`, **not** gated on the runtime `wizard` flag: ``` when WS_NOP: break; #ifdef MASTER otherwise: msg("what a bizarre schtick!"); #endif } obj->o_charges--; ``` This port compiles as MASTER (precedent: the `'+'` arm from #11), so the message is unconditional here. It must **not** be wrapped in `if g.Wizard` — that is exactly the #11 trap in reverse. 2. C's switch covers all 14 `WS_` values (0..13), so `otherwise` is reachable only for an `o_which` outside the table. That is precisely what `Object.hasValidWhich()` already screens for, so the "unhandled kind" vs. `WandNothing` split needs no new state. 3. `main.c:107-113`: only the **wizard** arm is `#ifdef MASTER`; the normal greeting is unconditional. Verbatim: ``` #ifdef MASTER if (wizard) printf("Hello %s, welcome to dungeon #%d", whoami, dnum); else #endif printf("Hello %s, just a moment while I dig the dungeon...", whoami); fflush(stdout); ``` No trailing newline in either. `dnum` is the dungeon/seed number (`seed = dnum` a few lines up) = `Params.Seed`. 4. Two placement details the issue does not mention. The printf sits **after** `parse_opts(env)`, so `whoami` reflects a ROGUEOPTS `name=` setting, not just the username; and it sits after the `-s`/`-d` handling (both `exit()` first) and after `restore()` (which never returns), so only a new-dungeon start greets. `command.c:288-291` confirms item 2 exactly as described: `after = FALSE; clearok(curscr,TRUE); wrefresh(curscr);` — no `refresh()` of stdscr, and the command loop refreshes before the next key read anyway (`command.go:113`). ## Plan 1. **`game/sticks.go` `doZap`** — replace the `if h != nil` with a three-way switch: handler present, else `hasValidWhich()` (WS_NOP, deliberately silent), else `g.msg("what a bizarre schtick!")`. All three fall through to `obj.Charges--`, as C's switch does. Replaces PR #20's deferral comment. 2. **Forced repaint.** Add `Repaint()` to the `Terminal` interface (`game/screen.go`) — named for what curses does, since the interface is the game's abstraction, not tcell's — plus `Screen.Repaint` and a `g.repaint()` wrapper. `term.Tcell.Repaint` is `t.screen.Sync()`. Test terminals: `testTerm` (counts calls), `blockingTerm`. Three implementations total, so the change is no more invasive than PR #26's `ReadChar` signature change: **no split expected**. `tables.go`'s `CTRL('R')` arm drops `g.refresh()` for `g.repaint()`, matching C. 3. **Greeting.** `game.Greeting(Params) string` next to `New` in `game/game.go` (message text belongs with the rest of the game text), which resolves `whoami` through `ParseOpts` the way C's ordering demands. `cmd/rogue/main.go` prints it before `term.New()` — the Go equivalent of "before `initscr()`" — gated on a small `digsNewDungeon(deathDemo, args)` helper so `-s`, `-d` and restore stay silent, as in C. ## Tests - `game/effects_test.go`: schtick asserted present for a malformed wand (`malformed(KindWand)`) and asserted **absent** for `WandNothing`; charge spent in both. - new `game/command_test.go`: `CTRL('R')` bumps `testTerm`'s repaint count and leaves `After` false. - new `game/greeting_test.go`: both wordings byte-for-byte, the seed in the wizard one, ROGUEOPTS `name=` honoured, no trailing newline. - `cmd/rogue/main_test.go`: `digsNewDungeon` truth table. No RNG call is added on any path (`Greeting` runs before `New`, and `ParseOpts` never reaches `rnd`), so `TestSeedCompatItemTables` stays green against the untouched golden. `TODO.md` gets a Completed Steps entry in the same commit; Next Step deliberately not rotated (out-of-band issue work). ARCHITECTURE.md 5.3's `Terminal` listing gains the new method.
Author
Collaborator

Status: work in progress is preserved on fix/lost-c-behaviors, but it is
NOT ready for review.

The implementing session hit a capacity limit partway through mutation
verification and terminated. It left roughly 300 lines across 13 files plus
two new test files uncommitted in a temporary worktree — one prune away
from being lost. I have committed that as an explicit WIP checkpoint
(3f0a14c) and pushed the branch so nothing is local-only. No PR is open and
the branch is deliberately not labelled needs-review.

The checkpoint does not pass the gate. make fmt was never run, so
fmt-check fails on ARCHITECTURE.md and TODO.md. That is expected for a
mid-task snapshot; it is recorded here so nobody mistakes a pushed branch for
finished work.

What appears done (unverified — read the diff, do not trust this list):

  • game/sticks.go + game/tables.go — the unhandled-wand-kind message
  • game/screen.go + term/tcell.go + game/term_test.go — a forced-repaint
    Terminal method, so item 2 was not split out
  • game/game.go + cmd/rogue/main.go — the startup greeting
  • new game/greeting_test.go and game/command_test.go

What remains, and must be done before this is fit for review:

  1. Run make fmt and fold the result into the commit.
  2. Finish mutation-proving all three behaviours. The session died immediately
    after confirming mutation 1 and while starting mutation 2 (asserting the
    message is absent for WandNothing) — so the WandNothing half, which
    is the whole reason this needed care, is unverified.
  3. Verify every message string byte-for-byte against the C sources, including
    the greeting's %d and whether the greeting is inside #ifdef MASTER.
  4. Confirm TestSeedCompatItemTables passes with its golden untouched — the
    greeting is emitted during startup and is the most likely thing in this
    change to perturb RNG call order.
  5. Amend the WIP commit into a single clean commit whose title ends with
    (closes #13).

Whoever picks this up: the existing work is a starting point, not a
foundation.
It has had no review and no completed verification. If any part
of it looks wrong, say so — on this repo, agents pushing back on inherited
claims have been right eight times running.

**Status: work in progress is preserved on `fix/lost-c-behaviors`, but it is NOT ready for review.** The implementing session hit a capacity limit partway through mutation verification and terminated. It left roughly 300 lines across 13 files plus two new test files **uncommitted in a temporary worktree** — one prune away from being lost. I have committed that as an explicit WIP checkpoint (`3f0a14c`) and pushed the branch so nothing is local-only. No PR is open and the branch is deliberately not labelled `needs-review`. **The checkpoint does not pass the gate.** `make fmt` was never run, so `fmt-check` fails on `ARCHITECTURE.md` and `TODO.md`. That is expected for a mid-task snapshot; it is recorded here so nobody mistakes a pushed branch for finished work. What appears done (unverified — read the diff, do not trust this list): - `game/sticks.go` + `game/tables.go` — the unhandled-wand-kind message - `game/screen.go` + `term/tcell.go` + `game/term_test.go` — a forced-repaint `Terminal` method, so item 2 was **not** split out - `game/game.go` + `cmd/rogue/main.go` — the startup greeting - new `game/greeting_test.go` and `game/command_test.go` What remains, and must be done before this is fit for review: 1. Run `make fmt` and fold the result into the commit. 2. Finish mutation-proving all three behaviours. The session died immediately after confirming mutation 1 and while starting mutation 2 (asserting the message is **absent** for `WandNothing`) — so the `WandNothing` half, which is the whole reason this needed care, is **unverified**. 3. Verify every message string byte-for-byte against the C sources, including the greeting's `%d` and whether the greeting is inside `#ifdef MASTER`. 4. Confirm `TestSeedCompatItemTables` passes with its golden untouched — the greeting is emitted during startup and is the most likely thing in this change to perturb RNG call order. 5. Amend the WIP commit into a single clean commit whose title ends with ` (closes #13)`. Whoever picks this up: **the existing work is a starting point, not a foundation.** It has had no review and no completed verification. If any part of it looks wrong, say so — on this repo, agents pushing back on inherited claims have been right eight times running.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/rgoue#13