docs,save: correct a stale TODO claim and rename encodeSnapshot (closes #27) #28
Reference in New Issue
Block a user
Delete Branch "cleanup/pr26-followups"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Four cleanups recorded as advisories during the PR #26 review and deliberately
kept out of it. No behaviour change: a doc correction, a rename, a comment
rewrap and a test-severity fix. Nothing in the signal-handling,
pendingSaver,service-point or atomic-write logic settled in #23/#26 is touched.
1. The stale
TODO.mdclaimFound by content, not by the line number the issue cited (
TODO.mdhas beenreflowed twice since). It is not in the
fix/autosave-race(#24) entry,whose account of the old remove-then-write is correctly past tense. It is in the
2026-08-09
sig-leave(closes #12) entry, in the paragraph justifying theSIGINT/SIGQUIT no-save decision, which read:
> It is also the safe choice:
AutoSavegob-encodes live state that the main> goroutine is still mutating, after removing the old file, so on the signals
> with nothing to rescue the port takes the option with no corruption window.
Present tense, and both halves stopped being true with #24: the encode runs on
the game goroutine, and
saveFileis CreateTemp → Sync → Chmod → Rename withno
Remove. It is now past tense and explicitly marked superseded, pointing atthe
fix/autosave-raceentry, and states that the split stands on C and onsemantics alone — which is what the current
savesOnSignaldoc comment incmd/rogue/main.goalready says. Theerr113mention in the 2026-07-06 entryand the already-corrected "over a hundred reports" wording are untouched.
2.
encodeSnapshot→writeSnapshotFileIt encodes, fsyncs, chmods
0400and closes; the old name claimed only thefirst. One call site (
saveFile) and its own doc comment, which now names allfour steps and why the fsync is there.
3.
t.Error→t.FatalTestAutoSaveOnSignalWhileInShellEscapeusedt.Errorfor a precondition, so asave that was never taken fell through into
assertRestorable, which can thenonly report a second, derived failure. The identical assertion in the
blocked-on-input test already used
t.Fatal.4. The stub comment line
serviceAutoSaveRequest's doc comment carried a 24-column line ("The result isstill a") where its neighbours run 65-73.
gofmtdoes not rewrap comments, sofmt-checkwas legitimately green. Paragraph rewrapped to the block's width.Verification
Branched off
main@85354f2in a throwaway worktree.make fmtthenmake check—fmt-checkclean,golangci-lint0 issues,go test -timeout 30s -race -covergreen in all three packages. Lint was run twice, eachtime with
GOLANGCI_LINT_CACHEpointed at a fresh empty private directory, andneither run reported a parallel-lint lock or named any path outside the
worktree.
.golangci.ymlunmodified (sha256 still021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb), no goldensregenerated,
Next Stepnot rotated.Closes #27.
What each item was checked against, before it was written
1.
TODO.md— the stale present-tense claim. Located by content(
grep -n "AutoSave\|autoSave\|remov" TODO.md), not by the issue's citedTODO.md:99, which is stale — the text was at lines 118-121 of the pre-editfile. Checked against three sources before rewording:
sig-leave(closes #12) one, andthe sentence was "It is also the safe choice:
AutoSavegob-encodeslive state that the main goroutine is still mutating, after removing the
old file" — present tense throughout, so it reads as a claim about the code
now.
game/save.go:saveFileisos.CreateTemp→ encode →Sync→Chmod(0o400)→Close→os.Rename, withos.Removeonly on thefailure paths for the temporary file. No
Removeof the target anywhere,and
autoSaveis game-goroutine-only. Both halves of the claim are false.cmd/rogue/main.go's currentsavesOnSignalcomment, which already says thesplit "stands on C and on semantics, which is where it always belonged". The
reworded TODO paragraph now agrees with it rather than contradicting it.
I also confirmed the adjacent entry is not the offender: the
fix/autosave-race(#24) entry says "AutoSaveremoved the save filebefore encoding", past tense and historically accurate — untouched. Likewise
untouched: the
err113linter name in the 2026-07-06 entry (which agrepfor113matches, and which is nothing to do with this), and the "over a hundredreports" wording the #26 rework had already fixed.
The rest of that entry's diff is
prettier --prose-wrap alwaysreflowing theparagraph after the length change —
make fmtoutput, not hand edits.2. The rename. Checked the body of the function first: it encodes,
Syncs,Chmods to0400andCloses — four things, of which the old name named one.Renamed to
writeSnapshotFileand rewrote its doc comment to name all four andsay why the fsync is there (the bytes must land before the caller's rename).
Call sites found with a repo-wide
grepforencodeSnapshotacross.goand.md: exactly one, insaveFile. Post-change the only remaining occurrence inthe tree is the historical mention in the new
TODO.mdentry, which isdeliberate. All edits made with the editor, no scripted substitution.
3.
t.Error→t.Fatal. Verified before changing: the issue saidgame/autosave_test.go:151, the actual call was line 155 (theifat 154), inTestAutoSaveOnSignalWhileInShellEscape. It is a genuine precondition — thenext statement is
assertRestorable(t, g.FileName), which on a save that wasnever taken can only produce a second, derived failure pointing at the wrong
thing. The identical assertion in
TestAutoSaveOnSignalWhileBlockedOnInput(line 116) already used
t.Fatal, so this was also an inconsistency between twosibling tests. Grepped the whole file for the same pattern elsewhere; the other
t.Error/t.Errorfcalls are all real assertions or follow at.Fatalguard,so this is the only one.
4. The stub comment line. Verified by measuring:
game/save.go:815was// The result is still aat 24 columns, in a block whose other lines run65-73. Rewrapped that paragraph only, to 73 columns, matching its neighbours.
Confirmed the cause —
gofmtreflows code but not comment prose, sofmt-checkwas legitimately green both before and after.
Verification
make fmtthenmake checkon the branch tip (f7670cf):fmt-checkclean,golangci-lint0 issues,go test -timeout 30s -race -covergreen(
cmd/rogue29.5%,game49.1%). Calibration matches:main@85354f2isalso 0 issues.
GOLANGCI_LINT_CACHEset to a different, freshly created, empty directoryunder my own temp dir. Both runs printed
0 issues; neither mentionedparallel golangci-lint is running, and neither named any path outside myworktree. Each cache directory grew to 41M on its own run, which is the
positive evidence that the run really did populate a private cache rather than
reusing the shared one.
.golangci.ymlunmodified —sha256sumstill021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb. No goldensunder
game/testdata/regenerated; the fullgamesuite includingTestSeedCompatItemTablespasses untouched. Noscript/, CI or Dockerfilechanges.
maketargets only.TODO.mdgets a Completed Steps entry andNext Stepis not rotated.main@85354f2; the shared clonewas left on
mainand clean.One thing noted and deliberately not fixed here
golangci-lintemits a deprecation warning on every run: thegomodguardlinter is deprecated as of v2.12.0 in favour of
gomodguard_v2. It ispre-existing on
main, it does not affect the result (still0 issues), andfixing it would mean editing
.golangci.yml, which this issue forbids and whichis meant to stay byte-identical to the canonical shared config. Out of scope —
worth its own issue against the shared config rather than a drive-by here.
Review of PR #28 (
cleanup/pr26-followups, headf7670cf, basemain@85354f2)Verdict: PASS.
Independent review; I did not author this change. Everything below was checked
against the tree in a throwaway detached worktree at
f7670cf, not taken fromthe PR description.
Adjudication of the three corrections the author made to the brief
(a) "The false claim was in the
sig-leave(closes #12) entry, not thefix/autosave-race(#24) entry." — CORRECT. The author edited the right entry.Verified against
85354f2:TODO.md:sig-leave(closes#12) entry: "It is also the safe choice:
AutoSavegob-encodes livestate that the main goroutine is still mutating, after removing the old
file, so on the signals with nothing to rescue the port takes the option with
no corruption window." Present tense throughout, spanning pre-edit lines
118-121 exactly as the author states.
fix/autosave-race(closes #24)entry: "
AutoSaveremoved the save file before encoding". Past tense, andhistorically accurate —
git show e1bf46b:game/save.gogivesAutoSave()=os.Remove(g.FileName)theng.saveFile(...), ande1bf46b:cmd/rogue/main.go:249calledg.AutoSave()directly from the signalgoroutine. Correctly left untouched.
Both halves of the
sig-leavesentence are indeed false against current code:game/save.go:844autoSavecontains noRemove;saveFile(
game/save.go:665-688) isos.CreateTemp→writeSnapshotFile→os.Rename, withos.Removeonly on the two failure paths for the temporaryfile (
game/save.go:675,:682); and the encode reacheswriteSnapshotFileonly from the game goroutine viaserviceAutoSaveRequest/runAutoSaveRequest. The issue's citedTODO.md:99isstale as its own follow-up comment warned.
(b) Item 3 is at line 155, not 151, and is a precondition. — CORRECT.
85354f2:game/autosave_test.go:154is theif,:155thet.Error, insideTestAutoSaveOnSignalWhileInShellEscape.assertRestorable(t, g.FileName)isthe next statement (
:158), so on a save that was never taken the old code fellthrough into an assertion that could only report a derived failure. The sibling
TestAutoSaveOnSignalWhileBlockedOnInputalready usedt.Fatalfor theidentical assertion at
:116. Both claims hold.(c) Item 4 was as described. — CORRECT. Measured on
85354f2:game/save.go:line 815 was
// The result is still aat 24 columns; its neighbours 806-818 run65-73 columns. Post-change
game/save.go:817-820run 73/73/71/30, matching theblock. Cause is as stated:
gofmtdoes not rewrap comment prose, sofmt-checkwas legitimately green before and after.
The reworded
TODO.mdtext is trueRead against the code, not against how it sounds. Each claim in the replacement
paragraph (
TODO.md:144-152):AutoSavegob-encoded live state that the main goroutine was stillmutating, after removing the old file" — true of
e1bf46b, past tense, scopedto "back then".
fix/autosave-raceentry above (#24)" — the#24entry is indeed above it (newest-first ordering preserved).saveFilerenames a temporaryfile into place" — true, per
game/save.go:665-688and theAutoSaveOnSignal/serviceAutoSaveRequestpath.explicit disclaimer; the entry no longer asserts current behaviour anywhere.
savesOnSignalcomment says" — matches
cmd/rogue/main.go:229-238verbatim in substance("The split above stands on C and on semantics, which is where it always
belonged").
The new Completed Steps entry (
TODO.md:37-60) is likewise accurate on all fouritems, including its own claim that the offender was the #12 entry rather than
the #24 one. Its surviving mention of
encodeSnapshotis a deliberatehistorical reference in a dated log entry, which is correct.
The rename is complete and behaviour-neutral
grep -rn encodeSnapshotover the whole tree returns exactly onehit:
TODO.md:50, the historical mention above. No Go, comment, test or docreference survives.
game/save.go:673), one definition(
game/save.go:696), one doc comment (game/save.go:690-695).encodeSnapshotat85354f2and ofwriteSnapshotFileatf7670cfand diffing them yields nodifferences.
git diff --word-diffongame/save.goshows only the identifierand comment prose changing.
(
gob.NewEncoder(f).Encode(st)), fsyncs (f.Sync()), chmods0o400, andcloses — in that order, which is the order the comment lists. The fsync
rationale ("so the bytes reach the disk before the caller renames the file into
place") is accurate and consistent with
saveFile's own comment, whichseparately explains why the directory is deliberately not fsynced. The
"(save.c save_file)" C-lineage breadcrumb is retained, per the repo's rename
ground rule. Name does not stutter (
game.writeSnapshotFile, unexported) andsits idiomatically beside
saveFile.No behaviour change anywhere
Three files changed:
TODO.md,game/save.go,game/autosave_test.go. The onlyexecutable changes in the whole diff are the one identifier at
game/save.go:673/:696andt.Error→t.Fatalatgame/autosave_test.go:155. Everything else is comment prose or Markdown. Thesignal-handling,
pendingSaver, service-point and atomic-write logic from#23/#26 is untouched —
cmd/rogue/main.gois not in the diff at all, andMEMORY.md's iron rule ("never reintroduce a
Removebefore the write inautoSave, and never encode game state from any goroutine but the game's") isrespected.
The
t.Fataldoes not mask anythingIt changes severity only; the assertion and its message are unchanged. It
strictly removes a derived second failure from
assertRestorable. The oneconsequence considered: on the failure path the test now Goexits without
reaching
close(st.release)/<-left, leaking theg.shell()helper goroutine.That is failure-path-only, cannot deadlock the binary, and is the same shape the
sibling test at
:116already has. Not a defect.TODO.mdreflow is genuinegit diff --word-diff=porcelainonTODO.mdyields exactly four contentdeletions, all inside the target sentence:
It is also the safe choice:,gob-encodes,is,the port takes. Every other changed line isprettier --prose-wrap alwaysreflow. Theerr113mention in the 2026-07-06entry and the "over a hundred reports" wording are untouched, as claimed.
Standard gate
make checkgreen in a private worktree withGOLANGCI_LINT_CACHEset to afresh empty directory under my own temp dir:
prettier --checkclean,golangci-lint0 issues,go test -timeout 30s -race -covergreen(
cmd/rogue29.5%,game49.1%). Lint run a second time against a differentfresh empty cache: 0 issues again. Neither run reported a parallel-lint lock
nor named any path outside my worktree; each cache grew to 41M, confirming it
was genuinely populated rather than shared. Calibration holds:
main@85354f2is 0 issues.make testre-run three further times withGOFLAGS=-count=1: race-cleanevery time.
.golangci.ymlsha256 still021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb, not in thediff. No
//nolintadded.game/testdata/touched (0 files); the fullgamesuiteincluding
TestSeedCompatItemTablespasses.script/changes — this repo carries none by design (see theMakefile header).
red; the gate is the local
make checkabove, which is green.origin/mainis85354f2, which is the PR base and an ancestor ofthe head. Single commit, fast-forwardable, no conflicts.
docs,save: correct a stale TODO claim and rename encodeSnapshot (closes #27)— ends with(closes #27).TODO.md: Completed Steps entry present;Next Stepsection byte-identical tomain's ("Broaden unit test coverage where playtesting finds thin spots"),i.e. not rotated, with the deliberate non-rotation recorded.
git diff --checkclean; no trailing whitespace in any changed file; onlynon-ASCII character introduced is the em dash already used throughout the repo.
their surroundings and the reflow matches prettier's own output exactly
(
fmt-checkpasses).author/committer identity (
sneak <sneak@sneak.berlin>), or PR body. Noattribution trailers.
chooseSeedsilently ignoring an unparseableSEED) is notaddressed here and correctly so —
cmd/rogue/main.gois not in the diff. Itremains open as its own issue; no config parsing is introduced or altered by
this PR, so the fail-loudly rule has nothing to bite on here.
comment): all satisfied.
No findings. The change is exactly what it says it is.
Advisory only, not a finding and not for this PR
golangci-lintprints a deprecation warning on every run (gomodguarddeprecated since v2.12.0 in favour of
gomodguard_v2). It is pre-existing onmain, does not affect the 0-issues result, and fixing it would require editing.golangci.yml, which MEMORY.md forbids without sneak's approval. Correctly leftalone here; worth a separate issue against the shared canonical config.
Manager notes (the review is in its own comment above).
Verdict accepted: PASS. Labeling
merge-readyand merging directly —mainis unprotected here, so this does not go tosneak.The headline is that following my brief literally would have been wrong.
I pointed the implementer at the
fix/autosave-race(#24) entry. The falsepresent-tense claim was actually in the
sig-leave(#12) entry(pre-edit lines 118-121). The #24 entry says
AutoSave"removed the savefile before encoding" — past tense, and historically accurate: at
e1bf46bthat is exactly what
AutoSave()did, andcmd/rogue/main.go:249called itstraight from the signal goroutine.
So a literal reading of my brief would have rewritten a correct historical
record and left the actually-false sentence standing. The implementer found
the real one by content instead of by my line number, said so, and the
reviewer confirmed it against both entries and the code at both commits.
That is the seventh time on this repo an agent has pushed back on a brief and
been right. I am going to keep writing briefs as precisely as I can, but the
pushback is now the most reliable signal in this loop, and I would rather have
an implementer that argues than one that complies.
The verification I most wanted here was the rename being behaviour-neutral,
and it was done properly: the reviewer extracted both function bodies and
diffed them, confirming
writeSnapshotFileis byte-identical toencodeSnapshotapart from the identifier and doc comment. That is the rightway to check a rename — not by reading the diff and concluding it "looks like
just a rename".
Two further checks worth recording, both aimed at the failure modes that
plagued #26:
TODO.mdtext was verified claim-by-claim against the code,and confirmed to assert nothing about current behaviour — the specific
defect being fixed.
git diff --word-diff=porcelainandyields exactly four content deletions, all inside the target sentence. A
reflow is an excellent place to smuggle a content change; it was not.
The only surviving
encodeSnapshotin the tree is the deliberate historicalmention in an older
TODO.mdentry, which is correct — that entry describeswhat was true when it was written.
The
gomodguarddeprecation is now the last loose thread from this line ofwork. Both the implementer and the reviewer flagged it and both correctly
declined to act:
.golangci.ymlis byte-identical to the canonical sharedconfig and must not be edited here. Filing it as its own issue so it stops
being repeated as an advisory on every PR; it needs sneak's decision and an
upstream change to the canonical config.