fix(save,sim): boundary hardening + V13 invariant fixes (closes #99 #101 #102 #103 #104 #105 #106 #107 #108 #109 #110)#111
Merged
Conversation
#102 #103 #104 #105 #106 #107 #108 #109 #110) Two clusters of fixes that share an entry-point hardening theme. ## Cluster A — save-file boundary hardening (8 issues) All in src/platform/save.ts. Validators run at parseSaveFile and deserializeWorldState boundaries; throws route through bootFromSave's catch → bootFresh, consistent with existing #59 / #65 / #66 hardening. • #99 — grid-shape validators (Surface / Underground / Pheromone), map cardinality caps (MAX_COLONIES_LOAD = 16, etc), and non-numeric / __-prefixed key rejection. • #101 — chamber-record validator: chamberType enum-bounded, posX/posY in-grid (FP coords), foodStored ≤ FOOD_CHAMBER_CAPACITY, width/height MUST equal CHAMBER_DIMENSIONS for the type (footprints are PRD-fixed, not player-set). • #102 — pendingChamber validator: same canonical-dims rule as #101 plus footprint-fits-grid check. Closes the CancelDigMark revert-loop DoS vector. • #103 — non-array inputLog normalized to [] in parseSaveFile so game-scene's for-of loop never receives a non-iterable. • #104 — snapshot.tick + rngState integer validation; pre-fix a string tick caused unbounded memory growth via tick++ string concat. • #105 — null/non-object guard at top of migrateInputLogCommand; pre-fix one bad inputLog entry destroyed the whole save via loadSave's swallowing try/catch → deleteSave. • #109 — foodPiles array validator: length cap (FOOD_PILE_COUNT × 4), per-pile validation, foodPileId + tile uniqueness checks. • #110 — envelope seed int32 validation; pre-fix a malformed seed silently coerced to 0 on next autosave, breaking the (seed, inputLog) → snapshot replay-truth contract. 44 new tests in save.test.ts cover each validator with a tampered shape and verify legitimate scenarios still round-trip cleanly. ## Cluster B — sim-invariant fixes (3 issues, V13 bump) LATEST_SIM_VERSION bumped 12 → 13 (SIM_VERSION_V13_INVARIANT_FIXES). All three fixes gated so pre-V13 saves replay byte-identically. • #106 — Underground→Surface ascent uses currentGridColonyId (not colonyId) for the entrance lookup, and writes back colonyId on success to restore the "Surface ⇒ currentGridColonyId === colonyId" invariant. Pre-V13 invader Fighters could warp home through coincidental tileX alignment with own-colony entrances. Bonus skipAscent guard: Fighting invaders in foreign grids do NOT ascend (mirrors descent's isFightingForeigner logic) — without it, V13 produced a descent→ascent ping-pong loop through the enemy entrance. • #107 — killAnt atomically clears bidirectional carry pointers (carryingBroodId, carriedBy) before zeroing alive. Pre-V13 a nurse killed mid-Feeding left the brood with stale carriedBy pointing at a dead ant; if the nurse died on a Marked or Solid tile, the brood was unreclaimable. • #108 — resolveSameColonyOccupancy zero-masks the gridColonyId portion of the per-tile key when zone === Surface, mirroring combat's tile-key encoding. Same-colony surface ants with diverging currentGridColonyId now bucket together; one shifts. Closes the surface-stacking vector that #106's pre-fix divergence created. 8 new tests in combat.test.ts and ant-system.test.ts cover both pre-V13 and V13 paths, including a regression test for the descent→ascent ping-pong bug uncovered during implementation. ## Test summary • 1899 total tests pass • 1 flake on the AI 6000-tick determinism integration test under parallel load — passes in 15s when run isolated (pre-existing pattern unrelated to this change)
Owner
Author
|
@codex review (combined save-file boundary hardening + V13 sim-invariant fixes — 11 issues, ~1100 lines incl. tests) |
|
Codex Review: Didn't find any major issues. Chef's kiss. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
This was referenced May 6, 2026
Closed
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #99 #101 #102 #103 #104 #105 #106 #107 #108 #109 #110.
Summary
Two clusters of related hardening fixes that share an entry-point validation theme. 11 issues land together because they all live at one of two boundaries (
parseSaveFile/deserializeWorldState, or sim-version-gated invariant restoration) and the test surface is uniform across each cluster.Cluster A — save-file boundary hardening (8 issues)
All in
src/platform/save.ts. Validators run at theparseSaveFile/deserializeWorldStateboundaries. Throws route throughbootFromSave's catch →bootFresh(consistent with existing #59 / #65 / #66 hardening).chamber.{width,height,posX,posY,foodStored}chamber-flow.tsseed looppendingChamber.{width,height,anchor*}tick.ts:382revert loopinputLogs.tickscalartick++string concatfoodPilesarrayseed44 new tests in
save.test.tscover each validator with a tampered shape and verify legitimate scenarios still round-trip cleanly.Cluster B — sim-invariant fixes (3 issues, V13 bump)
LATEST_SIM_VERSIONbumped 12 → 13 (SIM_VERSION_V13_INVARIANT_FIXES). All three fixes gated so pre-V13 saves replay byte-identically.colonyIdnotcurrentGridColonyIdisFightingForeigner— without it V13 produced a descent→ascent ping-pong)killAntdoesn't clear carry slotsalive=0tile-key.ts:56mask:gridByte = zone===Underground ? raw : 08 new tests cover both pre-V13 and V13 paths, including a regression test for the descent→ascent ping-pong bug uncovered during implementation.
Cross-references
currentGridColonyId === colonyId" invariant fully.Internal review
One fresh-context review pass (no prior session context) returned "Clean — ship it" with one P2 (test gap on the new skipAscent guard). Addressed by adding a regression test that asserts an invader Fighter at tileX matching the enemy entrance does NOT ascend.
Test plan
Sim-version gating
Pre-V13 saves replay byte-identically. The V13 changes are gated on
world.simVersion >= SIM_VERSION_V13_INVARIANT_FIXES; legacy saves loaded into V13 builds keep V12 behavior (sticky-on-load contract, same as #95/#96 and earlier bumps).