c64bridge 0.9.1 → 0.9.3

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 (46) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +115 -15
  3. package/data/assembly/assembly-spec.md +1 -1
  4. package/data/basic/rom-routines.md +269 -0
  5. package/data/context/bootstrap.md +4 -4
  6. package/data/context/fast-paths.md +3 -1
  7. package/data/graphics/petscii-style-guide.md +1 -1
  8. package/data/graphics/sprite-charset-best-practices.md +14 -13
  9. package/data/io/cia-spec.md +2 -2
  10. package/data/{memory/kernal-memory-map.md → kernal/rom-routines.md} +3 -2
  11. package/data/memory/low-memory-map.md +1 -0
  12. package/data/memory/memory-map.md +13 -0
  13. package/data/sound/sid-file-structure.md +5 -5
  14. package/data/sound/sid-programming-best-practices.md +5 -5
  15. package/data/vice/vice-binary-monitor-spec.md +5 -6
  16. package/dist/.tsbuildinfo +1 -1
  17. package/dist/c64Client.js +33 -15
  18. package/dist/config.js +22 -0
  19. package/dist/mcp-server.js +17 -5
  20. package/dist/rag/knowledgeIndex.js +54 -9
  21. package/dist/rag/resourceUris.js +2 -0
  22. package/dist/sidwaveCompiler.js +1 -1
  23. package/dist/tools/input.js +5 -7
  24. package/dist/tools/memory.js +11 -2
  25. package/dist/tools/programRunners.js +3 -3
  26. package/dist/tools/responses.js +14 -1
  27. package/dist/tools/{assember.js → translation/assembler.js} +56 -162
  28. package/dist/tools/{basicTokenizer.js → translation/basicTokenizer.js} +1 -1
  29. package/dist/tools/translation/disassembler.js +150 -0
  30. package/dist/tools/translation/opcodeInventory.js +126 -0
  31. package/doc/developer.md +3 -2
  32. package/doc/reviews/audit.md +143 -0
  33. package/mcp/resources.json +60 -7
  34. package/mcp/schemas/c64_input.schema.json +231 -113
  35. package/mcp/server.json +1 -1
  36. package/mcp/tools.json +234 -115
  37. package/mcp.json +8 -4
  38. package/package.json +12 -8
  39. package/scripts/c64-cli.mjs +2 -2
  40. package/scripts/run-coverage.mjs +54 -3
  41. package/scripts/run-tests.mjs +1 -0
  42. package/scripts/run-tests.ts +1 -0
  43. package/scripts/update-readme.ts +1 -0
  44. package/server.json +3 -3
  45. package/dist/tools/disassembler.js +0 -478
  46. /package/dist/tools/{symbolRegistry.js → translation/symbolRegistry.js} +0 -0
@@ -6,6 +6,7 @@ Licensed under the GNU General Public License v2.0 or later.
6
6
  See <https://www.gnu.org/licenses/> for details.
7
7
  */
8
8
  import { Buffer } from "node:buffer";
9
+ import { OPCODE_INVENTORY } from "./opcodeInventory.js";
9
10
  export class AssemblyError extends Error {
10
11
  constructor(message, location) {
11
12
  super(`${location.file}:${location.line}: ${message}`);
@@ -20,6 +21,7 @@ var AddressingMode;
20
21
  AddressingMode["ZeroPage"] = "zeroPage";
21
22
  AddressingMode["ZeroPageX"] = "zeroPageX";
22
23
  AddressingMode["ZeroPageY"] = "zeroPageY";
24
+ AddressingMode["ZeroPageS"] = "zeroPageS";
23
25
  AddressingMode["Absolute"] = "absolute";
24
26
  AddressingMode["AbsoluteX"] = "absoluteX";
25
27
  AddressingMode["AbsoluteY"] = "absoluteY";
@@ -519,7 +521,19 @@ function parseOperand(stream) {
519
521
  }
520
522
  if (first.kind === "punct" && first.value === "(") {
521
523
  stream.consumePunct("(");
522
- const expr = parseExpression(stream, new Set([")"]));
524
+ const expr = parseExpression(stream, new Set([",", ")"]));
525
+ if (stream.peek()?.kind === "punct" && stream.peek()?.value === ",") {
526
+ stream.consumePunct(",");
527
+ const register = stream.consumeSymbol("Expected X before ')'").toUpperCase();
528
+ if (register !== "X") {
529
+ throw new Error("Only X is allowed inside pre-indexed indirect operands");
530
+ }
531
+ stream.consumePunct(")", "Expected ')' to close indirect operand");
532
+ if (!stream.isAtEnd()) {
533
+ throw new Error("Unexpected tokens after indirect operand");
534
+ }
535
+ return { kind: "indirect", expr, register: "X" };
536
+ }
523
537
  stream.consumePunct(")", "Expected ')' to close indirect operand");
524
538
  if (stream.isAtEnd()) {
525
539
  return { kind: "indirect", expr };
@@ -545,9 +559,9 @@ function parseOperand(stream) {
545
559
  const next = stream.peek();
546
560
  if (next && next.kind === "punct" && next.value === ",") {
547
561
  stream.consumePunct(",");
548
- const register = stream.consumeSymbol("Expected X or Y after comma").toUpperCase();
549
- if (register !== "X" && register !== "Y") {
550
- throw new Error("Only X or Y are allowed after comma");
562
+ const register = stream.consumeSymbol("Expected X, Y, or S after comma").toUpperCase();
563
+ if (register !== "X" && register !== "Y" && register !== "S") {
564
+ throw new Error("Only X, Y, or S are allowed after comma");
551
565
  }
552
566
  return { kind: "expression", expr, register };
553
567
  }
@@ -830,6 +844,7 @@ class Assembler {
830
844
  case AddressingMode.ZeroPage:
831
845
  case AddressingMode.ZeroPageX:
832
846
  case AddressingMode.ZeroPageY:
847
+ case AddressingMode.ZeroPageS:
833
848
  case AddressingMode.IndirectX:
834
849
  case AddressingMode.IndirectY:
835
850
  this.writeByte(info.value ?? 0, location);
@@ -1040,6 +1055,11 @@ function chooseAddressingMode(mnemonic, operand, value, pass, location) {
1040
1055
  return AddressingMode.AbsoluteY;
1041
1056
  }
1042
1057
  }
1058
+ else if (register === "S") {
1059
+ if (available.has(AddressingMode.ZeroPageS)) {
1060
+ return AddressingMode.ZeroPageS;
1061
+ }
1062
+ }
1043
1063
  else {
1044
1064
  if (available.has(AddressingMode.ZeroPage) && value !== undefined && fitsByte(value)) {
1045
1065
  return AddressingMode.ZeroPage;
@@ -1066,6 +1086,7 @@ function instructionSize(mode) {
1066
1086
  case AddressingMode.ZeroPage:
1067
1087
  case AddressingMode.ZeroPageX:
1068
1088
  case AddressingMode.ZeroPageY:
1089
+ case AddressingMode.ZeroPageS:
1069
1090
  case AddressingMode.IndirectX:
1070
1091
  case AddressingMode.IndirectY:
1071
1092
  case AddressingMode.Relative:
@@ -1095,6 +1116,7 @@ function normalizeOperandValue(mode, value, locationCounterOrLocation, locationM
1095
1116
  case AddressingMode.ZeroPage:
1096
1117
  case AddressingMode.ZeroPageX:
1097
1118
  case AddressingMode.ZeroPageY:
1119
+ case AddressingMode.ZeroPageS:
1098
1120
  case AddressingMode.IndirectX:
1099
1121
  case AddressingMode.IndirectY:
1100
1122
  if (value === undefined)
@@ -1141,168 +1163,40 @@ function fitsByte(value) {
1141
1163
  function formatHex(value) {
1142
1164
  return `$${value.toString(16).toUpperCase().padStart(4, "0")}`;
1143
1165
  }
1166
+ const OPCODE_MODE_TO_ADDRESSING_MODE = {
1167
+ implied: AddressingMode.Implied,
1168
+ accumulator: AddressingMode.Accumulator,
1169
+ immediate: AddressingMode.Immediate,
1170
+ zeroPage: AddressingMode.ZeroPage,
1171
+ zeroPageX: AddressingMode.ZeroPageX,
1172
+ zeroPageY: AddressingMode.ZeroPageY,
1173
+ zeroPageS: AddressingMode.ZeroPageS,
1174
+ absolute: AddressingMode.Absolute,
1175
+ absoluteX: AddressingMode.AbsoluteX,
1176
+ absoluteY: AddressingMode.AbsoluteY,
1177
+ indirect: AddressingMode.Indirect,
1178
+ indirectX: AddressingMode.IndirectX,
1179
+ indirectY: AddressingMode.IndirectY,
1180
+ relative: AddressingMode.Relative,
1181
+ };
1144
1182
  function buildInstructionTable() {
1145
1183
  const table = new Map();
1146
- for (const entry of INSTRUCTION_DATA) {
1147
- const mnemonic = entry.mnemonic;
1148
- if (!table.has(mnemonic)) {
1149
- table.set(mnemonic, new Map());
1184
+ const entryByMnemonicAndMode = new Map();
1185
+ for (const entry of OPCODE_INVENTORY) {
1186
+ const mode = OPCODE_MODE_TO_ADDRESSING_MODE[entry.mode];
1187
+ const key = `${entry.mnemonic}\u0000${mode}`;
1188
+ const existing = entryByMnemonicAndMode.get(key);
1189
+ // The inventory is sorted by opcode, but assembly should prefer official
1190
+ // encodings over undocumented aliases for duplicate mnemonic/mode pairs.
1191
+ if (existing && (!existing.illegal || entry.illegal)) {
1192
+ continue;
1193
+ }
1194
+ entryByMnemonicAndMode.set(key, { opcode: entry.opcode, illegal: entry.illegal });
1195
+ if (!table.has(entry.mnemonic)) {
1196
+ table.set(entry.mnemonic, new Map());
1150
1197
  }
1151
- table.get(mnemonic).set(entry.mode, entry.opcode);
1198
+ table.get(entry.mnemonic).set(mode, entry.opcode);
1152
1199
  }
1153
1200
  return table;
1154
1201
  }
1155
- const INSTRUCTION_DATA = [
1156
- { mnemonic: "ADC", mode: AddressingMode.Immediate, opcode: 0x69 },
1157
- { mnemonic: "ADC", mode: AddressingMode.ZeroPage, opcode: 0x65 },
1158
- { mnemonic: "ADC", mode: AddressingMode.ZeroPageX, opcode: 0x75 },
1159
- { mnemonic: "ADC", mode: AddressingMode.Absolute, opcode: 0x6d },
1160
- { mnemonic: "ADC", mode: AddressingMode.AbsoluteX, opcode: 0x7d },
1161
- { mnemonic: "ADC", mode: AddressingMode.AbsoluteY, opcode: 0x79 },
1162
- { mnemonic: "ADC", mode: AddressingMode.IndirectX, opcode: 0x61 },
1163
- { mnemonic: "ADC", mode: AddressingMode.IndirectY, opcode: 0x71 },
1164
- { mnemonic: "AND", mode: AddressingMode.Immediate, opcode: 0x29 },
1165
- { mnemonic: "AND", mode: AddressingMode.ZeroPage, opcode: 0x25 },
1166
- { mnemonic: "AND", mode: AddressingMode.ZeroPageX, opcode: 0x35 },
1167
- { mnemonic: "AND", mode: AddressingMode.Absolute, opcode: 0x2d },
1168
- { mnemonic: "AND", mode: AddressingMode.AbsoluteX, opcode: 0x3d },
1169
- { mnemonic: "AND", mode: AddressingMode.AbsoluteY, opcode: 0x39 },
1170
- { mnemonic: "AND", mode: AddressingMode.IndirectX, opcode: 0x21 },
1171
- { mnemonic: "AND", mode: AddressingMode.IndirectY, opcode: 0x31 },
1172
- { mnemonic: "ASL", mode: AddressingMode.Accumulator, opcode: 0x0a },
1173
- { mnemonic: "ASL", mode: AddressingMode.ZeroPage, opcode: 0x06 },
1174
- { mnemonic: "ASL", mode: AddressingMode.ZeroPageX, opcode: 0x16 },
1175
- { mnemonic: "ASL", mode: AddressingMode.Absolute, opcode: 0x0e },
1176
- { mnemonic: "ASL", mode: AddressingMode.AbsoluteX, opcode: 0x1e },
1177
- { mnemonic: "BIT", mode: AddressingMode.ZeroPage, opcode: 0x24 },
1178
- { mnemonic: "BIT", mode: AddressingMode.Absolute, opcode: 0x2c },
1179
- { mnemonic: "BPL", mode: AddressingMode.Relative, opcode: 0x10 },
1180
- { mnemonic: "BMI", mode: AddressingMode.Relative, opcode: 0x30 },
1181
- { mnemonic: "BVC", mode: AddressingMode.Relative, opcode: 0x50 },
1182
- { mnemonic: "BVS", mode: AddressingMode.Relative, opcode: 0x70 },
1183
- { mnemonic: "BCC", mode: AddressingMode.Relative, opcode: 0x90 },
1184
- { mnemonic: "BCS", mode: AddressingMode.Relative, opcode: 0xb0 },
1185
- { mnemonic: "BNE", mode: AddressingMode.Relative, opcode: 0xd0 },
1186
- { mnemonic: "BEQ", mode: AddressingMode.Relative, opcode: 0xf0 },
1187
- { mnemonic: "BRK", mode: AddressingMode.Implied, opcode: 0x00 },
1188
- { mnemonic: "CLC", mode: AddressingMode.Implied, opcode: 0x18 },
1189
- { mnemonic: "CLD", mode: AddressingMode.Implied, opcode: 0xd8 },
1190
- { mnemonic: "CLI", mode: AddressingMode.Implied, opcode: 0x58 },
1191
- { mnemonic: "CLV", mode: AddressingMode.Implied, opcode: 0xb8 },
1192
- { mnemonic: "CMP", mode: AddressingMode.Immediate, opcode: 0xc9 },
1193
- { mnemonic: "CMP", mode: AddressingMode.ZeroPage, opcode: 0xc5 },
1194
- { mnemonic: "CMP", mode: AddressingMode.ZeroPageX, opcode: 0xd5 },
1195
- { mnemonic: "CMP", mode: AddressingMode.Absolute, opcode: 0xcd },
1196
- { mnemonic: "CMP", mode: AddressingMode.AbsoluteX, opcode: 0xdd },
1197
- { mnemonic: "CMP", mode: AddressingMode.AbsoluteY, opcode: 0xd9 },
1198
- { mnemonic: "CMP", mode: AddressingMode.IndirectX, opcode: 0xc1 },
1199
- { mnemonic: "CMP", mode: AddressingMode.IndirectY, opcode: 0xd1 },
1200
- { mnemonic: "CPX", mode: AddressingMode.Immediate, opcode: 0xe0 },
1201
- { mnemonic: "CPX", mode: AddressingMode.ZeroPage, opcode: 0xe4 },
1202
- { mnemonic: "CPX", mode: AddressingMode.Absolute, opcode: 0xec },
1203
- { mnemonic: "CPY", mode: AddressingMode.Immediate, opcode: 0xc0 },
1204
- { mnemonic: "CPY", mode: AddressingMode.ZeroPage, opcode: 0xc4 },
1205
- { mnemonic: "CPY", mode: AddressingMode.Absolute, opcode: 0xcc },
1206
- { mnemonic: "DEC", mode: AddressingMode.ZeroPage, opcode: 0xc6 },
1207
- { mnemonic: "DEC", mode: AddressingMode.ZeroPageX, opcode: 0xd6 },
1208
- { mnemonic: "DEC", mode: AddressingMode.Absolute, opcode: 0xce },
1209
- { mnemonic: "DEC", mode: AddressingMode.AbsoluteX, opcode: 0xde },
1210
- { mnemonic: "DEX", mode: AddressingMode.Implied, opcode: 0xca },
1211
- { mnemonic: "DEY", mode: AddressingMode.Implied, opcode: 0x88 },
1212
- { mnemonic: "EOR", mode: AddressingMode.Immediate, opcode: 0x49 },
1213
- { mnemonic: "EOR", mode: AddressingMode.ZeroPage, opcode: 0x45 },
1214
- { mnemonic: "EOR", mode: AddressingMode.ZeroPageX, opcode: 0x55 },
1215
- { mnemonic: "EOR", mode: AddressingMode.Absolute, opcode: 0x4d },
1216
- { mnemonic: "EOR", mode: AddressingMode.AbsoluteX, opcode: 0x5d },
1217
- { mnemonic: "EOR", mode: AddressingMode.AbsoluteY, opcode: 0x59 },
1218
- { mnemonic: "EOR", mode: AddressingMode.IndirectX, opcode: 0x41 },
1219
- { mnemonic: "EOR", mode: AddressingMode.IndirectY, opcode: 0x51 },
1220
- { mnemonic: "INC", mode: AddressingMode.ZeroPage, opcode: 0xe6 },
1221
- { mnemonic: "INC", mode: AddressingMode.ZeroPageX, opcode: 0xf6 },
1222
- { mnemonic: "INC", mode: AddressingMode.Absolute, opcode: 0xee },
1223
- { mnemonic: "INC", mode: AddressingMode.AbsoluteX, opcode: 0xfe },
1224
- { mnemonic: "INX", mode: AddressingMode.Implied, opcode: 0xe8 },
1225
- { mnemonic: "INY", mode: AddressingMode.Implied, opcode: 0xc8 },
1226
- { mnemonic: "JMP", mode: AddressingMode.Absolute, opcode: 0x4c },
1227
- { mnemonic: "JMP", mode: AddressingMode.Indirect, opcode: 0x6c },
1228
- { mnemonic: "JSR", mode: AddressingMode.Absolute, opcode: 0x20 },
1229
- { mnemonic: "LDA", mode: AddressingMode.Immediate, opcode: 0xa9 },
1230
- { mnemonic: "LDA", mode: AddressingMode.ZeroPage, opcode: 0xa5 },
1231
- { mnemonic: "LDA", mode: AddressingMode.ZeroPageX, opcode: 0xb5 },
1232
- { mnemonic: "LDA", mode: AddressingMode.Absolute, opcode: 0xad },
1233
- { mnemonic: "LDA", mode: AddressingMode.AbsoluteX, opcode: 0xbd },
1234
- { mnemonic: "LDA", mode: AddressingMode.AbsoluteY, opcode: 0xb9 },
1235
- { mnemonic: "LDA", mode: AddressingMode.IndirectX, opcode: 0xa1 },
1236
- { mnemonic: "LDA", mode: AddressingMode.IndirectY, opcode: 0xb1 },
1237
- { mnemonic: "LDX", mode: AddressingMode.Immediate, opcode: 0xa2 },
1238
- { mnemonic: "LDX", mode: AddressingMode.ZeroPage, opcode: 0xa6 },
1239
- { mnemonic: "LDX", mode: AddressingMode.ZeroPageY, opcode: 0xb6 },
1240
- { mnemonic: "LDX", mode: AddressingMode.Absolute, opcode: 0xae },
1241
- { mnemonic: "LDX", mode: AddressingMode.AbsoluteY, opcode: 0xbe },
1242
- { mnemonic: "LDY", mode: AddressingMode.Immediate, opcode: 0xa0 },
1243
- { mnemonic: "LDY", mode: AddressingMode.ZeroPage, opcode: 0xa4 },
1244
- { mnemonic: "LDY", mode: AddressingMode.ZeroPageX, opcode: 0xb4 },
1245
- { mnemonic: "LDY", mode: AddressingMode.Absolute, opcode: 0xac },
1246
- { mnemonic: "LDY", mode: AddressingMode.AbsoluteX, opcode: 0xbc },
1247
- { mnemonic: "LSR", mode: AddressingMode.Accumulator, opcode: 0x4a },
1248
- { mnemonic: "LSR", mode: AddressingMode.ZeroPage, opcode: 0x46 },
1249
- { mnemonic: "LSR", mode: AddressingMode.ZeroPageX, opcode: 0x56 },
1250
- { mnemonic: "LSR", mode: AddressingMode.Absolute, opcode: 0x4e },
1251
- { mnemonic: "LSR", mode: AddressingMode.AbsoluteX, opcode: 0x5e },
1252
- { mnemonic: "NOP", mode: AddressingMode.Implied, opcode: 0xea },
1253
- { mnemonic: "ORA", mode: AddressingMode.Immediate, opcode: 0x09 },
1254
- { mnemonic: "ORA", mode: AddressingMode.ZeroPage, opcode: 0x05 },
1255
- { mnemonic: "ORA", mode: AddressingMode.ZeroPageX, opcode: 0x15 },
1256
- { mnemonic: "ORA", mode: AddressingMode.Absolute, opcode: 0x0d },
1257
- { mnemonic: "ORA", mode: AddressingMode.AbsoluteX, opcode: 0x1d },
1258
- { mnemonic: "ORA", mode: AddressingMode.AbsoluteY, opcode: 0x19 },
1259
- { mnemonic: "ORA", mode: AddressingMode.IndirectX, opcode: 0x01 },
1260
- { mnemonic: "ORA", mode: AddressingMode.IndirectY, opcode: 0x11 },
1261
- { mnemonic: "PHA", mode: AddressingMode.Implied, opcode: 0x48 },
1262
- { mnemonic: "PHP", mode: AddressingMode.Implied, opcode: 0x08 },
1263
- { mnemonic: "PLA", mode: AddressingMode.Implied, opcode: 0x68 },
1264
- { mnemonic: "PLP", mode: AddressingMode.Implied, opcode: 0x28 },
1265
- { mnemonic: "ROL", mode: AddressingMode.Accumulator, opcode: 0x2a },
1266
- { mnemonic: "ROL", mode: AddressingMode.ZeroPage, opcode: 0x26 },
1267
- { mnemonic: "ROL", mode: AddressingMode.ZeroPageX, opcode: 0x36 },
1268
- { mnemonic: "ROL", mode: AddressingMode.Absolute, opcode: 0x2e },
1269
- { mnemonic: "ROL", mode: AddressingMode.AbsoluteX, opcode: 0x3e },
1270
- { mnemonic: "ROR", mode: AddressingMode.Accumulator, opcode: 0x6a },
1271
- { mnemonic: "ROR", mode: AddressingMode.ZeroPage, opcode: 0x66 },
1272
- { mnemonic: "ROR", mode: AddressingMode.ZeroPageX, opcode: 0x76 },
1273
- { mnemonic: "ROR", mode: AddressingMode.Absolute, opcode: 0x6e },
1274
- { mnemonic: "ROR", mode: AddressingMode.AbsoluteX, opcode: 0x7e },
1275
- { mnemonic: "RTI", mode: AddressingMode.Implied, opcode: 0x40 },
1276
- { mnemonic: "RTS", mode: AddressingMode.Implied, opcode: 0x60 },
1277
- { mnemonic: "SBC", mode: AddressingMode.Immediate, opcode: 0xe9 },
1278
- { mnemonic: "SBC", mode: AddressingMode.ZeroPage, opcode: 0xe5 },
1279
- { mnemonic: "SBC", mode: AddressingMode.ZeroPageX, opcode: 0xf5 },
1280
- { mnemonic: "SBC", mode: AddressingMode.Absolute, opcode: 0xed },
1281
- { mnemonic: "SBC", mode: AddressingMode.AbsoluteX, opcode: 0xfd },
1282
- { mnemonic: "SBC", mode: AddressingMode.AbsoluteY, opcode: 0xf9 },
1283
- { mnemonic: "SBC", mode: AddressingMode.IndirectX, opcode: 0xe1 },
1284
- { mnemonic: "SBC", mode: AddressingMode.IndirectY, opcode: 0xf1 },
1285
- { mnemonic: "SEC", mode: AddressingMode.Implied, opcode: 0x38 },
1286
- { mnemonic: "SED", mode: AddressingMode.Implied, opcode: 0xf8 },
1287
- { mnemonic: "SEI", mode: AddressingMode.Implied, opcode: 0x78 },
1288
- { mnemonic: "STA", mode: AddressingMode.ZeroPage, opcode: 0x85 },
1289
- { mnemonic: "STA", mode: AddressingMode.ZeroPageX, opcode: 0x95 },
1290
- { mnemonic: "STA", mode: AddressingMode.Absolute, opcode: 0x8d },
1291
- { mnemonic: "STA", mode: AddressingMode.AbsoluteX, opcode: 0x9d },
1292
- { mnemonic: "STA", mode: AddressingMode.AbsoluteY, opcode: 0x99 },
1293
- { mnemonic: "STA", mode: AddressingMode.IndirectX, opcode: 0x81 },
1294
- { mnemonic: "STA", mode: AddressingMode.IndirectY, opcode: 0x91 },
1295
- { mnemonic: "STX", mode: AddressingMode.ZeroPage, opcode: 0x86 },
1296
- { mnemonic: "STX", mode: AddressingMode.ZeroPageY, opcode: 0x96 },
1297
- { mnemonic: "STX", mode: AddressingMode.Absolute, opcode: 0x8e },
1298
- { mnemonic: "STY", mode: AddressingMode.ZeroPage, opcode: 0x84 },
1299
- { mnemonic: "STY", mode: AddressingMode.ZeroPageX, opcode: 0x94 },
1300
- { mnemonic: "STY", mode: AddressingMode.Absolute, opcode: 0x8c },
1301
- { mnemonic: "TAX", mode: AddressingMode.Implied, opcode: 0xaa },
1302
- { mnemonic: "TAY", mode: AddressingMode.Implied, opcode: 0xa8 },
1303
- { mnemonic: "TSX", mode: AddressingMode.Implied, opcode: 0xba },
1304
- { mnemonic: "TXA", mode: AddressingMode.Implied, opcode: 0x8a },
1305
- { mnemonic: "TXS", mode: AddressingMode.Implied, opcode: 0x9a },
1306
- { mnemonic: "TYA", mode: AddressingMode.Implied, opcode: 0x98 },
1307
- ];
1308
1202
  const INSTRUCTION_TABLE = buildInstructionTable();
@@ -6,7 +6,7 @@ Licensed under the GNU General Public License v2.0 or later.
6
6
  See <https://www.gnu.org/licenses/> for details.
7
7
  */
8
8
  import { Buffer } from "node:buffer";
9
- import { asciiToPetscii, encodeStringWithNames } from "../petscii.js";
9
+ import { asciiToPetscii, encodeStringWithNames } from "../../petscii.js";
10
10
  const DEFAULT_START_ADDRESS = 0x0801;
11
11
  const REM_TOKEN = 0x8f;
12
12
  const TOKEN_TABLE = buildTokenTable();
@@ -0,0 +1,150 @@
1
+ /**
2
+ * 6502/6510 disassembler covering the full 256-opcode space, including
3
+ * undocumented (illegal) opcodes. Mnemonic naming follows the canonical
4
+ * convention used by the 1541U monitor (HLT->JAM, ASO->SLO, LSE->SRE,
5
+ * DCM->DCP, INS->ISC).
6
+ */
7
+ import { OpcodeAddressingMode as Mode, OPCODES_BY_BYTE as OPCODES, opcodeSize as modeSize, } from "./opcodeInventory.js";
8
+ function hex2(v) {
9
+ return v.toString(16).toUpperCase().padStart(2, "0");
10
+ }
11
+ function hex4(v) {
12
+ return v.toString(16).toUpperCase().padStart(4, "0");
13
+ }
14
+ function labelOrHex4(addr, symbols) {
15
+ const label = symbols?.get(addr);
16
+ return label ? `${label} ($${hex4(addr)})` : `$${hex4(addr)}`;
17
+ }
18
+ function formatOperand(mode, bytes, offset, instrAddr, symbols) {
19
+ switch (mode) {
20
+ case Mode.Imp:
21
+ return "";
22
+ case Mode.Acc:
23
+ return "A";
24
+ case Mode.Imm:
25
+ return `#$${hex2(bytes[offset + 1] ?? 0)}`;
26
+ case Mode.Zp:
27
+ return `$${hex2(bytes[offset + 1] ?? 0)}`;
28
+ case Mode.ZpX:
29
+ return `$${hex2(bytes[offset + 1] ?? 0)},X`;
30
+ case Mode.ZpY:
31
+ return `$${hex2(bytes[offset + 1] ?? 0)},Y`;
32
+ case Mode.ZpS:
33
+ return `$${hex2(bytes[offset + 1] ?? 0)},S`;
34
+ case Mode.Rel: {
35
+ const rel = bytes[offset + 1] ?? 0;
36
+ const signed = rel >= 0x80 ? rel - 0x100 : rel;
37
+ const target = (instrAddr + 2 + signed) & 0xffff;
38
+ return labelOrHex4(target, symbols);
39
+ }
40
+ case Mode.Abs: {
41
+ const lo = bytes[offset + 1] ?? 0;
42
+ const hi = bytes[offset + 2] ?? 0;
43
+ const addr = lo | (hi << 8);
44
+ return labelOrHex4(addr, symbols);
45
+ }
46
+ case Mode.AbsX: {
47
+ const lo = bytes[offset + 1] ?? 0;
48
+ const hi = bytes[offset + 2] ?? 0;
49
+ const addr = lo | (hi << 8);
50
+ return `${labelOrHex4(addr, symbols)},X`;
51
+ }
52
+ case Mode.AbsY: {
53
+ const lo = bytes[offset + 1] ?? 0;
54
+ const hi = bytes[offset + 2] ?? 0;
55
+ const addr = lo | (hi << 8);
56
+ return `${labelOrHex4(addr, symbols)},Y`;
57
+ }
58
+ case Mode.Ind: {
59
+ const lo = bytes[offset + 1] ?? 0;
60
+ const hi = bytes[offset + 2] ?? 0;
61
+ const addr = lo | (hi << 8);
62
+ return `($${hex4(addr)})`;
63
+ }
64
+ case Mode.IndX:
65
+ return `($${hex2(bytes[offset + 1] ?? 0)},X)`;
66
+ case Mode.IndY:
67
+ return `($${hex2(bytes[offset + 1] ?? 0)}),Y`;
68
+ }
69
+ }
70
+ /**
71
+ * Disassemble a block of 6502/6510 bytes.
72
+ *
73
+ * @param bytes Raw memory bytes (may be longer than needed)
74
+ * @param baseAddress The C64 memory address of `bytes[0]`
75
+ * @param countOrOptions Either a max-instruction count (legacy form) or an options object
76
+ * @param symbols Optional address->label map (legacy positional form)
77
+ */
78
+ export function disassemble(bytes, baseAddress, countOrOptions, symbols) {
79
+ const options = typeof countOrOptions === "object" && countOrOptions !== null
80
+ ? countOrOptions
81
+ : { count: countOrOptions, symbols };
82
+ const limit = options.count ?? Infinity;
83
+ const symMap = options.symbols ?? symbols;
84
+ const allowUndocumented = options.allowUndocumented !== false;
85
+ const lines = [];
86
+ let offset = 0;
87
+ while (offset < bytes.length && lines.length < limit) {
88
+ const instrAddr = (baseAddress + offset) & 0xffff;
89
+ const opcode = bytes[offset] ?? 0;
90
+ const op = OPCODES[opcode];
91
+ const label = symMap?.get(instrAddr);
92
+ if (op.illegal && !allowUndocumented) {
93
+ lines.push({
94
+ address: instrAddr,
95
+ bytes: [opcode],
96
+ label,
97
+ mnemonic: "???",
98
+ operand: `$${hex2(opcode)}`,
99
+ undocumented: true,
100
+ });
101
+ offset += 1;
102
+ continue;
103
+ }
104
+ const size = modeSize(op.mode);
105
+ const instrBytes = [];
106
+ for (let i = 0; i < size && offset + i < bytes.length; i += 1) {
107
+ instrBytes.push(bytes[offset + i] ?? 0);
108
+ }
109
+ const operand = formatOperand(op.mode, bytes, offset, instrAddr, symMap);
110
+ lines.push({
111
+ address: instrAddr,
112
+ bytes: instrBytes,
113
+ label,
114
+ mnemonic: op.mnemonic,
115
+ operand,
116
+ undocumented: op.illegal,
117
+ });
118
+ offset += size;
119
+ }
120
+ return lines;
121
+ }
122
+ /**
123
+ * Render disassembly lines as a human-readable string.
124
+ * Format: [label:\n] $ADDR XX XX XX MNEM operand ; *undocumented*
125
+ */
126
+ export function formatDisassembly(lines) {
127
+ const parts = [];
128
+ for (const line of lines) {
129
+ if (line.label) {
130
+ parts.push(`${line.label}:`);
131
+ }
132
+ const addrStr = `$${hex4(line.address)}`;
133
+ const byteStr = line.bytes.map((b) => hex2(b)).join(" ").padEnd(8);
134
+ const mnemonic = line.undocumented ? `${line.mnemonic}*` : line.mnemonic;
135
+ const instrStr = line.operand
136
+ ? `${mnemonic} ${line.operand}`
137
+ : mnemonic;
138
+ parts.push(` ${addrStr} ${byteStr} ${instrStr}`);
139
+ }
140
+ return parts.join("\n");
141
+ }
142
+ /** Returns true when the opcode is undocumented per the 1541U reference. */
143
+ export function isUndocumentedOpcode(opcode) {
144
+ return OPCODES[opcode & 0xff].illegal;
145
+ }
146
+ /** Lookup helper used by tests to verify table coverage. */
147
+ export function opcodeMetadata(opcode) {
148
+ const entry = OPCODES[opcode & 0xff];
149
+ return { mnemonic: entry.mnemonic, undocumented: entry.illegal, size: modeSize(entry.mode) };
150
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Shared 6502/6510 opcode inventory.
3
+ *
4
+ * Entries are sorted by increasing binary opcode and cover the complete
5
+ * 256-byte opcode space, including undocumented opcodes. Mnemonic naming
6
+ * follows the canonical 1541U monitor convention:
7
+ * HLT -> JAM, ASO -> SLO, LSE -> SRE, DCM -> DCP, INS -> ISC.
8
+ */
9
+ export const OpcodeAddressingMode = {
10
+ Imp: "implied",
11
+ Acc: "accumulator",
12
+ Imm: "immediate",
13
+ Zp: "zeroPage",
14
+ ZpX: "zeroPageX",
15
+ ZpY: "zeroPageY",
16
+ ZpS: "zeroPageS",
17
+ Rel: "relative",
18
+ Abs: "absolute",
19
+ AbsX: "absoluteX",
20
+ AbsY: "absoluteY",
21
+ Ind: "indirect",
22
+ IndX: "indirectX",
23
+ IndY: "indirectY",
24
+ };
25
+ const M = OpcodeAddressingMode;
26
+ const OPCODE_ROWS = [
27
+ // 0x00-0x0F
28
+ [["BRK", M.Imp], ["ORA", M.IndX], ["HLT", M.Imp], ["ASO", M.IndX], ["NOP", M.Zp], ["ORA", M.Zp], ["ASL", M.Zp], ["ASO", M.Zp], ["PHP", M.Imp], ["ORA", M.Imm], ["ASL", M.Acc], ["ANC", M.Imm], ["NOP", M.Abs], ["ORA", M.Abs], ["ASL", M.Abs], ["ASO", M.Abs]],
29
+ // 0x10-0x1F
30
+ [["BPL", M.Rel], ["ORA", M.IndY], ["HLT", M.Imp], ["ASO", M.IndY], ["NOP", M.ZpX], ["ORA", M.ZpX], ["ASL", M.ZpX], ["ASO", M.ZpX], ["CLC", M.Imp], ["ORA", M.AbsY], ["NOP", M.Imp], ["ASO", M.AbsY], ["NOP", M.AbsX], ["ORA", M.AbsX], ["ASL", M.AbsX], ["ASO", M.AbsX]],
31
+ // 0x20-0x2F
32
+ [["JSR", M.Abs], ["AND", M.IndX], ["HLT", M.Imp], ["RLA", M.IndX], ["BIT", M.Zp], ["AND", M.Zp], ["ROL", M.Zp], ["RLA", M.Zp], ["PLP", M.Imp], ["AND", M.Imm], ["ROL", M.Acc], ["ANC", M.Imm], ["BIT", M.Abs], ["AND", M.Abs], ["ROL", M.Abs], ["RLA", M.Abs]],
33
+ // 0x30-0x3F
34
+ [["BMI", M.Rel], ["AND", M.IndY], ["HLT", M.Imp], ["RLA", M.IndY], ["NOP", M.ZpX], ["AND", M.ZpX], ["ROL", M.ZpX], ["RLA", M.ZpX], ["SEC", M.Imp], ["AND", M.AbsY], ["NOP", M.Imp], ["RLA", M.AbsY], ["NOP", M.AbsX], ["AND", M.AbsX], ["ROL", M.AbsX], ["RLA", M.AbsX]],
35
+ // 0x40-0x4F
36
+ [["RTI", M.Imp], ["EOR", M.IndX], ["HLT", M.Imp], ["LSE", M.IndX], ["NOP", M.Zp], ["EOR", M.Zp], ["LSR", M.Zp], ["LSE", M.Zp], ["PHA", M.Imp], ["EOR", M.Imm], ["LSR", M.Acc], ["ALR", M.Imm], ["JMP", M.Abs], ["EOR", M.Abs], ["LSR", M.Abs], ["LSE", M.Abs]],
37
+ // 0x50-0x5F
38
+ [["BVC", M.Rel], ["EOR", M.IndY], ["HLT", M.Imp], ["LSE", M.IndY], ["NOP", M.ZpX], ["EOR", M.ZpX], ["LSR", M.ZpX], ["LSE", M.ZpX], ["CLI", M.Imp], ["EOR", M.AbsY], ["NOP", M.Imp], ["LSE", M.AbsY], ["NOP", M.AbsX], ["EOR", M.AbsX], ["LSR", M.AbsX], ["LSE", M.AbsX]],
39
+ // 0x60-0x6F
40
+ [["RTS", M.Imp], ["ADC", M.IndX], ["HLT", M.Imp], ["RRA", M.IndX], ["NOP", M.Zp], ["ADC", M.Zp], ["ROR", M.Zp], ["RRA", M.Zp], ["PLA", M.Imp], ["ADC", M.Imm], ["ROR", M.Acc], ["ARR", M.Imm], ["JMP", M.Ind], ["ADC", M.Abs], ["ROR", M.Abs], ["RRA", M.Abs]],
41
+ // 0x70-0x7F
42
+ [["BVS", M.Rel], ["ADC", M.IndY], ["HLT", M.Imp], ["RRA", M.IndY], ["NOP", M.ZpX], ["ADC", M.ZpX], ["ROR", M.ZpX], ["RRA", M.ZpX], ["SEI", M.Imp], ["ADC", M.AbsY], ["NOP", M.Imp], ["RRA", M.AbsY], ["NOP", M.AbsX], ["ADC", M.AbsX], ["ROR", M.AbsX], ["RRA", M.AbsX]],
43
+ // 0x80-0x8F
44
+ [["NOP", M.Imm], ["STA", M.IndX], ["NOP", M.Imm], ["SAX", M.IndX], ["STY", M.Zp], ["STA", M.Zp], ["STX", M.Zp], ["SAX", M.Zp], ["DEY", M.Imp], ["NOP", M.Imm], ["TXA", M.Imp], ["XAA", M.Imm], ["STY", M.Abs], ["STA", M.Abs], ["STX", M.Abs], ["SAX", M.Abs]],
45
+ // 0x90-0x9F
46
+ [["BCC", M.Rel], ["STA", M.IndY], ["HLT", M.Imp], ["AHX", M.IndY], ["STY", M.ZpX], ["STA", M.ZpX], ["STX", M.ZpY], ["SAX", M.ZpY], ["TYA", M.Imp], ["STA", M.AbsY], ["TXS", M.Imp], ["TAS", M.AbsY], ["SHY", M.AbsX], ["STA", M.AbsX], ["SHX", M.AbsY], ["AHX", M.AbsY]],
47
+ // 0xA0-0xAF
48
+ [["LDY", M.Imm], ["LDA", M.IndX], ["LDX", M.Imm], ["LAX", M.IndX], ["LDY", M.Zp], ["LDA", M.Zp], ["LDX", M.Zp], ["LAX", M.Zp], ["TAY", M.Imp], ["LDA", M.Imm], ["TAX", M.Imp], ["LAX", M.Imm], ["LDY", M.Abs], ["LDA", M.Abs], ["LDX", M.Abs], ["LAX", M.Abs]],
49
+ // 0xB0-0xBF
50
+ [["BCS", M.Rel], ["LDA", M.IndY], ["HLT", M.Imp], ["LAX", M.IndY], ["LDY", M.ZpX], ["LDA", M.ZpX], ["LDX", M.ZpY], ["LAX", M.ZpY], ["CLV", M.Imp], ["LDA", M.AbsY], ["TSX", M.Imp], ["LAS", M.AbsY], ["LDY", M.AbsX], ["LDA", M.AbsX], ["LDX", M.AbsY], ["LAX", M.AbsY]],
51
+ // 0xC0-0xCF
52
+ [["CPY", M.Imm], ["CMP", M.IndX], ["NOP", M.Imm], ["DCM", M.IndX], ["CPY", M.Zp], ["CMP", M.Zp], ["DEC", M.Zp], ["DCM", M.Zp], ["INY", M.Imp], ["CMP", M.Imm], ["DEX", M.Imp], ["AXS", M.Imm], ["CPY", M.Abs], ["CMP", M.Abs], ["DEC", M.Abs], ["DCM", M.Abs]],
53
+ // 0xD0-0xDF
54
+ [["BNE", M.Rel], ["CMP", M.IndY], ["HLT", M.Imp], ["DCM", M.IndY], ["NOP", M.ZpX], ["CMP", M.ZpX], ["DEC", M.ZpX], ["DCM", M.ZpX], ["CLD", M.Imp], ["CMP", M.AbsY], ["NOP", M.Imp], ["DCM", M.AbsY], ["NOP", M.AbsX], ["CMP", M.AbsX], ["DEC", M.AbsX], ["DCM", M.AbsX]],
55
+ // 0xE0-0xEF
56
+ [["CPX", M.Imm], ["SBC", M.IndX], ["NOP", M.Imm], ["INS", M.IndX], ["CPX", M.Zp], ["SBC", M.Zp], ["INC", M.Zp], ["INS", M.Zp], ["INX", M.Imp], ["SBC", M.Imm], ["NOP", M.Imp], ["SBC", M.Imm], ["CPX", M.Abs], ["SBC", M.Abs], ["INC", M.Abs], ["INS", M.Abs]],
57
+ // 0xF0-0xFF
58
+ [["BEQ", M.Rel], ["SBC", M.IndY], ["HLT", M.Imp], ["INS", M.IndY], ["NOP", M.ZpS], ["SBC", M.ZpX], ["INC", M.ZpX], ["INS", M.ZpX], ["SED", M.Imp], ["SBC", M.AbsY], ["NOP", M.Imp], ["INS", M.AbsY], ["NOP", M.AbsX], ["SBC", M.AbsX], ["INC", M.AbsX], ["INS", M.AbsX]],
59
+ ];
60
+ const ILLEGAL_MAP = [
61
+ 0x989C, 0x9C9C, 0x888C, 0x9C9C,
62
+ 0x889C, 0x9C9C, 0x889C, 0x9C9C,
63
+ 0x8A8D, 0xD88C, 0x8888, 0x888C,
64
+ 0x888C, 0x9C9C, 0x888C, 0x9C9C,
65
+ ];
66
+ function isIllegal(opcode) {
67
+ const row = ILLEGAL_MAP[opcode >> 4];
68
+ return ((row >> (opcode & 0x0F)) & 1) !== 0;
69
+ }
70
+ function canonical(mnemonic) {
71
+ switch (mnemonic) {
72
+ case "HLT": return "JAM";
73
+ case "ASO": return "SLO";
74
+ case "LSE": return "SRE";
75
+ case "DCM": return "DCP";
76
+ case "INS": return "ISC";
77
+ default: return mnemonic;
78
+ }
79
+ }
80
+ function buildOpcodeInventory() {
81
+ const entries = OPCODE_ROWS.flatMap((row, rowIndex) => row.map(([mnemonic, mode], columnIndex) => {
82
+ const opcode = (rowIndex << 4) | columnIndex;
83
+ return {
84
+ opcode,
85
+ mnemonic: canonical(mnemonic),
86
+ mode,
87
+ illegal: isIllegal(opcode),
88
+ };
89
+ }));
90
+ validateOpcodeInventory(entries);
91
+ return entries;
92
+ }
93
+ function validateOpcodeInventory(entries) {
94
+ if (entries.length !== 256) {
95
+ throw new Error(`Opcode inventory must contain 256 entries, found ${entries.length}`);
96
+ }
97
+ for (let opcode = 0; opcode < 256; opcode += 1) {
98
+ const entry = entries[opcode];
99
+ if (!entry || entry.opcode !== opcode) {
100
+ throw new Error(`Opcode inventory is not sorted at $${opcode.toString(16).padStart(2, "0")}`);
101
+ }
102
+ }
103
+ }
104
+ export function opcodeSize(mode) {
105
+ switch (mode) {
106
+ case M.Imp:
107
+ case M.Acc:
108
+ return 1;
109
+ case M.Imm:
110
+ case M.Zp:
111
+ case M.ZpX:
112
+ case M.ZpY:
113
+ case M.ZpS:
114
+ case M.Rel:
115
+ case M.IndX:
116
+ case M.IndY:
117
+ return 2;
118
+ case M.Abs:
119
+ case M.AbsX:
120
+ case M.AbsY:
121
+ case M.Ind:
122
+ return 3;
123
+ }
124
+ }
125
+ export const OPCODE_INVENTORY = Object.freeze(buildOpcodeInventory());
126
+ export const OPCODES_BY_BYTE = OPCODE_INVENTORY;
package/doc/developer.md CHANGED
@@ -124,9 +124,10 @@ Environment knobs: `RAG_EMBEDDINGS_DIR`, `RAG_BUILD_ON_START`, `RAG_REINDEX_INTE
124
124
 
125
125
  Tagging a release through the GitHub release UI triggers [`.github/workflows/release.yaml`](../.github/workflows/release.yaml), which publishes the npm package and then publishes the same version to the MCP Registry.
126
126
 
127
- - Required GitHub secret: `NPM_TOKEN`
127
+ - npm publication now uses trusted publishing over GitHub OIDC rather than an `NPM_TOKEN` secret
128
+ - One-time npm setup is required in the package settings for `c64bridge`: provider `GitHub Actions`, repository `chrisgleissner/c64bridge`, workflow `.github/workflows/release.yaml`, and allowed action `npm publish`
128
129
  - No dedicated MCP Registry secret is required for the GitHub-driven flow; the workflow uses `mcp-publisher login github-oidc`
129
- - The release workflow must grant `id-token: write` so GitHub Actions can mint the short-lived OIDC token used by `mcp-publisher`
130
+ - The release workflow must grant `id-token: write` so GitHub Actions can mint the short-lived OIDC tokens used by both npm trusted publishing and `mcp-publisher`
130
131
  - Keep [`package.json`](../package.json) `mcpName` aligned with [`server.json`](../server.json) `name`
131
132
  - The root [`server.json`](../server.json) is the MCP Registry manifest; [`mcp/server.json`](../mcp/server.json) remains generated MCP interface output
132
133