@henols/c64-re-tools 2.0.0-rc.9 → 2.0.0

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.
Files changed (117) hide show
  1. package/README.md +97 -116
  2. package/package.json +1 -1
  3. package/skills/c64-assembler/SKILL.md +22 -6
  4. package/skills/c64-assembler/scripts/assemble.ts +42 -14
  5. package/skills/c64-assembler/scripts/lib/compare.ts +48 -0
  6. package/skills/c64-basic/SKILL.md +10 -1
  7. package/skills/c64-basic/scripts/basic.ts +3 -3
  8. package/skills/c64-disk/SKILL.md +13 -4
  9. package/skills/c64-disk/scripts/disk.ts +7 -7
  10. package/skills/c64-disk/scripts/{containers.ts → lib/containers.ts} +2 -2
  11. package/skills/c64-docs/SKILL.md +138 -0
  12. package/skills/c64-docs/references/page-types.md +141 -0
  13. package/skills/c64-docs/references/style.md +43 -0
  14. package/skills/c64-docs/scripts/generate.ts +109 -0
  15. package/skills/c64-docs/scripts/lib/links.ts +167 -0
  16. package/skills/c64-docs/scripts/lib/markdown.ts +79 -0
  17. package/skills/c64-docs/scripts/lib/reference.ts +406 -0
  18. package/skills/c64-docs/scripts/lib/source-map.ts +81 -0
  19. package/skills/c64-docs/scripts/link.ts +239 -0
  20. package/skills/c64-emulator/SKILL.md +16 -5
  21. package/skills/c64-knowledge/SKILL.md +140 -18
  22. package/skills/c64-knowledge/scripts/knowledge.ts +737 -112
  23. package/skills/c64-knowledge/scripts/lib/listing.ts +162 -0
  24. package/skills/c64-memory-map/SKILL.md +11 -2
  25. package/skills/c64-memory-map/scripts/memmap.ts +2 -2
  26. package/skills/c64-provenance/SKILL.md +66 -11
  27. package/skills/c64-provenance/scripts/{compare.ts → lib/compare.ts} +3 -3
  28. package/skills/c64-provenance/scripts/lib/library.ts +584 -0
  29. package/skills/c64-provenance/scripts/{runtime.ts → lib/runtime.ts} +2 -2
  30. package/skills/c64-provenance/scripts/{separate.ts → lib/separate.ts} +3 -3
  31. package/skills/c64-provenance/scripts/provenance.ts +242 -40
  32. package/skills/c64-reverse-engineering/SKILL.md +51 -26
  33. package/skills/c64-source/SKILL.md +76 -28
  34. package/skills/c64-source/scripts/generate.ts +425 -127
  35. package/skills/c64-source/scripts/{loader.ts → lib/loader.ts} +84 -25
  36. package/skills/c64-source/scripts/lib/source.ts +1121 -0
  37. package/skills/c64-static-analysis/SKILL.md +35 -18
  38. package/skills/c64-static-analysis/scripts/analyze.ts +184 -55
  39. package/skills/c64-static-analysis/scripts/{coverage.ts → lib/coverage.ts} +19 -25
  40. package/skills/c64-static-analysis/scripts/{findings.ts → lib/findings.ts} +6 -2
  41. package/skills/c64-static-analysis/scripts/{idioms.ts → lib/idioms.ts} +21 -12
  42. package/skills/c64-static-analysis/scripts/{instructions.ts → lib/instructions.ts} +1 -1
  43. package/skills/c64-static-analysis/scripts/lib/macros.ts +233 -0
  44. package/skills/c64-static-analysis/scripts/lib/pitfalls.ts +68 -0
  45. package/skills/c64-static-analysis/scripts/lib/pointers.ts +108 -0
  46. package/skills/c64-testing/SKILL.md +36 -7
  47. package/skills/c64-testing/references/scenario-format.md +12 -8
  48. package/skills/c64-testing/scripts/checklist.ts +3 -3
  49. package/skills/c64-testing/scripts/diff.ts +167 -0
  50. package/skills/c64-testing/scripts/lib/diff.ts +70 -0
  51. package/skills/c64-testing/scripts/{scenario.ts → lib/scenario.ts} +45 -34
  52. package/skills/c64-testing/scripts/run.ts +73 -21
  53. package/skills/c64-testing/scripts/test.ts +5 -5
  54. package/skills/c64-unpacker/SKILL.md +23 -9
  55. package/skills/c64-unpacker/scripts/{evidence.ts → lib/evidence.ts} +1 -1
  56. package/skills/c64-unpacker/scripts/{loads.ts → lib/loads.ts} +4 -4
  57. package/skills/c64-unpacker/scripts/{machine.ts → lib/machine.ts} +4 -4
  58. package/skills/c64-unpacker/scripts/{stages.ts → lib/stages.ts} +17 -5
  59. package/skills/c64-unpacker/scripts/unpack.ts +41 -24
  60. package/src/c64.ts +103 -5
  61. package/src/cli/cleanup.ts +1 -1
  62. package/src/cli/main.ts +7 -4
  63. package/src/cli/status.ts +70 -3
  64. package/src/graphics.ts +158 -0
  65. package/src/host/main.ts +18 -4
  66. package/src/host/server.ts +10 -3
  67. package/src/host/tools/petcat.ts +1 -1
  68. package/src/host/tools/status.ts +1 -1
  69. package/src/host/vice/adapter.ts +4 -3
  70. package/src/host/vice/process.ts +2 -2
  71. package/src/host/vice/screen.ts +1 -42
  72. package/src/host/vice/session.ts +196 -23
  73. package/src/host-client/autostart.ts +1 -1
  74. package/src/host-client/connect.ts +1 -1
  75. package/src/host-client/coverage.ts +2 -29
  76. package/src/host-client/image.ts +66 -0
  77. package/src/host-client/steps.ts +9 -4
  78. package/src/host-client/tools.ts +1 -0
  79. package/src/host-client/transfer.ts +39 -11
  80. package/src/host-client/vice-session.ts +33 -16
  81. package/src/knowledge/conflicts.ts +161 -0
  82. package/src/knowledge/database.ts +150 -38
  83. package/src/knowledge/expression.ts +209 -0
  84. package/src/knowledge/graph.ts +104 -0
  85. package/src/knowledge/history.ts +70 -22
  86. package/src/knowledge/import.ts +64 -55
  87. package/src/knowledge/join.ts +211 -0
  88. package/src/knowledge/program.ts +266 -0
  89. package/src/knowledge/read.ts +258 -27
  90. package/src/knowledge/schema.ts +199 -75
  91. package/src/knowledge/text.ts +415 -0
  92. package/src/knowledge/write.ts +740 -57
  93. package/src/mcp/main.ts +1 -1
  94. package/src/mcp/output.ts +15 -8
  95. package/src/mcp/server.ts +3 -1
  96. package/src/mcp/tools/debug.ts +97 -16
  97. package/src/mcp/tools/execution.ts +38 -5
  98. package/src/mcp/tools/machine.ts +11 -2
  99. package/src/mcp/tools/media.ts +18 -13
  100. package/src/mcp/tools/memory.ts +17 -8
  101. package/src/mcp/tools/video.ts +9 -9
  102. package/src/native/discover.ts +2 -2
  103. package/src/native/ghidra/analyze.ts +2 -2
  104. package/src/native/ghidra/index.ts +1 -1
  105. package/src/native/ghidra/language/c64rt_6510.slaspec +4 -1
  106. package/src/native/ghidra/scripts/C64Export.java +1 -1
  107. package/src/native/ghidra/scripts/C64Prepare.java +7 -1
  108. package/src/native/types.ts +14 -8
  109. package/{skills/c64-source/scripts → src}/opcodes.ts +75 -3
  110. package/src/platform.ts +70 -7
  111. package/src/project.ts +180 -3
  112. package/src/protocol/messages.ts +1 -1
  113. package/src/protocol/tools.ts +2 -16
  114. package/src/protocol/vice.ts +124 -15
  115. package/src/script.ts +46 -7
  116. package/skills/c64-assembler/scripts/compare.ts +0 -84
  117. package/skills/c64-source/scripts/source.ts +0 -595
package/src/host/main.ts CHANGED
@@ -7,6 +7,7 @@ import { ProcessSupervisor } from "../native/processes.ts";
7
7
  import { startTrace, trace } from "../trace.ts";
8
8
  import { ListenError, parsePort, startHostServer, stopSignal } from "./server.ts";
9
9
  import { viceFlaws, type ViceVersion } from "../native/discover.ts";
10
+ import { inspect } from "./tools/c1541.ts";
10
11
  import { createToolDispatcher } from "./tools/index.ts";
11
12
  import { checkViceStarts, findVice } from "./vice/process.ts";
12
13
  import { viceSessionFactory } from "./vice/session.ts";
@@ -34,8 +35,9 @@ Options:
34
35
  directory, with a copy of each VICE log.
35
36
  --help Show this help.
36
37
 
37
- Without --log-console and --log-dir the host logs only its start, its stop,
38
- connections and failures. C64RT_TRACE does not apply to the host.`;
38
+ The host always logs its start and stop, connections, failures, and each call
39
+ from a skill script (c1541, petcat) with its outcome. --log-console adds the
40
+ calls of the MCP server and the tool runs. C64RT_TRACE does not apply to the host.`;
39
41
 
40
42
  function log(line: string): void {
41
43
  process.stderr.write(`c64-re-tools-host: ${line}\n`);
@@ -76,6 +78,9 @@ async function main(): Promise<number> {
76
78
  return 2;
77
79
  }
78
80
 
81
+ // First, so that every start, also a refused one, tells which runtime runs.
82
+ log(`${PACKAGE.name} ${PACKAGE.version} (Node.js ${process.version}, ${process.platform} ${process.arch})`);
83
+
79
84
  // Without an emulator the runtime is useless: refuse to start, by name and with the remedy.
80
85
  try {
81
86
  findVice(process.env);
@@ -101,7 +106,7 @@ async function main(): Promise<number> {
101
106
  }
102
107
  log(`VICE ${vice.major}.${vice.minor}${vice.revision === undefined ? "" : ` r${vice.revision}`} starts`);
103
108
  const flaws = viceFlaws(vice);
104
- // A known VICE flaw is named, not worked around (D56).
109
+ // A known VICE flaw is named, not worked around.
105
110
  for (const flaw of flaws) log(`Known VICE flaw: ${flaw}`);
106
111
 
107
112
  let server;
@@ -110,7 +115,16 @@ async function main(): Promise<number> {
110
115
  host: "0.0.0.0",
111
116
  port,
112
117
  log,
113
- createViceSession: viceSessionFactory({ supervisor, log, flaws }),
118
+ createViceSession: viceSessionFactory({
119
+ supervisor,
120
+ log,
121
+ flaws,
122
+ // c1541 lists the disk, so an autostart index selects a program.
123
+ directory: async (imageType, image) => {
124
+ const { result } = await inspect({ action: "directory", imageType }, image, { supervisor, signal: new AbortController().signal, log });
125
+ return result.action === "directory" ? result.entries : [];
126
+ },
127
+ }),
114
128
  tools: createToolDispatcher({ supervisor, log, vice }),
115
129
  });
116
130
  } catch (error) {
@@ -4,7 +4,7 @@ import { encodeFrame, MessageReader } from "../protocol/framing.ts";
4
4
  import { checkHello, isOneOf, HEARTBEAT_TIMEOUT_MS, parseClientMessage, ProtocolError, type HostMessage, type ReceivedRequest, type VideoStandard, type WireError, WireFailure } from "../protocol/messages.ts";
5
5
  import { TOOL_OPERATIONS, validateToolParams, type ToolOperation, type ToolOperations } from "../protocol/tools.ts";
6
6
  import { attachmentCount, VICE_OPERATIONS, validateViceParams, type ViceOperation, type ViceOperations } from "../protocol/vice.ts";
7
- import { elapsedMs, trace } from "../trace.ts";
7
+ import { elapsedMs, summarize, trace } from "../trace.ts";
8
8
 
9
9
  /** One live VICE session, owned by one connection. */
10
10
  export interface ViceSessionHandle {
@@ -234,8 +234,15 @@ class Connection {
234
234
 
235
235
  async #answer(request: ReceivedRequest, attachments: Buffer[]): Promise<void> {
236
236
  const started = performance.now();
237
- const record = (outcome: Record<string, unknown>) =>
238
- trace().event("request", { connection: this.#id, op: request.op, id: request.id, params: request.params, attachments: attachments.length, ms: elapsedMs(started), ...outcome });
237
+ const record = (outcome: Record<string, unknown>) => {
238
+ const ms = elapsedMs(started);
239
+ trace().event("request", { connection: this.#id, op: request.op, id: request.id, params: request.params, attachments: attachments.length, ms, ...outcome });
240
+ // A call from a skill script (c1541, petcat, status) is always logged; the many calls of a VICE session only in the trace.
241
+ if (this.#isTool) {
242
+ const what = outcome.ok === true ? "ok" : `${String(outcome.code)}: ${String(outcome.message)}`;
243
+ this.#log(`${request.op} ${JSON.stringify(summarize(request.params) ?? {})}: ${what} (${ms} ms)`);
244
+ }
245
+ };
239
246
  try {
240
247
  let result: unknown;
241
248
  let replyAttachments: Buffer[] = [];
@@ -18,7 +18,7 @@ const TIMEOUT_MS = 30_000;
18
18
  const LISTING = "listing.txt";
19
19
  /**
20
20
  * petcat's listing lines (" 10 sys2061"), as number and text. petcat
21
- * prints π ($ff) as `~`, which is no PETSCII character: the text has π (F-13).
21
+ * prints π ($ff) as `~`, which is no PETSCII character: the text has π.
22
22
  */
23
23
  export function parseListing(listing: string): Array<{ number: number; text: string }> {
24
24
  const lines: Array<{ number: number; text: string }> = [];
@@ -38,7 +38,7 @@ export async function hostStatus(context: ToolContext, started?: ViceVersion): P
38
38
  );
39
39
  const tools = started === undefined ? probed : [{ name: VICE.name, found: true, path: findVice(context.env ?? process.env), version: versionText(started), runs: true }, ...probed];
40
40
  const checked = tools.map((status, index) => requireMinimum(status, specs[index]!));
41
- // VICE runs with its known flaws; status names them (D56).
41
+ // VICE runs with its known flaws; status names them.
42
42
  const vice = checked[0]!;
43
43
  const version = vice.runs ? viceVersionOf(vice.version ?? "") : undefined;
44
44
  const flaws = version === undefined ? [] : viceFlaws(version);
@@ -4,7 +4,8 @@
4
4
  import { WireFailure, type VideoStandard } from "../../protocol/messages.ts";
5
5
  import { type Comparison, type Condition, type BacktraceFrame, type HistoryEntry, type Instruction, type MemmapRange, type ProfileEntry, type JoystickState, type MemoryView, type RegisterValues, type Registers, type Space } from "../../protocol/vice.ts";
6
6
  import { COVERAGE, formatC64Address, formatC64Byte } from "../../c64.ts";
7
- import { decodeDisplay, decodePalette, visibleFrame, type IndexedFrame } from "./screen.ts";
7
+ import type { IndexedFrame } from "../../graphics.ts";
8
+ import { decodeDisplay, decodePalette, visibleFrame } from "./screen.ts";
8
9
  import type { TextMonitor } from "./text-monitor.ts";
9
10
  import type { CiaHidden } from "./chips.ts";
10
11
  import {
@@ -260,7 +261,7 @@ function unknownForm(what: string, lines: string[]): WireFailure {
260
261
  return new WireFailure("operation-failed", `VICE printed the ${what} in a form that c64-re-tools does not read: ${JSON.stringify(lines.slice(0, 3))}`);
261
262
  }
262
263
 
263
- /** The coverage byte (D40) of one memmap row's I/O, ROM and RAM columns, each "rwx" with "-" for no access. */
264
+ /** The coverage byte of one memmap row's I/O, ROM and RAM columns, each "rwx" with "-" for no access. */
264
265
  export function memmapFlags(io: string, rom: string, ram: string): number {
265
266
  let flags = 0;
266
267
  if (ram[0] === "r") flags |= COVERAGE.ramRead;
@@ -434,7 +435,7 @@ export class ViceAdapter {
434
435
 
435
436
  /**
436
437
  * The timer latches and the interrupt mask of the CIA at `base`, from VICE's
437
- * text-monitor dump: no register read gives them (D63).
438
+ * text-monitor dump: no register read gives them.
438
439
  */
439
440
  async ciaHidden(base: 0xdc00 | 0xdd00): Promise<CiaHidden> {
440
441
  const answer = await this.#computerText(`io ${hex4(base)}`);
@@ -12,7 +12,7 @@
12
12
  // - autostart of a PRG needs `-autostartprgmode 1` (inject into RAM);
13
13
  // - the joyport command reaches the C64 only through the "Joyport I/O
14
14
  // simulation" device (37). In VICE 3.10 and older it holds bits 5-7 of
15
- // both CIA 1 ports low (VICE bug #2202, a known flaw named by D56);
15
+ // both CIA 1 ports low (VICE bug #2202, a known flaw);
16
16
  // - with the default JAM action a jammed CPU hangs silently; -jamaction 2
17
17
  // enters the monitor instead;
18
18
  // - the monitor's profiler prints numbers in the host locale, so VICE runs
@@ -97,7 +97,7 @@ export function viceArguments(options: {
97
97
  // Autostart injects a PRG into RAM; the default mode copies it to a disk image first.
98
98
  "-autostartprgmode",
99
99
  "1",
100
- // VICE's default waits a random time before an autostart types its LOAD, so two runs of one file differ (R2-12).
100
+ // VICE's default waits a random time before an autostart types its LOAD, so two runs of one file differ.
101
101
  "+autostart-delay-random",
102
102
  // The same RAM after every power-up: a hard reset is a power cycle, and VICE's default can flip
103
103
  // random bits, so two runs from one start state could differ.
@@ -1,18 +1,10 @@
1
1
  // Canonical emulator-frame capture: VICE's own rendered VIC-II frame, never a
2
2
  // desktop screenshot; the frame comparison and difference image for baselines.
3
3
 
4
- import { crc32, deflateSync } from "node:zlib";
5
-
4
+ import type { IndexedFrame } from "../../graphics.ts";
6
5
  import type { VideoStandard } from "../../protocol/messages.ts";
7
6
  import { FRAME_SIZE } from "../../protocol/vice.ts";
8
7
 
9
- export interface IndexedFrame {
10
- width: number;
11
- height: number;
12
- /** One palette index per pixel, row by row. */
13
- pixels: Uint8Array;
14
- }
15
-
16
8
  export interface RawDisplay {
17
9
  /** Full buffer size, blanking included. */
18
10
  bufferWidth: number;
@@ -83,39 +75,6 @@ export function decodePalette(body: Buffer): Array<[number, number, number]> {
83
75
  return colours;
84
76
  }
85
77
 
86
- function chunk(type: string, data: Uint8Array): Buffer {
87
- const length = Buffer.alloc(4);
88
- length.writeUInt32BE(data.length, 0);
89
- const body = Buffer.concat([Buffer.from(type, "latin1"), data]);
90
- const crc = Buffer.alloc(4);
91
- crc.writeUInt32BE(crc32(body), 0);
92
- return Buffer.concat([length, body, crc]);
93
- }
94
-
95
- /** Encodes an indexed frame as an 8-bit palette PNG. */
96
- export function encodePng(frame: IndexedFrame, palette: ReadonlyArray<readonly [number, number, number]>): Buffer {
97
- for (const index of frame.pixels) {
98
- if (index >= palette.length) throw new Error(`pixel colour ${index} is not in the ${palette.length}-colour palette`);
99
- }
100
- const header = Buffer.alloc(13);
101
- header.writeUInt32BE(frame.width, 0);
102
- header.writeUInt32BE(frame.height, 4);
103
- header[8] = 8; // bit depth
104
- header[9] = 3; // indexed colour
105
- const rows = Buffer.alloc((frame.width + 1) * frame.height);
106
- for (let row = 0; row < frame.height; row++) {
107
- // Filter byte 0 (none), then the row.
108
- rows.set(frame.pixels.subarray(row * frame.width, (row + 1) * frame.width), row * (frame.width + 1) + 1);
109
- }
110
- return Buffer.concat([
111
- Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
112
- chunk("IHDR", header),
113
- chunk("PLTE", Buffer.from(palette.flat())),
114
- chunk("IDAT", deflateSync(rows)),
115
- chunk("IEND", new Uint8Array(0)),
116
- ]);
117
- }
118
-
119
78
  export interface FrameDifference {
120
79
  mismatchingPixels: number;
121
80
  /** Pixels compared: the frame minus the masked ones. */
@@ -3,14 +3,17 @@
3
3
  // session starts headless and can move the machine into a VICE with a window
4
4
  // and back; breakpoints, watchpoints and the rest go along.
5
5
 
6
- import { WireFailure, type VideoStandard } from "../../protocol/messages.ts";
7
- import { MAX_BASELINES, MAX_SNAPSHOTS, MAX_COMPARE_DIFFERENCES, RASTER, type Breakpoint, type BreakpointParams, type PointRemoved, type Condition, type ExecutionParams, type HistoryEntry, type RunTarget, type RunUntilResult, type ScreenComparison, type CiaSelection, type CiaState, type ObserveParams, type ObserveResult, type SidState, type SpriteState, type ViciiState, type JoystickState, type Watchpoint, type WatchpointParams, type ExecutionResult, type MachineStatus, type RunState, type ViceOperation, type ViceOperations, type WindowResult } from "../../protocol/vice.ts";
6
+ import { isOneOf, WireFailure, type VideoStandard } from "../../protocol/messages.ts";
7
+ import { DISK_IMAGE_TYPES, type DiskFile, type DiskImageType } from "../../protocol/tools.ts";
8
+ import { MAX_BASELINES, MAX_SNAPSHOTS, MAX_COMPARE_DIFFERENCES, RASTER, type Breakpoint, type BreakpointParams, type PointRemoved, type Condition, type ExecutionParams, type HistoryEntry, type RunTarget, type RunUntilResult, type ScreenComparison, type CiaSelection, type CiaState, type ObserveParams, type ObserveResult, type SidState, type SpriteState, type ViciiState, type JoystickState, type Watchpoint, type WatchpointParams, type ExecutionResult, type MachineStatus, type TraceHit, type TraceKind, type TraceResult, type PointStopReason, type RunState, type ViceOperation, type ViceOperations, type WindowResult } from "../../protocol/vice.ts";
8
9
  import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
9
10
  import { tmpdir } from "node:os";
10
11
  import { join } from "node:path";
11
12
  import { setTimeout as sleep } from "node:timers/promises";
12
13
 
13
- import { formatC64Address } from "../../c64.ts";
14
+ import { formatC64Address, instructionLength } from "../../c64.ts";
15
+ import { encodePng, type IndexedFrame } from "../../graphics.ts";
16
+ import { instructionText, operandAddress } from "../../opcodes.ts";
14
17
  import type { ProcessSupervisor } from "../../native/processes.ts";
15
18
  import { trace } from "../../trace.ts";
16
19
  import type { ViceSessionFactory, ViceSessionHandle } from "../server.ts";
@@ -26,7 +29,7 @@ import {
26
29
  } from "./adapter.ts";
27
30
  import { decodeProgramCounter, MonitorConnectionError, MonitorError, MonitorTimeoutError, ResponseType, type MonitorResponse } from "./binary-monitor.ts";
28
31
  import { decodeCia, decodeSid, decodeSprite, decodeVicii } from "./chips.ts";
29
- import { compareFrames, differenceImage, encodePng, type IndexedFrame } from "./screen.ts";
32
+ import { compareFrames, differenceImage } from "./screen.ts";
30
33
  import { launchVice, startErrorLines, type ViceMode, type ViceProcess } from "./process.ts";
31
34
  import { TextMonitorError, TextMonitorTimeoutError } from "./text-monitor.ts";
32
35
 
@@ -121,10 +124,14 @@ export interface SessionOptions {
121
124
  launch?: ViceLauncher;
122
125
  /** Makes the host remove a path if it dies; returns the release. */
123
126
  ownPath?: (path: string) => () => void;
124
- /** The known flaws of this VICE version (D56); keyboard and joystick results name them. */
127
+ /** The known flaws of this VICE version; keyboard and joystick results name them. */
125
128
  flaws?: readonly string[];
129
+ /** Lists the entries of a disk image, so an autostart index is checked first. Without it, VICE gets the index unchecked. */
130
+ directory?: DiskDirectory;
126
131
  }
127
132
 
133
+ export type DiskDirectory = (type: DiskImageType, image: Buffer) => Promise<DiskFile[]>;
134
+
128
135
  /**
129
136
  * Where VICE keeps a staged file: an autostarted disk stays in drive 8, a tape
130
137
  * in the datasette and a cartridge in the port, until the same place gets
@@ -151,6 +158,7 @@ export class ViceSession implements ViceSessionHandle {
151
158
  readonly #untilReturnLimitMs: number;
152
159
  readonly #stepLimitMs: number;
153
160
  readonly #launch: ViceLauncher | undefined;
161
+ readonly #directory: DiskDirectory | undefined;
154
162
  /** Session files that outlive one VICE: snapshots, staged media, handovers. */
155
163
  readonly #workDir: string;
156
164
  readonly #releaseWorkDir: () => void;
@@ -177,6 +185,8 @@ export class ViceSession implements ViceSessionHandle {
177
185
  #driveStop = false;
178
186
  /** Counts stops caused by checkpoints or a CPU JAM rather than by a monitor command. */
179
187
  #haltCount = 0;
188
+ /** What stopped the machine last, when a user breakpoint or watchpoint or a JAM did; cleared when it runs again. */
189
+ #lastStop: { stopReason: PointStopReason; pointId?: number } | undefined;
180
190
  #queue: Promise<unknown> = Promise.resolve();
181
191
  #closed = false;
182
192
  #lost = false;
@@ -218,6 +228,7 @@ export class ViceSession implements ViceSessionHandle {
218
228
  this.#untilReturnLimitMs = options.untilReturnLimitMs ?? UNTIL_RETURN_LIMIT_MS;
219
229
  this.#stepLimitMs = options.stepLimitMs ?? RUNNING_COMMAND_TIMEOUT_MS;
220
230
  this.#launch = options.launch;
231
+ this.#directory = options.directory;
221
232
  this.#inputFlaw = options.flaws === undefined || options.flaws.length === 0 ? undefined : options.flaws.join(" ");
222
233
  this.#workDir = mkdtempSync(join(tmpdir(), "c64-re-tools-session-"));
223
234
  this.#releaseWorkDir = options.ownPath?.(this.#workDir) ?? (() => {});
@@ -374,14 +385,16 @@ export class ViceSession implements ViceSessionHandle {
374
385
  return this.#programLoad(params as ViceOperations["programLoad"]["params"], onlyAttachment(attachments));
375
386
  case "autostart": {
376
387
  const { type, index, run } = params as ViceOperations["autostart"]["params"];
377
- const file = this.#stage(onlyAttachment(attachments), type);
388
+ const bytes = onlyAttachment(attachments);
389
+ if (index > 0 && this.#directory !== undefined && isOneOf(DISK_IMAGE_TYPES, type)) checkDirectoryEntry(await this.#directory(type, bytes), index);
390
+ const file = this.#stage(bytes, type);
378
391
  // VICE keeps a cartridge through every reset and autostart, as a real C64 does.
379
392
  if (type !== "crt" && this.#cartridge) {
380
393
  await this.#observe(() => this.#machine.detachCartridge());
381
394
  this.#cartridge = false;
382
395
  this.#replaceStaged("cartridge", undefined);
383
396
  }
384
- // Port 1 shares the keyboard lines: a held direction types keys into the RUN that the autostart types (R3-05).
397
+ // Port 1 shares the keyboard lines: a held direction types keys into the RUN that the autostart types.
385
398
  const centred: Array<1 | 2> = [];
386
399
  for (const [port, joystick] of this.#joysticks) {
387
400
  if (joystick.direction === "center" && !joystick.fire) continue;
@@ -445,6 +458,8 @@ export class ViceSession implements ViceSessionHandle {
445
458
  return this.#snapshotFile(params as ViceOperations["snapshotFile"]["params"], attachments, reply);
446
459
  case "cpuHistory":
447
460
  return this.#cpuHistory(params as ViceOperations["cpuHistory"]["params"]);
461
+ case "trace":
462
+ return this.#trace(params as ViceOperations["trace"]["params"]);
448
463
  case "backtrace": {
449
464
  const { depth, space } = params as ViceOperations["backtrace"]["params"];
450
465
  return this.#observe(async () => {
@@ -462,8 +477,12 @@ export class ViceSession implements ViceSessionHandle {
462
477
  return { cycles: (now - this.#timingStart).toString() };
463
478
  }
464
479
  case "profile": {
465
- const { limit } = params as ViceOperations["profile"]["params"];
466
- return { entries: await this.#observe(() => this.#machine.profile(limit)) };
480
+ const request = params as ViceOperations["profile"]["params"];
481
+ if (request.action === "clear") {
482
+ await this.#observe(() => this.#machine.startProfiler());
483
+ return { cleared: true };
484
+ }
485
+ return { entries: await this.#observe(() => this.#machine.profile(request.limit)) };
467
486
  }
468
487
  case "memmap": {
469
488
  const request = params as ViceOperations["memmap"]["params"];
@@ -508,7 +527,12 @@ export class ViceSession implements ViceSessionHandle {
508
527
  const warp = await this.#observe(() => this.#machine.warp());
509
528
  // Read after #observe: it resumes a running machine, unless a breakpoint or a JAM stopped it meanwhile.
510
529
  const status: MachineStatus = { state: this.#state, videoStandard: this.#videoStandard, warp, window: this.#mode === "window", cartridge: this.#cartridge };
511
- if (status.state === "stopped") status.pc = await this.#pc();
530
+ if (status.state === "stopped") {
531
+ status.pc = await this.#pc();
532
+ // VICE 3.9 sends no JAM event: the opcode at the pc tells.
533
+ const stop = this.#lastStop ?? ((await this.#jammedAt(status.pc)) ? { stopReason: "jam" as const } : undefined);
534
+ if (stop !== undefined) Object.assign(status, stop);
535
+ }
512
536
  return status;
513
537
  }
514
538
 
@@ -539,7 +563,13 @@ export class ViceSession implements ViceSessionHandle {
539
563
  throw new WireFailure("invalid-input", `The anchor is outside the frame: a ${this.#videoStandard} frame has lines 0-${raster.lines - 1} and cycles 0-${raster.cycles - 1}.`);
540
564
  }
541
565
  const outcome = await this.#runFrames(params.count, undefined, params.anchor);
542
- return { state: "stopped", pc: outcome.pc, advancedFrames: outcome.frames };
566
+ return {
567
+ state: "stopped",
568
+ pc: outcome.pc,
569
+ advancedFrames: outcome.frames,
570
+ stopReason: outcome.end === "target" ? "frames" : outcome.end,
571
+ ...(outcome.pointId === undefined ? {} : { pointId: outcome.pointId }),
572
+ };
543
573
  }
544
574
  await this.#stop();
545
575
  if (params.action === "until-return") {
@@ -600,7 +630,7 @@ export class ViceSession implements ViceSessionHandle {
600
630
  * checkpoint, a user breakpoint or watchpoint, or a JAM ends the run early.
601
631
  * The machine is stopped afterwards.
602
632
  */
603
- async #runFrames(limit: number, target?: { checkpoint: number; deferred: boolean }, anchor?: { line: number; cycle: number }): Promise<{ end: RunEnd; frames: number; pc: number; space: "c64" | "drive8" }> {
633
+ async #runFrames(limit: number, target?: { checkpoint: number; deferred: boolean }, anchor?: { line: number; cycle: number }): Promise<{ end: RunEnd; frames: number; pc: number; space: "c64" | "drive8"; pointId?: number }> {
604
634
  await this.#stop();
605
635
  const lines = RASTER[this.#videoStandard].lines;
606
636
  // A frame ends where the raster passes the anchor; without one, where this run starts.
@@ -642,13 +672,127 @@ export class ViceSession implements ViceSessionHandle {
642
672
  }
643
673
  // A drive stop is completed before the result names the computer's pc; `space` says which CPU stopped.
644
674
  const driveStop = this.#driveStop;
645
- return { end, frames, pc: (await this.#completeDriveStop()) ?? stop.pc, space: driveStop ? "drive8" : "c64" };
675
+ const pointId = end === "target" ? undefined : this.#userPoint(stop.hits)?.pointId;
676
+ return { end, frames, pc: (await this.#completeDriveStop()) ?? stop.pc, space: driveStop ? "drive8" : "c64", ...(pointId === undefined ? {} : { pointId }) };
646
677
  }
647
678
  } finally {
648
679
  await this.#machine.deleteCheckpoint(clock);
649
680
  }
650
681
  }
651
682
 
683
+ /**
684
+ * A trace: runs the machine for up to `frames` frames, counted as
685
+ * #runFrames counts them, with a stopping checkpoint for each point. At each
686
+ * hit the session records the instruction, its registers and the raster
687
+ * position, and runs on. It ends at the frame limit, at `maxHits` hits, or
688
+ * where a user point or a JAM stops the program. VICE also reports the hits
689
+ * of a checkpoint that does not stop, but without the PC, the registers or
690
+ * the raster position, so each point stops.
691
+ */
692
+ async #trace(params: ViceOperations["trace"]["params"]): Promise<TraceResult> {
693
+ await this.#stop();
694
+ const lines = RASTER[this.#videoStandard].lines;
695
+ const start = await this.#machine.rasterPosition();
696
+ const arming = rasterLinesExpression((start.line + Math.floor(lines / 2)) % lines, lines);
697
+ const ending = rasterWindowExpression(start.line, start.cycle, lines);
698
+ const operation: Record<TraceKind, number> = { exec: 0x04, read: 0x01, write: 0x02 };
699
+ const points: number[] = [];
700
+ const hits: TraceHit[] = [];
701
+ let frames = 0;
702
+ const clock = await this.#machine.addCheckpoint({ start: 0x0000, end: 0xffff, operation: 0x04, space: "c64" });
703
+ try {
704
+ for (const point of params.points) points.push(await this.#machine.addCheckpoint({ start: point.start, end: point.end, operation: operation[point.kind], space: "c64" }));
705
+ await this.#machine.setCondition(clock, arming);
706
+ let armingPhase = true;
707
+ for (;;) {
708
+ const next = this.#nextStop(HALF_FRAME_LIMIT_MS);
709
+ await this.#resume();
710
+ const stop = await next;
711
+ if (stop === undefined) {
712
+ await this.#stop();
713
+ throw new WireFailure("operation-failed", "The emulator did not reach the next frame in time.");
714
+ }
715
+ const traced = points.flatMap((checkpoint, index) => (stop.hits.includes(checkpoint) ? [index] : []));
716
+ for (const index of traced) {
717
+ hits.push(await this.#traceHit(index, params.points[index]!.kind, frames));
718
+ if (hits.length >= params.maxHits) return { hits, frames, end: "hits", pc: stop.pc };
719
+ }
720
+ if (stop.hits.includes(clock)) {
721
+ if (armingPhase) await this.#machine.setCondition(clock, ending);
722
+ else {
723
+ frames++;
724
+ if (frames >= params.frames) return { hits, frames, end: "frames", pc: stop.pc };
725
+ await this.#machine.setCondition(clock, arming);
726
+ }
727
+ armingPhase = !armingPhase;
728
+ continue;
729
+ }
730
+ if (traced.length > 0) continue;
731
+ const point = this.#userPoint(stop.hits);
732
+ if (point !== undefined) return { hits, frames, end: point.stopReason, pc: stop.pc };
733
+ if (await this.#jammedAt(stop.pc)) return { hits, frames, end: "jam", pc: stop.pc };
734
+ throw new WireFailure("operation-failed", "The emulator stopped the machine for a reason outside this trace.");
735
+ }
736
+ } finally {
737
+ await this.#machine.deleteCheckpoint(clock);
738
+ for (const checkpoint of points) await this.#machine.deleteCheckpoint(checkpoint);
739
+ }
740
+ }
741
+
742
+ /**
743
+ * One hit of a trace point. An exec point stops before its instruction: the
744
+ * registers and the raster position are the ones of now. A read or write
745
+ * point stops after the instruction that made the access: the last entry of
746
+ * the CPU history is that instruction, with its registers and its clock.
747
+ */
748
+ async #traceHit(point: number, kind: TraceKind, frame: number): Promise<TraceHit> {
749
+ const position = await this.#machine.rasterPosition();
750
+ const byteAt = async (address: number) => Number.parseInt(await this.#machine.readMemory({ address, size: 1, space: "c64", view: "cpu" }), 16);
751
+ if (kind === "exec") {
752
+ const registers = await this.#machine.readRegisters("c64");
753
+ const bytes = [...Buffer.from(await this.#machine.readMemory({ address: registers.pc, size: 3, space: "c64", view: "cpu" }), "hex")];
754
+ const length = instructionLength(bytes[0]!);
755
+ return {
756
+ point,
757
+ pc: registers.pc,
758
+ bytes: Buffer.from(bytes.slice(0, length)).toString("hex"),
759
+ text: instructionText(bytes, registers.pc),
760
+ a: registers.a,
761
+ x: registers.x,
762
+ y: registers.y,
763
+ sp: registers.sp,
764
+ frame,
765
+ rasterLine: position.line,
766
+ rasterCycle: position.cycle,
767
+ };
768
+ }
769
+ const [entry] = await this.#machine.history(1, "c64");
770
+ if (entry === undefined) throw new WireFailure("operation-failed", "The emulator gave no CPU history at a trace hit.");
771
+ const now = await this.#machine.clock();
772
+ const { lines, cycles } = RASTER[this.#videoStandard];
773
+ const span = BigInt(lines * cycles);
774
+ const at = Number((((BigInt(position.line * cycles + position.cycle) - (now - entry.clock)) % span) + span) % span);
775
+ const bytes = [...Buffer.from(entry.bytes, "hex")];
776
+ // A zero-page pointer is read now: the instruction did not change it.
777
+ const pointer = new Map<number, number>();
778
+ for (const address of [bytes[1] ?? 0, ((bytes[1] ?? 0) + 1) & 0xff, ((bytes[1] ?? 0) + entry.x) & 0xff, ((bytes[1] ?? 0) + entry.x + 1) & 0xff]) pointer.set(address, await byteAt(address));
779
+ const address = operandAddress(bytes, entry.x, entry.y, (zp) => pointer.get(zp) ?? 0);
780
+ return {
781
+ point,
782
+ pc: entry.address,
783
+ bytes: entry.bytes,
784
+ text: instructionText(bytes, entry.address),
785
+ a: entry.a,
786
+ x: entry.x,
787
+ y: entry.y,
788
+ sp: entry.sp,
789
+ frame,
790
+ rasterLine: Math.floor(at / cycles),
791
+ rasterCycle: at % cycles,
792
+ ...(address === undefined ? {} : { address, value: await byteAt(address) }),
793
+ };
794
+ }
795
+
652
796
  /**
653
797
  * Completes a stop that a drive 8 checkpoint caused: the computer runs to
654
798
  * its next instruction boundary, where a computer checkpoint stops it, so
@@ -659,6 +803,8 @@ export class ViceSession implements ViceSessionHandle {
659
803
  async #completeDriveStop(): Promise<number | undefined> {
660
804
  if (!this.#driveStop) return undefined;
661
805
  this.#driveStop = false;
806
+ // The drive checkpoint stopped the machine; the stop at the boundary keeps that reason.
807
+ const reason = this.#lastStop;
662
808
  const boundary = await this.#machine.addCheckpoint({ start: 0x0000, end: 0xffff, operation: 0x04, space: "c64" });
663
809
  try {
664
810
  for (let attempt = 0; attempt < DRIVE_STOP_TRIES; attempt++) {
@@ -669,6 +815,7 @@ export class ViceSession implements ViceSessionHandle {
669
815
  // The drive can hit its checkpoint again before the computer gets to the next instruction.
670
816
  if (stop.hits.includes(boundary)) {
671
817
  this.#driveStop = false;
818
+ this.#lastStop = reason;
672
819
  trace().event("vice.drive-stop", { pc: stop.pc, resumes: attempt + 1 });
673
820
  return stop.pc;
674
821
  }
@@ -683,14 +830,20 @@ export class ViceSession implements ViceSessionHandle {
683
830
  /** Names what caused a stop during a frame-clocked run. */
684
831
  #stopCause(stop: Stop, clock: number, target: number | undefined): Exclude<RunEnd, "frames" | "jam"> | "clock" | "unknown" {
685
832
  if (target !== undefined && stop.hits.includes(target)) return "target";
686
- const isPoint = (registry: Map<number, { checkpoint: number }>) =>
687
- [...registry.values()].some((point) => stop.hits.includes(point.checkpoint));
688
- if (isPoint(this.#breakpoints)) return "breakpoint";
689
- if (isPoint(this.#watchpoints)) return "watchpoint";
833
+ const point = this.#userPoint(stop.hits);
834
+ if (point !== undefined) return point.stopReason;
690
835
  if (stop.hits.includes(clock)) return "clock";
691
836
  return "unknown";
692
837
  }
693
838
 
839
+ /** The user breakpoint, else the user watchpoint, among the hit VICE checkpoints. */
840
+ #userPoint(hits: number[]): { stopReason: "breakpoint" | "watchpoint"; pointId: number } | undefined {
841
+ for (const [stopReason, registry] of [["breakpoint", this.#breakpoints], ["watchpoint", this.#watchpoints]] as const) {
842
+ for (const [pointId, point] of registry) if (hits.includes(point.checkpoint)) return { stopReason, pointId };
843
+ }
844
+ return undefined;
845
+ }
846
+
694
847
  async #jammedAt(pc: number): Promise<boolean> {
695
848
  const opcode = await this.#machine.readMemory({ address: pc, size: 1, space: "c64", view: "cpu" });
696
849
  return JAM_OPCODES.has(Number.parseInt(opcode, 16));
@@ -734,9 +887,9 @@ export class ViceSession implements ViceSessionHandle {
734
887
  if (expression !== undefined) await this.#machine.setCondition(checkpoint, expression);
735
888
  const outcome = await this.#runFrames(params.timeoutFrames, { checkpoint, deferred });
736
889
  const stopReason = outcome.end === "frames" ? "timeout" : outcome.end;
737
- // A stop event carries the computer's PC; a stop in the drive reports the drive's (F-31).
890
+ // A stop event carries the computer's PC; a stop in the drive reports the drive's.
738
891
  const pc = outcome.space === "drive8" ? (await this.#machine.readRegisters("drive8")).pc : outcome.pc;
739
- return { reached: stopReason === "target", stopReason, state: "stopped", pc, space: outcome.space };
892
+ return { reached: stopReason === "target", stopReason, state: "stopped", pc, space: outcome.space, ...(outcome.pointId === undefined ? {} : { pointId: outcome.pointId }) };
740
893
  } finally {
741
894
  await this.#machine.deleteCheckpoint(checkpoint);
742
895
  }
@@ -801,7 +954,7 @@ export class ViceSession implements ViceSessionHandle {
801
954
  if (params.action === "list") return { breakpoints: [...this.#breakpoints.values()].map(publicView) };
802
955
  if (params.action === "remove") return this.#removePoint(this.#breakpoints, "breakpoint", params.id);
803
956
  if (params.action !== "add") return publicView(await this.#changePoint(this.#breakpoints, "breakpoint", params));
804
- // A breakpoint over a range stops at the first instruction executed in it: exec, not a watch (D43).
957
+ // A breakpoint over a range stops at the first instruction executed in it: exec, not a watch.
805
958
  const added = await this.#addPoint({
806
959
  ...(params.size === undefined ? pointRange({ address: params.address }) : { start: params.address, end: params.address + params.size - 1, operation: 0x04 }),
807
960
  space: params.space,
@@ -1002,7 +1155,7 @@ export class ViceSession implements ViceSessionHandle {
1002
1155
  return { restored: true, state: "stopped" };
1003
1156
  }
1004
1157
 
1005
- /** A snapshot as bytes (D41): export a named one into `reply`, or restore from the attached .vsf. */
1158
+ /** A snapshot as bytes: export a named one into `reply`, or restore from the attached .vsf. */
1006
1159
  async #snapshotFile(params: ViceOperations["snapshotFile"]["params"], attachments: Buffer[], reply: Buffer[]): Promise<ViceOperations["snapshotFile"]["result"]> {
1007
1160
  if (params.action === "export") {
1008
1161
  const file = this.#snapshots.get(params.name);
@@ -1012,7 +1165,7 @@ export class ViceSession implements ViceSessionHandle {
1012
1165
  return { exported: true, bytes: bytes.length };
1013
1166
  }
1014
1167
  const bytes = onlyAttachment(attachments);
1015
- // Checked before the machine stops: VICE gives no reason for a file that is no snapshot (F-07).
1168
+ // Checked before the machine stops: VICE gives no reason for a file that is no snapshot.
1016
1169
  if (!bytes.subarray(0, VSF_MAGIC.length).equals(VSF_MAGIC)) throw new WireFailure("invalid-input", "The file is not a VICE snapshot file (.vsf). Give a file that c64_snapshot save wrote.");
1017
1170
  const file = this.#stage(bytes, "vsf");
1018
1171
  try {
@@ -1185,6 +1338,7 @@ export class ViceSession implements ViceSessionHandle {
1185
1338
  */
1186
1339
  async #resumingCommand(send: () => Promise<void>): Promise<void> {
1187
1340
  this.#state = "running";
1341
+ this.#lastStop = undefined;
1188
1342
  try {
1189
1343
  await send();
1190
1344
  } catch (error) {
@@ -1360,10 +1514,12 @@ export class ViceSession implements ViceSessionHandle {
1360
1514
  const stop: Stop = { pc: decodeProgramCounter(event.body), hits: this.#checkpointHits };
1361
1515
  if (stop.hits.length > 0 || event.type === ResponseType.jam) this.#haltCount++;
1362
1516
  if (stop.hits.some((hit) => this.#adapter?.isDriveCheckpoint(hit))) this.#driveStop = true;
1517
+ this.#lastStop = event.type === ResponseType.jam ? { stopReason: "jam" } : this.#userPoint(stop.hits);
1363
1518
  this.#checkpointHits = [];
1364
1519
  this.#state = "stopped";
1365
1520
  this.#stopWaiters = this.#stopWaiters.filter((waiter) => !waiter(stop));
1366
1521
  } else if (event.type === ResponseType.resumed) {
1522
+ this.#lastStop = undefined;
1367
1523
  this.#state = "running";
1368
1524
  }
1369
1525
  }
@@ -1400,6 +1556,17 @@ async function setPoint(
1400
1556
  return checkpoint;
1401
1557
  }
1402
1558
 
1559
+ /**
1560
+ * Refuses an autostart index that selects no program: VICE numbers the
1561
+ * entries from 1, and the caller from 0, as the c64-disk directory list does.
1562
+ * A DEL entry holds no program, and an index past the last entry selects nothing.
1563
+ */
1564
+ function checkDirectoryEntry(entries: DiskFile[], index: number): void {
1565
+ const entry = entries[index - 1];
1566
+ if (entry === undefined) throw new WireFailure("invalid-input", `The disk has ${entries.length} directory entries, so index ${index - 1} selects no entry. The c64-disk directory list gives the index of each entry.`);
1567
+ if (entry.type === "del") throw new WireFailure("invalid-input", `Directory entry ${index - 1} (${entry.name}) is a DEL entry and holds no program. Give the index of a PRG entry.`);
1568
+ }
1569
+
1403
1570
  /** The production factory: launch a headless VICE per session through the host's supervisor. */
1404
1571
  export function viceSessionFactory(options: {
1405
1572
  supervisor: ProcessSupervisor;
@@ -1407,6 +1574,7 @@ export function viceSessionFactory(options: {
1407
1574
  log?: (line: string) => void;
1408
1575
  /** The known flaws of the VICE version that the host checked at start. */
1409
1576
  flaws?: readonly string[];
1577
+ directory?: DiskDirectory;
1410
1578
  }): ViceSessionFactory {
1411
1579
  return async ({ videoStandard, log = options.log ?? (() => {}) }) => {
1412
1580
  const launch: ViceLauncher = (mode, launchLog) =>
@@ -1421,6 +1589,11 @@ export function viceSessionFactory(options: {
1421
1589
  ...(options.env === undefined ? {} : { env: options.env }),
1422
1590
  });
1423
1591
  const vice = await launch("headless", () => {});
1424
- return ViceSession.start(vice, videoStandard, log, { launch, ownPath: (path) => options.supervisor.ownPath(path), ...(options.flaws === undefined ? {} : { flaws: options.flaws }) });
1592
+ return ViceSession.start(vice, videoStandard, log, {
1593
+ launch,
1594
+ ownPath: (path) => options.supervisor.ownPath(path),
1595
+ ...(options.flaws === undefined ? {} : { flaws: options.flaws }),
1596
+ ...(options.directory === undefined ? {} : { directory: options.directory }),
1597
+ });
1425
1598
  };
1426
1599
  }
@@ -42,7 +42,7 @@ export async function runProgram(client: ViceSessionClient, path: string, prg: U
42
42
  /**
43
43
  * Types RUN at the READY prompt and stops where BASIC starts the first line,
44
44
  * before its SYS. Every tool that starts a loaded program this way reaches
45
- * its entry at the same raster line and timer values (D62). False when BASIC
45
+ * its entry at the same raster line and timer values. False when BASIC
46
46
  * runs no line within RUN_LIMIT_FRAMES.
47
47
  */
48
48
  export async function typeRun(client: ViceSessionClient): Promise<boolean> {
@@ -37,7 +37,7 @@ export function hostEndpoints(env: NodeJS.ProcessEnv = process.env): Endpoint[]
37
37
  return ["127.0.0.1", "host.docker.internal", "host.containers.internal"].map((host) => ({ host, port: DEFAULT_HOST_PORT }));
38
38
  }
39
39
 
40
- /** A host that does not run or cannot be reached is machine-unavailable for every role (D37). */
40
+ /** A host that does not run or cannot be reached is machine-unavailable for every role. */
41
41
  function unreachable(): WireFailure {
42
42
  return new WireFailure(
43
43
  "machine-unavailable",