@nodetool-ai/game-renderer 0.8.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.
Files changed (62) hide show
  1. package/dist/.nodetool-build-stamp +1 -0
  2. package/dist/audio.d.ts +39 -0
  3. package/dist/audio.d.ts.map +1 -0
  4. package/dist/audio.js +227 -0
  5. package/dist/audio.js.map +1 -0
  6. package/dist/browser-fonts.d.ts +8 -0
  7. package/dist/browser-fonts.d.ts.map +1 -0
  8. package/dist/browser-fonts.js +39 -0
  9. package/dist/browser-fonts.js.map +1 -0
  10. package/dist/browser.d.ts +31 -0
  11. package/dist/browser.d.ts.map +1 -0
  12. package/dist/browser.js +106 -0
  13. package/dist/browser.js.map +1 -0
  14. package/dist/build.d.ts +19 -0
  15. package/dist/build.d.ts.map +1 -0
  16. package/dist/build.js +236 -0
  17. package/dist/build.js.map +1 -0
  18. package/dist/canvas2d.d.ts +34 -0
  19. package/dist/canvas2d.d.ts.map +1 -0
  20. package/dist/canvas2d.js +208 -0
  21. package/dist/canvas2d.js.map +1 -0
  22. package/dist/fonts.d.ts +5 -0
  23. package/dist/fonts.d.ts.map +1 -0
  24. package/dist/fonts.js +10 -0
  25. package/dist/fonts.js.map +1 -0
  26. package/dist/frame.d.ts +55 -0
  27. package/dist/frame.d.ts.map +1 -0
  28. package/dist/frame.js +123 -0
  29. package/dist/frame.js.map +1 -0
  30. package/dist/gpu-capture.d.ts +4 -0
  31. package/dist/gpu-capture.d.ts.map +1 -0
  32. package/dist/gpu-capture.js +192 -0
  33. package/dist/gpu-capture.js.map +1 -0
  34. package/dist/index.d.ts +43 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +3 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/input.d.ts +3 -0
  39. package/dist/input.d.ts.map +1 -0
  40. package/dist/input.js +16 -0
  41. package/dist/input.js.map +1 -0
  42. package/dist/lighting.d.ts +17 -0
  43. package/dist/lighting.d.ts.map +1 -0
  44. package/dist/lighting.js +79 -0
  45. package/dist/lighting.js.map +1 -0
  46. package/dist/node.d.ts +14 -0
  47. package/dist/node.d.ts.map +1 -0
  48. package/dist/node.js +177 -0
  49. package/dist/node.js.map +1 -0
  50. package/dist/standalone-player.d.ts +2 -0
  51. package/dist/standalone-player.d.ts.map +1 -0
  52. package/dist/standalone-player.js +286 -0
  53. package/dist/standalone-player.js.map +1 -0
  54. package/dist/touch-controls.d.ts +18 -0
  55. package/dist/touch-controls.d.ts.map +1 -0
  56. package/dist/touch-controls.js +136 -0
  57. package/dist/touch-controls.js.map +1 -0
  58. package/dist/webgpu.d.ts +75 -0
  59. package/dist/webgpu.d.ts.map +1 -0
  60. package/dist/webgpu.js +807 -0
  61. package/dist/webgpu.js.map +1 -0
  62. package/package.json +64 -0
@@ -0,0 +1 @@
1
+ 2026-09-28T09:19:04.696Z
@@ -0,0 +1,39 @@
1
+ import type { GameAssetBinding, GameDocument, GameEvent, GameSnapshot } from "@nodetool-ai/protocol";
2
+ export interface GameAudioOptions {
3
+ readonly assets: GameDocument["assets"];
4
+ readonly tickRate: number;
5
+ readonly resolveAsset: (binding: GameAssetBinding) => Promise<string | null>;
6
+ readonly status: (message: string) => void;
7
+ readonly context?: AudioContext;
8
+ }
9
+ /** Shares Web Audio voice, decoding, and lifecycle behavior across browser players. */
10
+ export declare class GameAudioPlayer {
11
+ private readonly options;
12
+ private readonly context;
13
+ private readonly buffers;
14
+ private readonly voices;
15
+ private readonly fading;
16
+ private readonly pending;
17
+ private desiredMusic;
18
+ private sceneId;
19
+ private tick;
20
+ private generation;
21
+ private legacySequence;
22
+ private disposed;
23
+ private paused;
24
+ constructor(options: GameAudioOptions);
25
+ private buffer;
26
+ preload(): void;
27
+ unlock(): Promise<void>;
28
+ pause(): void;
29
+ resume(): void;
30
+ sync(snapshot: GameSnapshot): void;
31
+ private startDesiredMusic;
32
+ handle(event: GameEvent): void;
33
+ private start;
34
+ private playBuiltin;
35
+ private stop;
36
+ reset(snapshot: GameSnapshot): void;
37
+ dispose(): void;
38
+ }
39
+ //# sourceMappingURL=audio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio.d.ts","sourceRoot":"","sources":["../src/audio.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAYrG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7E,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,CAAC;CACjC;AAED,uFAAuF;AACvF,qBAAa,eAAe;IAcd,OAAO,CAAC,QAAQ,CAAC,OAAO;IAbpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkD;IAC1E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,YAAY,CAA+B;IACnD,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAS;IAEvB,YAA6B,OAAO,EAAE,gBAAgB,EAErD;IAED,OAAO,CAAC,MAAM;IAqBd,OAAO,IAAI,IAAI,CAId;IAEK,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ5B;IAED,KAAK,IAAI,IAAI,CAIZ;IAED,MAAM,IAAI,IAAI,CAGb;IAED,IAAI,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAoBjC;IAED,OAAO,CAAC,iBAAiB;IAWzB,MAAM,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAc7B;YAEa,KAAK;IA2CnB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,IAAI;IAeZ,KAAK,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAQlC;IAED,OAAO,IAAI,IAAI,CAUd;CACF"}
package/dist/audio.js ADDED
@@ -0,0 +1,227 @@
1
+ const MAX_VOICES = 32;
2
+ /** Shares Web Audio voice, decoding, and lifecycle behavior across browser players. */
3
+ export class GameAudioPlayer {
4
+ options;
5
+ context;
6
+ buffers = new Map();
7
+ voices = new Map();
8
+ fading = new Set();
9
+ pending = new Map();
10
+ desiredMusic = null;
11
+ sceneId = null;
12
+ tick = 0;
13
+ generation = 0;
14
+ legacySequence = 0;
15
+ disposed = false;
16
+ paused = false;
17
+ constructor(options) {
18
+ this.options = options;
19
+ this.context = options.context ?? new AudioContext();
20
+ }
21
+ buffer(assetId) {
22
+ const cached = this.buffers.get(assetId);
23
+ if (cached)
24
+ return cached;
25
+ const binding = this.options.assets[assetId];
26
+ if (!binding) {
27
+ this.options.status(`Audio asset ${assetId} is missing`);
28
+ return Promise.resolve(null);
29
+ }
30
+ const pending = this.options.resolveAsset(binding).then(async (url) => {
31
+ if (!url)
32
+ throw new Error("asset URL is unavailable");
33
+ const response = await fetch(url, { cache: "force-cache" });
34
+ if (!response.ok)
35
+ throw new Error(`HTTP ${response.status}`);
36
+ return this.context.decodeAudioData(await response.arrayBuffer());
37
+ }).catch((error) => {
38
+ this.options.status(`Audio ${assetId} could not be decoded: ${error instanceof Error ? error.message : String(error)}`);
39
+ return null;
40
+ });
41
+ this.buffers.set(assetId, pending);
42
+ return pending;
43
+ }
44
+ preload() {
45
+ for (const [id, binding] of Object.entries(this.options.assets)) {
46
+ if (binding.mediaKind === "audio" && !binding.assetId.startsWith("builtin:"))
47
+ void this.buffer(id);
48
+ }
49
+ }
50
+ async unlock() {
51
+ if (this.disposed || this.paused)
52
+ return;
53
+ try {
54
+ await this.context.resume();
55
+ this.startDesiredMusic();
56
+ }
57
+ catch {
58
+ this.options.status("Audio is blocked. Tap Play to enable sound in this browser.");
59
+ }
60
+ }
61
+ pause() {
62
+ if (this.disposed || this.paused)
63
+ return;
64
+ this.paused = true;
65
+ void this.context.suspend().catch(() => this.options.status("Audio could not pause"));
66
+ }
67
+ resume() {
68
+ this.paused = false;
69
+ void this.unlock();
70
+ }
71
+ sync(snapshot) {
72
+ if (this.disposed)
73
+ return;
74
+ this.tick = snapshot.tick;
75
+ if (this.sceneId && this.sceneId !== snapshot.sceneId) {
76
+ for (const id of this.pending.keys()) {
77
+ if (!id.startsWith(`effect:${snapshot.sceneId}:`))
78
+ this.pending.delete(id);
79
+ }
80
+ for (const id of this.voices.keys()) {
81
+ if (id.startsWith(`effect:${this.sceneId}:`))
82
+ this.stop(id, 0);
83
+ }
84
+ }
85
+ this.sceneId = snapshot.sceneId;
86
+ const next = snapshot.music;
87
+ if (this.desiredMusic?.voiceId !== next?.voiceId || this.desiredMusic?.startTick !== next?.startTick ||
88
+ this.desiredMusic?.assetId !== next?.assetId || this.desiredMusic?.volume !== next?.volume ||
89
+ this.desiredMusic?.fadeInTicks !== next?.fadeInTicks || this.desiredMusic?.fadeOutTicks !== next?.fadeOutTicks) {
90
+ if (this.desiredMusic)
91
+ this.stop(this.desiredMusic.voiceId, this.desiredMusic.fadeOutTicks);
92
+ this.desiredMusic = next;
93
+ }
94
+ this.startDesiredMusic();
95
+ }
96
+ startDesiredMusic() {
97
+ const music = this.desiredMusic;
98
+ if (!music || this.paused || this.context.state !== "running" || this.voices.has(music.voiceId) || this.pending.has(music.voiceId))
99
+ return;
100
+ const token = Symbol();
101
+ this.pending.set(music.voiceId, token);
102
+ void this.start(music.voiceId, music.assetId, true, music.volume, music.fadeInTicks, music.fadeOutTicks, token, music.startTick)
103
+ .catch((error) => this.options.status(`Music could not start: ${error instanceof Error ? error.message : String(error)}`))
104
+ .finally(() => { if (this.pending.get(music.voiceId) === token)
105
+ this.pending.delete(music.voiceId); });
106
+ }
107
+ handle(event) {
108
+ if (this.disposed || event.kind !== "audio")
109
+ return;
110
+ if (event.action === "stop") {
111
+ if (event.voiceId)
112
+ this.stop(event.voiceId, event.fadeOutTicks);
113
+ return;
114
+ }
115
+ if (this.paused || this.context.state !== "running")
116
+ return;
117
+ const voiceId = event.voiceId ?? `legacy:${this.tick}:${this.legacySequence++}`;
118
+ if (this.pending.has(voiceId))
119
+ return;
120
+ const token = Symbol();
121
+ this.pending.set(voiceId, token);
122
+ void this.start(voiceId, event.assetId, event.loop, event.volume, event.fadeInTicks, event.fadeOutTicks, token)
123
+ .catch((error) => this.options.status(`Audio effect could not start: ${error instanceof Error ? error.message : String(error)}`))
124
+ .finally(() => { if (this.pending.get(voiceId) === token)
125
+ this.pending.delete(voiceId); });
126
+ }
127
+ async start(id, assetId, loop, volume, fadeInTicks, fadeOutTicks, token, logicalStartTick) {
128
+ const generation = this.generation;
129
+ const binding = this.options.assets[assetId];
130
+ if (binding?.assetId.startsWith("builtin:")) {
131
+ if (loop)
132
+ this.options.status(`Audio ${assetId} cannot loop a built-in effect`);
133
+ else
134
+ this.playBuiltin(volume);
135
+ return;
136
+ }
137
+ const buffer = await this.buffer(assetId);
138
+ if (!buffer || this.disposed || this.paused || this.context.state !== "running" || generation !== this.generation ||
139
+ this.pending.get(id) !== token || this.voices.has(id))
140
+ return;
141
+ if (this.voices.size + this.fading.size >= MAX_VOICES && this.fading.size > 0) {
142
+ const fading = this.fading.values().next().value;
143
+ if (fading) {
144
+ fading.source.stop();
145
+ this.fading.delete(fading);
146
+ }
147
+ }
148
+ if (this.voices.size + this.fading.size >= MAX_VOICES) {
149
+ const oldestEffect = [...this.voices.values()].find((voice) => !voice.loop);
150
+ if (!oldestEffect)
151
+ return;
152
+ this.stop(oldestEffect.id, 0);
153
+ }
154
+ const source = this.context.createBufferSource();
155
+ const gain = this.context.createGain();
156
+ source.buffer = buffer;
157
+ source.loop = loop;
158
+ source.connect(gain).connect(this.context.destination);
159
+ const now = this.context.currentTime;
160
+ gain.gain.setValueAtTime(fadeInTicks > 0 ? 0 : volume, now);
161
+ if (fadeInTicks > 0)
162
+ gain.gain.linearRampToValueAtTime(volume, now + fadeInTicks / this.options.tickRate);
163
+ const voice = { id, source, gain, loop, fadeOutTicks };
164
+ this.voices.set(id, voice);
165
+ source.onended = () => {
166
+ if (this.voices.get(id) === voice)
167
+ this.voices.delete(id);
168
+ this.fading.delete(voice);
169
+ source.disconnect();
170
+ gain.disconnect();
171
+ };
172
+ const offset = logicalStartTick === undefined ? 0 : Math.max(0, this.tick - logicalStartTick) / this.options.tickRate;
173
+ source.start(0, loop && buffer.duration > 0 ? offset % buffer.duration : 0);
174
+ }
175
+ playBuiltin(volume) {
176
+ const oscillator = this.context.createOscillator();
177
+ const gain = this.context.createGain();
178
+ oscillator.frequency.value = 660;
179
+ gain.gain.setValueAtTime(volume * 0.08, this.context.currentTime);
180
+ gain.gain.exponentialRampToValueAtTime(0.001, this.context.currentTime + 0.12);
181
+ oscillator.connect(gain).connect(this.context.destination);
182
+ oscillator.onended = () => { oscillator.disconnect(); gain.disconnect(); };
183
+ oscillator.start();
184
+ oscillator.stop(this.context.currentTime + 0.12);
185
+ }
186
+ stop(id, fadeTicks) {
187
+ this.pending.delete(id);
188
+ const voice = this.voices.get(id);
189
+ if (!voice)
190
+ return;
191
+ this.voices.delete(id);
192
+ const now = this.context.currentTime;
193
+ voice.gain.gain.cancelScheduledValues(now);
194
+ if (fadeTicks > 0) {
195
+ this.fading.add(voice);
196
+ voice.gain.gain.setValueAtTime(voice.gain.gain.value, now);
197
+ voice.gain.gain.linearRampToValueAtTime(0, now + fadeTicks / this.options.tickRate);
198
+ }
199
+ voice.source.stop(now + fadeTicks / this.options.tickRate);
200
+ }
201
+ reset(snapshot) {
202
+ this.generation += 1;
203
+ this.pending.clear();
204
+ for (const id of this.voices.keys())
205
+ this.stop(id, 0);
206
+ for (const voice of this.fading)
207
+ voice.source.stop();
208
+ this.fading.clear();
209
+ this.desiredMusic = null;
210
+ this.sync(snapshot);
211
+ }
212
+ dispose() {
213
+ if (this.disposed)
214
+ return;
215
+ this.disposed = true;
216
+ this.generation += 1;
217
+ this.pending.clear();
218
+ for (const id of this.voices.keys())
219
+ this.stop(id, 0);
220
+ for (const voice of this.fading)
221
+ voice.source.stop();
222
+ this.fading.clear();
223
+ this.buffers.clear();
224
+ void this.context.close();
225
+ }
226
+ }
227
+ //# sourceMappingURL=audio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio.js","sourceRoot":"","sources":["../src/audio.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { GameDocument } from "@nodetool-ai/protocol";
2
+ export interface LoadedGameFonts {
3
+ readonly diagnostics: readonly string[];
4
+ dispose(): void;
5
+ }
6
+ /** Load every font before the first frame so optional fallback is deterministic. */
7
+ export declare function loadBrowserGameFonts(document: GameDocument, resolveFont: (assetId: string) => Promise<string | null>): Promise<LoadedGameFonts>;
8
+ //# sourceMappingURL=browser-fonts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-fonts.d.ts","sourceRoot":"","sources":["../src/browser-fonts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG1D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,oFAAoF;AACpF,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,YAAY,EACtB,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GACvD,OAAO,CAAC,eAAe,CAAC,CA2B1B"}
@@ -0,0 +1,39 @@
1
+ import { fontBindings, gameFontFamily } from "./fonts.js";
2
+ /** Load every font before the first frame so optional fallback is deterministic. */
3
+ export async function loadBrowserGameFonts(document, resolveFont) {
4
+ const loaded = [];
5
+ const diagnostics = [];
6
+ try {
7
+ for (const [fontId, binding] of fontBindings(document)) {
8
+ const family = gameFontFamily(document.id, fontId);
9
+ try {
10
+ const url = await resolveFont(binding.assetId);
11
+ if (!url)
12
+ throw new Error("asset is missing");
13
+ const response = await fetch(url);
14
+ if (!response.ok)
15
+ throw new Error(`HTTP ${response.status}`);
16
+ const bytes = await response.arrayBuffer();
17
+ const face = new FontFace(family, bytes);
18
+ await face.load();
19
+ documentGlobalFonts().add(face);
20
+ loaded.push(face);
21
+ }
22
+ catch (error) {
23
+ const detail = error instanceof Error ? error.message : String(error);
24
+ if (binding.required !== false)
25
+ throw new Error(`Required font ${fontId} could not load: ${detail}`);
26
+ diagnostics.push(`Optional font ${fontId} could not load; using system font`);
27
+ }
28
+ }
29
+ }
30
+ catch (error) {
31
+ loaded.forEach((face) => documentGlobalFonts().delete(face));
32
+ throw error;
33
+ }
34
+ return { diagnostics, dispose: () => loaded.forEach((face) => documentGlobalFonts().delete(face)) };
35
+ }
36
+ function documentGlobalFonts() {
37
+ return document.fonts;
38
+ }
39
+ //# sourceMappingURL=browser-fonts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-fonts.js","sourceRoot":"","sources":["../src/browser-fonts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAO1D,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,QAAsB,EACtB,WAAwD;IAExD,MAAM,MAAM,GAAe,EAAE,CAAC;IAC9B,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,IAAI,CAAC;QACH,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvD,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;YACnD,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBAC/C,IAAI,CAAC,GAAG;oBAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;gBAC9C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;gBAClC,IAAI,CAAC,QAAQ,CAAC,EAAE;oBAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC7D,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;gBAC3C,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;gBACzC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;gBAClB,mBAAmB,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAChC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACtE,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK;oBAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,MAAM,oBAAoB,MAAM,EAAE,CAAC,CAAC;gBACrG,WAAW,CAAC,IAAI,CAAC,iBAAiB,MAAM,oCAAoC,CAAC,CAAC;YAChF,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,mBAAmB,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7D,MAAM,KAAK,CAAC;IACd,CAAC;IACD,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,mBAAmB,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;AACtG,CAAC;AAED,SAAS,mBAAmB;IAC1B,OAAO,QAAQ,CAAC,KAAK,CAAC;AACxB,CAAC"}
@@ -0,0 +1,31 @@
1
+ import type { GameRenderFrame } from "@nodetool-ai/protocol";
2
+ import { type GameAssetResolver } from "./canvas2d.js";
3
+ import type { GameHudEffectOrder, GameRenderer, GameRendererBackend, GameRendererCapabilities, GameRendererEffect, GameRendererStats } from "./index.js";
4
+ export { loadBrowserGameFonts } from "./browser-fonts.js";
5
+ export interface CreateGameRendererOptions {
6
+ readonly canvas: HTMLCanvasElement;
7
+ readonly assets: GameAssetResolver;
8
+ readonly backend?: "auto" | GameRendererBackend;
9
+ }
10
+ export declare class RecoveringGameRenderer implements GameRenderer {
11
+ private current;
12
+ private readonly assets;
13
+ private effects;
14
+ private deviceLossCount;
15
+ private fallbackReason;
16
+ private renderTail;
17
+ constructor(current: GameRenderer, assets: GameAssetResolver);
18
+ get backend(): GameRendererBackend;
19
+ get canvas(): HTMLCanvasElement;
20
+ get capabilities(): GameRendererCapabilities;
21
+ setEffects(effects: readonly GameRendererEffect[], hudOrder?: GameHudEffectOrder): void;
22
+ render(frame: GameRenderFrame, interpolation: number): Promise<GameRendererStats>;
23
+ private renderCurrent;
24
+ resize(width: number, height: number): void;
25
+ invalidateAsset(assetId: string): void;
26
+ dispose(): void;
27
+ }
28
+ /** Creates a browser game renderer, falling back to Canvas2D when WebGPU is unavailable. */
29
+ export declare function createGameRenderer(options: CreateGameRendererOptions): Promise<GameRenderer>;
30
+ export type { GameAssetResolver, GameImage } from "./canvas2d.js";
31
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAoC,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACzF,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEzJ,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAAC;CACjD;AAaD,qBAAa,sBAAuB,YAAW,YAAY;IAK7C,OAAO,CAAC,OAAO;IAAgB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJlE,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,UAAU,CAAoC;IACtD,YAAoB,OAAO,EAAE,YAAY,EAAmB,MAAM,EAAE,iBAAiB,EAAI;IAEzF,IAAI,OAAO,IAAI,mBAAmB,CAAiC;IACnE,IAAI,MAAM,IAAI,iBAAiB,CAAgC;IAC/D,IAAI,YAAY,IAAI,wBAAwB,CAG3C;IACD,UAAU,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,EAAE,QAAQ,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAGtF;IAED,MAAM,CAAC,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAKhF;YAEa,aAAa;IAwB3B,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAwC;IACnF,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAA2C;IACjF,OAAO,IAAI,IAAI,CAA4B;CAC5C;AAED,4FAA4F;AAC5F,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,YAAY,CAAC,CAqClG;AAED,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,106 @@
1
+ import { AssetCache, Canvas2DGameRenderer } from "./canvas2d.js";
2
+ import { WebGPUGameRenderer } from "./webgpu.js";
3
+ export { loadBrowserGameFonts } from "./browser-fonts.js";
4
+ function replacementCanvas(canvas) {
5
+ const replacement = document.createElement("canvas");
6
+ replacement.width = canvas.width;
7
+ replacement.height = canvas.height;
8
+ replacement.id = canvas.id;
9
+ replacement.className = canvas.className;
10
+ replacement.style.cssText = canvas.style.cssText;
11
+ canvas.parentNode?.replaceChild(replacement, canvas);
12
+ return replacement;
13
+ }
14
+ export class RecoveringGameRenderer {
15
+ current;
16
+ assets;
17
+ effects = [];
18
+ deviceLossCount = 0;
19
+ fallbackReason = null;
20
+ renderTail = Promise.resolve();
21
+ constructor(current, assets) {
22
+ this.current = current;
23
+ this.assets = assets;
24
+ }
25
+ get backend() { return this.current.backend; }
26
+ get canvas() { return this.current.canvas; }
27
+ get capabilities() {
28
+ return { ...this.current.capabilities, deviceLossCount: this.deviceLossCount,
29
+ fallbackReason: this.fallbackReason };
30
+ }
31
+ setEffects(effects, hudOrder) {
32
+ this.current.setEffects(effects, hudOrder);
33
+ this.effects = effects;
34
+ }
35
+ render(frame, interpolation) {
36
+ // A later frame can replace HUD textures while an earlier frame is still being encoded.
37
+ const result = this.renderTail.then(() => this.renderCurrent(frame, interpolation));
38
+ this.renderTail = result.then(() => undefined, () => undefined);
39
+ return result;
40
+ }
41
+ async renderCurrent(frame, interpolation) {
42
+ if (this.effects.some((effect) => effect.required) && !this.current.capabilities.gpuEffects) {
43
+ throw new Error("Required GPU effect is unavailable after WebGPU failure");
44
+ }
45
+ try {
46
+ return await this.current.render(frame, interpolation);
47
+ }
48
+ catch (error) {
49
+ if (this.current.backend !== "webgpu" || !(error instanceof Error) ||
50
+ (error.message !== "WebGPU device was lost" && !error.message.startsWith("WebGPU game render failed"))) {
51
+ throw error;
52
+ }
53
+ const canvas = replacementCanvas(this.current.canvas);
54
+ this.current.dispose();
55
+ this.current = new Canvas2DGameRenderer(canvas, new AssetCache(this.assets));
56
+ this.deviceLossCount++;
57
+ this.fallbackReason = error.message;
58
+ if (this.effects.some((effect) => effect.required)) {
59
+ throw new Error("Required GPU effect is unavailable after WebGPU failure");
60
+ }
61
+ this.effects = [];
62
+ return this.current.render(frame, interpolation);
63
+ }
64
+ }
65
+ resize(width, height) { this.current.resize(width, height); }
66
+ invalidateAsset(assetId) { this.current.invalidateAsset(assetId); }
67
+ dispose() { this.current.dispose(); }
68
+ }
69
+ /** Creates a browser game renderer, falling back to Canvas2D when WebGPU is unavailable. */
70
+ export async function createGameRenderer(options) {
71
+ const backend = options.backend ?? "auto";
72
+ if (backend !== "canvas2d" && typeof navigator !== "undefined" && navigator.gpu) {
73
+ let device;
74
+ let context = null;
75
+ try {
76
+ const adapter = await navigator.gpu.requestAdapter();
77
+ if (adapter) {
78
+ device = await adapter.requestDevice();
79
+ context = options.canvas.getContext("webgpu");
80
+ if (context) {
81
+ const format = navigator.gpu.getPreferredCanvasFormat();
82
+ context.configure({ device, format, alphaMode: "premultiplied" });
83
+ const info = adapter.info;
84
+ const adapterType = info?.device?.toLowerCase().includes("software") ||
85
+ info?.architecture?.toLowerCase().includes("swiftshader") ||
86
+ info?.architecture?.toLowerCase().includes("llvmpipe") ? "software" : "unknown";
87
+ return new RecoveringGameRenderer(new WebGPUGameRenderer(options.canvas, device, context, format, new AssetCache(options.assets), adapterType), options.assets);
88
+ }
89
+ device.destroy();
90
+ }
91
+ }
92
+ catch (error) {
93
+ context?.unconfigure();
94
+ device?.destroy();
95
+ if (backend === "webgpu") {
96
+ throw error;
97
+ }
98
+ return new Canvas2DGameRenderer(replacementCanvas(options.canvas), new AssetCache(options.assets));
99
+ }
100
+ }
101
+ if (backend === "webgpu") {
102
+ throw new Error("WebGPU is unavailable on this device");
103
+ }
104
+ return new Canvas2DGameRenderer(options.canvas, new AssetCache(options.assets));
105
+ }
106
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { GameDocument } from "@nodetool-ai/protocol";
2
+ export interface StandaloneAsset {
3
+ readonly bytes: Uint8Array;
4
+ readonly mimeType: string;
5
+ }
6
+ export interface BuildStandaloneGameOptions {
7
+ readonly document: GameDocument;
8
+ readonly outputDir: string;
9
+ readonly resolveAsset: (sourceAssetId: string) => Promise<StandaloneAsset | null>;
10
+ }
11
+ export interface StandaloneGameBuild {
12
+ readonly outputDir: string;
13
+ readonly gamePath: string;
14
+ readonly playerPath: string;
15
+ readonly assetPaths: Readonly<Record<string, string>>;
16
+ }
17
+ /** Builds a self-contained web player with local content-addressed media. */
18
+ export declare function buildStandaloneGame(options: BuildStandaloneGameOptions): Promise<StandaloneGameBuild>;
19
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAK1D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;CACnF;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACvD;AAiGD,6EAA6E;AAC7E,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA+H3G"}