jsbeeb 1.10.0 → 1.12.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/src/models.js CHANGED
@@ -98,8 +98,8 @@ const masterSwram = [
98
98
 
99
99
  export const allModels = [
100
100
  new Model({
101
- name: "BBC B with DFS 1.2",
102
- synonyms: ["B-DFS1.2"],
101
+ name: "BBC B with 8271 (DFS 1.2)",
102
+ synonyms: ["B-DFS1.2", "BBC B with DFS 1.2"],
103
103
  os: ["os.rom", "BASIC.ROM", "b/DFS-1.2.rom"],
104
104
  cpuModel: CpuModel.MOS6502,
105
105
  isMaster: false,
@@ -107,8 +107,8 @@ export const allModels = [
107
107
  fdc: NoiseAwareIntelFdc,
108
108
  }),
109
109
  new Model({
110
- name: "BBC B with DFS 0.9",
111
- synonyms: ["B-DFS0.9", "B"],
110
+ name: "BBC B with 8271 (DFS 0.9)",
111
+ synonyms: ["B-DFS0.9", "B", "BBC B with DFS 0.9"],
112
112
  os: ["os.rom", "BASIC.ROM", "b/DFS-0.9.rom"],
113
113
  cpuModel: CpuModel.MOS6502,
114
114
  isMaster: false,
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+
3
+ // Shared helpers for snapshot importers (bem-snapshot.js, uef-snapshot.js).
4
+ // Contains volume table, default peripheral state, video state builder,
5
+ // and the common snapshot envelope that wraps per-format parsed state.
6
+
7
+ // Volume lookup table matching soundchip.js
8
+ export const volumeTable = new Float32Array(16);
9
+ (() => {
10
+ let f = 1.0;
11
+ for (let i = 0; i < 15; ++i) {
12
+ volumeTable[i] = f / 4;
13
+ f *= Math.pow(10, -0.1);
14
+ }
15
+ volumeTable[15] = 0;
16
+ })();
17
+
18
+ export const DefaultAcia = {
19
+ sr: 0x02,
20
+ cr: 0x00,
21
+ dr: 0x00,
22
+ rs423Selected: false,
23
+ motorOn: false,
24
+ tapeCarrierCount: 0,
25
+ tapeDcdLineLevel: false,
26
+ hadDcdHigh: false,
27
+ serialReceiveRate: 19200,
28
+ serialReceiveCyclesPerByte: 0,
29
+ txCompleteTaskOffset: null,
30
+ runTapeTaskOffset: null,
31
+ runRs423TaskOffset: null,
32
+ };
33
+
34
+ export const DefaultAdc = { status: 0x40, low: 0x00, high: 0x00, taskOffset: null };
35
+
36
+ /**
37
+ * Build jsbeeb video state from parsed CRTC, ULA, and palette data.
38
+ * @param {number} ulaControl - VideoULA control register
39
+ * @param {Uint8Array} ulaPalette - 16-entry raw ULA palette register values (lower nibble of &FE21 writes)
40
+ * @param {Uint8Array} crtcRegs - CRTC registers (at least 18 bytes)
41
+ * @param {Int32Array|null} [nulaCollook] - NULA colour lookup (16 ABGR entries), or null for default BBC palette
42
+ * @param {object|null} [crtcCounters] - CRTC counter state {hc, vc, sc, ma, maback}, or null for defaults
43
+ */
44
+ export function buildVideoState(ulaControl, ulaPalette, crtcRegs, nulaCollook, crtcCounters) {
45
+ const regs = new Uint8Array(32);
46
+ regs.set(crtcRegs.slice(0, 18));
47
+ const actualPal = new Uint8Array(16);
48
+ for (let i = 0; i < 16; i++) actualPal[i] = ulaPalette[i] & 0x0f;
49
+
50
+ // Use NULA collook if provided, otherwise use default BBC palette
51
+ const collook =
52
+ nulaCollook ||
53
+ new Int32Array([
54
+ 0xff000000, 0xff0000ff, 0xff00ff00, 0xff00ffff, 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff, 0xff000000,
55
+ 0xff0000ff, 0xff00ff00, 0xff00ffff, 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff,
56
+ ]);
57
+
58
+ // Compute ulaPal from actualPal + collook, matching jsbeeb's Ula._recomputeUlaPal
59
+ const flashEnabled = !!(ulaControl & 1);
60
+ const defaultFlash = new Uint8Array([1, 1, 1, 1, 1, 1, 1, 1]);
61
+ const flash = defaultFlash;
62
+ const ulaPal = new Int32Array(16);
63
+ for (let i = 0; i < 16; i++) {
64
+ const palVal = actualPal[i];
65
+ let colour = collook[(palVal & 0xf) ^ 7];
66
+ if (palVal & 8 && flashEnabled && flash[(palVal & 7) ^ 7]) {
67
+ colour = collook[palVal & 0xf];
68
+ }
69
+ ulaPal[i] = colour;
70
+ }
71
+
72
+ return {
73
+ regs,
74
+ bitmapX: 0,
75
+ bitmapY: 0,
76
+ oddClock: false,
77
+ frameCount: 0,
78
+ doEvenFrameLogic: false,
79
+ isEvenRender: true,
80
+ lastRenderWasEven: false,
81
+ firstScanline: true,
82
+ inHSync: false,
83
+ inVSync: false,
84
+ hadVSyncThisRow: false,
85
+ checkVertAdjust: false,
86
+ endOfMainLatched: false,
87
+ endOfVertAdjustLatched: false,
88
+ endOfFrameLatched: false,
89
+ inVertAdjust: false,
90
+ inDummyRaster: false,
91
+ hpulseWidth: regs[3] & 0x0f,
92
+ vpulseWidth: (regs[3] & 0xf0) >>> 4,
93
+ hpulseCounter: 0,
94
+ vpulseCounter: 0,
95
+ dispEnabled: 0x3f,
96
+ horizCounter: crtcCounters ? crtcCounters.hc : 0,
97
+ vertCounter: crtcCounters ? crtcCounters.vc : 0,
98
+ scanlineCounter: crtcCounters ? crtcCounters.sc : 0,
99
+ vertAdjustCounter: 0,
100
+ addr: crtcCounters ? crtcCounters.ma : (regs[13] | (regs[12] << 8)) & 0x3fff,
101
+ lineStartAddr: crtcCounters ? crtcCounters.maback : (regs[13] | (regs[12] << 8)) & 0x3fff,
102
+ nextLineStartAddr: crtcCounters ? crtcCounters.maback : (regs[13] | (regs[12] << 8)) & 0x3fff,
103
+ ulactrl: ulaControl,
104
+ pixelsPerChar: ulaControl & 0x10 ? 8 : 16,
105
+ halfClock: !(ulaControl & 0x10),
106
+ ulaMode: (ulaControl >>> 2) & 3,
107
+ teletextMode: !!(ulaControl & 2),
108
+ displayEnableSkew: Math.min((regs[8] & 0x30) >>> 4, 2),
109
+ ulaPal,
110
+ actualPal,
111
+ cursorOn: false,
112
+ cursorOff: false,
113
+ cursorOnThisFrame: false,
114
+ cursorDrawIndex: 0,
115
+ cursorPos: (regs[15] | (regs[14] << 8)) & 0x3fff,
116
+ interlacedSyncAndVideo: (regs[8] & 3) === 3,
117
+ screenSubtract: 0,
118
+ ula: {
119
+ collook: collook.slice(),
120
+ flash: new Uint8Array([1, 1, 1, 1, 1, 1, 1, 1]),
121
+ paletteWriteFlag: false,
122
+ paletteFirstByte: 0,
123
+ paletteMode: 0,
124
+ horizontalOffset: 0,
125
+ leftBlank: 0,
126
+ disabled: false,
127
+ attributeMode: 0,
128
+ attributeText: 0,
129
+ },
130
+ crtc: { curReg: 0 },
131
+ teletext: {
132
+ prevCol: 0,
133
+ col: 7,
134
+ bg: 0,
135
+ sep: false,
136
+ dbl: false,
137
+ oldDbl: false,
138
+ secondHalfOfDouble: false,
139
+ wasDbl: false,
140
+ gfx: false,
141
+ flash: false,
142
+ flashOn: false,
143
+ flashTime: 0,
144
+ heldChar: 0,
145
+ holdChar: false,
146
+ dataQueue: [0, 0, 0, 0],
147
+ scanlineCounter: 0,
148
+ levelDEW: false,
149
+ levelDISPTMG: false,
150
+ levelRA0: false,
151
+ nextGlyphs: "normal",
152
+ curGlyphs: "normal",
153
+ heldGlyphs: "normal",
154
+ },
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Build a jsbeeb snapshot envelope from parsed emulator components.
160
+ * @param {string} importedFrom - source identifier (e.g. "b-em", "beebem-uef")
161
+ * @param {string} modelName - jsbeeb model name/synonym
162
+ * @param {object} cpuState - parsed CPU state {a, x, y, flags, s, pc, nmi, fe30, fe34}
163
+ * @param {Uint8Array} ram - 128KB RAM array
164
+ * @param {Uint8Array|null} roms - 256KB sideways ROM/RAM array, or null
165
+ * @param {object} sysvia - jsbeeb sys VIA state
166
+ * @param {object} uservia - jsbeeb user VIA state
167
+ * @param {object} video - jsbeeb video state (from buildVideoState)
168
+ * @param {object} soundChip - jsbeeb sound chip state
169
+ */
170
+ export function buildSnapshot(importedFrom, modelName, cpuState, ram, roms, sysvia, uservia, video, soundChip) {
171
+ return {
172
+ format: "jsbeeb-snapshot",
173
+ version: 2,
174
+ model: modelName,
175
+ timestamp: new Date().toISOString(),
176
+ importedFrom,
177
+ state: {
178
+ a: cpuState.a,
179
+ x: cpuState.x,
180
+ y: cpuState.y,
181
+ s: cpuState.s,
182
+ pc: cpuState.pc,
183
+ p: cpuState.flags | 0x30,
184
+ nmiLevel: !!cpuState.nmi,
185
+ nmiEdge: false,
186
+ halted: false,
187
+ takeInt: false,
188
+ romsel: cpuState.fe30 ?? 0,
189
+ acccon: cpuState.fe34 ?? 0,
190
+ videoDisplayPage: 0,
191
+ currentCycles: 0,
192
+ targetCycles: 0,
193
+ cycleSeconds: 0,
194
+ peripheralCycles: 0,
195
+ videoCycles: 0,
196
+ music5000PageSel: 0,
197
+ ram,
198
+ ...(roms instanceof Uint8Array ? { roms } : {}),
199
+ scheduler: { epoch: 0 },
200
+ sysvia,
201
+ uservia,
202
+ video,
203
+ soundChip,
204
+ acia: { ...DefaultAcia },
205
+ adc: { ...DefaultAdc },
206
+ },
207
+ };
208
+ }
package/src/snapshot.js CHANGED
@@ -7,24 +7,15 @@ const SnapshotFormat = "jsbeeb-snapshot";
7
7
  const SnapshotVersion = 2;
8
8
 
9
9
  /**
10
- * Check if two model names are compatible for state restore.
11
- * Resolves synonyms via findModel, then compares by stripping any
12
- * trailing parenthesised suffix (e.g. treating "BBC Master 128 (DFS)"
13
- * and "BBC Master 128 (ADFS)" as compatible). Does not treat
14
- * differently-named models like "BBC B with DFS 0.9" vs "BBC B with DFS 1.2"
15
- * as compatible — those are distinct models.
10
+ * Check if two model names resolve to the same model (accounting for
11
+ * synonyms and old names). Used to decide whether a page reload is
12
+ * needed when loading a snapshot saved under a different model name.
16
13
  */
17
- export function modelsCompatible(snapshotModel, currentModel) {
18
- if (snapshotModel === currentModel) return true;
19
- const resolvedSnapshot = findModel(snapshotModel);
20
- const resolvedCurrent = findModel(currentModel);
21
- if (resolvedSnapshot && resolvedCurrent) {
22
- // Same model object, or same base machine (strip filesystem suffix)
23
- if (resolvedSnapshot === resolvedCurrent) return true;
24
- const base = (name) => name.replace(/\s*\(.*\)$/, "");
25
- return base(resolvedSnapshot.name) === base(resolvedCurrent.name);
26
- }
27
- return false;
14
+ export function isSameModel(nameA, nameB) {
15
+ if (nameA === nameB) return true;
16
+ const a = findModel(nameA);
17
+ const b = findModel(nameB);
18
+ return a !== null && a === b;
28
19
  }
29
20
 
30
21
  // Map of TypedArray constructor names for deserialization
@@ -100,7 +91,7 @@ export function restoreSnapshot(cpu, model, snapshot) {
100
91
  if (snapshot.version > SnapshotVersion) {
101
92
  throw new Error(`Snapshot version ${snapshot.version} is newer than supported version ${SnapshotVersion}`);
102
93
  }
103
- if (!modelsCompatible(snapshot.model, model.name)) {
94
+ if (!isSameModel(snapshot.model, model.name)) {
104
95
  throw new Error(`Model mismatch: snapshot is for "${snapshot.model}" but current model is "${model.name}"`);
105
96
  }
106
97
  cpu.restoreState(snapshot.state);
package/src/sth.js CHANGED
@@ -52,7 +52,7 @@ export class StairwayToHell {
52
52
  const response = await fetch(name);
53
53
  if (!response.ok) throw new Error("Network response was not ok");
54
54
  try {
55
- return utils.unzipDiscImage(new Uint8Array(await response.arrayBuffer())).data;
55
+ return (await utils.unzipDiscImage(new Uint8Array(await response.arrayBuffer()))).data;
56
56
  } catch (error) {
57
57
  console.error("Failed to fetch file:", error);
58
58
  throw error;
package/src/tapes.js CHANGED
@@ -245,8 +245,8 @@ class TapefileTape {
245
245
  }
246
246
  }
247
247
 
248
- export function loadTapeFromData(name, data) {
249
- const stream = new utils.DataStream(name, data);
248
+ export async function loadTapeFromData(name, data) {
249
+ const stream = await utils.DataStream.create(name, data);
250
250
  if (stream.readByte(0) === 0xff && stream.readByte(1) === 0x04) {
251
251
  console.log("Detected a 'tapefile' tape");
252
252
  return new TapefileTape(stream);