@henols/vice-mcp 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,17 @@
1
1
  // anno-export-asm.ts -- the ONE place annotation-store rows plus image bytes
2
- // become ACME source text (EXPORT-01).
2
+ // become ACME source text.
3
3
  //
4
4
  // ---------------------------------------------------------------------------
5
5
  // WHY THIS FILE EXISTS
6
6
  // ---------------------------------------------------------------------------
7
- // The previous export route was WITHDRAWN in Phase 29 rather than left
7
+ // The previous export route was WITHDRAWN rather than left
8
8
  // standing, because it made a reassembly claim nothing verified: it produced
9
9
  // something that looked like ACME source and asserted, in effect, that
10
10
  // assembling it would reproduce the program. No assembler ever ran. Withdrawing
11
11
  // it was the right call and the withdrawal notices in both skill trees are the
12
12
  // record that the capability was missing.
13
13
  //
14
- // This module is the rebuild, over the Phase 28 annotation store, and it is
14
+ // This module is the rebuild, over this project's own annotation store, and it is
15
15
  // allowed to exist only because the claim is now settled somewhere else: a real
16
16
  // ACME 0.97 assembles this module's output and the resulting bytes are diffed
17
17
  // against the IMAGE bytes. Nothing in this file verifies this file. Re-reading,
@@ -36,7 +36,7 @@
36
36
  // against a real assembler across all 256 opcodes. Decoding here happens
37
37
  // through `decode()` and nowhere else.
38
38
  // - Never write a second `!byte` / `+2` / hex emitter. `disasm-renderer.ts`
39
- // owns D-09's `!byte` substitution and D-11's width invariant, both
39
+ // owns the `!byte` substitution rule and the operand-width invariant, both
40
40
  // verified against real ACME. A second emitter would be a second answer to
41
41
  // "how wide is this operand", and the two would drift silently.
42
42
  // - Never restate the eleven auto-name prefixes here. `anno-types.ts:93-99`
@@ -56,9 +56,9 @@
56
56
  // - Never compare a `dataType` string in this module beyond the FOUR
57
57
  // places that already do, each of which says so in its own comment:
58
58
  // `CODE_DATA_TYPE`'s decoder-or-dump branch, `WORD_PAIR_DATA_TYPES`'s
59
- // `!word` eligibility check, (phase 47, plan 47-03)
59
+ // `!word` eligibility check,
60
60
  // `EXTERNAL_FILE_DATA_TYPE`'s `!binary`-versus-inline branch inside
61
- // `emitDataLines()`, and (phase 47, plan 47-06) `isSplitAddressDataType()`'s
61
+ // `emitDataLines()`, and `isSplitAddressDataType()`'s
62
62
  // paired-symbol-versus-raw-byte branch in the block loop. All four are
63
63
  // questions about the emitted TEXT. `block-class.ts` is the one place in
64
64
  // this tree allowed to INTERPRET that column -- what the data means -- and
@@ -72,7 +72,7 @@
72
72
  // - Never interpolate a read file's own bytes into an error message. A path,
73
73
  // an address and a length are facts ABOUT a file; its contents are not, and
74
74
  // an error text that quotes them turns a refusal into a content-disclosure
75
- // oracle (CR-03). Every throw below carries paths, addresses and counts and
75
+ // oracle. Every throw below carries paths, addresses and counts and
76
76
  // nothing read out of the image or the store.
77
77
  // - Never sanitise a label name. `assertLegalAcmeIdentifier()`'s contract is
78
78
  // REJECT: a space-to-underscore substitution silently merges two distinct
@@ -114,13 +114,12 @@ import { assertLegalAcmeIdentifier } from "./anno-acme-ident.ts";
114
114
  //
115
115
  // WHAT NOT TO DO: do not restate the eleven here, in any form -- not as an
116
116
  // array, not as a second regex, not as a doc comment listing them.
117
- // `anno-types.ts:93-99` forbids it by name, and `EXPORT-02` names the failure
118
- // mode: a five-prefix copy under-counts silently.
117
+ // `anno-types.ts:93-99` forbids it by name: a five-prefix copy under-counts silently.
119
118
  import { AUTO_NAME_PREFIX_RE } from "./anno-coverage.ts";
120
- // THE ONE OWNING DECODER (D-16, plan 45-03). This module decodes NOTHING
119
+ // THE ONE OWNING DECODER. This module decodes NOTHING
121
120
  // itself -- `decomposeRegisterValue()` is the ONLY place a register value is
122
- // split into named bit-fields, and `anno_disassemble` (plan 45-05, the other
123
- // D-16 renderer) calls the SAME function. Never re-derive a per-field bit
121
+ // split into named bit-fields, and `anno_disassemble` (the other renderer sharing
122
+ // this decoder) calls the SAME function. Never re-derive a per-field bit
124
123
  // mask in this file -- a phase-45 verification gate greps this file's own
125
124
  // text for that shape and must find none.
126
125
  import { decomposeRegisterValue, hasRegBitsEntry, type RegisterDecomposition } from "./anno-enum-gen.ts";
@@ -128,7 +127,7 @@ import { decode } from "./disasm-decoder.ts";
128
127
  import type { Instruction } from "./disasm-decoder.ts";
129
128
  import { renderLine } from "./disasm-renderer.ts";
130
129
  import { parsePrg, flatImageOrigin } from "./prg-image.ts";
131
- // BUILD-05 (phase 46 plan 01): the ONE reader of `recovery/PROVENANCE.md`'s
130
+ // the ONE reader of `recovery/PROVENANCE.md`'s
132
131
  // generated tier. This module never recomputes a verdict -- see
133
132
  // `anno-provenance-ledger.ts`'s own header for why reading and recomputing
134
133
  // are deliberately kept apart.
@@ -142,7 +141,7 @@ import { provenanceForRange, readProvenanceLedger, type ProvenanceLedger } from
142
141
  const CODE_DATA_TYPE = "code";
143
142
 
144
143
  /** The store's own spelling for a large binary blob exported AS ITS OWN
145
- * FILE rather than inline (phase 47, plan 47-03; `anno_set_data_type`'s
144
+ * FILE rather than inline (`anno_set_data_type`'s
146
145
  * schema calls this "large binary blob to export as-is") -- the eleventh of
147
146
  * `DATA_TYPES`' twelve members, the one vocabulary this module imports
148
147
  * rather than restates. The SAME discipline `CODE_DATA_TYPE` above already
@@ -167,7 +166,7 @@ export interface ExportBlock {
167
166
  /** How many CONTENT lines the block emitted -- not its `* =` origin line and
168
167
  * not the two `!if * != ...` assertions that bracket it. */
169
168
  lineCount: number;
170
- /** Phase 47, plan 47-01: exactly what `emitBlock()` returned for this block
169
+ /** exactly what `emitBlock()` returned for this block
171
170
  * -- the `* =` origin line, both `!if * != ...` bracket assertions, and
172
171
  * every content line between them, in emitted order. This is FOR a tree
173
172
  * writer to partition an emission that is already proven, rather than a
@@ -180,7 +179,7 @@ export interface ExportBlock {
180
179
  }
181
180
 
182
181
  /**
183
- * Phase 47, plan 47-03 (BUILD-02): one `external_file`-typed block's own
182
+ * one `external_file`-typed block's own
184
183
  * `.bin` sibling. `bytes` is the block's own slice of the IMAGE, carried
185
184
  * verbatim -- never a copy that passed through any conversion, on the same
186
185
  * terms `expectedBytes` is derived from the image and never from `source`.
@@ -226,8 +225,8 @@ export interface ExportAsmOptions {
226
225
  /**
227
226
  * The provenance ledger to annotate every emitted block from -- `c64-
228
227
  * provenance-diff`'s generated `recovery/PROVENANCE.md`, read (never
229
- * re-derived) through `readProvenanceLedger()`. OPTIONAL (assumption A1,
230
- * `46-01-PLAN.md`): every existing caller that omits it keeps exporting
228
+ * re-derived) through `readProvenanceLedger()`. OPTIONAL: every existing
229
+ * caller that omits it keeps exporting
231
230
  * exactly as before, byte for byte and comment for comment. NOTHING
232
231
  * CONFINES THIS PATH INSIDE THIS MODULE, on the same terms as `imagePath`
233
232
  * above -- the CALLER owns its confinement. Supplying it and having the
@@ -240,14 +239,14 @@ export interface ExportAsmOptions {
240
239
  export interface ExportAsmResult {
241
240
  /** The ACME source text. */
242
241
  source: string;
243
- /** Phase 47, plan 47-01: the symbol-definition block VERBATIM -- every
242
+ /** the symbol-definition block VERBATIM -- every
244
243
  * enum-variant definition line followed by every store label's own
245
244
  * definition line, in exactly the order `source` carries them. This is FOR
246
245
  * `exportAsmTree()` to write `symbols.a` from, never re-derived: `source`
247
246
  * is still `["!cpu 6510", ...headerLines, ...blockLines].join("\n")` plus
248
247
  * the trailing newline, byte for byte unchanged by this field's addition. */
249
248
  headerLines: string[];
250
- /** Phase 47, plan 47-01: every scope the store holds, read by `listScopes()`
249
+ /** every scope the store holds, read by `listScopes()`
251
250
  * as a SEVENTH call inside this function's existing store handle -- there
252
251
  * is still exactly one handle opened for the whole export. `exportAsmTree()`
253
252
  * uses this to decide which scope (or none) each block belongs to; nothing
@@ -267,7 +266,7 @@ export interface ExportAsmResult {
267
266
  * i.e. `sortedLabels.length`, one per `anno_label` row in range.
268
267
  *
269
268
  * THIS DOC USED TO SAY "how many symbol definitions the header carries",
270
- * AND THAT WAS NOT WHAT IT COUNTED (30-REVIEW WR-01, corrected 2026-08-31).
269
+ * AND THAT WAS NOT WHAT IT COUNTED (corrected 2026-08-31).
271
270
  * The two readings diverge in BOTH directions: a mid-instruction label is
272
271
  * defined inline and skipped by the header loop yet still counted here,
273
272
  * and every `enumDefinitionLines` entry IS a header definition yet is not.
@@ -283,7 +282,7 @@ export interface ExportAsmResult {
283
282
  /** How many definition lines the HEADER block actually carries -- enum
284
283
  * variant definitions plus every store label NOT defined inline. Computed
285
284
  * from `headerLines` itself, so it cannot drift from the emitted text the
286
- * way a separately-maintained count did (30-REVIEW WR-01). */
285
+ * way a separately-maintained count did. */
287
286
  headerDefinitionCount: number;
288
287
  /** How many decoded instructions ACME's `!cpu 6510` cannot express, and
289
288
  * which therefore went out as `!byte` directives with their mnemonic moved
@@ -293,12 +292,12 @@ export interface ExportAsmResult {
293
292
  * block whose `dataType` is not `code`. A code block contributes nothing
294
293
  * here, however many bytes it decoded. */
295
294
  dataByteCount: number;
296
- /** Phase 47, plan 47-03 (BUILD-02): every `external_file`-typed block's
295
+ /** every `external_file`-typed block's
297
296
  * own sibling data file, ascending by `start`. `dataByteCount` above still
298
297
  * counts these bytes -- they went out through the data path exactly as an
299
298
  * inline `!byte`-typed range's bytes would have, only to a different
300
299
  * destination file, and a count whose name stops matching what it counts
301
- * is the WR-01 lesson this module already learned once. See
300
+ * is the same lesson this module already learned once. See
302
301
  * `ExportBinary`'s own doc-comment for the obligation a caller that reads
303
302
  * this field takes on. */
304
303
  binaries: ExportBinary[];
@@ -313,8 +312,8 @@ export interface ExportAsmResult {
313
312
  * header definition block, because such a label is defined inline and
314
313
  * defining it twice is ACME's `Symbol already defined.`
315
314
  *
316
- * COUNTED PER EMITTED DEFINITION, NOT PER ADDRESS (30-REVIEW WR-02,
317
- * corrected 2026-08-31). It used to be `midInstructionLabelAddresses.size`,
315
+ * COUNTED PER EMITTED DEFINITION, NOT PER ADDRESS (corrected 2026-08-31).
316
+ * It used to be `midInstructionLabelAddresses.size`,
318
317
  * a set of ADDRESSES, while the inline loop emits one line per LABEL.
319
318
  * `anno_label` is `unique` on `name` only and `setLabel()` refuses only a
320
319
  * name already bound to a DIFFERENT address, so two names at one address is
@@ -334,7 +333,7 @@ export interface ExportAsmResult {
334
333
  * two named figures, never one combined figure). */
335
334
  enumSubstitutionCount: number;
336
335
  /** How many of `enumSubstitutionCount`'s substitutions were rendered as an
337
- * OR-ed multi-bit decomposition (D-16/D-17) rather than a single whole-value
336
+ * OR-ed multi-bit decomposition rather than a single whole-value
338
337
  * variant name -- one register key `regbits` entry with two or more
339
338
  * fields, decoded through `decomposeRegisterValue()`. A single-field
340
339
  * register, or an enum usage whose name is not a register key at all,
@@ -342,10 +341,11 @@ export interface ExportAsmResult {
342
341
  enumDecompositionCount: number;
343
342
  /** How many RECORDS from `anno_excluded_range` the export emitted a marker
344
343
  * for -- at least one of their addresses overlapping at least one emitted
345
- * block. `BUILD-07` (phase 46 plan 05).
344
+ * block. This is the marker for a user-requested exclusion, recorded rather
345
+ * than silently dropped.
346
346
  *
347
347
  * COUNTS RECORDS, NOT MARKER LINES AND NOT EXCLUDED BYTES -- the same
348
- * WR-01 lesson this file already learned once about `symbolCount`: a count
348
+ * lesson this file already learned once about `symbolCount`: a count
349
349
  * whose name does not match what it counts gets printed to a user
350
350
  * verbatim. One exclusion record spanning two emitted blocks emits TWO
351
351
  * marker lines (one per block) and counts ONCE here. And no byte is ever
@@ -376,7 +376,7 @@ const WORDS_PER_DATA_LINE = 8;
376
376
  const WORD_PAIR_DATA_TYPES: readonly string[] = Object.freeze(["word", "address"]);
377
377
 
378
378
  /**
379
- * Phase 47, plan 47-06 (BUILD-03): true iff `dataType` is one of the TWO split
379
+ * true iff `dataType` is one of the TWO split
380
380
  * layouts that denote ADDRESSES -- `lo_hi_address` / `hi_lo_address` -- as
381
381
  * opposed to the two that denote WORDS (`lo_hi_word` / `hi_lo_word`), which
382
382
  * stay on the raw-`!byte` fallback below unchanged. This is the branch that
@@ -425,7 +425,7 @@ interface DataLine {
425
425
  * table is NOT invented here; that would be the same drift hazard wearing a
426
426
  * local name.
427
427
  *
428
- * `external_file` (phase 47, plan 47-03) IS THE OPPOSITE CASE from `!text`
428
+ * `external_file` IS THE OPPOSITE CASE from `!text`
429
429
  * above, and for the identical reason. `!text` is refused because ACME's
430
430
  * conversion table is outside this exporter's control; `!binary` is used
431
431
  * here because it applies NO table at all -- the bytes written to the
@@ -486,7 +486,7 @@ function emitDataLines(slice: Uint8Array, dataType: string, blockStart: number):
486
486
  return out;
487
487
  }
488
488
 
489
- /** Phase 47, plan 47-06 (BUILD-03): one split-address block's own contribution
489
+ /** one split-address block's own contribution
490
490
  * to the export's shared in-tree-reference bookkeeping -- the SAME
491
491
  * `unresolvedReferences`/`inTreeReferenceCount` totals the code path's in-tree
492
492
  * symbol rule feeds inside `exportAsm()`'s block loop. Returned rather than
@@ -638,8 +638,7 @@ function hex4(value: number): string {
638
638
  /**
639
639
  * `$XXXX` FOR A BLOCK'S EXCLUSIVE END, WHICH IS `hex4()` -- MASKED, NOT PADDED.
640
640
  *
641
- * THIS FUNCTION USED TO DO THE OPPOSITE, AND IT WAS WRONG (30-REVIEW WR-04,
642
- * corrected 2026-08-31). It padded without masking, so a range ending at
641
+ * THIS FUNCTION USED TO DO THE OPPOSITE, AND IT WAS WRONG (corrected 2026-08-31). It padded without masking, so a range ending at
643
642
  * `$ffff` produced the end assertion `!if * != $10000`. Its doc justified that
644
643
  * by asserting that `hex4()`'s mask "would render that as `$0000` -- an
645
644
  * assertion no assembly can ever satisfy, firing on a correct export". That
@@ -761,7 +760,7 @@ function loadImage(imagePath: string): { origin: number; bytes: Uint8Array } {
761
760
  * incorrect.
762
761
  *
763
762
  * THE HEX CASE IS LOWER, MATCHING EVERY OTHER EMITTER IN THIS DOCUMENT
764
- * (30-REVIEW IN-03, corrected 2026-08-31). This function used to emit
763
+ * (corrected 2026-08-31). This function used to emit
765
764
  * uppercase (`start = $C000`) while `hex2()`, `hex4()` and `hexExtent()` all
766
765
  * emit lowercase (`* = $0801`, `!byte $a9`), so one generated file carried two
767
766
  * conventions. Both assemble identically -- ACME is case-insensitive for hex
@@ -781,10 +780,9 @@ function formatSymbolDefinition(name: string, address: number): string {
781
780
  /**
782
781
  * One mid-instruction label definition, in the golden witness's own compact
783
782
  * spelling -- no spaces around the `=`, the offset in two hex digits:
784
- * `f_0900 =*+$01` [`.planning/notes/dxa-ghidra-pivot-evidence/anno.asm:202`].
785
- * That witness carries SIX such labels (lines 51, 81, 135, 145, 202 and 205);
786
- * the ROADMAP note saying four is documentation drift, corrected in
787
- * `30-RESEARCH.md`.
783
+ * `f_0900 =*+$01`. That witness carries SIX such labels; an earlier note
784
+ * claiming four was documentation drift, corrected after a recount against
785
+ * the witness disassembly itself.
788
786
  *
789
787
  * `offset` is `label.address - instr.address`, so it is 1 or 2 for every
790
788
  * 6502/6510 instruction -- the value is rendered rather than bounded here
@@ -811,7 +809,7 @@ const AUTO_NAME_MARKER = " ; auto-generated name -- still in the annotation bac
811
809
 
812
810
  /**
813
811
  * The fixed trailing comment that marks a definition at an address carrying
814
- * MORE THAN ONE store label (30-REVIEW WR-02). ONE spelling, in one place, for
812
+ * MORE THAN ONE store label. ONE spelling, in one place, for
815
813
  * the same reason `AUTO_NAME_MARKER` is: a second wording makes it ungreppable
816
814
  * for the only reader it exists for.
817
815
  *
@@ -823,7 +821,7 @@ const ALIAS_MARKER_PREFIX = " ; ALIAS: this address also carries ";
823
821
 
824
822
  /**
825
823
  * The fixed leading comment on every line carrying a provenance ledger row's
826
- * Verdict and Confidence (BUILD-05, phase 46 plan 01). ONE spelling, in one
824
+ * Verdict and Confidence. ONE spelling, in one
827
825
  * place, for the same reason `AUTO_NAME_MARKER` and `ALIAS_MARKER_PREFIX`
828
826
  * are: a second wording makes it ungreppable for the only reader it exists
829
827
  * for.
@@ -832,10 +830,10 @@ const ALIAS_MARKER_PREFIX = " ; ALIAS: this address also carries ";
832
830
  * `renderLedger()` header prose mentions `; PROVENANCE:` once, as an
833
831
  * unwired, forward-looking remark about THIS PROJECT'S OWN documentation
834
832
  * provenance conventions -- it names no writer, no reader and no format, and
835
- * `.planning/ARCHITECTURE.md` (the real one, not `.planning/research/
836
- * ARCHITECTURE.md`) never uses the word "provenance" at all. Reusing that
833
+ * this project's own architecture document (the current one, not an early
834
+ * research-phase draft of the same name) never uses the word "provenance" at all. Reusing that
837
835
  * string silently would attribute intent to it that it does not carry
838
- * (`46-RESEARCH.md` Pitfall 6). `PROVENANCE_MARKER_PREFIX` is therefore a
836
+ * (a pitfall caught before this constant shipped). `PROVENANCE_MARKER_PREFIX` is therefore a
839
837
  * deliberately DIFFERENT, prefix-distinct spelling: `; PROVENANCE LEDGER:`,
840
838
  * never `; PROVENANCE:`.
841
839
  */
@@ -852,7 +850,7 @@ const PROVENANCE_AMBIGUITY_MARKER_PREFIX = " ; PROVENANCE LEDGER AMBIGUITY: ";
852
850
 
853
851
  /**
854
852
  * The fixed leading comment on every line naming a user-requested exclusion
855
- * (`BUILD-07`, phase 46 plan 05). ONE spelling, in one place, for the same
853
+ * ONE spelling, in one place, for the same
856
854
  * reason `AUTO_NAME_MARKER`, `ALIAS_MARKER_PREFIX` and `PROVENANCE_MARKER_PREFIX`
857
855
  * are: a second wording would make it ungreppable for the only reader it
858
856
  * exists for, and criterion 2's own readback -- recovering what was excluded
@@ -860,8 +858,8 @@ const PROVENANCE_AMBIGUITY_MARKER_PREFIX = " ; PROVENANCE LEDGER AMBIGUITY: ";
860
858
  * one spelling to anchor on.
861
859
  *
862
860
  * THIS IS HOW A USER'S REQUEST TO LEAVE A SPAN OUT APPEARS IN THE ARTEFACT,
863
- * and it is deliberately a MARKER rather than an OMISSION: `BUILD-07` requires
864
- * the export be lossless by default, and any exclusion be "emitted as a
861
+ * and it is deliberately a MARKER rather than an OMISSION: the export is
862
+ * REQUIRED to be lossless by default, and any exclusion be "emitted as a
865
863
  * recorded excluded range rather than a hole". The failure this constant's
866
864
  * existence prevents is the obvious wrong implementation -- skipping the
867
865
  * block would satisfy the word "exclude" and lose the bytes, and the
@@ -886,7 +884,8 @@ const MAX_IMMEDIATE_VARIANT_VALUE = 0xff;
886
884
  /**
887
885
  * The SHAPE `registerKeyFor(address).slice(1)` always produces (uppercase,
888
886
  * exactly four hex digits) -- the SAME string `planEnumsForPairing()` uses as
889
- * a project enum's own `enumName` (`anno-enum-gen.ts`, D-15). An enum usage
887
+ * a project enum's own `enumName` (`anno-enum-gen.ts`'s own naming rule).
888
+ * An enum usage
890
889
  * whose `enumName` matches this shape is a CANDIDATE for
891
890
  * `decomposeRegisterValue()`; one that does not (a hand-authored name like
892
891
  * `viccolor`) is never a candidate at all -- this module holds no second
@@ -896,7 +895,7 @@ const MAX_IMMEDIATE_VARIANT_VALUE = 0xff;
896
895
  * convention (`registerKeyFor()`) emits uppercase, and matching lowercase too
897
896
  * would accept a shape nothing in this codebase produces.
898
897
  *
899
- * SHAPE ALONE IS NOT ENOUGH (45-REVIEW CR-01, fixed 2026-09-11): a candidate
898
+ * SHAPE ALONE IS NOT ENOUGH (fixed 2026-09-11): a candidate
900
899
  * is only ATTEMPTED once `hasRegBitsEntry()` also confirms `anno-regbits.json`
901
900
  * has a table entry for it. `$D020`/`$D021` -- among the most commonly
902
901
  * hand-annotated C64 registers -- are register-shaped and absent from the
@@ -910,7 +909,7 @@ const REGISTER_ENUM_NAME_RE = /^[0-9A-F]{4}$/;
910
909
  * Replaces the `#$XX` immediate literal `renderLine()` produced with `#symbol`.
911
910
  *
912
911
  * WHY A TARGETED TEXT SUBSTITUTION RATHER THAN A `RenderOptions` WIDENING.
913
- * D-11 forbids `renderLine()` from substituting a symbol into an immediate
912
+ * This module's own invariant forbids `renderLine()` from substituting a symbol into an immediate
914
913
  * operand at all, because of the `#<`/`#>` high/low-byte ambiguity, and that
915
914
  * rule is verified against a real assembler in `disasm-roundtrip.test.ts`. It
916
915
  * is not relaxed here. What an ENUM adds is a caller-supplied fact the renderer
@@ -925,7 +924,7 @@ const REGISTER_ENUM_NAME_RE = /^[0-9A-F]{4}$/;
925
924
  * substitution happened.
926
925
  *
927
926
  * THE SEARCH IS CONFINED TO THE DIRECTIVE HALF OF THE LINE, AND THAT IS THE
928
- * SECOND HALF OF 30-REVIEW CR-01's FIX (2026-08-31). `renderLine()` emits a
927
+ * SECOND HALF OF THIS MODULE'S OWN 2026-08-31 FIX. `renderLine()` emits a
929
928
  * trailing `" ; "` comment for notes, and for an instruction whose
930
929
  * `acmeExpressible` is false it emits the whole thing as a `!byte` directive
931
930
  * with the mnemonic AND its `#$xx` operand moved INTO that comment
@@ -993,7 +992,7 @@ const [LINE_COMMENT, SIDE_COMMENT] = COMMENT_TYPES;
993
992
  * The store validator's own message is deliberately DISCARDED and replaced.
994
993
  * That message interpolates the offending text for one of its four cases, and
995
994
  * an exporter error that quotes a file's contents back is a content-disclosure
996
- * oracle (CR-03). What survives is the address and which rule fired -- facts
995
+ * oracle. What survives is the address and which rule fired -- facts
997
996
  * ABOUT the comment, never the comment.
998
997
  */
999
998
  export function assertExportableCommentText(text: string, address: number): string {
@@ -1012,7 +1011,7 @@ export function assertExportableCommentText(text: string, address: number): stri
1012
1011
 
1013
1012
  /**
1014
1013
  * Re-checks, at the EXPORT boundary, that a stored range's `dataType` is one
1015
- * the store's own vocabulary defines (30-REVIEW WR-03).
1014
+ * the store's own vocabulary defines.
1016
1015
  *
1017
1016
  * The sibling of `assertExportableCommentText()` below, on the same terms and
1018
1017
  * for the same reason: `listRanges()` casts the column with no validator, so
@@ -1067,7 +1066,7 @@ interface CommentPlacement {
1067
1066
  * the order they were written in rather than an order this module invented.
1068
1067
  *
1069
1068
  * A COMMENT ON A MULTI-ADDRESS LINE IS QUALIFIED WITH ITS OWN ADDRESS
1070
- * (30-REVIEW IN-02, added 2026-08-31). The CODE path calls this with a span of
1069
+ * (added 2026-08-31). The CODE path calls this with a span of
1071
1070
  * exactly ONE address (`[instr.address, instr.address + 1)`), so a comment
1072
1071
  * there is unambiguous and is emitted unchanged -- nothing about the existing
1073
1072
  * output moves. The DATA path calls it with a span of up to
@@ -1086,7 +1085,7 @@ interface CommentPlacement {
1086
1085
  * with an address it already sits next to is noise, and it would rewrite every
1087
1086
  * existing expected line in the test suite for nothing.
1088
1087
  *
1089
- * `generatedSuffix` (D-17, plan 45-05) is this module's OWN mechanical text --
1088
+ * `generatedSuffix` is this module's OWN mechanical text --
1090
1089
  * today, only `decomposeRegisterValue()`'s decoded-field comment for an OR-ed
1091
1090
  * multi-bit enum substitution -- never a second stored comment. It is NEVER
1092
1091
  * DROPPED and NEVER REORDERED BEHIND authored text: when a stored SIDE
@@ -1140,7 +1139,7 @@ function withComments(text: string, start: number, endExclusive: number, ctx: Co
1140
1139
  }
1141
1140
 
1142
1141
  /**
1143
- * Phase 47, plan 47-04 (BUILD-03): the address one decoded instruction's
1142
+ * the address one decoded instruction's
1144
1143
  * operand REFERENCES, for the in-tree symbol rule below -- the SAME address
1145
1144
  * a substitution would substitute, extracted through `resolvedTarget` FIRST
1146
1145
  * (`disasm-decoder.ts` rule 5 for every relative branch, rule 6 for absolute
@@ -1153,7 +1152,7 @@ function withComments(text: string, start: number, endExclusive: number, ctx: Co
1153
1152
  * immediate is a BYTE VALUE, not an address: project enums (the
1154
1153
  * `usageByAddress` branch above) are what give one a name, and treating it as
1155
1154
  * an address is how `lda #$08` would start demanding a label be recorded at
1156
- * `$0008`. `disasm-renderer.ts`'s own D-11 comment states the same exclusion
1155
+ * `$0008`. `disasm-renderer.ts`'s own comment states the same exclusion
1157
1156
  * for the identical reason, on the substitution side of this same boundary.
1158
1157
  */
1159
1158
  function referencedAddress(instr: Instruction): number | undefined {
@@ -1164,7 +1163,7 @@ function referencedAddress(instr: Instruction): number | undefined {
1164
1163
  }
1165
1164
 
1166
1165
  /**
1167
- * Phase 47, plan 47-04 (BUILD-03, D47-F): true iff `address` falls inside
1166
+ * true iff `address` falls inside
1168
1167
  * `[block.start, block.endExclusive)` for SOME block this export emitted --
1169
1168
  * the half-open interval every other boundary test in this module already
1170
1169
  * uses. Tested against the `blocks` array `exportAsm()` already built at the
@@ -1222,12 +1221,12 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1222
1221
  comments = listComments(handle);
1223
1222
  projectEnums = listProjectEnums(handle);
1224
1223
  enumUsage = listEnumUsage(handle);
1225
- // BUILD-07 (phase 46 plan 05): a sixth read in the SAME handle and the
1224
+ // a sixth read in the SAME handle and the
1226
1225
  // SAME `try`, mirroring the discipline the five siblings above already
1227
1226
  // follow -- one handle for the whole export, closed once in the
1228
1227
  // `finally` below. There is no second store opened for this.
1229
1228
  excludedRanges = listExcludedRanges(handle);
1230
- // Phase 47, plan 47-01: a SEVENTH read in the SAME handle and the SAME
1229
+ // a SEVENTH read in the SAME handle and the SAME
1231
1230
  // `try`, on the same terms as the sixth above -- still one handle for
1232
1231
  // the whole export, closed once in the `finally` below. There is no
1233
1232
  // second store opened for this either.
@@ -1251,8 +1250,8 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1251
1250
  const blocks: ExportBlock[] = sortedRanges.map((row) => ({
1252
1251
  start: row.start,
1253
1252
  endExclusive: row.endInclusive + 1,
1254
- // THE STORE'S `dataType` IS RE-CHECKED AT THIS BOUNDARY (30-REVIEW WR-03,
1255
- // fixed 2026-08-31), for exactly the reason `withComments()` re-checks
1253
+ // THE STORE'S `dataType` IS RE-CHECKED AT THIS BOUNDARY (fixed 2026-08-31),
1254
+ // for exactly the reason `withComments()` re-checks
1256
1255
  // `commentType` a few functions up: "Unreachable through the type, and
1257
1256
  // reachable through a store file somebody edited. Refusing beats
1258
1257
  // guessing." That reasoning applies here and had not been applied.
@@ -1293,7 +1292,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1293
1292
  }
1294
1293
  }
1295
1294
 
1296
- // BUILD-05 (phase 46 plan 01): ONE read for the whole export, mirroring the
1295
+ // ONE read for the whole export, mirroring the
1297
1296
  // one-store-handle discipline directly above. `ledgerPath === undefined` is
1298
1297
  // the ONLY question asked of the caller's intent here -- everything after
1299
1298
  // this line either has a ledger to join against or does not, and no branch
@@ -1312,7 +1311,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1312
1311
  for (const label of sortedLabels) {
1313
1312
  assertLegalAcmeIdentifier(label.name, `exportAsm: label at ${hex4(label.address)}`);
1314
1313
  // TWO NAMES AT ONE ADDRESS IS RECORDED IN THE EMITTED SOURCE, NOT RESOLVED
1315
- // IN SILENCE (30-REVIEW WR-02, second half, fixed 2026-08-31).
1314
+ // IN SILENCE (fixed 2026-08-31).
1316
1315
  //
1317
1316
  // `labelIndex` is a `Map<number, string>` while `anno_label` is `unique`
1318
1317
  // on NAME only -- `setLabel()` refuses only a name already bound to a
@@ -1352,7 +1351,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1352
1351
  * only the first name at each, so an ALIASED label would be invisible to a
1353
1352
  * collision check reading it. ACME has ONE symbol namespace, so an enum
1354
1353
  * variant symbol colliding with any of these is `Symbol already defined.`
1355
- * (30-REVIEW WR-10). */
1354
+ */
1356
1355
  const labelSymbolNames = new Set(sortedLabels.map((label) => label.name));
1357
1356
 
1358
1357
  // Comments indexed by the address they annotate, each address's list left in
@@ -1379,7 +1378,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1379
1378
  * They join the header block for the same reason label definitions do. */
1380
1379
  const enumDefinitionLines: string[] = [];
1381
1380
  /** Every emitted enum-derived symbol name (single-value or OR-ed term) to
1382
- * the ONE value it was defined with. A Map, not a Set (D-17, plan 45-05):
1381
+ * the ONE value it was defined with. A Map, not a Set:
1383
1382
  * a term name defined by one instruction with one value and referenced by a
1384
1383
  * SECOND instruction with a DIFFERENT value is a genuine collision in
1385
1384
  * ACME's one symbol namespace, and the value is what makes that collision
@@ -1389,13 +1388,13 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1389
1388
  let unexpressibleCount = 0;
1390
1389
  let dataByteCount = 0;
1391
1390
  let enumDecompositionCount = 0;
1392
- /** Phase 47, plan 47-03 (BUILD-02): every `external_file`-typed block's
1391
+ /** every `external_file`-typed block's
1393
1392
  * own `.bin` sibling, populated at the same point `dataByteCount` above
1394
1393
  * is incremented for that block -- see `ExportAsmResult.binaries`'s own
1395
1394
  * doc-comment for what a caller reading this field is obliged to do. */
1396
1395
  const binaries: ExportBinary[] = [];
1397
1396
 
1398
- /** Phase 47, plan 47-04 (BUILD-03): one in-tree reference this export could
1397
+ /** one in-tree reference this export could
1399
1398
  * not resolve to a symbol -- collected across the WHOLE block loop and
1400
1399
  * refused ONCE at the end, in the shape the unapplied-enum-usage and
1401
1400
  * unplaced-comment refusals below already use. */
@@ -1423,8 +1422,8 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1423
1422
  return `${line}${AUTO_NAME_MARKER}`;
1424
1423
  };
1425
1424
 
1426
- // THE ALIAS PICK IS MADE VISIBLE IN THE SOURCE (30-REVIEW WR-02, second
1427
- // half). Two store labels at one address are BOTH defined -- ACME accepts
1425
+ // THE ALIAS PICK IS MADE VISIBLE IN THE SOURCE (second half of the same fix
1426
+ // above). Two store labels at one address are BOTH defined -- ACME accepts
1428
1427
  // two symbols with one value and the bytes are unaffected -- but a
1429
1428
  // REFERENCE to that address can render through only one of them. Which one
1430
1429
  // was previously invisible. Marking both definitions with the same fixed
@@ -1446,7 +1445,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1446
1445
  // or ACME refuses the whole source with `Symbol already defined.`
1447
1446
  const midInstructionLabelAddresses = new Set<number>();
1448
1447
 
1449
- // ONE PER EMITTED INLINE DEFINITION, not one per address (30-REVIEW WR-02).
1448
+ // ONE PER EMITTED INLINE DEFINITION, not one per address.
1450
1449
  // The set above answers the HEADER's question ("is this address defined
1451
1450
  // inline already?"), which is per-address by nature. This counter answers
1452
1451
  // the RESULT's question ("how many inline definitions does the source
@@ -1469,11 +1468,11 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1469
1468
  const content: string[] = [];
1470
1469
 
1471
1470
  if (block.dataType === CODE_DATA_TYPE) {
1472
- // D-11 is inherited UNCHANGED: `renderLine()` decides operand width and
1471
+ // That invariant is inherited UNCHANGED: `renderLine()` decides operand width and
1473
1472
  // refuses to substitute a symbol into an immediate or zeropage-family
1474
1473
  // operand. Do not widen `RenderOptions` and do not bypass `renderLine()`.
1475
1474
  // `end` IS INCLUSIVE, SO IT IS HANDED AN INCLUSIVE VALUE (30-REVIEW
1476
- // WR-07, corrected 2026-08-31). This used to pass `block.endExclusive`.
1475
+ // corrected 2026-08-31). This used to pass `block.endExclusive`.
1477
1476
  // `DecodeOptions.end` is compared with `if (end !== undefined && address
1478
1477
  // > end) break` and documented as "an instruction starting past `end` is
1479
1478
  // dropped ... an instruction starting AT OR BEFORE `end` is emitted in
@@ -1556,7 +1555,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1556
1555
 
1557
1556
  let rendered = renderLine(instr, { showSymbols: true, symbolFor });
1558
1557
 
1559
- // Phase 47, plan 47-04 (BUILD-03): THE IN-TREE SYMBOL RULE, beside
1558
+ // THE IN-TREE SYMBOL RULE, beside
1560
1559
  // the `renderLine()` call it has to agree with. `rendered` above
1561
1560
  // already went through `symbolFor` -- the SAME `labelIndex` map read
1562
1561
  // here -- so a reference this check calls unresolved is a reference
@@ -1578,7 +1577,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1578
1577
  }
1579
1578
  }
1580
1579
 
1581
- // The mechanical decode text (D-17), set only by the OR-ed
1580
+ // The mechanical decode text, set only by the OR-ed
1582
1581
  // decomposition branch below and merged into this instruction's
1583
1582
  // trailing comment by `withComments()` after the enum-substitution
1584
1583
  // block finishes.
@@ -1598,7 +1597,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1598
1597
  }
1599
1598
 
1600
1599
  // ROLE IS NOT ENOUGH: THE OPERAND MUST ALSO BE ASSEMBLER-VISIBLE
1601
- // (30-REVIEW CR-02, fixed 2026-08-31). `decode()` assigns
1600
+ // (fixed 2026-08-31). `decode()` assigns
1602
1601
  // `role: "immediate"` from the ADDRESSING MODE alone, independently
1603
1602
  // of `acmeExpressible`. Six opcodes in `disasm-opcodes.ts` are
1604
1603
  // `mode: "immediate"` AND `acmeExpressible: false` -- $2b (`anc`),
@@ -1622,9 +1621,9 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1622
1621
  // !byte $eb, $00 ; sbc #viccolor_BLACK [illegal opcode | ...]
1623
1622
  // === enumSubstitutionCount: 1
1624
1623
  //
1625
- // This is D-30's "an annotation the exporter cannot express is
1626
- // REFUSED loudly and by name, never silently dropped while the
1627
- // export reports success" exactly inverted. It is refused now.
1624
+ // This inverts, exactly, the rule that an annotation the exporter cannot
1625
+ // express is REFUSED loudly and by name, never silently dropped while
1626
+ // the export reports success. It is refused now.
1628
1627
  if (!instr.acmeExpressible) {
1629
1628
  throw new Error(
1630
1629
  `exportAsm: enum ${JSON.stringify(usage.enumName)} is bound to the immediate operand at ${hex4(usage.address)}, but that ` +
@@ -1647,8 +1646,8 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1647
1646
  );
1648
1647
  }
1649
1648
 
1650
- // D-16 (plan 45-05): THE ONE OWNING DECODER. Attempted ONLY when
1651
- // BOTH (45-REVIEW CR-01, fixed 2026-09-11):
1649
+ // THE ONE OWNING DECODER. Attempted ONLY when
1650
+ // BOTH (fixed 2026-09-11):
1652
1651
  // 1. `usage.enumName` has the exact shape `registerKeyFor().slice(1)`
1653
1652
  // produces -- see `REGISTER_ENUM_NAME_RE`'s own comment for why a
1654
1653
  // name that does not have this shape (e.g. `viccolor`) is never
@@ -1679,7 +1678,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1679
1678
  }
1680
1679
 
1681
1680
  if (decomposition !== undefined && decomposition.multiField) {
1682
- // D-17: OR-ED NAMED CONSTANTS AND THE DECODED COMMENT -- BOTH,
1681
+ // OR-ED NAMED CONSTANTS AND THE DECODED COMMENT -- BOTH,
1683
1682
  // never either alone. A bare hex constant with a comment still
1684
1683
  // "emits one hex constant"; bare constants with no comment are
1685
1684
  // not readable.
@@ -1700,11 +1699,11 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1700
1699
  );
1701
1700
  }
1702
1701
  // Same name, same value, already defined by an earlier
1703
- // instruction -- no second definition line (30-REVIEW WR-10's
1702
+ // instruction -- no second definition line, the same
1704
1703
  // own "only what the source references" discipline, extended
1705
1704
  // to terms).
1706
1705
  } else {
1707
- // THE SAME LABEL COLLISION CHECK 30-REVIEW WR-10 ADDED FOR A
1706
+ // THE SAME LABEL COLLISION CHECK ADDED FOR A
1708
1707
  // SINGLE ENUM SYMBOL, EXTENDED HERE -- not a second check.
1709
1708
  if (labelSymbolNames.has(term.name)) {
1710
1709
  throw new Error(
@@ -1726,9 +1725,9 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1726
1725
  enumDecompositionCount++;
1727
1726
  decompositionComment = decomposition.comment;
1728
1727
  } else {
1729
- // THE EXISTING SINGLE-SYMBOL PATH (D-16: unchanged, not
1728
+ // THE EXISTING SINGLE-SYMBOL PATH (unchanged, not
1730
1729
  // replaced) -- a single-field register, an enum usage whose name
1731
- // is not register-shaped at all, OR (45-REVIEW CR-01) a
1730
+ // is not register-shaped at all, OR a
1732
1731
  // register-shaped name for a register `anno-regbits.json` simply
1733
1732
  // has no entry for (e.g. `D020`) -- reached here with no
1734
1733
  // substitution counted above, never a throw.
@@ -1771,7 +1770,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1771
1770
  assertLegalAcmeIdentifier(symbol, `exportAsm: enum variant symbol for ${hex4(usage.address)}`);
1772
1771
 
1773
1772
  // THE COLLISION THE COMMENT BELOW NAMES IS NOW CHECKED FOR
1774
- // (30-REVIEW WR-10, fixed 2026-08-31). That comment identified the
1773
+ // (fixed 2026-08-31). That comment identified the
1775
1774
  // hazard exactly -- "every extra emitted symbol is one more chance to
1776
1775
  // collide with a label name and turn a correct export into ACME's
1777
1776
  // `Symbol already defined.`" -- and then did not look.
@@ -1822,7 +1821,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1822
1821
  block.lineCount += emitted.length;
1823
1822
  }
1824
1823
  } else if (isSplitAddressDataType(block.dataType)) {
1825
- // Phase 47, plan 47-06 (BUILD-03): the two split ADDRESS layouts get
1824
+ // the two split ADDRESS layouts get
1826
1825
  // PAIRED symbol emission, never the raw-`!byte` fallback -- see
1827
1826
  // `emitSplitAddressLines()`'s own doc-comment for the orientation and
1828
1827
  // the one-symbol-per-entry rule. Read the SAME `blocks`/`labelIndex`
@@ -1851,7 +1850,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1851
1850
  }
1852
1851
  dataByteCount += slice.length;
1853
1852
 
1854
- // Phase 47, plan 47-03 (BUILD-02): an `external_file` block's bytes
1853
+ // an `external_file` block's bytes
1855
1854
  // also go out as their own sibling data file. `bytes` is `slice`
1856
1855
  // itself -- the same bytes `dataByteCount` above just counted, carried
1857
1856
  // verbatim and never re-read from anywhere else.
@@ -1860,7 +1859,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1860
1859
  }
1861
1860
  }
1862
1861
 
1863
- // BUILD-05 (phase 46 plan 01): when ledger mode is on, every block gets a
1862
+ // when ledger mode is on, every block gets a
1864
1863
  // provenance comment BEFORE it is bracketed -- never as a threshold, never
1865
1864
  // gating which blocks reach `emitBlock()` below. The only conditionals
1866
1865
  // here are "is ledger mode on", "did zero rows come back" (refuse) and
@@ -1903,7 +1902,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1903
1902
  content.unshift(...provenanceLines);
1904
1903
  }
1905
1904
 
1906
- // BUILD-07 (phase 46 plan 05): every recorded exclusion overlapping this
1905
+ // every recorded exclusion overlapping this
1907
1906
  // block gets a marker naming ITS OWN extent (never the block's) and its
1908
1907
  // checked reason -- NEVER a skipped block, a shortened slice, or a
1909
1908
  // shrunk `expectedBytes`. `block.start`/`block.endExclusive` above are
@@ -1950,7 +1949,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1950
1949
 
1951
1950
  // EVERY block goes through `emitBlock()`, code and data alike, so there is
1952
1951
  // exactly one place that brackets a block and no route that emits an
1953
- // unbracketed one. Captured onto `block.lines` (Phase 47, plan 47-01) at
1952
+ // unbracketed one. Captured onto `block.lines` at
1954
1953
  // the SAME point it is pushed onto `blockLines` -- one call, two
1955
1954
  // destinations, never a second bracketing.
1956
1955
  const emittedBlockLines = emitBlock(block.start, block.endExclusive, content);
@@ -1958,7 +1957,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
1958
1957
  blockLines.push(...emittedBlockLines);
1959
1958
  }
1960
1959
 
1961
- // Phase 47, plan 47-04 (BUILD-03): an in-tree reference this export could
1960
+ // an in-tree reference this export could
1962
1961
  // not resolve to a symbol is REFUSED BY NAME, once, across the whole
1963
1962
  // export -- never emitted as a hex literal that freezes the target's
1964
1963
  // address into the source while the export reports success. In the same
@@ -2070,7 +2069,7 @@ export function exportAsm(options: ExportAsmOptions): ExportAsmResult {
2070
2069
  }
2071
2070
 
2072
2071
  // ---------------------------------------------------------------------------
2073
- // Phase 47, plan 47-01: the TREE writer (D47-A). `exportAsm()` above stays the
2072
+ // the TREE writer (D47-A). `exportAsm()` above stays the
2074
2073
  // proven EMITTER -- this is the primary shape a caller reaches for, built by
2075
2074
  // PARTITIONING `exportAsm()`'s already-proven emission, never by emitting a
2076
2075
  // second time through a second route. See `exportAsmTree()`'s own doc-comment
@@ -2102,7 +2101,7 @@ export function scopeFileName(start: number): string {
2102
2101
  }
2103
2102
 
2104
2103
  /**
2105
- * Phase 47, plan 47-03 (BUILD-02): the `.bin` sibling file name for the
2104
+ * the `.bin` sibling file name for the
2106
2105
  * `external_file`-typed block starting at `start` -- `data_XXXX.bin`, four
2107
2106
  * LOWERCASE hex digits, no `$`, no store free text anywhere in it. Module-
2108
2107
  * private: nothing outside this file needs the name computed independently
@@ -2127,13 +2126,13 @@ export interface ExportAsmTreeOptions extends ExportAsmOptions {
2127
2126
  * absent comment beside a present one is itself a claim, and a silently-
2128
2127
  * undocumented path field is what a prior review named as the mechanism of
2129
2128
  * a real defect. The CALLER owns confining it (the CLI does, through
2130
- * `storePathWithinWorkspace()`, plan 47-05). Within the directory, every
2129
+ * `storePathWithinWorkspace()`). Within the directory, every
2131
2130
  * name this function writes is DERIVED (`scopeFileName()`, the three fixed
2132
2131
  * constants above), so nothing the caller supplies can escape it a second
2133
2132
  * time. */
2134
2133
  outDir: string;
2135
2134
  /**
2136
- * Phase 47, plan 47-02, Task 2: what the CALLER IS ASKING FOR, not a
2135
+ * What the CALLER IS ASKING FOR, not a
2137
2136
  * switch that widens what this function is willing to destroy. Set, it
2138
2137
  * means "this directory already holds a tree I exported before -- replace
2139
2138
  * it." It deliberately does NOT mean "remove whatever is in my way": a
@@ -2157,7 +2156,7 @@ export interface ExportAsmTreeResult extends ExportAsmResult {
2157
2156
  }
2158
2157
 
2159
2158
  /**
2160
- * Phase 47, plan 47-02: decides which file ONE block belongs to, given the
2159
+ * decides which file ONE block belongs to, given the
2161
2160
  * store's own scopes -- the ONE containment predicate, applied ONCE, so the
2162
2161
  * tree writer's placement answer can never drift from a second copy of this
2163
2162
  * question (see `blocks`'s own `.map()` above for the sibling boundary this
@@ -2272,7 +2271,7 @@ export function exportAsmTree(options: ExportAsmTreeOptions): ExportAsmTreeResul
2272
2271
  // never a guess revised after the fact.
2273
2272
  const populatedScopeStarts = [...scopeBlocks.keys()].sort((a, b) => a - b);
2274
2273
  const hasUnscoped = unscopedBlocks.length > 0;
2275
- // Phase 47, plan 47-03 (T-47-08): every `.bin` sibling this call will write
2274
+ // every `.bin` sibling this call will write
2276
2275
  // joins the SAME name set the directory contract below evaluates, so a
2277
2276
  // re-export with `force: true` may replace a previously-exported `.bin`
2278
2277
  // exactly as it may replace a previously-exported `.a` file -- and, without
@@ -2287,7 +2286,7 @@ export function exportAsmTree(options: ExportAsmTreeOptions): ExportAsmTreeResul
2287
2286
  ];
2288
2287
 
2289
2288
  // ---------------------------------------------------------------------
2290
- // Phase 47, plan 47-02, Task 2: the output-directory contract. Two rules,
2289
+ // The output-directory contract. Two rules,
2291
2290
  // both evaluated BEFORE the first write below -- a refusal that has
2292
2291
  // already written half a tree has left an artefact a later assemble might
2293
2292
  // succeed on (the same reason the placement pass above runs to completion
@@ -2367,7 +2366,7 @@ export function exportAsmTree(options: ExportAsmTreeOptions): ExportAsmTreeResul
2367
2366
  sourceOrder.push(UNSCOPED_FILE_NAME);
2368
2367
  }
2369
2368
 
2370
- // .bin siblings (T-47-08/T-47-09/T-47-10, phase 47 plan 47-03) -- one per
2369
+ // .bin siblings (T-47-08/T-47-09/T-47-10) -- one per
2371
2370
  // `external_file`-typed block, written in the SAME pass as every `.a`
2372
2371
  // file above and, like them, BEFORE root.a: data files precede the root
2373
2372
  // for the identical interruption-safety reason the `.a` files already do