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/dist/petscii.js
CHANGED
|
@@ -61,8 +61,6 @@ function ensureScreenCodeMap() {
|
|
|
61
61
|
}
|
|
62
62
|
SCREEN_CODE_TO_ASCII.set(code, deriveGlyphChar(glyph));
|
|
63
63
|
}
|
|
64
|
-
// Common control characters / gaps default to space to keep output tidy.
|
|
65
|
-
SCREEN_CODE_TO_ASCII.set(0x00, " ");
|
|
66
64
|
screenCodeMapInitialised = true;
|
|
67
65
|
}
|
|
68
66
|
export function screenCodesToAscii(bytes, options) {
|
|
@@ -132,7 +130,7 @@ const NAMED_CHARS = Object.freeze({
|
|
|
132
130
|
"cursor_left": 157,
|
|
133
131
|
// Graphic glyphs (commonly referenced by name)
|
|
134
132
|
// Note: Exact byte shown on screen depends on mode; these are PETSCII/CHR$ values
|
|
135
|
-
"heart":
|
|
133
|
+
"heart": 115, // $73 (shifted S); $51 would print the letter Q
|
|
136
134
|
"up_arrow": 94,
|
|
137
135
|
"uparrow": 94,
|
|
138
136
|
"left_arrow": 95,
|
|
@@ -146,7 +144,8 @@ export function listNamedPetscii() {
|
|
|
146
144
|
}
|
|
147
145
|
export function lookupNamedPetscii(name) {
|
|
148
146
|
const key = name.trim().toLowerCase().replace(/\s+/g, "_");
|
|
149
|
-
|
|
147
|
+
// Own keys only, so names like "constructor" don't resolve to Object.prototype members.
|
|
148
|
+
return Object.prototype.hasOwnProperty.call(NAMED_CHARS, key) ? NAMED_CHARS[key] : undefined;
|
|
150
149
|
}
|
|
151
150
|
/**
|
|
152
151
|
* Try to parse a named character token starting at the given index.
|
|
@@ -160,15 +159,16 @@ export function parseNamedCharToken(source, startIndex) {
|
|
|
160
159
|
return null;
|
|
161
160
|
}
|
|
162
161
|
const close = open === "{" ? "}" : open === ":" ? ":" : ">";
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
162
|
+
const idx = source.indexOf(close, startIndex + 1);
|
|
163
|
+
if (idx < 0) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
// Only sane name characters; anything else is ordinary text that happens to use the delimiters.
|
|
167
|
+
// Multi-word names such as {cursor up} need braces: ":" and "<" also appear in
|
|
168
|
+
// ordinary text ("Mode: reverse on: yes"), so they only delimit single words.
|
|
169
|
+
const name = source.slice(startIndex + 1, idx).trim();
|
|
170
|
+
const namePattern = open === "{" ? /^[A-Za-z0-9_\s-]+$/ : /^[A-Za-z0-9_-]+$/;
|
|
171
|
+
if (!namePattern.test(name)) {
|
|
172
172
|
return null;
|
|
173
173
|
}
|
|
174
174
|
const value = lookupNamedPetscii(name.replace(/[-\s]+/g, "_"));
|
|
@@ -181,7 +181,7 @@ export function parseNamedCharToken(source, startIndex) {
|
|
|
181
181
|
* Encode a text containing optional named tokens into PETSCII bytes.
|
|
182
182
|
* Named tokens may appear as {heart}, :heart:, or <heart>.
|
|
183
183
|
*/
|
|
184
|
-
export function encodeStringWithNames(input) {
|
|
184
|
+
export function encodeStringWithNames(input, options = {}) {
|
|
185
185
|
const bytes = [];
|
|
186
186
|
let i = 0;
|
|
187
187
|
while (i < input.length) {
|
|
@@ -191,7 +191,10 @@ export function encodeStringWithNames(input) {
|
|
|
191
191
|
i = maybe.endIndex;
|
|
192
192
|
continue;
|
|
193
193
|
}
|
|
194
|
-
|
|
194
|
+
// ASCII a-z are PETSCII graphics characters in the default character set,
|
|
195
|
+
// so callers can fold them to the letters $41-$5A. Named codes are not folded.
|
|
196
|
+
const char = input[i];
|
|
197
|
+
bytes.push(asciiToPetscii(options.foldLowercase && char >= "a" && char <= "z" ? char.toUpperCase() : char));
|
|
195
198
|
i += 1;
|
|
196
199
|
}
|
|
197
200
|
return Uint8Array.from(bytes);
|
package/dist/petsciiArt.js
CHANGED
|
@@ -19,6 +19,11 @@ const POPCOUNT = (() => {
|
|
|
19
19
|
}
|
|
20
20
|
return table;
|
|
21
21
|
})();
|
|
22
|
+
const RVS_ON = 18;
|
|
23
|
+
const RVS_OFF = 146;
|
|
24
|
+
// Marks the end of a character row in the generated DATA statements.
|
|
25
|
+
const ROW_END = -1;
|
|
26
|
+
const SCREEN_COLUMNS = 40;
|
|
22
27
|
const DEFAULT_MAX_WIDTH = 320;
|
|
23
28
|
const DEFAULT_MAX_HEIGHT = 200;
|
|
24
29
|
const SHAPES = [
|
|
@@ -252,10 +257,14 @@ function distanceBetween(block, glyph) {
|
|
|
252
257
|
}
|
|
253
258
|
return score;
|
|
254
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Match each 8x8 block to a glyph. `glyphs` has one entry per cell; `codes` is the
|
|
262
|
+
* PETSCII stream that prints them, with each run of reverse-video glyphs (screen
|
|
263
|
+
* codes $80-$FF) wrapped in RVS ON/RVS OFF. `rowCodes` holds that stream per row.
|
|
264
|
+
*/
|
|
255
265
|
export function bitmapToPetsciiCodes(bitmap) {
|
|
256
266
|
const { blocks, columns, rows } = splitIntoBlocks(bitmap);
|
|
257
267
|
const glyphs = getChargenGlyphs();
|
|
258
|
-
const codes = [];
|
|
259
268
|
const matchedGlyphs = [];
|
|
260
269
|
for (const block of blocks) {
|
|
261
270
|
let bestMatch;
|
|
@@ -270,11 +279,26 @@ export function bitmapToPetsciiCodes(bitmap) {
|
|
|
270
279
|
}
|
|
271
280
|
}
|
|
272
281
|
}
|
|
273
|
-
|
|
274
|
-
codes.push(selected.petsciiCode);
|
|
275
|
-
matchedGlyphs.push(selected);
|
|
282
|
+
matchedGlyphs.push(bestMatch ?? glyphs[0]);
|
|
276
283
|
}
|
|
277
|
-
|
|
284
|
+
const rowCodes = [];
|
|
285
|
+
for (let row = 0; row < rows; row += 1) {
|
|
286
|
+
const stream = [];
|
|
287
|
+
let reverse = false;
|
|
288
|
+
for (const glyph of matchedGlyphs.slice(row * columns, (row + 1) * columns)) {
|
|
289
|
+
const glyphReverse = (glyph.screenCode & 0x80) !== 0;
|
|
290
|
+
if (glyphReverse !== reverse) {
|
|
291
|
+
stream.push(glyphReverse ? RVS_ON : RVS_OFF);
|
|
292
|
+
reverse = glyphReverse;
|
|
293
|
+
}
|
|
294
|
+
stream.push(glyph.petsciiCode);
|
|
295
|
+
}
|
|
296
|
+
if (reverse) {
|
|
297
|
+
stream.push(RVS_OFF);
|
|
298
|
+
}
|
|
299
|
+
rowCodes.push(stream);
|
|
300
|
+
}
|
|
301
|
+
return { codes: rowCodes.flat(), rowCodes, glyphs: matchedGlyphs, columns, rows };
|
|
278
302
|
}
|
|
279
303
|
function buildBasicProgram(params) {
|
|
280
304
|
const lines = [];
|
|
@@ -288,19 +312,21 @@ function buildBasicProgram(params) {
|
|
|
288
312
|
lines.push(`${lineNumber} POKE 646,${fg}`);
|
|
289
313
|
lineNumber += 10;
|
|
290
314
|
}
|
|
315
|
+
// Rows vary in length because of the RVS ON/OFF codes, so each row's DATA ends with ROW_END.
|
|
291
316
|
lines.push(`${lineNumber} FORY=0TO${Math.max(0, params.rows - 1)}`);
|
|
292
317
|
lineNumber += 10;
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
lines.push(`${lineNumber} NEXTX:PRINT`);
|
|
318
|
+
const readLine = lineNumber;
|
|
319
|
+
// POKE212,0 clears quote mode: after a printed '"' glyph the editor would
|
|
320
|
+
// otherwise draw the following RVS ON/OFF codes as characters.
|
|
321
|
+
lines.push(`${readLine} READC:IFC>=0THENPRINT CHR$(C);:POKE212,0:GOTO${readLine}`);
|
|
298
322
|
lineNumber += 10;
|
|
299
|
-
|
|
323
|
+
// A full 40-column row already wraps the cursor; an extra PRINT would leave a blank line.
|
|
324
|
+
lines.push(params.columns >= SCREEN_COLUMNS ? `${lineNumber} NEXTY` : `${lineNumber} PRINT:NEXTY`);
|
|
300
325
|
lineNumber += 10;
|
|
326
|
+
const data = params.rowCodes.flatMap((row) => [...row, ROW_END]);
|
|
301
327
|
const chunkSize = 16;
|
|
302
|
-
for (let i = 0; i <
|
|
303
|
-
const slice =
|
|
328
|
+
for (let i = 0; i < data.length; i += chunkSize) {
|
|
329
|
+
const slice = data.slice(i, i + chunkSize);
|
|
304
330
|
const dataLine = slice.join(",");
|
|
305
331
|
lines.push(`${lineNumber} DATA ${dataLine}`);
|
|
306
332
|
lineNumber += 10;
|
|
@@ -334,7 +360,7 @@ export function createPetsciiArt(options) {
|
|
|
334
360
|
const converted = bitmapToPetsciiCodes(bitmap);
|
|
335
361
|
const codes = converted.codes;
|
|
336
362
|
const program = buildBasicProgram({
|
|
337
|
-
|
|
363
|
+
rowCodes: converted.rowCodes,
|
|
338
364
|
columns: converted.columns,
|
|
339
365
|
rows: converted.rows,
|
|
340
366
|
borderColor: options.borderColor,
|
package/dist/rag/indexer.js
CHANGED
|
@@ -12,7 +12,8 @@ const BASIC_DIR = path.join(ASSET_ROOT, "data/basic/examples");
|
|
|
12
12
|
const ASM_DIR = path.join(ASSET_ROOT, "data/assembly/examples");
|
|
13
13
|
const EXTERNAL_DIR = path.join(ASSET_ROOT, "external");
|
|
14
14
|
const DOC_ROOT = path.join(ASSET_ROOT, "doc");
|
|
15
|
-
|
|
15
|
+
// Plans and reviews are internal engineering notes, not C64 reference material.
|
|
16
|
+
const DOC_EXCLUDE_DIRS = new Set(["plans", "reviews"]);
|
|
16
17
|
const CONTEXT_DIR = path.join(ASSET_ROOT, "data/context");
|
|
17
18
|
const BOOTSTRAP_PATH = path.join(CONTEXT_DIR, "bootstrap.md");
|
|
18
19
|
const AGENTS_PATH = path.join(ASSET_ROOT, "AGENTS.md");
|