@jgengine/shell 0.8.0 → 0.10.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 (152) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +11 -0
  3. package/dist/GamePlayerShell.d.ts +10 -22
  4. package/dist/GamePlayerShell.js +709 -264
  5. package/dist/GameUiPreview.js +2 -1
  6. package/dist/audio/AudioComponents.js +25 -8
  7. package/dist/audio/audioEngine.d.ts +9 -0
  8. package/dist/audio/audioEngine.js +37 -0
  9. package/dist/audio/musicDirector.d.ts +31 -0
  10. package/dist/audio/musicDirector.js +125 -0
  11. package/dist/audio/musicVoices.d.ts +10 -0
  12. package/dist/audio/musicVoices.js +338 -0
  13. package/dist/audio/synthEngine.d.ts +9 -0
  14. package/dist/audio/synthEngine.js +57 -0
  15. package/dist/behaviour.d.ts +5 -24
  16. package/dist/behaviour.js +14 -50
  17. package/dist/behaviourAttach.d.ts +9 -0
  18. package/dist/behaviourAttach.js +32 -0
  19. package/dist/behaviourDriver.d.ts +7 -0
  20. package/dist/behaviourDriver.js +21 -0
  21. package/dist/camera/GameCameraRig.js +1 -1
  22. package/dist/camera/GameFirstPersonCamera.d.ts +3 -0
  23. package/dist/camera/GameFirstPersonCamera.js +35 -4
  24. package/dist/camera/GameInspectionCamera.js +12 -1
  25. package/dist/camera/GameOrbitCamera.js +44 -1
  26. package/dist/camera/PlayerFov.d.ts +18 -0
  27. package/dist/camera/PlayerFov.js +48 -0
  28. package/dist/camera/cameraBlendMath.d.ts +13 -0
  29. package/dist/camera/cameraBlendMath.js +36 -0
  30. package/dist/camera/cameraRigs.d.ts +3 -0
  31. package/dist/camera/cameraRigs.js +36 -33
  32. package/dist/camera/fovPreference.d.ts +25 -0
  33. package/dist/camera/fovPreference.js +73 -0
  34. package/dist/camera/index.d.ts +3 -0
  35. package/dist/camera/index.js +3 -0
  36. package/dist/camera/orbitCameraMath.d.ts +18 -0
  37. package/dist/camera/orbitCameraMath.js +6 -1
  38. package/dist/camera/rigMath.d.ts +10 -0
  39. package/dist/camera/rigMath.js +36 -1
  40. package/dist/camera/shakeChannel.d.ts +3 -17
  41. package/dist/camera/shakeChannel.js +2 -21
  42. package/dist/camera/shakeChannelMath.d.ts +8 -0
  43. package/dist/camera/shakeChannelMath.js +21 -0
  44. package/dist/cartridge.d.ts +145 -0
  45. package/dist/cartridge.js +246 -0
  46. package/dist/commandSink.d.ts +20 -0
  47. package/dist/commandSink.js +27 -0
  48. package/dist/defineGame.js +4 -1
  49. package/dist/devtools/CollisionDebugWorld.d.ts +5 -0
  50. package/dist/devtools/CollisionDebugWorld.js +181 -0
  51. package/dist/devtools/DevtoolsOverlay.d.ts +65 -1
  52. package/dist/devtools/DevtoolsOverlay.js +383 -41
  53. package/dist/devtools/collisionDebug.d.ts +57 -0
  54. package/dist/devtools/collisionDebug.js +127 -0
  55. package/dist/devtools/collisionDebugMath.d.ts +103 -0
  56. package/dist/devtools/collisionDebugMath.js +129 -0
  57. package/dist/environment/Daylight.d.ts +20 -8
  58. package/dist/environment/Daylight.js +63 -15
  59. package/dist/environment/EnvironmentScene.js +91 -35
  60. package/dist/environment/RoadRibbons.d.ts +7 -0
  61. package/dist/environment/RoadRibbons.js +57 -0
  62. package/dist/environment/groundPadMath.d.ts +2 -2
  63. package/dist/environment/groundPadMath.js +1 -1
  64. package/dist/environment/index.d.ts +2 -1
  65. package/dist/environment/index.js +1 -0
  66. package/dist/environment/skyLightingPolicy.d.ts +10 -0
  67. package/dist/environment/skyLightingPolicy.js +6 -0
  68. package/dist/input/mouseLook.d.ts +27 -0
  69. package/dist/input/mouseLook.js +35 -0
  70. package/dist/inputSink.d.ts +18 -0
  71. package/dist/inputSink.js +35 -0
  72. package/dist/materialOverride.d.ts +4 -6
  73. package/dist/materialOverride.js +12 -16
  74. package/dist/multiplayer.js +7 -3
  75. package/dist/pointer/PointerProbe.js +6 -2
  76. package/dist/pointer/pointerService.d.ts +3 -0
  77. package/dist/pointer/pointerService.js +6 -0
  78. package/dist/postfx/PostProcessing.d.ts +10 -0
  79. package/dist/postfx/PostProcessing.js +82 -0
  80. package/dist/postfx/gradeShader.d.ts +4 -0
  81. package/dist/postfx/gradeShader.js +64 -0
  82. package/dist/render/modelRender.d.ts +10 -1
  83. package/dist/render/modelRender.js +32 -5
  84. package/dist/render/resolveModel.d.ts +14 -0
  85. package/dist/render/resolveModel.js +24 -0
  86. package/dist/settings/QuickControls.d.ts +4 -0
  87. package/dist/settings/QuickControls.js +42 -0
  88. package/dist/settings/SettingsChrome.d.ts +1 -0
  89. package/dist/settings/SettingsChrome.js +10 -0
  90. package/dist/settings/SettingsMenu.d.ts +6 -0
  91. package/dist/settings/SettingsMenu.js +148 -0
  92. package/dist/settings/SettingsRuntime.d.ts +11 -0
  93. package/dist/settings/SettingsRuntime.js +19 -0
  94. package/dist/settings/appliedSettings.d.ts +14 -0
  95. package/dist/settings/appliedSettings.js +27 -0
  96. package/dist/settings/settingsController.d.ts +20 -0
  97. package/dist/settings/settingsController.js +165 -0
  98. package/dist/structures/GeneratedBuilding.d.ts +10 -0
  99. package/dist/structures/GeneratedBuilding.js +96 -8
  100. package/dist/structures/index.d.ts +1 -1
  101. package/dist/structures/index.js +1 -1
  102. package/dist/terrain/CarvedTerrain.d.ts +5 -1
  103. package/dist/terrain/CarvedTerrain.js +6 -5
  104. package/dist/terrain/GrassField.d.ts +3 -1
  105. package/dist/terrain/GrassField.js +4 -2
  106. package/dist/terrain/grassBudget.d.ts +3 -0
  107. package/dist/terrain/grassBudget.js +6 -0
  108. package/dist/terrain/grassGeometry.js +1 -1
  109. package/dist/terrain/terrainDetailMaterial.d.ts +14 -0
  110. package/dist/terrain/terrainDetailMaterial.js +90 -0
  111. package/dist/terrain/terrainMath.d.ts +8 -0
  112. package/dist/terrain/terrainMath.js +9 -0
  113. package/dist/touch/OrientationHint.d.ts +3 -0
  114. package/dist/touch/OrientationHint.js +13 -0
  115. package/dist/touch/TouchControlsOverlay.d.ts +17 -1
  116. package/dist/touch/TouchControlsOverlay.js +125 -9
  117. package/dist/visibility/CullingProvider.d.ts +21 -0
  118. package/dist/visibility/CullingProvider.js +134 -0
  119. package/dist/vision/FrustumSensorHud.d.ts +1 -6
  120. package/dist/vision/FrustumSensorHud.js +25 -27
  121. package/dist/vision/frustumSampleEqual.d.ts +2 -0
  122. package/dist/vision/frustumSampleEqual.js +10 -0
  123. package/dist/water/Ocean.js +1 -1
  124. package/dist/water/OceanConfig.d.ts +13 -0
  125. package/dist/water/OceanConfig.js +25 -17
  126. package/dist/water/index.d.ts +1 -1
  127. package/dist/water/index.js +1 -1
  128. package/dist/weather/FireSpreadLayer.js +7 -2
  129. package/dist/weather/RainField.d.ts +3 -1
  130. package/dist/weather/RainField.js +4 -4
  131. package/dist/weather/SnowField.d.ts +3 -1
  132. package/dist/weather/SnowField.js +4 -4
  133. package/dist/weather/fireSpreadPose.d.ts +2 -0
  134. package/dist/weather/fireSpreadPose.js +4 -0
  135. package/dist/weather/weatherMath.d.ts +5 -1
  136. package/dist/weather/weatherMath.js +7 -2
  137. package/dist/world/DataObjects.d.ts +1 -1
  138. package/dist/world/DataObjects.js +3 -1
  139. package/dist/world/SpriteBatch.d.ts +44 -0
  140. package/dist/world/SpriteBatch.js +112 -0
  141. package/dist/world/WorldHud.d.ts +6 -1
  142. package/dist/world/WorldHud.js +95 -48
  143. package/dist/world/entityPose.d.ts +14 -0
  144. package/dist/world/entityPose.js +10 -0
  145. package/dist/world/telegraphPulse.d.ts +1 -0
  146. package/dist/world/telegraphPulse.js +4 -0
  147. package/dist/world/worldBarSamples.d.ts +30 -0
  148. package/dist/world/worldBarSamples.js +57 -0
  149. package/dist/worldSync.d.ts +10 -0
  150. package/dist/worldSync.js +19 -0
  151. package/llms.txt +1522 -1143
  152. package/package.json +4 -4
@@ -0,0 +1,127 @@
1
+ export const COLLISION_DEBUG_LAYERS = [
2
+ "hitboxes",
3
+ "bodies",
4
+ "projectiles",
5
+ "muzzles",
6
+ "aimLaser",
7
+ ];
8
+ const DEFAULT_LAYERS = {
9
+ hitboxes: false,
10
+ bodies: false,
11
+ projectiles: false,
12
+ muzzles: false,
13
+ aimLaser: false,
14
+ };
15
+ export function createDefaultCollisionDebugState() {
16
+ return {
17
+ layers: { ...DEFAULT_LAYERS },
18
+ aimProbe: null,
19
+ projectileTraces: [],
20
+ maxProjectileTraces: 32,
21
+ projectileTraceLifeMs: 2500,
22
+ };
23
+ }
24
+ export function anyCollisionLayerOn(layers) {
25
+ return (layers.hitboxes ||
26
+ layers.bodies ||
27
+ layers.projectiles ||
28
+ layers.muzzles ||
29
+ layers.aimLaser);
30
+ }
31
+ export function colliderScanNeeded(layers) {
32
+ return layers.hitboxes || layers.bodies;
33
+ }
34
+ export function projectileListenNeeded(layers) {
35
+ return layers.projectiles || layers.muzzles;
36
+ }
37
+ export function aimProbeNeeded(layers) {
38
+ return layers.aimLaser;
39
+ }
40
+ export function createCollisionDebugController(initial = createDefaultCollisionDebugState()) {
41
+ let state = {
42
+ ...initial,
43
+ layers: { ...initial.layers },
44
+ projectileTraces: [...initial.projectileTraces],
45
+ };
46
+ const listeners = new Set();
47
+ let nextTraceId = 1;
48
+ function emit() {
49
+ for (const listener of listeners)
50
+ listener();
51
+ }
52
+ function replace(next) {
53
+ state = next;
54
+ emit();
55
+ }
56
+ return {
57
+ getState() {
58
+ return state;
59
+ },
60
+ subscribe(listener) {
61
+ listeners.add(listener);
62
+ return () => {
63
+ listeners.delete(listener);
64
+ };
65
+ },
66
+ setLayer(layer, on) {
67
+ if (state.layers[layer] === on)
68
+ return;
69
+ replace({ ...state, layers: { ...state.layers, [layer]: on } });
70
+ },
71
+ toggleLayer(layer) {
72
+ replace({ ...state, layers: { ...state.layers, [layer]: !state.layers[layer] } });
73
+ },
74
+ setLayers(partial) {
75
+ const layers = { ...state.layers, ...partial };
76
+ replace({ ...state, layers });
77
+ },
78
+ setAllLayers(on) {
79
+ const layers = {
80
+ hitboxes: on,
81
+ bodies: on,
82
+ projectiles: on,
83
+ muzzles: on,
84
+ aimLaser: on,
85
+ };
86
+ replace({ ...state, layers });
87
+ },
88
+ setAimProbe(probe) {
89
+ state = { ...state, aimProbe: probe };
90
+ },
91
+ getAimProbe() {
92
+ return state.aimProbe;
93
+ },
94
+ pushProjectileTrace(input) {
95
+ if (!projectileListenNeeded(state.layers))
96
+ return;
97
+ const trace = {
98
+ id: nextTraceId++,
99
+ origin: input.origin,
100
+ at: input.at,
101
+ hit: input.hit,
102
+ bornMs: input.nowMs ?? 0,
103
+ };
104
+ const next = [...state.projectileTraces, trace];
105
+ while (next.length > state.maxProjectileTraces)
106
+ next.shift();
107
+ replace({ ...state, projectileTraces: next });
108
+ },
109
+ pruneProjectileTraces(nowMs) {
110
+ if (state.projectileTraces.length === 0)
111
+ return;
112
+ const life = state.projectileTraceLifeMs;
113
+ const next = state.projectileTraces.filter((trace) => nowMs - trace.bornMs <= life);
114
+ if (next.length === state.projectileTraces.length)
115
+ return;
116
+ replace({ ...state, projectileTraces: next });
117
+ },
118
+ reset() {
119
+ nextTraceId = 1;
120
+ replace(createDefaultCollisionDebugState());
121
+ },
122
+ isActive() {
123
+ return anyCollisionLayerOn(state.layers);
124
+ },
125
+ };
126
+ }
127
+ export const collisionDebug = createCollisionDebugController();
@@ -0,0 +1,103 @@
1
+ import type { EntityPosition } from "@jgengine/core/scene/entityStore";
2
+ import { type ColliderPurpose, type EntityColliderSet, type ResolvedCollider } from "@jgengine/core/scene/colliders";
3
+ import { type SceneRaycastApi, type SceneRaycastHit } from "@jgengine/core/scene/sceneRaycast";
4
+ import { type ShotOriginPolicy } from "@jgengine/core/combat/shotOrigin";
5
+ import type { Aim } from "@jgengine/core/scene/spatial";
6
+ import type { CollisionDebugLayers } from "./collisionDebug.js";
7
+ export type AimEndpointKind = "damage" | "solid" | "miss";
8
+ export interface DebugShapeEntry {
9
+ key: string;
10
+ targetKind: "entity" | "object";
11
+ instanceId: string;
12
+ catalogId?: string;
13
+ name: string;
14
+ purpose: ColliderPurpose;
15
+ damageEligible: boolean;
16
+ blocks: boolean;
17
+ position: EntityPosition;
18
+ rotationY: number;
19
+ shape: {
20
+ kind: "sphere";
21
+ radius: number;
22
+ center: EntityPosition;
23
+ } | {
24
+ kind: "aabb";
25
+ halfExtents: EntityPosition;
26
+ center: EntityPosition;
27
+ };
28
+ label: string;
29
+ style: "hitbox" | "body";
30
+ }
31
+ export interface AimLaserDebug {
32
+ origin: EntityPosition;
33
+ direction: EntityPosition;
34
+ end: EntityPosition;
35
+ maxDistance: number;
36
+ kind: AimEndpointKind;
37
+ nearest: SceneRaycastHit | null;
38
+ firstImpact: SceneRaycastHit | null;
39
+ /** Counts scene raycast calls performed (0 when skipped). */
40
+ queryCount: number;
41
+ }
42
+ export interface CollectDebugShapesInput {
43
+ layers: CollisionDebugLayers;
44
+ entities: readonly {
45
+ id: string;
46
+ position: EntityPosition;
47
+ rotationY: number;
48
+ name?: string;
49
+ }[];
50
+ objects?: readonly {
51
+ instanceId: string;
52
+ catalogId: string;
53
+ position: EntityPosition;
54
+ rotationY: number;
55
+ }[];
56
+ entityCollidersOf?(instanceId: string): EntityColliderSet | null | undefined;
57
+ objectCollidersOf?(instanceId: string): EntityColliderSet | null | undefined;
58
+ objectHalfExtentsOf?(catalogId: string): EntityPosition | null | undefined;
59
+ /** Mutated when a scene scan runs — tests assert 0 when hidden. */
60
+ counters?: {
61
+ scans: number;
62
+ shapes: number;
63
+ };
64
+ }
65
+ export declare function classifyAimEndpoint(hit: Pick<SceneRaycastHit, "damageEligible"> | null | undefined): AimEndpointKind;
66
+ export declare function pointAlongRay(origin: EntityPosition, direction: EntityPosition, distance: number): EntityPosition;
67
+ export declare function shapeWorldCenter(collider: ResolvedCollider, position: EntityPosition, rotationY: number): EntityPosition;
68
+ /**
69
+ * Collects world-space collider wireframe entries for enabled layers.
70
+ * Returns [] and performs no entity/object iteration when both hitbox/body layers are off.
71
+ */
72
+ export declare function collectDebugShapes(input: CollectDebugShapesInput): DebugShapeEntry[];
73
+ export interface ComputeAimLaserInput {
74
+ layers: CollisionDebugLayers;
75
+ sceneRaycast: SceneRaycastApi;
76
+ positionOf(instanceId: string): EntityPosition | undefined;
77
+ rotationYOf?(instanceId: string): number | undefined;
78
+ collidersOf?(instanceId: string): EntityColliderSet | null | undefined;
79
+ from: string;
80
+ aim: Aim;
81
+ originPolicy?: ShotOriginPolicy;
82
+ maxDistance: number;
83
+ counters?: {
84
+ queries: number;
85
+ };
86
+ }
87
+ /**
88
+ * Builds the authoritative aim-laser segment using resolveShot + scene raycast
89
+ * (same seam as projectile prediction/settlement). Zero queries when aimLaser is off.
90
+ */
91
+ export declare function computeAimLaser(input: ComputeAimLaserInput): AimLaserDebug | null;
92
+ export declare function muzzleMarkerFromOrigin(origin: EntityPosition): {
93
+ center: EntityPosition;
94
+ radius: number;
95
+ color: string;
96
+ };
97
+ export declare const HITBOX_WIRE_COLOR = "#f472b6";
98
+ export declare const BODY_WIRE_COLOR = "#38bdf8";
99
+ export declare const PROJECTILE_PATH_COLOR = "#fde68a";
100
+ export declare const AIM_LASER_COLOR = "#a3e635";
101
+ export declare const AIM_DAMAGE_COLOR = "#f87171";
102
+ export declare const AIM_SOLID_COLOR = "#fbbf24";
103
+ export declare const AIM_MISS_COLOR = "#94a3b8";
@@ -0,0 +1,129 @@
1
+ import { colliderWorldCenter, defaultEntityColliders, defaultObjectColliders, resolveColliders, } from "@jgengine/core/scene/colliders";
2
+ import { firstImpact, hitsUntilBlocked, } from "@jgengine/core/scene/sceneRaycast";
3
+ import { resolveShot } from "@jgengine/core/combat/shotOrigin";
4
+ import { aimProbeNeeded, colliderScanNeeded } from "./collisionDebug.js";
5
+ export function classifyAimEndpoint(hit) {
6
+ if (hit === null || hit === undefined)
7
+ return "miss";
8
+ return hit.damageEligible ? "damage" : "solid";
9
+ }
10
+ export function pointAlongRay(origin, direction, distance) {
11
+ return [
12
+ origin[0] + direction[0] * distance,
13
+ origin[1] + direction[1] * distance,
14
+ origin[2] + direction[2] * distance,
15
+ ];
16
+ }
17
+ export function shapeWorldCenter(collider, position, rotationY) {
18
+ return colliderWorldCenter(collider, position, rotationY);
19
+ }
20
+ function styleFor(purpose) {
21
+ return purpose === "damage" ? "hitbox" : "body";
22
+ }
23
+ function labelFor(collider) {
24
+ return `${collider.purpose}:${collider.name}`;
25
+ }
26
+ function pushResolved(out, targetKind, instanceId, catalogId, position, rotationY, set, layers) {
27
+ for (const collider of resolveColliders(set)) {
28
+ const style = styleFor(collider.purpose);
29
+ if (style === "hitbox" && !layers.hitboxes)
30
+ continue;
31
+ if (style === "body" && !layers.bodies)
32
+ continue;
33
+ const center = shapeWorldCenter(collider, position, rotationY);
34
+ const shape = collider.shape.kind === "sphere"
35
+ ? { kind: "sphere", radius: collider.shape.radius, center }
36
+ : {
37
+ kind: "aabb",
38
+ halfExtents: collider.shape.halfExtents,
39
+ center,
40
+ };
41
+ out.push({
42
+ key: `${targetKind}:${instanceId}:${collider.purpose}:${collider.name}`,
43
+ targetKind,
44
+ instanceId,
45
+ ...(catalogId !== undefined ? { catalogId } : {}),
46
+ name: collider.name,
47
+ purpose: collider.purpose,
48
+ damageEligible: collider.damageEligible,
49
+ blocks: collider.blocks,
50
+ position,
51
+ rotationY,
52
+ shape,
53
+ label: labelFor(collider),
54
+ style,
55
+ });
56
+ }
57
+ }
58
+ /**
59
+ * Collects world-space collider wireframe entries for enabled layers.
60
+ * Returns [] and performs no entity/object iteration when both hitbox/body layers are off.
61
+ */
62
+ export function collectDebugShapes(input) {
63
+ if (!colliderScanNeeded(input.layers))
64
+ return [];
65
+ input.counters !== undefined && (input.counters.scans += 1);
66
+ const out = [];
67
+ for (const entity of input.entities) {
68
+ const set = input.entityCollidersOf?.(entity.id) ?? defaultEntityColliders();
69
+ pushResolved(out, "entity", entity.id, entity.name, entity.position, entity.rotationY, set, input.layers);
70
+ }
71
+ if (input.objects !== undefined) {
72
+ for (const object of input.objects) {
73
+ const half = input.objectHalfExtentsOf?.(object.catalogId) ?? undefined;
74
+ const set = input.objectCollidersOf?.(object.instanceId) ??
75
+ defaultObjectColliders(half === null || half === undefined ? undefined : half);
76
+ pushResolved(out, "object", object.instanceId, object.catalogId, object.position, object.rotationY, set, input.layers);
77
+ }
78
+ }
79
+ if (input.counters !== undefined)
80
+ input.counters.shapes += out.length;
81
+ return out;
82
+ }
83
+ /**
84
+ * Builds the authoritative aim-laser segment using resolveShot + scene raycast
85
+ * (same seam as projectile prediction/settlement). Zero queries when aimLaser is off.
86
+ */
87
+ export function computeAimLaser(input) {
88
+ if (!aimProbeNeeded(input.layers)) {
89
+ return null;
90
+ }
91
+ const resolved = resolveShot({
92
+ positionOf: input.positionOf,
93
+ rotationYOf: input.rotationYOf,
94
+ collidersOf: input.collidersOf,
95
+ }, input.from, input.aim, input.originPolicy ?? { kind: "eye" });
96
+ if (resolved === null)
97
+ return null;
98
+ input.counters !== undefined && (input.counters.queries += 1);
99
+ const all = input.sceneRaycast.raycastAll({
100
+ origin: resolved.origin,
101
+ direction: resolved.direction,
102
+ maxDistance: input.maxDistance,
103
+ excludeInstanceIds: [input.from],
104
+ });
105
+ const until = hitsUntilBlocked(all);
106
+ const nearest = until[0] ?? null;
107
+ const impact = firstImpact(until);
108
+ const end = nearest !== null ? nearest.point : pointAlongRay(resolved.origin, resolved.direction, input.maxDistance);
109
+ return {
110
+ origin: resolved.origin,
111
+ direction: resolved.direction,
112
+ end,
113
+ maxDistance: input.maxDistance,
114
+ kind: classifyAimEndpoint(nearest),
115
+ nearest,
116
+ firstImpact: impact,
117
+ queryCount: 1,
118
+ };
119
+ }
120
+ export function muzzleMarkerFromOrigin(origin) {
121
+ return { center: origin, radius: 0.08, color: "#ef4444" };
122
+ }
123
+ export const HITBOX_WIRE_COLOR = "#f472b6";
124
+ export const BODY_WIRE_COLOR = "#38bdf8";
125
+ export const PROJECTILE_PATH_COLOR = "#fde68a";
126
+ export const AIM_LASER_COLOR = "#a3e635";
127
+ export const AIM_DAMAGE_COLOR = "#f87171";
128
+ export const AIM_SOLID_COLOR = "#fbbf24";
129
+ export const AIM_MISS_COLOR = "#94a3b8";
@@ -7,10 +7,16 @@ export interface SkyDomeProps {
7
7
  radius?: number;
8
8
  offset?: number;
9
9
  exponent?: number;
10
+ /** Direction toward the sun; a bright HDR sun disc + warm glow is drawn there (blooms through the post chain). Omit to skip the sun. */
11
+ sunDirection?: readonly [number, number, number];
12
+ /** Sun disc/glow colour. Default warm white. */
13
+ sunColor?: string;
14
+ /** Sun disc/glow intensity multiplier. Default 1. */
15
+ sunIntensity?: number;
10
16
  /** Exposes the created shader material so a time-of-day driver can mutate its uniforms per frame without recreating it. */
11
17
  materialRef?: MutableRefObject<THREE.ShaderMaterial | null>;
12
18
  }
13
- export declare function SkyDome({ topColor, horizonColor, radius, offset, exponent, materialRef, }?: SkyDomeProps): import("react").JSX.Element;
19
+ export declare function SkyDome({ topColor, horizonColor, radius, offset, exponent, sunDirection, sunColor, sunIntensity, materialRef, }?: SkyDomeProps): import("react").JSX.Element;
14
20
  export interface DaylightProps {
15
21
  sky?: SkyDomeProps | false;
16
22
  fog?: {
@@ -28,12 +34,16 @@ export interface DaylightProps {
28
34
  groundColor?: string;
29
35
  intensity?: number;
30
36
  };
37
+ /** When false, only the sky dome and fog mount — use with authored `PlayableGame.lighting`. Default true. */
38
+ lights?: boolean;
31
39
  }
32
- export declare function Daylight({ sky, fog, sun, ambient }?: DaylightProps): import("react").JSX.Element;
33
- /** Renders a fixed sky/sun/fog look sampled from `sky`'s preset (or, when `timeOfDay` is on but no clock drives it, its noon look). No per-frame updates. */
34
- export declare function SkyDaylight({ sky }: {
40
+ export declare function Daylight({ sky, fog, sun, ambient, lights }?: DaylightProps): import("react").JSX.Element;
41
+ export interface SkyDaylightProps {
35
42
  sky: SkyEnvironmentDescriptor;
36
- }): import("react").JSX.Element;
43
+ lights?: boolean;
44
+ }
45
+ /** Renders a fixed sky/sun/fog look sampled from `sky`'s preset (or, when `timeOfDay` is on but no clock drives it, its noon look). No per-frame updates. */
46
+ export declare function SkyDaylight({ sky, lights }: SkyDaylightProps): import("react").JSX.Element;
37
47
  export interface TimeOfDayDaylightProps {
38
48
  sky: SkyEnvironmentDescriptor;
39
49
  /** The world's `SimClock` (or a stub exposing `calendar().dayFraction`). Absent means static rendering. */
@@ -42,9 +52,11 @@ export interface TimeOfDayDaylightProps {
42
52
  dayFraction: number;
43
53
  };
44
54
  };
55
+ lights?: boolean;
45
56
  }
46
57
  /**
47
- * Drives `Daylight`'s sun/sky/fog from the world clock when `sky.timeOfDay` and `clock` are both present,
48
- * sampling `daylightStateAt` every frame; otherwise renders the static preset look via `SkyDaylight`.
58
+ * Drives sky/fog (and optional default lights) from the world clock when `sky.timeOfDay` and `clock`
59
+ * are both present. Authored `PlayableGame.lighting` is never rewritten pass `lights={false}` so
60
+ * only dome colors and fog track the day fraction.
49
61
  */
50
- export declare function TimeOfDayDaylight({ sky, clock }: TimeOfDayDaylightProps): import("react").JSX.Element;
62
+ export declare function TimeOfDayDaylight({ sky, clock, lights }: TimeOfDayDaylightProps): import("react").JSX.Element;
@@ -3,13 +3,36 @@ import { useFrame } from "@react-three/fiber";
3
3
  import { useEffect, useMemo, useRef } from "react";
4
4
  import * as THREE from "three";
5
5
  import { daylightStateAt, SKY_PRESET_DAY_FRACTION } from "./daylightCycle.js";
6
+ function normalizeVec3(v) {
7
+ const out = new THREE.Vector3(v[0], v[1], v[2]);
8
+ return out.lengthSq() === 0 ? new THREE.Vector3(0, 1, 0) : out.normalize();
9
+ }
6
10
  const SKY_TOP = "#3fa4f2";
7
11
  const SKY_HORIZON = "#e3f4ff";
8
12
  const FOG_COLOR = "#e9f6ff";
9
13
  const SUN_COLOR = "#fff1c9";
10
14
  const HEMI_SKY = "#bfe3ff";
11
15
  const HEMI_GROUND = "#4c6b34";
12
- export function SkyDome({ topColor = SKY_TOP, horizonColor = SKY_HORIZON, radius = 260, offset = 24, exponent = 0.65, materialRef, } = {}) {
16
+ /**
17
+ * Sun directional light whose high-resolution shadow camera follows the view each
18
+ * frame, so grounded shadows stay crisp under the player anywhere in a large world
19
+ * instead of only near the origin.
20
+ */
21
+ function ShadowCastingSun({ position, intensity, color, }) {
22
+ const ref = useRef(null);
23
+ useFrame((state) => {
24
+ const light = ref.current;
25
+ if (light === null)
26
+ return;
27
+ const cx = state.camera.position.x;
28
+ const cz = state.camera.position.z;
29
+ light.position.set(cx + position[0], position[1], cz + position[2]);
30
+ light.target.position.set(cx, 0, cz);
31
+ light.target.updateMatrixWorld();
32
+ });
33
+ return (_jsx("directionalLight", { ref: ref, position: [position[0], position[1], position[2]], intensity: intensity, color: color, castShadow: true, "shadow-mapSize-width": 2048, "shadow-mapSize-height": 2048, "shadow-camera-left": -90, "shadow-camera-right": 90, "shadow-camera-top": 90, "shadow-camera-bottom": -90, "shadow-camera-near": 10, "shadow-camera-far": 520, "shadow-bias": -0.0004, "shadow-normalBias": 0.02 }));
34
+ }
35
+ export function SkyDome({ topColor = SKY_TOP, horizonColor = SKY_HORIZON, radius = 260, offset = 24, exponent = 0.65, sunDirection, sunColor = "#fff4d6", sunIntensity = 1, materialRef, } = {}) {
13
36
  const material = useMemo(() => {
14
37
  return new THREE.ShaderMaterial({
15
38
  uniforms: {
@@ -17,6 +40,9 @@ export function SkyDome({ topColor = SKY_TOP, horizonColor = SKY_HORIZON, radius
17
40
  bottomColor: { value: new THREE.Color(horizonColor) },
18
41
  offset: { value: offset },
19
42
  exponent: { value: exponent },
43
+ uSunColor: { value: new THREE.Color(sunColor) },
44
+ uSunDirection: { value: sunDirection === undefined ? new THREE.Vector3(0, 1, 0) : normalizeVec3(sunDirection) },
45
+ uSunIntensity: { value: sunDirection === undefined ? 0 : sunIntensity },
20
46
  },
21
47
  vertexShader: `
22
48
  varying vec3 vWorldPosition;
@@ -29,19 +55,40 @@ export function SkyDome({ topColor = SKY_TOP, horizonColor = SKY_HORIZON, radius
29
55
  fragmentShader: `
30
56
  uniform vec3 topColor;
31
57
  uniform vec3 bottomColor;
58
+ uniform vec3 uSunColor;
59
+ uniform vec3 uSunDirection;
60
+ uniform float uSunIntensity;
32
61
  uniform float offset;
33
62
  uniform float exponent;
34
63
  varying vec3 vWorldPosition;
64
+ float sHash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
65
+ float sNoise(vec2 p){
66
+ vec2 i = floor(p); vec2 f = fract(p); vec2 u = f * f * (3.0 - 2.0 * f);
67
+ return mix(mix(sHash(i), sHash(i + vec2(1.0, 0.0)), u.x),
68
+ mix(sHash(i + vec2(0.0, 1.0)), sHash(i + vec2(1.0, 1.0)), u.x), u.y);
69
+ }
70
+ float sFbm(vec2 p){ float v = 0.0; float a = 0.5; for (int i = 0; i < 5; i++){ v += a * sNoise(p); p *= 2.0; a *= 0.5; } return v; }
35
71
  void main() {
36
- float h = normalize(vWorldPosition + vec3(0.0, offset, 0.0)).y;
37
- gl_FragColor = vec4(mix(bottomColor, topColor, max(pow(max(h, 0.0), exponent), 0.0)), 1.0);
72
+ vec3 dir = normalize(vWorldPosition + vec3(0.0, offset, 0.0));
73
+ float h = max(dir.y, 0.0);
74
+ vec3 col = mix(bottomColor, topColor, pow(h, exponent));
75
+ col = mix(col, bottomColor * 1.12, pow(1.0 - h, 6.0) * 0.5);
76
+ vec3 vd = normalize(vWorldPosition);
77
+ vec2 cuv = vd.xz / (max(vd.y, 0.06) + 0.15) * 1.2;
78
+ float clouds = smoothstep(0.52, 0.82, sFbm(cuv));
79
+ float band = smoothstep(0.08, 0.35, dir.y) * smoothstep(1.0, 0.35, dir.y);
80
+ col = mix(col, mix(topColor, vec3(1.0), 0.6), clouds * band * 0.35);
81
+ float sd = max(dot(vd, normalize(uSunDirection)), 0.0);
82
+ float glow = pow(sd, 8.0) * 0.35 + pow(sd, 128.0) * 2.6;
83
+ col += uSunColor * glow * uSunIntensity;
84
+ gl_FragColor = vec4(col, 1.0);
38
85
  }
39
86
  `,
40
87
  side: THREE.BackSide,
41
88
  depthWrite: false,
42
89
  fog: false,
43
90
  });
44
- }, [topColor, horizonColor, offset, exponent]);
91
+ }, [topColor, horizonColor, offset, exponent, sunColor, sunDirection, sunIntensity]);
45
92
  useEffect(() => {
46
93
  if (materialRef !== undefined)
47
94
  materialRef.current = material;
@@ -53,25 +100,26 @@ export function SkyDome({ topColor = SKY_TOP, horizonColor = SKY_HORIZON, radius
53
100
  }, [material, materialRef]);
54
101
  return (_jsx("mesh", { material: material, renderOrder: -1, children: _jsx("sphereGeometry", { args: [radius, 32, 16] }) }));
55
102
  }
56
- export function Daylight({ sky, fog, sun, ambient } = {}) {
103
+ export function Daylight({ sky, fog, sun, ambient, lights = true } = {}) {
57
104
  const sunPosition = sun?.position ?? [120, 160, 70];
58
- return (_jsxs(_Fragment, { children: [sky === false ? null : _jsx(SkyDome, { ...(sky ?? {}) }), fog === false ? null : (_jsx("fog", { attach: "fog", args: [fog?.color ?? FOG_COLOR, fog?.near ?? 70, fog?.far ?? 260] })), _jsx("hemisphereLight", { args: [ambient?.skyColor ?? HEMI_SKY, ambient?.groundColor ?? HEMI_GROUND, ambient?.intensity ?? 0.55] }), _jsx("directionalLight", { position: [sunPosition[0], sunPosition[1], sunPosition[2]], intensity: sun?.intensity ?? 0.85, color: sun?.color ?? SUN_COLOR, castShadow: true })] }));
105
+ return (_jsxs(_Fragment, { children: [sky === false ? null : (_jsx(SkyDome, { ...(sky ?? {}), sunDirection: sunPosition, sunColor: sun?.color ?? SUN_COLOR, sunIntensity: sun?.intensity ?? 1 })), fog === false ? null : (_jsx("fog", { attach: "fog", args: [fog?.color ?? FOG_COLOR, fog?.near ?? 70, fog?.far ?? 260] })), lights ? (_jsxs(_Fragment, { children: [_jsx("hemisphereLight", { args: [ambient?.skyColor ?? HEMI_SKY, ambient?.groundColor ?? HEMI_GROUND, ambient?.intensity ?? 0.55] }), _jsx(ShadowCastingSun, { position: sunPosition, intensity: sun?.intensity ?? 0.85, color: sun?.color ?? SUN_COLOR })] })) : null] }));
59
106
  }
60
107
  /** Renders a fixed sky/sun/fog look sampled from `sky`'s preset (or, when `timeOfDay` is on but no clock drives it, its noon look). No per-frame updates. */
61
- export function SkyDaylight({ sky }) {
108
+ export function SkyDaylight({ sky, lights = true }) {
62
109
  const state = useMemo(() => daylightStateAt(SKY_PRESET_DAY_FRACTION[sky.preset], sky), [sky]);
63
- return (_jsx(Daylight, { sky: { topColor: state.skyTop, horizonColor: state.skyBottom }, fog: { color: sky.fog?.color ?? state.background, near: sky.fog?.near, far: sky.fog?.far }, sun: { position: state.sunPosition, intensity: state.sunIntensity }, ambient: { intensity: state.ambientIntensity } }));
110
+ return (_jsx(Daylight, { sky: { topColor: state.skyTop, horizonColor: state.skyBottom }, fog: { color: sky.fog?.color ?? state.background, near: sky.fog?.near, far: sky.fog?.far }, sun: { position: state.sunPosition, intensity: state.sunIntensity }, ambient: { intensity: state.ambientIntensity }, lights: lights }));
64
111
  }
65
112
  /**
66
- * Drives `Daylight`'s sun/sky/fog from the world clock when `sky.timeOfDay` and `clock` are both present,
67
- * sampling `daylightStateAt` every frame; otherwise renders the static preset look via `SkyDaylight`.
113
+ * Drives sky/fog (and optional default lights) from the world clock when `sky.timeOfDay` and `clock`
114
+ * are both present. Authored `PlayableGame.lighting` is never rewritten pass `lights={false}` so
115
+ * only dome colors and fog track the day fraction.
68
116
  */
69
- export function TimeOfDayDaylight({ sky, clock }) {
117
+ export function TimeOfDayDaylight({ sky, clock, lights = true }) {
70
118
  if (!sky.timeOfDay || clock === undefined)
71
- return _jsx(SkyDaylight, { sky: sky });
72
- return _jsx(DrivenDaylight, { sky: sky, clock: clock });
119
+ return _jsx(SkyDaylight, { sky: sky, lights: lights });
120
+ return _jsx(DrivenDaylight, { sky: sky, clock: clock, lights: lights });
73
121
  }
74
- function DrivenDaylight({ sky, clock, }) {
122
+ function DrivenDaylight({ sky, clock, lights, }) {
75
123
  const initial = useMemo(() => daylightStateAt(clock.calendar().dayFraction, sky), [clock, sky]);
76
124
  const sunRef = useRef(null);
77
125
  const hemiRef = useRef(null);
@@ -96,5 +144,5 @@ function DrivenDaylight({ sky, clock, }) {
96
144
  skyMaterial.uniforms.bottomColor.value.set(state.skyBottom);
97
145
  }
98
146
  });
99
- return (_jsxs(_Fragment, { children: [_jsx(SkyDome, { topColor: initial.skyTop, horizonColor: initial.skyBottom, materialRef: skyMaterialRef }), _jsx("fog", { attach: "fog", ref: fogRef, args: [sky.fog?.color ?? initial.background, sky.fog?.near ?? 70, sky.fog?.far ?? 260] }), _jsx("hemisphereLight", { ref: hemiRef, args: [HEMI_SKY, HEMI_GROUND, initial.ambientIntensity] }), _jsx("directionalLight", { ref: sunRef, position: initial.sunPosition, intensity: initial.sunIntensity, color: SUN_COLOR, castShadow: true })] }));
147
+ return (_jsxs(_Fragment, { children: [_jsx(SkyDome, { topColor: initial.skyTop, horizonColor: initial.skyBottom, materialRef: skyMaterialRef }), _jsx("fog", { attach: "fog", ref: fogRef, args: [sky.fog?.color ?? initial.background, sky.fog?.near ?? 70, sky.fog?.far ?? 260] }), lights ? (_jsxs(_Fragment, { children: [_jsx("hemisphereLight", { ref: hemiRef, args: [HEMI_SKY, HEMI_GROUND, initial.ambientIntensity] }), _jsx("directionalLight", { ref: sunRef, position: initial.sunPosition, intensity: initial.sunIntensity, color: SUN_COLOR, castShadow: true })] })) : null] }));
100
148
  }