Skip to content

Commit 7456a8e

Browse files
authored
Include camera state in RenderCommandBuffer (#835)
* feat(renderer-contract): include camera in RCB (#814) * test(renderer-debug): cover render validation edge cases * refactor(renderer): reduce cognitive complexity * chore(dist): rebuild renderer outputs
1 parent b8b8059 commit 7456a8e

34 files changed

Lines changed: 1614 additions & 282 deletions

docs/coverage/index.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,21 @@ Run `pnpm coverage:md` from the repository root to regenerate this page after mo
1010
## Overall Coverage
1111
| Metric | Covered | Total | % |
1212
| --- | --- | --- | --- |
13-
| Statements | 40442 | 44945 | 89.98% |
14-
| Branches | 8295 | 9731 | 85.24% |
15-
| Functions | 2128 | 2280 | 93.33% |
16-
| Lines | 40442 | 44945 | 89.98% |
13+
| Statements | 40594 | 45024 | 90.16% |
14+
| Branches | 8384 | 9815 | 85.42% |
15+
| Functions | 2128 | 2281 | 93.29% |
16+
| Lines | 40594 | 45024 | 90.16% |
1717

1818
## Coverage by Package
1919
| Package | Statements | Branches | Functions | Lines |
2020
| --- | --- | --- | --- | --- |
21-
| @idle-engine/content-compiler | 1374 / 1525 (90.10%) | 238 / 301 (79.07%) | 88 / 92 (95.65%) | 1374 / 1525 (90.10%) |
21+
| @idle-engine/content-compiler | 1374 / 1525 (90.10%) | 237 / 300 (79.00%) | 88 / 92 (95.65%) | 1374 / 1525 (90.10%) |
2222
| @idle-engine/content-sample | 17 / 21 (80.95%) | 2 / 3 (66.67%) | 0 / 0 (0.00%) | 17 / 21 (80.95%) |
2323
| @idle-engine/content-schema | 8645 / 9715 (88.99%) | 1231 / 1446 (85.13%) | 307 / 337 (91.10%) | 8645 / 9715 (88.99%) |
2424
| @idle-engine/content-validation-cli | 1608 / 2200 (73.09%) | 276 / 374 (73.80%) | 80 / 99 (80.81%) | 1608 / 2200 (73.09%) |
2525
| @idle-engine/controls | 228 / 230 (99.13%) | 67 / 69 (97.10%) | 14 / 14 (100.00%) | 228 / 230 (99.13%) |
26-
| @idle-engine/core | 24710 / 27308 (90.49%) | 5446 / 6460 (84.30%) | 1435 / 1534 (93.55%) | 24710 / 27308 (90.49%) |
27-
| @idle-engine/renderer-contract | 500 / 507 (98.62%) | 131 / 135 (97.04%) | 27 / 27 (100.00%) | 500 / 507 (98.62%) |
28-
| @idle-engine/renderer-debug | 686 / 763 (89.91%) | 186 / 223 (83.41%) | 38 / 38 (100.00%) | 686 / 763 (89.91%) |
29-
| @idle-engine/renderer-webgpu | 1817 / 1819 (99.89%) | 493 / 495 (99.60%) | 95 / 95 (100.00%) | 1817 / 1819 (99.89%) |
30-
| @idle-engine/shell-desktop | 857 / 857 (100.00%) | 225 / 225 (100.00%) | 44 / 44 (100.00%) | 857 / 857 (100.00%) |
26+
| @idle-engine/core | 24720 / 27308 (90.52%) | 5450 / 6463 (84.33%) | 1435 / 1534 (93.55%) | 24720 / 27308 (90.52%) |
27+
| @idle-engine/renderer-contract | 516 / 523 (98.66%) | 134 / 138 (97.10%) | 27 / 27 (100.00%) | 516 / 523 (98.66%) |
28+
| @idle-engine/renderer-debug | 794 / 796 (99.75%) | 263 / 290 (90.69%) | 38 / 38 (100.00%) | 794 / 796 (99.75%) |
29+
| @idle-engine/renderer-webgpu | 1829 / 1843 (99.24%) | 498 / 505 (98.61%) | 95 / 96 (98.96%) | 1829 / 1843 (99.24%) |
30+
| @idle-engine/shell-desktop | 863 / 863 (100.00%) | 226 / 227 (99.56%) | 44 / 44 (100.00%) | 863 / 863 (100.00%) |

docs/issue-814-design.md

Lines changed: 224 additions & 0 deletions
Large diffs are not rendered by default.

packages/core/src/replay/visual-replay.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ describe('visual replay', () => {
9393
renderFrame: options.renderFrame,
9494
contentHash: options.contentHash,
9595
},
96+
scene: {
97+
camera: { x: 0, y: 0, zoom: 1 },
98+
},
9699
passes: [{ id: 'world' }, { id: 'ui' }],
97100
draws: [
98101
{
@@ -273,6 +276,58 @@ describe('visual replay', () => {
273276
).rejects.toThrow(/"event":"visual_replay_mismatch"/);
274277
});
275278

279+
it('reports a diffable mismatch summary when the RCB camera differs', async () => {
280+
resetRNG();
281+
setRNGSeed(4242);
282+
283+
const content = createGoldContentPack();
284+
const wiring = createGameRuntime({ content, stepSizeMs: 100 });
285+
const stepSizeMs = wiring.runtime.getStepSizeMs();
286+
287+
const recorder = new SimReplayRecorder({ content, wiring, recordedAt: 0, capturedAt: 0 });
288+
289+
wiring.runtime.tick(stepSizeMs);
290+
291+
const step = wiring.runtime.getCurrentStep() - 1;
292+
const simTimeMs = step * stepSizeMs;
293+
const goldAmount = getGoldAmount(wiring);
294+
const contentHash = content.digest.hash;
295+
296+
const recordedRcb = buildRcb({ step, simTimeMs, renderFrame: step, contentHash, goldAmount });
297+
const replay = recorder.export({ capturedAt: 0 });
298+
const replayWithFrames = {
299+
...replay,
300+
frames: {
301+
viewModels: [],
302+
rcbs: [
303+
{
304+
renderFrame: step,
305+
step,
306+
hash: await hashRenderCommandBuffer(recordedRcb),
307+
rcb: recordedRcb,
308+
},
309+
],
310+
},
311+
};
312+
313+
const decoded = decodeSimReplayJsonLines(encodeSimReplayJsonLines(replayWithFrames));
314+
315+
await expect(
316+
runCombinedReplay({
317+
content,
318+
replay: decoded,
319+
buildRenderCommandBuffers: () => [
320+
{
321+
...recordedRcb,
322+
scene: {
323+
camera: { x: 1, y: 0, zoom: 1 },
324+
},
325+
},
326+
],
327+
}),
328+
).rejects.toThrow(/"stream":"rcb"/);
329+
});
330+
276331
it('replays schemaVersion 1 streams without visual builders', async () => {
277332
resetRNG();
278333
setRNGSeed(4242);

packages/renderer-contract/dist/render-compiler.d.ts.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/renderer-contract/dist/render-compiler.js

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/renderer-contract/dist/render-compiler.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/renderer-contract/dist/types.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export declare const RENDERER_CONTRACT_SCHEMA_VERSION: 3;
1+
export declare const RENDERER_CONTRACT_SCHEMA_VERSION: 4;
22
export type RendererContractSchemaVersion = typeof RENDERER_CONTRACT_SCHEMA_VERSION;
33
export type Sha256Hex = string;
44
export type AssetId = string & {
@@ -142,6 +142,9 @@ export interface ScissorPopDraw {
142142
export type RenderDraw = ClearDraw | RectDraw | ImageDraw | TextDraw | ScissorPushDraw | ScissorPopDraw;
143143
export interface RenderCommandBuffer {
144144
readonly frame: FrameHeader;
145+
readonly scene: {
146+
readonly camera: Camera2D;
147+
};
145148
readonly passes: readonly RenderPass[];
146149
readonly draws: readonly RenderDraw[];
147150
}

packages/renderer-contract/dist/types.d.ts.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/renderer-contract/dist/types.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/renderer-contract/src/hashing.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,9 @@ describe('hashing', () => {
313313
simTimeMs: 32,
314314
contentHash: 'content:abc',
315315
},
316+
scene: {
317+
camera: { x: 0, y: 0, zoom: 1 },
318+
},
316319
passes: [
317320
{ id: 'world' },
318321
{ id: 'ui' },
@@ -340,6 +343,9 @@ describe('hashing', () => {
340343
{ id: 'world' },
341344
{ id: 'ui' },
342345
],
346+
scene: {
347+
camera: { x: 0, y: 0, zoom: 1 },
348+
},
343349
frame: {
344350
contentHash: 'content:abc',
345351
simTimeMs: 32,
@@ -352,4 +358,30 @@ describe('hashing', () => {
352358
await hashRenderCommandBuffer(b),
353359
);
354360
});
361+
362+
it('includes scene.camera in RenderCommandBuffer hashes', async () => {
363+
const base: RenderCommandBuffer = {
364+
frame: {
365+
schemaVersion: RENDERER_CONTRACT_SCHEMA_VERSION,
366+
step: 1,
367+
simTimeMs: 16,
368+
contentHash: 'content:test',
369+
},
370+
scene: {
371+
camera: { x: 0, y: 0, zoom: 1 },
372+
},
373+
passes: [{ id: 'world' }],
374+
draws: [],
375+
};
376+
377+
const hashA = await hashRenderCommandBuffer(base);
378+
const hashB = await hashRenderCommandBuffer({
379+
...base,
380+
scene: {
381+
camera: { x: 0, y: 0, zoom: 2 },
382+
},
383+
});
384+
385+
expect(hashA).not.toEqual(hashB);
386+
});
355387
});

0 commit comments

Comments
 (0)