c64bridge 1.0.4 → 1.0.6
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 +2 -2
- package/data/embeddings_basic.json +1 -1
- package/data/embeddings_hardware.json +1 -1
- package/data/embeddings_mixed.json +1 -1
- package/data/embeddings_other.json +1 -1
- package/data/graphics/character-set.csv +64 -64
- package/data/sound/sidwave.md +113 -36
- package/data/sound/sidwave.schema.json +138 -28
- package/dist/.tsbuildinfo +1 -1
- package/dist/c64Client.js +144 -150
- package/dist/config.js +93 -39
- package/dist/device.js +252 -130
- package/dist/diagnostics.js +10 -2
- package/dist/knowledge.js +2 -1
- package/dist/mcp-server.js +12 -4
- package/dist/petscii.js +18 -15
- package/dist/petsciiArt.js +40 -14
- package/dist/rag/indexer.js +2 -1
- package/dist/sidwave.js +453 -61
- package/dist/sidwaveCompiler.js +1424 -275
- package/dist/tools/audio.js +97 -18
- package/dist/tools/debug.js +9 -9
- package/dist/tools/developer.js +7 -7
- package/dist/tools/errors.js +19 -1
- package/dist/tools/graphics.js +4 -4
- package/dist/tools/hex.js +9 -0
- package/dist/tools/input.js +21 -18
- package/dist/tools/machineControl.js +6 -6
- package/dist/tools/memory.js +11 -10
- package/dist/tools/meta/audio.js +8 -2
- package/dist/tools/meta/background.js +6 -1
- package/dist/tools/meta/filesystem.js +37 -10
- package/dist/tools/meta/graphics.js +11 -16
- package/dist/tools/meta/memory.js +16 -22
- package/dist/tools/meta/program.js +28 -9
- package/dist/tools/pollValidator.js +30 -2
- package/dist/tools/printer.js +4 -4
- package/dist/tools/programRunners.js +10 -9
- package/dist/tools/registry/disk.js +7 -5
- package/dist/tools/storage.js +21 -17
- package/dist/tools/streaming.js +2 -2
- package/dist/tools/translation/assembler.js +192 -57
- package/dist/tools/translation/basicTokenizer.js +44 -10
- package/dist/tools/translation/opcodeInventory.js +1 -1
- package/dist/tools/types.js +11 -4
- package/dist/tools/vice.js +1 -1
- package/dist/vicBitmap.js +16 -0
- package/dist/vice/mockServer.js +99 -8
- package/dist/vice/readiness.js +4 -9
- package/dist/vice/viceClient.js +34 -6
- package/mcp/schemas/c64_disk.schema.json +2 -2
- package/mcp/schemas/c64_input.schema.json +3 -3
- package/mcp/schemas/c64_program.schema.json +2 -2
- package/mcp/schemas/c64_system.schema.json +6 -4
- package/mcp/schemas/c64_vice.schema.json +3 -3
- package/mcp/server.json +1 -1
- package/mcp/tools.json +20 -14
- package/mcp.json +1 -1
- package/package.json +1 -1
- package/scripts/mockC64Server.mjs +113 -20
- package/scripts/run-coverage.mjs +31 -1
- package/scripts/update-readme.ts +3 -2
- package/server.json +2 -2
package/mcp/tools.json
CHANGED
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"properties": {
|
|
67
67
|
"type": {
|
|
68
68
|
"type": "string",
|
|
69
|
-
"description": "Assertion type",
|
|
69
|
+
"description": "Assertion type; sid_silent needs the C64 Ultimate audio stream (c64u backend).",
|
|
70
70
|
"enum": [
|
|
71
71
|
"screen_contains",
|
|
72
72
|
"memory_equals",
|
|
@@ -468,7 +468,7 @@
|
|
|
468
468
|
"properties": {
|
|
469
469
|
"type": {
|
|
470
470
|
"type": "string",
|
|
471
|
-
"description": "Assertion type",
|
|
471
|
+
"description": "Assertion type; sid_silent needs the C64 Ultimate audio stream (c64u backend).",
|
|
472
472
|
"enum": [
|
|
473
473
|
"screen_contains",
|
|
474
474
|
"memory_equals",
|
|
@@ -2872,8 +2872,9 @@
|
|
|
2872
2872
|
},
|
|
2873
2873
|
"name": {
|
|
2874
2874
|
"type": "string",
|
|
2875
|
-
"description": "Unique task name",
|
|
2876
|
-
"minLength": 1
|
|
2875
|
+
"description": "Unique task name (letters, digits, spaces, '_', '-', '.'; must not start with '.' or a space).",
|
|
2876
|
+
"minLength": 1,
|
|
2877
|
+
"pattern": "^[A-Za-z0-9_][A-Za-z0-9_. -]*$"
|
|
2877
2878
|
},
|
|
2878
2879
|
"operation": {
|
|
2879
2880
|
"type": "string",
|
|
@@ -3114,8 +3115,9 @@
|
|
|
3114
3115
|
"properties": {
|
|
3115
3116
|
"name": {
|
|
3116
3117
|
"type": "string",
|
|
3117
|
-
"description": "Unique task name",
|
|
3118
|
-
"minLength": 1
|
|
3118
|
+
"description": "Unique task name (letters, digits, spaces, '_', '-', '.'; must not start with '.' or a space).",
|
|
3119
|
+
"minLength": 1,
|
|
3120
|
+
"pattern": "^[A-Za-z0-9_][A-Za-z0-9_. -]*$"
|
|
3119
3121
|
},
|
|
3120
3122
|
"operation": {
|
|
3121
3123
|
"type": "string",
|
|
@@ -4481,7 +4483,7 @@
|
|
|
4481
4483
|
"integer",
|
|
4482
4484
|
"null"
|
|
4483
4485
|
],
|
|
4484
|
-
"description": "Track count (D64 supports 35
|
|
4486
|
+
"description": "Track count (D64 supports 35 to 41; DNP requires explicit tracks).",
|
|
4485
4487
|
"minimum": 1,
|
|
4486
4488
|
"maximum": 255
|
|
4487
4489
|
},
|
|
@@ -4783,7 +4785,7 @@
|
|
|
4783
4785
|
"integer",
|
|
4784
4786
|
"null"
|
|
4785
4787
|
],
|
|
4786
|
-
"description": "Track count (D64 supports 35
|
|
4788
|
+
"description": "Track count (D64 supports 35 to 41; DNP requires explicit tracks).",
|
|
4787
4789
|
"minimum": 1,
|
|
4788
4790
|
"maximum": 255
|
|
4789
4791
|
},
|
|
@@ -4947,6 +4949,10 @@
|
|
|
4947
4949
|
"vice"
|
|
4948
4950
|
],
|
|
4949
4951
|
"operationPlatforms": {
|
|
4952
|
+
"unmount": [
|
|
4953
|
+
"c64u",
|
|
4954
|
+
"u2"
|
|
4955
|
+
],
|
|
4950
4956
|
"file_info": [
|
|
4951
4957
|
"c64u",
|
|
4952
4958
|
"u2"
|
|
@@ -8073,7 +8079,7 @@
|
|
|
8073
8079
|
},
|
|
8074
8080
|
"name": {
|
|
8075
8081
|
"type": "string",
|
|
8076
|
-
"description": "VICE resource name (example: SidEngine,
|
|
8082
|
+
"description": "VICE resource name (example: SidEngine, SidModel).",
|
|
8077
8083
|
"minLength": 1,
|
|
8078
8084
|
"maxLength": 64,
|
|
8079
8085
|
"pattern": "^[A-Z][A-Za-z0-9_.-]{0,63}$"
|
|
@@ -8108,7 +8114,7 @@
|
|
|
8108
8114
|
},
|
|
8109
8115
|
"name": {
|
|
8110
8116
|
"type": "string",
|
|
8111
|
-
"description": "VICE resource name (example: SidEngine,
|
|
8117
|
+
"description": "VICE resource name (example: SidEngine, SidModel).",
|
|
8112
8118
|
"minLength": 1,
|
|
8113
8119
|
"maxLength": 64,
|
|
8114
8120
|
"pattern": "^[A-Z][A-Za-z0-9_.-]{0,63}$"
|
|
@@ -8138,7 +8144,7 @@
|
|
|
8138
8144
|
},
|
|
8139
8145
|
"name": {
|
|
8140
8146
|
"type": "string",
|
|
8141
|
-
"description": "VICE resource name (example: SidEngine,
|
|
8147
|
+
"description": "VICE resource name (example: SidEngine, SidModel).",
|
|
8142
8148
|
"minLength": 1,
|
|
8143
8149
|
"maxLength": 64,
|
|
8144
8150
|
"pattern": "^[A-Z][A-Za-z0-9_.-]{0,63}$"
|
|
@@ -8390,7 +8396,7 @@
|
|
|
8390
8396
|
"required": [
|
|
8391
8397
|
"op"
|
|
8392
8398
|
],
|
|
8393
|
-
"description": "Input operations: write_text, key, keyboard, joystick, release_all, state.\n\nOperations:\n- write_text: Send a text string to the keyboard buffer, with PETSCII token expansion. Required inputs: text. Optional inputs: delayMs.\n- key: Tap a single key or hold it for a duration. Required inputs: key. Optional inputs: durationMs, count.\n- joystick: Simulate joystick input. On C64U/U64 this uses machine:input
|
|
8399
|
+
"description": "Input operations: write_text, key, keyboard, joystick, release_all, state.\n\nOperations:\n- write_text: Send a text string to the keyboard buffer, with PETSCII token expansion. Required inputs: text. Optional inputs: delayMs.\n- key: Tap a single key or hold it for a duration. Required inputs: key. Optional inputs: durationMs, count.\n- joystick: Simulate joystick input. On C64U/U64 this uses machine:input. On VICE it attaches VICE's I/O simulation joyport device while pressed; that device also pulls CIA lines 5-7 low, so port 1 produces phantom keys (e.g. SHIFT+C=) and port 2 reads $1E for up instead of $7E. Prefer port 2 on VICE and test bits 0-4. Required inputs: port, controls, action. Optional inputs: durationMs.\n- keyboard: Send physical C64 keyboard matrix events through machine:input (a C64U firmware version that provides it, or U64 3.15+). Required inputs: inputs, transition.\n- release_all: Release every key and joystick control injected through machine:input (a C64U firmware version that provides it, or U64 3.15+). Required inputs: none.\n- state: Read the keys and joystick controls held through machine:input (a C64U firmware version that provides it, or U64 3.15+). Required inputs: none.",
|
|
8394
8400
|
"additionalProperties": false,
|
|
8395
8401
|
"x-c64bridge-operations": [
|
|
8396
8402
|
{
|
|
@@ -8486,7 +8492,7 @@
|
|
|
8486
8492
|
},
|
|
8487
8493
|
{
|
|
8488
8494
|
"op": "joystick",
|
|
8489
|
-
"description": "Simulate joystick input. On C64U/U64 this uses machine:input
|
|
8495
|
+
"description": "Simulate joystick input. On C64U/U64 this uses machine:input. On VICE it attaches VICE's I/O simulation joyport device while pressed; that device also pulls CIA lines 5-7 low, so port 1 produces phantom keys (e.g. SHIFT+C=) and port 2 reads $1E for up instead of $7E. Prefer port 2 on VICE and test bits 0-4.",
|
|
8490
8496
|
"required": [
|
|
8491
8497
|
"port",
|
|
8492
8498
|
"controls",
|
|
@@ -8497,7 +8503,7 @@
|
|
|
8497
8503
|
],
|
|
8498
8504
|
"inputSchema": {
|
|
8499
8505
|
"type": "object",
|
|
8500
|
-
"description": "Simulate joystick input. On C64U/U64 this uses machine:input
|
|
8506
|
+
"description": "Simulate joystick input. On C64U/U64 this uses machine:input. On VICE it attaches VICE's I/O simulation joyport device while pressed; that device also pulls CIA lines 5-7 low, so port 1 produces phantom keys (e.g. SHIFT+C=) and port 2 reads $1E for up instead of $7E. Prefer port 2 on VICE and test bits 0-4.",
|
|
8501
8507
|
"properties": {
|
|
8502
8508
|
"op": {
|
|
8503
8509
|
"const": "joystick"
|
package/mcp.json
CHANGED
package/package.json
CHANGED
|
@@ -250,6 +250,14 @@ function cancelHeartbeatOnWrite(state, address, length) {
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
function seedReadyPrompt(state) {
|
|
253
|
+
// After a reset the KERNAL clears screen RAM to spaces and BASIC sets up an
|
|
254
|
+
// empty program: TXTTAB=$0801, VARTAB/ARYTAB/STREND=$0803, FRETOP/MEMSIZ=$A000
|
|
255
|
+
// (as read from an Ultimate 64 after machine:reset).
|
|
256
|
+
state.memory.fill(toScreenCode(" "), 0x0400, 0x0400 + 1000);
|
|
257
|
+
state.memory.set([0x01, 0x08, 0x03, 0x08, 0x03, 0x08, 0x03, 0x08, 0x00, 0xa0], 0x2b);
|
|
258
|
+
state.memory.set([0x00, 0xa0], 0x37);
|
|
259
|
+
state.memory.set([0x00, 0x00], 0x0801);
|
|
260
|
+
|
|
253
261
|
const text = "READY.";
|
|
254
262
|
const buffer = new Uint8Array(text.length + 1);
|
|
255
263
|
for (let i = 0; i < text.length; i += 1) {
|
|
@@ -259,6 +267,26 @@ function seedReadyPrompt(state) {
|
|
|
259
267
|
state.memory.set(buffer, 0x0400);
|
|
260
268
|
}
|
|
261
269
|
|
|
270
|
+
/**
|
|
271
|
+
* BASIC LINKPRG ($A533): walk the line chain from $0801 and rewrite each link
|
|
272
|
+
* pointer to the byte after the line's terminating zero. Stops at a zero link
|
|
273
|
+
* high byte, as the ROM does; the step limit only guards the mock.
|
|
274
|
+
*/
|
|
275
|
+
function relinkBasicProgram(memory) {
|
|
276
|
+
let line = 0x0801;
|
|
277
|
+
for (let steps = 0; steps < 0x10000 && line < 0xfffb; steps += 1) {
|
|
278
|
+
if (memory[line + 1] === 0) return;
|
|
279
|
+
let y = 4;
|
|
280
|
+
do {
|
|
281
|
+
y += 1;
|
|
282
|
+
} while (line + y < 0xffff && memory[line + y] !== 0);
|
|
283
|
+
const next = line + y + 1;
|
|
284
|
+
memory[line] = next & 0xff;
|
|
285
|
+
memory[line + 1] = (next >> 8) & 0xff;
|
|
286
|
+
line = next;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
262
290
|
export async function startMockC64Server(options = {}) {
|
|
263
291
|
const state = createInitialState();
|
|
264
292
|
const streamRuntime = createStreamRuntime();
|
|
@@ -269,9 +297,14 @@ export async function startMockC64Server(options = {}) {
|
|
|
269
297
|
// seed memory with READY prompt at $0400 using screen codes
|
|
270
298
|
seedReadyPrompt(state);
|
|
271
299
|
|
|
300
|
+
// Firmware drive ids (route_drives.h DriveToSubsys): a and b are the
|
|
301
|
+
// emulated drives, softiec the IEC file system; anything else is rejected.
|
|
302
|
+
const FIRMWARE_DRIVE_BUS_IDS = { a: 8, b: 9, softiec: 11 };
|
|
303
|
+
|
|
272
304
|
function ensureDriveState(id) {
|
|
273
305
|
if (!state.drives[id]) {
|
|
274
306
|
state.drives[id] = {
|
|
307
|
+
busId: FIRMWARE_DRIVE_BUS_IDS[id] ?? null,
|
|
275
308
|
mountedImage: null,
|
|
276
309
|
mode: "1541",
|
|
277
310
|
power: "off",
|
|
@@ -282,14 +315,20 @@ export async function startMockC64Server(options = {}) {
|
|
|
282
315
|
return state.drives[id];
|
|
283
316
|
}
|
|
284
317
|
|
|
285
|
-
|
|
318
|
+
function seedDrives() {
|
|
319
|
+
ensureDriveState("a");
|
|
320
|
+
ensureDriveState("b");
|
|
321
|
+
ensureDriveState("softiec");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
seedDrives();
|
|
286
325
|
|
|
287
326
|
function resetState() {
|
|
288
327
|
stopAllStreams();
|
|
289
328
|
const fresh = createInitialState();
|
|
290
329
|
Object.assign(state, fresh);
|
|
291
330
|
seedReadyPrompt(state);
|
|
292
|
-
|
|
331
|
+
seedDrives();
|
|
293
332
|
}
|
|
294
333
|
|
|
295
334
|
function stopAllStreams() {
|
|
@@ -401,18 +440,32 @@ export async function startMockC64Server(options = {}) {
|
|
|
401
440
|
}
|
|
402
441
|
|
|
403
442
|
if (method === "GET" && url === "/v1/drives") {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
443
|
+
// Firmware shape (route_drives.cc drive_info, IecInterface::info): a list
|
|
444
|
+
// of single-key objects. Power is `enabled`; the mounted image is split
|
|
445
|
+
// into its directory (image_path) and file name (image_file). The soft
|
|
446
|
+
// IEC drive is listed as "IEC Drive" although its route id is softiec,
|
|
447
|
+
// and the printer is listed but is not a drive.
|
|
448
|
+
const splitImage = (image) => {
|
|
449
|
+
if (!image) return { image_file: "", image_path: "" };
|
|
450
|
+
const slash = image.lastIndexOf("/");
|
|
451
|
+
return { image_file: image.slice(slash + 1), image_path: slash > 0 ? image.slice(0, slash) : "/" };
|
|
452
|
+
};
|
|
453
|
+
const drives = ["a", "b"].map((driveId) => {
|
|
454
|
+
const driveState = state.drives[driveId];
|
|
455
|
+
return {
|
|
456
|
+
[driveId]: {
|
|
457
|
+
enabled: driveState.power !== "off",
|
|
458
|
+
bus_id: driveState.busId,
|
|
459
|
+
type: driveState.mode,
|
|
460
|
+
rom: `${driveState.mode}.rom`,
|
|
461
|
+
...splitImage(driveState.mountedImage?.image),
|
|
462
|
+
},
|
|
414
463
|
};
|
|
415
|
-
}
|
|
464
|
+
});
|
|
465
|
+
drives.push(
|
|
466
|
+
{ "IEC Drive": { bus_id: state.drives.softiec.busId, enabled: state.drives.softiec.power !== "off", type: "DOS emulation", last_error: "00, OK, 00, 00", partitions: [{ id: 1, path: "/Temp/" }] } },
|
|
467
|
+
{ "Printer Emulation": { bus_id: 4, enabled: false } },
|
|
468
|
+
);
|
|
416
469
|
sendJson(res, { drives });
|
|
417
470
|
return;
|
|
418
471
|
}
|
|
@@ -526,11 +579,20 @@ export async function startMockC64Server(options = {}) {
|
|
|
526
579
|
state.lastLoadedPrg = prg;
|
|
527
580
|
state.loadCount += 1;
|
|
528
581
|
|
|
529
|
-
// Mirror
|
|
530
|
-
// address without starting it
|
|
582
|
+
// Mirror Ultimate 64 firmware 3.15: the PRG body is placed at its
|
|
583
|
+
// embedded load address without starting it, and, as after a BASIC
|
|
584
|
+
// LOAD, VARTAB/ARYTAB/STREND are set to the end address and the BASIC
|
|
585
|
+
// program at $0801 is relinked (which rewrites raw code at $0801).
|
|
531
586
|
if (prg.length >= 2) {
|
|
532
587
|
const loadAddress = prg.readUInt16LE(0);
|
|
533
|
-
|
|
588
|
+
const body = prg.subarray(2);
|
|
589
|
+
state.memory.set(body.subarray(0, 0x10000 - loadAddress), loadAddress);
|
|
590
|
+
const endAddress = Math.min(0xffff, loadAddress + body.length);
|
|
591
|
+
for (const pointer of [0x2d, 0x2f, 0x31]) {
|
|
592
|
+
state.memory[pointer] = endAddress & 0xff;
|
|
593
|
+
state.memory[pointer + 1] = endAddress >> 8;
|
|
594
|
+
}
|
|
595
|
+
relinkBasicProgram(state.memory);
|
|
534
596
|
}
|
|
535
597
|
|
|
536
598
|
sendJson(res, { result: "ok", bytes: prg.length });
|
|
@@ -564,9 +626,22 @@ export async function startMockC64Server(options = {}) {
|
|
|
564
626
|
}
|
|
565
627
|
|
|
566
628
|
const attachment = Buffer.concat(chunks);
|
|
629
|
+
// Like the firmware, a multipart upload is a list of files in part
|
|
630
|
+
// order: the SID first, optional song lengths second. A text field
|
|
631
|
+
// (for example a JSON-serialised Buffer) is not a SID upload.
|
|
632
|
+
const contentType = String(req.headers["content-type"] ?? "");
|
|
633
|
+
const files = contentType.startsWith("multipart/form-data")
|
|
634
|
+
? [...(await new Response(attachment, { headers: { "content-type": contentType } }).formData()).values()]
|
|
635
|
+
: null;
|
|
636
|
+
if (files && (files.length === 0 || files.some((part) => typeof part === "string"))) {
|
|
637
|
+
sendJson(res, { errors: ["Upload of file failed."] }, 400);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
const sid = files ? Buffer.from(await files[0].arrayBuffer()) : attachment;
|
|
641
|
+
const songlengths = files?.[1] ? Buffer.from(await files[1].arrayBuffer()) : null;
|
|
567
642
|
state.sidAttachmentCount += 1;
|
|
568
|
-
state.lastSidAttachment = { songnr, bytes:
|
|
569
|
-
sendJson(res, { result: "sidplay_attachment", bytes:
|
|
643
|
+
state.lastSidAttachment = { songnr, bytes: sid.length, sid, songlengths };
|
|
644
|
+
sendJson(res, { result: "sidplay_attachment", bytes: sid.length, songnr });
|
|
570
645
|
return;
|
|
571
646
|
}
|
|
572
647
|
}
|
|
@@ -610,7 +685,21 @@ export async function startMockC64Server(options = {}) {
|
|
|
610
685
|
const lengthValue = routeUrl.searchParams.get("length") ?? "256";
|
|
611
686
|
const address = parseNumeric(addressValue);
|
|
612
687
|
const length = Math.max(0, parseNumeric(lengthValue, 10));
|
|
613
|
-
|
|
688
|
+
// Same checks as the firmware (route_machine.cc readmem).
|
|
689
|
+
if (length < 1 || length > 65536) {
|
|
690
|
+
sendJson(res, { errors: ["Invalid length"] }, 400);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
if (address + length > 65536) {
|
|
694
|
+
sendJson(res, { errors: ["Memory read exceeds location $FFFF"] }, 400);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
const bytes = Uint8Array.from(readMemoryWithHeartbeat(state, address, length));
|
|
698
|
+
// SID registers $D400-$D418 are write-only; the Ultimate 64 reads them as $00.
|
|
699
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
700
|
+
const target = address + i;
|
|
701
|
+
if (target >= 0xd400 && target <= 0xd418) bytes[i] = 0;
|
|
702
|
+
}
|
|
614
703
|
|
|
615
704
|
const accept = String(req.headers["accept"] || "");
|
|
616
705
|
if (accept.includes("application/octet-stream")) {
|
|
@@ -816,8 +905,12 @@ export async function startMockC64Server(options = {}) {
|
|
|
816
905
|
if (routeUrl) {
|
|
817
906
|
const match = /^\/v1\/drives\/([^:]+):(mount|remove|reset|on|off|load_rom|set_mode)$/.exec(routeUrl.pathname);
|
|
818
907
|
if (match) {
|
|
819
|
-
const driveId = decodeURIComponent(match[1]);
|
|
908
|
+
const driveId = decodeURIComponent(match[1]).toLowerCase();
|
|
820
909
|
const action = match[2];
|
|
910
|
+
if (!Object.hasOwn(FIRMWARE_DRIVE_BUS_IDS, driveId)) {
|
|
911
|
+
sendJson(res, { errors: [`Invalid Drive '${decodeURIComponent(match[1])}'`] }, 400);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
821
914
|
const driveState = ensureDriveState(driveId);
|
|
822
915
|
|
|
823
916
|
const respond = (payload) => {
|
package/scripts/run-coverage.mjs
CHANGED
|
@@ -98,7 +98,10 @@ export async function main() {
|
|
|
98
98
|
legOutputs.push(legPath);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
const finalReport =
|
|
101
|
+
const finalReport = await dropNonCodeLines(
|
|
102
|
+
mergeReports(await Promise.all(legOutputs.map(readReport))),
|
|
103
|
+
(relativePath) => fs.readFile(path.join(repoRoot, relativePath), "utf8"),
|
|
104
|
+
);
|
|
102
105
|
const finalPath = path.join(coverageDir, "lcov.info");
|
|
103
106
|
await fs.writeFile(finalPath, serializeReport(finalReport), "utf8");
|
|
104
107
|
|
|
@@ -265,6 +268,33 @@ function parseReport(text) {
|
|
|
265
268
|
return report;
|
|
266
269
|
}
|
|
267
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Bun's LCOV output lists blank and comment-only lines inside executed
|
|
273
|
+
* functions as executable lines that were never hit. Drop them, using each
|
|
274
|
+
* file's source, so coverage counts only code.
|
|
275
|
+
*/
|
|
276
|
+
export async function dropNonCodeLines(report, readSource) {
|
|
277
|
+
const result = new Map();
|
|
278
|
+
for (const [filePath, record] of report.entries()) {
|
|
279
|
+
let sourceLines;
|
|
280
|
+
try {
|
|
281
|
+
sourceLines = (await readSource(filePath)).split(/\r?\n/);
|
|
282
|
+
} catch {
|
|
283
|
+
result.set(filePath, record);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const lines = new Map();
|
|
287
|
+
for (const [lineNo, hits] of record.lines.entries()) {
|
|
288
|
+
const text = (sourceLines[lineNo - 1] ?? "").trim();
|
|
289
|
+
const isComment = text.startsWith("//") || text.startsWith("/*") || text.startsWith("*");
|
|
290
|
+
if (text === "" || isComment) continue;
|
|
291
|
+
lines.set(lineNo, hits);
|
|
292
|
+
}
|
|
293
|
+
result.set(filePath, { ...record, lines });
|
|
294
|
+
}
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
|
|
268
298
|
function mergeReports(reports) {
|
|
269
299
|
const merged = new Map();
|
|
270
300
|
for (const report of reports) {
|
package/scripts/update-readme.ts
CHANGED
|
@@ -485,8 +485,9 @@ export async function updateReadme(): Promise<boolean> {
|
|
|
485
485
|
const generatedDocs = `\n\n${buildDocumentation()}\n\n`;
|
|
486
486
|
const generatedEnv = `\n\n${buildEnvironmentDocumentation()}\n\n`;
|
|
487
487
|
const nextReadme = readme
|
|
488
|
-
|
|
489
|
-
.replace(
|
|
488
|
+
// Replacement functions, not strings: generated text such as "$1E" must not be read as "$1".
|
|
489
|
+
.replace(docsPattern, () => `${START_MARKER}${generatedDocs}${END_MARKER}`)
|
|
490
|
+
.replace(envPattern, () => `${ENV_START_MARKER}${generatedEnv}${ENV_END_MARKER}`);
|
|
490
491
|
|
|
491
492
|
if (nextReadme === readme) {
|
|
492
493
|
return false;
|
package/server.json
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
10
|
"websiteUrl": "https://github.com/chrisgleissner/c64bridge",
|
|
11
|
-
"version": "1.0.
|
|
11
|
+
"version": "1.0.6",
|
|
12
12
|
"packages": [
|
|
13
13
|
{
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"identifier": "c64bridge",
|
|
16
|
-
"version": "1.0.
|
|
16
|
+
"version": "1.0.6",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
19
19
|
},
|