@henols/c64-re-tools 2.0.0-rc.3 → 2.0.0-rc.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.
- package/README.md +15 -3
- package/package.json +1 -1
- package/skills/c64-assembler/SKILL.md +5 -2
- package/skills/c64-assembler/scripts/assemble.ts +8 -1
- package/skills/c64-assembler/scripts/compare.ts +84 -0
- package/skills/c64-basic/SKILL.md +1 -2
- package/skills/c64-disk/SKILL.md +20 -6
- package/skills/c64-disk/scripts/containers.ts +124 -0
- package/skills/c64-disk/scripts/disk.ts +59 -5
- package/skills/c64-emulator/SKILL.md +11 -4
- package/skills/c64-knowledge/SKILL.md +53 -3
- package/skills/c64-knowledge/scripts/knowledge.ts +258 -29
- package/skills/c64-memory-map/SKILL.md +4 -1
- package/skills/c64-memory-map/scripts/memmap.ts +14 -3
- package/skills/c64-provenance/SKILL.md +37 -9
- package/skills/c64-provenance/scripts/compare.ts +60 -29
- package/skills/c64-provenance/scripts/provenance.ts +248 -19
- package/skills/c64-provenance/scripts/runtime.ts +179 -0
- package/skills/c64-provenance/scripts/separate.ts +132 -0
- package/skills/c64-reverse-engineering/SKILL.md +65 -11
- package/skills/c64-source/SKILL.md +119 -0
- package/skills/c64-source/scripts/generate.ts +402 -0
- package/skills/c64-source/scripts/loader.ts +320 -0
- package/skills/c64-source/scripts/opcodes.ts +61 -0
- package/skills/c64-source/scripts/source.ts +558 -0
- package/skills/c64-static-analysis/SKILL.md +27 -4
- package/skills/c64-static-analysis/scripts/analyze.ts +102 -13
- package/skills/c64-static-analysis/scripts/coverage.ts +78 -0
- package/skills/c64-static-analysis/scripts/findings.ts +6 -4
- package/skills/c64-static-analysis/scripts/idioms.ts +144 -0
- package/skills/c64-static-analysis/scripts/instructions.ts +105 -0
- package/skills/c64-testing/SKILL.md +27 -3
- package/skills/c64-testing/references/scenario-format.md +43 -3
- package/skills/c64-testing/scripts/run.ts +189 -0
- package/skills/c64-testing/scripts/scenario.ts +153 -135
- package/skills/c64-testing/scripts/test.ts +16 -6
- package/skills/c64-unpacker/SKILL.md +65 -14
- package/skills/c64-unpacker/scripts/evidence.ts +86 -21
- package/skills/c64-unpacker/scripts/loads.ts +174 -0
- package/skills/c64-unpacker/scripts/machine.ts +68 -0
- package/skills/c64-unpacker/scripts/unpack.ts +302 -48
- package/src/c64.ts +91 -0
- package/src/cli/status.ts +3 -1
- package/src/host/server.ts +6 -5
- package/src/host/vice/adapter.ts +25 -10
- package/src/host/vice/session.ts +64 -20
- package/src/host-client/autostart.ts +45 -0
- package/src/host-client/connect.ts +4 -2
- package/src/host-client/coverage.ts +59 -0
- package/src/host-client/steps.ts +170 -0
- package/src/host-client/transfer.ts +29 -3
- package/src/host-client/vice-session.ts +42 -13
- package/src/knowledge/history.ts +40 -13
- package/src/knowledge/import.ts +72 -56
- package/src/knowledge/read.ts +203 -52
- package/src/knowledge/schema.ts +94 -12
- package/src/knowledge/write.ts +432 -123
- package/src/mcp/output.ts +32 -0
- package/src/mcp/server.ts +4 -2
- package/src/mcp/tools/debug.ts +110 -24
- package/src/mcp/tools/input.ts +1 -0
- package/src/mcp/tools/media.ts +22 -6
- package/src/mcp/tools/memory.ts +57 -10
- package/src/mcp/tools/video.ts +9 -3
- package/src/native/discover.ts +5 -2
- package/src/native/ghidra/index.ts +19 -3
- package/src/native/ghidra/scripts/C64Export.java +33 -1
- package/src/native/status.ts +15 -3
- package/src/native/types.ts +30 -1
- package/{skills/c64-memory-map/scripts → src}/platform.ts +18 -2
- package/src/protocol/messages.ts +17 -1
- package/src/protocol/vice.ts +72 -18
- package/src/script.ts +3 -2
|
@@ -40,19 +40,25 @@ Before the first step of each side, the script does a hard reset. The CPU stops
|
|
|
40
40
|
| `{ "reset": "hard" }` | Reset the C64 (`hard` or `soft`). The CPU stops at the reset vector. |
|
|
41
41
|
| `{ "frames": 150 }` | Run for an exact number of frames, from 1 to 10000. Use it after a reset to start the C64. |
|
|
42
42
|
| `{ "load": true }` | Load the program of the side into memory. The CPU stays stopped. |
|
|
43
|
-
| `{ "autostart": true }` | Load and
|
|
43
|
+
| `{ "autostart": true }` | Load the program of the side, type `RUN` and wait until the program runs. The program must have a BASIC start line. The keys of a later `type` step go to the program. |
|
|
44
44
|
| `{ "write": { "at": "seed", "bytes": [1, 2] } }` | Write bytes into memory: 1 to 4096 numbers from 0 to 255. |
|
|
45
45
|
| `{ "registers": { "pc": "start", "a": 0 } }` | Set registers: `pc`, `a`, `x`, `y` or `sp`. A value is a number or a name. |
|
|
46
46
|
| `{ "joystick": { "port": 2, "direction": "left", "fire": true } }` | Set a joystick. The joystick stays in this state until the next joystick step. |
|
|
47
|
-
| `{ "type": "RUN\n" }` | Type text on the keyboard, at most 1024 keys. |
|
|
47
|
+
| `{ "type": "RUN\n" }` | Type text on the keyboard, at most 1024 keys. The keys go into the keyboard queue of the KERNAL. A program that reads the keyboard hardware does not see them. A step cannot press one specific key of the keyboard matrix. |
|
|
48
48
|
| `{ "runUntil": { "at": "main_loop" }, "timeoutFrames": 600 }` | Run until the CPU gets to an address. |
|
|
49
49
|
| `{ "runUntil": { "memory": { "at": "game_state", "equals": 2 } }, "timeoutFrames": 600 }` | Run until a byte in memory has a value. |
|
|
50
|
+
| `{ "runUntil": { "raster": 250 }, "timeoutFrames": 5 }` | Run until the raster beam gets to a line (0 to 311 for PAL, 0 to 262 for NTSC). |
|
|
51
|
+
| `{ "runUntil": { "at": "irq_tick", "raster": 250 }, "timeoutFrames": 600 }` | Run until the CPU gets to an address while the beam is on that line. |
|
|
52
|
+
| `{ "snapshot": "snapshots/start.vsf" }` | Restore a snapshot file. Usually with `side`: the snapshot of one program is not a state of the other. |
|
|
53
|
+
| `{ "normalize": "unpacked/entry_state.json" }` | Write pages 0 to 3 and `$00`/`$01` from an entry state file of the c64-unpacker skill. Then both sides have the same zero page, stack and KERNAL work area. |
|
|
50
54
|
| `{ "observe": "moved", ... }` | Record a checkpoint. |
|
|
51
55
|
|
|
52
56
|
The `port` of a joystick is 1 or 2. The `direction` is `center`, `up`, `down`, `left`, `right`, `up-left`, `up-right`, `down-left` or `down-right`.
|
|
53
57
|
|
|
54
58
|
`timeoutFrames` is from 1 to 30000. A `runUntil` that does not get to its target in `timeoutFrames` frames makes the result INCONCLUSIVE.
|
|
55
59
|
|
|
60
|
+
Each step except `observe` can have `"side": "original"` or `"side": "rebuild"`. Then only that side does the step. Use it when only one side needs a step, for example a key for the intro of the original. An `observe` step has no `side`, because it compares both sides.
|
|
61
|
+
|
|
56
62
|
An address is `$` and four hex digits, or a symbol name. Each side finds a name in its own symbols.
|
|
57
63
|
|
|
58
64
|
## Observations
|
|
@@ -61,7 +67,7 @@ An `observe` step has a checkpoint name. A scenario can have each name only one
|
|
|
61
67
|
|
|
62
68
|
| Key | What the script compares |
|
|
63
69
|
|---|---|
|
|
64
|
-
| `memory` | A list of names, `$` addresses, or objects with `at`, `size` (1 to 4096, default 1)
|
|
70
|
+
| `memory` | A list of names, `$` addresses, or objects with `at`, `size` (1 to 4096, default 1), `tolerance` (0 to 255, default 0) and `exclude`. |
|
|
65
71
|
| `registers` | A list of register names: `pc`, `a`, `x`, `y`, `sp`, `flags`. |
|
|
66
72
|
| `screen` | The screen of the rebuild against the screen of the original. `maxMismatchRatio` (default 0) is the largest permitted part of different pixels. `mask` is a list of rectangles (`x`, `y`, `width`, `height`) that the comparison does not include. |
|
|
67
73
|
| `vicii` | The VIC-II state: mode, screen address, scroll and colors. |
|
|
@@ -69,8 +75,42 @@ An `observe` step has a checkpoint name. A scenario can have each name only one
|
|
|
69
75
|
|
|
70
76
|
A `tolerance` is the largest permitted difference for each byte. A `mask` has at most 64 rectangles. A scenario can observe the screen at most at 64 checkpoints.
|
|
71
77
|
|
|
78
|
+
`exclude` is a list of bytes in the observed range that the comparison does not include. Each has `at` and a `reason`, for example `{ "at": "$d01e", "reason": "latched sprite collision, not writable" }`. The result lists each excluded byte with its reason.
|
|
79
|
+
|
|
72
80
|
A `pc` value is an address. Compare `pc` only when both programs have the code at the same address.
|
|
73
81
|
|
|
82
|
+
## Runs
|
|
83
|
+
|
|
84
|
+
A run file (for `run.ts`) has:
|
|
85
|
+
|
|
86
|
+
- `name`: a short name.
|
|
87
|
+
- `start`: `{ "program": "game.prg" }` to autostart a program with a BASIC start line, `{ "program": "game.prg", "load": true }` to load it with the CPU stopped (set `pc` in a step), or `{ "snapshot": "snapshots/room0.vsf" }`.
|
|
88
|
+
- `symbols`: optional, `knowledge`, `none` (default), or a JSON file as for a subject.
|
|
89
|
+
- `videoStandard`: optional, `pal` (default) or `ntsc`.
|
|
90
|
+
- `steps`: the steps `write`, `registers`, `joystick`, `type`, `frames` and `runUntil` of a scenario, and the run steps `snapshot`, `save`, `screen`, `coverage` and `observe`.
|
|
91
|
+
|
|
92
|
+
A name in `save`, `screen`, `coverage` and `observe` has 1 to 64 letters, digits, dots, underscores or hyphens. The files go into the `--out` directory, with `results.json`. It has the observations, the files and the steps done. If a `runUntil` step did not reach its target, it also tells why the run stopped.
|
|
93
|
+
|
|
94
|
+
Example: visit two rooms from the same start.
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"name": "rooms",
|
|
99
|
+
"start": { "snapshot": "snapshots/room0.vsf" },
|
|
100
|
+
"steps": [
|
|
101
|
+
{ "write": { "at": "$0350", "bytes": [1] } },
|
|
102
|
+
{ "frames": 50 },
|
|
103
|
+
{ "screen": "room1" },
|
|
104
|
+
{ "coverage": "room1" },
|
|
105
|
+
{ "snapshot": "snapshots/room0.vsf" },
|
|
106
|
+
{ "write": { "at": "$0350", "bytes": [2] } },
|
|
107
|
+
{ "frames": 50 },
|
|
108
|
+
{ "screen": "room2" },
|
|
109
|
+
{ "coverage": "room2" }
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
74
114
|
## Checks
|
|
75
115
|
|
|
76
116
|
The script checks the scenario before it starts the emulator. A step with an unknown key or a wrong value stops the script with `invalid-input`. The message names the step.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// The c64-testing run script (D45): runs steps in its own emulator, from a
|
|
2
|
+
// program or a snapshot file, and writes what the steps ask for into a
|
|
3
|
+
// directory: screens, coverage maps, snapshot files, and results.json with
|
|
4
|
+
// the observations. One side, no comparison. Exit status: 0 when every step
|
|
5
|
+
// ran, 1 when a step did not, 2 for a bad call.
|
|
6
|
+
|
|
7
|
+
import { parseArgs } from "node:util";
|
|
8
|
+
|
|
9
|
+
import { formatC64Address, parseC64Address } from "#src/c64.ts";
|
|
10
|
+
import { runProgram } from "#src/host-client/autostart.ts";
|
|
11
|
+
import { readCoverage } from "#src/host-client/coverage.ts";
|
|
12
|
+
import { ACTION_STEP_KEYS, ACTION_STEP_KINDS, checkActionStep, isInteger, isObject, runActionStep, type ActionStep } from "#src/host-client/steps.ts";
|
|
13
|
+
import { MAX_MEMORY_READ, WireFailure } from "#src/host-client/tools.ts";
|
|
14
|
+
import { readProjectFile, writeProjectFile } from "#src/host-client/transfer.ts";
|
|
15
|
+
import { ViceSessionClient } from "#src/host-client/vice-session.ts";
|
|
16
|
+
import { resolveProjectPath } from "#src/project.ts";
|
|
17
|
+
import { runScript, UsageError } from "#src/script.ts";
|
|
18
|
+
import { loadSymbols } from "./scenario.ts";
|
|
19
|
+
|
|
20
|
+
const USAGE = `run.ts <run.json> --out <dir>
|
|
21
|
+
|
|
22
|
+
<run.json> is a run file relative to the project directory: where to start,
|
|
23
|
+
optional symbols, and the steps. See references/scenario-format.md, section
|
|
24
|
+
Runs. The files that the steps write, and results.json, go into <dir>. The
|
|
25
|
+
result is one JSON object.`;
|
|
26
|
+
|
|
27
|
+
type RunStep =
|
|
28
|
+
| ActionStep
|
|
29
|
+
| { snapshot: string }
|
|
30
|
+
| { save: string }
|
|
31
|
+
| { screen: string }
|
|
32
|
+
| { coverage: string }
|
|
33
|
+
| { observe: string; memory?: Array<string | { at: string; size?: number }>; registers?: boolean; vicii?: boolean; sprites?: number[] };
|
|
34
|
+
|
|
35
|
+
interface Run {
|
|
36
|
+
name: string;
|
|
37
|
+
videoStandard?: "pal" | "ntsc";
|
|
38
|
+
start: { program: string; load?: boolean } | { snapshot: string };
|
|
39
|
+
symbols?: string;
|
|
40
|
+
steps: RunStep[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const OWN_KINDS = ["snapshot", "save", "screen", "coverage", "observe"] as const;
|
|
44
|
+
const OBSERVE_KEYS = ["observe", "memory", "registers", "vicii", "sprites"];
|
|
45
|
+
/** A name of a file that a step writes: letters, digits, dots, underscores or hyphens. */
|
|
46
|
+
const NAME = /^[A-Za-z0-9._-]{1,64}$/;
|
|
47
|
+
|
|
48
|
+
function readRun(path: string): Run {
|
|
49
|
+
let value: unknown;
|
|
50
|
+
try {
|
|
51
|
+
value = JSON.parse(readProjectFile(path).bytes.toString("utf8"));
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (error instanceof WireFailure) throw error;
|
|
54
|
+
throw new UsageError(`the run ${path} is not JSON: ${(error as Error).message}`);
|
|
55
|
+
}
|
|
56
|
+
const fail = (message: string): never => {
|
|
57
|
+
throw new UsageError(`run ${path}: ${message}`);
|
|
58
|
+
};
|
|
59
|
+
if (!isObject(value)) return fail("is not a JSON object");
|
|
60
|
+
if (typeof value.name !== "string" || value.name === "") fail("needs a name");
|
|
61
|
+
if (value.videoStandard !== undefined && value.videoStandard !== "pal" && value.videoStandard !== "ntsc") fail("videoStandard must be pal or ntsc");
|
|
62
|
+
const start = value.start;
|
|
63
|
+
if (!isObject(start) || (typeof start.program !== "string") === (typeof start.snapshot !== "string")) fail("start needs program or snapshot");
|
|
64
|
+
if (isObject(start) && start.load !== undefined && typeof start.load !== "boolean") fail("start.load must be true or false");
|
|
65
|
+
if (value.symbols !== undefined && typeof value.symbols !== "string") fail("symbols must be knowledge, none or a JSON file");
|
|
66
|
+
if (!Array.isArray(value.steps) || value.steps.length === 0) fail("needs steps");
|
|
67
|
+
const labels = new Set<string>();
|
|
68
|
+
(value.steps as unknown[]).forEach((step, index) => {
|
|
69
|
+
const at = `step ${index + 1}`;
|
|
70
|
+
if (!isObject(step)) return fail(`${at} must be an object`);
|
|
71
|
+
const kinds = "observe" in step ? ["observe"] : Object.keys(step).filter((key) => (ACTION_STEP_KINDS as readonly string[]).includes(key) || (OWN_KINDS as readonly string[]).includes(key));
|
|
72
|
+
if (kinds.length !== 1) return fail(`${at} must have exactly one of ${[...ACTION_STEP_KINDS, ...OWN_KINDS].join(", ")}`);
|
|
73
|
+
const kind = kinds[0]!;
|
|
74
|
+
const allowed = kind === "observe" ? OBSERVE_KEYS : (ACTION_STEP_KEYS[kind] ?? [kind]);
|
|
75
|
+
for (const key of Object.keys(step)) if (!allowed.includes(key)) fail(`${at} has the key ${key}, which a ${kind} step does not have`);
|
|
76
|
+
if ((ACTION_STEP_KINDS as readonly string[]).includes(kind)) return checkActionStep(kind, step, at, fail);
|
|
77
|
+
const name = step[kind];
|
|
78
|
+
if (kind === "snapshot") {
|
|
79
|
+
if (typeof name !== "string" || name === "") fail(`${at}: snapshot needs a project file`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (typeof name !== "string" || !NAME.test(name)) fail(`${at}: ${kind} needs a name of 1 to 64 letters, digits, dots, underscores or hyphens`);
|
|
83
|
+
if (kind !== "observe") return;
|
|
84
|
+
if (labels.has(name as string)) fail(`${at} needs a new observation name`);
|
|
85
|
+
labels.add(name as string);
|
|
86
|
+
if (step.memory !== undefined && (!Array.isArray(step.memory) || step.memory.some((item) => typeof item !== "string" && !(isObject(item) && typeof item.at === "string" && (item.size === undefined || isInteger(item.size, 1, MAX_MEMORY_READ)))))) {
|
|
87
|
+
fail(`${at}: memory is a list of names, $ addresses, or objects with at and size`);
|
|
88
|
+
}
|
|
89
|
+
if (step.registers !== undefined && typeof step.registers !== "boolean") fail(`${at}: registers must be true or false`);
|
|
90
|
+
if (step.vicii !== undefined && typeof step.vicii !== "boolean") fail(`${at}: vicii must be true or false`);
|
|
91
|
+
if (step.sprites !== undefined && (!Array.isArray(step.sprites) || !step.sprites.every((sprite) => isInteger(sprite, 0, 7)))) fail(`${at}: sprites is a list of numbers from 0 to 7`);
|
|
92
|
+
});
|
|
93
|
+
return value as unknown as Run;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function runFile(path: string, out: string): Promise<{ output: Record<string, unknown>; failed: boolean }> {
|
|
97
|
+
const run = readRun(path);
|
|
98
|
+
const symbols = loadSymbols({ program: "", symbols: run.symbols ?? "none" });
|
|
99
|
+
const at = (location: string): number => {
|
|
100
|
+
if (location.startsWith("$")) return parseC64Address(location);
|
|
101
|
+
const address = symbols.get(location);
|
|
102
|
+
if (address === undefined) throw new UsageError(`the symbols have no ${location}`);
|
|
103
|
+
return address;
|
|
104
|
+
};
|
|
105
|
+
const files: string[] = [];
|
|
106
|
+
const write = (name: string, bytes: Uint8Array) => {
|
|
107
|
+
writeProjectFile(`${out}/${name}`, bytes, { what: "--out" });
|
|
108
|
+
files.push(name);
|
|
109
|
+
};
|
|
110
|
+
const observations: Record<string, unknown> = {};
|
|
111
|
+
const client = await ViceSessionClient.open({ videoStandard: run.videoStandard ?? "pal" });
|
|
112
|
+
let done = 0;
|
|
113
|
+
let reason: string | undefined;
|
|
114
|
+
try {
|
|
115
|
+
await client.reset({ mode: "hard", run: false });
|
|
116
|
+
if ("snapshot" in run.start) {
|
|
117
|
+
await client.snapshotRestore(readProjectFile(run.start.snapshot).bytes);
|
|
118
|
+
} else if (run.start.load === true) {
|
|
119
|
+
await client.execution({ action: "advance-frames", count: 150, space: "c64" });
|
|
120
|
+
await client.programLoad({ path: run.start.program });
|
|
121
|
+
} else {
|
|
122
|
+
const failed = await runProgram(client, run.start.program, readProjectFile(run.start.program).bytes);
|
|
123
|
+
if (failed !== undefined) reason = failed === "not-loaded" ? "The program was not in memory after autostart." : "The program did not start after RUN; it needs a BASIC start line.";
|
|
124
|
+
}
|
|
125
|
+
await client.memmap({ action: "clear" });
|
|
126
|
+
for (const step of reason === undefined ? run.steps : []) {
|
|
127
|
+
if ("snapshot" in step) {
|
|
128
|
+
await client.snapshotRestore(readProjectFile(step.snapshot).bytes);
|
|
129
|
+
} else if ("save" in step) {
|
|
130
|
+
await client.snapshot({ action: "save", name: "run-save" });
|
|
131
|
+
write(`${step.save}.vsf`, await client.snapshotExport("run-save"));
|
|
132
|
+
await client.snapshot({ action: "discard", name: "run-save" });
|
|
133
|
+
} else if ("screen" in step) {
|
|
134
|
+
write(`${step.screen}.png`, Buffer.from((await client.screenCapture()).png, "base64"));
|
|
135
|
+
} else if ("coverage" in step) {
|
|
136
|
+
write(`${step.coverage}.bin`, await readCoverage(client));
|
|
137
|
+
await client.memmap({ action: "clear" });
|
|
138
|
+
} else if ("observe" in step) {
|
|
139
|
+
const seen: Record<string, unknown> = {};
|
|
140
|
+
if (step.memory !== undefined) {
|
|
141
|
+
const memory: Record<string, string> = {};
|
|
142
|
+
for (const item of step.memory) {
|
|
143
|
+
const spec = typeof item === "string" ? { at: item } : item;
|
|
144
|
+
memory[spec.at] = (await client.memoryRead({ address: at(spec.at), size: spec.size ?? 1, space: "c64", view: "cpu" })).data;
|
|
145
|
+
}
|
|
146
|
+
seen.memory = memory;
|
|
147
|
+
}
|
|
148
|
+
if (step.registers === true) {
|
|
149
|
+
const registers = await client.registersGet("c64");
|
|
150
|
+
seen.registers = { ...registers, pc: formatC64Address(registers.pc) };
|
|
151
|
+
}
|
|
152
|
+
if (step.vicii === true) seen.vicii = await client.vicii();
|
|
153
|
+
if (step.sprites !== undefined) seen.sprites = (await client.sprites(step.sprites)).sprites;
|
|
154
|
+
observations[step.observe] = seen;
|
|
155
|
+
} else {
|
|
156
|
+
const notReached = await runActionStep(client, step, at);
|
|
157
|
+
if (notReached !== undefined && "runUntil" in step) {
|
|
158
|
+
reason = `Step ${done + 1} did not reach its target in ${step.timeoutFrames} frames. It stopped by ${notReached.stopReason} at ${formatC64Address(notReached.pc)}.`;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
done++;
|
|
163
|
+
}
|
|
164
|
+
const pc = (await client.registersGet("c64")).pc;
|
|
165
|
+
const results = { name: run.name, completed: reason === undefined, steps: run.steps.length, done, ...(reason === undefined ? {} : { reason }), pc: formatC64Address(pc), files: [...files], observations };
|
|
166
|
+
write("results.json", Buffer.from(`${JSON.stringify(results, null, 2)}\n`));
|
|
167
|
+
return { output: { ...results, files, observations: Object.keys(observations), out }, failed: reason !== undefined };
|
|
168
|
+
} finally {
|
|
169
|
+
await client.close();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
await runScript(
|
|
174
|
+
async () => {
|
|
175
|
+
const { values, positionals } = parseArgs({ args: process.argv.slice(2), strict: true, allowPositionals: true, options: { out: { type: "string" }, help: { type: "boolean" } } });
|
|
176
|
+
if (values.help) throw new UsageError("");
|
|
177
|
+
if (positionals.length !== 1) throw new UsageError("give one run file");
|
|
178
|
+
if (values.out === undefined) throw new UsageError("run needs --out <dir>");
|
|
179
|
+
try {
|
|
180
|
+
resolveProjectPath(values.out);
|
|
181
|
+
} catch {
|
|
182
|
+
throw new UsageError(`--out must be a path relative to the project directory that stays inside it, not ${values.out}`);
|
|
183
|
+
}
|
|
184
|
+
const { output, failed } = await runFile(positionals[0]!, values.out);
|
|
185
|
+
if (failed) process.exitCode = 1;
|
|
186
|
+
return output;
|
|
187
|
+
},
|
|
188
|
+
{ usage: USAGE },
|
|
189
|
+
);
|