Skip to content

Commit 76ee16f

Browse files
author
DavidQ
committed
Move background render order into shared engine pipeline with game custom draw callback - PR_26133_111-engine-background-render-pipeline
1 parent 50e3759 commit 76ee16f

5 files changed

Lines changed: 219 additions & 73 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# PR_26133_111-engine-background-render-pipeline Report
2+
3+
## Summary
4+
- Read `docs/dev/PROJECT_INSTRUCTIONS.md` before implementation.
5+
- Used available `PR_26133_110-collision-inspector-and-background-flow_report.md` and existing `codex_review.diff` references as prior context; no `PR_26133_110` delta ZIP was present under `tmp/`.
6+
- Moved shared frame rendering into `Engine.renderFrame()` with `Engine.renderBackgroundPipeline()` ordering: clear, background color, custom background callback, runtime overlay sync, background image, then scene/game objects.
7+
- Added optional `customBackgroundCallback` wiring on the engine and scene without hardcoding Asteroids into shared engine code.
8+
- Moved Asteroids starfield drawing from the legacy scene background-effects hook into `scene.customBackgroundCallback`.
9+
- Kept existing Asset Manager background color wiring and the `assets.color.background.game` key unchanged.
10+
11+
## Changed Files
12+
- `src/engine/core/Engine.js`
13+
- `games/Asteroids/game/AsteroidsGameScene.js`
14+
- `tests/playwright/tools/WorkspaceManagerV2.spec.mjs`
15+
- `docs/dev/reports/PR_26133_111-engine-background-render-pipeline_report.md`
16+
17+
## Pre-Existing Dirty Files
18+
- `games/Asteroids/game.manifest.json` was already modified before this BUILD and was not edited for this PR.
19+
20+
## Validation
21+
- PASS: `node --check src/engine/core/Engine.js`
22+
- PASS: `node --check games/Asteroids/game/AsteroidsGameScene.js`
23+
- PASS: `node --check tests/playwright/tools/WorkspaceManagerV2.spec.mjs`
24+
- PASS: `git diff --check -- src/engine/core/Engine.js games/Asteroids/game/AsteroidsGameScene.js tests/playwright/tools/WorkspaceManagerV2.spec.mjs` (line-ending warnings only)
25+
- PASS: `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list -g "loads Object Vector Studio V2 runtime assets into Asteroids gameplay rendering"` (1 passed)
26+
- PASS: targeted background render-order validation in the Asteroids runtime test asserted `clear`, `background-color`, `custom-background`, `overlay`, `background-image`, `game-objects`.
27+
- PASS: targeted Asteroids custom background callback validation asserted `scene.customBackgroundCallback` exists and draws starfield rects.
28+
- PASS: `npm run test:workspace-v2` (56 passed)
29+
- PASS: Playwright V8 coverage reports regenerated at `docs/dev/reports/playwright_v8_coverage_report.txt` and `docs/dev/reports/coverage_changed_js_guardrail.txt`.
30+
31+
## Playwright
32+
- Playwright impacted: Yes.
33+
- Validated shared engine render ordering, Asteroids custom background callback wiring, Workspace V2 behavior, runtime asset loading, fullscreen bezel state, and existing workspace/tool regressions.
34+
- Expected pass behavior: the shared renderer preserves the requested stage order and Workspace V2 passes.
35+
- Expected fail behavior: tests fail if the starfield remains outside the custom callback, background image renders before overlay, game objects render before background stages, or Workspace V2 regressions appear.
36+
37+
## Full Samples Smoke
38+
- Skipped full samples smoke test by request.
39+
- Reason: this PR changes shared render sequencing but is covered by the targeted Asteroids runtime validation and `npm run test:workspace-v2`; sample JSON launch validation remains out-of-scope until sample alignment.
40+
41+
## Manual Validation
42+
1. Open `games/Asteroids/index.html`.
43+
2. Wait for boot completion.
44+
3. Start gameplay.
45+
4. Expected: the canvas clears, background color resolves from `assets.color.background.game`, Asteroids starfield renders through the custom background callback, the runtime overlay remains synchronized, the gameplay background image draws before game objects, and gameplay objects remain visible/interactive.
46+
47+
## ZIP
48+
- Output path: `tmp/PR_26133_111-engine-background-render-pipeline_delta.zip`

games/Asteroids/game.manifest.json

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -224,12 +224,12 @@
224224
"locked": false,
225225
"geometry": {
226226
"point1": {
227-
"x": -7,
228-
"y": 16
227+
"x": -3,
228+
"y": 7
229229
},
230230
"point2": {
231-
"x": -0.079,
232-
"y": 19.96
231+
"x": 0,
232+
"y": 9
233233
}
234234
},
235235
"style": {
@@ -254,12 +254,12 @@
254254
"locked": false,
255255
"geometry": {
256256
"point1": {
257-
"x": 7,
258-
"y": 16
257+
"x": 3,
258+
"y": 7
259259
},
260260
"point2": {
261-
"x": -0.079,
262-
"y": 19.96
261+
"x": 0,
262+
"y": 9
263263
}
264264
},
265265
"style": {
@@ -286,23 +286,19 @@
286286
"points": [
287287
{
288288
"x": 0,
289-
"y": -24
290-
},
291-
{
292-
"x": 21,
293-
"y": 24
289+
"y": -16
294290
},
295291
{
296-
"x": 7,
297-
"y": 16
292+
"x": 9,
293+
"y": 10
298294
},
299295
{
300-
"x": -7,
301-
"y": 16
296+
"x": 0,
297+
"y": 5
302298
},
303299
{
304-
"x": -21,
305-
"y": 24
300+
"x": -9,
301+
"y": 10
306302
}
307303
]
308304
},
@@ -316,7 +312,6 @@
316312
false,
317313
false,
318314
false,
319-
false,
320315
false
321316
],
322317
"startPointStyle": "square",
@@ -339,11 +334,11 @@
339334
"geometry": {
340335
"point1": {
341336
"x": 0,
342-
"y": 24
337+
"y": 10
343338
},
344339
"point2": {
345-
"x": -7,
346-
"y": 16
340+
"x": -3,
341+
"y": 7
347342
}
348343
},
349344
"style": {
@@ -368,12 +363,12 @@
368363
"locked": false,
369364
"geometry": {
370365
"point1": {
371-
"x": 7,
372-
"y": 16
366+
"x": 3,
367+
"y": 7
373368
},
374369
"point2": {
375370
"x": 0,
376-
"y": 24
371+
"y": 10
377372
}
378373
},
379374
"style": {
@@ -472,7 +467,14 @@
472467
"shapeOverrides": [
473468
{
474469
"visible": true,
475-
"shapeIndex": 0
470+
"shapeIndex": 0,
471+
"transform": {
472+
"rotation": 0,
473+
"scaleX": 1,
474+
"scaleY": 1,
475+
"x": 0,
476+
"y": 0
477+
}
476478
},
477479
{
478480
"shapeIndex": 4,
@@ -495,6 +497,28 @@
495497
"y": 0
496498
},
497499
"visible": false
500+
},
501+
{
502+
"shapeIndex": 1,
503+
"transform": {
504+
"rotation": 0,
505+
"scaleX": 1,
506+
"scaleY": 1,
507+
"x": 0,
508+
"y": 0
509+
},
510+
"visible": true
511+
},
512+
{
513+
"shapeIndex": 2,
514+
"transform": {
515+
"rotation": 0,
516+
"scaleX": 1,
517+
"scaleY": 1,
518+
"x": 0,
519+
"y": 0
520+
},
521+
"visible": true
498522
}
499523
]
500524
},
@@ -597,7 +621,7 @@
597621
],
598622
"objectOrigin": {
599623
"x": 0,
600-
"y": 0
624+
"y": -3
601625
}
602626
},
603627
{

games/Asteroids/game/AsteroidsGameScene.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,11 @@ export default class AsteroidsGameScene extends Scene {
181181
this.debugFrame = 0;
182182
this.debugEventLimit = 40;
183183
this.debugEvents = [];
184+
this.customBackgroundCallback = (renderer) => {
185+
this.world.starfield.forEach((star) => {
186+
renderer.drawRect(star.x, star.y, star.size, star.size, '#94a3b8');
187+
});
188+
};
184189
this.lastThrusting = false;
185190
this.lastRotateDirection = 0;
186191
this.lastMode = this.session.mode;
@@ -729,12 +734,6 @@ export default class AsteroidsGameScene extends Scene {
729734
this.lastPPressed = pPressed;
730735
}
731736

732-
renderBackgroundEffects(renderer) {
733-
this.world.starfield.forEach((star) => {
734-
renderer.drawRect(star.x, star.y, star.size, star.size, '#94a3b8');
735-
});
736-
}
737-
738737
render(renderer, engine) {
739738
const leaderboardTopScore = this.highScoreService.getTopScore(this.highScoreRows);
740739
const liveHudHighScore = Math.max(this.session.highScore, leaderboardTopScore);

src/engine/core/Engine.js

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export default class Engine {
3636
fullscreen = null,
3737
backgroundColorLayer = null,
3838
backgroundImageLayer = null,
39+
customBackgroundCallback = null,
3940
fullscreenBezelLayer = null,
4041
audio = null,
4142
logger = null,
@@ -76,6 +77,9 @@ export default class Engine {
7677
this.backgroundImageLayer = backgroundImageLayer || new backgroundImage({
7778
documentRef: this.documentRef
7879
});
80+
this.customBackgroundCallback = typeof customBackgroundCallback === 'function'
81+
? customBackgroundCallback
82+
: null;
7983
this.fullscreenBezelLayer = fullscreenBezelLayer || new fullscreenBezel({
8084
canvas,
8185
host: this.fullscreenTarget,
@@ -274,6 +278,60 @@ export default class Engine {
274278
}
275279
}
276280

281+
setCustomBackgroundCallback(callback) {
282+
this.customBackgroundCallback = typeof callback === 'function' ? callback : null;
283+
}
284+
285+
renderCustomBackground() {
286+
if (typeof this.customBackgroundCallback === 'function') {
287+
try {
288+
return this.customBackgroundCallback(this.renderer, { scene: this.scene, engine: this });
289+
} catch (error) {
290+
this.trackRuntimeError('engine.customBackgroundCallback', error, { severity: 'error' });
291+
throw error;
292+
}
293+
}
294+
295+
if (this.scene && typeof this.scene.customBackgroundCallback === 'function') {
296+
try {
297+
return this.scene.customBackgroundCallback(this.renderer, { scene: this.scene, engine: this });
298+
} catch (error) {
299+
this.trackRuntimeError('scene.customBackgroundCallback', error, { severity: 'error' });
300+
throw error;
301+
}
302+
}
303+
304+
return null;
305+
}
306+
307+
syncRuntimeOverlay() {
308+
const fullscreenActive = this.fullscreen?.getState?.().active === true;
309+
const fullscreenElement = this.fullscreen?.documentRef?.fullscreenElement
310+
|| this.documentRef?.fullscreenElement
311+
|| null;
312+
return this.fullscreenBezelLayer?.sync?.({ fullscreenActive, fullscreenElement }) || null;
313+
}
314+
315+
renderBackgroundPipeline() {
316+
this.renderer.clear();
317+
this.backgroundColorLayer?.render?.(this.renderer, { scene: this.scene, engine: this });
318+
this.renderCustomBackground();
319+
this.syncRuntimeOverlay();
320+
this.backgroundImageLayer?.render?.(this.renderer, { scene: this.scene, engine: this });
321+
}
322+
323+
renderFrame() {
324+
this.renderBackgroundPipeline();
325+
if (this.scene && typeof this.scene.render === 'function') {
326+
try {
327+
this.scene.render(this.renderer, this);
328+
} catch (error) {
329+
this.trackRuntimeError('scene.render', error, { severity: 'error' });
330+
throw error;
331+
}
332+
}
333+
}
334+
277335
tick(now) {
278336
const { deltaMs, deltaSeconds } = this.frameClock.tick(now);
279337
const frameStart = performance.now();
@@ -325,30 +383,7 @@ export default class Engine {
325383
updateDurationMs = performance.now() - updateStart;
326384

327385
const renderStart = performance.now();
328-
this.renderer.clear();
329-
this.backgroundColorLayer?.render?.(this.renderer, { scene: this.scene, engine: this });
330-
if (this.scene && typeof this.scene.renderBackgroundEffects === 'function') {
331-
try {
332-
this.scene.renderBackgroundEffects(this.renderer, this);
333-
} catch (error) {
334-
this.trackRuntimeError('scene.renderBackgroundEffects', error, { severity: 'error' });
335-
throw error;
336-
}
337-
}
338-
this.backgroundImageLayer?.render?.(this.renderer, { scene: this.scene, engine: this });
339-
if (this.scene && typeof this.scene.render === 'function') {
340-
try {
341-
this.scene.render(this.renderer, this);
342-
} catch (error) {
343-
this.trackRuntimeError('scene.render', error, { severity: 'error' });
344-
throw error;
345-
}
346-
}
347-
const fullscreenActive = this.fullscreen?.getState?.().active === true;
348-
const fullscreenElement = this.fullscreen?.documentRef?.fullscreenElement
349-
|| this.documentRef?.fullscreenElement
350-
|| null;
351-
this.fullscreenBezelLayer?.sync?.({ fullscreenActive, fullscreenElement });
386+
this.renderFrame();
352387
renderDurationMs = performance.now() - renderStart;
353388

354389
const frameData = {

0 commit comments

Comments
 (0)