@vgai/engine 0.5.13 → 0.5.14
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.
- package/dist/adapter/adapter-module.d.ts +447 -0
- package/dist/adapter/adapter-module.d.ts.map +1 -0
- package/dist/adapter/adapter-module.js +308 -0
- package/dist/adapter/authoring.d.ts +357 -10
- package/dist/adapter/authoring.d.ts.map +1 -1
- package/dist/adapter/authoring.js +87 -1
- package/dist/adapter/constraint.d.ts +9 -3
- package/dist/adapter/constraint.d.ts.map +1 -1
- package/dist/adapter/finders/finder-result.d.ts +28 -0
- package/dist/adapter/finders/finder-result.d.ts.map +1 -0
- package/dist/adapter/finders/finder-result.js +10 -0
- package/dist/adapter/finders/index.d.ts +38 -0
- package/dist/adapter/finders/index.d.ts.map +1 -0
- package/dist/adapter/finders/index.js +41 -0
- package/dist/adapter/finders/prefabs-from-stories.d.ts +64 -0
- package/dist/adapter/finders/prefabs-from-stories.d.ts.map +1 -0
- package/dist/adapter/finders/prefabs-from-stories.js +92 -0
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +67 -0
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -0
- package/dist/adapter/finders/scenes-from-entrypoint-selection.js +226 -0
- package/dist/adapter/first-party-systems.d.ts.map +1 -1
- package/dist/adapter/first-party-systems.js +29 -0
- package/dist/adapter/index.d.ts +7 -3
- package/dist/adapter/index.d.ts.map +1 -1
- package/dist/adapter/index.js +5 -2
- package/dist/adapter/ingest/contract-system-adapters.d.ts +24 -1
- package/dist/adapter/ingest/contract-system-adapters.d.ts.map +1 -1
- package/dist/adapter/ingest/contract-system-adapters.js +100 -34
- package/dist/adapter/ingest/game-contract.d.ts +188 -2
- package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
- package/dist/adapter/ingest/game-contract.js +107 -0
- package/dist/adapter/ingest/mount-readiness.d.ts +84 -0
- package/dist/adapter/ingest/mount-readiness.d.ts.map +1 -0
- package/dist/adapter/ingest/mount-readiness.js +81 -0
- package/dist/adapter/ingest/scene-capture.d.ts +91 -2
- package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
- package/dist/adapter/ingest/scene-capture.js +110 -18
- package/dist/adapter/ingest/structural-ids.d.ts +11 -5
- package/dist/adapter/ingest/structural-ids.d.ts.map +1 -1
- package/dist/adapter/ingest/structural-ids.js +18 -9
- package/dist/adapter/ingest/upstream-pin.d.ts +4 -2
- package/dist/adapter/ingest/upstream-pin.d.ts.map +1 -1
- package/dist/adapter/ingest/upstream-pin.js +4 -2
- package/dist/adapter/ingest/visible-capture-window.d.ts +116 -0
- package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -0
- package/dist/adapter/ingest/visible-capture-window.js +141 -0
- package/dist/adapter/loop-gate-report.d.ts +3 -4
- package/dist/adapter/loop-gate-report.d.ts.map +1 -1
- package/dist/adapter/loop-gate-report.js +3 -4
- package/dist/adapter/root-adapter.d.ts +22 -2
- package/dist/adapter/root-adapter.d.ts.map +1 -1
- package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
- package/dist/adapter/setup-three-root-adapter.js +15 -0
- package/dist/adapter/system-adapter.d.ts +161 -2
- package/dist/adapter/system-adapter.d.ts.map +1 -1
- package/dist/adapter/system-adapter.js +18 -1
- package/dist/animation/camera-ownership.d.ts +4 -2
- package/dist/animation/camera-ownership.d.ts.map +1 -1
- package/dist/animation/camera-ownership.js +58 -1
- package/dist/animation/runtime-inspection.d.ts +70 -0
- package/dist/animation/runtime-inspection.d.ts.map +1 -0
- package/dist/animation/runtime-inspection.js +25 -0
- package/dist/animation/xstate-animation-binding.d.ts +9 -1
- package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
- package/dist/animation/xstate-animation-binding.js +48 -1
- package/dist/asset-formats/material.d.ts +1 -1
- package/dist/asset-formats/mesh.d.ts +1 -1
- package/dist/asset-formats/particles.d.ts +13 -0
- package/dist/asset-formats/particles.d.ts.map +1 -1
- package/dist/asset-formats/particles.js +28 -0
- package/dist/behavior/xstate-inspection.d.ts +24 -0
- package/dist/behavior/xstate-inspection.d.ts.map +1 -0
- package/dist/behavior/xstate-inspection.js +25 -0
- package/dist/core/game-loop.d.ts +7 -5
- package/dist/core/game-loop.d.ts.map +1 -1
- package/dist/core/game-loop.js +39 -18
- package/dist/core/types.d.ts +11 -12
- package/dist/core/types.d.ts.map +1 -1
- package/dist/data/data-asset.js +1 -1
- package/dist/dev/instruments.d.ts.map +1 -1
- package/dist/dev/instruments.js +5 -1
- package/dist/dev/render-debug-adapter.d.ts +19 -2
- package/dist/dev/render-debug-adapter.d.ts.map +1 -1
- package/dist/dev/render-debug-adapter.js +10 -0
- package/dist/ecs/user-data.d.ts +10 -2
- package/dist/ecs/user-data.d.ts.map +1 -1
- package/dist/ecs/user-data.js +6 -2
- package/dist/input/binding-identity.d.ts +10 -0
- package/dist/input/binding-identity.d.ts.map +1 -0
- package/dist/input/binding-identity.js +36 -0
- package/dist/input/input-manager.d.ts +18 -185
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +78 -68
- package/dist/manifest/load.d.ts +14 -0
- package/dist/manifest/load.d.ts.map +1 -1
- package/dist/manifest/load.js +6 -1
- package/dist/manifest/schema.d.ts +18 -0
- package/dist/manifest/schema.d.ts.map +1 -1
- package/dist/manifest/schema.js +64 -7
- package/dist/pixi/authoring.d.ts +27 -0
- package/dist/pixi/authoring.d.ts.map +1 -1
- package/dist/pixi/authoring.js +10 -3
- package/dist/pixi/index.d.ts +1 -1
- package/dist/pixi/index.d.ts.map +1 -1
- package/dist/pixi/index.js +1 -1
- package/dist/pixi/ingest.d.ts +8 -0
- package/dist/pixi/ingest.d.ts.map +1 -1
- package/dist/pixi/ingest.js +10 -1
- package/dist/pixi/render-pass-bracket.d.ts +56 -0
- package/dist/pixi/render-pass-bracket.d.ts.map +1 -0
- package/dist/pixi/render-pass-bracket.js +98 -0
- package/dist/pixi/scene-capture.d.ts +32 -1
- package/dist/pixi/scene-capture.d.ts.map +1 -1
- package/dist/pixi/scene-capture.js +21 -9
- package/dist/pixi/system-adapters.d.ts +29 -0
- package/dist/pixi/system-adapters.d.ts.map +1 -1
- package/dist/pixi/system-adapters.js +27 -0
- package/dist/render/particles-factory.d.ts +16 -2
- package/dist/render/particles-factory.d.ts.map +1 -1
- package/dist/render/particles-factory.js +88 -5
- package/dist/render/soft-particle-depth.d.ts +149 -0
- package/dist/render/soft-particle-depth.d.ts.map +1 -0
- package/dist/render/soft-particle-depth.js +305 -0
- package/dist/render/voxel-surface.d.ts +117 -0
- package/dist/render/voxel-surface.d.ts.map +1 -0
- package/dist/render/voxel-surface.js +263 -0
- package/dist/runtime/create-runtime.js +7 -0
- package/dist/runtime/debug-bridge.d.ts +15 -15
- package/dist/runtime/debug-bridge.d.ts.map +1 -1
- package/dist/runtime/debug-bridge.js +27 -26
- package/dist/runtime/game.d.ts.map +1 -1
- package/dist/runtime/game.js +1 -2
- package/dist/setup/setup-particles.d.ts.map +1 -1
- package/dist/setup/setup-particles.js +9 -0
- package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
- package/dist/world3d-react/r3f-adapter.js +42 -3
- package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
- package/dist/world3d-react/rapier-physics-bridge.js +201 -11
- package/dist-config/config.js.map +1 -1
- package/package.json +4 -2
- package/schemas/engine-capabilities.json +12 -12
- package/schemas/vgai-project.schema.json +41 -2
- package/src/adapter/adapter-module.ts +607 -0
- package/src/adapter/authoring.ts +440 -10
- package/src/adapter/constraint.ts +10 -3
- package/src/adapter/finders/finder-result.ts +29 -0
- package/src/adapter/finders/index.ts +62 -0
- package/src/adapter/finders/prefabs-from-stories.ts +142 -0
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +304 -0
- package/src/adapter/first-party-systems.ts +30 -0
- package/src/adapter/index.ts +32 -1
- package/src/adapter/ingest/contract-system-adapters.ts +116 -32
- package/src/adapter/ingest/game-contract.ts +265 -2
- package/src/adapter/ingest/mount-readiness.ts +140 -0
- package/src/adapter/ingest/scene-capture.ts +199 -22
- package/src/adapter/ingest/structural-ids.ts +21 -9
- package/src/adapter/ingest/upstream-pin.ts +4 -2
- package/src/adapter/ingest/visible-capture-window.ts +213 -0
- package/src/adapter/loop-gate-report.ts +3 -4
- package/src/adapter/root-adapter.ts +22 -2
- package/src/adapter/setup-three-root-adapter.ts +16 -0
- package/src/adapter/system-adapter.ts +188 -2
- package/src/animation/camera-ownership.ts +68 -3
- package/src/animation/runtime-inspection.ts +104 -0
- package/src/animation/xstate-animation-binding.ts +53 -1
- package/src/asset-formats/particles.ts +31 -0
- package/src/behavior/xstate-inspection.ts +51 -0
- package/src/core/game-loop.ts +39 -17
- package/src/core/types.ts +11 -12
- package/src/data/data-asset.ts +1 -1
- package/src/dev/instruments.ts +5 -1
- package/src/dev/render-debug-adapter.ts +22 -2
- package/src/ecs/user-data.ts +10 -2
- package/src/input/binding-identity.ts +37 -0
- package/src/input/input-manager.ts +93 -68
- package/src/manifest/load.ts +23 -2
- package/src/manifest/schema.ts +76 -7
- package/src/pixi/authoring.ts +37 -5
- package/src/pixi/index.ts +2 -0
- package/src/pixi/ingest.ts +15 -2
- package/src/pixi/render-pass-bracket.ts +117 -0
- package/src/pixi/scene-capture.ts +63 -13
- package/src/pixi/system-adapters.ts +41 -0
- package/src/render/particles-factory.ts +89 -6
- package/src/render/soft-particle-depth.ts +336 -0
- package/src/render/voxel-surface.ts +358 -0
- package/src/runtime/create-runtime.ts +7 -0
- package/src/runtime/debug-bridge.ts +32 -31
- package/src/runtime/game.ts +1 -2
- package/src/setup/setup-particles.ts +9 -0
- package/src/world3d-react/r3f-adapter.tsx +48 -2
- package/src/world3d-react/rapier-physics-bridge.tsx +249 -11
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* READINESS, declared vs measured — and the three sentences a failed mount is
|
|
3
|
+
* allowed to say (M29).
|
|
4
|
+
*
|
|
5
|
+
* ARCHITECTURE-CORE §The editor protocol, "Zero inference": the system answers
|
|
6
|
+
* a question by reading a DECLARATION, by walking ground truth, or by
|
|
7
|
+
* diagnosing declared-vs-measured drift — and a guess at a fact the game's
|
|
8
|
+
* author could have stated is a defect whose fix is a declaration slot. "Is
|
|
9
|
+
* this game ready?" was the guess: the host polled draw counts, watched a
|
|
10
|
+
* scene's child count stop growing, and waited fixed windows.
|
|
11
|
+
*
|
|
12
|
+
* The declaration is `window.vgaiGame.ready` ({@link VgaiGameReady} in
|
|
13
|
+
* `game-contract.ts`) for a self-booting game, and MOUNT COMPLETION for a
|
|
14
|
+
* host-mounted (exported-composition) root — the host runs that mount, so it
|
|
15
|
+
* answers the question without the game writing a line. The measured waits
|
|
16
|
+
* stay for everything else; what they stop being is silent, which is what
|
|
17
|
+
* {@link ReadinessSource} is for.
|
|
18
|
+
*
|
|
19
|
+
* M29's defect: a game that threw during its own async init reported as
|
|
20
|
+
* "rendered no capturable frame within timeout" — the wait's expiry, which is a
|
|
21
|
+
* CONSEQUENCE of the crash, presented as the blocker. It misdirected two sweep
|
|
22
|
+
* measurements. {@link describeMountFailure} is the fix: three different
|
|
23
|
+
* sentences pointing at three different blockers, chosen from facts the host
|
|
24
|
+
* already has (did the game declare readiness; did anything throw during its
|
|
25
|
+
* boot window).
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* WHERE a readiness answer came from.
|
|
29
|
+
*
|
|
30
|
+
* `declared` — the game (or, for a host-mounted root, the host's own completed
|
|
31
|
+
* mount) STATED it. `measured` — nobody stated it, so a host-side wait stood in.
|
|
32
|
+
* Both are legitimate; only one of them is a fact about the game, which is why
|
|
33
|
+
* every reporting surface carries this field instead of presenting the two as
|
|
34
|
+
* the same answer.
|
|
35
|
+
*/
|
|
36
|
+
export type ReadinessSource = 'declared' | 'measured';
|
|
37
|
+
/**
|
|
38
|
+
* The three blockers a mount that never reached a live world can have. Carried
|
|
39
|
+
* on the report so a reader (status bar, `vgai status`, the Console line) can
|
|
40
|
+
* branch without re-parsing the sentence.
|
|
41
|
+
*/
|
|
42
|
+
export type MountFailureKind =
|
|
43
|
+
/** Something threw during the game's own boot window. Fix that first. */
|
|
44
|
+
'crashed-before-ready'
|
|
45
|
+
/** The game DECLARED `ready`, yet no capturable render arrived and nothing
|
|
46
|
+
* was thrown. (The host awaits `ready` only after the first captured
|
|
47
|
+
* render, so whether it resolved was not observed — the sentence states
|
|
48
|
+
* exactly that.) */
|
|
49
|
+
| 'declared-ready-never-resolved'
|
|
50
|
+
/** No declaration at all: the measured fallback wait is what expired. */
|
|
51
|
+
| 'no-readiness-declaration';
|
|
52
|
+
export interface MountFailureDescription {
|
|
53
|
+
readonly kind: MountFailureKind;
|
|
54
|
+
/** The one sentence every surface prints. */
|
|
55
|
+
readonly message: string;
|
|
56
|
+
/** The errors thrown during the boot window, ATTACHED rather than left for
|
|
57
|
+
* the reader to correlate by timestamp in a console they may not have open.
|
|
58
|
+
* Empty for the two non-crash kinds. */
|
|
59
|
+
readonly pageErrors: readonly string[];
|
|
60
|
+
}
|
|
61
|
+
export interface MountFailureInput {
|
|
62
|
+
/** The game's manifest/root id, as every other report names it. */
|
|
63
|
+
readonly gameId: string;
|
|
64
|
+
/** The capture window that expired, in VISIBLE ms. */
|
|
65
|
+
readonly timeoutMs: number;
|
|
66
|
+
/** Where the readiness answer would have come from — see {@link ReadinessSource}. */
|
|
67
|
+
readonly readinessSource: ReadinessSource;
|
|
68
|
+
/** Errors observed during the game's boot window, oldest first. */
|
|
69
|
+
readonly pageErrors: readonly string[];
|
|
70
|
+
/** The underlying rejection's own text, kept verbatim at the end. */
|
|
71
|
+
readonly cause: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Which of the three sentences this failure gets.
|
|
75
|
+
*
|
|
76
|
+
* A CRASH wins over everything: if something threw during the boot window,
|
|
77
|
+
* every other reading is downstream of it, and the old single sentence's whole
|
|
78
|
+
* defect was reporting the downstream one. After that the split is the
|
|
79
|
+
* declaration itself — a game that stated `ready` and never resolved it has a
|
|
80
|
+
* different blocker (and a different owner) from a game that stated nothing and
|
|
81
|
+
* outlasted a host-side guess.
|
|
82
|
+
*/
|
|
83
|
+
export declare function describeMountFailure(input: MountFailureInput): MountFailureDescription;
|
|
84
|
+
//# sourceMappingURL=mount-readiness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount-readiness.d.ts","sourceRoot":"","sources":["../../../src/adapter/ingest/mount-readiness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,UAAU,CAAC;AAEtD;;;;GAIG;AACH,MAAM,MAAM,gBAAgB;AAC1B,yEAAyE;AACvE,sBAAsB;AACxB;;;qBAGqB;GACnB,+BAA+B;AACjC,yEAAyE;GACvE,0BAA0B,CAAC;AAE/B,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;6CAEyC;IACzC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,iBAAiB;IAChC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,qFAAqF;IACrF,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,mEAAmE;IACnE,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAQD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,uBAAuB,CA8CtF"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* READINESS, declared vs measured — and the three sentences a failed mount is
|
|
3
|
+
* allowed to say (M29).
|
|
4
|
+
*
|
|
5
|
+
* ARCHITECTURE-CORE §The editor protocol, "Zero inference": the system answers
|
|
6
|
+
* a question by reading a DECLARATION, by walking ground truth, or by
|
|
7
|
+
* diagnosing declared-vs-measured drift — and a guess at a fact the game's
|
|
8
|
+
* author could have stated is a defect whose fix is a declaration slot. "Is
|
|
9
|
+
* this game ready?" was the guess: the host polled draw counts, watched a
|
|
10
|
+
* scene's child count stop growing, and waited fixed windows.
|
|
11
|
+
*
|
|
12
|
+
* The declaration is `window.vgaiGame.ready` ({@link VgaiGameReady} in
|
|
13
|
+
* `game-contract.ts`) for a self-booting game, and MOUNT COMPLETION for a
|
|
14
|
+
* host-mounted (exported-composition) root — the host runs that mount, so it
|
|
15
|
+
* answers the question without the game writing a line. The measured waits
|
|
16
|
+
* stay for everything else; what they stop being is silent, which is what
|
|
17
|
+
* {@link ReadinessSource} is for.
|
|
18
|
+
*
|
|
19
|
+
* M29's defect: a game that threw during its own async init reported as
|
|
20
|
+
* "rendered no capturable frame within timeout" — the wait's expiry, which is a
|
|
21
|
+
* CONSEQUENCE of the crash, presented as the blocker. It misdirected two sweep
|
|
22
|
+
* measurements. {@link describeMountFailure} is the fix: three different
|
|
23
|
+
* sentences pointing at three different blockers, chosen from facts the host
|
|
24
|
+
* already has (did the game declare readiness; did anything throw during its
|
|
25
|
+
* boot window).
|
|
26
|
+
*/
|
|
27
|
+
/** The clause every kind shares: hidden time was not charged to the game, so
|
|
28
|
+
* nobody reads this as a backgrounded-tab failure (it was, twice). */
|
|
29
|
+
const VISIBLE_TIME_CLAUSE = 'Time the tab spent hidden was NOT counted against it, so this is not a backgrounded-tab ' +
|
|
30
|
+
'failure.';
|
|
31
|
+
/**
|
|
32
|
+
* Which of the three sentences this failure gets.
|
|
33
|
+
*
|
|
34
|
+
* A CRASH wins over everything: if something threw during the boot window,
|
|
35
|
+
* every other reading is downstream of it, and the old single sentence's whole
|
|
36
|
+
* defect was reporting the downstream one. After that the split is the
|
|
37
|
+
* declaration itself — a game that stated `ready` and never resolved it has a
|
|
38
|
+
* different blocker (and a different owner) from a game that stated nothing and
|
|
39
|
+
* outlasted a host-side guess.
|
|
40
|
+
*/
|
|
41
|
+
export function describeMountFailure(input) {
|
|
42
|
+
const { gameId, timeoutMs, readinessSource, pageErrors, cause } = input;
|
|
43
|
+
const budget = `${timeoutMs}ms of VISIBLE time`;
|
|
44
|
+
if (pageErrors.length > 0) {
|
|
45
|
+
const listed = pageErrors.map((error) => ` · ${error}`).join('\n');
|
|
46
|
+
return {
|
|
47
|
+
kind: 'crashed-before-ready',
|
|
48
|
+
pageErrors: [...pageErrors],
|
|
49
|
+
message: `Ingest game "${gameId}" CRASHED BEFORE IT BECAME READY: ${pageErrors.length} error(s) ` +
|
|
50
|
+
`were thrown while it was booting, and the capture window (${budget}) then expired. ` +
|
|
51
|
+
'The expiry is a CONSEQUENCE of the crash, not the blocker — fix these first:\n' +
|
|
52
|
+
`${listed}\n` +
|
|
53
|
+
`${VISIBLE_TIME_CLAUSE} Underlying wait: ${cause}`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (readinessSource === 'declared') {
|
|
57
|
+
return {
|
|
58
|
+
kind: 'declared-ready-never-resolved',
|
|
59
|
+
pageErrors: [],
|
|
60
|
+
message: `Ingest game "${gameId}" DECLARED A READINESS SIGNAL AND STILL PRODUCED NO CAPTURABLE ` +
|
|
61
|
+
`RENDER: it stated \`window.vgaiGame.ready\`, but the capture window (${budget}) expired ` +
|
|
62
|
+
"before the editor's three saw a render, and nothing was thrown. (The host awaits " +
|
|
63
|
+
'`ready` only after the first captured render, so whether it resolved was not observed.) ' +
|
|
64
|
+
"Nothing is wrong with the host's wait — the likely blocker is whatever the game's own " +
|
|
65
|
+
'boot is waiting on (an asset fetch, a socket, a user gesture the editor never makes), ' +
|
|
66
|
+
'or a game that draws through a renderer the capture trap cannot see. ' +
|
|
67
|
+
`${VISIBLE_TIME_CLAUSE} Underlying wait: ${cause}`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
kind: 'no-readiness-declaration',
|
|
72
|
+
pageErrors: [],
|
|
73
|
+
message: `Ingest game "${gameId}" DECLARED NO READINESS SIGNAL, and the MEASURED fallback wait ` +
|
|
74
|
+
`expired: the editor's three saw no render within ${budget} and nothing was thrown, so ` +
|
|
75
|
+
'the host has no way to tell a slow boot from a stopped one. Recovery: declare ' +
|
|
76
|
+
'`window.vgaiGame.ready` (a promise resolved when the game has built its world) so this ' +
|
|
77
|
+
'question stops being measured — or, if the game genuinely needs longer on screen, raise ' +
|
|
78
|
+
`"captureTimeoutMs" in the root's ingest block. ${VISIBLE_TIME_CLAUSE} ` +
|
|
79
|
+
`Underlying wait: ${cause}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
* (`docs/f13-bloom-composer-proof/record-fixed.mjs`).
|
|
50
50
|
*/
|
|
51
51
|
import type * as THREE from 'three';
|
|
52
|
+
import { type VisibilityClock, type VisibleCaptureWindow } from './visible-capture-window';
|
|
52
53
|
/** A live runtime captured from an external game on its first rendered frame. */
|
|
53
54
|
export interface CapturedRuntime {
|
|
54
55
|
scene: THREE.Scene;
|
|
@@ -79,7 +80,65 @@ export interface SceneCaptureOptions {
|
|
|
79
80
|
* so (`packages/editor/src/host-renderers.ts`).
|
|
80
81
|
*/
|
|
81
82
|
isHostRenderer?: (renderer: unknown) => boolean;
|
|
83
|
+
/**
|
|
84
|
+
* The game's OWN declared world, read from its contract
|
|
85
|
+
* (`window.vgaiGame` — the host passes a reader, never a cached value, because
|
|
86
|
+
* the contract is declared by the game's modules and may not exist yet when
|
|
87
|
+
* the trap installs).
|
|
88
|
+
*
|
|
89
|
+
* When it answers non-null, the DECLARATION decides: only a render of that
|
|
90
|
+
* scene is adopted, and first-render-wins never runs. When it answers null —
|
|
91
|
+
* the case for every game that declares nothing — behaviour is unchanged and
|
|
92
|
+
* the adoption is reported as `measured`.
|
|
93
|
+
*/
|
|
94
|
+
declaredScene?: () => unknown;
|
|
95
|
+
/**
|
|
96
|
+
* How the world was adopted, and every DISTINCT world seen afterwards.
|
|
97
|
+
*
|
|
98
|
+
* First-non-host-render-wins is a good measured default and a permanent,
|
|
99
|
+
* SILENT commitment: a splash scene, a shadow pre-pass, or a
|
|
100
|
+
* render-to-texture warm-up that happens to draw first is adopted as the game
|
|
101
|
+
* forever, and the real world that renders one frame later reaches no reader
|
|
102
|
+
* at all. This is that reader. It never changes which world is adopted — it
|
|
103
|
+
* makes the ambiguity a recorded fact (`packages/editor/src/world-adoption.ts`
|
|
104
|
+
* publishes it to `vgai status`).
|
|
105
|
+
*
|
|
106
|
+
* Post-processing games legitimately render several (scene, camera) pairs per
|
|
107
|
+
* frame, so alternates are INFORMATION, never an error. Host renders are
|
|
108
|
+
* excluded by the same `isHostRenderer` declaration the adoption itself uses.
|
|
109
|
+
*/
|
|
110
|
+
onWorldAdoption?: (event: WorldAdoptionEvent) => void;
|
|
82
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* One world-adoption fact. `adopted` fires exactly once, when the trap commits
|
|
114
|
+
* to a (scene, camera, renderer) triple; `alternate` fires for each DISTINCT
|
|
115
|
+
* triple seen afterwards, up to {@link MAX_RECORDED_ALTERNATES}.
|
|
116
|
+
*/
|
|
117
|
+
export type WorldAdoptionEvent = {
|
|
118
|
+
readonly phase: 'adopted';
|
|
119
|
+
/** `declared` = the contract named this scene; `measured` = first render won. */
|
|
120
|
+
readonly source: 'declared' | 'measured';
|
|
121
|
+
readonly sceneId: string;
|
|
122
|
+
readonly cameraId: string;
|
|
123
|
+
} | {
|
|
124
|
+
readonly phase: 'alternate';
|
|
125
|
+
readonly sceneId: string;
|
|
126
|
+
readonly cameraId: string;
|
|
127
|
+
/** `false` ⇒ a SECOND renderer is drawing, which is the stronger signal. */
|
|
128
|
+
readonly sameRenderer: boolean;
|
|
129
|
+
/** Draws observed when this alternate first appeared — how far past the
|
|
130
|
+
* adoption it is, without a wall clock. */
|
|
131
|
+
readonly drawCount: number;
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* How many distinct alternates are recorded before the trap stops looking.
|
|
135
|
+
*
|
|
136
|
+
* The bound is the point: this runs inside the game's own render call, and a
|
|
137
|
+
* post-processing chain can present a new (scene, camera) pair every frame. A
|
|
138
|
+
* handful names the ambiguity; an unbounded set would turn a diagnostic into a
|
|
139
|
+
* leak on the hottest path in the process.
|
|
140
|
+
*/
|
|
141
|
+
export declare const MAX_RECORDED_ALTERNATES = 8;
|
|
83
142
|
/**
|
|
84
143
|
* Brackets ONE `render()` call the captured renderer makes.
|
|
85
144
|
*
|
|
@@ -99,6 +158,27 @@ export interface RenderPassHooks {
|
|
|
99
158
|
before(): void;
|
|
100
159
|
after(): void;
|
|
101
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Options for {@link SceneCaptureHandle.waitForCapture}. Passing a bare number
|
|
163
|
+
* is the same as `{ timeoutMs }` — the shape every existing caller uses.
|
|
164
|
+
*/
|
|
165
|
+
export interface CaptureWaitOptions {
|
|
166
|
+
/** The capture window, in VISIBLE milliseconds (default 10s). */
|
|
167
|
+
timeoutMs?: number | undefined;
|
|
168
|
+
/** Injected in tests; defaults to the document's own visibility. */
|
|
169
|
+
visibility?: VisibilityClock | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* Called with a LIVE view of the wait when it begins, and with `null` the
|
|
172
|
+
* moment it ends (captured, expired, or the window was cancelled).
|
|
173
|
+
*
|
|
174
|
+
* A wait parked on a hidden tab is otherwise indistinguishable from a hung
|
|
175
|
+
* mount: nothing renders, nothing fails, and every door reports silence.
|
|
176
|
+
* This is the seam the editor publishes to `vgai status` so the answer is
|
|
177
|
+
* "waiting for the first visible frame — the tab is hidden", not a
|
|
178
|
+
* countdown that is not running.
|
|
179
|
+
*/
|
|
180
|
+
onWait?: ((wait: VisibleCaptureWindow | null) => void) | undefined;
|
|
181
|
+
}
|
|
102
182
|
/** Handle returned by {@link installSceneCapture}. */
|
|
103
183
|
export interface SceneCaptureHandle {
|
|
104
184
|
/** The captured runtime, or null until the game renders its first frame. */
|
|
@@ -111,8 +191,17 @@ export interface SceneCaptureHandle {
|
|
|
111
191
|
* renders come through the same trap and are not the game's frame.
|
|
112
192
|
*/
|
|
113
193
|
setRenderPassHooks(hooks: RenderPassHooks | null): void;
|
|
114
|
-
/**
|
|
115
|
-
|
|
194
|
+
/**
|
|
195
|
+
* Resolve once a scene+camera is captured.
|
|
196
|
+
*
|
|
197
|
+
* The timeout is a budget of **visible** time, not wall-clock time: a hidden
|
|
198
|
+
* document cannot render (the browser parks rAF), so counting hidden time
|
|
199
|
+
* against the game is counting time it was not allowed to use. The wait
|
|
200
|
+
* therefore PARKS while `document.hidden` and resumes on `visibilitychange`
|
|
201
|
+
* — see `visible-capture-window.ts` for the whole argument. Rejects only
|
|
202
|
+
* when the window is spent with the document VISIBLE.
|
|
203
|
+
*/
|
|
204
|
+
waitForCapture(options?: number | CaptureWaitOptions): Promise<CapturedRuntime>;
|
|
116
205
|
/** Total `render()` calls observed through the trap (a liveness signal). */
|
|
117
206
|
getDrawCount(): number;
|
|
118
207
|
/** The game's last (non-null) `setAnimationLoop` callback for a renderer, so the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-capture.d.ts","sourceRoot":"","sources":["../../../src/adapter/ingest/scene-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"scene-capture.d.ts","sourceRoot":"","sources":["../../../src/adapter/ingest/scene-capture.ts"],"names":[],"mappings":"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"}
|
|
@@ -48,6 +48,25 @@
|
|
|
48
48
|
* against the `bloom-composer` fixture
|
|
49
49
|
* (`docs/f13-bloom-composer-proof/record-fixed.mjs`).
|
|
50
50
|
*/
|
|
51
|
+
import { startVisibleCaptureWindow, } from './visible-capture-window';
|
|
52
|
+
/**
|
|
53
|
+
* How many distinct alternates are recorded before the trap stops looking.
|
|
54
|
+
*
|
|
55
|
+
* The bound is the point: this runs inside the game's own render call, and a
|
|
56
|
+
* post-processing chain can present a new (scene, camera) pair every frame. A
|
|
57
|
+
* handful names the ambiguity; an unbounded set would turn a diagnostic into a
|
|
58
|
+
* leak on the hottest path in the process.
|
|
59
|
+
*/
|
|
60
|
+
export const MAX_RECORDED_ALTERNATES = 8;
|
|
61
|
+
/** A three object's identity, as a string a status facet can carry. `type` is
|
|
62
|
+
* what a reader recognizes ("Scene", "PerspectiveCamera"); `uuid` is what
|
|
63
|
+
* makes two of the same type tellable apart. */
|
|
64
|
+
function objectId(value) {
|
|
65
|
+
const obj = value;
|
|
66
|
+
const type = typeof obj?.type === 'string' ? obj.type : 'unknown';
|
|
67
|
+
const uuid = typeof obj?.uuid === 'string' ? obj.uuid : '(no uuid)';
|
|
68
|
+
return `${type}:${uuid}`;
|
|
69
|
+
}
|
|
51
70
|
/**
|
|
52
71
|
* Install the render accessor trap on `threeNamespace.WebGLRenderer.prototype`.
|
|
53
72
|
* Pass the host's `three` module so the game (which shares it) is trapped.
|
|
@@ -69,6 +88,14 @@ export function installSceneCapture(threeNamespace, effectComposerCtor, opts) {
|
|
|
69
88
|
const REAL = Symbol('vgai.realRender');
|
|
70
89
|
let captured = null;
|
|
71
90
|
let drawCount = 0;
|
|
91
|
+
/** Memoized answer of `opts.declaredScene` — asked each render until it
|
|
92
|
+
* answers, because a game declares its contract from its own modules and may
|
|
93
|
+
* not have run yet when the trap installs. Once it answers, it is fixed:
|
|
94
|
+
* a declaration that changes mid-boot is not a thing the host chases. */
|
|
95
|
+
let declaredScene = null;
|
|
96
|
+
/** Distinct alternates already reported, keyed by the same triple identity the
|
|
97
|
+
* event carries — see {@link MAX_RECORDED_ALTERNATES} for why it is bounded. */
|
|
98
|
+
const seenAlternates = new Set();
|
|
72
99
|
/** Set post-capture by the host; see {@link SceneCaptureHandle.setRenderPassHooks}. */
|
|
73
100
|
let renderPassHooks = null;
|
|
74
101
|
const waiters = [];
|
|
@@ -123,6 +150,59 @@ export function installSceneCapture(threeNamespace, effectComposerCtor, opts) {
|
|
|
123
150
|
hooks.after();
|
|
124
151
|
}
|
|
125
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Commit to a world, DECLARATION FIRST.
|
|
155
|
+
*
|
|
156
|
+
* First-non-host-render-wins is the measured default and a permanent one, so
|
|
157
|
+
* a game that states which scene is its world is not made to race its own
|
|
158
|
+
* splash screen. `declaredScene` is asked until it answers (the game's own
|
|
159
|
+
* modules declare the contract, and may not have run when the trap installed);
|
|
160
|
+
* once it does, only that scene is adopted.
|
|
161
|
+
*/
|
|
162
|
+
function adoptWorld(self, scene, camera) {
|
|
163
|
+
if (declaredScene === null)
|
|
164
|
+
declaredScene = opts?.declaredScene?.() ?? null;
|
|
165
|
+
if (declaredScene !== null && declaredScene !== scene)
|
|
166
|
+
return;
|
|
167
|
+
captured = {
|
|
168
|
+
scene: scene,
|
|
169
|
+
camera: camera,
|
|
170
|
+
renderer: self,
|
|
171
|
+
};
|
|
172
|
+
opts?.onWorldAdoption?.({
|
|
173
|
+
phase: 'adopted',
|
|
174
|
+
source: declaredScene === null ? 'measured' : 'declared',
|
|
175
|
+
sceneId: objectId(scene),
|
|
176
|
+
cameraId: objectId(camera),
|
|
177
|
+
});
|
|
178
|
+
for (const resolve of waiters.splice(0))
|
|
179
|
+
resolve(captured);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* A DISTINCT world drew after the adopted one. Recorded, never acted on:
|
|
183
|
+
* which world is adopted does not change (that would break every handle the
|
|
184
|
+
* host already built from it) — but the reader stops being the only party who
|
|
185
|
+
* could have noticed. Deduped and bounded because this is the game's own
|
|
186
|
+
* render call.
|
|
187
|
+
*/
|
|
188
|
+
function recordAlternateWorld(self, scene, camera) {
|
|
189
|
+
if (!captured || seenAlternates.size >= MAX_RECORDED_ALTERNATES)
|
|
190
|
+
return;
|
|
191
|
+
if (scene === captured.scene && camera === captured.camera)
|
|
192
|
+
return;
|
|
193
|
+
const sameRenderer = self === captured.renderer;
|
|
194
|
+
const key = `${objectId(scene)}|${objectId(camera)}|${sameRenderer}`;
|
|
195
|
+
if (seenAlternates.has(key))
|
|
196
|
+
return;
|
|
197
|
+
seenAlternates.add(key);
|
|
198
|
+
opts?.onWorldAdoption?.({
|
|
199
|
+
phase: 'alternate',
|
|
200
|
+
sceneId: objectId(scene),
|
|
201
|
+
cameraId: objectId(camera),
|
|
202
|
+
sameRenderer,
|
|
203
|
+
drawCount,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
126
206
|
Object.defineProperty(proto, 'render', {
|
|
127
207
|
configurable: true,
|
|
128
208
|
set(fn) {
|
|
@@ -153,15 +233,11 @@ export function installSceneCapture(threeNamespace, effectComposerCtor, opts) {
|
|
|
153
233
|
// Whether that happened at all came down to whether the editor
|
|
154
234
|
// rendered a frame inside the game's capture window, so the same
|
|
155
235
|
// session could pass or fail on timing alone.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
renderer: self,
|
|
162
|
-
};
|
|
163
|
-
for (const resolve of waiters.splice(0))
|
|
164
|
-
resolve(captured);
|
|
236
|
+
if (opts?.isHostRenderer?.(self) !== true && scene?.isScene) {
|
|
237
|
+
if (captured)
|
|
238
|
+
recordAlternateWorld(self, scene, camera);
|
|
239
|
+
else
|
|
240
|
+
adoptWorld(self, scene, camera);
|
|
165
241
|
}
|
|
166
242
|
return forwardRender(self, args);
|
|
167
243
|
};
|
|
@@ -226,22 +302,38 @@ export function installSceneCapture(threeNamespace, effectComposerCtor, opts) {
|
|
|
226
302
|
composer.setPixelRatio(pixelRatio);
|
|
227
303
|
}
|
|
228
304
|
},
|
|
229
|
-
waitForCapture(
|
|
305
|
+
waitForCapture(options) {
|
|
306
|
+
const opts = typeof options === 'number' ? { timeoutMs: options } : (options ?? {});
|
|
307
|
+
const timeoutMs = opts.timeoutMs ?? 10_000;
|
|
230
308
|
if (captured)
|
|
231
309
|
return Promise.resolve(captured);
|
|
232
310
|
return new Promise((resolve, reject) => {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
311
|
+
// A budget of VISIBLE time. The window disarms itself while the
|
|
312
|
+
// document is hidden and resumes when it comes back, so a tab that
|
|
313
|
+
// boots in the background waits instead of dying — and the trap it is
|
|
314
|
+
// waiting on stays installed the whole time, which is what makes the
|
|
315
|
+
// first frame after foregrounding a capture rather than a retry.
|
|
316
|
+
const captureWindow = startVisibleCaptureWindow({
|
|
317
|
+
budgetMs: timeoutMs,
|
|
318
|
+
...(opts.visibility !== undefined ? { clock: opts.visibility } : {}),
|
|
319
|
+
onExpire: () => {
|
|
320
|
+
const i = waiters.indexOf(wrapped);
|
|
321
|
+
if (i >= 0)
|
|
322
|
+
waiters.splice(i, 1);
|
|
323
|
+
opts.onWait?.(null);
|
|
324
|
+
reject(new Error(`Scene capture timed out after ${timeoutMs}ms of VISIBLE time ` +
|
|
325
|
+
`(${Math.round(captureWindow.elapsedHiddenMs())}ms hidden, which is not counted — ` +
|
|
326
|
+
'a hidden tab cannot render) — the game never rendered, ' +
|
|
327
|
+
'or it bundles its own (un-shared) copy of three.'));
|
|
328
|
+
},
|
|
329
|
+
});
|
|
240
330
|
const wrapped = (rt) => {
|
|
241
|
-
|
|
331
|
+
captureWindow.cancel();
|
|
332
|
+
opts.onWait?.(null);
|
|
242
333
|
resolve(rt);
|
|
243
334
|
};
|
|
244
335
|
waiters.push(wrapped);
|
|
336
|
+
opts.onWait?.(captureWindow);
|
|
245
337
|
});
|
|
246
338
|
},
|
|
247
339
|
uninstall() {
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
* reflection helper that rides along with it) the editor's live-three authoring
|
|
4
4
|
* adapter uses to address the objects of a world whose source carries no
|
|
5
5
|
* serve-time identity stamps
|
|
6
|
-
* (`packages/editor/src/
|
|
6
|
+
* (`packages/editor/src/projection/three.ts`, `structuralIdentity`).
|
|
7
7
|
*
|
|
8
8
|
* Identity: each object gets a **structural-path id** — deterministic from the
|
|
9
9
|
* scene's shape (position in the tree + three.js type + name), so the SAME id
|
|
10
10
|
* re-binds to the SAME object after the game rebuilds its scene within a
|
|
11
|
-
* session.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* session. The pure walk returns that identity beside the native objects; the
|
|
12
|
+
* editor keeps the reverse lookup in its authoring adapter rather than writing
|
|
13
|
+
* editor currency into a foreign graph.
|
|
14
14
|
*
|
|
15
15
|
* This module used to be the shared core of a per-game JSON sidecar
|
|
16
16
|
* persistence system, which was deleted outright (2026-08-02) — ingest edits
|
|
@@ -38,7 +38,7 @@ export interface StructuralIdWalk {
|
|
|
38
38
|
lights: number;
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
|
-
* (Re)
|
|
41
|
+
* (Re)collect structural-path ids for every object under `scene` (and, if
|
|
42
42
|
* given, the separately-captured render `camera`, under the fixed
|
|
43
43
|
* {@link CAMERA_ID}). Idempotent and deterministic from scene structure
|
|
44
44
|
* (position + type + name) — so a re-walk after the game rebuilds part of its
|
|
@@ -60,5 +60,11 @@ export interface StructuralIdWalk {
|
|
|
60
60
|
* hierarchy/inspector projections built on this walk, where they were
|
|
61
61
|
* selectable and colorable as if they were game content.
|
|
62
62
|
*/
|
|
63
|
+
export declare function collectStructuralIds(scene: THREE.Object3D, camera?: THREE.Object3D | undefined): StructuralIdWalk;
|
|
64
|
+
/**
|
|
65
|
+
* Compatibility entry point for first-party callers that explicitly want
|
|
66
|
+
* structural ids stamped into their own graph. Foreign ingest authoring uses
|
|
67
|
+
* {@link collectStructuralIds} and never calls this mutating form.
|
|
68
|
+
*/
|
|
63
69
|
export declare function assignStructuralIds(scene: THREE.Object3D, camera?: THREE.Object3D | undefined): StructuralIdWalk;
|
|
64
70
|
//# sourceMappingURL=structural-ids.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structural-ids.d.ts","sourceRoot":"","sources":["../../../src/adapter/ingest/structural-ids.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAGpC,0FAA0F;AAC1F,eAAO,MAAM,SAAS,kBAAkB,CAAC;AAoBzC;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,oBAAoB,GAAG,IAAI,CAKpF;AAED,uFAAuF;AACvF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,KAAK,CAAC,QAAQ,EACrB,MAAM,CAAC,EAAE,KAAK,CAAC,QAAQ,GAAG,SAAS,GAClC,gBAAgB,
|
|
1
|
+
{"version":3,"file":"structural-ids.d.ts","sourceRoot":"","sources":["../../../src/adapter/ingest/structural-ids.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAGpC,0FAA0F;AAC1F,eAAO,MAAM,SAAS,kBAAkB,CAAC;AAoBzC;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,oBAAoB,GAAG,IAAI,CAKpF;AAED,uFAAuF;AACvF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,KAAK,CAAC,QAAQ,EACrB,MAAM,CAAC,EAAE,KAAK,CAAC,QAAQ,GAAG,SAAS,GAClC,gBAAgB,CA8BlB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,KAAK,CAAC,QAAQ,EACrB,MAAM,CAAC,EAAE,KAAK,CAAC,QAAQ,GAAG,SAAS,GAClC,gBAAgB,CAIlB"}
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
* reflection helper that rides along with it) the editor's live-three authoring
|
|
4
4
|
* adapter uses to address the objects of a world whose source carries no
|
|
5
5
|
* serve-time identity stamps
|
|
6
|
-
* (`packages/editor/src/
|
|
6
|
+
* (`packages/editor/src/projection/three.ts`, `structuralIdentity`).
|
|
7
7
|
*
|
|
8
8
|
* Identity: each object gets a **structural-path id** — deterministic from the
|
|
9
9
|
* scene's shape (position in the tree + three.js type + name), so the SAME id
|
|
10
10
|
* re-binds to the SAME object after the game rebuilds its scene within a
|
|
11
|
-
* session.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* session. The pure walk returns that identity beside the native objects; the
|
|
12
|
+
* editor keeps the reverse lookup in its authoring adapter rather than writing
|
|
13
|
+
* editor currency into a foreign graph.
|
|
14
14
|
*
|
|
15
15
|
* This module used to be the shared core of a per-game JSON sidecar
|
|
16
16
|
* persistence system, which was deleted outright (2026-08-02) — ingest edits
|
|
@@ -31,7 +31,7 @@ export const CAMERA_ID = 'ingest:camera';
|
|
|
31
31
|
const EDITOR_ONLY_LAYER_MASK = 1 << 31;
|
|
32
32
|
/**
|
|
33
33
|
* Editor furniture, not game content. Excluded from the walk entirely — see
|
|
34
|
-
* {@link
|
|
34
|
+
* {@link collectStructuralIds} for why that exclusion is what makes the id
|
|
35
35
|
* scheme's central promise true.
|
|
36
36
|
*/
|
|
37
37
|
function isEditorOnly(o) {
|
|
@@ -53,7 +53,7 @@ export function colorMaterialOf(o) {
|
|
|
53
53
|
return m?.color ? m : null;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
|
-
* (Re)
|
|
56
|
+
* (Re)collect structural-path ids for every object under `scene` (and, if
|
|
57
57
|
* given, the separately-captured render `camera`, under the fixed
|
|
58
58
|
* {@link CAMERA_ID}). Idempotent and deterministic from scene structure
|
|
59
59
|
* (position + type + name) — so a re-walk after the game rebuilds part of its
|
|
@@ -75,14 +75,13 @@ export function colorMaterialOf(o) {
|
|
|
75
75
|
* hierarchy/inspector projections built on this walk, where they were
|
|
76
76
|
* selectable and colorable as if they were game content.
|
|
77
77
|
*/
|
|
78
|
-
export function
|
|
78
|
+
export function collectStructuralIds(scene, camera) {
|
|
79
79
|
const byId = new Map();
|
|
80
80
|
let count = 0;
|
|
81
81
|
let meshes = 0;
|
|
82
82
|
let lights = 0;
|
|
83
83
|
const visit = (o, path) => {
|
|
84
84
|
const id = `ingest:${path}:${o.type}:${o.name || ''}`;
|
|
85
|
-
setUserData(o, 'entityId', id);
|
|
86
85
|
byId.set(id, o);
|
|
87
86
|
count++;
|
|
88
87
|
if (o.isLight)
|
|
@@ -102,9 +101,19 @@ export function assignStructuralIds(scene, camera) {
|
|
|
102
101
|
};
|
|
103
102
|
visitChildren(scene, '');
|
|
104
103
|
if (camera) {
|
|
105
|
-
setUserData(camera, 'entityId', CAMERA_ID);
|
|
106
104
|
byId.set(CAMERA_ID, camera);
|
|
107
105
|
count++;
|
|
108
106
|
}
|
|
109
107
|
return { byId, count, meshes, lights };
|
|
110
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Compatibility entry point for first-party callers that explicitly want
|
|
111
|
+
* structural ids stamped into their own graph. Foreign ingest authoring uses
|
|
112
|
+
* {@link collectStructuralIds} and never calls this mutating form.
|
|
113
|
+
*/
|
|
114
|
+
export function assignStructuralIds(scene, camera) {
|
|
115
|
+
const walk = collectStructuralIds(scene, camera);
|
|
116
|
+
for (const [id, object] of walk.byId)
|
|
117
|
+
setUserData(object, 'entityId', id);
|
|
118
|
+
return walk;
|
|
119
|
+
}
|
|
@@ -27,8 +27,10 @@
|
|
|
27
27
|
* - Commit: **`6339493130b4b1d4d28f0f52d17b2fba738c7d47`**
|
|
28
28
|
*
|
|
29
29
|
* i.e. a Markdown bullet whose label is `Commit:` and whose value is a bold,
|
|
30
|
-
* backtick-code full (or abbreviated) git SHA. Every vendored game in
|
|
31
|
-
* repo (`tanks`, `
|
|
30
|
+
* backtick-code full (or abbreviated) git SHA. Every BUNDLE-vendored game in
|
|
31
|
+
* this repo (`tanks`, `simcity`) follows this exact shape. Source-tree
|
|
32
|
+
* vendored games carry their pin in `vendor/games/<id>.UPSTREAM.lock`'s
|
|
33
|
+
* machine-readable `commit` instead and never reach this parser.
|
|
32
34
|
*/
|
|
33
35
|
/**
|
|
34
36
|
* Extract the pinned upstream commit hash from a vendored game's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upstream-pin.d.ts","sourceRoot":"","sources":["../../../src/adapter/ingest/upstream-pin.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"upstream-pin.d.ts","sourceRoot":"","sources":["../../../src/adapter/ingest/upstream-pin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAKH;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAElE"}
|
|
@@ -27,8 +27,10 @@
|
|
|
27
27
|
* - Commit: **`6339493130b4b1d4d28f0f52d17b2fba738c7d47`**
|
|
28
28
|
*
|
|
29
29
|
* i.e. a Markdown bullet whose label is `Commit:` and whose value is a bold,
|
|
30
|
-
* backtick-code full (or abbreviated) git SHA. Every vendored game in
|
|
31
|
-
* repo (`tanks`, `
|
|
30
|
+
* backtick-code full (or abbreviated) git SHA. Every BUNDLE-vendored game in
|
|
31
|
+
* this repo (`tanks`, `simcity`) follows this exact shape. Source-tree
|
|
32
|
+
* vendored games carry their pin in `vendor/games/<id>.UPSTREAM.lock`'s
|
|
33
|
+
* machine-readable `commit` instead and never reach this parser.
|
|
32
34
|
*/
|
|
33
35
|
/** Matches `- Commit: **\`<sha>\`**` (any amount of internal whitespace, case-insensitive hex). */
|
|
34
36
|
const COMMIT_BULLET_RE = /-\s*Commit:\s*\*\*`([0-9a-fA-F]{7,40})`\*\*/;
|