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

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 (118) 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 +14 -5
  63. package/src/cli/status.ts +70 -3
  64. package/src/graphics.ts +158 -0
  65. package/src/host/main.ts +25 -5
  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 +8 -2
  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/package.ts +13 -3
  111. package/src/platform.ts +70 -7
  112. package/src/project.ts +180 -3
  113. package/src/protocol/messages.ts +1 -1
  114. package/src/protocol/tools.ts +2 -16
  115. package/src/protocol/vice.ts +124 -15
  116. package/src/script.ts +46 -7
  117. package/skills/c64-assembler/scripts/compare.ts +0 -84
  118. package/skills/c64-source/scripts/source.ts +0 -595
@@ -0,0 +1,158 @@
1
+ // Indexed images: the PNG encoder of emulator frames, and the C64 graphics
2
+ // in memory as images for the documentation. A region of the knowledge
3
+ // gives no display mode and no colours, so a sprite, a bitmap or a charset is read
4
+ // both in hires and in multicolour, with fixed pattern palettes.
5
+
6
+ import { crc32, deflateSync } from "node:zlib";
7
+
8
+ export interface IndexedFrame {
9
+ width: number;
10
+ height: number;
11
+ /** One palette index per pixel, row by row. */
12
+ pixels: Uint8Array;
13
+ }
14
+
15
+ export type Rgb = readonly [number, number, number];
16
+
17
+ function chunk(type: string, data: Uint8Array): Buffer {
18
+ const length = Buffer.alloc(4);
19
+ length.writeUInt32BE(data.length, 0);
20
+ const body = Buffer.concat([Buffer.from(type, "latin1"), data]);
21
+ const crc = Buffer.alloc(4);
22
+ crc.writeUInt32BE(crc32(body), 0);
23
+ return Buffer.concat([length, body, crc]);
24
+ }
25
+
26
+ /** Encodes an indexed frame as an 8-bit palette PNG. */
27
+ export function encodePng(frame: IndexedFrame, palette: ReadonlyArray<Rgb>): Buffer {
28
+ for (const index of frame.pixels) {
29
+ if (index >= palette.length) throw new Error(`pixel colour ${index} is not in the ${palette.length}-colour palette`);
30
+ }
31
+ const header = Buffer.alloc(13);
32
+ header.writeUInt32BE(frame.width, 0);
33
+ header.writeUInt32BE(frame.height, 4);
34
+ header[8] = 8; // bit depth
35
+ header[9] = 3; // indexed colour
36
+ const rows = Buffer.alloc((frame.width + 1) * frame.height);
37
+ for (let row = 0; row < frame.height; row++) {
38
+ // Filter byte 0 (none), then the row.
39
+ rows.set(frame.pixels.subarray(row * frame.width, (row + 1) * frame.width), row * (frame.width + 1) + 1);
40
+ }
41
+ return Buffer.concat([
42
+ Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
43
+ chunk("IHDR", header),
44
+ chunk("PLTE", Buffer.from(palette.flat())),
45
+ chunk("IDAT", deflateSync(rows)),
46
+ chunk("IEND", new Uint8Array(0)),
47
+ ]);
48
+ }
49
+
50
+ export type GraphicsMode = "hires" | "multicolour";
51
+
52
+ /**
53
+ * The pattern palettes: the index is the pixel value (hires: the bit; multicolour:
54
+ * the bit pair, 0 = background $d021, 1 = $d022 or screen high nibble, 2 = $d023 or
55
+ * screen low nibble, 3 = colour RAM), and the last index is the gap between sprites.
56
+ */
57
+ export const PATTERN_PALETTES: Record<GraphicsMode, readonly Rgb[]> = {
58
+ hires: [
59
+ [0x00, 0x00, 0x00],
60
+ [0xff, 0xff, 0xff],
61
+ [0x35, 0x28, 0x79],
62
+ ],
63
+ multicolour: [
64
+ [0x00, 0x00, 0x00],
65
+ [0xad, 0xad, 0xad],
66
+ [0x6c, 0x6c, 0x6c],
67
+ [0xed, 0xf1, 0x71],
68
+ [0x35, 0x28, 0x79],
69
+ ],
70
+ };
71
+
72
+ /** The pixel values of one byte, left to right: 8 hires pixels, or 4 multicolour pixels of 2 each. */
73
+ function bytePixels(byte: number, mode: GraphicsMode): number[] {
74
+ if (mode === "hires") return Array.from({ length: 8 }, (_, bit) => (byte >> (7 - bit)) & 1);
75
+ return Array.from({ length: 8 }, (_, pixel) => (byte >> (6 - 2 * (pixel >> 1))) & 3);
76
+ }
77
+
78
+ export const SPRITE = { width: 24, height: 21, bytes: 63 } as const;
79
+
80
+ /**
81
+ * The sprites of `bytes` as one sheet, `perRow` sprites wide with a gap of one
82
+ * pixel. Each sprite takes `stride` bytes (64 in a VIC bank, 63 when packed).
83
+ */
84
+ export function spriteSheet(bytes: Uint8Array, mode: GraphicsMode, stride = 64, perRow = 8): IndexedFrame {
85
+ const count = Math.max(1, Math.floor((bytes.length + stride - SPRITE.bytes) / stride));
86
+ const columns = Math.min(count, perRow);
87
+ const rows = Math.ceil(count / perRow);
88
+ const width = columns * (SPRITE.width + 1) + 1;
89
+ const height = rows * (SPRITE.height + 1) + 1;
90
+ const gap = PATTERN_PALETTES[mode].length - 1;
91
+ const pixels = new Uint8Array(width * height).fill(gap);
92
+ for (let sprite = 0; sprite < count; sprite++) {
93
+ const left = (sprite % perRow) * (SPRITE.width + 1) + 1;
94
+ const top = Math.floor(sprite / perRow) * (SPRITE.height + 1) + 1;
95
+ for (let row = 0; row < SPRITE.height; row++) {
96
+ for (let column = 0; column < 3; column++) {
97
+ const values = bytePixels(bytes[sprite * stride + row * 3 + column] ?? 0, mode);
98
+ values.forEach((value, x) => (pixels[(top + row) * width + left + column * 8 + x] = value));
99
+ }
100
+ }
101
+ }
102
+ return { width, height, pixels };
103
+ }
104
+
105
+ /** Cells of 8 bytes as 8x8 pixels, `perRow` cells to a row, cell 0 at the top left. */
106
+ function cellFrame(bytes: Uint8Array, mode: GraphicsMode, perRow: number): IndexedFrame {
107
+ const cells = Math.ceil(bytes.length / 8);
108
+ const width = Math.min(cells, perRow) * 8;
109
+ const height = Math.ceil(cells / perRow) * 8;
110
+ const pixels = new Uint8Array(width * height);
111
+ for (let cell = 0; cell < cells; cell++) {
112
+ const left = (cell % perRow) * 8;
113
+ const top = Math.floor(cell / perRow) * 8;
114
+ for (let row = 0; row < 8; row++) {
115
+ bytePixels(bytes[cell * 8 + row] ?? 0, mode).forEach((value, x) => (pixels[(top + row) * width + left + x] = value));
116
+ }
117
+ }
118
+ return { width, height, pixels };
119
+ }
120
+
121
+ /** A bitmap of 8x8 cells of 8 bytes, 40 cells to a row, as the VIC reads it. */
122
+ export function bitmapFrame(bytes: Uint8Array, mode: GraphicsMode): IndexedFrame {
123
+ return cellFrame(bytes, mode, 40);
124
+ }
125
+
126
+ /** A character set: characters of 8 bytes, 16 to a row, so character N is in row N / 16 and column N % 16. */
127
+ export function charsetFrame(bytes: Uint8Array, mode: GraphicsMode): IndexedFrame {
128
+ return cellFrame(bytes, mode, 16);
129
+ }
130
+
131
+ /** Each pixel as a square of `factor` pixels, so a small image is readable on a page. */
132
+ export function scaleFrame(frame: IndexedFrame, factor: number): IndexedFrame {
133
+ const width = frame.width * factor;
134
+ const height = frame.height * factor;
135
+ const pixels = new Uint8Array(width * height);
136
+ for (let y = 0; y < height; y++) {
137
+ for (let x = 0; x < width; x++) pixels[y * width + x] = frame.pixels[Math.floor(y / factor) * frame.width + Math.floor(x / factor)]!;
138
+ }
139
+ return { width, height, pixels };
140
+ }
141
+
142
+ /**
143
+ * Screen codes as text rows of 40, as the character ROM shows the upper-case
144
+ * set: @, A-Z, the signs and digits; a reversed code (bit 7) shows as its
145
+ * code without bit 7, and a graphics code as a dot.
146
+ */
147
+ export function screenText(bytes: Uint8Array): string[] {
148
+ const rows: string[] = [];
149
+ for (let start = 0; start < bytes.length; start += 40) {
150
+ let row = "";
151
+ for (const code of bytes.subarray(start, start + 40)) {
152
+ const plain = code & 0x7f;
153
+ row += plain === 0 ? "@" : plain <= 26 ? String.fromCharCode(0x40 + plain) : plain <= 31 ? "[£]^<"[plain - 27]! : plain <= 63 ? String.fromCharCode(plain) : ".";
154
+ }
155
+ rows.push(row);
156
+ }
157
+ return rows;
158
+ }
package/src/host/main.ts CHANGED
@@ -7,10 +7,11 @@ 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";
13
- import { PACKAGE } from "../package.ts";
14
+ import { oldNodeRefusal, PACKAGE } from "../package.ts";
14
15
 
15
16
  const HELP = `c64-re-tools-host
16
17
 
@@ -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) {
@@ -130,4 +144,10 @@ async function main(): Promise<number> {
130
144
  return 0;
131
145
  }
132
146
 
147
+ const oldNode = oldNodeRefusal();
148
+ if (oldNode !== undefined) {
149
+ log(oldNode);
150
+ process.exit(1);
151
+ }
152
+
133
153
  process.exitCode = await main();
@@ -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. */