@jgengine/core 0.7.0 → 0.9.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 (329) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +2 -0
  3. package/dist/ai/flock.d.ts +36 -0
  4. package/dist/ai/flock.js +182 -0
  5. package/dist/ai/groupAssist.d.ts +19 -0
  6. package/dist/ai/groupAssist.js +59 -0
  7. package/dist/ai/laneSelect.d.ts +21 -0
  8. package/dist/ai/laneSelect.js +37 -0
  9. package/dist/ai/mobBrain.d.ts +68 -0
  10. package/dist/ai/mobBrain.js +115 -0
  11. package/dist/ai/spawnDirector.d.ts +5 -0
  12. package/dist/ai/spawnDirector.js +14 -2
  13. package/dist/ai/threat.d.ts +2 -0
  14. package/dist/ai/threat.js +34 -0
  15. package/dist/anim/easing.d.ts +14 -0
  16. package/dist/anim/easing.js +55 -0
  17. package/dist/behaviour/behaviour.d.ts +136 -0
  18. package/dist/behaviour/behaviour.js +388 -0
  19. package/dist/cards/cardPile.d.ts +1 -0
  20. package/dist/cards/cardPile.js +12 -3
  21. package/dist/cartridge/runtime.d.ts +15 -0
  22. package/dist/cartridge/runtime.js +556 -0
  23. package/dist/cartridge/spec.d.ts +188 -0
  24. package/dist/cartridge/spec.js +19 -0
  25. package/dist/cartridge/validate.d.ts +2 -0
  26. package/dist/cartridge/validate.js +78 -0
  27. package/dist/combat/abilityKit.d.ts +22 -2
  28. package/dist/combat/abilityKit.js +72 -13
  29. package/dist/combat/castRunner.d.ts +40 -0
  30. package/dist/combat/castRunner.js +56 -0
  31. package/dist/combat/comboPoints.d.ts +14 -0
  32. package/dist/combat/comboPoints.js +52 -0
  33. package/dist/combat/defensiveWindow.d.ts +1 -1
  34. package/dist/combat/defensiveWindow.js +5 -3
  35. package/dist/combat/effects.d.ts +1 -1
  36. package/dist/combat/effects.js +9 -6
  37. package/dist/combat/projectiles.d.ts +64 -6
  38. package/dist/combat/projectiles.js +232 -74
  39. package/dist/combat/resistance.d.ts +16 -4
  40. package/dist/combat/resistance.js +43 -7
  41. package/dist/combat/resourcePool.d.ts +20 -0
  42. package/dist/combat/resourcePool.js +55 -0
  43. package/dist/combat/shotOrigin.d.ts +40 -0
  44. package/dist/combat/shotOrigin.js +57 -0
  45. package/dist/combat/telegraph.d.ts +30 -1
  46. package/dist/combat/telegraph.js +61 -0
  47. package/dist/commands/commandRegistry.d.ts +1 -1
  48. package/dist/commands/commandRegistry.js +2 -1
  49. package/dist/crafting/recipe.js +4 -1
  50. package/dist/data/dataSource.d.ts +26 -0
  51. package/dist/data/dataSource.js +93 -0
  52. package/dist/data/devProxy.d.ts +11 -0
  53. package/dist/data/devProxy.js +38 -0
  54. package/dist/data/fetchJson.d.ts +20 -0
  55. package/dist/data/fetchJson.js +43 -0
  56. package/dist/data/jsonDataSource.d.ts +4 -0
  57. package/dist/data/jsonDataSource.js +6 -0
  58. package/dist/devtools/devtools.d.ts +205 -0
  59. package/dist/devtools/devtools.js +832 -0
  60. package/dist/devtools/transformTunables.d.ts +14 -0
  61. package/dist/devtools/transformTunables.js +38 -0
  62. package/dist/devtools/tunableSchema.d.ts +140 -0
  63. package/dist/devtools/tunableSchema.js +476 -0
  64. package/dist/faction/factions.d.ts +33 -0
  65. package/dist/faction/factions.js +76 -0
  66. package/dist/faction/reputation.d.ts +32 -0
  67. package/dist/faction/reputation.js +82 -0
  68. package/dist/game/chat.d.ts +68 -0
  69. package/dist/game/chat.js +173 -0
  70. package/dist/game/chatFilter.d.ts +15 -0
  71. package/dist/game/chatFilter.js +55 -0
  72. package/dist/game/controlGate.d.ts +4 -0
  73. package/dist/game/controlGate.js +7 -0
  74. package/dist/game/defineGame.d.ts +10 -2
  75. package/dist/game/defineGame.js +2 -1
  76. package/dist/game/events.d.ts +35 -0
  77. package/dist/game/gamePhase.d.ts +20 -0
  78. package/dist/game/gamePhase.js +21 -0
  79. package/dist/game/levelSequence.d.ts +44 -0
  80. package/dist/game/levelSequence.js +78 -0
  81. package/dist/game/lootTable.d.ts +10 -1
  82. package/dist/game/lootTable.js +27 -7
  83. package/dist/game/playableGame.d.ts +286 -6
  84. package/dist/game/playableGame.js +6 -1
  85. package/dist/game/progression.d.ts +1 -0
  86. package/dist/game/progression.js +23 -2
  87. package/dist/game/quest.d.ts +7 -3
  88. package/dist/game/quest.js +17 -8
  89. package/dist/game/race.d.ts +44 -1
  90. package/dist/game/race.js +116 -11
  91. package/dist/game/recordBook.d.ts +29 -0
  92. package/dist/game/recordBook.js +86 -0
  93. package/dist/game/social.d.ts +42 -0
  94. package/dist/game/social.js +122 -0
  95. package/dist/game/spawnPoints.d.ts +19 -0
  96. package/dist/game/spawnPoints.js +23 -0
  97. package/dist/game/talents.d.ts +47 -0
  98. package/dist/game/talents.js +140 -0
  99. package/dist/game/worldItem.d.ts +3 -0
  100. package/dist/game/worldItem.js +12 -6
  101. package/dist/index.d.ts +1 -0
  102. package/dist/index.js +1 -0
  103. package/dist/input/actionBindings.d.ts +10 -0
  104. package/dist/input/actionBindings.js +14 -0
  105. package/dist/input/axisInput.d.ts +26 -3
  106. package/dist/input/axisInput.js +44 -4
  107. package/dist/input/bindingOverrides.d.ts +25 -0
  108. package/dist/input/bindingOverrides.js +95 -0
  109. package/dist/input/gestureSurface.d.ts +31 -0
  110. package/dist/input/gestureSurface.js +120 -0
  111. package/dist/input/pointer.d.ts +45 -0
  112. package/dist/input/pointer.js +48 -0
  113. package/dist/input/pointerAxis.d.ts +30 -0
  114. package/dist/input/pointerAxis.js +31 -0
  115. package/dist/input/touchScheme.d.ts +89 -0
  116. package/dist/input/touchScheme.js +153 -0
  117. package/dist/item/affix.d.ts +1 -1
  118. package/dist/item/affix.js +1 -19
  119. package/dist/meta/changelog.d.ts +1 -1
  120. package/dist/meta/changelog.js +59 -1
  121. package/dist/movement/dash.d.ts +3 -0
  122. package/dist/movement/dash.js +19 -5
  123. package/dist/movement/glideModel.d.ts +44 -0
  124. package/dist/movement/glideModel.js +54 -0
  125. package/dist/movement/grappleSwing.d.ts +34 -0
  126. package/dist/movement/grappleSwing.js +56 -0
  127. package/dist/movement/leaderTrail.d.ts +29 -0
  128. package/dist/movement/leaderTrail.js +67 -0
  129. package/dist/movement/movementModel.d.ts +36 -3
  130. package/dist/movement/movementModel.js +81 -5
  131. package/dist/movement/steering.d.ts +27 -0
  132. package/dist/movement/steering.js +40 -0
  133. package/dist/movement/voxelController.d.ts +61 -0
  134. package/dist/movement/voxelController.js +229 -0
  135. package/dist/multiplayer/chatContract.d.ts +42 -0
  136. package/dist/multiplayer/chatContract.js +57 -0
  137. package/dist/multiplayer/feedWriteGate.d.ts +10 -0
  138. package/dist/multiplayer/feedWriteGate.js +12 -0
  139. package/dist/multiplayer/identity.d.ts +13 -0
  140. package/dist/multiplayer/identity.js +20 -0
  141. package/dist/multiplayer/poseSyncGate.d.ts +1 -0
  142. package/dist/multiplayer/poseSyncGate.js +13 -1
  143. package/dist/multiplayer/presenceModel.d.ts +17 -2
  144. package/dist/multiplayer/presenceModel.js +30 -4
  145. package/dist/multiplayer/voiceContract.d.ts +44 -0
  146. package/dist/multiplayer/voiceContract.js +108 -0
  147. package/dist/nav/corridors.d.ts +30 -0
  148. package/dist/nav/corridors.js +69 -0
  149. package/dist/nav/navConstrain.d.ts +14 -0
  150. package/dist/nav/navConstrain.js +26 -0
  151. package/dist/nav/navFromEnvironment.d.ts +11 -0
  152. package/dist/nav/navFromEnvironment.js +16 -0
  153. package/dist/nav/navGrid.d.ts +10 -0
  154. package/dist/nav/navGrid.js +48 -5
  155. package/dist/nav/pathFollow.d.ts +4 -2
  156. package/dist/nav/pathFollow.js +5 -3
  157. package/dist/nav/railGraph.d.ts +66 -0
  158. package/dist/nav/railGraph.js +129 -0
  159. package/dist/nav/timetable.d.ts +41 -0
  160. package/dist/nav/timetable.js +94 -0
  161. package/dist/physics/ballisticSweep.d.ts +18 -0
  162. package/dist/physics/ballisticSweep.js +49 -0
  163. package/dist/physics/buoyancy.d.ts +6 -0
  164. package/dist/physics/buoyancy.js +14 -0
  165. package/dist/physics/flowTube.d.ts +32 -0
  166. package/dist/physics/flowTube.js +64 -0
  167. package/dist/physics/forceVolume.d.ts +27 -0
  168. package/dist/physics/forceVolume.js +43 -5
  169. package/dist/physics/kinematicVehicle.d.ts +52 -0
  170. package/dist/physics/kinematicVehicle.js +81 -0
  171. package/dist/physics/physicsWorld.d.ts +54 -8
  172. package/dist/physics/physicsWorld.js +400 -79
  173. package/dist/physics/ragdoll.js +1 -1
  174. package/dist/physics/spatialGrid.d.ts +1 -1
  175. package/dist/physics/spatialGrid.js +5 -0
  176. package/dist/physics/vehicleBody.d.ts +6 -0
  177. package/dist/physics/vehicleBody.js +18 -18
  178. package/dist/puzzle/cellGrid.d.ts +26 -0
  179. package/dist/puzzle/cellGrid.js +110 -0
  180. package/dist/puzzle/fallingPiece.d.ts +24 -0
  181. package/dist/puzzle/fallingPiece.js +51 -0
  182. package/dist/random/nameGen.d.ts +18 -0
  183. package/dist/random/nameGen.js +35 -0
  184. package/dist/random/rng.d.ts +2 -0
  185. package/dist/random/rng.js +23 -0
  186. package/dist/random/seedLink.d.ts +5 -0
  187. package/dist/random/seedLink.js +35 -0
  188. package/dist/runtime/adapter.d.ts +35 -0
  189. package/dist/runtime/adapter.js +37 -1
  190. package/dist/runtime/cameraDirector.d.ts +15 -0
  191. package/dist/runtime/cameraDirector.js +31 -0
  192. package/dist/runtime/commandRunner.d.ts +2 -2
  193. package/dist/runtime/commandRunner.js +2 -2
  194. package/dist/runtime/gameContext.d.ts +65 -2
  195. package/dist/runtime/gameContext.js +280 -9
  196. package/dist/runtime/gameRuntime.d.ts +5 -1
  197. package/dist/runtime/gameRuntime.js +13 -12
  198. package/dist/runtime/hostPersistence.d.ts +30 -1
  199. package/dist/runtime/hostPersistence.js +70 -5
  200. package/dist/runtime/inputSnapshot.d.ts +12 -0
  201. package/dist/runtime/inputSnapshot.js +17 -0
  202. package/dist/runtime/motionIntents.d.ts +33 -0
  203. package/dist/runtime/motionIntents.js +43 -0
  204. package/dist/runtime/snapshot.d.ts +1 -0
  205. package/dist/runtime/snapshot.js +1 -1
  206. package/dist/runtime/transport.d.ts +32 -0
  207. package/dist/runtime/visibility.d.ts +74 -0
  208. package/dist/runtime/visibility.js +127 -0
  209. package/dist/scene/colliders.d.ts +44 -0
  210. package/dist/scene/colliders.js +77 -0
  211. package/dist/scene/entityStore.d.ts +30 -5
  212. package/dist/scene/entityStore.js +83 -13
  213. package/dist/scene/movementSpeed.d.ts +35 -0
  214. package/dist/scene/movementSpeed.js +20 -0
  215. package/dist/scene/objectQuery.d.ts +29 -0
  216. package/dist/scene/objectQuery.js +140 -0
  217. package/dist/scene/objectStore.d.ts +13 -0
  218. package/dist/scene/objectStore.js +130 -6
  219. package/dist/scene/paintLayer.d.ts +15 -0
  220. package/dist/scene/paintLayer.js +53 -0
  221. package/dist/scene/sceneRaycast.d.ts +80 -0
  222. package/dist/scene/sceneRaycast.js +284 -0
  223. package/dist/scene/spatial.d.ts +17 -1
  224. package/dist/scene/spatial.js +140 -17
  225. package/dist/sensor/concealment.d.ts +21 -0
  226. package/dist/sensor/concealment.js +63 -0
  227. package/dist/sensor/freezeMonitor.d.ts +17 -0
  228. package/dist/sensor/freezeMonitor.js +35 -0
  229. package/dist/sensor/recordingBuffer.d.ts +6 -0
  230. package/dist/sensor/recordingBuffer.js +7 -0
  231. package/dist/sensor/replayLoop.d.ts +39 -0
  232. package/dist/sensor/replayLoop.js +64 -0
  233. package/dist/sensor/visionCone.d.ts +31 -0
  234. package/dist/sensor/visionCone.js +72 -0
  235. package/dist/session/extraction.d.ts +2 -2
  236. package/dist/session/extraction.js +13 -2
  237. package/dist/session/roles.d.ts +6 -0
  238. package/dist/session/roles.js +32 -0
  239. package/dist/session/roundState.d.ts +41 -17
  240. package/dist/session/roundState.js +67 -38
  241. package/dist/settings/settingsModel.d.ts +97 -0
  242. package/dist/settings/settingsModel.js +100 -0
  243. package/dist/stats/statModifiers.d.ts +5 -2
  244. package/dist/stats/statModifiers.js +32 -14
  245. package/dist/store/observableKeyedStore.js +8 -2
  246. package/dist/survival/decayMeter.d.ts +1 -1
  247. package/dist/survival/decayMeter.js +0 -1
  248. package/dist/tactics/fallingGrid.d.ts +62 -0
  249. package/dist/tactics/fallingGrid.js +158 -0
  250. package/dist/tactics/tacticalGrid.d.ts +11 -0
  251. package/dist/tactics/tacticalGrid.js +17 -0
  252. package/dist/time/beatClock.d.ts +18 -0
  253. package/dist/time/beatClock.js +24 -0
  254. package/dist/time/idleProgress.d.ts +29 -0
  255. package/dist/time/idleProgress.js +31 -0
  256. package/dist/time/serverTick.d.ts +7 -1
  257. package/dist/time/serverTick.js +27 -4
  258. package/dist/time/simClock.d.ts +25 -1
  259. package/dist/time/simClock.js +28 -3
  260. package/dist/time/stateSchedule.d.ts +57 -0
  261. package/dist/time/stateSchedule.js +121 -0
  262. package/dist/turn/commit.d.ts +2 -2
  263. package/dist/turn/commit.js +2 -2
  264. package/dist/turn/intent.d.ts +14 -0
  265. package/dist/turn/intent.js +29 -0
  266. package/dist/turn/turnLoop.d.ts +2 -0
  267. package/dist/turn/turnLoop.js +8 -1
  268. package/dist/ui/hudLayout.d.ts +58 -0
  269. package/dist/ui/hudLayout.js +207 -0
  270. package/dist/ui/hudScale.d.ts +44 -0
  271. package/dist/ui/hudScale.js +55 -0
  272. package/dist/visibility/assetStreaming.d.ts +66 -0
  273. package/dist/visibility/assetStreaming.js +201 -0
  274. package/dist/visibility/bounds.d.ts +66 -0
  275. package/dist/visibility/bounds.js +113 -0
  276. package/dist/visibility/camera.d.ts +21 -0
  277. package/dist/visibility/camera.js +3 -0
  278. package/dist/visibility/config.d.ts +21 -0
  279. package/dist/visibility/config.js +1 -0
  280. package/dist/visibility/diagnostics.d.ts +26 -0
  281. package/dist/visibility/diagnostics.js +36 -0
  282. package/dist/visibility/distance.d.ts +11 -0
  283. package/dist/visibility/distance.js +30 -0
  284. package/dist/visibility/frustum.d.ts +47 -0
  285. package/dist/visibility/frustum.js +186 -0
  286. package/dist/visibility/occlusion.d.ts +36 -0
  287. package/dist/visibility/occlusion.js +45 -0
  288. package/dist/visibility/settings.d.ts +75 -0
  289. package/dist/visibility/settings.js +49 -0
  290. package/dist/visibility/simulationCulling.d.ts +34 -0
  291. package/dist/visibility/simulationCulling.js +36 -0
  292. package/dist/visibility/spatialIndex.d.ts +45 -0
  293. package/dist/visibility/spatialIndex.js +198 -0
  294. package/dist/visibility/visibilitySystem.d.ts +90 -0
  295. package/dist/visibility/visibilitySystem.js +257 -0
  296. package/dist/world/buildings.d.ts +6 -0
  297. package/dist/world/buildings.js +152 -0
  298. package/dist/world/carve.d.ts +7 -2
  299. package/dist/world/carve.js +17 -7
  300. package/dist/world/cellStates.d.ts +45 -0
  301. package/dist/world/cellStates.js +103 -0
  302. package/dist/world/environmentSummary.d.ts +61 -0
  303. package/dist/world/environmentSummary.js +138 -0
  304. package/dist/world/features.d.ts +138 -9
  305. package/dist/world/features.js +73 -2
  306. package/dist/world/gridInstances.d.ts +12 -0
  307. package/dist/world/gridInstances.js +160 -0
  308. package/dist/world/lod.d.ts +17 -0
  309. package/dist/world/lod.js +66 -0
  310. package/dist/world/mapLayers.d.ts +60 -0
  311. package/dist/world/mapLayers.js +44 -0
  312. package/dist/world/massing.d.ts +45 -0
  313. package/dist/world/massing.js +581 -0
  314. package/dist/world/minimap.d.ts +13 -1
  315. package/dist/world/minimap.js +21 -2
  316. package/dist/world/placementController.d.ts +22 -1
  317. package/dist/world/placementController.js +56 -0
  318. package/dist/world/terrain.d.ts +62 -1
  319. package/dist/world/terrain.js +253 -9
  320. package/dist/world/voxelField.d.ts +54 -0
  321. package/dist/world/voxelField.js +241 -0
  322. package/dist/world/water.d.ts +7 -0
  323. package/dist/world/water.js +19 -4
  324. package/dist/world/weather.d.ts +3 -18
  325. package/dist/world/weather.js +4 -17
  326. package/dist/world/windZones.d.ts +47 -0
  327. package/dist/world/windZones.js +59 -0
  328. package/llms.txt +4983 -0
  329. package/package.json +14 -3
@@ -0,0 +1,20 @@
1
+ import type { GameContext } from "../runtime/gameContext.js";
2
+ /**
3
+ * Canonical run phase every game moves through. `menu` (title/main menu),
4
+ * `playing` (live), `paused` (mid-run pause), `ended` (win/lose/results).
5
+ * Touch controls are shown only while `playing`; menus and results never paint
6
+ * the touch dock over themselves.
7
+ */
8
+ export type GamePhase = "menu" | "playing" | "paused" | "ended";
9
+ export declare const GAME_PHASE_STORE_KEY = "jg.phase";
10
+ /**
11
+ * Set the current phase. Publishes it to `ctx.game.store` (React reads it via
12
+ * `useGamePhase`) and gates the shell's on-screen touch controls in one call —
13
+ * `playing` shows them, every other phase hides them. This is the whole
14
+ * "main menu shouldn't show touch controls" wiring: call it once per phase
15
+ * transition and the dock follows.
16
+ */
17
+ export declare function setGamePhase(ctx: GameContext, phase: GamePhase): void;
18
+ /** Current phase; defaults to `playing` when unset so always-live games need no wiring. */
19
+ export declare function gamePhase(ctx: GameContext): GamePhase;
20
+ export declare function isPlaying(ctx: GameContext): boolean;
@@ -0,0 +1,21 @@
1
+ import { setPlayControlsActive } from "./controlGate.js";
2
+ export const GAME_PHASE_STORE_KEY = "jg.phase";
3
+ /**
4
+ * Set the current phase. Publishes it to `ctx.game.store` (React reads it via
5
+ * `useGamePhase`) and gates the shell's on-screen touch controls in one call —
6
+ * `playing` shows them, every other phase hides them. This is the whole
7
+ * "main menu shouldn't show touch controls" wiring: call it once per phase
8
+ * transition and the dock follows.
9
+ */
10
+ export function setGamePhase(ctx, phase) {
11
+ ctx.game.store.set(GAME_PHASE_STORE_KEY, phase);
12
+ setPlayControlsActive(ctx, phase === "playing");
13
+ }
14
+ /** Current phase; defaults to `playing` when unset so always-live games need no wiring. */
15
+ export function gamePhase(ctx) {
16
+ const value = ctx.game.store.get(GAME_PHASE_STORE_KEY);
17
+ return value === "menu" || value === "paused" || value === "ended" ? value : "playing";
18
+ }
19
+ export function isPlaying(ctx) {
20
+ return gamePhase(ctx) === "playing";
21
+ }
@@ -0,0 +1,44 @@
1
+ export interface LevelDescriptor<TLevelConfig> {
2
+ id: string;
3
+ config: TLevelConfig;
4
+ }
5
+ export interface LevelSequenceConfig<TLevelConfig> {
6
+ levels: readonly LevelDescriptor<TLevelConfig>[];
7
+ /** Extra attempts granted after the first per level. Defaults to 0 (a single attempt, no retries). */
8
+ retriesPerLevel?: number;
9
+ }
10
+ export type LevelSequenceStatus = "idle" | "playing" | "cleared" | "failed" | "complete";
11
+ export interface CurrentLevel<TLevelConfig> {
12
+ id: string;
13
+ index: number;
14
+ config: TLevelConfig;
15
+ attempt: number;
16
+ }
17
+ export interface LevelSequenceProgress {
18
+ index: number;
19
+ total: number;
20
+ cleared: readonly string[];
21
+ }
22
+ export interface LevelSequence<TLevelConfig> {
23
+ current(): CurrentLevel<TLevelConfig> | null;
24
+ status(): LevelSequenceStatus;
25
+ /** Enters level 0. A no-op on an empty level list, which leaves `status()` at "idle" forever. */
26
+ start(): void;
27
+ /** Marks the current level cleared; call `advance()` to move to the next one. */
28
+ clear(): void;
29
+ /** Consumes an attempt on the current level. Returns "retry" while attempts remain, else "failed". */
30
+ fail(): "retry" | "failed";
31
+ /** Restarts the current level after a "retry"-eligible failure. Returns false if there was nothing to retry. */
32
+ retry(): boolean;
33
+ /** Moves past a cleared level, or to "complete" if it was the last one. Returns false unless status is "cleared". */
34
+ advance(): boolean;
35
+ reset(): void;
36
+ progress(): LevelSequenceProgress;
37
+ }
38
+ /**
39
+ * A pure, deterministic level campaign: an ordered list of levels, each with its own opaque config, played
40
+ * through a `start` → (`clear` → `advance`)* → `complete` happy path, with `fail`/`retry` handling per-level
41
+ * attempts. Mirrors the reducer style of `game/race.ts` and `ai/spawnDirector.ts` — no I/O, no timers, just
42
+ * state transitions driven by the caller.
43
+ */
44
+ export declare function createLevelSequence<TLevelConfig>(config: LevelSequenceConfig<TLevelConfig>): LevelSequence<TLevelConfig>;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * A pure, deterministic level campaign: an ordered list of levels, each with its own opaque config, played
3
+ * through a `start` → (`clear` → `advance`)* → `complete` happy path, with `fail`/`retry` handling per-level
4
+ * attempts. Mirrors the reducer style of `game/race.ts` and `ai/spawnDirector.ts` — no I/O, no timers, just
5
+ * state transitions driven by the caller.
6
+ */
7
+ export function createLevelSequence(config) {
8
+ const levels = config.levels;
9
+ const maxAttempts = 1 + Math.max(0, Math.floor(config.retriesPerLevel ?? 0));
10
+ let index = 0;
11
+ let attempt = 1;
12
+ let status = "idle";
13
+ const clearedIds = [];
14
+ function current() {
15
+ if (status === "idle" || status === "complete")
16
+ return null;
17
+ const level = levels[index];
18
+ if (level === undefined)
19
+ return null;
20
+ return { id: level.id, index, config: level.config, attempt };
21
+ }
22
+ return {
23
+ current,
24
+ status: () => status,
25
+ start() {
26
+ if (levels.length === 0)
27
+ return;
28
+ index = 0;
29
+ attempt = 1;
30
+ clearedIds.length = 0;
31
+ status = "playing";
32
+ },
33
+ clear() {
34
+ if (status !== "playing" || current() === null)
35
+ return;
36
+ clearedIds.push(levels[index].id);
37
+ status = "cleared";
38
+ },
39
+ fail() {
40
+ if (status !== "playing" || current() === null)
41
+ return "failed";
42
+ status = "failed";
43
+ return attempt < maxAttempts ? "retry" : "failed";
44
+ },
45
+ retry() {
46
+ if (status !== "failed" || attempt >= maxAttempts)
47
+ return false;
48
+ attempt += 1;
49
+ status = "playing";
50
+ return true;
51
+ },
52
+ advance() {
53
+ if (status !== "cleared")
54
+ return false;
55
+ if (index >= levels.length - 1) {
56
+ status = "complete";
57
+ return true;
58
+ }
59
+ index += 1;
60
+ attempt = 1;
61
+ status = "playing";
62
+ return true;
63
+ },
64
+ reset() {
65
+ index = 0;
66
+ attempt = 1;
67
+ clearedIds.length = 0;
68
+ status = "idle";
69
+ },
70
+ progress() {
71
+ return {
72
+ index: status === "complete" ? levels.length : index,
73
+ total: levels.length,
74
+ cleared: [...clearedIds],
75
+ };
76
+ },
77
+ };
78
+ }
@@ -2,11 +2,20 @@ export interface LootEntry {
2
2
  item?: string;
3
3
  currency?: string;
4
4
  count: number | [number, number];
5
- weight: number;
5
+ /** Relative pick weight — required in `"weighted"` mode, forbidden in `"independent"` mode. */
6
+ weight?: number;
7
+ /** Per-entry drop probability in `(0, 1]` — required in `"independent"` mode, forbidden in `"weighted"` mode. */
8
+ chance?: number;
6
9
  }
7
10
  export interface LootTableDef {
8
11
  id: string;
9
12
  rolls?: number;
13
+ /**
14
+ * `"weighted"` (default): each roll picks exactly one entry by relative `weight`.
15
+ * `"independent"`: each roll gives every entry its own `chance` — classic drop lists
16
+ * where a kill can yield several items (or nothing) without filler entries.
17
+ */
18
+ mode?: "weighted" | "independent";
10
19
  entries: LootEntry[];
11
20
  }
12
21
  export interface Drop {
@@ -1,11 +1,23 @@
1
- function assertValidEntry(entry) {
1
+ function assertValidEntry(entry, mode) {
2
2
  const hasItem = entry.item !== undefined;
3
3
  const hasCurrency = entry.currency !== undefined;
4
4
  if (hasItem === hasCurrency) {
5
5
  throw new Error("loot entry must have exactly one of item or currency");
6
6
  }
7
- if (!(entry.weight > 0)) {
8
- throw new Error(`loot entry weight must be positive, got ${entry.weight}`);
7
+ if (mode === "weighted") {
8
+ if (entry.chance !== undefined) {
9
+ throw new Error("loot entry chance is only valid in independent mode");
10
+ }
11
+ if (entry.weight === undefined || !(entry.weight > 0)) {
12
+ throw new Error(`loot entry weight must be positive, got ${entry.weight}`);
13
+ }
14
+ return;
15
+ }
16
+ if (entry.weight !== undefined) {
17
+ throw new Error("loot entry weight is only valid in weighted mode");
18
+ }
19
+ if (entry.chance === undefined || !(entry.chance > 0) || entry.chance > 1) {
20
+ throw new Error(`loot entry chance must be in (0, 1], got ${entry.chance}`);
9
21
  }
10
22
  }
11
23
  function assertValidDef(def) {
@@ -13,7 +25,7 @@ function assertValidDef(def) {
13
25
  throw new Error(`loot table "${def.id}" must have at least one entry`);
14
26
  }
15
27
  for (const entry of def.entries)
16
- assertValidEntry(entry);
28
+ assertValidEntry(entry, def.mode ?? "weighted");
17
29
  }
18
30
  function resolveCount(count, rng) {
19
31
  if (typeof count === "number")
@@ -22,10 +34,10 @@ function resolveCount(count, rng) {
22
34
  return min + Math.floor(rng() * (max - min + 1));
23
35
  }
24
36
  function pickEntry(entries, rng) {
25
- const total = entries.reduce((sum, entry) => sum + entry.weight, 0);
37
+ const total = entries.reduce((sum, entry) => sum + (entry.weight ?? 0), 0);
26
38
  let roll = rng() * total;
27
39
  for (const entry of entries) {
28
- roll -= entry.weight;
40
+ roll -= entry.weight ?? 0;
29
41
  if (roll < 0)
30
42
  return entry;
31
43
  }
@@ -56,7 +68,15 @@ export function createLootRegistry() {
56
68
  const rolls = table.rolls ?? 1;
57
69
  const drops = [];
58
70
  for (let i = 0; i < rolls; i++) {
59
- drops.push(rollEntry(pickEntry(table.entries, rng), rng));
71
+ if (table.mode === "independent") {
72
+ for (const entry of table.entries) {
73
+ if (rng() < (entry.chance ?? 0))
74
+ drops.push(rollEntry(entry, rng));
75
+ }
76
+ }
77
+ else {
78
+ drops.push(rollEntry(pickEntry(table.entries, rng), rng));
79
+ }
60
80
  }
61
81
  return drops;
62
82
  },
@@ -1,7 +1,12 @@
1
1
  import type { AudioBusDef, SoundDef } from "../audio/audioFalloff.js";
2
+ import type { TouchControlsConfig } from "../input/touchScheme.js";
3
+ import type { GameSettingsConfig } from "../settings/settingsModel.js";
4
+ import type { HudPlatform, HudViewportConfig } from "../ui/hudScale.js";
2
5
  import type { PositionedPrompt } from "../interaction/proximityPrompt.js";
3
- import type { GameContext, GameContextContent } from "../runtime/gameContext.js";
6
+ import type { CatalogEntityRole, GameContext, GameContextContent } from "../runtime/gameContext.js";
4
7
  import type { ModelDims } from "../scene/assetCatalog.js";
8
+ import type { SkyEnvironmentConfig } from "../world/features.js";
9
+ import type { VisibilityConfig } from "../visibility/config.js";
5
10
  import type { GameDefinition, GameLoop } from "./defineGame.js";
6
11
  import type { LootFilterRule } from "./lootFilter.js";
7
12
  import type { RarityStyle } from "./worldItem.js";
@@ -25,6 +30,8 @@ export interface PointerConfig {
25
30
  grabWorldItems?: boolean;
26
31
  /** Press the bound `ping` action → `worldHit()` → run this command with `{ point, entity, object, normal }` (contextual ping, #94). */
27
32
  pingCommand?: string;
33
+ /** Right-click runs this command with `{ point, entity, object, aim }` when neither `orderCommand` nor `contextMenu` claims the click (#164.4). */
34
+ secondaryCommand?: string;
28
35
  }
29
36
  export interface CameraFollowState {
30
37
  entityId: string;
@@ -65,13 +72,33 @@ export interface FirstPersonCameraConfig {
65
72
  * - `lockOn` — yaw bound to the player→target vector; move axis becomes strafe.
66
73
  * - `chase` — speed-reactive vehicle chase (speed→FOV, spring arm, shake) + cockpit/hood/rear views.
67
74
  * - `observer` — detached spectator/photo cam bound to any entity or fixed point; never reads player input.
75
+ * - `turntable` — slow auto-orbit of a fixed point: a rotating display stand for a scene. The friendly, flat spelling of `observer`'s point-orbit mode; providing `camera.turntable` selects it without an explicit `rig`.
76
+ * - `sideScroll` — fixed lateral follow (2.5D platformer/beat-'em-up side view); reads no player input.
77
+ * - `inspection` — model-viewer rig (#207.7): left-drag orbit, middle/right-drag pan, scroll zoom toward a configurable anchor; orbits a fixed point, reads no player/entity input.
78
+ * - `none` — no camera rig is mounted; use for HUD-only presentations or a game that manages its own camera.
68
79
  */
69
- export type CameraRigKind = "orbit" | "first" | "topDown" | "rts" | "shoulder" | "lockOn" | "chase" | "observer";
80
+ export type CameraRigKind = "orbit" | "first" | "topDown" | "rts" | "shoulder" | "lockOn" | "chase" | "observer" | "turntable" | "sideScroll" | "inspection" | "none";
81
+ /** Canvas camera projection. "orthographic" renders a flat 2D-style view (side-scrollers, falling-block puzzles) — pair with `rig: "sideScroll"`; default "perspective". */
82
+ export type CameraProjection = "perspective" | "orthographic";
83
+ /** Fixed lateral 2.5D follow (side-on platformer cam): the camera sits perpendicular to the travel axis, tracks the followed entity, and never reads player look input. */
84
+ export interface SideScrollCameraConfig {
85
+ /** World axis the action travels along; the camera watches from the perpendicular side. Default "x". */
86
+ axis?: "x" | "z";
87
+ /** Camera distance from the followed entity along the perpendicular axis. Default 10. */
88
+ distance?: number;
89
+ /** Camera height above the followed entity. Default 3. */
90
+ height?: number;
91
+ /** Height of the look point above the entity. Default 1. */
92
+ lookHeight?: number;
93
+ /** Per-second smoothing factor for the follow (0 hard-locks). Default 8. */
94
+ followSmoothing?: number;
95
+ fov?: number;
96
+ }
70
97
  /** Fixed top-down / isometric rig (#23) — height/pitch/yaw + decoupled follow. */
71
98
  export interface TopDownCameraConfig {
72
99
  /** Camera height above the follow point. Default 18. */
73
100
  height?: number;
74
- /** Look-down angle in radians (0 = straight down, PI/2 = level). Default ~1.02 (~58°, ARPG iso). */
101
+ /** Elevation of the camera above the ground plane in radians (PI/2 = straight down, near 0 = grazing; values below 0.05 clamp to 0.05). Default ~1.02 (~58°, ARPG iso). */
75
102
  pitch?: number;
76
103
  /** Fixed azimuth in radians; ~PI/4 reads as isometric, 0 as straight top-down. Default PI/4. */
77
104
  yaw?: number;
@@ -113,6 +140,8 @@ export interface RtsCameraConfig extends TopDownCameraConfig {
113
140
  x?: number;
114
141
  z?: number;
115
142
  };
143
+ /** Read WASD/arrow pan, edge-scroll, Q/E rotate, and wheel zoom. `false` sits static at the start/bounds position — a backdrop camera, no input at all. Default true. */
144
+ pan?: boolean;
116
145
  }
117
146
  /** Over-the-shoulder combat rig (#25) — offset, ADS, shoulder swap, decoupled reticle. */
118
147
  export interface ShoulderCameraConfig {
@@ -178,6 +207,17 @@ export interface ChaseCameraConfig {
178
207
  };
179
208
  /** Procedural shake amplitude per unit of speed (adds to the trauma channel). Default 0.0. */
180
209
  shakePerSpeed?: number;
210
+ /** Velocity-lead / predictive follow (#286.9): the rig aims `time` seconds ahead of the target along its velocity, clamped to `max` world units (default 4). */
211
+ lead?: {
212
+ time: number;
213
+ max?: number;
214
+ };
215
+ /** Roll into turns (#286.10): radians of camera roll per rad/s of the target's yaw rate, clamped to `max` (default 0.35), exponentially smoothed by `damping` (default 8). */
216
+ bank?: {
217
+ perYawRate: number;
218
+ max?: number;
219
+ damping?: number;
220
+ };
181
221
  /** Which view to mount. Default "chase". */
182
222
  view?: ChaseView;
183
223
  /** Local offset for cockpit/hood/rear seats (relative to the vehicle, +z forward). */
@@ -225,6 +265,70 @@ export interface ObserverCameraConfig {
225
265
  startAngle?: number;
226
266
  fov?: number;
227
267
  }
268
+ /**
269
+ * Turntable / showcase rig — slowly auto-orbits a fixed world point (no player
270
+ * input), the way a museum turntable rotates an object on display. A flat,
271
+ * self-describing spelling of `observer`'s point-orbit mode: `target` names the
272
+ * point directly instead of `bind: { kind: "point", position }`. Set
273
+ * `camera.turntable` and the rig is inferred — you don't also write `rig`.
274
+ */
275
+ export interface TurntableCameraConfig {
276
+ /** World point the camera circles. Default the world origin. */
277
+ target?: {
278
+ x: number;
279
+ y: number;
280
+ z: number;
281
+ };
282
+ /** Orbit radius from the target. Default 8. */
283
+ distance?: number;
284
+ /** Camera height above the target. Default 3. */
285
+ height?: number;
286
+ /** Height of the look point above the target. Default 1.2. */
287
+ lookHeight?: number;
288
+ /** Radians/second of rotation; 0 holds a fixed angle. Default 0.2. */
289
+ orbitSpeed?: number;
290
+ /** Starting angle in radians. Default 0. */
291
+ startAngle?: number;
292
+ fov?: number;
293
+ }
294
+ /**
295
+ * How scroll-zoom re-anchors the view for the inspection rig (#207.7):
296
+ * - `target` — dolly toward the orbit target (classic OrbitControls behavior).
297
+ * - `cursor` — dolly toward the point under the pointer.
298
+ * - `center` — dolly toward the viewport center; equivalent to `target` for an
299
+ * OrbitControls-driven rig, since the camera always faces `target` and that
300
+ * point already projects to the exact center of the viewport.
301
+ */
302
+ export type InspectionZoomAnchor = "target" | "cursor" | "center";
303
+ /** Model-viewer / inspection rig (#207.7) — orbit + pan + anchored zoom around a fixed point, never reads player input. */
304
+ export interface InspectionCameraConfig {
305
+ /** Where scroll-zoom re-anchors the view. Default "target". */
306
+ anchor?: InspectionZoomAnchor;
307
+ /** Orbit target (the point orbited/panned around). Default origin. */
308
+ target?: {
309
+ x?: number;
310
+ y?: number;
311
+ z?: number;
312
+ };
313
+ /** Camera distance from `target` used to seed the initial camera position when `initialPosition` is unset. Default 6. */
314
+ initialDistance?: number;
315
+ /** Explicit starting camera world position; overrides `initialDistance` when set. */
316
+ initialPosition?: {
317
+ x?: number;
318
+ y?: number;
319
+ z?: number;
320
+ };
321
+ minDistance?: number;
322
+ maxDistance?: number;
323
+ /** Vertical orbit clamp (radians). Unset allows a full pole-to-pole orbit, unlike the tighter default on the classic orbit rig. */
324
+ minPolarAngle?: number;
325
+ maxPolarAngle?: number;
326
+ /** Middle-mouse/right-drag pan. Default true for this rig. */
327
+ pan?: boolean;
328
+ rotateSpeed?: number;
329
+ zoomSpeed?: number;
330
+ dampingFactor?: number;
331
+ }
228
332
  /** One stop on a scripted camera path (#29). */
229
333
  export interface CameraKeyframe {
230
334
  position: {
@@ -262,9 +366,39 @@ export interface CameraShakeConfig {
262
366
  /** Noise frequency (Hz) of the shake oscillation. Default 24. */
263
367
  frequency?: number;
264
368
  }
369
+ export declare const CAMERA_FRUSTUM_DEFAULTS: {
370
+ readonly fov: 55;
371
+ readonly near: 0.1;
372
+ readonly far: 300;
373
+ readonly zoom: 50;
374
+ };
375
+ /** Player-facing FOV preference applied across every perspective camera rig. Orthographic projections ignore it. */
376
+ export interface PlayerFovConfig {
377
+ /** Inclusive lower bound for the slider and persisted values. Default 40. */
378
+ min?: number;
379
+ /** Inclusive upper bound for the slider and persisted values. Default 120. */
380
+ max?: number;
381
+ /** Initial FOV when nothing is persisted. Defaults to `frustum.fov` or 55. */
382
+ default?: number;
383
+ /** Persist the preference in localStorage across reloads. Default true. */
384
+ persist?: boolean;
385
+ /** Show the shell FOV slider. Default true for perspective cameras; forced off for orthographic. */
386
+ control?: boolean;
387
+ }
265
388
  export interface GameCameraConfig {
266
389
  /** Selects the rig. Overrides `perspective`; leave unset to fall back to `perspective`. */
267
390
  rig?: CameraRigKind;
391
+ /** Canvas camera projection. "orthographic" renders a flat 2D-style view (side-scrollers, falling-block puzzles) — pair with `rig: "sideScroll"`; default perspective. */
392
+ projection?: CameraProjection;
393
+ /** Render frustum overrides applied to the canvas camera. `far` defaults to 300 — raise it for worlds whose content spans more than a few hundred units, or distant scenery silently clips. `zoom` is the orthographic zoom in canvas pixels per world unit, read only when `projection` is "orthographic"; default 50. */
394
+ frustum?: {
395
+ fov?: number;
396
+ near?: number;
397
+ far?: number;
398
+ zoom?: number;
399
+ };
400
+ /** Universal player FOV preference (slider + persistence) for perspective rigs. Ignored when `projection` is `"orthographic"`. */
401
+ playerFov?: PlayerFovConfig;
268
402
  /** "third" mounts the orbit camera (default); "first" mounts pointer-lock mouse-look. Shorthand for `rig: "orbit" | "first"`. */
269
403
  perspective?: "third" | "first";
270
404
  /** First-person tuning; only read when the rig resolves to "first". */
@@ -281,6 +415,12 @@ export interface GameCameraConfig {
281
415
  chase?: ChaseCameraConfig;
282
416
  /** Detached spectator/photo cam tuning (#120); read when `rig: "observer"`. */
283
417
  observer?: ObserverCameraConfig;
418
+ /** Turntable / showcase tuning; setting this selects the `turntable` rig on its own. */
419
+ turntable?: TurntableCameraConfig;
420
+ /** Fixed side-on follow tuning; read when `rig: "sideScroll"`. */
421
+ sideScroll?: SideScrollCameraConfig;
422
+ /** Model-viewer / inspection tuning (#207.7); read when `rig: "inspection"`. */
423
+ inspection?: InspectionCameraConfig;
284
424
  /** Camera-shake / trauma channel defaults (#28); read by every rig. */
285
425
  shake?: CameraShakeConfig;
286
426
  /** Scripted keyframe path (#29); when set, plays over the active rig. */
@@ -331,6 +471,47 @@ export interface WorldItemRenderConfig {
331
471
  pickupRadius?: number;
332
472
  /** Beam height above the item's ground position. Default 2.5. */
333
473
  beamHeight?: number;
474
+ /** Walk-over collection: the shell grants the nearest dropped item within this radius of the local player each frame (Minecraft-style pickup). `true` uses `pickupRadius`. Omitted/false leaves pickup to `pointer.grabWorldItems` clicks. */
475
+ autoPickup?: boolean | {
476
+ radius?: number;
477
+ };
478
+ }
479
+ /**
480
+ * Player-vs-world collision for the first-person controller. Without this the
481
+ * shell keeps the player on flat ground at y=0. With `voxel: true` the shell
482
+ * resolves the player as a box against placed scene objects (each treated as a
483
+ * solid unit cell), so they stand on blocks, fall into holes, and are stopped by
484
+ * walls — the controller a block-building/mining game needs.
485
+ */
486
+ export interface VoxelCollisionConfig {
487
+ voxel: true;
488
+ /** Half the player box width on each horizontal axis. Default 0.3. */
489
+ halfWidth?: number;
490
+ /** Player box height from the feet. Default 1.8. */
491
+ height?: number;
492
+ /** Tallest ledge walked up without jumping. Default 0.6. */
493
+ stepHeight?: number;
494
+ }
495
+ /** Rig playback for a `ModelConfig`'s GLTF animation clips — looping idles, one-shots, and held poses. */
496
+ export interface ModelAnimationConfig {
497
+ /** Clip name to play; defaults to the GLB's first clip. */
498
+ clip?: string;
499
+ /** Loop the clip. Default true. */
500
+ loop?: boolean;
501
+ /** Playback rate multiplier. Default 1. */
502
+ timeScale?: number;
503
+ /** Hold the rig on a fixed frame instead of advancing it each tick. */
504
+ paused?: boolean;
505
+ /** Seek the clip to this time in seconds; combine with `paused: true` to hold a specific pose ("pose library" usage). */
506
+ time?: number;
507
+ }
508
+ /** Per-entity PBR material override (#151.3) applied to every `MeshStandardMaterial` in the model's cloned scene graph. */
509
+ export interface ModelMaterialOverride {
510
+ color?: string;
511
+ metalness?: number;
512
+ roughness?: number;
513
+ emissive?: string;
514
+ emissiveIntensity?: number;
334
515
  }
335
516
  export interface ModelConfig {
336
517
  url: string;
@@ -340,35 +521,119 @@ export interface ModelConfig {
340
521
  anchor?: "center" | "origin";
341
522
  /** Measured footprint/center/minY; supplied automatically when the model resolves through an `@jgengine/assets` catalog. Required for `anchor: "center"` to take effect. */
342
523
  dims?: ModelDims;
524
+ /** Per-entity PBR tint/finish override (#151.3); cloned onto each `MeshStandardMaterial` in the model so shared GLTF caches stay untouched. */
525
+ material?: ModelMaterialOverride;
526
+ /** Plays a GLTF animation clip on the model when the source has any (skinned or not); omit to render the rig's bind pose. */
527
+ animation?: ModelAnimationConfig;
528
+ }
529
+ export interface ObjectStyle {
530
+ color?: string;
531
+ opacity?: number;
532
+ hidden?: boolean;
533
+ }
534
+ export interface AmbientLightingConfig {
535
+ color?: string;
536
+ intensity?: number;
537
+ }
538
+ export interface DirectionalLightingConfig {
539
+ color?: string;
540
+ intensity?: number;
541
+ position: readonly [number, number, number];
542
+ castShadow?: boolean;
543
+ }
544
+ export interface HemisphereLightingConfig {
545
+ skyColor?: string;
546
+ groundColor?: string;
547
+ intensity?: number;
548
+ }
549
+ /** Declarative lighting replacing the shell's hardcoded ambient/directional default (#207.5); mounts regardless of world kind, only when supplied. */
550
+ export interface LightingConfig {
551
+ ambient?: AmbientLightingConfig;
552
+ directional?: readonly DirectionalLightingConfig[];
553
+ hemisphere?: HemisphereLightingConfig;
554
+ }
555
+ export interface BackdropFogConfig {
556
+ color?: string;
557
+ near?: number;
558
+ far?: number;
559
+ /** Exponential (`FogExp2`) falloff instead of linear near/far; when set, `near`/`far` are ignored. */
560
+ density?: number;
561
+ }
562
+ /** Generic sky/background/fog for ANY world kind, including a custom `environment` component (#207.6). */
563
+ export interface BackdropConfig {
564
+ background?: string;
565
+ sky?: SkyEnvironmentConfig;
566
+ fog?: BackdropFogConfig;
567
+ }
568
+ /** Movement-control levers for the shell-driven local player walk controller. */
569
+ export interface PlayerMovementConfig {
570
+ /** "free" (default) moves camera-relative across the plane; "axis" locks travel to one world axis; "grid" snaps each committed position to cell centers. */
571
+ mode?: "free" | "axis" | "grid";
572
+ /** World axis for mode "axis". Default "x". */
573
+ axis?: "x" | "z";
574
+ /** Cell size for mode "grid". Default 1. */
575
+ cellSize?: number;
576
+ /** Collide the walking player against placed scene objects (unit-box AABBs) even without `collision.voxel`. Default false. */
577
+ collideObjects?: boolean;
578
+ /** Intercepts each frame's resolved position before the pose commits (and before onTick): return a replacement [x,y,z] to constrain or redirect the step, or nothing to accept it. */
579
+ beforeCommit?: (frame: MovementCommitFrame) => readonly [number, number, number] | undefined | void;
580
+ /** Gates the built-in sprint (`runSpeedMultiplier`) behind live game state — a stamina stat, an encumbrance check (#282.3). Called each frame while sprint is held; `false` walks. */
581
+ canSprint?: (ctx: GameContext) => boolean;
582
+ }
583
+ /** One frame's movement resolution handed to `PlayerMovementConfig.beforeCommit`. */
584
+ export interface MovementCommitFrame {
585
+ entityId: string;
586
+ current: readonly [number, number, number];
587
+ next: readonly [number, number, number];
588
+ dt: number;
589
+ /** The live game context (#282.2) — movement rules read stores/stats directly instead of module-level bridge objects. */
590
+ ctx: GameContext;
343
591
  }
344
- export interface PlayableGame<TUi = unknown, TWorldOverlay = unknown, TRenderEntity = never> {
592
+ export interface PlayableGame<TUi = unknown, TWorldOverlay = unknown, TRenderEntity = never, TRenderObject = never> {
345
593
  game: GameDefinition;
346
594
  content: GameContextContent;
347
595
  loop: Required<GameLoop<GameContext>>;
348
596
  GameUI: TUi;
597
+ /** Which shell mount to use. Default `"3d"` (canvas, camera rig, pointer, world rendering). `"hud"` mounts no 3D canvas, camera rig, or pointer — the game is `GameUI` plus the command/input loop, for board/card/menu games. */
598
+ presentation?: "3d" | "hud";
349
599
  /** Optional canvas-layer VFX component (e.g. traveling projectiles). */
350
600
  WorldOverlay?: TWorldOverlay;
351
- /** Replaces the default demo backdrop (ground + grid + rocks) with the game's own scene — ground, sky, structures. Camera, input, HUD, entity rendering, and the loop stay shell-provided; supply your world without forking the shell. */
601
+ /** Replaces the default demo backdrop (ground + grid + rocks) with the game's own scene — ground, sky, structures. Camera, input, HUD, entity rendering, and the loop stay shell-provided; supply your world without forking the shell. When unset and `game.world` is an `environment()` descriptor, the shell auto-renders that world here — no manual wiring needed. */
352
602
  environment?: TWorldOverlay;
353
603
  /** Per-entity visual override: return your own mesh for an entity and the shell still positions it and drives selection/targeting. Return null/undefined to fall back to model → sprite → primitive. */
354
604
  renderEntity?: TRenderEntity;
605
+ /** Per-object visual override: return your own mesh for a placed scene object and the shell still positions it and drives picking. Return null/undefined to fall back to objectModels → styled box. */
606
+ renderObject?: TRenderObject;
355
607
  /** Billboard sprites keyed by entity kind name; unmatched entities get primitive markers. */
356
608
  entitySprites?: Record<string, EntitySpriteConfig>;
357
609
  /** GLB models keyed by entity kind name; a string resolves as an asset-catalog key via game.assets, a ModelConfig renders directly. Takes priority over sprites, then primitives. */
358
610
  entityModels?: Record<string, string | ModelConfig>;
359
611
  /** GLB models keyed by object catalog id; a string resolves via game.assets, a ModelConfig renders directly. Replaces the colored box when present. */
360
612
  objectModels?: Record<string, string | ModelConfig>;
613
+ /** Styling for the default colored-box object render, keyed by catalog id: color override, opacity (< 1 sets transparent), hidden (skips the mesh but keeps the positioning group + picking). */
614
+ objectStyles?: Record<string, ObjectStyle>;
361
615
  /** Optional scroll-selected hotbar index for primary ability (mouse0). */
362
616
  hotbarSelection?: () => number;
363
617
  /** Positioned proximity prompts for the interact key + HUD; single source shared with useActivePrompt. */
364
618
  prompts?: (ctx: GameContext) => readonly PositionedPrompt[];
365
619
  /** Camera tuning (perspective, orbit, first-person) for the dev game player shell. */
366
620
  camera?: GameCameraConfig;
621
+ /** Cast/receive shadows across the scene (R3F Canvas shadow pass). Default true. */
622
+ shadows?: boolean;
367
623
  /** Pointer-driven input: click-to-move, box-select, right-click verbs, cursor aim (#22/#30/#31). */
368
624
  pointer?: PointerConfig;
369
- /** Opt in to world-space health bars floating over non-local entities that carry the stat. */
625
+ /** Touch controls on coarse-pointer devices. Unset derives a scheme from `input` (virtual joystick for movement actions, on-screen buttons for the rest); a config refines it with gestures and curated buttons; `false` opts out. */
626
+ touch?: TouchControlsConfig | false;
627
+ /** Preferred phone orientation. When a coarse-pointer device is held the other way the shell shows a dismissible rotate hint; purely advisory, never blocks play. */
628
+ orientation?: "landscape" | "portrait";
629
+ /** Where the game is meant to be played. Default `["web", "mobile"]` — design-resolution HUD fit is on for every game: `HudCanvas` auto-scales from `hudFit.designSize` down to the live viewport, so the desktop layout shrinks to fit a phone instead of overflowing it. Declare `["web"]` to opt a desktop-only game out (compact displays fall back to the legacy fixed 0.85 zoom). */
630
+ platforms?: readonly HudPlatform[];
631
+ /** HUD design resolution + scale clamps (default 1600×900, scale 0.4–1). `mobile` overrides tune the phone fit separately — the same resolution system drives desktop UI-scale and phone shrink. */
632
+ hudFit?: HudViewportConfig;
633
+ /** Opt in to world-space health bars floating over non-local entities that carry the stat. `roles` restricts bars to entities whose catalog entry declares one of the given roles. */
370
634
  worldHealthBars?: boolean | {
371
635
  statId?: string;
636
+ roles?: readonly CatalogEntityRole[];
372
637
  };
373
638
  /** Sound catalog + mix buses (music/sfx/ambient/…) the shell's Web Audio glue plays from. Catalog-first — no per-game audio wiring. */
374
639
  audio?: {
@@ -381,4 +646,19 @@ export interface PlayableGame<TUi = unknown, TWorldOverlay = unknown, TRenderEnt
381
646
  objectSounds?: Record<string, string>;
382
647
  /** Rarity render binding + loot filter for dropped-item ground presentation (#32/#33). */
383
648
  worldItem?: WorldItemRenderConfig;
649
+ /** Player-vs-world collision for the first-person controller (block/voxel worlds). Off by default (flat ground). */
650
+ collision?: VoxelCollisionConfig;
651
+ /** Movement-control levers (axis/grid constraints, object collision, pre-commit hook) for the shell-driven walk controller. */
652
+ movement?: PlayerMovementConfig;
653
+ /** Declarative ambient/directional/hemisphere lighting (#207.5); replaces the shell's hardcoded default lights when present, regardless of world kind. */
654
+ lighting?: LightingConfig;
655
+ /** Generic background/sky/fog (#207.6), applied for any world kind including a custom `environment` component. */
656
+ backdrop?: BackdropConfig;
657
+ /** F2 debug overlay (frame/sim timing, logs, backend latency, keybinds, live tunables). On for every game by default; `false` disables the toggle. */
658
+ devtools?: boolean;
659
+ /** Player settings menu. Auto-mounted for every game (Sound / Graphics / Gameplay / Controls); unset uses the defaults, `false` opts out. Add game-specific rows via `extra`, switch overlay/full-page via `mode`, or swap the gear for compact on-screen buttons via `surface: "quick"`. */
660
+ settings?: GameSettingsConfig | false;
661
+ /** Automatic camera frustum + distance culling and asset streaming. On by default with conservative margins; unset uses engine defaults, `{ enabled: false }` opts out. Override bounds/distance/pins per entity kind or object catalog id. */
662
+ visibility?: VisibilityConfig;
384
663
  }
664
+ export declare function worldHealthBarAllowsRole(roles: readonly CatalogEntityRole[] | undefined, role: CatalogEntityRole | undefined): boolean;
@@ -1 +1,6 @@
1
- export {};
1
+ export const CAMERA_FRUSTUM_DEFAULTS = { fov: 55, near: 0.1, far: 300, zoom: 50 };
2
+ export function worldHealthBarAllowsRole(roles, role) {
3
+ if (roles === undefined)
4
+ return true;
5
+ return role !== undefined && roles.includes(role);
6
+ }