Skip to content

Validate every chain-step merge output; reconcile insertions globally; atomic writes - #33

Merged
TheValiantOne merged 1 commit into
mainfrom
fix/chain-step-validation
Aug 11, 2026
Merged

Validate every chain-step merge output; reconcile insertions globally; atomic writes#33
TheValiantOne merged 1 commit into
mainfrom
fix/chain-step-validation

Conversation

@TheValiantOne

Copy link
Copy Markdown
Owner

Summary

Round 2 of engine hardening, driven by using the real 199-mod install as a regression testing ground (read-only + mirrored runs). Three root causes found and fixed:

1. Silent chain-step corruption was ungated

DiffPlex's ThreeWayDiffer bug has a silent variant — no exception, no conflict block, but content lost or duplicated (documented in CLAUDE.md with measured rates). Only the exception/conflict paths were guarded; "clean" whole-file outputs shipped unchecked, and multi-step chains multiplied exposure. Observed live: steps of the real r4Player.ws chain silently duplicated SetSlowActive/GetVoiceSetLastPlayed/SetVoiceSetLastPlayed, and an actor.ws step duplicated ManageMonstersRegenTimer — corruption that shipped in every previous successful run. Now every clean whole-file output and every function-level rescue output is validated: structural sanity gate + per-scoped-unit no-duplication (a name can't occur more often than in any input) and no-loss (a name in both inputs, or inserted by either, must survive) invariants. A violation routes the step to the function-level rescue (which re-merges from the original inputs, sidestepping the corrupt output), then to skip — corrupt text is never written anywhere, sidecar included.

2. Insertion reconciliation was per-slot only

Re-merge chains start from the previous full merge, so the accumulated side already contains what later chain mods insert — at different slots than the mods' own copies. Per-slot reconciliation never pairs those up (both get emitted → the observed duplication), and byte-identical-only matching declined on whitespace-only formatting drift. ReconcileInsertionsGlobally now pairs same-scoped-name insertions across all slots and both sides: identical or whitespace-equal (the engine's existing NBSP-safe collapse standard) keeps the accumulated side's copy — span emission cuts consumed units out of verbatim spans while preserving position and separators — and genuinely different content still declines as a real new-functionality collision.

3. Merged-file writes were non-atomic

Merged output lands inside the live game's Mods tree; launching the game during a 40-file re-merge compiled truncated scripts into 'X' is not a member of Y errors that perfectly impersonated a bad merge (a full evening of forensics proved every symbol present-and-correctly-scoped in the on-disk output). WriteUtf16Atomic (same-directory temp + File.Move overwrite) closes that window for both merge and rescue outputs.

Validation

  • 8 new tests (cross-slot duplicate → one copy; whitespace-equal insertion pair → old side; differing → decline; one-side identical dups collapse; validator detects duplication/loss, allows legitimate deletion propagation, trusts non-.ws). 153 total, all green; format check clean.
  • Real-chain replay (Core API, read-only): the actor.ws silent duplication is now caught and rescued cleanly.
  • Full mirrored 41-file run + the r4Player end-to-end proof are pending mod redeployment (Vortex was purged mid-validation); results will be posted on this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GXAuGMLB44T5Zv5o5ZzKah

…; atomic writes

Three fixes from using the live install as a regression testing ground:

- Chain-step output validation: DiffPlex's ThreeWayDiffer bug has a SILENT
  variant (no exception, no conflict block, content lost or duplicated) that
  only ever had the exception/conflict paths guarded - "clean" whole-file
  outputs shipped unchecked, and chains multiplied exposure. Observed live:
  mid-chain steps of a real 12-mod r4Player.ws chain silently duplicated
  three functions, and an actor.ws step duplicated ManageMonstersRegenTimer.
  Every clean output (and every function-level rescue output) now passes
  structural sanity plus per-scoped-unit no-duplication/no-loss invariants
  against its three inputs; a violation routes the step to the function-level
  rescue (which re-merges from the ORIGINAL inputs), then to skip - the
  corrupt text is never written, not even as a sidecar.

- Global cross-slot insertion reconciliation: re-merge chains start from the
  previous full merge, so the accumulated side already contains what later
  mods insert - at different positions than the mods' own copies, which
  per-slot reconciliation never pairs up (emitting BOTH, the observed
  duplication) and whose whitespace-only formatting differences the old
  byte-identical rule declined on. Same-name insertions across sides now
  reconcile globally: identical or whitespace-equal keeps the accumulated
  side's copy (span emission cuts consumed units out while preserving
  position/separators); genuinely different content still declines.

- Atomic merged-file writes (WriteUtf16Atomic: temp + same-volume rename):
  merged output lands inside the live game's Mods tree, and a game launch
  during a 40-file re-merge compiled truncated scripts into
  "'X' is not a member of Y" errors that looked exactly like a bad merge.

8 new tests (153 total).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GXAuGMLB44T5Zv5o5ZzKah
@TheValiantOne
TheValiantOne merged commit ee640c0 into main Aug 11, 2026
1 check passed
@TheValiantOne

Copy link
Copy Markdown
Owner Author

Mirrored end-to-end validation results (post-redeploy)

Full real-pipeline run (MCP merge_conflicts, overwrite: true, all conflicts) against a junction-mirrored copy of the live 195-mod install:

  • 41/41 merged, zero skips — including r4Player.ws, previously unmergeable. Its 11-step chain now completes: 7 steps rescued (5 DiffAlgorithmException + 2 silent-corruption catches by the new validator, at the modImmersiveSound and modSwordsDurability steps), the rest clean.
  • 3 whole-file outputs rejected mid-run by the content invariants and successfully re-merged via the function-level rescue — corruption that previous builds shipped silently.
  • Post-run sweep over all 41 outputs: sanity gate 41/41 pass; key mod-added symbols verified present (WPI, renewableIngredientList, the map-menu canary member, GetVoiceSetLastPlayed, IsSlowActive); 73 function-level decisions, all audited.
  • One duplicate scoped name remains in r4Player.ws output: CR4Player::default:interactDist — traced to the input (the hand-repaired accumulated file contains both vanilla's 3.5f and the mod's 4.5f lines), which the validator correctly tolerates (output count ≤ max input count). Pre-existing input wart, not an engine defect; a pristine rebuild of that one file would clear it.

73 total: 153 unit tests + this end-to-end run, all green.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant