score splits into scoreInsert/scoreLines/showScores; wakeMonster gains
meanWakes/medusaCatches/medusaGaze predicates and effect. Behavior and
RNG call order unchanged.
digRooms splits into digRoom/placeGoneRoom/placeMazeRoom/
placeNormalRoom/roomGold/roomMonster; dig gains digPick/digWallGap;
findFloorImpl gains floorChar; enterRoom and leaveRoom gain per-cell
helpers. rooms.go is complexity-clean. Behavior and RNG call order
unchanged.
runDaemon's switch becomes gameData.daemonHandlers (the C d_func
function pointers restored as method expressions); stomach splits into
stomachFaint/stomachDigest; visuals gains visualMonsters. daemons.go
is complexity-clean. Behavior and RNG call order unchanged.
saveGame splits into askDefaultSave/saveFileName/saveCheckOverwrite/
askOverwrite around a saveAnswer tri-state; snapshot gains destRefFor;
applySnapshot gains applyMonsters/applyDests. save.go is
complexity-clean. Behavior unchanged.
inventoryName splits into nameScroll/nameFood/nameWeapon/nameArmor/
describeWorn/fixNameCase; newThing gains newFoodThing/newWeaponThing/
newArmorThing/newRingThing; dropCheck gains dropRing; addLine splits
into addLineSlow/addLinePaged/addLineOverlay. things.go is
complexity-clean. Behavior and RNG call order unchanged.
addPack splits into pickupScareScroll and packInsert with
packScanKind/packScanWhich/packMatch/packMatchGroup for the C
linked-list walk; promptPackItem gains repeatLastItem and
promptItemPurpose; inventory's empty-handed messages flatten via
chooseTerse. pack.go is complexity-clean. Behavior unchanged.
digPassages gains pickNeighbor; connectRooms splits into
connOrient/connPlanDown/connPlanRight/connEnd/digCorridor around a
corridorPlan struct; addPass gains addPassSpot; the shared door/
secret-door predicate becomes hiddenExit. passages.go is
complexity-clean. Behavior and RNG call order unchanged.
The be_trapped switch becomes gameData.trapHandlers with one trap*
method per trap kind (mystery messages split in two); moveHero splits
into moveTarget/moveResolve/moveEnter/moveOnto/offMap; passageTurn
gains per-axis passageTurnVertical/Horizontal. move.go is
complexity-clean. Behavior and RNG call order unchanged.
chase splits into chaseBestSpot/chaseTry/scareScrollAt with a
chaseSearch state struct; chaseStep gains chaseRooms, chaseGoal,
dragonBreath/dragonShoots, and chaseTakeObject; runners gains
runnerTurn; findDest gains objectClaimed. chase.go is complexity-clean.
Behavior and RNG call order unchanged.
The ordinary command keys move into gameData.commandHandlers (method
expressions and small literals); dispatchKey keeps only re-dispatching
prefixes (runCommand/fightCommand/repeatCommand/moveOnCommand) and the
wizard fallthrough. command() splits into playTurn/turnUpkeep/
readCommand/executeCommand/countPrefix/ringTurnEffects; search gains
searchSpot/searchFloor; help gains helpOne/helpAll/helpLines; call
gains callTarget/callPrelude; wizardCommand splits in two plus
wizardKit; uLevel and current flatten to early returns. command.go is
complexity-clean. Behavior and RNG call order unchanged.
look's nine-square scan splits into lookAround/lookCell with a
lookScan state struct and guard helpers (lookSkips,
lookForeignPassage, lookDiagonalBlocked, lookCellChar, lookShow,
lookRunCheck, atRunEdge); promptDirection gains deltaFor and
confuseDirection. misc.go is complexity-clean. Behavior and RNG call
order unchanged.
The monster special-power switch in attack becomes
gameData.hitHandlers (indexed by monster letter); attack splits into
monsterHit/monsterMiss; fight gains revealXeroc and heroHits;
rollAttacks gains weaponAttack, wieldedRingBonus, and defenderArmor;
killed gains killedSpecial. fight.go is complexity-clean. Behavior and
RNG call order unchanged.
The fire_bolt loop splits into boltDirChar, boltBounces,
boltStrikesMonster, and boltStrikesHero; loop state (hitHero/changed/
used) stays in fireBolt. Effect order and RNG calls unchanged.
The do_zap switch becomes gameData.zapHandlers, indexed by WandKind;
the shared monster-ray preamble is zapRayMonster/zapVictim, teleport
away/to and the three bolt wands share handlers, and a false return
aborts the zap without spending a charge (drain life on a too-weak
hero, as in C). Effect order and RNG call sequence unchanged.
The read_scroll switch becomes gameData.readHandlers, indexed by
ScrollKind; the five identify scrolls share one handler. The magic
mapping cell logic is extracted into revealSpot. Effect order and RNG
call sequence unchanged.
The quaff switch becomes gameData.quaffHandlers, a method-expression
table indexed by PotionKind; each case body moved verbatim into a
quaff* method. Effect order and RNG call sequence unchanged.
Refactor step 6. MessageLine (was MsgLine) owns the msg/addmsg/endmsg
machinery, wired to its screen, pre---More-- redraw, and input via
attach(); RogueGame keeps one-line msg/addmsgf/endmsg shorthands so
the ~400 call sites are unchanged. Player gains nextPackChar and
removeFromPack (the state half of pack.c leave_pack); leavePack keeps
only the LastPick repeat-command tracking. Level gains ObjectAt
(misc.c find_obj) and AddObject/RemoveObject/AddMonster/RemoveMonster,
replacing direct attach/detach calls on the level lists. Inventory and
pickup UI flows stay on RogueGame: display and orchestration, not
state surgery. Behavior and RNG order unchanged; suite green.
Refactor step 5, combat: rollEm→rollAttacks; attack, moveMonster, and
chaseStep return (removed bool) instead of the C -1/0 int codes.
Behavior unchanged; suite green.
getItem→promptPackItem now returns (obj, ok) instead of a nil-signaling
pointer; invName→inventoryName; doPot→applyPotionFuse. C breadcrumbs
kept. Behavior unchanged; suite green.
Refactor step 4. Renames (C breadcrumbs kept in doc comments):
doMove→moveHero, beTrapped→springTrap, rndmove→randomStep,
doRooms→digRooms, doPassages→digPassages, doMaze→digMaze,
chgStr→changeStrength, doRun→startRun, moveStuff→finishMove,
turnref→turnRefresh, moveMonst→moveMonster, doChase→chaseStep,
setOldch→setOldChar, cansee→canSee, roomin→roomIn, runto→runTo,
conn→connectRooms, putpass→putPassage, passnum→numberPassages,
numpass→numberPassage, rndPos→randomPos, rndRoom→randomRoom,
treasRoom→treasureRoom, accntMaze→accountMaze.
All goto/label flows are gone: moveHero uses a retry loop with the
PASSGO corner logic extracted into passageTurn; dispatch re-dispatches
via a loop; chaseStep re-checks via a loop; saveGame uses a labeled
prompt loop. Control flow and RNG call order are unchanged; suite
green.
The 289 findings are C-faithful gameplay literals (probability rolls,
damage spreads, screen coordinates); naming them would invent constants
the C never had and hurt greppability against the reference sources.
gochecknoglobals: all 37 package-level tables consolidated into the
gameData struct (game/tables.go), built by newGameData() and carried
on RogueGame as g.data (set in NewGame and Restore). ObjectKind
Glyph()/objectKindForGlyph are now switches; the table-reading subtype
Stringer methods are gone; isMagic is a RogueGame method.
goconst: repeated words named (potionName/scrollName/ringName/goldName
in object.go, wandName/staffName in sticks.go, ripWall in tables.go).
exhaustive, testpackage: disabled in .golangci.yml with sneak's
approval (2026-07-07).
Also reverts misspell's silent corruption of the "ther" scroll-name
syllable (it had become "there", changing generated scroll names vs C).
Remaining red: cyclop/gocognit/nestif until refactor step 7; mnd
awaits a ruling. TODO.md rotated; MEMORY.md lint notes updated.
.golangci.yml is the prompts-repo standard verbatim plus one approved
exception (paralleltest, sneak 2026-07-06). Roughly 1,500 findings
fixed:
- autofix sweep (wsl_v5/nlreturn/intrange/modernize formatting), with
the misspell autofix REVERTED where it rewrote authentic C game text
("missle vanishes" stays, with nolint and a comment)
- error handling: errcheck sites get real handling — saveFile now
closes explicitly and removes corrupt saves, scoreboard writes are
documented best-effort, err113 sentinel errors (ErrSaveOutOfDate,
ErrScreenTooSmall); panics allowed for unrecoverable states per
MEMORY.md policy
- gosec: real fixes (ParseInt for SEED, 0600 scorefile) and justified
per-line nolints for provably-bounded conversions (randomMonsterLetter
helper collapses eight rnd(26)+'A' sites)
- API tidying from linters: pointer receivers on flag types
(recvcheck), msg helpers renamed addmsgf/doaddf/Printwf/MvPrintwf
(goprintffuncname), myExit()/findFloor(monst)/mkGameInput(t) drop
always-constant params (unparam), gameEnd carries no status
- gocritic/staticcheck: if-else chains to switches, the pack.c
inventory filter untangled into matchesFilter, main.go split so
defers run before exit (exitAfterDefer, funlen)
- revive doc comments on all exported flag types/consts/methods
Remaining findings are confined to linters pending sneak's exception
decision (mnd, gochecknoglobals, cyclop, nestif, gocognit, exhaustive,
goconst, testpackage); TODO.md records the state. MEMORY.md added at
repo root as the project's agent working notes.
go.mod, term/ and cmd/ imports, the ARCHITECTURE.md module references,
and the in-game version string. User request; the repo already lives
at this remote.
Object.Arm carried four meanings in C (o_arm/o_charges/o_goldval plus
ring bonuses); it is now four fields: ArmorClass, Charges, GoldValue,
and Bonus. Stats.Arm becomes Stats.ArmorClass. The Charges()/GoldVal()
accessor pair is gone.
Damage dice strings ("1x4/1x2") are parsed once into DiceSpec — at
table definition for the bestiary and weapon tables, at creation for
items — instead of re-parsed with atoi on every swing as fight.c
roll_em did. ParseDice preserves the C parse semantics exactly,
including the junk-tolerant "%%%x0" bestiary placeholder and the
"000x0" flytrap reset (regression-tested in dice_test.go); the
flytrap's growing grip becomes DiceSpec{{VfHit, 1}}.
Save format bumps to 5.4.4-go3 (field renames and retypes would
silently zero under gob's match-by-name decoding).
No behavior change; full suite green.
The rotation should have ridden the step-2 commit per the Workflow
section, but the file was edited concurrently and the update was lost;
recording it now. Step 3 (un-overload Object fields) is Next Step.