.
Some checks failed
Deploy to Pages / build (pull_request) Failing after 3m15s

This commit is contained in:
Andras Schmelczer 2026-05-13 22:13:15 +01:00
commit 2347ecd201
71 changed files with 3779 additions and 1586 deletions

View file

@ -19,6 +19,7 @@ import { RenderInputCache } from './render-input-cache';
export default class GameLoop {
private static readonly MAX_MIRROR_SEGMENT_COUNT =
appConfig.simulation.maxMirrorSegmentCount;
private static readonly DEV_STATS_INTERVAL_MS = 250;
private readonly resources: GameLoopResources;
private readonly audio = new GardenAudio(gardenAudioConfig);
@ -29,10 +30,12 @@ export default class GameLoop {
private readonly agentPopulation: AgentPopulation;
private readonly export4KRenderer: Export4KRenderer;
private readonly framePerformance = new FramePerformance();
private readonly devStatsElement: HTMLDivElement | null = null;
private readonly seed = Math.floor(Math.random() * 0xffffffff).toString(16);
private readonly resizeListener = this.resize.bind(this);
private readonly keydownListener: (event: KeyboardEvent) => void;
private lastDevStatsUpdateAt = 0;
private hasFinished = false;
private readonly finished = Promise.withResolvers<void>();
@ -43,6 +46,9 @@ export default class GameLoop {
ui: GardenUi
) {
this.resize();
if (import.meta.env.DEV) {
this.devStatsElement = this.createDevStatsElement();
}
this.resources = new GameLoopResources(canvas, device, this.canvasSize);
this.introPrompt = new IntroPrompt(ui.prompt);
this.eraserPreview = new EraserPreview(canvas, ui.eraserPreview);
@ -58,10 +64,7 @@ export default class GameLoop {
getCanvasSize: () => this.canvasSize,
getDevicePixelRatio: () => this.devicePixelRatio,
getMirrorSegmentCount: () => this.mirrorSegmentCount,
onStartDrawing: () => {
this.introPrompt.markStartedDrawing();
this.introPrompt.complete();
},
onStartDrawing: () => this.introPrompt.markStartedDrawing(),
onEraseGestureEnded: () => this.agentPopulation.requestCompactionAfterErase(),
spawnStrokeAgents: (from, to) => this.agentPopulation.spawnStrokeAgents(from, to),
});
@ -133,6 +136,7 @@ export default class GameLoop {
window.removeEventListener('resize', this.resizeListener);
window.removeEventListener('keydown', this.keydownListener);
this.pointerInput.detach();
this.devStatsElement?.remove();
this.introPrompt.destroy();
this.resources.destroy();
await this.audio.destroy();
@ -159,7 +163,7 @@ export default class GameLoop {
const scaledTime = time * settings.renderSpeed;
const { channelColors, backgroundColor } = this.renderInputs.get();
const introProgress = this.introPrompt.progress;
const cameraZoom = 1 + (1 - introProgress) * appConfig.simulation.introCameraZoom;
const cameraZoom = 1;
const cameraCenter: [number, number] = [
this.canvas.width / 2,
this.canvas.height / 2,
@ -172,6 +176,7 @@ export default class GameLoop {
vibe: activeVibe,
selectedColorIndex: settings.selectedColorIndex,
isErasing,
mirrorSegmentCount: this.mirrorSegmentCount,
});
this.resources.setFrameParameters({
@ -205,6 +210,7 @@ export default class GameLoop {
devicePixelRatio: this.devicePixelRatio,
renderSpeed: settings.renderSpeed,
});
this.updateDevStats(time);
if (settings.simulatedDelayMs > 0) {
await sleep(settings.simulatedDelayMs);
@ -213,6 +219,42 @@ export default class GameLoop {
requestAnimationFrame(this.render);
};
private createDevStatsElement(): HTMLDivElement | null {
const container = this.canvas.parentElement;
if (!container) {
return null;
}
const element = document.createElement('div');
element.className = 'dev-stats-overlay';
element.setAttribute('aria-hidden', 'true');
container.appendChild(element);
return element;
}
private updateDevStats(time: DOMHighResTimeStamp): void {
if (
!this.devStatsElement ||
time - this.lastDevStatsUpdateAt < GameLoop.DEV_STATS_INTERVAL_MS
) {
return;
}
this.lastDevStatsUpdateAt = time;
this.devStatsElement.textContent = [
`FPS ${this.framePerformance.smoothedFps.toFixed(1)} / ${Math.round(
this.framePerformance.refreshTargetFps
)}`,
`Agents ${this.formatDevStatNumber(this.agentPopulation.activeAgentCount)}`,
`Target ${this.formatDevStatNumber(this.agentPopulation.targetAgentBudget)}`,
`Cap ${this.formatDevStatNumber(settings.agentBudgetMax)}`,
].join('\n');
}
private formatDevStatNumber(value: number): string {
return Math.max(0, Math.round(value)).toLocaleString('en-US');
}
private resize(): void {
const width = Math.max(
1,