From e595b877185e86a5368b747c62d332254ab24e44 Mon Sep 17 00:00:00 2001 From: sneak Date: Fri, 24 Jul 2026 03:05:01 +0700 Subject: [PATCH] Cross-validate item appearance tables against the C reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instrumented the C game on modern-rogue with a DUMP mode (patch in testdata/c_seedcompat.patch) that forces the RNG seed and prints the per-seed item appearance tables — potion colors, scroll names, ring stones, wand/staff materials — in the normal init order, before initscr so no terminal is needed. Captured its output for four seeds as testdata/item_tables.golden. TestSeedCompatItemTables regenerates the same tables from the Go port via New(Params{Seed, Wizard: true}) and checks they match the golden byte for byte. They do, for all four seeds — proving the LCG and its consumption order through the whole init sequence (init_probs → init_player → init_names → init_colors → init_stones → init_materials, including init_player's arrow rnd(8)+rnd(15)) agree with C exactly. testdata/README.md documents how to regenerate the golden. --- game/seedcompat_test.go | 98 ++++++++++++ game/testdata/README.md | 28 ++++ game/testdata/c_seedcompat.patch | 37 +++++ game/testdata/item_tables.golden | 260 +++++++++++++++++++++++++++++++ 4 files changed, 423 insertions(+) create mode 100644 game/seedcompat_test.go create mode 100644 game/testdata/README.md create mode 100644 game/testdata/c_seedcompat.patch create mode 100644 game/testdata/item_tables.golden diff --git a/game/seedcompat_test.go b/game/seedcompat_test.go new file mode 100644 index 0000000..d3b5def --- /dev/null +++ b/game/seedcompat_test.go @@ -0,0 +1,98 @@ +package game + +import ( + "fmt" + "os" + "strings" + "testing" +) + +// dumpItemTables formats a game's per-seed item appearance tables in the +// same layout the instrumented C reference prints: the potion colors, +// scroll names, ring stones, and wand/staff materials, each generated by +// consuming the RNG in a fixed order during New(). +func dumpItemTables(seed int32, g *RogueGame) string { + var b strings.Builder + + fmt.Fprintf(&b, "SEED %d\n", seed) + + fmt.Fprintln(&b, "POTIONS") + + for _, c := range g.Items.PotColors { + fmt.Fprintln(&b, c) + } + + fmt.Fprintln(&b, "SCROLLS") + + for _, s := range g.Items.ScrNames { + fmt.Fprintln(&b, s) + } + + fmt.Fprintln(&b, "RINGS") + + for _, s := range g.Items.RingStones { + fmt.Fprintln(&b, s) + } + + fmt.Fprintln(&b, "STICKS") + + for i := range g.Items.WandType { + fmt.Fprintf(&b, "%s %s\n", g.Items.WandType[i], g.Items.WandMade[i]) + } + + return b.String() +} + +// TestSeedCompatItemTables proves the port's seed-compatibility claim: for +// the same seed, the Go game generates the exact per-seed item appearance +// tables as the C reference on modern-rogue. That requires the LCG and its +// consumption order through the whole init sequence (init_probs → +// init_player → init_names → init_colors → init_stones → init_materials) to +// match C byte for byte. The golden is captured from an instrumented build +// of the C game (testdata/README.md). +func TestSeedCompatItemTables(t *testing.T) { + golden, err := os.ReadFile("testdata/item_tables.golden") + if err != nil { + t.Fatalf("read golden: %v", err) + } + + // These must match the seeds the golden was generated from + // (testdata/README.md). + seeds := []int32{1, 42, 12345, 99999} + + var got strings.Builder + + for _, seed := range seeds { + g := New(Params{Seed: seed, Wizard: true}) + got.WriteString(dumpItemTables(seed, g)) + } + + if got.String() != string(golden) { + t.Errorf("Go item tables diverge from the C reference at %s", + firstDiff(string(golden), got.String())) + } +} + +// firstDiff returns a description of the first line where want and got +// differ, for a readable failure. +func firstDiff(want, got string) string { + wl := strings.Split(want, "\n") + gl := strings.Split(got, "\n") + + for i := 0; i < len(wl) || i < len(gl); i++ { + w, g := "", "" + if i < len(wl) { + w = wl[i] + } + + if i < len(gl) { + g = gl[i] + } + + if w != g { + return fmt.Sprintf("line %d: C=%q Go=%q", i+1, w, g) + } + } + + return "no line difference (trailing content?)" +} diff --git a/game/testdata/README.md b/game/testdata/README.md new file mode 100644 index 0000000..9867b07 --- /dev/null +++ b/game/testdata/README.md @@ -0,0 +1,28 @@ +# Seed-compatibility golden + +`item_tables.golden` is the per-seed item appearance tables (potion colors, +scroll names, ring stones, wand/staff materials) captured from the **C +reference** on the `modern-rogue` branch, for the seeds in the `seeds` list in +`TestSeedCompatItemTables`. That test regenerates the same tables +from the Go port and checks they match byte for byte — proving the LCG and its +consumption order through the whole init sequence (`init_probs` → +`init_player` → `init_names` → `init_colors` → `init_stones` → +`init_materials`) agree with C. + +## Regenerating the golden + +`c_seedcompat.patch` adds a `DUMP` mode to the C `main.c`: with `DUMP` set it +forces the RNG seed from `SEED`, runs the item-table init in the normal order, +prints the tables, and exits before `initscr` (so no terminal is needed). + +```sh +# from a checkout of the C reference (modern-rogue branch): +git archive modern-rogue | tar -x -C /tmp/rogue-c +cd /tmp/rogue-c +patch -p1 < .../game/testdata/c_seedcompat.patch +./configure && make +for s in 1 42 12345 99999; do DUMP=1 SEED=$s ./rogue; done \ + > .../game/testdata/item_tables.golden +``` + +The seed list must match the `seeds` slice in `TestSeedCompatItemTables`. diff --git a/game/testdata/c_seedcompat.patch b/game/testdata/c_seedcompat.patch new file mode 100644 index 0000000..84569c1 --- /dev/null +++ b/game/testdata/c_seedcompat.patch @@ -0,0 +1,37 @@ +--- a/main.c 2026-07-24 03:02:38 ++++ b/main.c 2026-07-24 02:48:31 +@@ -63,6 +63,34 @@ + #endif + dnum = lowtime + md_getpid(); + seed = dnum; ++ ++ /* SEEDCOMPAT: dump the per-game item appearance tables for a fixed ++ * seed and exit, without initscr. The init sequence and everything ++ * it consumes from rnd() mirror the normal startup (main.c), so the ++ * tables are exactly what a real game with SEED would show. */ ++ if (getenv("DUMP") != NULL) ++ { ++ int di; ++ char *sv = getenv("SEED"); ++ if (sv != NULL) ++ seed = atoi(sv); ++ printf("SEED %d\n", seed); ++ init_probs(); ++ init_player(); ++ init_names(); ++ init_colors(); ++ init_stones(); ++ init_materials(); ++ printf("POTIONS\n"); ++ for (di = 0; di < MAXPOTIONS; di++) printf("%s\n", p_colors[di]); ++ printf("SCROLLS\n"); ++ for (di = 0; di < MAXSCROLLS; di++) printf("%s\n", s_names[di]); ++ printf("RINGS\n"); ++ for (di = 0; di < MAXRINGS; di++) printf("%s\n", r_stones[di]); ++ printf("STICKS\n"); ++ for (di = 0; di < MAXSTICKS; di++) printf("%s %s\n", ws_type[di], ws_made[di]); ++ exit(0); ++ } + + open_score(); + diff --git a/game/testdata/item_tables.golden b/game/testdata/item_tables.golden new file mode 100644 index 0000000..fa8b794 --- /dev/null +++ b/game/testdata/item_tables.golden @@ -0,0 +1,260 @@ +SEED 1 +POTIONS +tangerine +white +ecru +gold +amber +violet +vermilion +pink +aquamarine +plaid +clear +orange +cyan +tan +SCROLLS +miwhon garsnanih +xomimi roke eshwedshu +potwexrol ipbjorod turs evsnelg +bekornan oxyfatox +iv wexpo wun +ha sefnelgtue whon pay +alari wedit +zantmon umzonski umwhonjo yot +bluoxun rokkho yottrol sta +vomarg microgcomp iteulkshu mung +jo urokeep yuskiun +ox xozantaks klisstaevs ag +ipnih bek +shu ami erk +nejti zim +iprol mic ishoxyvom fagan +reacreti oodrol +bytsri solsa tabu fri +RINGS +agate +zircon +jade +tiger eye +onyx +germanium +lapis lazuli +emerald +taaffeite +kryptonite +garnet +ruby +turquoise +pearl +STICKS +wand steel +wand platinum +staff redwood +staff pine +wand silicon +staff spruce +staff pecan +wand bone +staff maple +wand zinc +wand iron +wand pewter +wand electrum +staff dogwood +SEED 42 +POTIONS +blue +green +grey +amber +violet +gold +pink +tan +purple +yellow +plaid +magenta +turquoise +cyan +SCROLLS +bek itod oxytaod oxy +tarhovzant cre sname oxroy +plemik ganod hyd wergerkpot +hyd sol um bekzok +esh eep ganmung +anera ishsa ingala mon +alasniklech viv +yunejorn garro con nej +dotrolther gopum eltitrol trolmonsri +nes alazum +itegopmung ti +ere haeta wergla +nejerecre poipi iprea +ha falechrhov +monskiwex sabitla frido +rhovmar sno +mar bekurzant satbuzum +somon sri +RINGS +carnelian +onyx +jade +granite +stibotantalite +kryptonite +lapis lazuli +germanium +garnet +tiger eye +opal +topaz +agate +peridot +STICKS +staff birch +staff ebony +staff redwood +wand gold +wand copper +wand aluminum +wand titanium +wand mercury +staff cypress +staff bamboo +staff dogwood +wand silicon +staff zebrawood +wand beryllium +SEED 12345 +POTIONS +purple +black +grey +brown +plaid +violet +vermilion +ecru +orange +turquoise +tan +magenta +silver +gold +SCROLLS +readalf shuplu ivnin +plelaiv solel skibyt monha +xo wun +wedyfri o ewhonxo favompay +eep zantreanelg +plu buxo +un zontabdan +bie snik +ulkitzant bluri +apporg ash posnevly dennepwex +u urval rol +arzepotsno snovly pay snoropay +pottox erewed faoxro +ther sun ulkipo mik +argzebfri elgrekli tuenepzon sehturssef +isheep blumur +wedash yuzimsun +plupofri ski rejo fa +RINGS +onyx +tiger eye +alexandrite +turquoise +pearl +emerald +germanium +sapphire +zircon +ruby +granite +stibotantalite +opal +diamond +STICKS +wand silicon +staff ironwood +staff holly +wand gold +staff mahogany +wand iron +wand brass +wand pewter +staff hemlock +staff cherry +staff elm +wand mercury +staff banyan +staff dogwood +SEED 99999 +POTIONS +aquamarine +plaid +gold +black +vermilion +red +cyan +tan +orange +violet +brown +clear +silver +green +SCROLLS +zant jocompan vomervly +mur shusat prok +prokmurklis oxysriklis +ingcre prokbu whonengarg kli +kli bot +rokcoswerg ipsolsan klisvlypay +glen yot whontox +lechme markho fazim +dalfsunbie micjosef cre comp +vlyfumi bjorzantbot werg +po argfidcos klipones +ashtemarg ycrezim dalfiv whon +turs unmisa zimpo therdo +miccompuni uni neswex sef +odwexing elwergmur mung +itcon rhov nejmic lech +garturs engseh ganish +oodta whonorgsno monabmik vomyeng +RINGS +obsidian +moonstone +jade +carnelian +tiger eye +taaffeite +turquoise +stibotantalite +agate +ruby +onyx +topaz +germanium +granite +STICKS +wand titanium +wand brass +wand silicon +staff zebrawood +wand mercury +staff dogwood +wand pewter +staff cinnibar +staff kukui wood +staff banyan +wand magnesium +wand gold +staff maple +wand nickel