@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/dist/moonbase-player.js +12 -12
- package/dist/moonbase-player.js.map +3 -3
- package/package.json +6 -6
- package/remake/card.js +20 -4
- package/remake/engine.js +21 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moonbase-machines/player",
|
|
3
|
-
"version": "0.9.
|
|
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.
|
|
18
|
-
"@moonbase-machines/c64": "0.9.
|
|
19
|
-
"@moonbase-machines/charset": "0.9.
|
|
20
|
-
"@moonbase-machines/mos6502": "0.9.
|
|
21
|
-
"@moonbase-machines/vector": "0.9.
|
|
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
|
-
|
|
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)
|
|
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
|
|
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
|
-
|
|
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
|
|
328
|
-
//
|
|
329
|
-
|
|
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;
|