@waica/engine 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/README.md +79 -1
  2. package/dist/anchored-pieces.d.ts +3 -2
  3. package/dist/anchored-pieces.js +12 -8
  4. package/dist/archetype.d.ts +2 -2
  5. package/dist/assets/asset-loader.d.ts +28 -5
  6. package/dist/assets/asset-loader.js +63 -7
  7. package/dist/assets/model-backend.d.ts +21 -0
  8. package/dist/assets/model-backend.js +21 -0
  9. package/dist/assets/model-cache.d.ts +47 -0
  10. package/dist/assets/model-cache.js +117 -0
  11. package/dist/audio/audio-subsystem.d.ts +6 -1
  12. package/dist/audio/audio-subsystem.js +8 -15
  13. package/dist/audio/placement.d.ts +41 -0
  14. package/dist/audio/placement.js +21 -0
  15. package/dist/audio/spatial.d.ts +20 -0
  16. package/dist/audio/spatial.js +26 -0
  17. package/dist/audio/types.d.ts +7 -1
  18. package/dist/camera-projection.d.ts +38 -0
  19. package/dist/camera-projection.js +44 -0
  20. package/dist/camera-rig.d.ts +48 -0
  21. package/dist/camera-rig.js +98 -0
  22. package/dist/camera.d.ts +20 -3
  23. package/dist/camera.js +3 -1
  24. package/dist/component.d.ts +38 -2
  25. package/dist/component.js +3 -0
  26. package/dist/components/animated-sprite.d.ts +8 -1
  27. package/dist/components/animated-sprite.js +16 -1
  28. package/dist/components/collider.d.ts +89 -0
  29. package/dist/components/collider.js +57 -0
  30. package/dist/components/dynamic-body.d.ts +2 -1
  31. package/dist/components/dynamic-body.js +1 -0
  32. package/dist/components/hitbox.d.ts +2 -1
  33. package/dist/components/hitbox.js +1 -0
  34. package/dist/components/light.d.ts +69 -0
  35. package/dist/components/light.js +60 -0
  36. package/dist/components/model.d.ts +63 -0
  37. package/dist/components/model.js +117 -0
  38. package/dist/components/particle-emitter.d.ts +8 -1
  39. package/dist/components/particle-emitter.js +14 -0
  40. package/dist/components/point-light.d.ts +75 -0
  41. package/dist/components/point-light.js +102 -0
  42. package/dist/components/rigid-body.d.ts +114 -0
  43. package/dist/components/rigid-body.js +83 -0
  44. package/dist/components/solid.d.ts +2 -1
  45. package/dist/components/solid.js +1 -0
  46. package/dist/components/sprite.d.ts +8 -1
  47. package/dist/components/sprite.js +16 -1
  48. package/dist/components/sun.d.ts +46 -0
  49. package/dist/components/sun.js +74 -0
  50. package/dist/components/tilemap.d.ts +2 -1
  51. package/dist/components/tilemap.js +7 -0
  52. package/dist/entity-transform.d.ts +18 -0
  53. package/dist/entity-transform.js +17 -0
  54. package/dist/frame-quad.d.ts +21 -0
  55. package/dist/frame-quad.js +50 -0
  56. package/dist/game.d.ts +59 -15
  57. package/dist/game.js +100 -75
  58. package/dist/index.d.ts +39 -4
  59. package/dist/index.js +18 -1
  60. package/dist/light-field.d.ts +78 -0
  61. package/dist/light-field.js +145 -0
  62. package/dist/light-map-material.d.ts +40 -0
  63. package/dist/light-map-material.js +203 -0
  64. package/dist/light-map.d.ts +46 -0
  65. package/dist/light-map.js +132 -0
  66. package/dist/lit-frame.d.ts +48 -0
  67. package/dist/lit-frame.js +174 -0
  68. package/dist/occluder-grid.d.ts +19 -0
  69. package/dist/occluder-grid.js +50 -0
  70. package/dist/particle-batch.js +3 -2
  71. package/dist/physics-3d/body-contacts.d.ts +10 -0
  72. package/dist/physics-3d/body-contacts.js +59 -0
  73. package/dist/physics-3d/body-sync.d.ts +52 -0
  74. package/dist/physics-3d/body-sync.js +153 -0
  75. package/dist/physics-3d/character-controller.d.ts +44 -0
  76. package/dist/physics-3d/character-controller.js +126 -0
  77. package/dist/physics-3d/physics-host.d.ts +86 -0
  78. package/dist/physics-3d/physics-host.js +174 -0
  79. package/dist/physics-3d/physics-world.d.ts +65 -0
  80. package/dist/physics-3d/physics-world.js +158 -0
  81. package/dist/physics-3d/rapier-module.d.ts +17 -0
  82. package/dist/physics-3d/rapier-module.js +19 -0
  83. package/dist/physics-3d/sensor-dispatch.d.ts +10 -0
  84. package/dist/physics-3d/sensor-dispatch.js +77 -0
  85. package/dist/physics-3d/world-queries.d.ts +39 -0
  86. package/dist/physics-3d/world-queries.js +63 -0
  87. package/dist/pointer-raycast.d.ts +17 -0
  88. package/dist/pointer-raycast.js +48 -0
  89. package/dist/pointer.d.ts +27 -6
  90. package/dist/pointer.js +43 -17
  91. package/dist/post-effects.d.ts +33 -0
  92. package/dist/post-effects.js +48 -0
  93. package/dist/post-pass.d.ts +23 -0
  94. package/dist/post-pass.js +93 -0
  95. package/dist/render-layers.d.ts +18 -0
  96. package/dist/render-layers.js +19 -0
  97. package/dist/render-output.d.ts +7 -0
  98. package/dist/render-output.js +17 -1
  99. package/dist/runtime-bridge.d.ts +1 -1
  100. package/dist/runtime-inspection.d.ts +14 -0
  101. package/dist/runtime-inspection.js +12 -2
  102. package/dist/runtime-lighting-snapshot.d.ts +67 -0
  103. package/dist/runtime-lighting-snapshot.js +77 -0
  104. package/dist/runtime-physics-snapshot.d.ts +41 -0
  105. package/dist/runtime-physics-snapshot.js +45 -0
  106. package/dist/runtime-view-snapshot.d.ts +25 -0
  107. package/dist/runtime-view-snapshot.js +30 -0
  108. package/dist/scene-camera-3d.d.ts +66 -0
  109. package/dist/scene-camera-3d.js +117 -0
  110. package/dist/scene-lighting.d.ts +52 -0
  111. package/dist/scene-lighting.js +107 -0
  112. package/dist/scene-lights-3d.d.ts +17 -0
  113. package/dist/scene-lights-3d.js +27 -0
  114. package/dist/scene-render-options.d.ts +51 -0
  115. package/dist/scene-render-options.js +126 -0
  116. package/dist/scene-simulation.d.ts +22 -0
  117. package/dist/scene-simulation.js +29 -0
  118. package/dist/scene-space.d.ts +50 -0
  119. package/dist/scene-space.js +103 -0
  120. package/dist/scene.d.ts +41 -2
  121. package/dist/scene.js +11 -4
  122. package/dist/simulation-phases.d.ts +9 -0
  123. package/dist/simulation-phases.js +19 -0
  124. package/dist/spatial-query-3d.d.ts +48 -0
  125. package/dist/spatial-query-3d.js +96 -0
  126. package/dist/spatial-query.d.ts +18 -4
  127. package/dist/spatial-query.js +57 -1
  128. package/dist/sprite-batch.js +5 -1
  129. package/package.json +2 -1
@@ -0,0 +1,75 @@
1
+ import * as THREE from 'three/webgpu';
2
+ import { Component, type ComponentSpace } from '../component.js';
3
+ /**
4
+ * A point light in a 3D scene (ADR 0027, `CONTEXT.md` Point Light): it shines
5
+ * in every direction from the entity's position plus an offset (in the
6
+ * entity's local frame: it turns and scales with the entity, so a lantern's
7
+ * offset follows the object it hangs on), reaching
8
+ * `distance` world units (0 = without limit) and fading with `decay` (2 is
9
+ * physical). Not the 2D `Light`, which paints a light-map. No shadows. In a
10
+ * 2D scene it creates nothing; the three light lives under the entity's node
11
+ * and leaves with it.
12
+ */
13
+ export declare class PointLight extends Component {
14
+ static componentName: string;
15
+ static space: ComponentSpace;
16
+ static displayName: string;
17
+ static params: {
18
+ color: {
19
+ label: string;
20
+ kind: "color";
21
+ };
22
+ intensity: {
23
+ label: string;
24
+ min: number;
25
+ step: number;
26
+ };
27
+ distance: {
28
+ label: string;
29
+ min: number;
30
+ step: number;
31
+ };
32
+ decay: {
33
+ label: string;
34
+ min: number;
35
+ step: number;
36
+ };
37
+ offsetX: {
38
+ label: string;
39
+ };
40
+ offsetY: {
41
+ label: string;
42
+ };
43
+ offsetZ: {
44
+ label: string;
45
+ };
46
+ };
47
+ static transient: string[];
48
+ /** The three light, null in a 2D scene or before the component is ready. */
49
+ light: THREE.PointLight | null;
50
+ private _color;
51
+ private _intensity;
52
+ private _distance;
53
+ private _decay;
54
+ private _offsetX;
55
+ private _offsetY;
56
+ private _offsetZ;
57
+ get color(): number;
58
+ set color(value: number);
59
+ get intensity(): number;
60
+ set intensity(value: number);
61
+ /** World units the light reaches; 0 reaches without limit. */
62
+ get distance(): number;
63
+ set distance(value: number);
64
+ get decay(): number;
65
+ set decay(value: number);
66
+ get offsetX(): number;
67
+ set offsetX(value: number);
68
+ get offsetY(): number;
69
+ set offsetY(value: number);
70
+ get offsetZ(): number;
71
+ set offsetZ(value: number);
72
+ onReady(): void;
73
+ onDestroy(): void;
74
+ private place;
75
+ }
@@ -0,0 +1,102 @@
1
+ import * as THREE from 'three/webgpu';
2
+ import { Component } from '../component.js';
3
+ /**
4
+ * A point light in a 3D scene (ADR 0027, `CONTEXT.md` Point Light): it shines
5
+ * in every direction from the entity's position plus an offset (in the
6
+ * entity's local frame: it turns and scales with the entity, so a lantern's
7
+ * offset follows the object it hangs on), reaching
8
+ * `distance` world units (0 = without limit) and fading with `decay` (2 is
9
+ * physical). Not the 2D `Light`, which paints a light-map. No shadows. In a
10
+ * 2D scene it creates nothing; the three light lives under the entity's node
11
+ * and leaves with it.
12
+ */
13
+ export class PointLight extends Component {
14
+ static componentName = 'PointLight';
15
+ static space = '3d';
16
+ static displayName = 'Point light (3D)';
17
+ static params = {
18
+ color: { label: 'Color', kind: 'color' },
19
+ intensity: { label: 'Intensity', min: 0, step: 0.5 },
20
+ distance: { label: 'Distance (0 = unlimited)', min: 0, step: 0.5 },
21
+ decay: { label: 'Decay', min: 0, step: 0.1 },
22
+ offsetX: { label: 'x offset' },
23
+ offsetY: { label: 'y offset' },
24
+ offsetZ: { label: 'z offset' },
25
+ };
26
+ static transient = ['light'];
27
+ /** The three light, null in a 2D scene or before the component is ready. */
28
+ light = null;
29
+ _color = 0xffffff;
30
+ _intensity = 1;
31
+ _distance = 0;
32
+ _decay = 2;
33
+ _offsetX = 0;
34
+ _offsetY = 0;
35
+ _offsetZ = 0;
36
+ get color() {
37
+ return this._color;
38
+ }
39
+ set color(value) {
40
+ this._color = value;
41
+ this.light?.color.setHex(value);
42
+ }
43
+ get intensity() {
44
+ return this._intensity;
45
+ }
46
+ set intensity(value) {
47
+ this._intensity = value;
48
+ if (this.light)
49
+ this.light.intensity = value;
50
+ }
51
+ /** World units the light reaches; 0 reaches without limit. */
52
+ get distance() {
53
+ return this._distance;
54
+ }
55
+ set distance(value) {
56
+ this._distance = value;
57
+ if (this.light)
58
+ this.light.distance = value;
59
+ }
60
+ get decay() {
61
+ return this._decay;
62
+ }
63
+ set decay(value) {
64
+ this._decay = value;
65
+ if (this.light)
66
+ this.light.decay = value;
67
+ }
68
+ get offsetX() {
69
+ return this._offsetX;
70
+ }
71
+ set offsetX(value) {
72
+ this._offsetX = value;
73
+ this.place();
74
+ }
75
+ get offsetY() {
76
+ return this._offsetY;
77
+ }
78
+ set offsetY(value) {
79
+ this._offsetY = value;
80
+ this.place();
81
+ }
82
+ get offsetZ() {
83
+ return this._offsetZ;
84
+ }
85
+ set offsetZ(value) {
86
+ this._offsetZ = value;
87
+ this.place();
88
+ }
89
+ onReady() {
90
+ if (this.game.space !== '3d')
91
+ return;
92
+ this.light = new THREE.PointLight(this._color, this._intensity, this._distance, this._decay);
93
+ this.entity.node.add(this.light);
94
+ this.place();
95
+ }
96
+ onDestroy() {
97
+ this.light?.removeFromParent();
98
+ }
99
+ place() {
100
+ this.light?.position.set(this._offsetX, this._offsetY, this._offsetZ);
101
+ }
102
+ }
@@ -0,0 +1,114 @@
1
+ import { Component, type ComponentSpace } from '../component.js';
2
+ import type { Vec3Json } from '../scene-camera-3d.js';
3
+ export type RigidBodyType = 'dynamic' | 'kinematic';
4
+ export declare const RIGID_BODY_TYPES: readonly RigidBodyType[];
5
+ /** A 3D vector as the body's public API reads and writes it. */
6
+ export interface BodyVector {
7
+ x: number;
8
+ y: number;
9
+ z: number;
10
+ }
11
+ /**
12
+ * Makes the entity's `Collider` a body that moves (ADR 0028). A `dynamic` body
13
+ * is Rapier's: it falls under the scene's gravity, bounces, stacks and rests,
14
+ * and after every Simulation Step the engine writes the entity's position and
15
+ * rotation from it. A `kinematic` body is moved by code. It needs a `Collider`
16
+ * on the same entity (`validate_project` reports a body without one, and at
17
+ * runtime none is created). `mass` is the collider's mass; `velocity` is the
18
+ * initial linear velocity. A `kinematic` body is a character: every step the
19
+ * engine moves it through Rapier's character controller by its
20
+ * `desiredVelocity` on x and z and its own vertical velocity under gravity,
21
+ * stopping at walls, climbing steps up to `stepHeight` and slopes up to
22
+ * `maxSlope`, sticking to the ground within `snapDistance`, and pushing
23
+ * dynamic bodies it walks into. A param outside its range (`type` unknown,
24
+ * `mass` under the minimum, a `velocity` that is not three numbers) creates
25
+ * no body and warns once; `validate_project` reports it as
26
+ * `invalid-rigid-body-param`. A RigidBody in a 2D scene creates nothing.
27
+ */
28
+ export declare class RigidBody extends Component {
29
+ static componentName: string;
30
+ static space: ComponentSpace;
31
+ static params: {
32
+ type: {
33
+ label: string;
34
+ options: RigidBodyType[];
35
+ };
36
+ mass: {
37
+ label: string;
38
+ min: number;
39
+ step: number;
40
+ };
41
+ gravityScale: {
42
+ label: string;
43
+ step: number;
44
+ };
45
+ linearDamping: {
46
+ label: string;
47
+ min: number;
48
+ step: number;
49
+ };
50
+ angularDamping: {
51
+ label: string;
52
+ min: number;
53
+ step: number;
54
+ };
55
+ lockRotations: {
56
+ label: string;
57
+ };
58
+ velocity: {
59
+ label: string;
60
+ kind: "vector3";
61
+ };
62
+ stepHeight: {
63
+ label: string;
64
+ min: number;
65
+ step: number;
66
+ };
67
+ maxSlope: {
68
+ label: string;
69
+ min: number;
70
+ max: number;
71
+ step: number;
72
+ };
73
+ snapDistance: {
74
+ label: string;
75
+ min: number;
76
+ step: number;
77
+ };
78
+ };
79
+ static transient: string[];
80
+ type: RigidBodyType;
81
+ mass: number;
82
+ gravityScale: number;
83
+ linearDamping: number;
84
+ angularDamping: number;
85
+ lockRotations: boolean;
86
+ /** The initial linear velocity `[x, y, z]`, applied when the body is created. */
87
+ velocity: Vec3Json;
88
+ /** Kinematic only: the tallest step it climbs without jumping. */
89
+ stepHeight: number;
90
+ /** Kinematic only: the steepest slope it climbs, in degrees. */
91
+ maxSlope: number;
92
+ /** Kinematic only: how far below its feet it still sticks to the ground (walking down steps and slopes). */
93
+ snapDistance: number;
94
+ /**
95
+ * Kinematic only: where it walks, in units per second on the world x and z
96
+ * axes. A motor sets it; the physics step reads it every Simulation Step and
97
+ * moves the body through the character controller. It stays until set again.
98
+ */
99
+ desiredVelocity: {
100
+ x: number;
101
+ z: number;
102
+ };
103
+ /** The body's linear velocity now, in units per second; zero before it exists. Assigning sets it. */
104
+ get linearVelocity(): BodyVector;
105
+ set linearVelocity(value: BodyVector);
106
+ /** Launches a kinematic body upward at `speed` (units per second), only while it stands on something; whether it did. */
107
+ jump(speed: number): boolean;
108
+ /** Whether the body stands on something; only a kinematic body moved by the character controller can. */
109
+ get grounded(): boolean;
110
+ /** Pushes a dynamic body with an instantaneous impulse (mass · velocity change); a no-op on any other body. */
111
+ applyImpulse(impulse: BodyVector): void;
112
+ onReady(): void;
113
+ onDestroy(): void;
114
+ }
@@ -0,0 +1,83 @@
1
+ import { Component } from '../component.js';
2
+ export const RIGID_BODY_TYPES = ['dynamic', 'kinematic'];
3
+ const ZERO = { x: 0, y: 0, z: 0 };
4
+ /**
5
+ * Makes the entity's `Collider` a body that moves (ADR 0028). A `dynamic` body
6
+ * is Rapier's: it falls under the scene's gravity, bounces, stacks and rests,
7
+ * and after every Simulation Step the engine writes the entity's position and
8
+ * rotation from it. A `kinematic` body is moved by code. It needs a `Collider`
9
+ * on the same entity (`validate_project` reports a body without one, and at
10
+ * runtime none is created). `mass` is the collider's mass; `velocity` is the
11
+ * initial linear velocity. A `kinematic` body is a character: every step the
12
+ * engine moves it through Rapier's character controller by its
13
+ * `desiredVelocity` on x and z and its own vertical velocity under gravity,
14
+ * stopping at walls, climbing steps up to `stepHeight` and slopes up to
15
+ * `maxSlope`, sticking to the ground within `snapDistance`, and pushing
16
+ * dynamic bodies it walks into. A param outside its range (`type` unknown,
17
+ * `mass` under the minimum, a `velocity` that is not three numbers) creates
18
+ * no body and warns once; `validate_project` reports it as
19
+ * `invalid-rigid-body-param`. A RigidBody in a 2D scene creates nothing.
20
+ */
21
+ export class RigidBody extends Component {
22
+ static componentName = 'RigidBody';
23
+ static space = '3d';
24
+ static params = {
25
+ type: { label: 'Type', options: [...RIGID_BODY_TYPES] },
26
+ mass: { label: 'Mass', min: 0.001, step: 0.1 },
27
+ gravityScale: { label: 'Gravity scale', step: 0.1 },
28
+ linearDamping: { label: 'Linear damping', min: 0, step: 0.1 },
29
+ angularDamping: { label: 'Angular damping', min: 0, step: 0.1 },
30
+ lockRotations: { label: 'Lock rotations' },
31
+ velocity: { label: 'Initial velocity', kind: 'vector3' },
32
+ stepHeight: { label: 'Step height', min: 0, step: 0.05 },
33
+ maxSlope: { label: 'Max slope (degrees)', min: 0, max: 89, step: 1 },
34
+ snapDistance: { label: 'Snap to ground', min: 0, step: 0.05 },
35
+ };
36
+ // Live reads and writes of the body, not authorable defaults (they need a Game to answer).
37
+ static transient = ['linearVelocity', 'desiredVelocity'];
38
+ type = 'dynamic';
39
+ mass = 1;
40
+ gravityScale = 1;
41
+ linearDamping = 0;
42
+ angularDamping = 0;
43
+ lockRotations = false;
44
+ /** The initial linear velocity `[x, y, z]`, applied when the body is created. */
45
+ velocity = [0, 0, 0];
46
+ /** Kinematic only: the tallest step it climbs without jumping. */
47
+ stepHeight = 0.3;
48
+ /** Kinematic only: the steepest slope it climbs, in degrees. */
49
+ maxSlope = 45;
50
+ /** Kinematic only: how far below its feet it still sticks to the ground (walking down steps and slopes). */
51
+ snapDistance = 0.2;
52
+ /**
53
+ * Kinematic only: where it walks, in units per second on the world x and z
54
+ * axes. A motor sets it; the physics step reads it every Simulation Step and
55
+ * moves the body through the character controller. It stays until set again.
56
+ */
57
+ desiredVelocity = { x: 0, z: 0 };
58
+ /** The body's linear velocity now, in units per second; zero before it exists. Assigning sets it. */
59
+ get linearVelocity() {
60
+ return this.game.physics.velocityOf(this.entity) ?? { ...ZERO };
61
+ }
62
+ set linearVelocity(value) {
63
+ this.game.physics.setVelocityOf(this.entity, value);
64
+ }
65
+ /** Launches a kinematic body upward at `speed` (units per second), only while it stands on something; whether it did. */
66
+ jump(speed) {
67
+ return this.game.physics.jumpOf(this.entity, speed);
68
+ }
69
+ /** Whether the body stands on something; only a kinematic body moved by the character controller can. */
70
+ get grounded() {
71
+ return this.game.physics.groundedOf(this.entity);
72
+ }
73
+ /** Pushes a dynamic body with an instantaneous impulse (mass · velocity change); a no-op on any other body. */
74
+ applyImpulse(impulse) {
75
+ this.game.physics.applyImpulseTo(this.entity, impulse);
76
+ }
77
+ onReady() {
78
+ this.game.physics.attach(this.entity);
79
+ }
80
+ onDestroy() {
81
+ this.game.physics.detach(this.entity);
82
+ }
83
+ }
@@ -1,4 +1,4 @@
1
- import { Component } from '../component.js';
1
+ import { Component, type ComponentSpace } from '../component.js';
2
2
  import { type CollisionPoint, type CollisionShape } from '../collision-shape.js';
3
3
  /**
4
4
  * Static collider. Character motors (e.g. PlatformerMotor) collide against
@@ -9,6 +9,7 @@ import { type CollisionPoint, type CollisionShape } from '../collision-shape.js'
9
9
  */
10
10
  export declare class Solid extends Component {
11
11
  static componentName: string;
12
+ static space: ComponentSpace;
12
13
  static params: {
13
14
  offsetX: {
14
15
  label: string;
@@ -9,6 +9,7 @@ import { collisionBounds, resolveCollisionPoints, } from '../collision-shape.js'
9
9
  */
10
10
  export class Solid extends Component {
11
11
  static componentName = 'Solid';
12
+ static space = '2d';
12
13
  static params = {
13
14
  offsetX: { label: 'x offset' },
14
15
  offsetY: { label: 'y offset' },
@@ -1,4 +1,4 @@
1
- import { Component } from '../component.js';
1
+ import { Component, type ComponentSpace } from '../component.js';
2
2
  import type { YSortParticipant } from '../render-sort.js';
3
3
  export type SpriteShape = 'rectangle' | 'circle';
4
4
  /**
@@ -7,6 +7,7 @@ export type SpriteShape = 'rectangle' | 'circle';
7
7
  */
8
8
  export declare class Sprite extends Component implements YSortParticipant {
9
9
  static componentName: string;
10
+ static space: ComponentSpace;
10
11
  static params: {
11
12
  offsetX: {
12
13
  label: string;
@@ -32,6 +33,9 @@ export declare class Sprite extends Component implements YSortParticipant {
32
33
  max: number;
33
34
  step: number;
34
35
  };
36
+ emissive: {
37
+ label: string;
38
+ };
35
39
  };
36
40
  static transient: string[];
37
41
  private _width;
@@ -55,6 +59,9 @@ export declare class Sprite extends Component implements YSortParticipant {
55
59
  set anchorX(value: number);
56
60
  get anchorY(): number;
57
61
  set anchorY(value: number);
62
+ private _emissive;
63
+ get emissive(): boolean;
64
+ set emissive(value: boolean);
58
65
  /** Optional texture URL; with pixelArt on it filters in nearest. */
59
66
  texture?: string;
60
67
  pixelArt: boolean;
@@ -3,6 +3,7 @@ import { Component } from '../component.js';
3
3
  import { spritePlacement } from '../sprite-placement.js';
4
4
  import { reportRejection } from '../report-rejection.js';
5
5
  import { spriteBatchesOf, spriteInstanceOf } from '../sprite-batches.js';
6
+ import { setEmissive, TRANSPARENT_TEXEL_ALPHA } from '../render-layers.js';
6
7
  const clampAnchor = (value) => Math.min(1, Math.max(0, value));
7
8
  /**
8
9
  * Textured or flat-color quad. In the unified pipeline, a 2D sprite is a
@@ -10,12 +11,14 @@ const clampAnchor = (value) => Math.min(1, Math.max(0, value));
10
11
  */
11
12
  export class Sprite extends Component {
12
13
  static componentName = 'Sprite';
14
+ static space = '2d';
13
15
  static params = {
14
16
  offsetX: { label: 'x offset' },
15
17
  offsetY: { label: 'y offset' },
16
18
  anchorX: { label: 'x anchor', min: 0, max: 1, step: 0.25 },
17
19
  anchorY: { label: 'y anchor', min: 0, max: 1, step: 0.25 },
18
20
  layer: { label: 'layer', min: -5, max: 5, step: 1 },
21
+ emissive: { label: 'emissive' },
19
22
  };
20
23
  static transient = ['mesh'];
21
24
  // Size, color and offset are reactive so inspector edits update the live quad.
@@ -80,6 +83,17 @@ export class Sprite extends Component {
80
83
  this._anchorY = clampAnchor(value);
81
84
  this.syncQuad();
82
85
  }
86
+ // Emissive (issue #78 CA-10): never darkened by the light-map, drawn after
87
+ // it at full brightness. On its own render layer.
88
+ _emissive = false;
89
+ get emissive() {
90
+ return this._emissive;
91
+ }
92
+ set emissive(value) {
93
+ this._emissive = value === true;
94
+ if (this.mesh)
95
+ setEmissive(this.mesh, this._emissive);
96
+ }
83
97
  /** Optional texture URL; with pixelArt on it filters in nearest. */
84
98
  texture;
85
99
  pixelArt = false;
@@ -127,11 +141,12 @@ export class Sprite extends Component {
127
141
  if (!this.mesh)
128
142
  return;
129
143
  this.mesh.position.z = this.layer * 0.01;
144
+ setEmissive(this.mesh, this._emissive);
130
145
  this.syncQuad();
131
146
  this.entity.node.add(this.mesh);
132
147
  }
133
148
  buildMesh() {
134
- const material = new THREE.MeshBasicMaterial({ color: this.color, transparent: true });
149
+ const material = new THREE.MeshBasicMaterial({ color: this.color, transparent: true, alphaTest: TRANSPARENT_TEXEL_ALPHA });
135
150
  if (this.texture) {
136
151
  // Its own clone of the cached base (game.assets, ADR 0019): filters
137
152
  // are per clone, colour space comes with the base, and the image lands
@@ -0,0 +1,46 @@
1
+ import * as THREE from 'three/webgpu';
2
+ import { Component, type ComponentSpace } from '../component.js';
3
+ import type { Vec3Json } from '../scene-camera-3d.js';
4
+ /**
5
+ * The 3D scene's sunlight (ADR 0027, `CONTEXT.md` Sun): a directional light
6
+ * that shines along `direction`, in world space — where its entity stands, or
7
+ * how it is rotated, does not matter. Color is `0xrrggbb`; no shadows. A Sun
8
+ * in a 2D scene creates nothing. The three light lives in the Game's scene,
9
+ * moves with its props, and leaves with the entity.
10
+ */
11
+ export declare class Sun extends Component {
12
+ static componentName: string;
13
+ static space: ComponentSpace;
14
+ static params: {
15
+ direction: {
16
+ label: string;
17
+ kind: "vector3";
18
+ };
19
+ color: {
20
+ label: string;
21
+ kind: "color";
22
+ };
23
+ intensity: {
24
+ label: string;
25
+ min: number;
26
+ step: number;
27
+ };
28
+ };
29
+ static transient: string[];
30
+ /** The three light, null in a 2D scene or before the component is ready. */
31
+ light: THREE.DirectionalLight | null;
32
+ private _direction;
33
+ private _color;
34
+ private _intensity;
35
+ /** Where the light travels, as a world-space vector `[x, y, z]`; any length but zero. */
36
+ get direction(): Vec3Json;
37
+ set direction(value: Vec3Json);
38
+ get color(): number;
39
+ set color(value: number);
40
+ get intensity(): number;
41
+ set intensity(value: number);
42
+ onReady(): void;
43
+ onDestroy(): void;
44
+ /** Puts the light on the opposite side of the origin from where it travels, targeting the origin. */
45
+ private aim;
46
+ }
@@ -0,0 +1,74 @@
1
+ import * as THREE from 'three/webgpu';
2
+ import { Component } from '../component.js';
3
+ const DEFAULT_DIRECTION = [-1, -2, -1];
4
+ /** Scene JSON is untyped at runtime: only an array of three finite numbers, not all zero, aims a light. */
5
+ const finiteDirection = (value) => Array.isArray(value) && value.length === 3 && value.every(Number.isFinite) && value.some((component) => component !== 0);
6
+ /**
7
+ * The 3D scene's sunlight (ADR 0027, `CONTEXT.md` Sun): a directional light
8
+ * that shines along `direction`, in world space — where its entity stands, or
9
+ * how it is rotated, does not matter. Color is `0xrrggbb`; no shadows. A Sun
10
+ * in a 2D scene creates nothing. The three light lives in the Game's scene,
11
+ * moves with its props, and leaves with the entity.
12
+ */
13
+ export class Sun extends Component {
14
+ static componentName = 'Sun';
15
+ static space = '3d';
16
+ static params = {
17
+ direction: { label: 'Direction', kind: 'vector3' },
18
+ color: { label: 'Color', kind: 'color' },
19
+ intensity: { label: 'Intensity', min: 0, step: 0.1 },
20
+ };
21
+ static transient = ['light'];
22
+ /** The three light, null in a 2D scene or before the component is ready. */
23
+ light = null;
24
+ _direction = [...DEFAULT_DIRECTION];
25
+ _color = 0xffffff;
26
+ _intensity = 1;
27
+ /** Where the light travels, as a world-space vector `[x, y, z]`; any length but zero. */
28
+ get direction() {
29
+ return [...this._direction];
30
+ }
31
+ set direction(value) {
32
+ // A zero or unreadable direction keeps the last good one: there is nothing to aim along.
33
+ if (!finiteDirection(value))
34
+ return;
35
+ this._direction = [...value];
36
+ this.aim();
37
+ }
38
+ get color() {
39
+ return this._color;
40
+ }
41
+ set color(value) {
42
+ this._color = value;
43
+ this.light?.color.setHex(value);
44
+ }
45
+ get intensity() {
46
+ return this._intensity;
47
+ }
48
+ set intensity(value) {
49
+ this._intensity = value;
50
+ if (this.light)
51
+ this.light.intensity = value;
52
+ }
53
+ onReady() {
54
+ if (this.game.space !== '3d')
55
+ return;
56
+ const light = new THREE.DirectionalLight(this._color, this._intensity);
57
+ this.light = light;
58
+ // The target joins the scene too, or three never updates its world matrix.
59
+ this.game.scene.add(light, light.target);
60
+ this.aim();
61
+ }
62
+ onDestroy() {
63
+ this.light?.removeFromParent();
64
+ this.light?.target.removeFromParent();
65
+ }
66
+ /** Puts the light on the opposite side of the origin from where it travels, targeting the origin. */
67
+ aim() {
68
+ if (!this.light)
69
+ return;
70
+ const [x, y, z] = this._direction;
71
+ this.light.position.set(-x, -y, -z).normalize();
72
+ this.light.target.position.set(0, 0, 0);
73
+ }
74
+ }
@@ -1,10 +1,11 @@
1
- import { Component } from '../component.js';
1
+ import { Component, type ComponentSpace } from '../component.js';
2
2
  import { SOLID_SOURCE_SYMBOL, type SolidSource } from '../scene-solids.js';
3
3
  import { type TilemapCell, type TilemapCellBounds } from '../tilemap-grid.js';
4
4
  import { Solid } from './solid.js';
5
5
  /** One authorable cell map rendered as a single merged geometry. */
6
6
  export declare class Tilemap extends Component implements SolidSource {
7
7
  static componentName: string;
8
+ static space: ComponentSpace;
8
9
  static params: {
9
10
  color: {
10
11
  label: string;
@@ -6,9 +6,12 @@ import { SOLID_SOURCE_SYMBOL } from '../scene-solids.js';
6
6
  import { cellAt as gridCellAt, cellBounds as gridCellBounds, cellIndex as gridCellIndex, } from '../tilemap-grid.js';
7
7
  import { Solid } from './solid.js';
8
8
  import { reportRejection } from '../report-rejection.js';
9
+ import { markOccludersChanged } from '../scene-lighting.js';
10
+ import { TRANSPARENT_TEXEL_ALPHA } from '../render-layers.js';
9
11
  /** One authorable cell map rendered as a single merged geometry. */
10
12
  export class Tilemap extends Component {
11
13
  static componentName = 'Tilemap';
14
+ static space = '2d';
12
15
  static params = {
13
16
  color: { label: 'color' },
14
17
  cols: { label: 'tileset columns', min: 1, step: 1 },
@@ -175,6 +178,7 @@ export class Tilemap extends Component {
175
178
  this.loadedTexture?.dispose();
176
179
  this.loadedTexture = undefined;
177
180
  this.derivedSolids = [];
181
+ markOccludersChanged(this.game.lighting);
178
182
  }
179
183
  solids() {
180
184
  return this.derivedSolids;
@@ -255,6 +259,7 @@ export class Tilemap extends Component {
255
259
  return new THREE.MeshBasicMaterial({
256
260
  color: this.color,
257
261
  transparent: true,
262
+ alphaTest: TRANSPARENT_TEXEL_ALPHA,
258
263
  });
259
264
  }
260
265
  rebuildGeometry() {
@@ -349,5 +354,7 @@ export class Tilemap extends Component {
349
354
  next.push(solid);
350
355
  }
351
356
  this.derivedSolids = next;
357
+ // Occlusion is rebuilt from the solid tiles only when they change (inference 9).
358
+ markOccludersChanged(this.game.lighting);
352
359
  }
353
360
  }
@@ -0,0 +1,18 @@
1
+ import type { Entity } from './entity.js';
2
+ /** A scene entity's position: `[x, y]` (z is 0) or `[x, y, z]`. */
3
+ export type PositionJson = [number, number] | [number, number, number];
4
+ /** The transform a scene or prefab entity declares, applied when it spawns. */
5
+ export interface TransformJson {
6
+ /** `[x, y]` (z = 0) or `[x, y, z]`; in a 3D scene z is a world axis. */
7
+ position?: PositionJson;
8
+ /** Euler angles in degrees, order XYZ, applied to the entity's node (3D scenes). */
9
+ rotation?: [number, number, number];
10
+ /** Per-axis scale of the entity's node. */
11
+ scale?: [number, number, number];
12
+ }
13
+ /**
14
+ * Applies a declared transform to a freshly spawned entity. A two-number
15
+ * position behaves exactly as it always did (z = 0); `rotation` is converted
16
+ * from degrees and `scale` replaces the node's unit scale.
17
+ */
18
+ export declare function applyTransformJson(entity: Entity, json: TransformJson): void;