@vgai/engine 0.5.17 → 0.5.18

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.
@@ -0,0 +1,83 @@
1
+ /**
2
+ * adapter/renderer-config.ts — the seam a WORLD uses to state how its own frame is rendered.
3
+ *
4
+ * It lives in the ADAPTER SEAM, not in a surface entry, because `WorldRendererConfig` IS part of
5
+ * the root contract: `MountedThreeRoot.rendererConfig` reports it (`./root-adapter.ts`) and
6
+ * `./root-seam-contract.ts` already lists it as a contract field. The seam defines the shape; the
7
+ * surface entries and the editor CONFORM to it — `world3d-react/r3f-adapter.tsx` is the declarer,
8
+ * `runtime/create-runtime.ts` the host, `editor/src/components/ViewportPanel.tsx` the editor's
9
+ * applier. Do NOT move it back under `world3d-react/`: nothing here touches react, and a core
10
+ * adapter file reaching into a react entry is exactly what `react-core-import-ban.test.ts` forbids
11
+ * (that ban carries no type-only carve-out on purpose — unlike its pixi sibling, whose carve-out
12
+ * exists only because `PIXI.Container` is a third-party type core cannot relocate).
13
+ *
14
+ * The host owns the `WebGLRenderer` (`ThreeHostContext.renderer`) and configures it with this
15
+ * engine's defaults: ACES tone mapping, sRGB output, PCF-soft shadows. Those defaults are right for
16
+ * a world authored against them and WRONG for a world that was authored against a different
17
+ * engine's pipeline — an imported Godot 3 GLES2 game does gamma-space lighting with no tonemapper
18
+ * at all, so ACES quietly desaturates and darkens every colour its author picked.
19
+ *
20
+ * So a world may DECLARE the pipeline it was authored for, and `createR3FAdapter` applies it to the
21
+ * host's renderer for the life of the mount, restoring what it found on dispose. Three properties
22
+ * of that shape are load-bearing:
23
+ *
24
+ * - **It is per-renderer, never engine-wide.** Every field here is a `WebGLRenderer` instance
25
+ * property, and a play root gets its own renderer (`create-runtime.ts`). Nothing here reaches a
26
+ * module-level three global, so one world's declaration cannot change how the editor's own
27
+ * viewport, another root, or a thumbnail bake renders.
28
+ * - **Absent means "leave the host's value alone".** Every field is optional and an omitted one
29
+ * is never written, so declaring a tone mapping does not silently reset the clear colour.
30
+ * - **It is restored on dispose.** The renderer outlives the mount, so a world that did not put
31
+ * back what it found would leak its pipeline into whatever mounts next.
32
+ *
33
+ * This is deliberately NOT a general render-settings system. It carries what a world can honestly
34
+ * state about its own colour pipeline and nothing else; a property the host fixes at CONSTRUCTION
35
+ * (the WebGL context's `antialias` attribute, and therefore the MSAA sample count) cannot be
36
+ * declared here, because there would be no honest moment to apply it.
37
+ */
38
+ import type * as THREE from 'three';
39
+ /** The tone-mapping operators three exposes, named as data rather than as three's numeric enum. */
40
+ export type WorldToneMapping = 'none' | 'linear' | 'reinhard' | 'cineon' | 'aces' | 'agx' | 'neutral';
41
+ /**
42
+ * The output transfer function the frame is written with.
43
+ *
44
+ * - `srgb` — three's own default and this engine's: linear lighting, sRGB encode on output.
45
+ * - `srgb-linear` — NO output transform. This is what a gamma-space renderer needs: the shading
46
+ * result is already in display space and encoding it a second time washes the frame out.
47
+ */
48
+ export type WorldOutputColorSpace = 'srgb' | 'srgb-linear';
49
+ /**
50
+ * The shadow-map filter, named as data rather than as three's numeric enum.
51
+ *
52
+ * This is a renderer INSTANCE property (`WebGLRenderer.shadowMap.type`), not a context attribute,
53
+ * so unlike MSAA it has an honest moment at which a world can ask for it — which is the whole test
54
+ * this file's header states. A source engine that declares its own shadow filter (Godot 3's
55
+ * `rendering/quality/shadows/filter_mode`) would otherwise inherit whatever the host built with.
56
+ */
57
+ export type WorldShadowMapType = 'basic' | 'pcf' | 'pcf-soft' | 'vsm';
58
+ /** What a world may declare about the renderer that draws it. Every field is optional; see header. */
59
+ export interface WorldRendererConfig {
60
+ readonly toneMapping?: WorldToneMapping | undefined;
61
+ readonly toneMappingExposure?: number | undefined;
62
+ readonly outputColorSpace?: WorldOutputColorSpace | undefined;
63
+ /**
64
+ * `WebGLRenderer.shadowMap.type`. Writing it after a shadow map has already been built needs
65
+ * `shadowMap.needsUpdate`, which this function sets — three caches the compiled depth material
66
+ * per type and would otherwise keep filtering with the previous one.
67
+ */
68
+ readonly shadowMapType?: WorldShadowMapType | undefined;
69
+ /**
70
+ * The colour the frame is cleared to, as a CSS hex string. The renderer's existing clear ALPHA
71
+ * is preserved: a stacked canvas is transparent on purpose (`create-runtime.ts` gives every
72
+ * non-bottom root `alpha: true`), and forcing it opaque here would hide every layer below.
73
+ */
74
+ readonly clearColor?: string | undefined;
75
+ }
76
+ /**
77
+ * Apply `config` to `renderer`, returning the restore function that puts back what was there.
78
+ *
79
+ * `three` is passed in rather than imported for values so the enum constants come from the HOST's
80
+ * three instance — the same identity rule `r3f-adapter.tsx` follows for the scene and camera.
81
+ */
82
+ export declare function applyWorldRendererConfig(three: typeof THREE, renderer: THREE.WebGLRenderer, config: WorldRendererConfig): () => void;
83
+ //# sourceMappingURL=renderer-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer-config.d.ts","sourceRoot":"","sources":["../../src/adapter/renderer-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAEpC,mGAAmG;AACnG,MAAM,MAAM,gBAAgB,GACxB,MAAM,GACN,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,MAAM,GACN,KAAK,GACL,SAAS,CAAC;AAEd;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,aAAa,CAAC;AAE3D;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,KAAK,GAAG,UAAU,GAAG,KAAK,CAAC;AAEtE,sGAAsG;AACtG,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IAC9D;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IACxD;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,KAAK,EACnB,QAAQ,EAAE,KAAK,CAAC,aAAa,EAC7B,MAAM,EAAE,mBAAmB,GAC1B,MAAM,IAAI,CA+EZ"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * adapter/renderer-config.ts — the seam a WORLD uses to state how its own frame is rendered.
3
+ *
4
+ * It lives in the ADAPTER SEAM, not in a surface entry, because `WorldRendererConfig` IS part of
5
+ * the root contract: `MountedThreeRoot.rendererConfig` reports it (`./root-adapter.ts`) and
6
+ * `./root-seam-contract.ts` already lists it as a contract field. The seam defines the shape; the
7
+ * surface entries and the editor CONFORM to it — `world3d-react/r3f-adapter.tsx` is the declarer,
8
+ * `runtime/create-runtime.ts` the host, `editor/src/components/ViewportPanel.tsx` the editor's
9
+ * applier. Do NOT move it back under `world3d-react/`: nothing here touches react, and a core
10
+ * adapter file reaching into a react entry is exactly what `react-core-import-ban.test.ts` forbids
11
+ * (that ban carries no type-only carve-out on purpose — unlike its pixi sibling, whose carve-out
12
+ * exists only because `PIXI.Container` is a third-party type core cannot relocate).
13
+ *
14
+ * The host owns the `WebGLRenderer` (`ThreeHostContext.renderer`) and configures it with this
15
+ * engine's defaults: ACES tone mapping, sRGB output, PCF-soft shadows. Those defaults are right for
16
+ * a world authored against them and WRONG for a world that was authored against a different
17
+ * engine's pipeline — an imported Godot 3 GLES2 game does gamma-space lighting with no tonemapper
18
+ * at all, so ACES quietly desaturates and darkens every colour its author picked.
19
+ *
20
+ * So a world may DECLARE the pipeline it was authored for, and `createR3FAdapter` applies it to the
21
+ * host's renderer for the life of the mount, restoring what it found on dispose. Three properties
22
+ * of that shape are load-bearing:
23
+ *
24
+ * - **It is per-renderer, never engine-wide.** Every field here is a `WebGLRenderer` instance
25
+ * property, and a play root gets its own renderer (`create-runtime.ts`). Nothing here reaches a
26
+ * module-level three global, so one world's declaration cannot change how the editor's own
27
+ * viewport, another root, or a thumbnail bake renders.
28
+ * - **Absent means "leave the host's value alone".** Every field is optional and an omitted one
29
+ * is never written, so declaring a tone mapping does not silently reset the clear colour.
30
+ * - **It is restored on dispose.** The renderer outlives the mount, so a world that did not put
31
+ * back what it found would leak its pipeline into whatever mounts next.
32
+ *
33
+ * This is deliberately NOT a general render-settings system. It carries what a world can honestly
34
+ * state about its own colour pipeline and nothing else; a property the host fixes at CONSTRUCTION
35
+ * (the WebGL context's `antialias` attribute, and therefore the MSAA sample count) cannot be
36
+ * declared here, because there would be no honest moment to apply it.
37
+ */
38
+ /**
39
+ * Apply `config` to `renderer`, returning the restore function that puts back what was there.
40
+ *
41
+ * `three` is passed in rather than imported for values so the enum constants come from the HOST's
42
+ * three instance — the same identity rule `r3f-adapter.tsx` follows for the scene and camera.
43
+ */
44
+ export function applyWorldRendererConfig(three, renderer, config) {
45
+ // A host that mounts a world WITHOUT rasterizing it hands the adapter a duck-typed renderer —
46
+ // the editor's design session (`createDesignTimeRenderer`: four members, deliberately never
47
+ // widened) and jsdom test harnesses both do. Such a surface has no colour pipeline to configure:
48
+ // the frame the user sees is drawn by a DIFFERENT renderer (the editor's own), so applying the
49
+ // world's config there is meaningless — and calling `getClearColor` on it is a TypeError that
50
+ // unmounts the whole world at edit time (measured: every Godot port's edit viewport blanked with
51
+ // '"world" failed to mount — renderer.getClearColor is not a function'). Detect the real
52
+ // `WebGLRenderer` surface by the one method this function must call, and no-op otherwise.
53
+ if (typeof renderer.getClearColor !== 'function') {
54
+ return () => { };
55
+ }
56
+ const toneMappings = {
57
+ none: three.NoToneMapping,
58
+ linear: three.LinearToneMapping,
59
+ reinhard: three.ReinhardToneMapping,
60
+ cineon: three.CineonToneMapping,
61
+ aces: three.ACESFilmicToneMapping,
62
+ agx: three.AgXToneMapping,
63
+ neutral: three.NeutralToneMapping,
64
+ };
65
+ const colorSpaces = {
66
+ srgb: three.SRGBColorSpace,
67
+ 'srgb-linear': three.LinearSRGBColorSpace,
68
+ };
69
+ const shadowMapTypes = {
70
+ basic: three.BasicShadowMap,
71
+ pcf: three.PCFShadowMap,
72
+ 'pcf-soft': three.PCFSoftShadowMap,
73
+ vsm: three.VSMShadowMap,
74
+ };
75
+ const restores = [];
76
+ if (config.toneMapping !== undefined) {
77
+ const previous = renderer.toneMapping;
78
+ renderer.toneMapping = toneMappings[config.toneMapping];
79
+ restores.push(() => {
80
+ renderer.toneMapping = previous;
81
+ });
82
+ }
83
+ if (config.toneMappingExposure !== undefined) {
84
+ const previous = renderer.toneMappingExposure;
85
+ renderer.toneMappingExposure = config.toneMappingExposure;
86
+ restores.push(() => {
87
+ renderer.toneMappingExposure = previous;
88
+ });
89
+ }
90
+ if (config.outputColorSpace !== undefined) {
91
+ const previous = renderer.outputColorSpace;
92
+ renderer.outputColorSpace = colorSpaces[config.outputColorSpace];
93
+ restores.push(() => {
94
+ renderer.outputColorSpace = previous;
95
+ });
96
+ }
97
+ if (config.shadowMapType !== undefined && renderer.shadowMap !== undefined) {
98
+ const previous = renderer.shadowMap.type;
99
+ renderer.shadowMap.type = shadowMapTypes[config.shadowMapType];
100
+ renderer.shadowMap.needsUpdate = true;
101
+ restores.push(() => {
102
+ renderer.shadowMap.type = previous;
103
+ renderer.shadowMap.needsUpdate = true;
104
+ });
105
+ }
106
+ if (config.clearColor !== undefined) {
107
+ const previousColor = new three.Color();
108
+ renderer.getClearColor(previousColor);
109
+ // Alpha is READ BACK and re-passed, never assumed: see `clearColor`'s doc above.
110
+ const alpha = renderer.getClearAlpha();
111
+ renderer.setClearColor(new three.Color(config.clearColor), alpha);
112
+ restores.push(() => {
113
+ renderer.setClearColor(previousColor, alpha);
114
+ });
115
+ }
116
+ return () => {
117
+ // Reverse order, so a field written twice (it cannot be, today) unwinds correctly.
118
+ for (let i = restores.length - 1; i >= 0; i--)
119
+ restores[i]?.();
120
+ };
121
+ }
@@ -8,10 +8,10 @@
8
8
  */
9
9
  import type { Container } from 'pixi.js';
10
10
  import type * as THREE from 'three';
11
- import type { WorldRendererConfig } from '../world3d-react/renderer-config';
12
11
  import type { AdapterSurface } from './adapter-surface';
13
12
  import type { AuthoringAdapter } from './authoring';
14
13
  import type { HostContextFor } from './host-context';
14
+ import type { WorldRendererConfig } from './renderer-config';
15
15
  import type { SystemAdapters } from './system-adapter';
16
16
  /**
17
17
  * The ingested-world observation contract (T7.4 slice 2, "the
@@ -91,7 +91,7 @@ export interface MountedThreeRoot extends MountedRootBase {
91
91
  /**
92
92
  * The colour pipeline this world was authored for, REPORTED rather than
93
93
  * applied — the adapter has already applied it to the renderer its own host
94
- * handed it (`world3d-react/renderer-config.ts`).
94
+ * handed it (`./renderer-config.ts`).
95
95
  *
96
96
  * It is here because the host that MOUNTS a world is not always the host
97
97
  * that DRAWS it. The editor's design session mounts against a
@@ -1 +1 @@
1
- {"version":3,"file":"root-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/root-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AACpC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C;;;;;;OAMG;IACH,QAAQ,IAAI,OAAO,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAEhC,MAAM,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,IAAI,CAAC,IAAI,IAAI,CAAC;IAEd,MAAM,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,OAAO,IAAI,IAAI,CAAC;IAChB;;iFAE6E;IAC7E,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC,gFAAgF;IAChF,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC;IAClC;;;;iBAIa;IACb,QAAQ,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC;CACtC;AAED;oBACoB;AACpB,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC;IAC9B;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;CAC3D;AAED;;uEAEuE;AACvE,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;CAC3B;AAED;;;wEAGwE;AACxE,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;CACjC;AAED,yCAAyC;AACzC,MAAM,MAAM,WAAW,GAAG,gBAAgB,GAAG,eAAe,GAAG,gBAAgB,CAAC;AAEhF;;gFAEgF;AAChF,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,cAAc,IAAI,CAAC,SAAS,OAAO,GACpE,gBAAgB,GAChB,CAAC,SAAS,QAAQ,GAChB,eAAe,GACf,CAAC,SAAS,KAAK,GACb,gBAAgB,GAChB,KAAK,CAAC;AAEd;;;;;;;;;;GAUG;AACH;;;;;;GAMG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,cAAc,GAAG,OAAO;IAC7D,kDAAkD;IAClD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;kBACc;IACd,KAAK,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;CAC5D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,cAAc,IAAI;IACxD,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;CAClC,CAAC;AAEF;;;;oEAIoE;AACpE,MAAM,MAAM,cAAc,GACtB,iBAAiB,CAAC,OAAO,CAAC,GAC1B,iBAAiB,CAAC,QAAQ,CAAC,GAC3B,iBAAiB,CAAC,KAAK,CAAC,CAAC"}
1
+ {"version":3,"file":"root-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/root-adapter.ts"],"names":[],"mappings":"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"}
@@ -483,7 +483,7 @@ export const GameManifestSchema = z
483
483
  antialias: z
484
484
  .boolean()
485
485
  .describe('Whether every three root is built with a multisampled drawing buffer. This is the ' +
486
- 'ONE render property a world cannot declare for itself (world3d-react/' +
486
+ 'ONE render property a world cannot declare for itself (adapter/' +
487
487
  'renderer-config.ts): a WebGL context fixes its sample count at CREATION from this ' +
488
488
  'boolean, long before a world mounts, so it belongs to the PROJECT. The runtime ' +
489
489
  'reader is mount-manifest.ts, which threads it into createHostRenderer for each ' +
@@ -234,7 +234,7 @@ export async function mountManifestRoots(opts) {
234
234
  headless: opts.headless,
235
235
  // `rendering.antialias` reaches the WebGL context at CONSTRUCTION and can be honoured
236
236
  // nowhere else — see the manifest schema's own `rendering` block and
237
- // `world3d-react/renderer-config.ts`'s header for why it is not a world-level declaration.
237
+ // `adapter/renderer-config.ts`'s header for why it is not a world-level declaration.
238
238
  ...(manifest.rendering === undefined ? {} : { antialias: manifest.rendering.antialias }),
239
239
  seed: resolvedSeed,
240
240
  playtest: opts.playtest,
@@ -39,9 +39,9 @@
39
39
  * should pull it into its graph; a deep import keeps this barrel's peer
40
40
  * contract to `react`/`@react-three/fiber`/`three` alone.
41
41
  */
42
+ export { applyWorldRendererConfig, type WorldOutputColorSpace, type WorldRendererConfig, type WorldToneMapping, } from '../adapter/renderer-config';
42
43
  export { EngineBridge, type EngineBridgeValue, useGameContext } from './engine-bridge';
43
44
  export { type CreateR3FAdapterOptions, createR3FAdapter } from './r3f-adapter';
44
45
  export { r3fRootFactory, resolveR3FEntryAdapter } from './r3f-root-factory';
45
- export { applyWorldRendererConfig, type WorldOutputColorSpace, type WorldRendererConfig, type WorldToneMapping, } from './renderer-config';
46
46
  export { createR3FRootContext, DEFAULT_INPUT_MAP_PATH, type R3FGameContext, type R3FRootContextOptions, type R3FRootRuntime, } from './world-context';
47
47
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/world3d-react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACvF,OAAO,EAAE,KAAK,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EACL,wBAAwB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,GACpB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/world3d-react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EACL,wBAAwB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,GACtB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACvF,OAAO,EAAE,KAAK,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,GACpB,MAAM,iBAAiB,CAAC"}
@@ -39,8 +39,8 @@
39
39
  * should pull it into its graph; a deep import keeps this barrel's peer
40
40
  * contract to `react`/`@react-three/fiber`/`three` alone.
41
41
  */
42
+ export { applyWorldRendererConfig, } from '../adapter/renderer-config';
42
43
  export { EngineBridge, useGameContext } from './engine-bridge';
43
44
  export { createR3FAdapter } from './r3f-adapter';
44
45
  export { r3fRootFactory, resolveR3FEntryAdapter } from './r3f-root-factory';
45
- export { applyWorldRendererConfig, } from './renderer-config';
46
46
  export { createR3FRootContext, DEFAULT_INPUT_MAP_PATH, } from './world-context';
@@ -20,7 +20,7 @@
20
20
  */
21
21
  import { type ReactNode } from 'react';
22
22
  import type { RootAdapter } from '../adapter';
23
- import { type WorldRendererConfig } from './renderer-config';
23
+ import { type WorldRendererConfig } from '../adapter/renderer-config';
24
24
  /** What {@link createR3FAdapter} needs to build one `RootAdapter`. */
25
25
  export interface CreateR3FAdapterOptions {
26
26
  /** Stable id (telemetry/registry/conformance) — `RootAdapter.id`. */
@@ -45,7 +45,7 @@ export interface CreateR3FAdapterOptions {
45
45
  /** The colour pipeline this world was AUTHORED for, applied to the host's renderer for the life
46
46
  * of the mount and restored on dispose. Omit it (every world here does) to keep the host's own
47
47
  * defaults; declare it when the world's colours were picked against a different engine's
48
- * pipeline — see `./renderer-config.ts`. */
48
+ * pipeline — see `../adapter/renderer-config.ts`. */
49
49
  readonly renderer?: WorldRendererConfig | undefined;
50
50
  }
51
51
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"r3f-adapter.d.ts","sourceRoot":"","sources":["../../src/world3d-react/r3f-adapter.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAWH,OAAO,EAA2B,KAAK,SAAS,EAAsB,MAAM,OAAO,CAAC;AACpF,OAAO,KAAK,EAAoB,WAAW,EAAoB,MAAM,YAAY,CAAC;AAgBlF,OAAO,EAA4B,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAgDvF,sEAAsE;AACtE,MAAM,WAAW,uBAAuB;IACtC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;uBAEmB;IACnB,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B;;;;0CAIsC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClD;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7C;;;iDAG6C;IAC7C,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;CACrD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,WAAW,CA2kB9E"}
1
+ {"version":3,"file":"r3f-adapter.d.ts","sourceRoot":"","sources":["../../src/world3d-react/r3f-adapter.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAWH,OAAO,EAA2B,KAAK,SAAS,EAAsB,MAAM,OAAO,CAAC;AACpF,OAAO,KAAK,EAAoB,WAAW,EAAoB,MAAM,YAAY,CAAC;AAClF,OAAO,EAA4B,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AA+DhG,sEAAsE;AACtE,MAAM,WAAW,uBAAuB;IACtC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;uBAEmB;IACnB,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B;;;;0CAIsC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClD;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7C;;;0DAGsD;IACtD,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;CACrD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,WAAW,CA2kB9E"}
@@ -20,6 +20,7 @@
20
20
  */
21
21
  import { advance, createRoot, extend, flushSync, events as pointerEvents, useFrame, } from '@react-three/fiber';
22
22
  import { createElement, Fragment, useEffect, useMemo } from 'react';
23
+ import { applyWorldRendererConfig } from '../adapter/renderer-config';
23
24
  import { registerRenderVitals } from '../dev/register-render-vitals';
24
25
  import { createRenderDebugAdapter, frameCaptureContextFor, } from '../dev/render-debug-adapter';
25
26
  import { collectRenderMemory } from '../dev/render-memory';
@@ -30,7 +31,6 @@ import { createSoftParticleDepthPass } from '../render/soft-particle-depth';
30
31
  import { getDebugRegistry } from '../runtime/debug-registry';
31
32
  import { devBuildEnabled } from '../runtime/dev-build';
32
33
  import { EngineBridge } from './engine-bridge';
33
- import { applyWorldRendererConfig } from './renderer-config';
34
34
  import { createR3FRootContext, DEFAULT_INPUT_MAP_PATH, wireGameInputSeams } from './world-context';
35
35
  /**
36
36
  * Scene depth for any soft-particle system this world mounted, drawn between
@@ -1,73 +1,11 @@
1
1
  /**
2
- * world3d-react/renderer-config.tsthe seam a WORLD uses to state how its own frame is rendered.
2
+ * `@engine/world3d-react/renderer-config` — a published entry point.
3
3
  *
4
- * The host owns the `WebGLRenderer` (`ThreeHostContext.renderer`) and configures it with this
5
- * engine's defaults: ACES tone mapping, sRGB output, PCF-soft shadows. Those defaults are right for
6
- * a world authored against them and WRONG for a world that was authored against a different
7
- * engine's pipeline an imported Godot 3 GLES2 game does gamma-space lighting with no tonemapper
8
- * at all, so ACES quietly desaturates and darkens every colour its author picked.
9
- *
10
- * So a world may DECLARE the pipeline it was authored for, and `createR3FAdapter` applies it to the
11
- * host's renderer for the life of the mount, restoring what it found on dispose. Three properties
12
- * of that shape are load-bearing:
13
- *
14
- * - **It is per-renderer, never engine-wide.** Every field here is a `WebGLRenderer` instance
15
- * property, and a play root gets its own renderer (`create-runtime.ts`). Nothing here reaches a
16
- * module-level three global, so one world's declaration cannot change how the editor's own
17
- * viewport, another root, or a thumbnail bake renders.
18
- * - **Absent means "leave the host's value alone".** Every field is optional and an omitted one
19
- * is never written, so declaring a tone mapping does not silently reset the clear colour.
20
- * - **It is restored on dispose.** The renderer outlives the mount, so a world that did not put
21
- * back what it found would leak its pipeline into whatever mounts next.
22
- *
23
- * This is deliberately NOT a general render-settings system. It carries what a world can honestly
24
- * state about its own colour pipeline and nothing else; a property the host fixes at CONSTRUCTION
25
- * (the WebGL context's `antialias` attribute, and therefore the MSAA sample count) cannot be
26
- * declared here, because there would be no honest moment to apply it.
27
- */
28
- import type * as THREE from 'three';
29
- /** The tone-mapping operators three exposes, named as data rather than as three's numeric enum. */
30
- export type WorldToneMapping = 'none' | 'linear' | 'reinhard' | 'cineon' | 'aces' | 'agx' | 'neutral';
31
- /**
32
- * The output transfer function the frame is written with.
33
- *
34
- * - `srgb` — three's own default and this engine's: linear lighting, sRGB encode on output.
35
- * - `srgb-linear` — NO output transform. This is what a gamma-space renderer needs: the shading
36
- * result is already in display space and encoding it a second time washes the frame out.
37
- */
38
- export type WorldOutputColorSpace = 'srgb' | 'srgb-linear';
39
- /**
40
- * The shadow-map filter, named as data rather than as three's numeric enum.
41
- *
42
- * This is a renderer INSTANCE property (`WebGLRenderer.shadowMap.type`), not a context attribute,
43
- * so unlike MSAA it has an honest moment at which a world can ask for it — which is the whole test
44
- * this file's header states. A source engine that declares its own shadow filter (Godot 3's
45
- * `rendering/quality/shadows/filter_mode`) would otherwise inherit whatever the host built with.
46
- */
47
- export type WorldShadowMapType = 'basic' | 'pcf' | 'pcf-soft' | 'vsm';
48
- /** What a world may declare about the renderer that draws it. Every field is optional; see header. */
49
- export interface WorldRendererConfig {
50
- readonly toneMapping?: WorldToneMapping | undefined;
51
- readonly toneMappingExposure?: number | undefined;
52
- readonly outputColorSpace?: WorldOutputColorSpace | undefined;
53
- /**
54
- * `WebGLRenderer.shadowMap.type`. Writing it after a shadow map has already been built needs
55
- * `shadowMap.needsUpdate`, which this function sets — three caches the compiled depth material
56
- * per type and would otherwise keep filtering with the previous one.
57
- */
58
- readonly shadowMapType?: WorldShadowMapType | undefined;
59
- /**
60
- * The colour the frame is cleared to, as a CSS hex string. The renderer's existing clear ALPHA
61
- * is preserved: a stacked canvas is transparent on purpose (`create-runtime.ts` gives every
62
- * non-bottom root `alpha: true`), and forcing it opaque here would hide every layer below.
63
- */
64
- readonly clearColor?: string | undefined;
65
- }
66
- /**
67
- * Apply `config` to `renderer`, returning the restore function that puts back what was there.
68
- *
69
- * `three` is passed in rather than imported for values so the enum constants come from the HOST's
70
- * three instance — the same identity rule `r3f-adapter.tsx` follows for the scene and camera.
4
+ * The engine package's export map is the wildcard `"./*"`, so every file under
5
+ * `packages/engine/src/` is an entry point a game outside this repo can import by path. The module
6
+ * itself now lives in the adapter seam (`@engine/adapter/renderer-config`, which its header
7
+ * explains), and this file keeps the path that shipped resolving to it. Import the seam path in
8
+ * new code.
71
9
  */
72
- export declare function applyWorldRendererConfig(three: typeof THREE, renderer: THREE.WebGLRenderer, config: WorldRendererConfig): () => void;
10
+ export * from '../adapter/renderer-config';
73
11
  //# sourceMappingURL=renderer-config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"renderer-config.d.ts","sourceRoot":"","sources":["../../src/world3d-react/renderer-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAEpC,mGAAmG;AACnG,MAAM,MAAM,gBAAgB,GACxB,MAAM,GACN,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,MAAM,GACN,KAAK,GACL,SAAS,CAAC;AAEd;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,aAAa,CAAC;AAE3D;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,KAAK,GAAG,UAAU,GAAG,KAAK,CAAC;AAEtE,sGAAsG;AACtG,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IAC9D;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IACxD;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,KAAK,EACnB,QAAQ,EAAE,KAAK,CAAC,aAAa,EAC7B,MAAM,EAAE,mBAAmB,GAC1B,MAAM,IAAI,CA+EZ"}
1
+ {"version":3,"file":"renderer-config.d.ts","sourceRoot":"","sources":["../../src/world3d-react/renderer-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,cAAc,4BAA4B,CAAC"}
@@ -1,111 +1,10 @@
1
1
  /**
2
- * world3d-react/renderer-config.tsthe seam a WORLD uses to state how its own frame is rendered.
2
+ * `@engine/world3d-react/renderer-config` — a published entry point.
3
3
  *
4
- * The host owns the `WebGLRenderer` (`ThreeHostContext.renderer`) and configures it with this
5
- * engine's defaults: ACES tone mapping, sRGB output, PCF-soft shadows. Those defaults are right for
6
- * a world authored against them and WRONG for a world that was authored against a different
7
- * engine's pipeline an imported Godot 3 GLES2 game does gamma-space lighting with no tonemapper
8
- * at all, so ACES quietly desaturates and darkens every colour its author picked.
9
- *
10
- * So a world may DECLARE the pipeline it was authored for, and `createR3FAdapter` applies it to the
11
- * host's renderer for the life of the mount, restoring what it found on dispose. Three properties
12
- * of that shape are load-bearing:
13
- *
14
- * - **It is per-renderer, never engine-wide.** Every field here is a `WebGLRenderer` instance
15
- * property, and a play root gets its own renderer (`create-runtime.ts`). Nothing here reaches a
16
- * module-level three global, so one world's declaration cannot change how the editor's own
17
- * viewport, another root, or a thumbnail bake renders.
18
- * - **Absent means "leave the host's value alone".** Every field is optional and an omitted one
19
- * is never written, so declaring a tone mapping does not silently reset the clear colour.
20
- * - **It is restored on dispose.** The renderer outlives the mount, so a world that did not put
21
- * back what it found would leak its pipeline into whatever mounts next.
22
- *
23
- * This is deliberately NOT a general render-settings system. It carries what a world can honestly
24
- * state about its own colour pipeline and nothing else; a property the host fixes at CONSTRUCTION
25
- * (the WebGL context's `antialias` attribute, and therefore the MSAA sample count) cannot be
26
- * declared here, because there would be no honest moment to apply it.
27
- */
28
- /**
29
- * Apply `config` to `renderer`, returning the restore function that puts back what was there.
30
- *
31
- * `three` is passed in rather than imported for values so the enum constants come from the HOST's
32
- * three instance — the same identity rule `r3f-adapter.tsx` follows for the scene and camera.
4
+ * The engine package's export map is the wildcard `"./*"`, so every file under
5
+ * `packages/engine/src/` is an entry point a game outside this repo can import by path. The module
6
+ * itself now lives in the adapter seam (`@engine/adapter/renderer-config`, which its header
7
+ * explains), and this file keeps the path that shipped resolving to it. Import the seam path in
8
+ * new code.
33
9
  */
34
- export function applyWorldRendererConfig(three, renderer, config) {
35
- // A host that mounts a world WITHOUT rasterizing it hands the adapter a duck-typed renderer —
36
- // the editor's design session (`createDesignTimeRenderer`: four members, deliberately never
37
- // widened) and jsdom test harnesses both do. Such a surface has no colour pipeline to configure:
38
- // the frame the user sees is drawn by a DIFFERENT renderer (the editor's own), so applying the
39
- // world's config there is meaningless — and calling `getClearColor` on it is a TypeError that
40
- // unmounts the whole world at edit time (measured: every Godot port's edit viewport blanked with
41
- // '"world" failed to mount — renderer.getClearColor is not a function'). Detect the real
42
- // `WebGLRenderer` surface by the one method this function must call, and no-op otherwise.
43
- if (typeof renderer.getClearColor !== 'function') {
44
- return () => { };
45
- }
46
- const toneMappings = {
47
- none: three.NoToneMapping,
48
- linear: three.LinearToneMapping,
49
- reinhard: three.ReinhardToneMapping,
50
- cineon: three.CineonToneMapping,
51
- aces: three.ACESFilmicToneMapping,
52
- agx: three.AgXToneMapping,
53
- neutral: three.NeutralToneMapping,
54
- };
55
- const colorSpaces = {
56
- srgb: three.SRGBColorSpace,
57
- 'srgb-linear': three.LinearSRGBColorSpace,
58
- };
59
- const shadowMapTypes = {
60
- basic: three.BasicShadowMap,
61
- pcf: three.PCFShadowMap,
62
- 'pcf-soft': three.PCFSoftShadowMap,
63
- vsm: three.VSMShadowMap,
64
- };
65
- const restores = [];
66
- if (config.toneMapping !== undefined) {
67
- const previous = renderer.toneMapping;
68
- renderer.toneMapping = toneMappings[config.toneMapping];
69
- restores.push(() => {
70
- renderer.toneMapping = previous;
71
- });
72
- }
73
- if (config.toneMappingExposure !== undefined) {
74
- const previous = renderer.toneMappingExposure;
75
- renderer.toneMappingExposure = config.toneMappingExposure;
76
- restores.push(() => {
77
- renderer.toneMappingExposure = previous;
78
- });
79
- }
80
- if (config.outputColorSpace !== undefined) {
81
- const previous = renderer.outputColorSpace;
82
- renderer.outputColorSpace = colorSpaces[config.outputColorSpace];
83
- restores.push(() => {
84
- renderer.outputColorSpace = previous;
85
- });
86
- }
87
- if (config.shadowMapType !== undefined && renderer.shadowMap !== undefined) {
88
- const previous = renderer.shadowMap.type;
89
- renderer.shadowMap.type = shadowMapTypes[config.shadowMapType];
90
- renderer.shadowMap.needsUpdate = true;
91
- restores.push(() => {
92
- renderer.shadowMap.type = previous;
93
- renderer.shadowMap.needsUpdate = true;
94
- });
95
- }
96
- if (config.clearColor !== undefined) {
97
- const previousColor = new three.Color();
98
- renderer.getClearColor(previousColor);
99
- // Alpha is READ BACK and re-passed, never assumed: see `clearColor`'s doc above.
100
- const alpha = renderer.getClearAlpha();
101
- renderer.setClearColor(new three.Color(config.clearColor), alpha);
102
- restores.push(() => {
103
- renderer.setClearColor(previousColor, alpha);
104
- });
105
- }
106
- return () => {
107
- // Reverse order, so a field written twice (it cannot be, today) unwinds correctly.
108
- for (let i = restores.length - 1; i >= 0; i--)
109
- restores[i]?.();
110
- };
111
- }
10
+ export * from '../adapter/renderer-config';
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@vgai/engine",
3
3
  "author": "Volter AI, Inc.",
4
4
  "license": "Apache-2.0",
5
- "version": "0.5.17",
5
+ "version": "0.5.18",
6
6
  "description": "Readable TypeScript game engine and universal host for Three.js, PixiJS, and React games.",
7
7
  "keywords": [
8
8
  "game-engine",
@@ -73,7 +73,7 @@
73
73
  "notes": "Limits: ARCHITECTURE-CORE.md. Any editor capability a mount does not reach stands warned per capability until it works.",
74
74
  "evidenceHashes": {
75
75
  "vendor/games/verify-unaltered.mjs": "257a362030a10f4d72a3280815069fbd83f436e1619391635b6f8df2ddd6c339",
76
- "packages/engine/src/manifest/schema.ts": "2847dc09a55de1d9c0750872773b355c74ee33cf1542d1b082242f346c42cbd0"
76
+ "packages/engine/src/manifest/schema.ts": "ce385bc9181b82f9d5044da6a41a025a5cb8ba1fc7156263665080c9fdf2b11c"
77
77
  }
78
78
  },
79
79
  {
@@ -331,7 +331,7 @@
331
331
  "properties": {
332
332
  "antialias": {
333
333
  "type": "boolean",
334
- "description": "Whether every three root is built with a multisampled drawing buffer. This is the ONE render property a world cannot declare for itself (world3d-react/renderer-config.ts): a WebGL context fixes its sample count at CREATION from this boolean, long before a world mounts, so it belongs to the PROJECT. The runtime reader is mount-manifest.ts, which threads it into createHostRenderer for each three root. WebGL exposes no sample COUNT — the implementation picks one (4x on every desktop browser measured), so a source engine that authored 8x/16x gets multisampling but not its exact count."
334
+ "description": "Whether every three root is built with a multisampled drawing buffer. This is the ONE render property a world cannot declare for itself (adapter/renderer-config.ts): a WebGL context fixes its sample count at CREATION from this boolean, long before a world mounts, so it belongs to the PROJECT. The runtime reader is mount-manifest.ts, which threads it into createHostRenderer for each three root. WebGL exposes no sample COUNT — the implementation picks one (4x on every desktop browser measured), so a source engine that authored 8x/16x gets multisampling but not its exact count."
335
335
  }
336
336
  },
337
337
  "required": [
@@ -0,0 +1,178 @@
1
+ /**
2
+ * adapter/renderer-config.ts — the seam a WORLD uses to state how its own frame is rendered.
3
+ *
4
+ * It lives in the ADAPTER SEAM, not in a surface entry, because `WorldRendererConfig` IS part of
5
+ * the root contract: `MountedThreeRoot.rendererConfig` reports it (`./root-adapter.ts`) and
6
+ * `./root-seam-contract.ts` already lists it as a contract field. The seam defines the shape; the
7
+ * surface entries and the editor CONFORM to it — `world3d-react/r3f-adapter.tsx` is the declarer,
8
+ * `runtime/create-runtime.ts` the host, `editor/src/components/ViewportPanel.tsx` the editor's
9
+ * applier. Do NOT move it back under `world3d-react/`: nothing here touches react, and a core
10
+ * adapter file reaching into a react entry is exactly what `react-core-import-ban.test.ts` forbids
11
+ * (that ban carries no type-only carve-out on purpose — unlike its pixi sibling, whose carve-out
12
+ * exists only because `PIXI.Container` is a third-party type core cannot relocate).
13
+ *
14
+ * The host owns the `WebGLRenderer` (`ThreeHostContext.renderer`) and configures it with this
15
+ * engine's defaults: ACES tone mapping, sRGB output, PCF-soft shadows. Those defaults are right for
16
+ * a world authored against them and WRONG for a world that was authored against a different
17
+ * engine's pipeline — an imported Godot 3 GLES2 game does gamma-space lighting with no tonemapper
18
+ * at all, so ACES quietly desaturates and darkens every colour its author picked.
19
+ *
20
+ * So a world may DECLARE the pipeline it was authored for, and `createR3FAdapter` applies it to the
21
+ * host's renderer for the life of the mount, restoring what it found on dispose. Three properties
22
+ * of that shape are load-bearing:
23
+ *
24
+ * - **It is per-renderer, never engine-wide.** Every field here is a `WebGLRenderer` instance
25
+ * property, and a play root gets its own renderer (`create-runtime.ts`). Nothing here reaches a
26
+ * module-level three global, so one world's declaration cannot change how the editor's own
27
+ * viewport, another root, or a thumbnail bake renders.
28
+ * - **Absent means "leave the host's value alone".** Every field is optional and an omitted one
29
+ * is never written, so declaring a tone mapping does not silently reset the clear colour.
30
+ * - **It is restored on dispose.** The renderer outlives the mount, so a world that did not put
31
+ * back what it found would leak its pipeline into whatever mounts next.
32
+ *
33
+ * This is deliberately NOT a general render-settings system. It carries what a world can honestly
34
+ * state about its own colour pipeline and nothing else; a property the host fixes at CONSTRUCTION
35
+ * (the WebGL context's `antialias` attribute, and therefore the MSAA sample count) cannot be
36
+ * declared here, because there would be no honest moment to apply it.
37
+ */
38
+
39
+ import type * as THREE from 'three';
40
+
41
+ /** The tone-mapping operators three exposes, named as data rather than as three's numeric enum. */
42
+ export type WorldToneMapping =
43
+ | 'none'
44
+ | 'linear'
45
+ | 'reinhard'
46
+ | 'cineon'
47
+ | 'aces'
48
+ | 'agx'
49
+ | 'neutral';
50
+
51
+ /**
52
+ * The output transfer function the frame is written with.
53
+ *
54
+ * - `srgb` — three's own default and this engine's: linear lighting, sRGB encode on output.
55
+ * - `srgb-linear` — NO output transform. This is what a gamma-space renderer needs: the shading
56
+ * result is already in display space and encoding it a second time washes the frame out.
57
+ */
58
+ export type WorldOutputColorSpace = 'srgb' | 'srgb-linear';
59
+
60
+ /**
61
+ * The shadow-map filter, named as data rather than as three's numeric enum.
62
+ *
63
+ * This is a renderer INSTANCE property (`WebGLRenderer.shadowMap.type`), not a context attribute,
64
+ * so unlike MSAA it has an honest moment at which a world can ask for it — which is the whole test
65
+ * this file's header states. A source engine that declares its own shadow filter (Godot 3's
66
+ * `rendering/quality/shadows/filter_mode`) would otherwise inherit whatever the host built with.
67
+ */
68
+ export type WorldShadowMapType = 'basic' | 'pcf' | 'pcf-soft' | 'vsm';
69
+
70
+ /** What a world may declare about the renderer that draws it. Every field is optional; see header. */
71
+ export interface WorldRendererConfig {
72
+ readonly toneMapping?: WorldToneMapping | undefined;
73
+ readonly toneMappingExposure?: number | undefined;
74
+ readonly outputColorSpace?: WorldOutputColorSpace | undefined;
75
+ /**
76
+ * `WebGLRenderer.shadowMap.type`. Writing it after a shadow map has already been built needs
77
+ * `shadowMap.needsUpdate`, which this function sets — three caches the compiled depth material
78
+ * per type and would otherwise keep filtering with the previous one.
79
+ */
80
+ readonly shadowMapType?: WorldShadowMapType | undefined;
81
+ /**
82
+ * The colour the frame is cleared to, as a CSS hex string. The renderer's existing clear ALPHA
83
+ * is preserved: a stacked canvas is transparent on purpose (`create-runtime.ts` gives every
84
+ * non-bottom root `alpha: true`), and forcing it opaque here would hide every layer below.
85
+ */
86
+ readonly clearColor?: string | undefined;
87
+ }
88
+
89
+ /**
90
+ * Apply `config` to `renderer`, returning the restore function that puts back what was there.
91
+ *
92
+ * `three` is passed in rather than imported for values so the enum constants come from the HOST's
93
+ * three instance — the same identity rule `r3f-adapter.tsx` follows for the scene and camera.
94
+ */
95
+ export function applyWorldRendererConfig(
96
+ three: typeof THREE,
97
+ renderer: THREE.WebGLRenderer,
98
+ config: WorldRendererConfig,
99
+ ): () => void {
100
+ // A host that mounts a world WITHOUT rasterizing it hands the adapter a duck-typed renderer —
101
+ // the editor's design session (`createDesignTimeRenderer`: four members, deliberately never
102
+ // widened) and jsdom test harnesses both do. Such a surface has no colour pipeline to configure:
103
+ // the frame the user sees is drawn by a DIFFERENT renderer (the editor's own), so applying the
104
+ // world's config there is meaningless — and calling `getClearColor` on it is a TypeError that
105
+ // unmounts the whole world at edit time (measured: every Godot port's edit viewport blanked with
106
+ // '"world" failed to mount — renderer.getClearColor is not a function'). Detect the real
107
+ // `WebGLRenderer` surface by the one method this function must call, and no-op otherwise.
108
+ if (typeof renderer.getClearColor !== 'function') {
109
+ return () => {};
110
+ }
111
+ const toneMappings: Record<WorldToneMapping, THREE.ToneMapping> = {
112
+ none: three.NoToneMapping,
113
+ linear: three.LinearToneMapping,
114
+ reinhard: three.ReinhardToneMapping,
115
+ cineon: three.CineonToneMapping,
116
+ aces: three.ACESFilmicToneMapping,
117
+ agx: three.AgXToneMapping,
118
+ neutral: three.NeutralToneMapping,
119
+ };
120
+ const colorSpaces: Record<WorldOutputColorSpace, THREE.ColorSpace> = {
121
+ srgb: three.SRGBColorSpace,
122
+ 'srgb-linear': three.LinearSRGBColorSpace,
123
+ };
124
+ const shadowMapTypes: Record<WorldShadowMapType, THREE.ShadowMapType> = {
125
+ basic: three.BasicShadowMap,
126
+ pcf: three.PCFShadowMap,
127
+ 'pcf-soft': three.PCFSoftShadowMap,
128
+ vsm: three.VSMShadowMap,
129
+ };
130
+
131
+ const restores: (() => void)[] = [];
132
+
133
+ if (config.toneMapping !== undefined) {
134
+ const previous = renderer.toneMapping;
135
+ renderer.toneMapping = toneMappings[config.toneMapping];
136
+ restores.push(() => {
137
+ renderer.toneMapping = previous;
138
+ });
139
+ }
140
+ if (config.toneMappingExposure !== undefined) {
141
+ const previous = renderer.toneMappingExposure;
142
+ renderer.toneMappingExposure = config.toneMappingExposure;
143
+ restores.push(() => {
144
+ renderer.toneMappingExposure = previous;
145
+ });
146
+ }
147
+ if (config.outputColorSpace !== undefined) {
148
+ const previous = renderer.outputColorSpace;
149
+ renderer.outputColorSpace = colorSpaces[config.outputColorSpace];
150
+ restores.push(() => {
151
+ renderer.outputColorSpace = previous;
152
+ });
153
+ }
154
+ if (config.shadowMapType !== undefined && renderer.shadowMap !== undefined) {
155
+ const previous = renderer.shadowMap.type;
156
+ renderer.shadowMap.type = shadowMapTypes[config.shadowMapType];
157
+ renderer.shadowMap.needsUpdate = true;
158
+ restores.push(() => {
159
+ renderer.shadowMap.type = previous;
160
+ renderer.shadowMap.needsUpdate = true;
161
+ });
162
+ }
163
+ if (config.clearColor !== undefined) {
164
+ const previousColor = new three.Color();
165
+ renderer.getClearColor(previousColor);
166
+ // Alpha is READ BACK and re-passed, never assumed: see `clearColor`'s doc above.
167
+ const alpha = renderer.getClearAlpha();
168
+ renderer.setClearColor(new three.Color(config.clearColor), alpha);
169
+ restores.push(() => {
170
+ renderer.setClearColor(previousColor, alpha);
171
+ });
172
+ }
173
+
174
+ return () => {
175
+ // Reverse order, so a field written twice (it cannot be, today) unwinds correctly.
176
+ for (let i = restores.length - 1; i >= 0; i--) restores[i]?.();
177
+ };
178
+ }
@@ -9,10 +9,10 @@
9
9
 
10
10
  import type { Container } from 'pixi.js';
11
11
  import type * as THREE from 'three';
12
- import type { WorldRendererConfig } from '../world3d-react/renderer-config';
13
12
  import type { AdapterSurface } from './adapter-surface';
14
13
  import type { AuthoringAdapter } from './authoring';
15
14
  import type { HostContextFor } from './host-context';
15
+ import type { WorldRendererConfig } from './renderer-config';
16
16
  import type { SystemAdapters } from './system-adapter';
17
17
 
18
18
  /**
@@ -98,7 +98,7 @@ export interface MountedThreeRoot extends MountedRootBase {
98
98
  /**
99
99
  * The colour pipeline this world was authored for, REPORTED rather than
100
100
  * applied — the adapter has already applied it to the renderer its own host
101
- * handed it (`world3d-react/renderer-config.ts`).
101
+ * handed it (`./renderer-config.ts`).
102
102
  *
103
103
  * It is here because the host that MOUNTS a world is not always the host
104
104
  * that DRAWS it. The editor's design session mounts against a
@@ -573,7 +573,7 @@ export const GameManifestSchema = z
573
573
  .boolean()
574
574
  .describe(
575
575
  'Whether every three root is built with a multisampled drawing buffer. This is the ' +
576
- 'ONE render property a world cannot declare for itself (world3d-react/' +
576
+ 'ONE render property a world cannot declare for itself (adapter/' +
577
577
  'renderer-config.ts): a WebGL context fixes its sample count at CREATION from this ' +
578
578
  'boolean, long before a world mounts, so it belongs to the PROJECT. The runtime ' +
579
579
  'reader is mount-manifest.ts, which threads it into createHostRenderer for each ' +
@@ -412,7 +412,7 @@ export async function mountManifestRoots(opts: MountManifestOptions): Promise<Ga
412
412
  headless: opts.headless,
413
413
  // `rendering.antialias` reaches the WebGL context at CONSTRUCTION and can be honoured
414
414
  // nowhere else — see the manifest schema's own `rendering` block and
415
- // `world3d-react/renderer-config.ts`'s header for why it is not a world-level declaration.
415
+ // `adapter/renderer-config.ts`'s header for why it is not a world-level declaration.
416
416
  ...(manifest.rendering === undefined ? {} : { antialias: manifest.rendering.antialias }),
417
417
  seed: resolvedSeed,
418
418
  playtest: opts.playtest,
@@ -40,15 +40,15 @@
40
40
  * contract to `react`/`@react-three/fiber`/`three` alone.
41
41
  */
42
42
 
43
- export { EngineBridge, type EngineBridgeValue, useGameContext } from './engine-bridge';
44
- export { type CreateR3FAdapterOptions, createR3FAdapter } from './r3f-adapter';
45
- export { r3fRootFactory, resolveR3FEntryAdapter } from './r3f-root-factory';
46
43
  export {
47
44
  applyWorldRendererConfig,
48
45
  type WorldOutputColorSpace,
49
46
  type WorldRendererConfig,
50
47
  type WorldToneMapping,
51
- } from './renderer-config';
48
+ } from '../adapter/renderer-config';
49
+ export { EngineBridge, type EngineBridgeValue, useGameContext } from './engine-bridge';
50
+ export { type CreateR3FAdapterOptions, createR3FAdapter } from './r3f-adapter';
51
+ export { r3fRootFactory, resolveR3FEntryAdapter } from './r3f-root-factory';
52
52
  export {
53
53
  createR3FRootContext,
54
54
  DEFAULT_INPUT_MAP_PATH,
@@ -30,6 +30,7 @@ import {
30
30
  } from '@react-three/fiber';
31
31
  import { createElement, Fragment, type ReactNode, useEffect, useMemo } from 'react';
32
32
  import type { MountedThreeRoot, RootAdapter, ThreeHostContext } from '../adapter';
33
+ import { applyWorldRendererConfig, type WorldRendererConfig } from '../adapter/renderer-config';
33
34
  import type { SystemAdapters } from '../adapter/system-adapter';
34
35
  import { type RenderVitalsRegistration, registerRenderVitals } from '../dev/register-render-vitals';
35
36
  import {
@@ -45,7 +46,6 @@ import { createSoftParticleDepthPass } from '../render/soft-particle-depth';
45
46
  import { getDebugRegistry } from '../runtime/debug-registry';
46
47
  import { devBuildEnabled } from '../runtime/dev-build';
47
48
  import { EngineBridge, type EngineBridgeValue } from './engine-bridge';
48
- import { applyWorldRendererConfig, type WorldRendererConfig } from './renderer-config';
49
49
  import { createR3FRootContext, DEFAULT_INPUT_MAP_PATH, wireGameInputSeams } from './world-context';
50
50
 
51
51
  /** The slice of `WebGLRenderer.info` the vitals reporter reads. Declared
@@ -117,7 +117,7 @@ export interface CreateR3FAdapterOptions {
117
117
  /** The colour pipeline this world was AUTHORED for, applied to the host's renderer for the life
118
118
  * of the mount and restored on dispose. Omit it (every world here does) to keep the host's own
119
119
  * defaults; declare it when the world's colours were picked against a different engine's
120
- * pipeline — see `./renderer-config.ts`. */
120
+ * pipeline — see `../adapter/renderer-config.ts`. */
121
121
  readonly renderer?: WorldRendererConfig | undefined;
122
122
  }
123
123
 
@@ -1,168 +1,11 @@
1
1
  /**
2
- * world3d-react/renderer-config.tsthe seam a WORLD uses to state how its own frame is rendered.
2
+ * `@engine/world3d-react/renderer-config` — a published entry point.
3
3
  *
4
- * The host owns the `WebGLRenderer` (`ThreeHostContext.renderer`) and configures it with this
5
- * engine's defaults: ACES tone mapping, sRGB output, PCF-soft shadows. Those defaults are right for
6
- * a world authored against them and WRONG for a world that was authored against a different
7
- * engine's pipeline an imported Godot 3 GLES2 game does gamma-space lighting with no tonemapper
8
- * at all, so ACES quietly desaturates and darkens every colour its author picked.
9
- *
10
- * So a world may DECLARE the pipeline it was authored for, and `createR3FAdapter` applies it to the
11
- * host's renderer for the life of the mount, restoring what it found on dispose. Three properties
12
- * of that shape are load-bearing:
13
- *
14
- * - **It is per-renderer, never engine-wide.** Every field here is a `WebGLRenderer` instance
15
- * property, and a play root gets its own renderer (`create-runtime.ts`). Nothing here reaches a
16
- * module-level three global, so one world's declaration cannot change how the editor's own
17
- * viewport, another root, or a thumbnail bake renders.
18
- * - **Absent means "leave the host's value alone".** Every field is optional and an omitted one
19
- * is never written, so declaring a tone mapping does not silently reset the clear colour.
20
- * - **It is restored on dispose.** The renderer outlives the mount, so a world that did not put
21
- * back what it found would leak its pipeline into whatever mounts next.
22
- *
23
- * This is deliberately NOT a general render-settings system. It carries what a world can honestly
24
- * state about its own colour pipeline and nothing else; a property the host fixes at CONSTRUCTION
25
- * (the WebGL context's `antialias` attribute, and therefore the MSAA sample count) cannot be
26
- * declared here, because there would be no honest moment to apply it.
27
- */
28
-
29
- import type * as THREE from 'three';
30
-
31
- /** The tone-mapping operators three exposes, named as data rather than as three's numeric enum. */
32
- export type WorldToneMapping =
33
- | 'none'
34
- | 'linear'
35
- | 'reinhard'
36
- | 'cineon'
37
- | 'aces'
38
- | 'agx'
39
- | 'neutral';
40
-
41
- /**
42
- * The output transfer function the frame is written with.
43
- *
44
- * - `srgb` — three's own default and this engine's: linear lighting, sRGB encode on output.
45
- * - `srgb-linear` — NO output transform. This is what a gamma-space renderer needs: the shading
46
- * result is already in display space and encoding it a second time washes the frame out.
47
- */
48
- export type WorldOutputColorSpace = 'srgb' | 'srgb-linear';
49
-
50
- /**
51
- * The shadow-map filter, named as data rather than as three's numeric enum.
52
- *
53
- * This is a renderer INSTANCE property (`WebGLRenderer.shadowMap.type`), not a context attribute,
54
- * so unlike MSAA it has an honest moment at which a world can ask for it — which is the whole test
55
- * this file's header states. A source engine that declares its own shadow filter (Godot 3's
56
- * `rendering/quality/shadows/filter_mode`) would otherwise inherit whatever the host built with.
57
- */
58
- export type WorldShadowMapType = 'basic' | 'pcf' | 'pcf-soft' | 'vsm';
59
-
60
- /** What a world may declare about the renderer that draws it. Every field is optional; see header. */
61
- export interface WorldRendererConfig {
62
- readonly toneMapping?: WorldToneMapping | undefined;
63
- readonly toneMappingExposure?: number | undefined;
64
- readonly outputColorSpace?: WorldOutputColorSpace | undefined;
65
- /**
66
- * `WebGLRenderer.shadowMap.type`. Writing it after a shadow map has already been built needs
67
- * `shadowMap.needsUpdate`, which this function sets — three caches the compiled depth material
68
- * per type and would otherwise keep filtering with the previous one.
69
- */
70
- readonly shadowMapType?: WorldShadowMapType | undefined;
71
- /**
72
- * The colour the frame is cleared to, as a CSS hex string. The renderer's existing clear ALPHA
73
- * is preserved: a stacked canvas is transparent on purpose (`create-runtime.ts` gives every
74
- * non-bottom root `alpha: true`), and forcing it opaque here would hide every layer below.
75
- */
76
- readonly clearColor?: string | undefined;
77
- }
78
-
79
- /**
80
- * Apply `config` to `renderer`, returning the restore function that puts back what was there.
81
- *
82
- * `three` is passed in rather than imported for values so the enum constants come from the HOST's
83
- * three instance — the same identity rule `r3f-adapter.tsx` follows for the scene and camera.
4
+ * The engine package's export map is the wildcard `"./*"`, so every file under
5
+ * `packages/engine/src/` is an entry point a game outside this repo can import by path. The module
6
+ * itself now lives in the adapter seam (`@engine/adapter/renderer-config`, which its header
7
+ * explains), and this file keeps the path that shipped resolving to it. Import the seam path in
8
+ * new code.
84
9
  */
85
- export function applyWorldRendererConfig(
86
- three: typeof THREE,
87
- renderer: THREE.WebGLRenderer,
88
- config: WorldRendererConfig,
89
- ): () => void {
90
- // A host that mounts a world WITHOUT rasterizing it hands the adapter a duck-typed renderer —
91
- // the editor's design session (`createDesignTimeRenderer`: four members, deliberately never
92
- // widened) and jsdom test harnesses both do. Such a surface has no colour pipeline to configure:
93
- // the frame the user sees is drawn by a DIFFERENT renderer (the editor's own), so applying the
94
- // world's config there is meaningless — and calling `getClearColor` on it is a TypeError that
95
- // unmounts the whole world at edit time (measured: every Godot port's edit viewport blanked with
96
- // '"world" failed to mount — renderer.getClearColor is not a function'). Detect the real
97
- // `WebGLRenderer` surface by the one method this function must call, and no-op otherwise.
98
- if (typeof renderer.getClearColor !== 'function') {
99
- return () => {};
100
- }
101
- const toneMappings: Record<WorldToneMapping, THREE.ToneMapping> = {
102
- none: three.NoToneMapping,
103
- linear: three.LinearToneMapping,
104
- reinhard: three.ReinhardToneMapping,
105
- cineon: three.CineonToneMapping,
106
- aces: three.ACESFilmicToneMapping,
107
- agx: three.AgXToneMapping,
108
- neutral: three.NeutralToneMapping,
109
- };
110
- const colorSpaces: Record<WorldOutputColorSpace, THREE.ColorSpace> = {
111
- srgb: three.SRGBColorSpace,
112
- 'srgb-linear': three.LinearSRGBColorSpace,
113
- };
114
- const shadowMapTypes: Record<WorldShadowMapType, THREE.ShadowMapType> = {
115
- basic: three.BasicShadowMap,
116
- pcf: three.PCFShadowMap,
117
- 'pcf-soft': three.PCFSoftShadowMap,
118
- vsm: three.VSMShadowMap,
119
- };
120
-
121
- const restores: (() => void)[] = [];
122
-
123
- if (config.toneMapping !== undefined) {
124
- const previous = renderer.toneMapping;
125
- renderer.toneMapping = toneMappings[config.toneMapping];
126
- restores.push(() => {
127
- renderer.toneMapping = previous;
128
- });
129
- }
130
- if (config.toneMappingExposure !== undefined) {
131
- const previous = renderer.toneMappingExposure;
132
- renderer.toneMappingExposure = config.toneMappingExposure;
133
- restores.push(() => {
134
- renderer.toneMappingExposure = previous;
135
- });
136
- }
137
- if (config.outputColorSpace !== undefined) {
138
- const previous = renderer.outputColorSpace;
139
- renderer.outputColorSpace = colorSpaces[config.outputColorSpace];
140
- restores.push(() => {
141
- renderer.outputColorSpace = previous;
142
- });
143
- }
144
- if (config.shadowMapType !== undefined && renderer.shadowMap !== undefined) {
145
- const previous = renderer.shadowMap.type;
146
- renderer.shadowMap.type = shadowMapTypes[config.shadowMapType];
147
- renderer.shadowMap.needsUpdate = true;
148
- restores.push(() => {
149
- renderer.shadowMap.type = previous;
150
- renderer.shadowMap.needsUpdate = true;
151
- });
152
- }
153
- if (config.clearColor !== undefined) {
154
- const previousColor = new three.Color();
155
- renderer.getClearColor(previousColor);
156
- // Alpha is READ BACK and re-passed, never assumed: see `clearColor`'s doc above.
157
- const alpha = renderer.getClearAlpha();
158
- renderer.setClearColor(new three.Color(config.clearColor), alpha);
159
- restores.push(() => {
160
- renderer.setClearColor(previousColor, alpha);
161
- });
162
- }
163
10
 
164
- return () => {
165
- // Reverse order, so a field written twice (it cannot be, today) unwinds correctly.
166
- for (let i = restores.length - 1; i >= 0; i--) restores[i]?.();
167
- };
168
- }
11
+ export * from '../adapter/renderer-config';