Skip to content

Fix function-level merge gap handling (compile-breaking defects 1+2) - #27

Merged
TheValiantOne merged 1 commit into
mainfrom
fix/function-level-gap-handling-v2
Aug 11, 2026
Merged

Fix function-level merge gap handling (compile-breaking defects 1+2)#27
TheValiantOne merged 1 commit into
mainfrom
fix/function-level-gap-handling-v2

Conversation

@TheValiantOne

Copy link
Copy Markdown
Owner

Summary

Fixes both compile-breaking defects documented in docs/bugs/function-level-merge-gap-handling.md (committed here alongside the fix), found on a real 198-mod install where the function-level rescue produced merged output for r4Player.ws/player.ws/damageManagerProcessor.ws that the game refused to compile.

Defect 2 (dropped declarations) — plain member declarations (private var x : bool;, default y = 4.5f;) lived in "gap" territory, which always reverted to vanilla's text on reassembly, silently dropping mod-added declarations while the code referencing them survived. Fix: var/default/autobind statements now extract as ScriptUnitKind.MemberDeclaration units and participate in per-unit resolution. Unit identity is scope-qualified (ScriptUnit.ScopedName, e.g. CR4Player::mCSMCR; states as Combat@CR4Player::phase) via a top-level type-range prescan — member names recur across the several classes a real .ws file contains, so bare-name alignment would mis-align them. UnitAligner now matches on ScopedName.

Defect 1 (units emitted outside their class, separators eaten) — vanilla's gap at an insertion slot can contain the class-closing brace, and the old emission appended inserted units after the whole vanilla gap ('public' has no sense for global function ...), with the separators that live in the side's own gaps discarded (}<TAB>public function ...). Fix: a slot where exactly one side inserts emits that side's own contiguous span (its gaps + units, verbatim), preserving both brace-relative position and separators. Ambiguous placements — both sides inserting at a brace-carrying slot, or a deleted anchor beside one — decline to the conflict-marker sidecar instead of guessing.

Post-reassembly sanity gate (bug doc's suggested check 1) — PassesReassemblySanityGate (public, so validation tooling can reuse it) walks the output's structural mask and declines any rescue whose output has a member-shaped declaration at brace depth 0, negative/nonzero final brace depth, or content that no longer scans. This catches whole classes of future interleaving bugs, not just the two known ones.

Real-data validation

  • Gate fails the actual broken engine output preserved from the failing run, on exactly the orphaned accessor the game rejected (public function GetVoiceSetLastPlayed() : float at global scope), and passes the hand-repaired live merged files and vanilla r4Player.ws — zero false positives.
  • Extraction round-trips byte-exact on real vanilla r4Player.ws (1075 units, 308 of them member declarations), player.ws, damageManagerProcessor.ws, actor.ws, baseEffect.ws; worst-case extract 118 ms.
  • The failing run's inputs get re-merged end-to-end against the live install in the follow-up regression pass (depends on the overwrite support PR, next).

Tests

17 new tests (145 total, all green): defect-shaped fixtures (member appended at class end + following global exec function; declaration added while the other side edits a function; default-value change; both-sides-insert separation + decline-at-class-boundary), scoped extraction (multi-declarator lists, default:x identity, same member name across two classes, state-in-parent scoping, struct members, locals-never-extracted, @addField precedence), and the gate directly (orphaned member, mangled-onto-brace-line, valid global shapes, unbalanced braces).

One test in this suite caught a real bug in the fix itself during development (a vanilla unit following synthesized insertions lost its separator) — fixed via endedOnSynthesizedUnit threading.

dotnet build / dotnet test (145/145) / dotnet format whitespace --verify-no-changes all green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GXAuGMLB44T5Zv5o5ZzKah

…g bug)

Fixes both compile-breaking defects in docs/bugs/
function-level-merge-gap-handling.md:

- Plain member declarations (var/default/autobind) are extracted as
  scope-qualified units (ScriptUnit.ScopedName, via a top-level type-range
  prescan) so a mod adding one participates in per-unit resolution instead
  of being silently dropped with vanilla's gap text (defect 2).
- Insertion slots emit the inserting side's own contiguous span, preserving
  position relative to class braces and the separators between consecutive
  inserted units (defect 1); ambiguous placements (both sides inserting at
  a brace-carrying slot, or deleted anchors next to one) decline instead of
  guessing.
- A post-reassembly sanity gate declines any rescue whose output has a
  member-shaped declaration at brace depth 0 or unbalanced braces -
  validated against the real broken output (fails on exactly the orphaned
  accessor the game rejected) with zero false positives on real vanilla
  r4Player.ws/player.ws/actor.ws/baseEffect.ws, all of which also
  round-trip byte-exact through the extended extractor.

17 new tests (145 total).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GXAuGMLB44T5Zv5o5ZzKah
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