c64bridge 0.5.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.
- package/.github/prompts/README.md +6 -0
- package/.github/prompts/assembly-routine-session.prompt.md +13 -0
- package/.github/prompts/basic-program-session.prompt.md +13 -0
- package/.github/prompts/drive-management-session.prompt.md +13 -0
- package/.github/prompts/petscii-art-session.prompt.md +13 -0
- package/.github/prompts/play-classic-game.prompt.md +13 -0
- package/.github/prompts/sid-music-session.prompt.md +13 -0
- package/AGENTS.md +173 -0
- package/LICENSE +339 -0
- package/README.md +582 -0
- package/data/README.md +16 -0
- package/data/api/basic-api-spec.md +154 -0
- package/data/api/kernal-api-spec.md +68 -0
- package/data/assembly/assembly-spec.md +196 -0
- package/data/assembly/examples/effects/sinewave.asm +5 -0
- package/data/assembly/examples/graphics/sprite/mover.asm +15 -0
- package/data/assembly/examples/sid-file-structure.md +8 -0
- package/data/assembly/examples/sid-overview.md +11 -0
- package/data/audio/examples/Biosphere.sid +0 -0
- package/data/audio/sid-file-structure.md +91 -0
- package/data/audio/sid-programming-best-practices.md +198 -0
- package/data/audio/sid-spec.md +166 -0
- package/data/audio/sidwave.md +131 -0
- package/data/audio/sidwave.schema.json +64 -0
- package/data/basic/basic-spec.md +230 -0
- package/data/basic/examples/audio/alle-meine-entchen-expressive.bas +41 -0
- package/data/basic/examples/audio/c-major-scale-pleasant.bas +29 -0
- package/data/basic/examples/io/joystick.bas +5 -0
- package/data/basic/examples/video/bounce.bas +8 -0
- package/data/basic/examples/video/entchen-petscii.bas +16 -0
- package/data/basic/examples/video/hello-world.bas +4 -0
- package/data/basic/examples/video/wave.bas +7 -0
- package/data/context/bootstrap.md +148 -0
- package/data/context/chat.md +36 -0
- package/data/drive/drive-spec.md +215 -0
- package/data/embeddings_asm.json +1 -0
- package/data/embeddings_basic.json +1 -0
- package/data/embeddings_hardware.json +1 -0
- package/data/embeddings_mixed.json +1 -0
- package/data/embeddings_other.json +1 -0
- package/data/io/cia-spec.md +115 -0
- package/data/io/io-spec.md +124 -0
- package/data/memory/kernal-memory-map.md +227 -0
- package/data/memory/low-memory-map.md +133 -0
- package/data/memory/memory-map.md +186 -0
- package/data/printer/printer-commodore-bitmap.md +31 -0
- package/data/printer/printer-commodore.md +28 -0
- package/data/printer/printer-epson-bitmap.md +29 -0
- package/data/printer/printer-epson.md +30 -0
- package/data/printer/printer-prompts.md +29 -0
- package/data/printer/printer-spec.md +180 -0
- package/data/video/character-set.csv +257 -0
- package/data/video/vic-spec.md +276 -0
- package/dist/assemblyConverter.js +1299 -0
- package/dist/audio/record_and_analyze_audio.js +292 -0
- package/dist/basicConverter.js +193 -0
- package/dist/bootstrap/stdio-logger.js +32 -0
- package/dist/c64Client.js +1052 -0
- package/dist/chargen.js +197 -0
- package/dist/config.js +142 -0
- package/dist/context.js +157 -0
- package/dist/device.js +413 -0
- package/dist/index.js +7 -0
- package/dist/knowledge.js +454 -0
- package/dist/logger.js +182 -0
- package/dist/loggingHttpClient.js +88 -0
- package/dist/mcp-server.js +426 -0
- package/dist/petscii.js +135 -0
- package/dist/petsciiArt.js +356 -0
- package/dist/platform.js +82 -0
- package/dist/prompts/registry.js +560 -0
- package/dist/rag/discover.config.json +41 -0
- package/dist/rag/discover.js +401 -0
- package/dist/rag/embeddings.js +60 -0
- package/dist/rag/externalFetcher.js +934 -0
- package/dist/rag/indexer.js +770 -0
- package/dist/rag/init.js +139 -0
- package/dist/rag/knowledgeIndex.js +361 -0
- package/dist/rag/loggingRetriever.js +46 -0
- package/dist/rag/rateLimiter.js +127 -0
- package/dist/rag/retriever.js +74 -0
- package/dist/rag/sources.csv +31 -0
- package/dist/rag/types.js +5 -0
- package/dist/rag/urlUtils.js +38 -0
- package/dist/sidplayRunner.js +240 -0
- package/dist/sidwave.js +101 -0
- package/dist/sidwaveCompiler.js +329 -0
- package/dist/tools/audio.js +944 -0
- package/dist/tools/developer.js +515 -0
- package/dist/tools/errors.js +59 -0
- package/dist/tools/graphics.js +469 -0
- package/dist/tools/machineControl.js +267 -0
- package/dist/tools/memory.js +224 -0
- package/dist/tools/printer.js +482 -0
- package/dist/tools/programRunners.js +315 -0
- package/dist/tools/rag.js +153 -0
- package/dist/tools/registry.js +58 -0
- package/dist/tools/responses.js +36 -0
- package/dist/tools/schema.js +304 -0
- package/dist/tools/storage.js +740 -0
- package/dist/tools/streaming.js +132 -0
- package/dist/tools/types.js +99 -0
- package/dist/viceRunner.js +239 -0
- package/doc/MCP_SETUP.md +153 -0
- package/doc/README.md +7 -0
- package/doc/developer.md +312 -0
- package/doc/img/logo.png +0 -0
- package/doc/img/prompts/duck_petscii.png +0 -0
- package/doc/img/prompts/duck_song.png +0 -0
- package/doc/img/vscode/vscode-copilot-c64-chat-mode.png +0 -0
- package/doc/img/vscode/vscode-copilot-hello-world.png +0 -0
- package/doc/prompts/README.md +96 -0
- package/doc/prompts/user-prompt-ideas.md +147 -0
- package/doc/rest/c64-openapi.yaml +979 -0
- package/doc/rest/c64-rest-api.md +106 -0
- package/doc/troubleshooting-mcp.md +177 -0
- package/generated/c64/index.js +857 -0
- package/mcp.json +106 -0
- package/package.json +79 -0
- package/scripts/c64-cli.mjs +142 -0
- package/scripts/c64-tool.sh +83 -0
- package/scripts/check-package.mjs +89 -0
- package/scripts/cli.js +61 -0
- package/scripts/fetch-external-sources.mjs +67 -0
- package/scripts/generate-changelog.mjs +131 -0
- package/scripts/postbuild.mjs +101 -0
- package/scripts/prepare-release.mjs +58 -0
- package/scripts/rag-rebuild.mjs +53 -0
- package/scripts/register-ts-node.mjs +6 -0
- package/scripts/run-mcp-check.sh +104 -0
- package/scripts/run-tests.mjs +195 -0
- package/scripts/sid2wav.sh +94 -0
- package/scripts/smoke-run.sh +65 -0
- package/scripts/start-mock-c64.mjs +51 -0
- package/scripts/start-mock.mjs +31 -0
- package/scripts/start.mjs +72 -0
- package/scripts/update-readme.ts +164 -0
- package/scripts/verify-package.mjs +147 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/*
|
|
2
|
+
C64 Bridge - An MCP Server for the Commodore 64 Ultimate
|
|
3
|
+
Copyright (C) 2025 Christian Gleissner
|
|
4
|
+
|
|
5
|
+
Licensed under the GNU General Public License v2.0 or later.
|
|
6
|
+
See <https://www.gnu.org/licenses/> for details.
|
|
7
|
+
*/
|
|
8
|
+
import { findGlyphByBasicChar, getChargenGlyphs } from "./chargen.js";
|
|
9
|
+
const POPCOUNT = (() => {
|
|
10
|
+
const table = new Uint8Array(256);
|
|
11
|
+
for (let i = 0; i < 256; i += 1) {
|
|
12
|
+
let count = 0;
|
|
13
|
+
let value = i;
|
|
14
|
+
while (value) {
|
|
15
|
+
value &= value - 1;
|
|
16
|
+
count += 1;
|
|
17
|
+
}
|
|
18
|
+
table[i] = count;
|
|
19
|
+
}
|
|
20
|
+
return table;
|
|
21
|
+
})();
|
|
22
|
+
const DEFAULT_MAX_WIDTH = 320;
|
|
23
|
+
const DEFAULT_MAX_HEIGHT = 200;
|
|
24
|
+
const SHAPES = [
|
|
25
|
+
{
|
|
26
|
+
name: "heart",
|
|
27
|
+
keywords: /\b(heart|love)\b/i,
|
|
28
|
+
pattern: [
|
|
29
|
+
"0011110000111100",
|
|
30
|
+
"0111111001111110",
|
|
31
|
+
"1111111111111111",
|
|
32
|
+
"1111111111111111",
|
|
33
|
+
"1111111111111111",
|
|
34
|
+
"0111111111111110",
|
|
35
|
+
"0011111111111100",
|
|
36
|
+
"0001111111111000",
|
|
37
|
+
"0000111111110000",
|
|
38
|
+
"0000011111100000",
|
|
39
|
+
"0000001111000000",
|
|
40
|
+
"0000000110000000",
|
|
41
|
+
"0000000100000000",
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "smiley",
|
|
46
|
+
keywords: /\b(face|smile|smiley|happy)\b/i,
|
|
47
|
+
pattern: [
|
|
48
|
+
"0000011111100000",
|
|
49
|
+
"0001111111110000",
|
|
50
|
+
"0011111111111000",
|
|
51
|
+
"0111100000011100",
|
|
52
|
+
"0111001100111100",
|
|
53
|
+
"1110011111001110",
|
|
54
|
+
"1110011111001110",
|
|
55
|
+
"1110011111001110",
|
|
56
|
+
"1110011111001110",
|
|
57
|
+
"1110011111001110",
|
|
58
|
+
"1110011111001110",
|
|
59
|
+
"1110000000001110",
|
|
60
|
+
"0111000000001110",
|
|
61
|
+
"0111110000111110",
|
|
62
|
+
"0011111111111100",
|
|
63
|
+
"0001111111111000",
|
|
64
|
+
"0000011111100000",
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "star",
|
|
69
|
+
keywords: /\b(star|sparkle)\b/i,
|
|
70
|
+
pattern: [
|
|
71
|
+
"0000001110000000",
|
|
72
|
+
"0000011111000000",
|
|
73
|
+
"0000111111100000",
|
|
74
|
+
"0001111111110000",
|
|
75
|
+
"1111111111111111",
|
|
76
|
+
"0011111111111000",
|
|
77
|
+
"0001111111110000",
|
|
78
|
+
"0000111111100000",
|
|
79
|
+
"0000011111000000",
|
|
80
|
+
"0000001110000000",
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
export function bitmapToHexRows(bitmap) {
|
|
85
|
+
const rows = [];
|
|
86
|
+
for (let y = 0; y < bitmap.height; y += 1) {
|
|
87
|
+
let nibbleValue = 0;
|
|
88
|
+
let nibbleSize = 0;
|
|
89
|
+
let hexRow = "";
|
|
90
|
+
for (let x = 0; x < bitmap.width; x += 1) {
|
|
91
|
+
const pixel = bitmap.pixels[y * bitmap.width + x] ?? 0;
|
|
92
|
+
nibbleValue = (nibbleValue << 1) | (pixel ? 1 : 0);
|
|
93
|
+
nibbleSize += 1;
|
|
94
|
+
if (nibbleSize === 4) {
|
|
95
|
+
hexRow += nibbleValue.toString(16);
|
|
96
|
+
nibbleValue = 0;
|
|
97
|
+
nibbleSize = 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (nibbleSize > 0) {
|
|
101
|
+
const shift = 4 - nibbleSize;
|
|
102
|
+
hexRow += ((nibbleValue << shift) & 0xf).toString(16);
|
|
103
|
+
}
|
|
104
|
+
rows.push(hexRow);
|
|
105
|
+
}
|
|
106
|
+
return rows;
|
|
107
|
+
}
|
|
108
|
+
function detectShape(prompt) {
|
|
109
|
+
if (!prompt) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
for (const shape of SHAPES) {
|
|
113
|
+
if (shape.keywords.test(prompt)) {
|
|
114
|
+
return { name: shape.name, bitmap: patternToBitmap(shape.pattern) };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
function patternToBitmap(pattern) {
|
|
120
|
+
const height = pattern.length;
|
|
121
|
+
const width = pattern.reduce((max, row) => Math.max(max, row.length), 0);
|
|
122
|
+
const pixels = new Uint8Array(width * height);
|
|
123
|
+
for (let y = 0; y < height; y += 1) {
|
|
124
|
+
const row = pattern[y] ?? "";
|
|
125
|
+
for (let x = 0; x < width; x += 1) {
|
|
126
|
+
const ch = row[x] ?? "0";
|
|
127
|
+
const bit = ch === "1" || ch === "#" || ch === "X" || ch === "@";
|
|
128
|
+
pixels[y * width + x] = bit ? 1 : 0;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return { width, height, pixels };
|
|
132
|
+
}
|
|
133
|
+
function deriveText(options) {
|
|
134
|
+
if (options.text && options.text.trim().length > 0) {
|
|
135
|
+
return { text: options.text.trim(), sourceText: options.text.trim() };
|
|
136
|
+
}
|
|
137
|
+
const prompt = options.prompt?.trim();
|
|
138
|
+
if (!prompt || prompt.length === 0) {
|
|
139
|
+
throw new Error("Expected either prompt or text to describe the image");
|
|
140
|
+
}
|
|
141
|
+
const quoted = prompt.match(/"([^"]+)"/);
|
|
142
|
+
if (quoted?.[1]) {
|
|
143
|
+
return { text: quoted[1].trim(), sourceText: quoted[1].trim() };
|
|
144
|
+
}
|
|
145
|
+
const backtick = prompt.match(/`([^`]+)`/);
|
|
146
|
+
if (backtick?.[1]) {
|
|
147
|
+
return { text: backtick[1].trim(), sourceText: backtick[1].trim() };
|
|
148
|
+
}
|
|
149
|
+
const short = prompt.length <= 40 ? prompt : prompt.slice(0, 40);
|
|
150
|
+
return { text: short.trim(), sourceText: short.trim() };
|
|
151
|
+
}
|
|
152
|
+
function wrapTextLines(source, maxCols, maxRows) {
|
|
153
|
+
const lines = [];
|
|
154
|
+
const rawLines = source.split(/\r?\n/);
|
|
155
|
+
for (const rawLine of rawLines) {
|
|
156
|
+
let line = rawLine.trim();
|
|
157
|
+
if (line.length === 0) {
|
|
158
|
+
if (lines.length < maxRows) {
|
|
159
|
+
lines.push("");
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
line = line.replace(/\s+/g, " ");
|
|
164
|
+
while (line.length > 0 && lines.length < maxRows) {
|
|
165
|
+
if (line.length <= maxCols) {
|
|
166
|
+
lines.push(line);
|
|
167
|
+
line = "";
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
// Try to break on whitespace
|
|
171
|
+
let breakIndex = line.lastIndexOf(" ", maxCols);
|
|
172
|
+
if (breakIndex <= 0) {
|
|
173
|
+
breakIndex = maxCols;
|
|
174
|
+
}
|
|
175
|
+
const chunk = line.slice(0, breakIndex);
|
|
176
|
+
lines.push(chunk.trim());
|
|
177
|
+
line = line.slice(breakIndex).trim();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (lines.length >= maxRows) {
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (lines.length === 0) {
|
|
185
|
+
lines.push("");
|
|
186
|
+
}
|
|
187
|
+
return lines;
|
|
188
|
+
}
|
|
189
|
+
function renderTextToBitmap(text, options) {
|
|
190
|
+
const maxCols = Math.max(1, Math.min(40, Math.floor(options.maxWidth / 8)));
|
|
191
|
+
const maxRows = Math.max(1, Math.min(25, Math.floor(options.maxHeight / 8)));
|
|
192
|
+
const lines = wrapTextLines(text, maxCols, maxRows);
|
|
193
|
+
const glyphs = getChargenGlyphs();
|
|
194
|
+
const spaceGlyph = findGlyphByBasicChar(" ") ?? glyphs.find((g) => g.petsciiCode === 32);
|
|
195
|
+
const questionGlyph = findGlyphByBasicChar("?") ?? spaceGlyph;
|
|
196
|
+
const maxLineLength = lines.reduce((max, line) => Math.max(max, line.length), 0);
|
|
197
|
+
const width = Math.max(1, Math.min(options.maxWidth, maxLineLength * 8));
|
|
198
|
+
const height = Math.max(1, Math.min(options.maxHeight, lines.length * 8));
|
|
199
|
+
const pixels = new Uint8Array(width * height);
|
|
200
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
201
|
+
const line = lines[lineIndex] ?? "";
|
|
202
|
+
for (let charIndex = 0; charIndex < line.length; charIndex += 1) {
|
|
203
|
+
const char = line[charIndex];
|
|
204
|
+
const glyph = findGlyphByBasicChar(char) ?? findGlyphByBasicChar(char.toUpperCase())
|
|
205
|
+
?? findGlyphByBasicChar(char.toLowerCase()) ?? questionGlyph ?? spaceGlyph;
|
|
206
|
+
if (!glyph) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
for (let row = 0; row < 8; row += 1) {
|
|
210
|
+
const glyphRow = glyph.bitmap[row] ?? 0;
|
|
211
|
+
for (let col = 0; col < 8; col += 1) {
|
|
212
|
+
const bit = (glyphRow >> (7 - col)) & 0x1;
|
|
213
|
+
const x = charIndex * 8 + col;
|
|
214
|
+
const y = lineIndex * 8 + row;
|
|
215
|
+
if (x >= width || y >= height) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
pixels[y * width + x] = bit;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return { width, height, pixels };
|
|
224
|
+
}
|
|
225
|
+
function splitIntoBlocks(bitmap) {
|
|
226
|
+
const columns = Math.max(1, Math.ceil(bitmap.width / 8));
|
|
227
|
+
const rows = Math.max(1, Math.ceil(bitmap.height / 8));
|
|
228
|
+
const blocks = [];
|
|
229
|
+
for (let rowBlock = 0; rowBlock < rows; rowBlock += 1) {
|
|
230
|
+
for (let colBlock = 0; colBlock < columns; colBlock += 1) {
|
|
231
|
+
const block = new Uint8Array(8);
|
|
232
|
+
for (let localRow = 0; localRow < 8; localRow += 1) {
|
|
233
|
+
let value = 0;
|
|
234
|
+
for (let localCol = 0; localCol < 8; localCol += 1) {
|
|
235
|
+
const x = colBlock * 8 + localCol;
|
|
236
|
+
const y = rowBlock * 8 + localRow;
|
|
237
|
+
value = (value << 1) | (x < bitmap.width && y < bitmap.height ? (bitmap.pixels[y * bitmap.width + x] ?? 0) : 0);
|
|
238
|
+
}
|
|
239
|
+
block[localRow] = value & 0xff;
|
|
240
|
+
}
|
|
241
|
+
blocks.push(block);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return { blocks, columns, rows };
|
|
245
|
+
}
|
|
246
|
+
function distanceBetween(block, glyph) {
|
|
247
|
+
let score = 0;
|
|
248
|
+
for (let i = 0; i < 8; i += 1) {
|
|
249
|
+
const blockRow = block[i] ?? 0;
|
|
250
|
+
const glyphRow = glyph.bitmap[i] ?? 0;
|
|
251
|
+
score += POPCOUNT[blockRow ^ glyphRow];
|
|
252
|
+
}
|
|
253
|
+
return score;
|
|
254
|
+
}
|
|
255
|
+
export function bitmapToPetsciiCodes(bitmap) {
|
|
256
|
+
const { blocks, columns, rows } = splitIntoBlocks(bitmap);
|
|
257
|
+
const glyphs = getChargenGlyphs();
|
|
258
|
+
const codes = [];
|
|
259
|
+
const matchedGlyphs = [];
|
|
260
|
+
for (const block of blocks) {
|
|
261
|
+
let bestMatch;
|
|
262
|
+
let bestScore = Number.POSITIVE_INFINITY;
|
|
263
|
+
for (const glyph of glyphs) {
|
|
264
|
+
const score = distanceBetween(block, glyph);
|
|
265
|
+
if (score < bestScore) {
|
|
266
|
+
bestScore = score;
|
|
267
|
+
bestMatch = glyph;
|
|
268
|
+
if (score === 0) {
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const selected = bestMatch ?? glyphs[0];
|
|
274
|
+
codes.push(selected.petsciiCode);
|
|
275
|
+
matchedGlyphs.push(selected);
|
|
276
|
+
}
|
|
277
|
+
return { codes, glyphs: matchedGlyphs, columns, rows };
|
|
278
|
+
}
|
|
279
|
+
function buildBasicProgram(params) {
|
|
280
|
+
const lines = [];
|
|
281
|
+
let lineNumber = 10;
|
|
282
|
+
const border = Number.isFinite(params.borderColor) ? Number(params.borderColor) & 0xff : 6;
|
|
283
|
+
const background = Number.isFinite(params.backgroundColor) ? Number(params.backgroundColor) & 0xff : 0;
|
|
284
|
+
lines.push(`${lineNumber} POKE 53280,${border}:POKE 53281,${background}:PRINT CHR$(147)`);
|
|
285
|
+
lineNumber += 10;
|
|
286
|
+
if (Number.isFinite(params.foregroundColor)) {
|
|
287
|
+
const fg = Number(params.foregroundColor) & 0xff;
|
|
288
|
+
lines.push(`${lineNumber} POKE 646,${fg}`);
|
|
289
|
+
lineNumber += 10;
|
|
290
|
+
}
|
|
291
|
+
lines.push(`${lineNumber} FORY=0TO${Math.max(0, params.rows - 1)}`);
|
|
292
|
+
lineNumber += 10;
|
|
293
|
+
lines.push(`${lineNumber} FORX=0TO${Math.max(0, params.columns - 1)}`);
|
|
294
|
+
lineNumber += 10;
|
|
295
|
+
lines.push(`${lineNumber} READC:PRINT CHR$(C);`);
|
|
296
|
+
lineNumber += 10;
|
|
297
|
+
lines.push(`${lineNumber} NEXTX:PRINT`);
|
|
298
|
+
lineNumber += 10;
|
|
299
|
+
lines.push(`${lineNumber} NEXTY`);
|
|
300
|
+
lineNumber += 10;
|
|
301
|
+
const chunkSize = 16;
|
|
302
|
+
for (let i = 0; i < params.codes.length; i += chunkSize) {
|
|
303
|
+
const slice = params.codes.slice(i, i + chunkSize);
|
|
304
|
+
const dataLine = slice.join(",");
|
|
305
|
+
lines.push(`${lineNumber} DATA ${dataLine}`);
|
|
306
|
+
lineNumber += 10;
|
|
307
|
+
}
|
|
308
|
+
lines.push(`${lineNumber} END`);
|
|
309
|
+
return lines.join("\n");
|
|
310
|
+
}
|
|
311
|
+
export function createPetsciiArt(options) {
|
|
312
|
+
const maxWidth = Math.max(1, Math.min(DEFAULT_MAX_WIDTH, options.maxWidth ?? DEFAULT_MAX_WIDTH));
|
|
313
|
+
const maxHeight = Math.max(1, Math.min(DEFAULT_MAX_HEIGHT, options.maxHeight ?? DEFAULT_MAX_HEIGHT));
|
|
314
|
+
let bitmap;
|
|
315
|
+
let usedShape;
|
|
316
|
+
let sourceText;
|
|
317
|
+
if (options.bitmap) {
|
|
318
|
+
bitmap = options.bitmap;
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
const detectedShape = detectShape(options.prompt);
|
|
322
|
+
if (detectedShape) {
|
|
323
|
+
bitmap = detectedShape.bitmap;
|
|
324
|
+
usedShape = detectedShape.name;
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
const derived = deriveText(options);
|
|
328
|
+
sourceText = derived.sourceText;
|
|
329
|
+
bitmap = renderTextToBitmap(derived.text, { maxWidth, maxHeight });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const rowHex = bitmapToHexRows(bitmap);
|
|
333
|
+
const bitmapHex = rowHex.join("");
|
|
334
|
+
const converted = bitmapToPetsciiCodes(bitmap);
|
|
335
|
+
const codes = converted.codes;
|
|
336
|
+
const program = buildBasicProgram({
|
|
337
|
+
codes,
|
|
338
|
+
columns: converted.columns,
|
|
339
|
+
rows: converted.rows,
|
|
340
|
+
borderColor: options.borderColor,
|
|
341
|
+
backgroundColor: options.backgroundColor,
|
|
342
|
+
foregroundColor: options.foregroundColor,
|
|
343
|
+
});
|
|
344
|
+
return {
|
|
345
|
+
program,
|
|
346
|
+
bitmap,
|
|
347
|
+
bitmapHex,
|
|
348
|
+
rowHex,
|
|
349
|
+
charColumns: converted.columns,
|
|
350
|
+
charRows: converted.rows,
|
|
351
|
+
petsciiCodes: codes,
|
|
352
|
+
glyphs: converted.glyphs,
|
|
353
|
+
sourceText,
|
|
354
|
+
usedShape,
|
|
355
|
+
};
|
|
356
|
+
}
|
package/dist/platform.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const PLATFORM_FEATURES = {
|
|
2
|
+
c64u: {
|
|
3
|
+
features: [
|
|
4
|
+
"ultimate-rest-api",
|
|
5
|
+
"hardware-execution",
|
|
6
|
+
"sid-control",
|
|
7
|
+
"drive-management",
|
|
8
|
+
"printer-integration",
|
|
9
|
+
"streaming",
|
|
10
|
+
],
|
|
11
|
+
limited: [],
|
|
12
|
+
},
|
|
13
|
+
vice: {
|
|
14
|
+
features: ["software-emulation", "prg-autostart"],
|
|
15
|
+
limited: ["no-rest-api", "no-drive-management", "limited-sid"],
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
let currentPlatform = process.env.C64_MODE === "vice" ? "vice" : "c64u";
|
|
19
|
+
export function getPlatformStatus() {
|
|
20
|
+
const spec = PLATFORM_FEATURES[currentPlatform];
|
|
21
|
+
return {
|
|
22
|
+
id: currentPlatform,
|
|
23
|
+
features: spec.features,
|
|
24
|
+
limitedFeatures: spec.limited,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function setPlatform(target) {
|
|
28
|
+
if (!PLATFORM_FEATURES[target]) {
|
|
29
|
+
throw new Error(`Unsupported platform id: ${target}`);
|
|
30
|
+
}
|
|
31
|
+
currentPlatform = target;
|
|
32
|
+
return getPlatformStatus();
|
|
33
|
+
}
|
|
34
|
+
export function isPlatformSupported(platform, supported) {
|
|
35
|
+
if (!supported || supported.length === 0) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return supported.includes(platform);
|
|
39
|
+
}
|
|
40
|
+
export function describePlatformCapabilities(tools) {
|
|
41
|
+
const map = {
|
|
42
|
+
c64u: { available: [], unsupported: [] },
|
|
43
|
+
vice: { available: [], unsupported: [] },
|
|
44
|
+
};
|
|
45
|
+
for (const tool of tools) {
|
|
46
|
+
const supported = tool.metadata.platforms ?? ["c64u"];
|
|
47
|
+
for (const id of ["c64u", "vice"]) {
|
|
48
|
+
if (supported.includes(id)) {
|
|
49
|
+
map[id].available.push(tool.name);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
map[id].unsupported.push(tool.name);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
platforms: {
|
|
58
|
+
c64u: {
|
|
59
|
+
features: PLATFORM_FEATURES.c64u.features,
|
|
60
|
+
limited_features: PLATFORM_FEATURES.c64u.limited,
|
|
61
|
+
tools: map.c64u.available.sort(),
|
|
62
|
+
unsupported_tools: map.c64u.unsupported.sort(),
|
|
63
|
+
},
|
|
64
|
+
vice: {
|
|
65
|
+
features: PLATFORM_FEATURES.vice.features,
|
|
66
|
+
limited_features: PLATFORM_FEATURES.vice.limited,
|
|
67
|
+
tools: map.vice.available.sort(),
|
|
68
|
+
unsupported_tools: map.vice.unsupported.sort(),
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function getAllPlatformStatuses() {
|
|
74
|
+
return Object.keys(PLATFORM_FEATURES).map((id) => {
|
|
75
|
+
const spec = PLATFORM_FEATURES[id];
|
|
76
|
+
return {
|
|
77
|
+
id,
|
|
78
|
+
features: spec.features,
|
|
79
|
+
limitedFeatures: spec.limited,
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
}
|