Skip to content

Commit cb3a328

Browse files
committed
Start animations early
1 parent be0960c commit cb3a328

2 files changed

Lines changed: 5 additions & 74 deletions

File tree

apps/gif-service/src/emulator.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,6 @@ export class Emulator {
5252
private audioSamplesPerFrame: number = 0;
5353
private lastAudioLeft: Float32Array | null = null;
5454
private lastAudioRight: Float32Array | null = null;
55-
// Number of tape-load traps that fired during the most recent runFrame.
56-
// Non-zero only while a LOAD is pulling blocks in, so it marks the loader
57-
// phase distinctly from the program running afterwards.
58-
private tapeTrapsLastFrame: number = 0;
5955

6056
// ZX Spectrum keyboard matrix (character -> [row, bitmask]).
6157
// Rows/masks match the core's keyDown(row, mask) convention used by the web
@@ -147,12 +143,6 @@ export class Emulator {
147143
return this.tapeIsPlaying;
148144
}
149145

150-
// How many tape-load traps fired during the most recent runFrame. Used to
151-
// tell the loader phase apart from the program running afterwards.
152-
getTapeTrapsLastFrame(): number {
153-
return this.tapeTrapsLastFrame;
154-
}
155-
156146
// Ask the core to render `samplesPerFrame` stereo audio samples each frame
157147
// (0 disables audio generation entirely, the default).
158148
enableAudio(samplesPerFrame: number): void {
@@ -269,7 +259,6 @@ export class Emulator {
269259
}
270260
status = this.core.resumeFrame();
271261
}
272-
this.tapeTrapsLastFrame = trapCount;
273262

274263
if (this.audioSamplesPerFrame > 0) {
275264
// Copy out of WASM memory: the views alias the heap, which the next

apps/gif-service/src/gif-generator.ts

Lines changed: 5 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -59,26 +59,6 @@ export class GIFGenerator {
5959
return true;
6060
}
6161

62-
// Count the set (non-zero) bitmap bytes on screen, i.e. how many pixel cells
63-
// carry drawn content. The 0x6600 buffer is a 24-row top border, then 192
64-
// rows of [16 left-border][32 (bitmap,attr) pairs][16 right-border], then a
65-
// 24-row bottom border (see FrameDecoder). Only the bitmap byte of each pair
66-
// is counted, so a blank/cleared screen reads ~0 regardless of paper colour,
67-
// while text or graphics read higher.
68-
private inkBytes(a: Uint8Array): number {
69-
let n = 0;
70-
let p = 24 * 160; // skip top border
71-
for (let row = 0; row < 192; row++) {
72-
p += 16; // left border
73-
for (let i = 0; i < 32; i++) {
74-
if (a[p] !== 0) n++; // bitmap byte
75-
p += 2; // step over this pair's attribute byte
76-
}
77-
p += 16; // right border
78-
}
79-
return n;
80-
}
81-
8262
// ZX Spectrum keyboard matrix cells.
8363
private static readonly KEY = {
8464
ENTER: [6, 0x01] as [number, number],
@@ -159,7 +139,6 @@ export class GIFGenerator {
159139
let previousFrame: Uint8Array | null = null;
160140
let staleCount = 0;
161141
let lastChangeIndex = -1;
162-
let lastLoadFrame = -1; // last frame in which a tape-load trap fired
163142

164143
for (let f = 0; f < maxFrames; f++) {
165144
if (Date.now() > renderDeadline) {
@@ -173,7 +152,6 @@ export class GIFGenerator {
173152
}
174153
const frameBuffer = new Uint8Array(this.emulator.runFrame());
175154
frames.push(frameBuffer);
176-
if (this.emulator.getTapeTrapsLastFrame() > 0) lastLoadFrame = f;
177155

178156
// Audio activity counts as a change: a tune over a static screen must
179157
// not be cut short, nor its tail trimmed. Kept aligned 1:1 with frames.
@@ -210,48 +188,12 @@ export class GIFGenerator {
210188
return { frames: frames.slice(0, keepStatic), audio: audio.slice(0, keepStatic) };
211189
}
212190

213-
// Open on the program's first real content, not the ROM editor/loader
214-
// pre-roll or the bare cleared screen. Capturing begins at the load
215-
// keypress (so no opening draw is ever missed), which leaves the blank
216-
// boot screen and the loader's "Program:" header at the head. Tape-load
217-
// traps fire only while LOAD pulls blocks in, so the program takes
218-
// control on the frame after the last trap. From there a program
219-
// typically clears the loader screen (CLS) and then draws: open on the
220-
// first frame that carries real drawn pixels after that clear, so the
221-
// first frame (and thus the social preview, which is frame 0) shows
222-
// content rather than a blank screen.
223-
const INK_CONTENT = 16; // set bitmap bytes that count as real drawn content
224-
const INK_CLEARED = 8; // at/below this the screen is effectively blank
225-
let start = lastLoadFrame >= 0 ? Math.min(lastLoadFrame + 1, lastChangeIndex) : 0;
226-
if (lastLoadFrame >= 0) {
227-
// Find where the program clears the loader's screen (ink falls to
228-
// ~blank) after taking control.
229-
let clearedAt = -1;
230-
for (let i = lastLoadFrame + 1; i <= lastChangeIndex; i++) {
231-
if (this.inkBytes(frames[i]) <= INK_CLEARED) {
232-
clearedAt = i;
233-
break;
234-
}
235-
}
236-
// Then open on the first frame drawing real content. Searching after
237-
// the clear skips the loader header; if the program never clears,
238-
// search from the load handoff so the clip still opens on content.
239-
const from = clearedAt >= 0 ? clearedAt + 1 : lastLoadFrame + 1;
240-
for (let i = from; i <= lastChangeIndex; i++) {
241-
if (this.inkBytes(frames[i]) >= INK_CONTENT) {
242-
start = i;
243-
break;
244-
}
245-
}
246-
// No content found after the clear (e.g. an audio-only program with a
247-
// blank screen): fall back to the cleared frame rather than the
248-
// loader header.
249-
if (clearedAt >= 0 && start < clearedAt) start = clearedAt;
250-
}
251-
191+
// Keep every captured frame from the very first one. The social preview
192+
// is frame 0, so it may show the blank boot/loader screen before the
193+
// program draws, but no opening frames are ever skipped.
252194
const keep = Math.min(frames.length, lastChangeIndex + 1 + tailFrames);
253-
console.log(`Captured ${frames.length} frames, keeping ${start}..${keep}`);
254-
return { frames: frames.slice(start, keep), audio: audio.slice(start, keep) };
195+
console.log(`Captured ${frames.length} frames, keeping ${keep}`);
196+
return { frames: frames.slice(0, keep), audio: audio.slice(0, keep) };
255197
}
256198

257199
private isAudioSilent(left: Float32Array, right: Float32Array): boolean {

0 commit comments

Comments
 (0)