# Inventory of `disasm_tools/` across the four sibling projects

Living document. Captures the state of the four `disasm_tools/` packages at
the time of fantasm's initial consolidation, and notes the strategy for
each module.

| Project              | path                       |
|----------------------|----------------------------|
| acorn-6502-tube-client | `src/disasm_tools/`      |
| acorn-adfs           | `src/disasm_tools/`        |
| acorn-econet-bridge  | `src/disasm_tools/`        |
| acorn-nfs            | `src/disasm_tools/`        |

All four packages share a near-identical surface, but `acorn-nfs` has
absorbed the most additions over time and is the working baseline for most
modules. A handful of modules exist in only one fork.

## Per-module status

Line counts and diff-line counts (versus NFS) collected on 2026-05-01.

| Module           | NFS | ADFS | EBR | TUBE | Status                                      | Port |
|------------------|----:|-----:|----:|-----:|---------------------------------------------|------|
| `__init__.py`    |   0 |    1 |   1 |    1 | Trivial                                     | ✅ done |
| `mos6502.py`     | 152 |   97 |  98 |   99 | NFS adds CMOS (65C02) overrides + dispatcher | ✅ done — ROM_BASE/ROM_SIZE moved out, set up for `[rom] cpu` config |
| `paths.py`       |  28 |   32 |  32 |   36 | Project-specific PREFIX; NFS supports two   | ✅ done — refactored against ProjectContext; `[versions]` config |
| `asm_extract.py` | 105 |  100 | 100 |  100 | NFS has 5 extra lines (relocation indexing) | ✅ done — typed extract_section() returning AsmSection |
| `audit.py`       | 828 |  828 | 828 |  828 | **Byte-identical**                          | ⚠️ partial — pure-logic + load_subroutines + find_undeclared_subs ported; `format_*` and top-level `audit()` deferred to CLI |
| `backfill.py`    | 621 |  621 | 621 |  621 | **Byte-identical**                          | ⚠️ partial — build_confidence_map + parse_annotations + translate_* ported; VERSION_CHAIN and chained_map deferred (NFS-specific data → fantasm.toml) |
| `cfg.py`         | 415 |  415 | 415 |  415 | NFS = ADFS = TUBE; EBR has 4 diff lines     | ⚠️ partial — build_call_graph + resolve_sub_node ported with KEY FIX (uses audit.build_memory_regions(meta), not hardcoded NFS regions); format_* deferred |
| `comment_check.py` | 677 |  677 | 677 |  677 | **Byte-identical**                        | ⚠️ partial — every check function + run_checks ported; format_findings + comment_check() deferred |
| `compare.py`     | 470 |  319 | 319 |  319 | NFS has ~150 extra lines of capability      | ✅ done — Instruction/RomHeader dataclasses, parse_rom_header, disassemble_linear, compare_roms; rom_base parameterised, "NFS" hardcode removed |
| `context.py`     | 459 |  459 | 459 |  459 | **Byte-identical**                          | ⚠️ partial — compute_call_depths ported; generate_context (file IO) deferred |
| `insert_point.py`| 183 |  183 | 183 |  183 | **Byte-identical**                          | ⚠️ partial — parse_subroutine_declarations + find_main_block + new compute_insert_point ported; print/IO wrapper deferred |
| `labels.py`      | 495 |  495 | 495 |  495 | **Byte-identical**                          | ⚠️ partial — classify_labels + sort_labels + collect_auto_labels (was `_collect_auto_labels`); generate_labels file-IO deferred |
| `lint.py`        | 555 |  522 | 522 |  492 | NFS most capable; check others for unique rules | ⚠️ partial — extract_annotations (multi-line aware, with names) + valid_addresses_from_data + address_ranges_from_data + markdown helpers ported; ROM size now a parameter; lint_*() and lint() top-level deferred |
| `rename_labels.py`| 284 |  284 | 284 |  284 | **Byte-identical**                          | ⚠️ partial — parse_label_declarations + find_rename_section + find_insert_position + new apply_renames_to_lines pure transformer; rename_labels() and show_sub_labels() file-IO deferred |
| `verify.py`      | 102 |  102 | 102 |  112 | Trivial diffs (project-name strings); TUBE has 24 extra | ✅ done — verify_round_trip() returns VerifyResult dataclass; BeebasmNotFoundError raised |
| `cli.py`         | 417 |  401 | 445 |  358 | All diverge — Click sub-commands             | ⏳ in progress — `fantasm project` group landed; rest under design |
| `promote.py`     |   – |  235 | 235 |    – | ADFS+EBR identical; not in NFS or TUBE       | ⚠️ partial — analyze_labels + load_and_analyze_labels ported; format_promote_report deferred to CLI |
| `find_shared.py` |   – |    – | 254 |    – | EBR only                                     | ⚠️ partial — sweep_opcodes / find_matching_spans / parse_rom_spec / load_rom ported; report_matches + argparse main deferred |
| `blockmatch.py`  | 299 |    – |   – |    – | NFS only                                     | ✅ done — primary + supplementary opcode mapping; rom_base parameterised |
| `fingerprint.py` | 112 |    – |   – |    – | NFS only                                     | ✅ done — opcode_fingerprint, fingerprint_blocks, find_duplicate_blocks |

### Porting strategy that emerged

For each ported module the same shape recurs: lift the pure-logic surface
into `fantasm.api.<topic>` with type hints, frozen dataclasses for results,
typed exceptions instead of `sys.exit`/stderr-print, and `warnings.warn`
for soft diagnostics. Tests are characterisation tests against
hand-crafted in-memory data — small enough to read, complete enough to
pin behaviour for refactors.

The `format_*` and file-reading top-level entry functions (the CLI side)
are intentionally deferred for each module. They will land alongside the
`fantasm <topic>` Click sub-commands in a follow-up pass, where the
project-specific strings and `rom_prefix(version_dirpath)` calls can be
threaded through a `ProjectContext` once.

Legend: NFS = `acorn-nfs`, ADFS = `acorn-adfs`, EBR = `acorn-econet-bridge`,
TUBE = `acorn-6502-tube-client`. Em-dash (`–`) means the module is absent
in that fork.

## Cross-cutting refactors

A handful of concerns appear in multiple modules and need to be lifted out
during consolidation:

- **Project layout assumptions.** `paths.py` knows the directory naming
  convention (`{PREFIX}-{version_id}/`) and the ROM-name prefix. fantasm
  expresses these in `fantasm.toml` instead of hardcoded module constants.
- **ROM bank constants.** `ROM_BASE` and `ROM_SIZE` live in `mos6502.py`
  today, but they are properties of how a ROM was *banked*, not of the
  6502. They move into `fantasm.toml` (and a future `fantasm.api.rom`
  module) so the same disassembly tools can target the BBC's 16K sideways
  ROMs, the 8K NFS sideways "half-bank", and other layouts.
- **`SystemExit` from library code.** Several siblings call `sys.exit(1)`
  on error inside library functions (e.g. `paths.resolve_version_dirpath`).
  The fantasm port raises a typed exception (e.g. `VersionNotFound`) and
  the CLI layer translates it to a clean exit code with a useful message.
- **Console-script names.** Sibling code references the project-specific
  `acorn-{x}-disasm-tool` console-script name in error messages. fantasm
  uses `fantasm`.

## Porting order

Foundational first, so later modules can build on a stable base:

1. `mos6502` (no deps; opcode tables are the bedrock)
2. `paths` (refactored against `ProjectContext` + `fantasm.toml`)
3. `labels` (used by many)
4. `comment_check`
5. `compare`
6. `audit`, `lint`, `verify`
7. `cfg`, `context`, `insert_point`
8. `rename_labels`, `asm_extract`, `backfill`
9. Specialised: `blockmatch`, `fingerprint`, `promote`, `find_shared`

CLI integration happens alongside each module: a `fantasm <topic>` group is
added with the relevant sub-commands, and the public surface is re-exported
from `fantasm.api.__init__` so callers can either `from fantasm.api import
…` or reach into `fantasm.api.<topic>`.

Tests are characterisation tests: small hand-crafted inputs whose expected
output is captured from the working code, plus round-trip tests against
`beebasm` for any module that emits assembly source.
