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

View File

@@ -40,6 +40,13 @@ func run() int {
return 0
}
// C printed its greeting just before initscr(); here that means just
// before the tcell screen takes the terminal, and on stdout, exactly
// as C did (main.c main).
if digsNewDungeon(*deathDemo, flag.Args()) {
fmt.Print(game.Greeting(params))
}
t, err := term.New()
if err != nil {
fmt.Fprintln(os.Stderr, err)
@@ -87,6 +94,23 @@ func run() int {
return 0
}
// digsNewDungeon reports whether this invocation is the one that digs a
// fresh dungeon, and so the only one that greets.
//
// C's printf is the last statement before initscr(), and everything that
// does something else has already left by then: -s scores and exits, -d
// runs the death demo and exits, and restore() — the argc == 2 case that
// is neither — never returns. So a saved game resumes without a greeting,
// which is right: nothing is being dug.
//
// The restore test is duplicated from run's own, deliberately. Keeping
// them as one predicate would mean deciding the startup path before the
// terminal exists and carrying it past the error returns, which is more
// rearrangement of run than a greeting is worth.
func digsNewDungeon(deathDemo bool, args []string) bool {
return !deathDemo && len(args) != 1
}
// loadParams gathers the game parameters from the environment: home
// directory, ROGUEOPTS, user name, wizard mode, and the dungeon seed
// (main.c's startup).

View File

@@ -380,3 +380,40 @@ func (s *stuckSaver) AutoSaveOnSignal(time.Duration) bool {
return true
}
// TestDigsNewDungeon pins which invocations reach C's greeting. In main.c
// the printf is the last statement before initscr(), so -s and -d, which
// exit earlier, never see it, and neither does a restored game, because
// restore() does not return. The saved-game case is the one worth having
// a test for: resuming a dungeon must not announce that one is being dug.
func TestDigsNewDungeon(t *testing.T) {
t.Parallel()
cases := []struct {
name string
deathDemo bool
args []string
want bool
}{
{name: "new game", args: nil, want: true},
{name: "restore a save", args: []string{"rogue.save"}, want: false},
{name: "death demo", deathDemo: true, want: false},
{
name: "death demo wins over a save argument",
deathDemo: true,
args: []string{"rogue.save"},
want: false,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if got := digsNewDungeon(tc.deathDemo, tc.args); got != tc.want {
t.Errorf("digsNewDungeon(%v, %v) = %v, want %v",
tc.deathDemo, tc.args, got, tc.want)
}
})
}
}