Parent: #82
Depends on: #86
Goal
Fix the class-3 emission divergence: string values inside Array(...) initializers must be emitted in the pinned oracle's Custom String("...") form, so supported-surface programs with string arrays produce N-level-equal Workshop output.
Context (QA class-3 finding, first of M11)
globalvar x = ["a","b"] emits native Array("a","b") vs oracle Array(Custom String("a"), Custom String("b")). Corpus instance: pixelart.opy:35 globalvar owo = [' ', ...] diverges at normalized char 411; pixelart full compile exit 0 and settings-section equality hold, but full-program normalized equality fails. Arrays + strings are inside the declared supported surface (support-matrix); no documented intent (not class 5); complete evidence (not class 6). Pre-existing M8 emitter bug, surfaced by the first full-program compile; not a settings regression. The 6 v1-gates fixtures pass because none contains a string array.
Scope (bounded emitter correctness fix)
crates/wright-workshop/src/emitter.rs: render string values in the contexts the pinned oracle wraps with Custom String("...") — known instance: direct string elements of Value::Array (the shared native/adapter emission path covers both). Match the oracle's string-wrapping in value positions for every context evidenced by the minimal repro and the corpus snapshots; no context that currently matches the oracle may change.
- Regression tests: minimal repro (
globalvar x = ["a","b"] + one rule) byte-asserted Array(Custom String("a"), Custom String("b")); pixelart full-program normalized-equality check (v1 normalizer: debug-HUD collapse + whitespace collapse); the 6 v1-gates rows still green; the 7 settings sections still equal (pixelart 256, santa 350, broken-weapons 511, client-to-server 297, parabola 136, crosshair 144, inputhud 476 — whitespace-collapsed); ws-parser roundtrip for the new spelling.
Non-goals (hard)
- No deferred syntax (settings semantics,
\, ++/--, dicts, """, #!obfuscate, expression-level in/not in, named arguments, range literals, Team.2, inline conditionals, comprehensions, bytes literals, multiline strings).
- No normalizer change to mask the divergence (would manufacture a class 5 without documented intent).
- No v1-gates
FIXTURES change. A pixelart gate row may be proposed later only after verification, with inventory evidence.
Acceptance criteria (QA-verifiable)
- AC-1: minimal repro emits
Array(Custom String("a"), Custom String("b")) (byte-asserted test); pinned-oracle artifact identical under the v1 normalizer.
- AC-2: pixelart full-program normalized equality (
normalizedEqual: True in target/m11-nlevel.json, nativeExit 0) — or, if any divergence remains, QA records the precise remaining divergence list and reclassifies each.
- AC-3: previously-passing emission contexts unchanged: v1-gates 6/6 (FIXTURES unchanged); the 7 settings sections still equal; roundtrip/emitter suites green.
- AC-4: no new class-3 findings: per-program first-failure matrix unchanged or strictly improved (pixelart becomes the first full-compile + full-equality program); oracle 20/20; adapter 22/22; differential green (PARITY_CASES 7 rows; a pixelart row only if native==adapter HIR subtree equality is separately verified — never forced); all cargo suites; six CI jobs green.
- AC-5: inventory refreshed at the fix commit (class-3 resolved; parity baseline; pixelart row status exactly as verified).
Admin note
#86 closes with AC-3 recorded as contingent-not-justified (its acceptance record is complete); this issue carries the pixelart full-program row forward (N-level AC; PARITY_CASES row contingent on a new native==adapter HIR assertion).
PM amendment — extended acceptance criteria (2026-08-14)
QA verification at dbd342e (docs/opy/m11-issue87-verification.md) found AC-2 not met: pixelart full-program normalized equality fails (279 regions / 10,481 positions, all in the 9 artwork string-array sites) with three recorded mechanisms. The residual set is the same emission contract as this issue; the acceptance criteria are extended within #87 (no new issue).
- AC-1 (stand): minimal repro
Array(Custom String("a"), Custom String("b")) byte-equal; the test artifact uses a real action body (no pass), oracle-identical end to end.
- AC-2 (amended): pixelart full-program normalized equality (
normalizedEqual: True in target/m11-nlevel.json) OR a residual list shrinking to exactly what remains after A/B/C, each item reclassified; mechanisms A and B must be gone.
- AC-3 (new): long-string split parity — oracle rule: non-final segments = exactly 125 content chars +
{0} (128 total), chained Custom String("...{0}", Custom String("...")), final segment = remainder; synthetic 300-char (3 segments) and 1000-char (8 segments) repros byte-equal to the pinned oracle artifact (byte-quoted in tests); char-count basis pinned from the oracle artifact.
- AC-4 (new): value-string re-escape parity —
globalvar x = "a\nb" emits Custom String("a\nb") with literal \n (0x5C 0x6E), byte-equal to oracle; \ " \t \r handled per oracle evidence; the 7 settings sections still equal (256/350/511/297/136/144/476) proving the settings path untouched.
- AC-5 (new): empty-rule drop parity — pass-only rule and condition-without-actions repros emit no rule; artifact byte-equal to the oracle.
- AC-6 (new): roundtrip fixed-point for the new spellings — split strings and re-escaped strings parse and re-emit byte-identically; a bounded ws-parser gain for the oracle's
Custom String chain shape is authorized if the generic call path cannot consume it (self-contained; no general call rework).
- AC-7 (stand): previously-passing contexts unchanged — v1-gates 6/6 (FIXTURES unchanged), emitter/driver/roundtrip suites green.
- AC-8 (stand): 12-program first-failure matrix unchanged; no new class-3; oracle 20/20; adapter 22/22; differential green (PARITY_CASES 7; a pixelart row only with a committed native==adapter HIR subtree assertion — never forced); all cargo suites; six CI jobs green.
- AC-9 (stand): pixelart parity-row decision per AC-8; N-level row status exactly as verified.
- AC-10 (new): inventory refresh at the fix commit (class-3 A/B/C resolved; pixelart row status; supersede the 4f41cdf residual sections where they differ).
Non-negotiables: no normalizer masking; no v1-gates FIXTURES change; no deferred syntax; the settings emission path untouched.
PM amendment 2 — final correctness batch (AC-11..AC-14, 2026-08-14)
QA's pre-gate scan (docs/opy/m11-inventory-final.md, a87b0ec) resolved the pixelart class-3 family and surfaced three further class-3 findings on supported surface, all repro-pinned: (1) trailing-if End; omission + ws-parser roundtrip asymmetry; (2) .format() constant folding (semantically equivalent); (3) non-default numeric globalvar initializers dropped (semantic — variables start at 0; live adapter carries them; the support-matrix claim is refuted and corrected in-batch). All three are authorized as one bounded batch inside #87.
- AC-11 (numeric initializers):
j = 5 preserved in HIR and emitted Set Global Variable(j, 5) in the Initialize rule byte-equal to oracle; h = 0 dropped (matching oracle and adapter); k = 0.0 preserved with the oracle's spelling; bare-index form untouched; new synthetic fixture (e.g. synthetic/declarations-numbers) with adapter fixture + oracle.json + PARITY_CASES row (HIR parity) + N-level; support-matrix claim corrected: "integer-0 literal initializers are dropped from HIR (matching the reference adapter); non-zero and non-integer numeric initializers are preserved".
- AC-12 (trailing-if): rule-final if/if-else emitted without the closing
End; byte-equal to oracle (repro pins a-d: rule-final if, rule-final if-else, if inside rule-final for/while, middle-of-rule if which keeps End); ws parser accepts the oracle's spelling (bounded, rule-block-close-only); roundtrip fixed-point green.
- AC-13 (format folding): all-constant numeric-arg
.format() folds to Custom String("value: 3") byte-equal (repros: single arg, multi-arg, float spelling 0.5); variable-arg case unchanged byte-equal; HUD/debug path untouched; folded output feeds splitting/re-escaping.
- AC-14 (no regression): v1-gates 6/6 (FIXTURES unchanged); 7 settings sections equal; pixelart N-level still True; 12-program first-failure matrix unchanged; differential green (PARITY_CASES 7 or 8 per AC-11); oracle 20/20; adapter 22/22; all cargo suites; clippy 0; fmt clean; six CI jobs green at the batch commit; inventory refreshed (superseding a87b0ec's findings sections); no new class-3 anywhere.
Non-negotiables: no deferred syntax; no normalizer masking; no v1-gates FIXTURES change; no settings-surface change; no E-level work. After the checklist passes, the #82 final gate runs (accept-with-limitations criteria per the #82 decision record).
PM amendment 3 — final batch (AC-15..AC-17, 2026-08-14)
QA's final checklist (ea2c1a4) verified the full batch but the final scan found two more class-3 items, both emission/roundtrip canonicalization on matrix-listed supported surface (corpus-unexercised, zero first-failure/parity impact): (1) format placeholder canonicalization — "v: {}".format(x) native emits {}, oracle emits {0}; (2) playervar member reads in value positions — native Event Player.p, oracle (Event Player).p, and both spellings fail the native ws parser (native emission does not round-trip). Authorized as the final batch:
- AC-15 (placeholder canonicalization): implicit
{} rewrites to the oracle's explicit numbered form per pinned repros (single/multi/mixed explicit+implicit/repeated-arg shapes — pin from oracle artifacts FIRST); HIR format text stays {} in both producers (differential unaffected); constant-fold and HUD/debug paths untouched; un-pinned shapes unchanged with a fixed-point parseability assertion.
- AC-16 (playervar reads):
g = eventPlayer.p emits Set Global Variable(g, (Event Player).p) byte-equal to oracle; ws parser accepts the oracle's spelling (bounded); roundtrip fixed-point green; the SET form and method-call receivers untouched.
- AC-17 (closure scan + no new class-3): the ENUMERATED matrix-surface closure scan runs clean — every emitter-produced spelling self-roundtrips (parse(emit(x)) byte-identical) and matches the oracle's canonical form where a repro compiles; 12-program first-failure matrix unchanged; v1-gates 6/6 (FIXTURES unchanged); 7 settings sections equal; pixelart normalizedEqual True (19,925/19,925); PARITY_CASES 8 unchanged; oracle 21/21; adapter 23/23; all cargo suites; clippy 0; fmt clean; six CI jobs green at the batch commit; inventory refreshed (superseding ea2c1a4's open-items sections).
Scan boundary (decision): the no-open-class-3 criterion is scoped to the enumerated matrix-listed emission surface (self-roundtrip closure + oracle-canonical repros); constructs outside the matrix are deferred by definition; this is the LAST scan — no further ad-hoc scans. After (a)+(b) and a clean closure scan, the #82 final gate runs as accept-with-limitations per the #82 decision record.
PM amendment 4 — final single item (AC-18, 2026-08-14)
The closure scan's sole remaining finding (augmented playervar assignment eventPlayer.p += 1: native Set Player Variable(..., Add((Event Player).p, 1)) vs oracle Modify Player Variable(Event Player, p, Add, 1); matrix-listed, oracle success, semantically equivalent, both spellings roundtrip, zero corpus coverage) is accepted as class 3 and authorized as the final single-item batch:
- AC-18: pin the oracle's per-operator Modify spellings for playervar augmented assignments from byte-quoted artifacts first (+= -= *= and any the oracle renders as Modify Player Variable; operators the oracle does NOT support via Modify — per actual behavior e.g. /= //= %= if un-evidenced — stay on the current Set (...) path with a recorded note); fix the HIR->WIR lowering so playervar augmented assignments produce ModifyPlayerVariable for the oracle-evidenced op set, mirroring the already-byte-equal global path (shared by native and adapter-driven lowering; HIR unchanged; differential/PARITY_CASES 8 unaffected); add the playervar augmented-assignment family to the closure scan (roundtrip fixed-point). Regression: global
g += 1 unchanged; pixelart normalizedEqual True; 7 settings sections; v1-gates 6/6; PARITY_CASES 8; 12-program matrix; oracle 21/21; adapter 23/23; all suites; clippy/fmt; six CI jobs green; inventory refreshed. The eventPlayer.getCurrentHero() probe is closed as outside-surface (catalog-data gap, unknown-enum-member precedent — deferred data change, not class 3).
Parent: #82
Depends on: #86
Goal
Fix the class-3 emission divergence: string values inside
Array(...)initializers must be emitted in the pinned oracle'sCustom String("...")form, so supported-surface programs with string arrays produce N-level-equal Workshop output.Context (QA class-3 finding, first of M11)
globalvar x = ["a","b"]emits nativeArray("a","b")vs oracleArray(Custom String("a"), Custom String("b")). Corpus instance:pixelart.opy:35globalvar owo = [' ', ...]diverges at normalized char 411; pixelart full compile exit 0 and settings-section equality hold, but full-program normalized equality fails. Arrays + strings are inside the declared supported surface (support-matrix); no documented intent (not class 5); complete evidence (not class 6). Pre-existing M8 emitter bug, surfaced by the first full-program compile; not a settings regression. The 6 v1-gates fixtures pass because none contains a string array.Scope (bounded emitter correctness fix)
crates/wright-workshop/src/emitter.rs: render string values in the contexts the pinned oracle wraps withCustom String("...")— known instance: direct string elements ofValue::Array(the shared native/adapter emission path covers both). Match the oracle's string-wrapping in value positions for every context evidenced by the minimal repro and the corpus snapshots; no context that currently matches the oracle may change.globalvar x = ["a","b"]+ one rule) byte-assertedArray(Custom String("a"), Custom String("b")); pixelart full-program normalized-equality check (v1 normalizer: debug-HUD collapse + whitespace collapse); the 6 v1-gates rows still green; the 7 settings sections still equal (pixelart 256, santa 350, broken-weapons 511, client-to-server 297, parabola 136, crosshair 144, inputhud 476 — whitespace-collapsed); ws-parser roundtrip for the new spelling.Non-goals (hard)
\,++/--, dicts,""",#!obfuscate, expression-levelin/not in, named arguments, range literals,Team.2, inline conditionals, comprehensions, bytes literals, multiline strings).FIXTURESchange. A pixelart gate row may be proposed later only after verification, with inventory evidence.Acceptance criteria (QA-verifiable)
Array(Custom String("a"), Custom String("b"))(byte-asserted test); pinned-oracle artifact identical under the v1 normalizer.normalizedEqual: Truein target/m11-nlevel.json, nativeExit 0) — or, if any divergence remains, QA records the precise remaining divergence list and reclassifies each.Admin note
#86 closes with AC-3 recorded as contingent-not-justified (its acceptance record is complete); this issue carries the pixelart full-program row forward (N-level AC; PARITY_CASES row contingent on a new native==adapter HIR assertion).
PM amendment — extended acceptance criteria (2026-08-14)
QA verification at dbd342e (docs/opy/m11-issue87-verification.md) found AC-2 not met: pixelart full-program normalized equality fails (279 regions / 10,481 positions, all in the 9 artwork string-array sites) with three recorded mechanisms. The residual set is the same emission contract as this issue; the acceptance criteria are extended within #87 (no new issue).
Array(Custom String("a"), Custom String("b"))byte-equal; the test artifact uses a real action body (no pass), oracle-identical end to end.normalizedEqual: Truein target/m11-nlevel.json) OR a residual list shrinking to exactly what remains after A/B/C, each item reclassified; mechanisms A and B must be gone.{0}(128 total), chainedCustom String("...{0}", Custom String("...")), final segment = remainder; synthetic 300-char (3 segments) and 1000-char (8 segments) repros byte-equal to the pinned oracle artifact (byte-quoted in tests); char-count basis pinned from the oracle artifact.globalvar x = "a\nb"emitsCustom String("a\nb")with literal\n(0x5C 0x6E), byte-equal to oracle;\"\t\rhandled per oracle evidence; the 7 settings sections still equal (256/350/511/297/136/144/476) proving the settings path untouched.Custom Stringchain shape is authorized if the generic call path cannot consume it (self-contained; no general call rework).Non-negotiables: no normalizer masking; no v1-gates FIXTURES change; no deferred syntax; the settings emission path untouched.
PM amendment 2 — final correctness batch (AC-11..AC-14, 2026-08-14)
QA's pre-gate scan (docs/opy/m11-inventory-final.md, a87b0ec) resolved the pixelart class-3 family and surfaced three further class-3 findings on supported surface, all repro-pinned: (1) trailing-if
End;omission + ws-parser roundtrip asymmetry; (2).format()constant folding (semantically equivalent); (3) non-default numeric globalvar initializers dropped (semantic — variables start at 0; live adapter carries them; the support-matrix claim is refuted and corrected in-batch). All three are authorized as one bounded batch inside #87.j = 5preserved in HIR and emittedSet Global Variable(j, 5)in the Initialize rule byte-equal to oracle;h = 0dropped (matching oracle and adapter);k = 0.0preserved with the oracle's spelling; bare-index form untouched; new synthetic fixture (e.g. synthetic/declarations-numbers) with adapter fixture + oracle.json + PARITY_CASES row (HIR parity) + N-level; support-matrix claim corrected: "integer-0literal initializers are dropped from HIR (matching the reference adapter); non-zero and non-integer numeric initializers are preserved".End;byte-equal to oracle (repro pins a-d: rule-final if, rule-final if-else, if inside rule-final for/while, middle-of-rule if which keeps End); ws parser accepts the oracle's spelling (bounded, rule-block-close-only); roundtrip fixed-point green..format()folds toCustom String("value: 3")byte-equal (repros: single arg, multi-arg, float spelling 0.5); variable-arg case unchanged byte-equal; HUD/debug path untouched; folded output feeds splitting/re-escaping.Non-negotiables: no deferred syntax; no normalizer masking; no v1-gates FIXTURES change; no settings-surface change; no E-level work. After the checklist passes, the #82 final gate runs (accept-with-limitations criteria per the #82 decision record).
PM amendment 3 — final batch (AC-15..AC-17, 2026-08-14)
QA's final checklist (ea2c1a4) verified the full batch but the final scan found two more class-3 items, both emission/roundtrip canonicalization on matrix-listed supported surface (corpus-unexercised, zero first-failure/parity impact): (1) format placeholder canonicalization —
"v: {}".format(x)native emits{}, oracle emits{0}; (2) playervar member reads in value positions — nativeEvent Player.p, oracle(Event Player).p, and both spellings fail the native ws parser (native emission does not round-trip). Authorized as the final batch:{}rewrites to the oracle's explicit numbered form per pinned repros (single/multi/mixed explicit+implicit/repeated-arg shapes — pin from oracle artifacts FIRST); HIR format text stays{}in both producers (differential unaffected); constant-fold and HUD/debug paths untouched; un-pinned shapes unchanged with a fixed-point parseability assertion.g = eventPlayer.pemitsSet Global Variable(g, (Event Player).p)byte-equal to oracle; ws parser accepts the oracle's spelling (bounded); roundtrip fixed-point green; the SET form and method-call receivers untouched.Scan boundary (decision): the no-open-class-3 criterion is scoped to the enumerated matrix-listed emission surface (self-roundtrip closure + oracle-canonical repros); constructs outside the matrix are deferred by definition; this is the LAST scan — no further ad-hoc scans. After (a)+(b) and a clean closure scan, the #82 final gate runs as accept-with-limitations per the #82 decision record.
PM amendment 4 — final single item (AC-18, 2026-08-14)
The closure scan's sole remaining finding (augmented playervar assignment
eventPlayer.p += 1: nativeSet Player Variable(..., Add((Event Player).p, 1))vs oracleModify Player Variable(Event Player, p, Add, 1); matrix-listed, oracle success, semantically equivalent, both spellings roundtrip, zero corpus coverage) is accepted as class 3 and authorized as the final single-item batch:g += 1unchanged; pixelart normalizedEqual True; 7 settings sections; v1-gates 6/6; PARITY_CASES 8; 12-program matrix; oracle 21/21; adapter 23/23; all suites; clippy/fmt; six CI jobs green; inventory refreshed. The eventPlayer.getCurrentHero() probe is closed as outside-surface (catalog-data gap, unknown-enum-member precedent — deferred data change, not class 3).