feat(opcodes): register full AGE opcode ABI
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -655,12 +655,21 @@ A cursory Kamidori boot probe on 2026-07-20 validated much of this boundary: its
|
|||||||
archives loaded, `SYSTEM4.BIN` completed the initializer chain, call-script resolution entered
|
archives loaded, `SYSTEM4.BIN` completed the initializer chain, call-script resolution entered
|
||||||
`TITLE.BIN`, and title graphics/resource commands resolved. The first hard stop was not a different
|
`TITLE.BIN`, and title graphics/resource commands resolved. The first hard stop was not a different
|
||||||
container or catalog. Kamidori's title loop reached opcode `0x1be` (`u0041D9D0`, argc 2) at bytecode
|
container or catalog. Kamidori's title loop reached opcode `0x1be` (`u0041D9D0`, argc 2) at bytecode
|
||||||
offset `0xd7`. That opcode exists in Kelebek's full AGE/SYS4 table, but it is absent from the generated
|
offset `0xd7`. That opcode existed in Kelebek's full AGE/SYS4 table, but was absent from the generated
|
||||||
`build/opcodes.json` because that runtime artifact currently contains only opcodes observed in Himegari.
|
`build/opcodes.json` because the runtime artifact contained only opcodes observed in Himegari.
|
||||||
`Sys4Loader` therefore stopped decoding at the unknown opcode; `TITLE.BIN` returned as if it had ended,
|
`Sys4Loader` therefore stopped decoding at the unknown opcode; `TITLE.BIN` returned as if it had ended,
|
||||||
and Godot displayed its ordinary `— end —` marker instead of reporting an incompatibility.
|
and Godot displayed its ordinary `— end —` marker instead of reporting an incompatibility.
|
||||||
|
|
||||||
The future multi-game design must keep three separate concepts:
|
**ABI-registry floor completed 2026-07-28.** `vm-map/opcodes.toml` and generated `build/opcodes.json`
|
||||||
|
now contain the complete 548-entry Kelebek AGE catalog: 248 instructions observed in Himegari and 300
|
||||||
|
catalog-only compatibility entries (including the already mapped but unused persistence opcode `0x19f`).
|
||||||
|
Each entry carries `observed_in_himegari`; per-game observation no longer limits decoding. A recognized
|
||||||
|
but unimplemented opcode reaches the VM's traced stub-and-advance fallback, so a probe continues beyond
|
||||||
|
it instead of losing the rest of the script. Catalog-only entries deliberately remain
|
||||||
|
`noop_headless=false`: skipping them is a temporary compatibility-probe behavior, not evidence that their
|
||||||
|
native effects are semantically safe to omit.
|
||||||
|
|
||||||
|
The multi-game design keeps three separate concepts:
|
||||||
|
|
||||||
1. A **version ABI registry** containing every known opcode number, operand count/shape, and version gate
|
1. A **version ABI registry** containing every known opcode number, operand count/shape, and version gate
|
||||||
needed to decode that SYS generation, regardless of whether the active game uses it.
|
needed to decode that SYS generation, regardless of whether the active game uses it.
|
||||||
@@ -669,11 +678,11 @@ The future multi-game design must keep three separate concepts:
|
|||||||
3. **Semantic/runtime implementation coverage**, which may remain incomplete and should report a precise
|
3. **Semantic/runtime implementation coverage**, which may remain incomplete and should report a precise
|
||||||
unsupported-service error containing game/profile, script, opcode, and bytecode offset.
|
unsupported-service error containing game/profile, script, opcode, and bytecode offset.
|
||||||
|
|
||||||
An opcode that is absent even from the selected version ABI should likewise be a structured decode error,
|
An opcode that is absent even from the selected version ABI should still become a structured decode error,
|
||||||
not a synthetic final instruction or natural script return. Supporting a new same-version game then means
|
not a synthetic final instruction or natural script return; that diagnostic hardening remains open.
|
||||||
selecting the complete shared ABI, measuring its corpus against existing semantics, and implementing only
|
Supporting a new same-version game now means selecting the complete shared ABI, measuring its corpus
|
||||||
the newly exercised services. It should not require cloning the VM or manufacturing a new parser table
|
against existing semantics, and implementing only the newly exercised services. It does not require
|
||||||
from that game's corpus.
|
cloning the VM or manufacturing a new parser table from that game's corpus.
|
||||||
|
|
||||||
The probe also exposed a separate profile/presentation concern: Kamidori creates a `1024x576` render
|
The probe also exposed a separate profile/presentation concern: Kamidori creates a `1024x576` render
|
||||||
target while the current Himegari frontend assumes an `800x600` presentation. Logical canvas geometry,
|
target while the current Himegari frontend assumes an `800x600` presentation. Logical canvas geometry,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ All opcode knowledge (ABI, semantics, provenance, `depends_on`) is hand-edited *
|
|||||||
|
|
||||||
| Tool | Purpose | Run | Reads → Writes |
|
| Tool | Purpose | Run | Reads → Writes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `opcodes_build.py` | Generator + linter for the opcode reference. | `--build` · `--lint` · `--bootstrap` | `vm-map/opcodes.toml` → ⚙ `tools/age_opcodes_himegari.py`, ⚙ `build/opcodes.json`, ⚙ `docs/opcode-reference.md`, ⚙ `build/opcode-coverage.md` |
|
| `opcodes_build.py` | Generator + linter for the opcode reference. `--bootstrap` appends entries observed in the Himegari corpus; `--bootstrap-age` appends every missing opcode from the pristine 548-entry AGE catalog as an unobserved compatibility stub. Catalog stubs retain Kelebek's ABI label/argument count so other AGE scripts decode past them, but remain `noop_headless=false`: the VM currently traces/skips them, while coverage continues to report them as unresolved rather than semantically safe. | `--build` · `--lint` · `--bootstrap` · `--bootstrap-age` | `vm-map/opcodes.toml` → ⚙ `tools/age_opcodes_himegari.py`, ⚙ `build/opcodes.json`, ⚙ `docs/opcode-reference.md`, ⚙ `build/opcode-coverage.md` |
|
||||||
| `opcodes_model.py` | In-memory model + loader + linter (dangling-ref / confidence-ceiling / vocabulary / dependents). | *Imported by `opcodes_build.py`.* | `vm-map/opcodes.toml` → — |
|
| `opcodes_model.py` | In-memory model + loader + linter (dangling-ref / confidence-ceiling / vocabulary / dependents). | *Imported by `opcodes_build.py`.* | `vm-map/opcodes.toml` → — |
|
||||||
| `test_opcodes.py` | Unit tests for the opcode tooling. | `test_opcodes.py` | — |
|
| `test_opcodes.py` | Unit tests for the opcode tooling. | `test_opcodes.py` | — |
|
||||||
| `opcode_context.py` | Read-only evidence gatherer for classifying unnamed opcodes (frequency, argc, operand-type signature, neighbours, disasm snippets, Kelebek comment). | `--top 20` · `opcode_context.py 0x1f4 0x71 …` | corpus → stdout |
|
| `opcode_context.py` | Read-only evidence gatherer for classifying unnamed opcodes (frequency, argc, operand-type signature, neighbours, disasm snippets, Kelebek comment). | `--top 20` · `opcode_context.py 0x1f4 0x71 …` | corpus → stdout |
|
||||||
|
|||||||
@@ -1,19 +1,37 @@
|
|||||||
|
using Age.Engine.Model;
|
||||||
using Age.Engine.Sys4;
|
using Age.Engine.Sys4;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
public class OpcodeTableTests
|
public class OpcodeTableTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void LoadsObservedOpcodesPlusMappedUnusedAbiEntries()
|
public void LoadsCompleteAgeCatalog()
|
||||||
{
|
{
|
||||||
var t = OpcodeTableJson.Load(Paths.OpcodesJson);
|
var t = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||||
Assert.Equal(249, t.Count); // 248 corpus-observed + unused persistence ABI opcode 0x19f
|
Assert.Equal(548, t.Count); // 248 Himegari-observed + 300 broader AGE compatibility stubs
|
||||||
Assert.True(t.TryGet(0x55, out var label, out var argc));
|
Assert.True(t.TryGet(0x55, out var label, out var argc));
|
||||||
Assert.Equal("mov", label);
|
Assert.Equal("mov", label);
|
||||||
Assert.Equal(2, argc);
|
Assert.Equal(2, argc);
|
||||||
Assert.Equal("u0041BEB0", t.Label(0x90));
|
Assert.Equal("u0041BEB0", t.Label(0x90));
|
||||||
Assert.Equal(7, t.Argc(0x90));
|
Assert.Equal(7, t.Argc(0x90));
|
||||||
Assert.Equal(2, t.Argc(0x19f));
|
Assert.Equal(2, t.Argc(0x19f));
|
||||||
|
Assert.Equal(2, t.Argc(0x1be)); // Kamidori probe blocker; catalog-only in Himegari
|
||||||
Assert.Equal(-1, t.Argc(0x9999)); // absent -> -1
|
Assert.Equal(-1, t.Argc(0x9999)); // absent -> -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CatalogOnlyOpcodeDoesNotTruncateFollowingInstructions()
|
||||||
|
{
|
||||||
|
var t = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||||
|
var script = ScriptAssembler.Assemble(t, "CROSS_GAME_STUB", new List<(int, Operand[])>
|
||||||
|
{
|
||||||
|
(0x1be, new[] { new Operand(0, 11), new Operand(0, 22) }),
|
||||||
|
(0x2, Array.Empty<Operand>()),
|
||||||
|
}, Array.Empty<string>());
|
||||||
|
|
||||||
|
Assert.Equal(2, script.Instructions.Count);
|
||||||
|
Assert.Equal(0x1be, script.Instructions[0].Opcode);
|
||||||
|
Assert.Equal(2, script.Instructions[0].Args.Count);
|
||||||
|
Assert.Equal(0x2, script.Instructions[1].Opcode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Generator + linter for the living opcode reference (vm-map/opcodes.toml).
|
"""Generator + linter for the living opcode reference (vm-map/opcodes.toml).
|
||||||
--bootstrap seed skeletons for every used opcode (append-only; preserves hand edits)
|
--bootstrap seed skeletons for every used opcode (append-only; preserves hand edits)
|
||||||
|
--bootstrap-age seed compatibility stubs for every opcode in the broader AGE catalog
|
||||||
--build emit age_opcodes_himegari.py + build/opcodes.json + docs/opcode-reference.md + build/opcode-coverage.md
|
--build emit age_opcodes_himegari.py + build/opcodes.json + docs/opcode-reference.md + build/opcode-coverage.md
|
||||||
--lint run the linter, print errors/warnings, exit nonzero on errors
|
--lint run the linter, print errors/warnings, exit nonzero on errors
|
||||||
See docs/superpowers/specs/2026-07-06-opcode-reference-design.md."""
|
See docs/superpowers/specs/2026-07-06-opcode-reference-design.md."""
|
||||||
@@ -77,14 +78,22 @@ def scan_corpus():
|
|||||||
def _is_named(label: str) -> bool:
|
def _is_named(label: str) -> bool:
|
||||||
return not (label.startswith("u00") or label == "dev_ukn" or label.startswith("?"))
|
return not (label.startswith("u00") or label == "dev_ukn" or label.startswith("?"))
|
||||||
|
|
||||||
def skeleton_toml(op: int, label: str, argc: int, argtypes_for_op: dict) -> str:
|
def skeleton_toml(op: int, label: str, argc: int, argtypes_for_op: dict,
|
||||||
|
*, observed_in_himegari: bool = True) -> str:
|
||||||
conf = "med" if _is_named(label) else "low"
|
conf = "med" if _is_named(label) else "low"
|
||||||
lines = ["[[opcode]]", f"op = 0x{op:x}", f'label = "{label}"', f"argc = {argc}",
|
abi_source = "kelebek+decode-validated" if observed_in_himegari else "kelebek"
|
||||||
'abi_source = "kelebek+decode-validated"', "", "[opcode.semantics]",
|
summary = ("" if observed_in_himegari else
|
||||||
f'name = "{label}"', 'category = "unknown"', 'summary = ""',
|
"Broader AGE-catalog compatibility stub; the port currently traces and skips it.")
|
||||||
|
evidence = ("" if observed_in_himegari else
|
||||||
|
"Not observed in Himegari's script corpus; ABI label/argc come from Kelebek's AGE table.")
|
||||||
|
lines = ["[[opcode]]", f"op = 0x{op:x}", f'label = "{label}"', f"argc = {argc}"]
|
||||||
|
if not observed_in_himegari:
|
||||||
|
lines.append("observed_in_himegari = false")
|
||||||
|
lines += [f'abi_source = "{abi_source}"', "", "[opcode.semantics]",
|
||||||
|
f'name = "{label}"', 'category = "unknown"', f'summary = "{summary}"',
|
||||||
"noop_headless = false", 'source = "kelebek"', f'confidence = "{conf}"',
|
"noop_headless = false", 'source = "kelebek"', f'confidence = "{conf}"',
|
||||||
"depends_on = []", 'evidence = ""']
|
"depends_on = []", f'evidence = "{evidence}"']
|
||||||
for i in range(argc):
|
for i in range(argc if observed_in_himegari else 0):
|
||||||
tnames = [TYPE_NAMES.get(t, "t%#x" % t) for t in sorted(argtypes_for_op.get(i, ()))]
|
tnames = [TYPE_NAMES.get(t, "t%#x" % t) for t in sorted(argtypes_for_op.get(i, ()))]
|
||||||
obs = ", ".join('"%s"' % n for n in tnames)
|
obs = ", ".join('"%s"' % n for n in tnames)
|
||||||
lines += ["", "[[opcode.semantics.args]]", f"i = {i + 1}", 'role = ""',
|
lines += ["", "[[opcode.semantics.args]]", f"i = {i + 1}", 'role = ""',
|
||||||
@@ -107,6 +116,22 @@ def bootstrap(toml_path: Path) -> None:
|
|||||||
f.write("\n".join(blocks))
|
f.write("\n".join(blocks))
|
||||||
print(f"bootstrap: {len(used)} used opcodes; appended {len(blocks)} new skeletons -> {toml_path}")
|
print(f"bootstrap: {len(used)} used opcodes; appended {len(blocks)} new skeletons -> {toml_path}")
|
||||||
|
|
||||||
|
def bootstrap_age(toml_path: Path) -> None:
|
||||||
|
"""Append compatibility stubs for catalog opcodes absent from the canonical map."""
|
||||||
|
present = set(M.load(toml_path).opcodes) if toml_path.exists() else set()
|
||||||
|
blocks = [
|
||||||
|
skeleton_toml(op, label, argc, {}, observed_in_himegari=False)
|
||||||
|
for op, (label, argc) in sorted(OPCODES.items())
|
||||||
|
if op not in present
|
||||||
|
]
|
||||||
|
if not toml_path.exists():
|
||||||
|
toml_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
toml_path.write_text(META_TOML + "\n", encoding="utf-8")
|
||||||
|
with toml_path.open("a", encoding="utf-8") as f:
|
||||||
|
f.write("\n".join(blocks))
|
||||||
|
print(f"bootstrap-age: {len(OPCODES)} catalog opcodes; "
|
||||||
|
f"appended {len(blocks)} compatibility stubs -> {toml_path}")
|
||||||
|
|
||||||
GEN_HEADER = "# DO NOT EDIT -- generated from vm-map/opcodes.toml by tools/opcodes_build.py --build\n"
|
GEN_HEADER = "# DO NOT EDIT -- generated from vm-map/opcodes.toml by tools/opcodes_build.py --build\n"
|
||||||
|
|
||||||
def emit_inferred_py(model: M.Model) -> str:
|
def emit_inferred_py(model: M.Model) -> str:
|
||||||
@@ -128,6 +153,7 @@ def emit_json(model: M.Model) -> str:
|
|||||||
"dependents": {"0x%x" % k: ["0x%x" % d for d in v] for k, v in rev.items() if v}}
|
"dependents": {"0x%x" % k: ["0x%x" % d for d in v] for k, v in rev.items() if v}}
|
||||||
for op, oc in sorted(model.opcodes.items()):
|
for op, oc in sorted(model.opcodes.items()):
|
||||||
e = {"op": "0x%x" % op, "label": oc.label, "argc": oc.argc,
|
e = {"op": "0x%x" % op, "label": oc.label, "argc": oc.argc,
|
||||||
|
"observed_in_himegari": oc.observed_in_himegari,
|
||||||
"code_target_args": oc.code_target_args, "abi_source": oc.abi_source}
|
"code_target_args": oc.code_target_args, "abi_source": oc.abi_source}
|
||||||
s = oc.semantics
|
s = oc.semantics
|
||||||
if s:
|
if s:
|
||||||
@@ -140,9 +166,12 @@ def emit_json(model: M.Model) -> str:
|
|||||||
|
|
||||||
def emit_reference_md(model: M.Model) -> str:
|
def emit_reference_md(model: M.Model) -> str:
|
||||||
rev = M.dependents(model)
|
rev = M.dependents(model)
|
||||||
|
observed = sum(oc.observed_in_himegari for oc in model.opcodes.values())
|
||||||
|
catalog_only = len(model.opcodes) - observed
|
||||||
L = ["<!-- DO NOT EDIT -- generated from vm-map/opcodes.toml by tools/opcodes_build.py --build -->",
|
L = ["<!-- DO NOT EDIT -- generated from vm-map/opcodes.toml by tools/opcodes_build.py --build -->",
|
||||||
"# Opcode Reference (generated)", "",
|
"# Opcode Reference (generated)", "",
|
||||||
f"{len(model.opcodes)} opcodes used by Himegari. Source of truth: `vm-map/opcodes.toml`.", ""]
|
f"{len(model.opcodes)} AGE catalog opcodes: {observed} observed in Himegari and "
|
||||||
|
f"{catalog_only} compatibility stubs. Source of truth: `vm-map/opcodes.toml`.", ""]
|
||||||
by_cat = collections.defaultdict(list)
|
by_cat = collections.defaultdict(list)
|
||||||
for op, oc in model.opcodes.items():
|
for op, oc in model.opcodes.items():
|
||||||
cat = oc.semantics.category if oc.semantics else "unknown"
|
cat = oc.semantics.category if oc.semantics else "unknown"
|
||||||
@@ -182,8 +211,12 @@ def emit_coverage_md(model: M.Model) -> str:
|
|||||||
by_cat[s.category] += 1
|
by_cat[s.category] += 1
|
||||||
if s.name != oc.label:
|
if s.name != oc.label:
|
||||||
named += 1
|
named += 1
|
||||||
|
observed = sum(oc.observed_in_himegari for oc in model.opcodes.values())
|
||||||
L = ["<!-- DO NOT EDIT -- generated from vm-map/opcodes.toml -->", "# Opcode Coverage (generated)", "",
|
L = ["<!-- DO NOT EDIT -- generated from vm-map/opcodes.toml -->", "# Opcode Coverage (generated)", "",
|
||||||
f"- opcodes: {len(model.opcodes)}", f"- given a distinct mnemonic: {named}", "",
|
f"- AGE catalog opcodes: {len(model.opcodes)}",
|
||||||
|
f"- observed in Himegari: {observed}",
|
||||||
|
f"- compatibility stubs: {len(model.opcodes) - observed}",
|
||||||
|
f"- given a distinct mnemonic: {named}", "",
|
||||||
"## by source", ""]
|
"## by source", ""]
|
||||||
L += [f"- {k}: {v}" for k, v in sorted(by_src.items())]
|
L += [f"- {k}: {v}" for k, v in sorted(by_src.items())]
|
||||||
L += ["", "## by confidence", ""] + [f"- {k}: {by_conf[k]}" for k in ("high", "med", "low")]
|
L += ["", "## by confidence", ""] + [f"- {k}: {by_conf[k]}" for k in ("high", "med", "low")]
|
||||||
@@ -193,6 +226,7 @@ def emit_coverage_md(model: M.Model) -> str:
|
|||||||
def main(argv=None):
|
def main(argv=None):
|
||||||
ap = argparse.ArgumentParser()
|
ap = argparse.ArgumentParser()
|
||||||
ap.add_argument("--bootstrap", action="store_true")
|
ap.add_argument("--bootstrap", action="store_true")
|
||||||
|
ap.add_argument("--bootstrap-age", action="store_true")
|
||||||
ap.add_argument("--build", action="store_true")
|
ap.add_argument("--build", action="store_true")
|
||||||
ap.add_argument("--lint", action="store_true")
|
ap.add_argument("--lint", action="store_true")
|
||||||
ap.add_argument("--toml", default=str(TOML_DEFAULT))
|
ap.add_argument("--toml", default=str(TOML_DEFAULT))
|
||||||
@@ -201,6 +235,9 @@ def main(argv=None):
|
|||||||
if args.bootstrap:
|
if args.bootstrap:
|
||||||
bootstrap(tp)
|
bootstrap(tp)
|
||||||
return 0
|
return 0
|
||||||
|
if args.bootstrap_age:
|
||||||
|
bootstrap_age(tp)
|
||||||
|
return 0
|
||||||
if args.build:
|
if args.build:
|
||||||
model = M.load(tp)
|
model = M.load(tp)
|
||||||
errors, warnings = M.lint(model)
|
errors, warnings = M.lint(model)
|
||||||
@@ -226,7 +263,7 @@ def main(argv=None):
|
|||||||
print("error:", m)
|
print("error:", m)
|
||||||
print(f"lint: {len(errors)} errors, {len(warnings)} warnings")
|
print(f"lint: {len(errors)} errors, {len(warnings)} warnings")
|
||||||
return 1 if errors else 0
|
return 1 if errors else 0
|
||||||
ap.error("no action (expected --bootstrap/--build/--lint)")
|
ap.error("no action (expected --bootstrap/--bootstrap-age/--build/--lint)")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class Opcode:
|
|||||||
op: int
|
op: int
|
||||||
label: str
|
label: str
|
||||||
argc: int
|
argc: int
|
||||||
|
observed_in_himegari: bool = True
|
||||||
code_target_args: list[int] = field(default_factory=list)
|
code_target_args: list[int] = field(default_factory=list)
|
||||||
abi_source: str = "kelebek+decode-validated"
|
abi_source: str = "kelebek+decode-validated"
|
||||||
abi_note: str = ""
|
abi_note: str = ""
|
||||||
@@ -61,6 +62,7 @@ def load(path) -> Model:
|
|||||||
)
|
)
|
||||||
ops[int(e["op"])] = Opcode(
|
ops[int(e["op"])] = Opcode(
|
||||||
op=int(e["op"]), label=e.get("label", ""), argc=int(e["argc"]),
|
op=int(e["op"]), label=e.get("label", ""), argc=int(e["argc"]),
|
||||||
|
observed_in_himegari=bool(e.get("observed_in_himegari", True)),
|
||||||
code_target_args=[int(x) for x in e.get("code_target_args", [])],
|
code_target_args=[int(x) for x in e.get("code_target_args", [])],
|
||||||
abi_source=e.get("abi_source", "kelebek+decode-validated"),
|
abi_source=e.get("abi_source", "kelebek+decode-validated"),
|
||||||
abi_note=e.get("abi_note", ""), semantics=sem,
|
abi_note=e.get("abi_note", ""), semantics=sem,
|
||||||
@@ -73,6 +75,12 @@ def lint(model: Model) -> tuple[list[str], list[str]]:
|
|||||||
errors: list[str] = []
|
errors: list[str] = []
|
||||||
warnings: list[str] = []
|
warnings: list[str] = []
|
||||||
ops = model.opcodes
|
ops = model.opcodes
|
||||||
|
expected_observed = model.meta.get("opcodes_used_by_himegari")
|
||||||
|
actual_observed = sum(oc.observed_in_himegari for oc in ops.values())
|
||||||
|
if expected_observed is not None and int(expected_observed) != actual_observed:
|
||||||
|
errors.append(
|
||||||
|
"meta.opcodes_used_by_himegari="
|
||||||
|
f"{expected_observed} but {actual_observed} entries are marked observed")
|
||||||
for op, oc in sorted(ops.items()):
|
for op, oc in sorted(ops.items()):
|
||||||
s = oc.semantics
|
s = oc.semantics
|
||||||
if not s:
|
if not s:
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ def test_lint():
|
|||||||
|
|
||||||
def test_bootstrap():
|
def test_bootstrap():
|
||||||
import opcodes_build as B
|
import opcodes_build as B
|
||||||
|
from age_opcodes import OPCODES
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
fd, p = tempfile.mkstemp(suffix=".toml"); os.close(fd); os.remove(p)
|
fd, p = tempfile.mkstemp(suffix=".toml"); os.close(fd); os.remove(p)
|
||||||
tp = Path(p)
|
tp = Path(p)
|
||||||
@@ -130,6 +131,16 @@ def test_bootstrap():
|
|||||||
check(len(M.load(tp).opcodes) == n1, "second bootstrap adds no duplicates")
|
check(len(M.load(tp).opcodes) == n1, "second bootstrap adds no duplicates")
|
||||||
e, w = M.lint(m)
|
e, w = M.lint(m)
|
||||||
check(e == [], f"bootstrapped file lints clean (errors: {e[:3]})")
|
check(e == [], f"bootstrapped file lints clean (errors: {e[:3]})")
|
||||||
|
B.bootstrap_age(tp)
|
||||||
|
full = M.load(tp)
|
||||||
|
check(len(full.opcodes) == len(OPCODES), "bootstrap-age seeds the complete AGE catalog")
|
||||||
|
check(sum(o.observed_in_himegari for o in full.opcodes.values()) == len(m.opcodes),
|
||||||
|
"bootstrap-age marks only added catalog entries unobserved")
|
||||||
|
check(all(not o.semantics.noop_headless for o in full.opcodes.values()
|
||||||
|
if not o.observed_in_himegari),
|
||||||
|
"compatibility stubs are not misclassified as semantically safe no-ops")
|
||||||
|
e, w = M.lint(full)
|
||||||
|
check(e == [], f"full catalog lints clean (errors: {e[:3]})")
|
||||||
|
|
||||||
def test_emit_inferred():
|
def test_emit_inferred():
|
||||||
import opcodes_build as B
|
import opcodes_build as B
|
||||||
|
|||||||
5083
vm-map/opcodes.toml
5083
vm-map/opcodes.toml
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user