@moonbase-machines/player 0.9.0 → 0.9.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moonbase-machines/player",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "The player for Mark Wharton's titles of 1985 to 1995: the Original, a game's own disk on Moonbase's machines, and the Remake, its scenes on an engine; the player's own disk of saves in the browser.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -14,11 +14,11 @@
14
14
  "moonbase-play": "./bin/moonbase-play.mjs"
15
15
  },
16
16
  "dependencies": {
17
- "@moonbase-machines/apple2e": "0.9.0",
18
- "@moonbase-machines/c64": "0.9.0",
19
- "@moonbase-machines/charset": "0.9.0",
20
- "@moonbase-machines/mos6502": "0.9.0",
21
- "@moonbase-machines/vector": "0.9.0"
17
+ "@moonbase-machines/apple2e": "0.9.1",
18
+ "@moonbase-machines/c64": "0.9.1",
19
+ "@moonbase-machines/charset": "0.9.1",
20
+ "@moonbase-machines/mos6502": "0.9.1",
21
+ "@moonbase-machines/vector": "0.9.1"
22
22
  },
23
23
  "scripts": {
24
24
  "test": "node --test test/*.test.js",
package/remake/card.js CHANGED
@@ -139,7 +139,8 @@ export function cardRoutines(title) {
139
139
  if (!f && c64 && at >= 0xa000 && at < 0xc000) throw new Error('a BASIC program (Zoopak\'s NOTEBOOK or ZOOTIME): not written yet in the remake');
140
140
  if (!f) throw new Error('no ROM routine at $' + hex4(at));
141
141
  engine = e;
142
- Object.assign(cpu, { a: e.a, x: e.x, y: e.y, c: e.c, z: e.z, n: e.n, v: e.v });
142
+ // (the stack pointer as the call to the routine left it: the frames' place, less the call's two bytes)
143
+ Object.assign(cpu, { a: e.a, x: e.x, y: e.y, c: e.c, z: e.z, n: e.n, v: e.v, pc: at, sp: e.sp() - 2 & 0xff });
143
144
  // a routine that reads the keyboard (RDKEY) waits, with no key, and the step runs again
144
145
  const start = romFrom = cpu.cycles;
145
146
  const r = f(cpu);
@@ -150,6 +151,9 @@ export function cardRoutines(title) {
150
151
  e.delay += (cpu.cycles - start + 6) / CLOCK * 1000; e.cycles += cpu.cycles - start + 6;
151
152
  Object.assign(e, { a: cpu.a & 0xff, x: cpu.x & 0xff, y: cpu.y & 0xff, c: cpu.c, z: cpu.z, n: cpu.n });
152
153
  if (r === 'return') return r; // (a routine that returns from its caller too: $E206 at a statement's end)
154
+ // a routine that goes on elsewhere, not back: DOS 3.3's error exit, from a command printed through COUT, to where
155
+ // DOS goes on (rom.js `dosError`)
156
+ if (r === false && cpu.pc !== at) return { to: cpu.pc };
153
157
  };
154
158
 
155
159
  // Where an address of the lifted code is: a label in a script. The scenes all run at $AD00,
@@ -199,10 +203,11 @@ export function cardRoutines(title) {
199
203
  return [name, 'L' + hex4(at)];
200
204
  };
201
205
  // an address the ROM's routine answers: above $D000, unless the card is read there and a script
202
- // has its code there (Pieces of Eight's PO8:BLINK, over HPOSN and DRAW); on a Commodore 64, where
206
+ // has its code there (Pieces of Eight's PO8:BLINK, over HPOSN and DRAW), or below where rom.js answers DOS's own
207
+ // (INITIOHK, $A851) and no script has code; on a Commodore 64, where
203
208
  // the 6510's port shows the KERNAL ($E000 on) or BASIC ($A000 to $BFFF: Tell-A-Tale's numbers,
204
209
  // over MENU's code in the RAM under it)
205
- const romAt = (e, a) => c64 ? (a >= 0xe000 ? (e.mem[1] & 2) !== 0 : a >= 0xa000 && a < 0xc000 ? (e.mem[1] & 3) === 3 : a === 0x73 || a === 0x79) : a >= 0xd000 && !(lc.read && labelled(e, a).length);
210
+ const romAt = (e, a) => c64 ? (a >= 0xe000 ? (e.mem[1] & 2) !== 0 : a >= 0xa000 && a < 0xc000 ? (e.mem[1] & 3) === 3 : a === 0x73 || a === 0x79) : a >= 0xd000 ? !(lc.read && labelled(e, a).length) : cpu.hooks.has(a) && !labelled(e, a).length;
206
211
  // the stir of START.SYSTEM's wait: its calls are no steps
207
212
  const stirring = step => toolkit.stir && step?.at >= toolkit.stir.from && step.at <= toolkit.stir.to;
208
213
  const emit = (e, ev, quiet) => { if (quiet) e.inside++; e.emit(ev); if (quiet) e.inside--; };
@@ -235,7 +240,16 @@ export function cardRoutines(title) {
235
240
  inputs(e, name, args); emit(e, unplaced(describe(name, [], e, undefined, undefined, a => e.mem[a])), stirring(step));
236
241
  const v = c64 && VECTORED[name], to = v && (e.mem[v[0]] | e.mem[v[0] + 1] << 8);
237
242
  if (v && to !== v[1]) { const [script, label] = find(e, to); e.callInside(script, label, x => { if (GIVES[name]) x.emit(result(name, { a: x.a, x: x.x, y: x.y, c: x.c })); }, step?.at == null ? undefined : (step.at + 2) & 0xffff); return; }
238
- if (rom(e, at) === 'wait') return 'wait';
243
+ const r = rom(e, at);
244
+ if (r === 'wait') return 'wait';
245
+ // (gone on elsewhere: the code there run inside the step, the routine's return still on the stack, as the
246
+ // original's is, until that code drops it, as Pieces of Eight's handler ($93F0) does with a TXS to the frames
247
+ // under it, kept at their place for it (Engine.stackPage))
248
+ if (r?.to !== undefined) {
249
+ e.stackPage();
250
+ e.stack.push({ name: e.name, pc: e.pc, inside: true, ret: step?.at == null ? undefined : (step.at + 2) & 0xffff }); e.inside++;
251
+ return routines['goto-engine'](e, ['$' + hex4(r.to)], step);
252
+ }
239
253
  if (GIVES[name] && c64) e.emit(result(name, { a: e.a, x: e.x, y: e.y, c: e.c }));
240
254
  };
241
255
 
@@ -301,6 +315,8 @@ export function cardRoutines(title) {
301
315
  if (c64 && ENDS.has(a)) pullRegisters(e); // the end of an interrupt: the registers back, then its RTI
302
316
  back(e); return; // as its RTS: out of a routine it ends
303
317
  }
318
+ // (code the game has written since, a JMP: on through it, as Pieces of Eight's at $A6D8, into DOS's error exit)
319
+ if (!c64 && e.mem[a] === 0x4c && !labelled(e, a).length) return routines['goto-engine'](e, ['$' + hex4(e.mem[a + 1] | e.mem[a + 2] << 8)], step);
304
320
  e.enter(...find(e, a));
305
321
  },
306
322
  };
package/remake/engine.js CHANGED
@@ -324,19 +324,32 @@ export class Engine {
324
324
  }
325
325
  const f = this.stack.pop(); if (a) this.a = this.flags(f.a); else [this.n, this.z, this.c, this.v] = f.flags;
326
326
  }
327
- // the machine's own instructions a scene keeps. TSX gives the stack's place, as the
328
- // original's stack pointer would be with these frames on it, and keeps the frames under
329
- // it; TXS goes back to the frames kept at the place it is given (Detect-A-Pet's save and
327
+ // the stack's place, as the original's stack pointer would be with these frames on it: each
328
+ // frame its bytes, a call's two (its return address) and a push's one
329
+ sp() { return 0xff - this.stack.reduce((n, f) => n + (f.name === undefined ? 1 : f.half ? 1 : 2), 0) & 0xff; }
330
+ // the frames kept at their place, for a TXS to go back to, and their bytes in the stack page as
331
+ // the original's are there: a call's return address, high byte first from the bottom, as JSR
332
+ // pushes it (half of one pulled, its high byte), and a push's byte, PHP's as the 6502's (Pieces of Eight's DOS error
333
+ // handler, $93F0, reads the return address under its own to see who called)
334
+ stackPage() {
335
+ const sp = this.sp();
336
+ (this.stacks ??= new Map()).set(sp, this.stack.map(f => ({ ...f })));
337
+ let at = 0xff;
338
+ for (const f of this.stack) {
339
+ const r = f.ret ?? 0;
340
+ if (f.name === undefined) this.mem[0x100 + at--] = f.a ?? (f.flags ? f.flags[0] << 7 | f.flags[3] << 6 | 0x30 | f.flags[1] << 1 | f.flags[2] : 0);
341
+ else { this.mem[0x100 + at--] = r >> 8; if (!f.half) this.mem[0x100 + at--] = r & 0xff; }
342
+ }
343
+ return sp;
344
+ }
345
+ // the machine's own instructions a scene keeps. TSX gives the stack's place (`stackPage`); TXS
346
+ // goes back to the frames kept at the place it is given (Detect-A-Pet's save and
330
347
  // load keep the stack page and its pointer), and to none at any other, as the town's
331
348
  // resets the stack and drops every script called. SED and CLD set decimal mode, and SEI and
332
349
  // CLI the mask a Commodore 64's interrupt waits on (card.js). The rest (a byte that
333
350
  // is no instruction) change nothing the remake holds.
334
351
  asm(op) {
335
- if (op === 'TSX') {
336
- const sp = 0xff - this.stack.reduce((n, f) => n + (f.name === undefined ? 1 : f.half ? 1 : 2), 0) & 0xff;
337
- (this.stacks ??= new Map()).set(sp, this.stack.map(f => ({ ...f })));
338
- this.x = this.flags(sp);
339
- }
352
+ if (op === 'TSX') this.x = this.flags(this.stackPage());
340
353
  if (op === 'TXS') {
341
354
  const kept = this.stacks?.get(this.x);
342
355
  this.stack = kept ? kept.map(f => ({ ...f })) : []; this.visited = null; this.inside = this.stack.filter(f => f.inside).length;