@jgengine/core 0.7.0 → 0.8.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.
- package/CHANGELOG.md +47 -0
- package/README.md +2 -0
- package/dist/ai/spawnDirector.d.ts +5 -0
- package/dist/ai/spawnDirector.js +13 -1
- package/dist/anim/easing.d.ts +14 -0
- package/dist/anim/easing.js +55 -0
- package/dist/behaviour/behaviour.d.ts +136 -0
- package/dist/behaviour/behaviour.js +388 -0
- package/dist/cards/cardPile.d.ts +1 -0
- package/dist/cards/cardPile.js +12 -3
- package/dist/combat/abilityKit.d.ts +5 -0
- package/dist/combat/abilityKit.js +10 -0
- package/dist/combat/effects.d.ts +1 -1
- package/dist/combat/effects.js +9 -6
- package/dist/combat/projectiles.d.ts +42 -5
- package/dist/combat/projectiles.js +98 -12
- package/dist/combat/telegraph.d.ts +5 -1
- package/dist/combat/telegraph.js +14 -0
- package/dist/commands/commandRegistry.d.ts +1 -1
- package/dist/commands/commandRegistry.js +2 -1
- package/dist/data/dataSource.d.ts +26 -0
- package/dist/data/dataSource.js +93 -0
- package/dist/data/devProxy.d.ts +11 -0
- package/dist/data/devProxy.js +38 -0
- package/dist/data/fetchJson.d.ts +20 -0
- package/dist/data/fetchJson.js +43 -0
- package/dist/data/jsonDataSource.d.ts +4 -0
- package/dist/data/jsonDataSource.js +6 -0
- package/dist/devtools/devtools.d.ts +157 -0
- package/dist/devtools/devtools.js +484 -0
- package/dist/devtools/transformTunables.d.ts +14 -0
- package/dist/devtools/transformTunables.js +38 -0
- package/dist/game/chat.d.ts +66 -0
- package/dist/game/chat.js +165 -0
- package/dist/game/defineGame.d.ts +8 -2
- package/dist/game/defineGame.js +2 -1
- package/dist/game/events.d.ts +25 -0
- package/dist/game/levelSequence.d.ts +44 -0
- package/dist/game/levelSequence.js +78 -0
- package/dist/game/playableGame.d.ts +237 -6
- package/dist/game/playableGame.js +6 -1
- package/dist/game/progression.d.ts +1 -0
- package/dist/game/progression.js +23 -2
- package/dist/game/race.d.ts +5 -0
- package/dist/game/race.js +20 -0
- package/dist/game/social.d.ts +42 -0
- package/dist/game/social.js +122 -0
- package/dist/game/spawnPoints.d.ts +19 -0
- package/dist/game/spawnPoints.js +23 -0
- package/dist/game/worldItem.d.ts +3 -0
- package/dist/game/worldItem.js +12 -6
- package/dist/input/actionBindings.d.ts +10 -0
- package/dist/input/actionBindings.js +14 -0
- package/dist/input/gestureSurface.d.ts +31 -0
- package/dist/input/gestureSurface.js +120 -0
- package/dist/input/pointer.d.ts +45 -0
- package/dist/input/pointer.js +48 -0
- package/dist/input/touchScheme.d.ts +83 -0
- package/dist/input/touchScheme.js +115 -0
- package/dist/item/affix.d.ts +1 -1
- package/dist/item/affix.js +1 -19
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +37 -1
- package/dist/movement/movementModel.d.ts +36 -3
- package/dist/movement/movementModel.js +81 -5
- package/dist/movement/voxelController.d.ts +61 -0
- package/dist/movement/voxelController.js +229 -0
- package/dist/multiplayer/chatContract.d.ts +42 -0
- package/dist/multiplayer/chatContract.js +57 -0
- package/dist/multiplayer/identity.d.ts +13 -0
- package/dist/multiplayer/identity.js +20 -0
- package/dist/multiplayer/poseSyncGate.d.ts +1 -0
- package/dist/multiplayer/poseSyncGate.js +13 -1
- package/dist/multiplayer/presenceModel.d.ts +3 -0
- package/dist/multiplayer/presenceModel.js +15 -1
- package/dist/multiplayer/voiceContract.d.ts +44 -0
- package/dist/multiplayer/voiceContract.js +108 -0
- package/dist/nav/navConstrain.d.ts +14 -0
- package/dist/nav/navConstrain.js +26 -0
- package/dist/nav/navFromEnvironment.d.ts +11 -0
- package/dist/nav/navFromEnvironment.js +16 -0
- package/dist/nav/navGrid.d.ts +10 -0
- package/dist/nav/navGrid.js +18 -1
- package/dist/nav/pathFollow.d.ts +4 -2
- package/dist/nav/pathFollow.js +5 -3
- package/dist/physics/ballisticSweep.d.ts +18 -0
- package/dist/physics/ballisticSweep.js +49 -0
- package/dist/physics/forceVolume.js +5 -5
- package/dist/physics/physicsWorld.d.ts +41 -2
- package/dist/physics/physicsWorld.js +232 -40
- package/dist/physics/spatialGrid.d.ts +1 -1
- package/dist/physics/spatialGrid.js +5 -0
- package/dist/physics/vehicleBody.d.ts +6 -0
- package/dist/physics/vehicleBody.js +16 -17
- package/dist/puzzle/cellGrid.d.ts +26 -0
- package/dist/puzzle/cellGrid.js +110 -0
- package/dist/puzzle/fallingPiece.d.ts +24 -0
- package/dist/puzzle/fallingPiece.js +51 -0
- package/dist/random/nameGen.d.ts +18 -0
- package/dist/random/nameGen.js +35 -0
- package/dist/random/rng.d.ts +2 -0
- package/dist/random/rng.js +23 -0
- package/dist/runtime/adapter.d.ts +35 -0
- package/dist/runtime/adapter.js +37 -1
- package/dist/runtime/cameraDirector.d.ts +10 -0
- package/dist/runtime/cameraDirector.js +25 -0
- package/dist/runtime/gameContext.d.ts +44 -2
- package/dist/runtime/gameContext.js +100 -4
- package/dist/runtime/hostPersistence.d.ts +21 -0
- package/dist/runtime/hostPersistence.js +50 -0
- package/dist/runtime/inputSnapshot.d.ts +7 -0
- package/dist/runtime/inputSnapshot.js +12 -0
- package/dist/runtime/motionIntents.d.ts +25 -0
- package/dist/runtime/motionIntents.js +25 -0
- package/dist/runtime/transport.d.ts +32 -0
- package/dist/scene/entityStore.d.ts +8 -0
- package/dist/scene/entityStore.js +25 -2
- package/dist/scene/movementSpeed.d.ts +35 -0
- package/dist/scene/movementSpeed.js +20 -0
- package/dist/scene/objectQuery.d.ts +18 -0
- package/dist/scene/objectQuery.js +108 -0
- package/dist/scene/objectStore.d.ts +11 -0
- package/dist/scene/objectStore.js +109 -4
- package/dist/scene/paintLayer.d.ts +15 -0
- package/dist/scene/paintLayer.js +53 -0
- package/dist/scene/spatial.d.ts +21 -0
- package/dist/scene/spatial.js +63 -1
- package/dist/sensor/concealment.d.ts +21 -0
- package/dist/sensor/concealment.js +63 -0
- package/dist/sensor/freezeMonitor.d.ts +17 -0
- package/dist/sensor/freezeMonitor.js +35 -0
- package/dist/session/roles.d.ts +6 -0
- package/dist/session/roles.js +32 -0
- package/dist/session/roundState.d.ts +41 -17
- package/dist/session/roundState.js +67 -38
- package/dist/tactics/fallingGrid.d.ts +62 -0
- package/dist/tactics/fallingGrid.js +158 -0
- package/dist/tactics/tacticalGrid.d.ts +11 -0
- package/dist/tactics/tacticalGrid.js +17 -0
- package/dist/time/simClock.d.ts +25 -1
- package/dist/time/simClock.js +28 -3
- package/dist/turn/intent.d.ts +14 -0
- package/dist/turn/intent.js +29 -0
- package/dist/turn/turnLoop.d.ts +2 -0
- package/dist/turn/turnLoop.js +8 -1
- package/dist/world/environmentSummary.d.ts +53 -0
- package/dist/world/environmentSummary.js +129 -0
- package/dist/world/features.d.ts +87 -6
- package/dist/world/features.js +52 -2
- package/dist/world/gridInstances.d.ts +13 -0
- package/dist/world/gridInstances.js +22 -0
- package/dist/world/placementController.d.ts +22 -1
- package/dist/world/placementController.js +56 -0
- package/dist/world/terrain.d.ts +23 -1
- package/dist/world/terrain.js +64 -1
- package/dist/world/voxelField.d.ts +54 -0
- package/dist/world/voxelField.js +241 -0
- package/llms.txt +4102 -0
- package/package.json +3 -2
package/llms.txt
ADDED
|
@@ -0,0 +1,4102 @@
|
|
|
1
|
+
# @jgengine/core
|
|
2
|
+
> Genre-agnostic TypeScript game engine SDK: game runtime, state store, entity scene, combat, movement, inventory, economy, quests, multiplayer primitives.
|
|
3
|
+
|
|
4
|
+
Version: 0.8.0
|
|
5
|
+
License: AGPL-3.0-only
|
|
6
|
+
Repository: https://github.com/Noisemaker111/jgengine
|
|
7
|
+
Docs: https://jgengine.com
|
|
8
|
+
Imports use deep paths: `@jgengine/core/<path>`.
|
|
9
|
+
|
|
10
|
+
## Exported surface
|
|
11
|
+
|
|
12
|
+
### @jgengine/core/ai/crowd
|
|
13
|
+
|
|
14
|
+
- computeFlowField (function): function computeFlowField(grid: NavGrid, goals: readonly NavPoint[], options: FlowFieldOptions = {}): FlowField
|
|
15
|
+
- createCrowdField (function): function createCrowdField(grid: NavGrid): CrowdField
|
|
16
|
+
- selectPoi (function): function selectPoi(pois: readonly Poi[], from: NavPoint, options: SelectPoiOptions): Poi | null
|
|
17
|
+
- FlowFieldOptions (interface): interface FlowFieldOptions
|
|
18
|
+
- FlowField (interface): interface FlowField
|
|
19
|
+
- CrowdField (interface): interface CrowdField
|
|
20
|
+
- Poi (interface): interface Poi
|
|
21
|
+
- SelectPoiOptions (interface): interface SelectPoiOptions
|
|
22
|
+
|
|
23
|
+
### @jgengine/core/ai/jobBoard
|
|
24
|
+
|
|
25
|
+
- createJobBoard (function): function createJobBoard(): JobBoard
|
|
26
|
+
- JobPhase (type): type JobPhase = "idle" | "travelling" | "working" | "done"
|
|
27
|
+
- JobDef (interface): interface JobDef
|
|
28
|
+
- Job (interface): interface Job
|
|
29
|
+
- JobTickContext (interface): interface JobTickContext
|
|
30
|
+
- JobReport (interface): interface JobReport
|
|
31
|
+
- WorkerState (interface): interface WorkerState
|
|
32
|
+
- JobBoard (interface): interface JobBoard
|
|
33
|
+
|
|
34
|
+
### @jgengine/core/ai/spawnDirector
|
|
35
|
+
|
|
36
|
+
- createSpawnDirectorState (function): function createSpawnDirectorState(config: SpawnDirectorConfig): SpawnDirectorState
|
|
37
|
+
- raiseAlert (function): function raiseAlert(state: SpawnDirectorState, amount: number): SpawnDirectorState
|
|
38
|
+
- advanceWave (function): function advanceWave(config: SpawnDirectorConfig, state: SpawnDirectorState): SpawnDirectorState
|
|
39
|
+
- advanceSpawnDirector (function): function advanceSpawnDirector(config: SpawnDirectorConfig, state: SpawnDirectorState, dt: number, ctx: DirectorContext): DirectorStep
|
|
40
|
+
- pickSpawnPoint (function): function pickSpawnPoint(points: readonly NavPoint[], players: readonly NavPoint[], options: { roll: number; bias?: number }): NavPoint | null
|
|
41
|
+
- SpawnEntry (interface): interface SpawnEntry
|
|
42
|
+
- WaveManifest (interface): interface WaveManifest
|
|
43
|
+
- SpawnDirectorConfig (interface): interface SpawnDirectorConfig
|
|
44
|
+
- SpawnDirectorState (interface): interface SpawnDirectorState
|
|
45
|
+
- DirectorContext (interface): interface DirectorContext
|
|
46
|
+
- SpawnRequest (interface): interface SpawnRequest
|
|
47
|
+
- DirectorStep (interface): interface DirectorStep
|
|
48
|
+
|
|
49
|
+
### @jgengine/core/ai/threat
|
|
50
|
+
|
|
51
|
+
- createThreatTable (function): function createThreatTable(config: ThreatTableConfig = {}): ThreatTable
|
|
52
|
+
- ThreatTableConfig (interface): interface ThreatTableConfig
|
|
53
|
+
- ThreatEntry (interface): interface ThreatEntry
|
|
54
|
+
- HighestThreatOptions (interface): interface HighestThreatOptions
|
|
55
|
+
- ThreatTable (interface): interface ThreatTable
|
|
56
|
+
|
|
57
|
+
### @jgengine/core/anim/easing
|
|
58
|
+
|
|
59
|
+
- lerp (function): function lerp(from: number, to: number, t: number): number
|
|
60
|
+
- clamp01 (function): function clamp01(t: number): number
|
|
61
|
+
- smoothstep (function): function smoothstep(t: number): number
|
|
62
|
+
- easeInQuad (function): function easeInQuad(t: number): number
|
|
63
|
+
- easeOutQuad (function): function easeOutQuad(t: number): number
|
|
64
|
+
- easeInOutQuad (function): function easeInOutQuad(t: number): number
|
|
65
|
+
- easeInCubic (function): function easeInCubic(t: number): number
|
|
66
|
+
- easeOutCubic (function): function easeOutCubic(t: number): number
|
|
67
|
+
- easeInOutCubic (function): function easeInOutCubic(t: number): number
|
|
68
|
+
- easeOutBack (function): function easeOutBack(t: number): number
|
|
69
|
+
- easeOutElastic (function): function easeOutElastic(t: number): number
|
|
70
|
+
- tween (function): function tween(from: number, to: number, t: number, easing: Easing = smoothstep): number
|
|
71
|
+
- timedProgress (function): function timedProgress(startedAt: number, now: number, durationMs: number): number
|
|
72
|
+
- Easing (type): type Easing = (t: number) => number
|
|
73
|
+
|
|
74
|
+
### @jgengine/core/assets/spriteSheet
|
|
75
|
+
|
|
76
|
+
- sanitizeAssetName (function): function sanitizeAssetName(value: string): string
|
|
77
|
+
- normalizeSpriteSheetGridConfig (function): function normalizeSpriteSheetGridConfig(imageWidth: number, imageHeight: number, config: SpriteSheetGridConfig): Required<SpriteSheetGridConfig>
|
|
78
|
+
- buildSpriteSheetGridBoxes (function): function buildSpriteSheetGridBoxes({ imageWidth, imageHeight, config, namePrefix, names, }: { imageWidth: number; imageHeight: number; config: SpriteSheetGridConfig; namePrefix: string; names?: readonly string[]; }): SpriteSheetSliceBox[]
|
|
79
|
+
- buildSpriteSheetManifest (function): function buildSpriteSheetManifest({ sourceName, imageWidth, imageHeight, config, slices, }: { sourceName: string; imageWidth: number; imageHeight: number; config: SpriteSheetGridConfig; slices: SpriteSheetSliceBox[]; }): SpriteSheetManifest
|
|
80
|
+
- SpriteSheetGridConfig (interface): interface SpriteSheetGridConfig
|
|
81
|
+
- SpriteSheetSliceBox (interface): interface SpriteSheetSliceBox
|
|
82
|
+
- SpriteSheetManifest (interface): interface SpriteSheetManifest
|
|
83
|
+
|
|
84
|
+
### @jgengine/core/audio/audioFalloff
|
|
85
|
+
|
|
86
|
+
- computeFalloffGain (function): function computeFalloffGain(distance: number, config: AudioFalloffConfig = {}): number
|
|
87
|
+
- resolveEmitterGain (function): function resolveEmitterGain(distance: number, sound: Pick<SoundDef, "gain" | "positional" | "falloff">, busGain: number): number
|
|
88
|
+
- distance3 (function): function distance3(a: { x: number; y: number; z: number }, b: { x: number; y: number; z: number }): number
|
|
89
|
+
- AudioBusId (type): type AudioBusId = string
|
|
90
|
+
- FalloffCurve (type): type FalloffCurve = "linear" | "inverse" | "none"
|
|
91
|
+
- AudioFalloffConfig (interface): interface AudioFalloffConfig
|
|
92
|
+
- AudioBusDef (interface): interface AudioBusDef
|
|
93
|
+
- SoundDef (interface): interface SoundDef
|
|
94
|
+
|
|
95
|
+
### @jgengine/core/behaviour/behaviour
|
|
96
|
+
|
|
97
|
+
- createBehaviourWorld (function): function createBehaviourWorld(): BehaviourWorld
|
|
98
|
+
- JGEngineRegister (interface): interface JGEngineRegister — Unity-style behaviour lifecycle over a headless id-keyed node tree, ported from vladkrutenyuk/three-start (MIT) and adapted to run without three.js: nodes are plain string ids (use entity instance ids to pair with `entityStore`), and the shell binds render-side hooks separately.
|
|
99
|
+
- RegisterField (type): type RegisterField<TKey extends string, TFallback = unknown> = TKey extends keyof JGEngineRegister ? JGEngineRegister[TKey] : TFallback
|
|
100
|
+
- BehaviourModules (type): type BehaviourModules = RegisterField<"modules", Record<string, BehaviourModule>> — Resolves to the augmented module map when `JGEngineRegister` declares one.
|
|
101
|
+
- Behaviour (class): class Behaviour — Subclass and override the lifecycle hooks. A behaviour only joins the per-frame update dispatch if it actually overrides `onUpdate` (prototype-identity check at each activation), so hook-only behaviours cost nothing per frame.
|
|
102
|
+
- BehaviourModule (class): class BehaviourModule — A world-lifetime service with typed sibling access via `this.modules`. Modules awake and start before any behaviour during `world.start()`, subscribe to update dispatch first (their `onUpdate` fires before every behaviour's), and have no disable/destroy — they live as long as the world.
|
|
103
|
+
- BehaviourWorld (interface): interface BehaviourWorld
|
|
104
|
+
|
|
105
|
+
### @jgengine/core/board/laneBoard
|
|
106
|
+
|
|
107
|
+
- createLaneBoardState (function): function createLaneBoardState<C>(config: LaneBoardConfig<C>): LaneBoardState<C>
|
|
108
|
+
- placeCard (function): function placeCard<C>(state: LaneBoardState<C>, config: LaneBoardConfig<C>, lane: number, side: Side, card: C): LaneResult<C>
|
|
109
|
+
- removeCard (function): function removeCard<C>(state: LaneBoardState<C>, lane: number, side: Side, predicate: (card: C, index: number) => boolean): LaneResult<C>
|
|
110
|
+
- laneAggregate (function): function laneAggregate<C>(state: LaneBoardState<C>, config: LaneBoardConfig<C>, lane: number, side: Side): LaneAggregate
|
|
111
|
+
- laneOutcome (function): function laneOutcome<C>(state: LaneBoardState<C>, config: LaneBoardConfig<C>, lane: number): LaneOutcome
|
|
112
|
+
- boardTotals (function): function boardTotals<C>(state: LaneBoardState<C>, config: LaneBoardConfig<C>): Readonly<Record<Side, number>>
|
|
113
|
+
- lanesWon (function): function lanesWon<C>(state: LaneBoardState<C>, config: LaneBoardConfig<C>): Readonly<Record<Side, number>>
|
|
114
|
+
- createLaneBoard (function): function createLaneBoard<C>(config: LaneBoardConfig<C>): LaneBoard<C>
|
|
115
|
+
- Side (type): type Side = string
|
|
116
|
+
- LaneRuleInput (interface): interface LaneRuleInput<C>
|
|
117
|
+
- LaneRule (interface): interface LaneRule<C>
|
|
118
|
+
- LaneBoardConfig (interface): interface LaneBoardConfig<C>
|
|
119
|
+
- LaneCells (interface): interface LaneCells<C>
|
|
120
|
+
- LaneBoardState (interface): interface LaneBoardState<C>
|
|
121
|
+
- LaneAggregate (interface): interface LaneAggregate
|
|
122
|
+
- LaneOutcome (interface): interface LaneOutcome
|
|
123
|
+
- LaneRejection (type): type LaneRejection = "invalid-lane" | "invalid-side"
|
|
124
|
+
- LanePlaceResult (interface): interface LanePlaceResult<C>
|
|
125
|
+
- LanePlaceRejected (interface): interface LanePlaceRejected
|
|
126
|
+
- LaneResult (type): type LaneResult<C> = LanePlaceResult<C> | LanePlaceRejected
|
|
127
|
+
- LaneBoard (interface): interface LaneBoard<C>
|
|
128
|
+
|
|
129
|
+
### @jgengine/core/board/timelineBoard
|
|
130
|
+
|
|
131
|
+
- createTimelineBoardState (function): function createTimelineBoardState(slots: readonly TimelineSlotConfig[]): TimelineBoardState
|
|
132
|
+
- tickTimeline (function): function tickTimeline(state: TimelineBoardState, dtMs: number): TimelineTickResult
|
|
133
|
+
- setSlotEnabled (function): function setSlotEnabled(state: TimelineBoardState, slotId: string, enabled: boolean): TimelineBoardState
|
|
134
|
+
- resetSlot (function): function resetSlot(state: TimelineBoardState, slotId: string, remainingMs?: number): TimelineBoardState
|
|
135
|
+
- createTimelineBoard (function): function createTimelineBoard(slots: readonly TimelineSlotConfig[]): TimelineBoard
|
|
136
|
+
- TimelineSlotConfig (interface): interface TimelineSlotConfig
|
|
137
|
+
- TimelineSlot (interface): interface TimelineSlot
|
|
138
|
+
- TimelineBoardState (interface): interface TimelineBoardState
|
|
139
|
+
- TimelineFire (interface): interface TimelineFire
|
|
140
|
+
- TimelineTickResult (interface): interface TimelineTickResult
|
|
141
|
+
- TimelineBoard (interface): interface TimelineBoard
|
|
142
|
+
|
|
143
|
+
### @jgengine/core/cards/cardPile
|
|
144
|
+
|
|
145
|
+
- pileRng (function): function pileRng(seed: string | number): () => number
|
|
146
|
+
- shuffleWithRng (function): function shuffleWithRng<T>(values: readonly T[], rng: () => number): T[]
|
|
147
|
+
- createCardPileState (function): function createCardPileState(config: CardPileConfig, initial?: Partial<Record<ZoneName, readonly string[]>>): CardPileState
|
|
148
|
+
- zoneOf (function): function zoneOf(state: CardPileState, cardId: string): ZoneName | null
|
|
149
|
+
- countIn (function): function countIn(state: CardPileState, zone: ZoneName): number
|
|
150
|
+
- peek (function): function peek(state: CardPileState, zone: ZoneName, n = 1): readonly string[]
|
|
151
|
+
- shuffleZone (function): function shuffleZone(state: CardPileState, zone: ZoneName, seed: string | number): CardPileState
|
|
152
|
+
- moveCards (function): function moveCards(state: CardPileState, ids: readonly string[], from: ZoneName, to: ZoneName, position: "top" | "bottom" = "top"): PileResult
|
|
153
|
+
- draw (function): function draw(state: CardPileState, n: number, options: { from: ZoneName; to: ZoneName; handLimit?: number; reshuffleFrom?: ZoneName; seed?: string | number; }): DrawResult
|
|
154
|
+
- createCardPile (function): function createCardPile(config: CardPileConfig, initial?: Partial<Record<ZoneName, readonly string[]>>): CardPile
|
|
155
|
+
- ZoneName (type): type ZoneName = string
|
|
156
|
+
- CardPileConfig (interface): interface CardPileConfig
|
|
157
|
+
- CardPileState (interface): interface CardPileState
|
|
158
|
+
- DrawResult (interface): interface DrawResult
|
|
159
|
+
- PileRejection (type): type PileRejection = | "unknown-zone" | "card-not-in-zone" | "hand-limit" | "empty-source"
|
|
160
|
+
- PileMoveResult (interface): interface PileMoveResult
|
|
161
|
+
- PileMoveRejected (interface): interface PileMoveRejected
|
|
162
|
+
- PileResult (type): type PileResult = PileMoveResult | PileMoveRejected
|
|
163
|
+
- CardPile (interface): interface CardPile
|
|
164
|
+
|
|
165
|
+
### @jgengine/core/cards/modifierPipeline
|
|
166
|
+
|
|
167
|
+
- runPipeline (function): function runPipeline<V>(base: V, modifiers: readonly Modifier<V>[], equals: (a: V, b: V) => boolean = Object.is): PipelineResult<V>
|
|
168
|
+
- createModifierPipeline (function): function createModifierPipeline<V>(initial: readonly Modifier<V>[] = [], equals: (a: V, b: V) => boolean = Object.is): ModifierPipeline<V>
|
|
169
|
+
- Modifier (interface): interface Modifier<V>
|
|
170
|
+
- PipelineContext (interface): interface PipelineContext<V>
|
|
171
|
+
- TraceStep (interface): interface TraceStep<V>
|
|
172
|
+
- PipelineResult (interface): interface PipelineResult<V>
|
|
173
|
+
- ModifierPipeline (interface): interface ModifierPipeline<V>
|
|
174
|
+
|
|
175
|
+
### @jgengine/core/combat/abilityKit
|
|
176
|
+
|
|
177
|
+
- createAbilityKit (function): function createAbilityKit(configs: readonly AbilitySlotConfig[]): AbilityKit
|
|
178
|
+
- AbilityCastType (type): type AbilityCastType = "instant" | "projectile" | "channel" | "targeted"
|
|
179
|
+
- AbilitySlotState (type): type AbilitySlotState = "ready" | "cooldown" | "no-resource" | "just-cast"
|
|
180
|
+
- AbilitySlotConfig (interface): interface AbilitySlotConfig
|
|
181
|
+
- AbilitySlotSnapshot (interface): interface AbilitySlotSnapshot
|
|
182
|
+
- AbilityCastReason (type): type AbilityCastReason = "unknown-slot" | "cooldown" | "no-resource"
|
|
183
|
+
- AbilitySlotRetune (interface): interface AbilitySlotRetune
|
|
184
|
+
- AbilityCastResult (type): type AbilityCastResult = | { ok: true; slot: AbilitySlotSnapshot } | { ok: false; reason: AbilityCastReason; slot: AbilitySlotSnapshot | null }
|
|
185
|
+
- AbilityKit (interface): interface AbilityKit
|
|
186
|
+
|
|
187
|
+
### @jgengine/core/combat/animationState
|
|
188
|
+
|
|
189
|
+
- clipDurationMs (function): function clipDurationMs(clip: AnimationClip): number
|
|
190
|
+
- frameAtMs (function): function frameAtMs(clip: AnimationClip, elapsedMs: number): number
|
|
191
|
+
- phasesAtFrame (function): function phasesAtFrame(clip: AnimationClip, frame: number): FramePhase[]
|
|
192
|
+
- activeRangeAtFrame (function): function activeRangeAtFrame(clip: AnimationClip, frame: number): FrameRange | null
|
|
193
|
+
- createAnimationState (function): function createAnimationState(config: AnimationStateConfig): AnimationState
|
|
194
|
+
- FramePhase (type): type FramePhase = "windup" | "active" | "recovery" | "cancel"
|
|
195
|
+
- FrameRange (interface): interface FrameRange
|
|
196
|
+
- AnimationClip (interface): interface AnimationClip
|
|
197
|
+
- AnimationSnapshot (interface): interface AnimationSnapshot
|
|
198
|
+
- AnimationTickResult (interface): interface AnimationTickResult
|
|
199
|
+
- AnimationState (interface): interface AnimationState
|
|
200
|
+
- AnimationStateConfig (interface): interface AnimationStateConfig
|
|
201
|
+
|
|
202
|
+
### @jgengine/core/combat/attackTags
|
|
203
|
+
|
|
204
|
+
- attackMeta (function): function attackMeta(tags: readonly AttackTag[], extra?: Omit<AttackMeta, "tags">): AttackMeta
|
|
205
|
+
- hasTag (function): function hasTag(meta: AttackMeta, tag: AttackTag): boolean
|
|
206
|
+
- hasAnyTag (function): function hasAnyTag(meta: AttackMeta, tags: readonly AttackTag[]): boolean
|
|
207
|
+
- isBlockable (function): function isBlockable(meta: AttackMeta): boolean
|
|
208
|
+
- isParryable (function): function isParryable(meta: AttackMeta): boolean
|
|
209
|
+
- isDodgeable (function): function isDodgeable(meta: AttackMeta): boolean
|
|
210
|
+
- counters (function): function counters(meta: AttackMeta, move: CounterMove): boolean
|
|
211
|
+
- AttackTag (type): type AttackTag = | "unblockable" | "thrust" | "sweep" | "grab" | "overhead" | "ranged" | "aerial"
|
|
212
|
+
- AttackMeta (interface): interface AttackMeta
|
|
213
|
+
- CounterMove (type): type CounterMove = "mikiri" | "deflect" | "sidestep"
|
|
214
|
+
|
|
215
|
+
### @jgengine/core/combat/breakMeters
|
|
216
|
+
|
|
217
|
+
- createStaggerMeter (function): function createStaggerMeter(config: StaggerMeterConfig): StaggerMeter
|
|
218
|
+
- createBuildupMeter (function): function createBuildupMeter(config: BuildupMeterConfig): BuildupMeter
|
|
219
|
+
- StaggerMeterConfig (interface): interface StaggerMeterConfig
|
|
220
|
+
- StaggerMeter (interface): interface StaggerMeter
|
|
221
|
+
- BuildupMeterConfig (interface): interface BuildupMeterConfig
|
|
222
|
+
- BuildupProc (interface): interface BuildupProc
|
|
223
|
+
- BuildupMeter (interface): interface BuildupMeter
|
|
224
|
+
|
|
225
|
+
### @jgengine/core/combat/comboString
|
|
226
|
+
|
|
227
|
+
- stepById (function): function stepById(combo: ComboString, stepId: string): ComboStep | null
|
|
228
|
+
- advanceCombo (function): function advanceCombo(input: AdvanceComboInput): AdvanceComboResult
|
|
229
|
+
- createComboRunner (function): function createComboRunner(combo: ComboString, anim: AnimationState): ComboRunner
|
|
230
|
+
- ComboStep (interface): interface ComboStep
|
|
231
|
+
- ComboString (interface): interface ComboString
|
|
232
|
+
- AdvanceComboInput (interface): interface AdvanceComboInput
|
|
233
|
+
- AdvanceComboResult (type): type AdvanceComboResult = | { accepted: true; step: ComboStep } | { accepted: false; reason: "unknown-step" | "not-chainable" | "wrong-stance" | "window-closed" }
|
|
234
|
+
- ComboRunner (interface): interface ComboRunner
|
|
235
|
+
|
|
236
|
+
### @jgengine/core/combat/death
|
|
237
|
+
|
|
238
|
+
- normalizeOnDeath (function): function normalizeOnDeath(spec: OnDeathSpec | null | undefined): NormalizedOnDeath
|
|
239
|
+
- createDeathSystem (function): function createDeathSystem(deps: DeathSystemDeps): DeathSystem
|
|
240
|
+
- deathReasonFromEffect (function): function deathReasonFromEffect(ctx: EffectDeathContext): DeathReason
|
|
241
|
+
- DeathReasonKind (type): type DeathReasonKind = DeathReason["kind"]
|
|
242
|
+
- OnDeathDropRule (interface): interface OnDeathDropRule
|
|
243
|
+
- OnDeathCommandRule (interface): interface OnDeathCommandRule
|
|
244
|
+
- DropMode (type): type DropMode = "grant" | "world"
|
|
245
|
+
- OnDeathSpec (interface): interface OnDeathSpec
|
|
246
|
+
- NormalizedOnDeath (interface): interface NormalizedOnDeath
|
|
247
|
+
- DeathIdentity (interface): interface DeathIdentity
|
|
248
|
+
- DeathSystemDeps (interface): interface DeathSystemDeps
|
|
249
|
+
- DeathResolution (type): type DeathResolution = | { status: "resolved"; drops: Drop[]; ranCommand: string | null } | { status: "rejected"; reason: string }
|
|
250
|
+
- DeathSystem (interface): interface DeathSystem
|
|
251
|
+
- EffectDeathContext (interface): interface EffectDeathContext
|
|
252
|
+
|
|
253
|
+
### @jgengine/core/combat/defensiveWindow
|
|
254
|
+
|
|
255
|
+
- windowActiveAt (function): function windowActiveAt(config: DefensiveWindowConfig, elapsedMs: number): boolean
|
|
256
|
+
- iframeActiveAt (function): function iframeActiveAt(config: DefensiveWindowConfig, elapsedMs: number): boolean
|
|
257
|
+
- totalWindowMs (function): function totalWindowMs(config: DefensiveWindowConfig): number
|
|
258
|
+
- resolveDefense (function): function resolveDefense(input: ResolveDefenseInput): DefenseResolution
|
|
259
|
+
- createDefensiveWindow (function): function createDefensiveWindow(config: DefensiveWindowConfig): DefensiveWindow
|
|
260
|
+
- DefenseKind (type): type DefenseKind = "parry" | "block" | "dodge"
|
|
261
|
+
- DefensiveWindowConfig (interface): interface DefensiveWindowConfig
|
|
262
|
+
- DefenseOutcome (type): type DefenseOutcome = "parry" | "block" | "iframe" | "hit"
|
|
263
|
+
- DefenseResolution (interface): interface DefenseResolution
|
|
264
|
+
- ResolveDefenseInput (interface): interface ResolveDefenseInput
|
|
265
|
+
- DefensiveWindow (interface): interface DefensiveWindow
|
|
266
|
+
|
|
267
|
+
### @jgengine/core/combat/downed
|
|
268
|
+
|
|
269
|
+
- createDownedState (function): function createDownedState(config: DownedConfig): DownedState
|
|
270
|
+
- DownedPhase (type): type DownedPhase = "alive" | "downed" | "dead"
|
|
271
|
+
- DownedConfig (interface): interface DownedConfig
|
|
272
|
+
- DownedEntry (interface): interface DownedEntry
|
|
273
|
+
- DownedEventKind (type): type DownedEventKind = | "downed" | "revived" | "reviving" | "died" | "banner.created" | "banner.expired" | "respawned"
|
|
274
|
+
- DownedEvent (interface): interface DownedEvent
|
|
275
|
+
- DownedState (interface): interface DownedState
|
|
276
|
+
|
|
277
|
+
### @jgengine/core/combat/effects
|
|
278
|
+
|
|
279
|
+
- resolveAreaTargets (function): function resolveAreaTargets(spatial: CombatSpatialDeps, input: AreaTargetInput, accept?: (instanceId: string) => boolean): AreaTarget[]
|
|
280
|
+
- createEffectSystem (function): function createEffectSystem(deps: EffectSystemDeps): EffectSystem
|
|
281
|
+
- ReceiveRule (interface): interface ReceiveRule
|
|
282
|
+
- ReceiveMap (type): type ReceiveMap = Record<string, ReceiveRule>
|
|
283
|
+
- EffectVia (interface): interface EffectVia
|
|
284
|
+
- SingleTargetEffectInput (interface): interface SingleTargetEffectInput
|
|
285
|
+
- AreaEffectInput (interface): interface AreaEffectInput
|
|
286
|
+
- EffectInput (type): type EffectInput = SingleTargetEffectInput | AreaEffectInput
|
|
287
|
+
- AppliedPoolDelta (interface): interface AppliedPoolDelta
|
|
288
|
+
- EffectResult (interface): interface EffectResult
|
|
289
|
+
- LethalContext (interface): interface LethalContext
|
|
290
|
+
- CombatSpatialDeps (interface): interface CombatSpatialDeps
|
|
291
|
+
- AreaTargetInput (interface): interface AreaTargetInput
|
|
292
|
+
- AreaTarget (interface): interface AreaTarget
|
|
293
|
+
- EffectSystemDeps (interface): interface EffectSystemDeps
|
|
294
|
+
- EffectSystem (interface): interface EffectSystem
|
|
295
|
+
|
|
296
|
+
### @jgengine/core/combat/hitReaction
|
|
297
|
+
|
|
298
|
+
- resolveHitReaction (function): function resolveHitReaction(config: HitReactionConfig, input: HitReactionInput): HitReaction
|
|
299
|
+
- applyImpulse (function): function applyImpulse(position: EntityPosition, impulse: [number, number, number]): EntityPosition
|
|
300
|
+
- CameraShake (interface): interface CameraShake
|
|
301
|
+
- HitReactionConfig (interface): interface HitReactionConfig
|
|
302
|
+
- HitReactionInput (interface): interface HitReactionInput
|
|
303
|
+
- HitReaction (interface): interface HitReaction
|
|
304
|
+
|
|
305
|
+
### @jgengine/core/combat/projectiles
|
|
306
|
+
|
|
307
|
+
- createProjectileSystem (function): function createProjectileSystem(deps: ProjectileSystemDeps): ProjectileSystem
|
|
308
|
+
- ProjectileShotInput (interface): interface ProjectileShotInput
|
|
309
|
+
- EntityRaycastHit (interface): interface EntityRaycastHit — A raycast hit against a scene entity — the ray reached `instanceId` at `distance` along its length.
|
|
310
|
+
- ObjectRaycastHit (interface): interface ObjectRaycastHit — A raycast hit against a placed scene object's bounding box — see `ProjectileObjectsDeps.halfExtents`.
|
|
311
|
+
- RaycastHit (type): type RaycastHit = EntityRaycastHit | ObjectRaycastHit
|
|
312
|
+
- Raycast (type): type Raycast = (from: string, aim: Aim, range: number) => RaycastHit[]
|
|
313
|
+
- ProjectileSettleReport (interface): interface ProjectileSettleReport
|
|
314
|
+
- ProjectileObjectsDeps (interface): interface ProjectileObjectsDeps — Read-only scene object access for the default `raycast`; matches `ObjectStore.list()`/a catalog lookup structurally.
|
|
315
|
+
- ProjectileSystemDeps (interface): interface ProjectileSystemDeps
|
|
316
|
+
- ProjectileHit (type): type ProjectileHit = | { kind: "entity"; instanceId: string; distance: number } | { kind: "object"; instanceId: string; catalogId: string; distance: number }
|
|
317
|
+
- ProjectilePrediction (interface): interface ProjectilePrediction
|
|
318
|
+
- SettleResult (type): type SettleResult = | { status: "settled"; shotId: string; at: [number, number, number]; hits: EffectResult[] } | { status: "rejected"; shotId: string; reason: string }
|
|
319
|
+
- ProjectileSystem (interface): interface ProjectileSystem
|
|
320
|
+
|
|
321
|
+
### @jgengine/core/combat/resistance
|
|
322
|
+
|
|
323
|
+
- resolveResistance (function): function resolveResistance(matrix: ResistanceMatrix, category: string, targetProperties: readonly string[]): ResistanceResult
|
|
324
|
+
- resistanceScale (function): function resistanceScale(matrix: ResistanceMatrix, category: string, targetProperties: readonly string[]): number
|
|
325
|
+
- ResistVerdict (type): type ResistVerdict = "immune" | "resist" | "normal" | "vulnerable"
|
|
326
|
+
- ResistanceMatrix (interface): interface ResistanceMatrix
|
|
327
|
+
- ResistanceResult (interface): interface ResistanceResult
|
|
328
|
+
|
|
329
|
+
### @jgengine/core/combat/telegraph
|
|
330
|
+
|
|
331
|
+
- telegraphProgress (function): function telegraphProgress(windupMs: number, startedAtMs: number, nowMs: number): number
|
|
332
|
+
- telegraphFired (function): function telegraphFired(windupMs: number, startedAtMs: number, nowMs: number): boolean
|
|
333
|
+
- telegraphTurnProgress (function): function telegraphTurnProgress(config: TelegraphConfig, startedTurn: number, currentTurn: number): number
|
|
334
|
+
- telegraphFiredAtTurn (function): function telegraphFiredAtTurn(config: TelegraphConfig, startedTurn: number, currentTurn: number): boolean
|
|
335
|
+
- telegraphTurnsRemaining (function): function telegraphTurnsRemaining(config: TelegraphConfig, startedTurn: number, currentTurn: number): number
|
|
336
|
+
- pointInTelegraph (function): function pointInTelegraph(config: TelegraphConfig, point: EntityPosition): boolean
|
|
337
|
+
- TelegraphShape (type): type TelegraphShape = | { kind: "circle"; radius: number } | { kind: "ring"; radius: number; innerRadius: number } | { kind: "cone"; radius: number; angle: number } | { kind: "line"; length: number; width: number }
|
|
338
|
+
- TelegraphConfig (interface): interface TelegraphConfig
|
|
339
|
+
|
|
340
|
+
### @jgengine/core/commands/commandRegistry
|
|
341
|
+
|
|
342
|
+
- createCommandRegistry (function): function createCommandRegistry<TState>(): CommandRegistry<TState>
|
|
343
|
+
- CommandRejection (interface): interface CommandRejection
|
|
344
|
+
- CommandDefinition (interface): interface CommandDefinition<TState, TInput>
|
|
345
|
+
- CommandResult (type): type CommandResult<TState> = | { status: "applied"; state: TState } | { status: "rejected"; reason: string } | { status: "unknown-command" }
|
|
346
|
+
- CommandRegistry (interface): interface CommandRegistry<TState>
|
|
347
|
+
|
|
348
|
+
### @jgengine/core/crafting/crop
|
|
349
|
+
|
|
350
|
+
- emptyTile (function): function emptyTile(): CropTileState
|
|
351
|
+
- canTill (function): function canTill(state: CropTileState): boolean
|
|
352
|
+
- tillTile (function): function tillTile(state: CropTileState): CropTileState
|
|
353
|
+
- canPlant (function): function canPlant(state: CropTileState): boolean
|
|
354
|
+
- plantCrop (function): function plantCrop(state: CropTileState, cropId: string): CropTileState
|
|
355
|
+
- waterTile (function): function waterTile(state: CropTileState): CropTileState
|
|
356
|
+
- maturityDays (function): function maturityDays(def: CropDef): number
|
|
357
|
+
- advanceCropDay (function): function advanceCropDay(def: CropDef, state: CropTileState): CropTileState
|
|
358
|
+
- harvestCrop (function): function harvestCrop(def: CropDef, state: CropTileState): HarvestResult
|
|
359
|
+
- tileKey (function): function tileKey(coord: TileCoord): string
|
|
360
|
+
- singleTile (function): function singleTile(): TilePattern
|
|
361
|
+
- squarePattern (function): function squarePattern(radius: number): TilePattern
|
|
362
|
+
- diamondPattern (function): function diamondPattern(radius: number): TilePattern
|
|
363
|
+
- rectPattern (function): function rectPattern(width: number, depth: number): TilePattern
|
|
364
|
+
- patternAt (function): function patternAt(center: TileCoord, pattern: TilePattern): TileCoord[]
|
|
365
|
+
- applyToolToTiles (function): function applyToolToTiles(tiles: ReadonlyMap<string, CropTileState>, center: TileCoord, pattern: TilePattern, apply: (state: CropTileState, coord: TileCoord) => CropTileState, fill: () => CropTileState = emptyTile): ApplyToolResult
|
|
366
|
+
- createDayTicker (function): function createDayTicker(startDay = 0): DayTicker
|
|
367
|
+
- createCropField (function): function createCropField(catalog: CropCatalog = () => null): CropField
|
|
368
|
+
- SoilState (type): type SoilState = "untilled" | "tilled"
|
|
369
|
+
- CropDef (interface): interface CropDef
|
|
370
|
+
- CropTileState (interface): interface CropTileState
|
|
371
|
+
- CropCatalog (type): type CropCatalog = (cropId: string) => CropDef | null
|
|
372
|
+
- HarvestResult (interface): interface HarvestResult
|
|
373
|
+
- TileCoord (type): type TileCoord = readonly [number, number]
|
|
374
|
+
- TilePattern (type): type TilePattern = readonly TileCoord[]
|
|
375
|
+
- ApplyToolResult (interface): interface ApplyToolResult
|
|
376
|
+
- DayTicker (interface): interface DayTicker
|
|
377
|
+
- CropField (interface): interface CropField
|
|
378
|
+
|
|
379
|
+
### @jgengine/core/crafting/production
|
|
380
|
+
|
|
381
|
+
- productionBuilding (function): function productionBuilding(config: ProductionBuildingConfig): ProductionBuildingDef
|
|
382
|
+
- createProductionState (function): function createProductionState(): ProductionState
|
|
383
|
+
- acceptsInput (function): function acceptsInput(def: ProductionBuildingDef, state: ProductionState, itemId: string): boolean
|
|
384
|
+
- feedProduction (function): function feedProduction(def: ProductionBuildingDef, state: ProductionState, itemId: string, count: number): { state: ProductionState; accepted: number }
|
|
385
|
+
- drainOutput (function): function drainOutput(state: ProductionState, itemId: string, count?: number): { state: ProductionState; taken: number }
|
|
386
|
+
- tickProduction (function): function tickProduction(def: ProductionBuildingDef, state: ProductionState, input: ProductionTickInput): ProductionState
|
|
387
|
+
- advanceTransport (function): function advanceTransport(path: TransportPath, items: readonly TransportItem[], dt: number): { items: TransportItem[]; delivered: TransportItem[] }
|
|
388
|
+
- resolvePowerGrid (function): function resolvePowerGrid(supply: number, consumers: readonly PowerConsumer[]): PowerGridResult
|
|
389
|
+
- ProductionBuildingDef (interface): interface ProductionBuildingDef
|
|
390
|
+
- ProductionBuildingConfig (interface): interface ProductionBuildingConfig
|
|
391
|
+
- ItemCounts (type): type ItemCounts = Readonly<Record<string, number>>
|
|
392
|
+
- ProductionState (interface): interface ProductionState
|
|
393
|
+
- ProductionTickInput (interface): interface ProductionTickInput
|
|
394
|
+
- TransportItem (interface): interface TransportItem
|
|
395
|
+
- TransportPath (interface): interface TransportPath
|
|
396
|
+
- PowerConsumer (interface): interface PowerConsumer
|
|
397
|
+
- PowerGridResult (interface): interface PowerGridResult
|
|
398
|
+
|
|
399
|
+
### @jgengine/core/crafting/recipe
|
|
400
|
+
|
|
401
|
+
- craftSeconds (function): function craftSeconds(recipe: RecipeDef): number
|
|
402
|
+
- missingInputs (function): function missingInputs(state: InventoryState, recipe: RecipeDef): RecipeItem[]
|
|
403
|
+
- hasRecipeInputs (function): function hasRecipeInputs(state: InventoryState, recipe: RecipeDef): boolean
|
|
404
|
+
- stationSatisfied (function): function stationSatisfied(recipe: RecipeDef, context: CraftContext): boolean
|
|
405
|
+
- canCraft (function): function canCraft(state: InventoryState, layout: InventoryLayout, traits: ItemTraits, recipe: RecipeDef, context: CraftContext = {}): CraftCheck
|
|
406
|
+
- craft (function): function craft(state: InventoryState, layout: InventoryLayout, traits: ItemTraits, recipe: RecipeDef, context: CraftContext = {}): CraftResult
|
|
407
|
+
- createRecipeGraph (function): function createRecipeGraph(defs: readonly RecipeDef[] = []): RecipeGraph
|
|
408
|
+
- RecipeItem (interface): interface RecipeItem
|
|
409
|
+
- RecipeDef (interface): interface RecipeDef
|
|
410
|
+
- StationInstance (interface): interface StationInstance
|
|
411
|
+
- CraftContext (interface): interface CraftContext
|
|
412
|
+
- CraftRejection (type): type CraftRejection = | { reason: "missing-inputs"; missing: readonly RecipeItem[] } | { reason: "no-station"; station: string } | { reason: "locked"; requires: readonly string[] } | { reason: "no-output-space" }
|
|
413
|
+
- CraftCheck (type): type CraftCheck = { ok: true } | ({ ok: false } & CraftRejection)
|
|
414
|
+
- CraftResult (type): type CraftResult = { status: "ok"; state: InventoryState } | ({ status: "rejected" } & CraftRejection)
|
|
415
|
+
- RecipeGraph (interface): interface RecipeGraph
|
|
416
|
+
|
|
417
|
+
### @jgengine/core/data/dataSource
|
|
418
|
+
|
|
419
|
+
- createDataSource (function): function createDataSource<T>(load: (signal: AbortSignal) => Promise<T>, options: DataSourceOptions = {}): DataSource<T>
|
|
420
|
+
- DataSourceStatus (type): type DataSourceStatus = "idle" | "loading" | "ready" | "error"
|
|
421
|
+
- DataSourceState (interface): interface DataSourceState<T>
|
|
422
|
+
- DataSourceClock (interface): interface DataSourceClock
|
|
423
|
+
- DataSourceOptions (interface): interface DataSourceOptions
|
|
424
|
+
- RefreshOptions (interface): interface RefreshOptions
|
|
425
|
+
- DataSource (interface): interface DataSource<T>
|
|
426
|
+
|
|
427
|
+
### @jgengine/core/data/devProxy
|
|
428
|
+
|
|
429
|
+
- parseDevProxyTable (function): function parseDevProxyTable(raw: string | undefined): DevProxyTable
|
|
430
|
+
- proxiedUrl (function): function proxiedUrl(target: string, options: ProxiedUrlOptions = {}): string
|
|
431
|
+
- DevProxyTable (interface): interface DevProxyTable
|
|
432
|
+
- ProxiedUrlOptions (interface): interface ProxiedUrlOptions
|
|
433
|
+
- DEFAULT_DEV_PROXY_PREFIX (const): const DEFAULT_DEV_PROXY_PREFIX: "/proxy"
|
|
434
|
+
|
|
435
|
+
### @jgengine/core/data/fetchJson
|
|
436
|
+
|
|
437
|
+
- fetchJson (function): function fetchJson<T>(url: string, options: FetchJsonOptions = {}): Promise<T>
|
|
438
|
+
- FetchImpl (type): type FetchImpl = typeof fetch
|
|
439
|
+
- FetchJsonOptions (interface): interface FetchJsonOptions
|
|
440
|
+
- HttpStatusError (class): class HttpStatusError extends Error
|
|
441
|
+
- JsonParseError (class): class JsonParseError extends Error
|
|
442
|
+
|
|
443
|
+
### @jgengine/core/data/jsonDataSource
|
|
444
|
+
|
|
445
|
+
- createJsonDataSource (function): function createJsonDataSource<T>(url: string, options: JsonDataSourceOptions = {}): DataSource<T>
|
|
446
|
+
- JsonDataSourceOptions (type): type JsonDataSourceOptions = DataSourceOptions & Omit<FetchJsonOptions, "signal">
|
|
447
|
+
|
|
448
|
+
### @jgengine/core/devtools/devtools
|
|
449
|
+
|
|
450
|
+
- formatLogMessage (function): function formatLogMessage(args: readonly unknown[]): string
|
|
451
|
+
- createDevtools (function): function createDevtools(): Devtools
|
|
452
|
+
- tunable (function): function tunable<T>(name: string, initial: T, options?: TunableOptions<T>): Tunable<T>
|
|
453
|
+
- snapshotDevtools (function): function snapshotDevtools(): DevtoolsSnapshot
|
|
454
|
+
- instrumentLatency (function): function instrumentLatency<T extends object>(target: T, methods: readonly (keyof T)[], record: (ms: number) => void = devtools.latency.record): T
|
|
455
|
+
- DevtoolsControlKind (type): type DevtoolsControlKind = "slider" | "toggle" | "color" | "select" | "text"
|
|
456
|
+
- TunableOptions (interface): interface TunableOptions<T>
|
|
457
|
+
- DevtoolsControl (interface): interface DevtoolsControl
|
|
458
|
+
- Tunable (interface): interface Tunable<T>
|
|
459
|
+
- FrameStats (interface): interface FrameStats
|
|
460
|
+
- RenderSample (interface): interface RenderSample
|
|
461
|
+
- LatencyStats (interface): interface LatencyStats
|
|
462
|
+
- DevtoolsLogLevel (type): type DevtoolsLogLevel = "log" | "info" | "warn" | "error"
|
|
463
|
+
- DevtoolsLogEntry (interface): interface DevtoolsLogEntry
|
|
464
|
+
- DiscoveredEntry (interface): interface DiscoveredEntry
|
|
465
|
+
- TunableAccessor (interface): interface TunableAccessor
|
|
466
|
+
- DevtoolsOverrides (interface): interface DevtoolsOverrides
|
|
467
|
+
- DevtoolsSnapshot (interface): interface DevtoolsSnapshot
|
|
468
|
+
- Devtools (interface): interface Devtools
|
|
469
|
+
- devtools (const): const devtools: Devtools
|
|
470
|
+
|
|
471
|
+
### @jgengine/core/devtools/transformTunables
|
|
472
|
+
|
|
473
|
+
- transformTunableExports (function): function transformTunableExports(code: string, table: string): TunableTransformResult
|
|
474
|
+
- tunableModuleTable (function): function tunableModuleTable(id: string): string | null
|
|
475
|
+
- tunableDiscoveryPlugin (function): function tunableDiscoveryPlugin(): { name: string; enforce: "pre"; transform(code: string, id: string): { code: string; map: null } | null; }
|
|
476
|
+
- TunableTransformResult (interface): interface TunableTransformResult
|
|
477
|
+
|
|
478
|
+
### @jgengine/core/economy/currency
|
|
479
|
+
|
|
480
|
+
- sanitizeCurrencyAmount (function): function sanitizeCurrencyAmount(amount: number): number
|
|
481
|
+
- formatCurrencyAmount (function): function formatCurrencyAmount(currency: CurrencyDefinition, amount: number): string
|
|
482
|
+
- insufficientCurrencyReason (function): function insufficientCurrencyReason(currency: CurrencyDefinition, needed: number, current: number): string
|
|
483
|
+
- applyCurrencyOperation (function): function applyCurrencyOperation(currency: CurrencyDefinition, current: number, operation: CurrencyOperation, amount: number): CurrencyAdjustment
|
|
484
|
+
- resolveCurrencyDelta (function): function resolveCurrencyDelta(current: number, delta: number): { operation: CurrencyOperation; amount: number } — Deducts clamp to the available balance (a delta can never overdraw); adds apply in full.
|
|
485
|
+
- CurrencyDefinition (interface): interface CurrencyDefinition<TCurrencyId extends string = string>
|
|
486
|
+
- CurrencyOperation (type): type CurrencyOperation = "add" | "deduct"
|
|
487
|
+
- CurrencyAdjustment (type): type CurrencyAdjustment = | { success: true; newBalance: number; appliedDelta: number } | { success: false; reason: string }
|
|
488
|
+
|
|
489
|
+
### @jgengine/core/economy/sharedWallet
|
|
490
|
+
|
|
491
|
+
- scopeKey (function): function scopeKey(scope: WalletScope): string
|
|
492
|
+
- userScope (function): function userScope(userId: string): WalletScope
|
|
493
|
+
- groupScope (function): function groupScope(groupId: string): WalletScope
|
|
494
|
+
- createWalletBook (function): function createWalletBook(): WalletBook
|
|
495
|
+
- balanceIn (function): function balanceIn(book: WalletBook, scope: WalletScope, currency: string): number
|
|
496
|
+
- grantTo (function): function grantTo(book: WalletBook, scope: WalletScope, currency: string, amount: number, by?: string): WalletBook
|
|
497
|
+
- chargeFrom (function): function chargeFrom(book: WalletBook, scope: WalletScope, currency: string, amount: number, by?: string): BookChargeResult
|
|
498
|
+
- contributionOf (function): function contributionOf(book: WalletBook, groupId: string, userId: string): Readonly<Record<string, number>>
|
|
499
|
+
- contributorsOf (function): function contributorsOf(book: WalletBook, groupId: string): string[]
|
|
500
|
+
- WalletScope (type): type WalletScope = | { kind: "user"; userId: string } | { kind: "group"; groupId: string }
|
|
501
|
+
- WalletBook (interface): interface WalletBook
|
|
502
|
+
- BookChargeResult (type): type BookChargeResult = | { status: "ok"; book: WalletBook } | { status: "rejected"; reason: "insufficient-funds" }
|
|
503
|
+
|
|
504
|
+
### @jgengine/core/economy/techTree
|
|
505
|
+
|
|
506
|
+
- techPrerequisitesMet (function): function techPrerequisitesMet(state: TechState, node: TechNodeDef): boolean
|
|
507
|
+
- missingPrerequisites (function): function missingPrerequisites(state: TechState, node: TechNodeDef): string[]
|
|
508
|
+
- canUnlockTech (function): function canUnlockTech(defs: readonly TechNodeDef[], state: TechState, id: string): TechCheck
|
|
509
|
+
- grantTech (function): function grantTech(state: TechState, node: TechNodeDef): string[]
|
|
510
|
+
- availableTech (function): function availableTech(defs: readonly TechNodeDef[], state: TechState): TechNodeDef[]
|
|
511
|
+
- unlockedRecipes (function): function unlockedRecipes(defs: readonly TechNodeDef[], state: TechState): string[]
|
|
512
|
+
- createTechTree (function): function createTechTree(defs: readonly TechNodeDef[] = []): TechTree
|
|
513
|
+
- TechNodeDef (interface): interface TechNodeDef extends UnlockDef
|
|
514
|
+
- TechState (type): type TechState = UnlockState
|
|
515
|
+
- TechRejection (type): type TechRejection = | { reason: "unknown-node" } | { reason: "already-unlocked" } | { reason: "missing-prerequisites"; missing: readonly string[] }
|
|
516
|
+
- TechCheck (type): type TechCheck = { ok: true } | ({ ok: false } & TechRejection)
|
|
517
|
+
- TechTree (interface): interface TechTree
|
|
518
|
+
|
|
519
|
+
### @jgengine/core/economy/wallet
|
|
520
|
+
|
|
521
|
+
- createEmptyWallet (function): function createEmptyWallet(): WalletState
|
|
522
|
+
- balance (function): function balance(state: WalletState, currency: string): number
|
|
523
|
+
- grant (function): function grant(state: WalletState, currency: string, amount: number): WalletState
|
|
524
|
+
- charge (function): function charge(state: WalletState, currency: string, amount: number): ChargeResult
|
|
525
|
+
- canAfford (function): function canAfford(state: WalletState, costs: Readonly<Record<string, number>>): boolean
|
|
526
|
+
- chargeAll (function): function chargeAll(state: WalletState, costs: Readonly<Record<string, number>>): ChargeResult
|
|
527
|
+
- WalletState (interface): interface WalletState
|
|
528
|
+
- ChargeResult (type): type ChargeResult = { status: "ok"; state: WalletState } | { status: "rejected"; reason: "insufficient-funds" }
|
|
529
|
+
|
|
530
|
+
### @jgengine/core/game/chat
|
|
531
|
+
|
|
532
|
+
- whisperChannelId (function): function whisperChannelId(a: string, b: string): string
|
|
533
|
+
- createChatRateLimiter (function): function createChatRateLimiter(limit: ChatRateLimit): ChatRateLimiter
|
|
534
|
+
- createChat (function): function createChat(deps: ChatDeps): Chat
|
|
535
|
+
- ChatChannelKind (type): type ChatChannelKind = "global" | "party" | "proximity"
|
|
536
|
+
- ChatRateLimit (interface): interface ChatRateLimit
|
|
537
|
+
- ChatChannelDef (interface): interface ChatChannelDef
|
|
538
|
+
- ChatMessage (interface): interface ChatMessage
|
|
539
|
+
- ChatRecipients (type): type ChatRecipients = readonly string[] | "all"
|
|
540
|
+
- ChatSendResult (type): type ChatSendResult = | { message: ChatMessage; recipients: ChatRecipients } | { reason: string }
|
|
541
|
+
- ChatSnapshot (interface): interface ChatSnapshot
|
|
542
|
+
- Chat (interface): interface Chat
|
|
543
|
+
- ChatDeps (interface): interface ChatDeps
|
|
544
|
+
- DEFAULT_CHAT_RATE_LIMIT (const): const DEFAULT_CHAT_RATE_LIMIT: ChatRateLimit
|
|
545
|
+
- DEFAULT_CHAT_HISTORY_LIMIT (const): const DEFAULT_CHAT_HISTORY_LIMIT: 100
|
|
546
|
+
- DEFAULT_CHAT_BODY_LENGTH (const): const DEFAULT_CHAT_BODY_LENGTH: 500
|
|
547
|
+
- DEFAULT_PROXIMITY_CHAT_RADIUS (const): const DEFAULT_PROXIMITY_CHAT_RADIUS: 20
|
|
548
|
+
- WHISPER_CHANNEL_PREFIX (const): const WHISPER_CHANNEL_PREFIX: "whisper:"
|
|
549
|
+
- ChatRateLimiter (interface): interface ChatRateLimiter
|
|
550
|
+
|
|
551
|
+
### @jgengine/core/game/cosmetics
|
|
552
|
+
|
|
553
|
+
- createCosmetics (function): function createCosmetics(deps: CosmeticsDeps = {}): Cosmetics
|
|
554
|
+
- CosmeticLoadoutDef (interface): interface CosmeticLoadoutDef
|
|
555
|
+
- CosmeticsChangedEvent (interface): interface CosmeticsChangedEvent
|
|
556
|
+
- CosmeticsEvents (interface): interface CosmeticsEvents
|
|
557
|
+
- CosmeticsDeps (interface): interface CosmeticsDeps
|
|
558
|
+
- Cosmetics (interface): interface Cosmetics
|
|
559
|
+
|
|
560
|
+
### @jgengine/core/game/defineGame
|
|
561
|
+
|
|
562
|
+
- defineGame (function): function defineGame<TAssetRef extends ModelAssetRef, TMultiplayer>(config: GameDefinitionConfig<TAssetRef, TMultiplayer>): GameDefinition<TAssetRef, TMultiplayer>
|
|
563
|
+
- PhysicsConfig (interface): interface PhysicsConfig
|
|
564
|
+
- InventoryDeclaration (interface): interface InventoryDeclaration
|
|
565
|
+
- GameServerConfig (type): type GameServerConfig = "persistent" | { mode: string; [key: string]: unknown }
|
|
566
|
+
- GameLoop (interface): interface GameLoop<TContext = unknown>
|
|
567
|
+
- GameDefinition (interface): interface GameDefinition<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown>
|
|
568
|
+
- GameDefinitionConfig (type): type GameDefinitionConfig<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> = Omit<GameDefinition<TAssetRef, TMultiplayer>, "scene" | "assets"> & { assets?: AssetCatalog<TAssetRef>; }
|
|
569
|
+
|
|
570
|
+
### @jgengine/core/game/events
|
|
571
|
+
|
|
572
|
+
- createGameEvents (function): function createGameEvents<TMap extends GameEventMap = GameEventMap>(): GameEvents<TMap>
|
|
573
|
+
- DeathReason (type): type DeathReason = | { kind: "player_kill"; killerUserId: string; via?: { item?: string } } | { kind: "environment"; source: string } | { kind: "self"; source: string }
|
|
574
|
+
- EntityDiedEvent (interface): interface EntityDiedEvent
|
|
575
|
+
- LootGrantedEvent (interface): interface LootGrantedEvent
|
|
576
|
+
- InventoryAddedEvent (interface): interface InventoryAddedEvent
|
|
577
|
+
- QuestAcceptedEvent (interface): interface QuestAcceptedEvent
|
|
578
|
+
- QuestUpdatedEvent (interface): interface QuestUpdatedEvent
|
|
579
|
+
- QuestCompletedEvent (interface): interface QuestCompletedEvent
|
|
580
|
+
- SocialFriendAddedEvent (interface): interface SocialFriendAddedEvent
|
|
581
|
+
- SocialPartyJoinedEvent (interface): interface SocialPartyJoinedEvent
|
|
582
|
+
- SocialPartyLeftEvent (interface): interface SocialPartyLeftEvent
|
|
583
|
+
- SocialWorldInvitedEvent (interface): interface SocialWorldInvitedEvent
|
|
584
|
+
- SocialWorldAcceptedEvent (interface): interface SocialWorldAcceptedEvent
|
|
585
|
+
- ChatMessageEvent (interface): interface ChatMessageEvent
|
|
586
|
+
- StatLevelUpEvent (interface): interface StatLevelUpEvent
|
|
587
|
+
- EntityFloatTextEvent (interface): interface EntityFloatTextEvent
|
|
588
|
+
- CombatTelegraphEvent (interface): interface CombatTelegraphEvent
|
|
589
|
+
- CombatHitReactionEvent (interface): interface CombatHitReactionEvent
|
|
590
|
+
- ProjectileSettledEvent (interface): interface ProjectileSettledEvent
|
|
591
|
+
- WorldItemDroppedEvent (interface): interface WorldItemDroppedEvent
|
|
592
|
+
- WorldItemPickedUpEvent (interface): interface WorldItemPickedUpEvent
|
|
593
|
+
- CosmeticsChangedEvent (interface): interface CosmeticsChangedEvent
|
|
594
|
+
- EmotePlayedEvent (interface): interface EmotePlayedEvent
|
|
595
|
+
- PossessionSwappedEvent (interface): interface PossessionSwappedEvent
|
|
596
|
+
- FormChangedEvent (interface): interface FormChangedEvent
|
|
597
|
+
- GameEventMap (interface): interface GameEventMap
|
|
598
|
+
- GameEventHandler (type): type GameEventHandler<TPayload> = (payload: TPayload) => void
|
|
599
|
+
- GameEvents (interface): interface GameEvents<TMap extends GameEventMap = GameEventMap>
|
|
600
|
+
|
|
601
|
+
### @jgengine/core/game/feed
|
|
602
|
+
|
|
603
|
+
- appendFeedEntry (function): function appendFeedEntry<T>(buffer: readonly FeedEntry<T>[], entry: FeedEntry<T>, limit: number): FeedEntry<T>[]
|
|
604
|
+
- recentFeedEntries (function): function recentFeedEntries<T>(buffer: readonly FeedEntry<T>[], limit?: number): FeedEntry<T>[]
|
|
605
|
+
- createGameFeed (function): function createGameFeed(options?: GameFeedOptions): GameFeed
|
|
606
|
+
- FeedEntry (interface): interface FeedEntry<T = unknown>
|
|
607
|
+
- GameFeedOptions (interface): interface GameFeedOptions
|
|
608
|
+
- GameFeed (interface): interface GameFeed
|
|
609
|
+
|
|
610
|
+
### @jgengine/core/game/leaderboard
|
|
611
|
+
|
|
612
|
+
- createLeaderboard (function): function createLeaderboard(sink?: { onIncrement?(row: LeaderboardRow): void }): Leaderboard
|
|
613
|
+
- LeaderboardScope (type): type LeaderboardScope = "global" | "server" | "profile"
|
|
614
|
+
- LeaderboardTrackDef (interface): interface LeaderboardTrackDef
|
|
615
|
+
- LeaderboardRow (interface): interface LeaderboardRow
|
|
616
|
+
- IncrementResult (type): type IncrementResult = { status: "ok"; value: number } | { status: "rejected"; reason: "not-tracked" }
|
|
617
|
+
- Leaderboard (interface): interface Leaderboard
|
|
618
|
+
|
|
619
|
+
### @jgengine/core/game/levelSequence
|
|
620
|
+
|
|
621
|
+
- createLevelSequence (function): function createLevelSequence<TLevelConfig>(config: LevelSequenceConfig<TLevelConfig>): LevelSequence<TLevelConfig> — A pure, deterministic level campaign: an ordered list of levels, each with its own opaque config, played through a `start` → (`clear` → `advance`)* → `complete` happy path, with `fail`/`retry` handling per-level attempts. Mirrors the reducer style of `game/race.ts` and `ai/spawnDirector.ts` — no I/O, no timers, just state transitions driven by the caller.
|
|
622
|
+
- LevelDescriptor (interface): interface LevelDescriptor<TLevelConfig>
|
|
623
|
+
- LevelSequenceConfig (interface): interface LevelSequenceConfig<TLevelConfig>
|
|
624
|
+
- LevelSequenceStatus (type): type LevelSequenceStatus = "idle" | "playing" | "cleared" | "failed" | "complete"
|
|
625
|
+
- CurrentLevel (interface): interface CurrentLevel<TLevelConfig>
|
|
626
|
+
- LevelSequenceProgress (interface): interface LevelSequenceProgress
|
|
627
|
+
- LevelSequence (interface): interface LevelSequence<TLevelConfig>
|
|
628
|
+
|
|
629
|
+
### @jgengine/core/game/loadout
|
|
630
|
+
|
|
631
|
+
- createLoadouts (function): function createLoadouts(deps: LoadoutDeps): Loadouts
|
|
632
|
+
- LoadoutItemEntry (interface): interface LoadoutItemEntry
|
|
633
|
+
- LoadoutDef (interface): interface LoadoutDef
|
|
634
|
+
- LoadoutInventoryTransaction (interface): interface LoadoutInventoryTransaction
|
|
635
|
+
- LoadoutDeps (interface): interface LoadoutDeps
|
|
636
|
+
- Loadouts (interface): interface Loadouts
|
|
637
|
+
|
|
638
|
+
### @jgengine/core/game/lootFilter
|
|
639
|
+
|
|
640
|
+
- evaluateLootFilter (function): function evaluateLootFilter(rules: readonly LootFilterRule[], item: LootFilterItem): LootFilterOverride — First matching rule wins (PoE/Last Epoch block semantics) — later rules never override an earlier match. Returns overrides only; fields the rule doesn't set are left for the caller's baseline (rarity style) to fill in.
|
|
641
|
+
- lootFilter (function): function lootFilter(rules: readonly LootFilterRule[]): readonly LootFilterRule[] — Validating factory — rule ids must be unique so authoring mistakes fail loudly.
|
|
642
|
+
- LootFilterItem (interface): interface LootFilterItem
|
|
643
|
+
- LootFilterCondition (interface): interface LootFilterCondition
|
|
644
|
+
- LootFilterRule (interface): interface LootFilterRule
|
|
645
|
+
- LootFilterOverride (interface): interface LootFilterOverride
|
|
646
|
+
|
|
647
|
+
### @jgengine/core/game/lootTable
|
|
648
|
+
|
|
649
|
+
- createLootRegistry (function): function createLootRegistry(): LootRegistry
|
|
650
|
+
- lootTable (function): function lootTable(def: LootTableDef): LootTableDef
|
|
651
|
+
- grantDrops (function): function grantDrops(drops: Drop[], appliers: { putItem: (itemId: string, count: number) => unknown; grantCurrency: (currencyId: string, amount: number) => unknown; }): void
|
|
652
|
+
- LootEntry (interface): interface LootEntry
|
|
653
|
+
- LootTableDef (interface): interface LootTableDef
|
|
654
|
+
- Drop (interface): interface Drop
|
|
655
|
+
- LootRegistry (interface): interface LootRegistry
|
|
656
|
+
|
|
657
|
+
### @jgengine/core/game/ping
|
|
658
|
+
|
|
659
|
+
- classifyPing (function): function classifyPing(hit: PointerHit, deps: PingClassifyDeps = {}, options: PingClassifyOptions = {}): PingCategory — Classify what a pointer/aim ray hit into a ping category. Entity hits resolve by catalog role (hostile → enemy, else location); object hits by an optional catalog category tag; open ground is a location ping.
|
|
660
|
+
- createPingSystem (function): function createPingSystem(deps: PingSystemDeps): PingSystem
|
|
661
|
+
- PingCategory (type): type PingCategory = string
|
|
662
|
+
- PingCategoryDef (interface): interface PingCategoryDef
|
|
663
|
+
- DEFAULT_PING_CATEGORIES (const): const DEFAULT_PING_CATEGORIES: Record<PingCategory, PingCategoryDef> — Content-agnostic default ping wheel: enemy / loot / location / danger.
|
|
664
|
+
- PING_FEED_ACTION (const): const PING_FEED_ACTION: "party.ping"
|
|
665
|
+
- PingClassifyDeps (interface): interface PingClassifyDeps
|
|
666
|
+
- PingClassifyOptions (interface): interface PingClassifyOptions
|
|
667
|
+
- PingPayload (interface): interface PingPayload
|
|
668
|
+
- PingFeedSink (interface): interface PingFeedSink
|
|
669
|
+
- PingParty (interface): interface PingParty
|
|
670
|
+
- PingSystemDeps (interface): interface PingSystemDeps
|
|
671
|
+
- PingSystem (interface): interface PingSystem
|
|
672
|
+
|
|
673
|
+
### @jgengine/core/game/playableGame
|
|
674
|
+
|
|
675
|
+
- worldHealthBarAllowsRole (function): function worldHealthBarAllowsRole(roles: readonly CatalogEntityRole[] | undefined, role: CatalogEntityRole | undefined): boolean
|
|
676
|
+
- PointerConfig (interface): interface PointerConfig
|
|
677
|
+
- CameraFollowState (interface): interface CameraFollowState
|
|
678
|
+
- FirstPersonCameraConfig (interface): interface FirstPersonCameraConfig
|
|
679
|
+
- CameraRigKind (type): type CameraRigKind = | "orbit" | "first" | "topDown" | "rts" | "shoulder" | "lockOn" | "chase" | "observer" | "turntable" | "sideScroll" | "inspection" | "none" — Which camera rig the shell mounts. Every rig accepts `followEntityId: null` (avatar-less games — city-builders, card games, auto-battlers — still get a camera). Rigs are tuned through their config block below, never by writing camera positions from `onTick`. - `orbit` — third-person chase (the historical default; `perspective: "third"`). - `first` — pointer-lock mouse-look (`perspective: "first"`). - `topDown` — fixed height/pitch/yaw with decoupled follow (ARPG iso, top-down). - `rts` — free-pan / edge-scroll / rotate / zoom, optional follow. - `shoulder` — over-the-shoulder with ADS transition + shoulder swap. - `lockOn` — yaw bound to the player→target vector; move axis becomes strafe. - `chase` — speed-reactive vehicle chase (speed→FOV, spring arm, shake) + cockpit/hood/rear views. - `observer` — detached spectator/photo cam bound to any entity or fixed point; never reads player input. - `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`. - `sideScroll` — fixed lateral follow (2.5D platformer/beat-'em-up side view); reads no player input. - `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. - `none` — no camera rig is mounted; use for HUD-only presentations or a game that manages its own camera.
|
|
680
|
+
- SideScrollCameraConfig (interface): interface SideScrollCameraConfig — 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.
|
|
681
|
+
- TopDownCameraConfig (interface): interface TopDownCameraConfig — Fixed top-down / isometric rig (#23) — height/pitch/yaw + decoupled follow.
|
|
682
|
+
- RtsCameraConfig (interface): interface RtsCameraConfig extends TopDownCameraConfig — Free-pan / edge-scroll RTS rig (#24) — pan/rotate/zoom independent of any avatar.
|
|
683
|
+
- ShoulderCameraConfig (interface): interface ShoulderCameraConfig — Over-the-shoulder combat rig (#25) — offset, ADS, shoulder swap, decoupled reticle.
|
|
684
|
+
- LockOnCameraConfig (interface): interface LockOnCameraConfig — Lock-on / strafe rig (#26) — yaw bound to player→target, move axis becomes strafe.
|
|
685
|
+
- ChaseView (type): type ChaseView = "chase" | "cockpit" | "hood" | "rear" — Fixed interior view for the chase rig (#27).
|
|
686
|
+
- ChaseCameraConfig (interface): interface ChaseCameraConfig — Speed-reactive vehicle chase rig (#27) — speed→FOV, spring arm, procedural shake, interior views.
|
|
687
|
+
- ObserverCameraConfig (interface): interface ObserverCameraConfig — Detached spectator/photo cam (#120) — binds to any entity or fixed point, never reads player input.
|
|
688
|
+
- TurntableCameraConfig (interface): interface TurntableCameraConfig — Turntable / showcase rig — slowly auto-orbits a fixed world point (no player input), the way a museum turntable rotates an object on display. A flat, self-describing spelling of `observer`'s point-orbit mode: `target` names the point directly instead of `bind: { kind: "point", position }`. Set `camera.turntable` and the rig is inferred — you don't also write `rig`.
|
|
689
|
+
- InspectionZoomAnchor (type): type InspectionZoomAnchor = "target" | "cursor" | "center" — How scroll-zoom re-anchors the view for the inspection rig (#207.7): - `target` — dolly toward the orbit target (classic OrbitControls behavior). - `cursor` — dolly toward the point under the pointer. - `center` — dolly toward the viewport center; equivalent to `target` for an OrbitControls-driven rig, since the camera always faces `target` and that point already projects to the exact center of the viewport.
|
|
690
|
+
- InspectionCameraConfig (interface): interface InspectionCameraConfig — Model-viewer / inspection rig (#207.7) — orbit + pan + anchored zoom around a fixed point, never reads player input.
|
|
691
|
+
- CameraKeyframe (interface): interface CameraKeyframe — One stop on a scripted camera path (#29).
|
|
692
|
+
- CinematicCameraConfig (interface): interface CinematicCameraConfig — Scripted keyframe / path player (#29). When set it overrides the active rig.
|
|
693
|
+
- CameraShakeConfig (interface): interface CameraShakeConfig — Camera-shake / trauma defaults (#28). Any rig reads the trauma channel; game systems feed it via `cameraShake()`.
|
|
694
|
+
- CAMERA_FRUSTUM_DEFAULTS (const): const CAMERA_FRUSTUM_DEFAULTS: { readonly fov: 55; readonly near: 0.1; readonly far: 300; }
|
|
695
|
+
- GameCameraConfig (interface): interface GameCameraConfig
|
|
696
|
+
- EntitySpriteConfig (interface): interface EntitySpriteConfig
|
|
697
|
+
- WorldItemRenderConfig (interface): interface WorldItemRenderConfig
|
|
698
|
+
- VoxelCollisionConfig (interface): interface VoxelCollisionConfig — Player-vs-world collision for the first-person controller. Without this the shell keeps the player on flat ground at y=0. With `voxel: true` the shell resolves the player as a box against placed scene objects (each treated as a solid unit cell), so they stand on blocks, fall into holes, and are stopped by walls — the controller a block-building/mining game needs.
|
|
699
|
+
- ModelAnimationConfig (interface): interface ModelAnimationConfig — Rig playback for a `ModelConfig`'s GLTF animation clips — looping idles, one-shots, and held poses.
|
|
700
|
+
- ModelMaterialOverride (interface): interface ModelMaterialOverride — Per-entity PBR material override (#151.3) applied to every `MeshStandardMaterial` in the model's cloned scene graph.
|
|
701
|
+
- ModelConfig (interface): interface ModelConfig
|
|
702
|
+
- ObjectStyle (interface): interface ObjectStyle
|
|
703
|
+
- AmbientLightingConfig (interface): interface AmbientLightingConfig
|
|
704
|
+
- DirectionalLightingConfig (interface): interface DirectionalLightingConfig
|
|
705
|
+
- HemisphereLightingConfig (interface): interface HemisphereLightingConfig
|
|
706
|
+
- LightingConfig (interface): interface LightingConfig — Declarative lighting replacing the shell's hardcoded ambient/directional default (#207.5); mounts regardless of world kind, only when supplied.
|
|
707
|
+
- BackdropFogConfig (interface): interface BackdropFogConfig
|
|
708
|
+
- BackdropConfig (interface): interface BackdropConfig — Generic sky/background/fog for ANY world kind, including a custom `environment` component (#207.6).
|
|
709
|
+
- PlayerMovementConfig (interface): interface PlayerMovementConfig — Movement-control levers for the shell-driven local player walk controller.
|
|
710
|
+
- MovementCommitFrame (interface): interface MovementCommitFrame — One frame's movement resolution handed to `PlayerMovementConfig.beforeCommit`.
|
|
711
|
+
- PlayableGame (interface): interface PlayableGame<TUi = unknown, TWorldOverlay = unknown, TRenderEntity = never, TRenderObject = never>
|
|
712
|
+
|
|
713
|
+
### @jgengine/core/game/progression
|
|
714
|
+
|
|
715
|
+
- evalCurve (function): function evalCurve(spec: Curve, x: number): number
|
|
716
|
+
- curve (function): function curve(spec: Curve): (x: number) => number
|
|
717
|
+
- leveling (function): function leveling(config: LevelingConfig): LevelingTrack
|
|
718
|
+
- CurveDef (type): type CurveDef = | { kind: "const"; value: number } | { kind: "linear"; base: number; per: number } | { kind: "power"; base: number; exponent: number } | { kind: "geometric"; base: number; ratio: number } | { kind: "steps"; values: number[] } | { kind: "piecewise"; points: [number, number][] }
|
|
719
|
+
- CurveShape (interface): interface CurveShape
|
|
720
|
+
- Curve (type): type Curve = CurveDef & CurveShape
|
|
721
|
+
- LevelProgress (interface): interface LevelProgress
|
|
722
|
+
- LevelingConfig (interface): interface LevelingConfig
|
|
723
|
+
- LevelingStatAccess (interface): interface LevelingStatAccess
|
|
724
|
+
- LevelingTrack (interface): interface LevelingTrack
|
|
725
|
+
|
|
726
|
+
### @jgengine/core/game/quest
|
|
727
|
+
|
|
728
|
+
- createQuestJournal (function): function createQuestJournal(deps: QuestJournalDeps): QuestJournal
|
|
729
|
+
- applyQuestRewards (function): function applyQuestRewards(rewards: QuestRewards, appliers: { grantXp?(amount: number): void; grantEconomy?(currencyId: string, amount: number): void; grantItem?(inventoryId: string, itemId: string, count: number): void; grantUnlock?(unlockId: string): void; }): void
|
|
730
|
+
- createQuestEvaluator (function): function createQuestEvaluator(defs: QuestDef[] | Record<string, QuestDef>): QuestEvaluator
|
|
731
|
+
- QuestObjective (interface): interface QuestObjective
|
|
732
|
+
- QuestRewards (interface): interface QuestRewards
|
|
733
|
+
- QuestDef (interface): interface QuestDef
|
|
734
|
+
- QuestStatus (type): type QuestStatus = "active" | "completed"
|
|
735
|
+
- QuestObjectiveProgress (interface): interface QuestObjectiveProgress
|
|
736
|
+
- QuestInstance (interface): interface QuestInstance
|
|
737
|
+
- QuestSnapshotEntry (type): type QuestSnapshotEntry = { questId: string; status: QuestStatus; progress: Record<string, number>; }
|
|
738
|
+
- QuestJournalDeps (interface): interface QuestJournalDeps
|
|
739
|
+
- QuestJournal (interface): interface QuestJournal
|
|
740
|
+
- QuestAcceptOptions (interface): interface QuestAcceptOptions
|
|
741
|
+
- QuestTurnIn (interface): interface QuestTurnIn
|
|
742
|
+
- QuestEvaluator (interface): interface QuestEvaluator
|
|
743
|
+
|
|
744
|
+
### @jgengine/core/game/race
|
|
745
|
+
|
|
746
|
+
- raceTrack (function): function raceTrack(config: RaceTrackConfig): RaceTrack — A race track is an ordered ring of checkpoint trigger volumes plus a lap count. The final checkpoint is the lap/finish line: a racer completes a lap by passing all checkpoints in order and hitting the last one.
|
|
747
|
+
- firstPastPost (function): function firstPastPost(count = 1): RaceWinCondition — Race ends when `count` racers have crossed the finish; ranking is the current standings order.
|
|
748
|
+
- topK (function): function topK(k: number): RaceWinCondition — Round-cut (Fall Guys): the first `k` finishers qualify; race resolves the moment `k` cross.
|
|
749
|
+
- everyoneFinishes (function): function everyoneFinishes(): RaceWinCondition — Every non-eliminated racer must finish.
|
|
750
|
+
- lastStanding (function): function lastStanding(): RaceWinCondition — Destruction-derby last-man-standing: ends when at most one racer is left un-eliminated.
|
|
751
|
+
- createRaceState (function): function createRaceState(config: RaceStateConfig): RaceState
|
|
752
|
+
- Checkpoint (interface): interface Checkpoint
|
|
753
|
+
- RaceTrackConfig (interface): interface RaceTrackConfig
|
|
754
|
+
- RaceTrack (interface): interface RaceTrack
|
|
755
|
+
- RacerProgress (interface): interface RacerProgress
|
|
756
|
+
- RaceEvent (type): type RaceEvent = | { type: "checkpoint.hit"; racerId: string; checkpoint: number; lap: number; time: number } | { type: "lap.completed"; racerId: string; lap: number; time: number } | { type: "position.changed"; racerId: string; position: number; previous: number } | { type: "race.finished"; ranking…
|
|
757
|
+
- RaceWinCondition (type): type RaceWinCondition = (standings: readonly RacerProgress[], track: RaceTrack) => readonly string[] | null
|
|
758
|
+
- RaceStateConfig (interface): interface RaceStateConfig
|
|
759
|
+
- RaceState (class): class RaceState — Race state machine (issue #87). Drive it each tick with `update(now, positions)` — `now` is game time (`ctx.time`), `positions` maps each racer to a world point tested against the ordered checkpoint volumes. It emits `checkpoint.hit` / `lap.completed` / `position.changed` / `race.finished`, keeps cumulative split times for PB deltas, resolves a pluggable win condition (first-past-post, round-cut, derby last-standing), and `resetToCheckpoint` hands back a respawn pose at the racer's last checkpoint. `removeRacer` drops a racer mid-race and `reset` returns the whole instance to its pre-race state for reuse.
|
|
760
|
+
|
|
761
|
+
### @jgengine/core/game/runDraft
|
|
762
|
+
|
|
763
|
+
- createRunModifierStack (function): function createRunModifierStack<TStat extends string = string, TData = unknown>(offers: readonly RunModifierOffer<TStat, TData>[]): RunModifierStack<TStat, TData>
|
|
764
|
+
- createRunDraft (function): function createRunDraft<TStat extends string = string, TData = unknown>(config: RunDraftConfig<TStat, TData>): RunDraft<TStat, TData>
|
|
765
|
+
- RunModifierOffer (interface): interface RunModifierOffer<TStat extends string = string, TData = unknown>
|
|
766
|
+
- RunModifierPick (interface): interface RunModifierPick
|
|
767
|
+
- RunModifierStack (interface): interface RunModifierStack<TStat extends string = string, TData = unknown>
|
|
768
|
+
- RunDraftConfig (interface): interface RunDraftConfig<TStat extends string = string, TData = unknown>
|
|
769
|
+
- RunDraft (interface): interface RunDraft<TStat extends string = string, TData = unknown>
|
|
770
|
+
|
|
771
|
+
### @jgengine/core/game/social
|
|
772
|
+
|
|
773
|
+
- createSocial (function): function createSocial(deps: SocialDeps): Social
|
|
774
|
+
- PresenceInfo (interface): interface PresenceInfo
|
|
775
|
+
- EmoteEntityLookup (interface): interface EmoteEntityLookup
|
|
776
|
+
- EmoteSpatialLookup (interface): interface EmoteSpatialLookup
|
|
777
|
+
- EmotesDeps (interface): interface EmotesDeps
|
|
778
|
+
- SocialDeps (interface): interface SocialDeps
|
|
779
|
+
- FriendEntry (interface): interface FriendEntry
|
|
780
|
+
- FriendsSnapshot (interface): interface FriendsSnapshot
|
|
781
|
+
- FriendRequestEntry (interface): interface FriendRequestEntry
|
|
782
|
+
- Friends (interface): interface Friends
|
|
783
|
+
- PartyRole (type): type PartyRole = "leader" | "member"
|
|
784
|
+
- PartyMemberEntry (interface): interface PartyMemberEntry
|
|
785
|
+
- PartyConfig (interface): interface PartyConfig
|
|
786
|
+
- PartyInviteEntry (interface): interface PartyInviteEntry
|
|
787
|
+
- Party (interface): interface Party
|
|
788
|
+
- DEFAULT_EMOTE_RADIUS (const): const DEFAULT_EMOTE_RADIUS: 20
|
|
789
|
+
- EmoteBroadcastResult (interface): interface EmoteBroadcastResult
|
|
790
|
+
- Emotes (interface): interface Emotes
|
|
791
|
+
- WorldInviteTarget (interface): interface WorldInviteTarget
|
|
792
|
+
- WorldInvite (interface): interface WorldInvite extends WorldInviteTarget
|
|
793
|
+
- WorldInvites (interface): interface WorldInvites
|
|
794
|
+
- Social (interface): interface Social
|
|
795
|
+
|
|
796
|
+
### @jgengine/core/game/spawnPoints
|
|
797
|
+
|
|
798
|
+
- createSpawnPoints (function): function createSpawnPoints(): SpawnPoints
|
|
799
|
+
- SpawnPointPose (interface): interface SpawnPointPose
|
|
800
|
+
- RespawnTarget (interface): interface RespawnTarget
|
|
801
|
+
- SpawnPoints (interface): interface SpawnPoints
|
|
802
|
+
|
|
803
|
+
### @jgengine/core/game/trade
|
|
804
|
+
|
|
805
|
+
- canAffordCosts (function): function canAffordCosts(balances: Record<string, number>, costs: Record<string, number>): string | null
|
|
806
|
+
- resolveBuy (function): function resolveBuy(itemId: string, trade: TradeField | null | undefined, shopId: string, count: number, balances: Record<string, number>): TradeResolution
|
|
807
|
+
- resolveSell (function): function resolveSell(itemId: string, trade: TradeField | null | undefined, count: number): TradeResolution
|
|
808
|
+
- applyTradeOutcome (function): function applyTradeOutcome(outcome: TradeOutcome, appliers: { adjustItem(itemId: string, count: number): { reason: string } | null; adjustCurrency(delta: Record<string, number>): void; }): { reason: string } | null
|
|
809
|
+
- createTradeSystem (function): function createTradeSystem(deps: TradeSystemDeps): TradeSystem
|
|
810
|
+
- TradeField (interface): interface TradeField
|
|
811
|
+
- TradeRejection (interface): interface TradeRejection
|
|
812
|
+
- TradeWallet (interface): interface TradeWallet
|
|
813
|
+
- TradeInventory (interface): interface TradeInventory
|
|
814
|
+
- TradeSystemDeps (interface): interface TradeSystemDeps
|
|
815
|
+
- TradeParty (interface): interface TradeParty
|
|
816
|
+
- TradeSystem (interface): interface TradeSystem
|
|
817
|
+
- TradeOutcome (interface): interface TradeOutcome
|
|
818
|
+
- TradeResolution (type): type TradeResolution = | { status: "ok"; outcome: TradeOutcome } | { status: "rejected"; reason: string }
|
|
819
|
+
|
|
820
|
+
### @jgengine/core/game/unlocks
|
|
821
|
+
|
|
822
|
+
- hasUnlock (function): function hasUnlock(granted: UnlockState, unlockId: string): boolean
|
|
823
|
+
- grantUnlock (function): function grantUnlock(granted: UnlockState, unlockId: string): string[]
|
|
824
|
+
- unlockTree (function): function unlockTree(defs: readonly UnlockDef[], categoryId: string): UnlockDef[]
|
|
825
|
+
- createUnlockCatalog (function): function createUnlockCatalog(defs: readonly UnlockDef[] = []): UnlockCatalog
|
|
826
|
+
- createUnlocks (function): function createUnlocks(defs: UnlockDef[] = []): Unlocks
|
|
827
|
+
- UnlockDef (interface): interface UnlockDef
|
|
828
|
+
- UnlockState (type): type UnlockState = readonly string[]
|
|
829
|
+
- UnlockCatalog (interface): interface UnlockCatalog
|
|
830
|
+
- Unlocks (interface): interface Unlocks
|
|
831
|
+
|
|
832
|
+
### @jgengine/core/game/worldItem
|
|
833
|
+
|
|
834
|
+
- scatterOffset (function): function scatterOffset(rng: () => number, options: ScatterOptions = DEFAULT_SCATTER): EntityPosition — Random offset within an annulus `[minRadius, radius]` around the origin — the on-death scatter impulse.
|
|
835
|
+
- scatterPosition (function): function scatterPosition(origin: EntityPosition, rng: () => number, options: ScatterOptions = DEFAULT_SCATTER): EntityPosition
|
|
836
|
+
- selectNearestWorldItem (function): function selectNearestWorldItem(candidates: readonly { instanceId: string; position: EntityPosition }[], from: EntityPosition, radius: number): string | null — Pure pickup-radius + nearest-item selection, usable without a live store (click-to-grab, proximity prompts).
|
|
837
|
+
- createWorldItemStore (function): function createWorldItemStore(deps: WorldItemStoreDeps): WorldItemStore
|
|
838
|
+
- resolveDeathDrops (function): function resolveDeathDrops(drops: readonly Drop[], options: ResolveDeathDropsOptions): ResolvedDeathDrops — Splits rolled death drops between direct grants and scattered ground items. `mode: "grant"` is the legacy behavior (loot straight to inventory); `mode: "world"` routes item drops through a scatter impulse and leaves currency drops granting directly (coins fly to the killer, gear hits the ground).
|
|
839
|
+
- resolveWorldItemPresentation (function): function resolveWorldItemPresentation(item: LootFilterItem, rarityStyle: Record<string, RarityStyle> | undefined, rules: readonly LootFilterRule[] | undefined): WorldItemPresentation — Composes the baseline rarity→beam/color/label render binding (#32, catalog data the game supplies) with the loot-filter rule overrides (#33). A matching rule wins field-by-field; unmatched fields fall back to the rarity's baseline style.
|
|
840
|
+
- WORLD_ITEM_ENTITY_NAME (const): const WORLD_ITEM_ENTITY_NAME: "world_item" — Scene-entity catalog name every dropped-item instance spawns under (see the three buckets: worldItem is an entity, never an inventory item or object).
|
|
841
|
+
- DEFAULT_RARITY (const): const DEFAULT_RARITY: "common"
|
|
842
|
+
- DEFAULT_PICKUP_RADIUS (const): const DEFAULT_PICKUP_RADIUS: 2
|
|
843
|
+
- ScatterOptions (interface): interface ScatterOptions
|
|
844
|
+
- DEFAULT_SCATTER (const): const DEFAULT_SCATTER: ScatterOptions
|
|
845
|
+
- WorldItemRecord (interface): interface WorldItemRecord
|
|
846
|
+
- WorldItemSpawnInput (interface): interface WorldItemSpawnInput
|
|
847
|
+
- WorldItemStoreDeps (interface): interface WorldItemStoreDeps
|
|
848
|
+
- WorldItemStore (interface): interface WorldItemStore
|
|
849
|
+
- ResolveDeathDropsOptions (interface): interface ResolveDeathDropsOptions
|
|
850
|
+
- ResolvedDeathDrops (interface): interface ResolvedDeathDrops
|
|
851
|
+
- RarityStyle (interface): interface RarityStyle
|
|
852
|
+
- WorldItemPresentation (interface): interface WorldItemPresentation
|
|
853
|
+
|
|
854
|
+
### @jgengine/core/index
|
|
855
|
+
|
|
856
|
+
- parseSaveAutoMs (function): function parseSaveAutoMs(auto: string): number
|
|
857
|
+
- saveScopeIncludesPlayer (function): function saveScopeIncludesPlayer(scope: SaveScope): boolean
|
|
858
|
+
- saveScopeIncludesChunks (function): function saveScopeIncludesChunks(scope: SaveScope): boolean
|
|
859
|
+
- isSaveEnabled (function): function isSaveEnabled(config: SaveConfig): config is Exclude<SaveConfig, "none">
|
|
860
|
+
- SaveScope (type): type SaveScope = "player" | "chunks" | "player+chunks"
|
|
861
|
+
- SaveConfig (type): type SaveConfig = | "none" | { auto: string; scope: SaveScope; }
|
|
862
|
+
- createEmptyServerRow (function): function createEmptyServerRow(): RuntimeServerRow
|
|
863
|
+
- createEmptyPlayerRow (function): function createEmptyPlayerRow(userId: string): RuntimePlayerRow
|
|
864
|
+
- createRuntimeSnapshot (function): function createRuntimeSnapshot(args: { gameId: string; serverId: string; server?: RuntimeServerRow; players?: Record<string, RuntimePlayerRow>; chunks?: Record<string, RuntimeChunkRow>; }): GameRuntimeSnapshot
|
|
865
|
+
- markServerDirty (function): function markServerDirty(snapshot: GameRuntimeSnapshot): GameRuntimeSnapshot
|
|
866
|
+
- markPlayerDirty (function): function markPlayerDirty(snapshot: GameRuntimeSnapshot, userId: string): GameRuntimeSnapshot
|
|
867
|
+
- clearDirtyFlags (function): function clearDirtyFlags(snapshot: GameRuntimeSnapshot): GameRuntimeSnapshot
|
|
868
|
+
- splitProfilePlayer (function): function splitProfilePlayer(player: RuntimePlayerRow): { persistent: RuntimePlayerRow; session: Record<string, unknown>; }
|
|
869
|
+
- RUNTIME_SNAPSHOT_VERSION (const): const RUNTIME_SNAPSHOT_VERSION: 1
|
|
870
|
+
- RuntimeEntityRow (type): type RuntimeEntityRow = { instanceId: string; catalogId: string; position?: [number, number, number]; rotationY?: number; parentSpace?: string; group?: string; stats?: Record<string, { current: number; max: number; min?: number }>; targetInstanceId?: string | null; userId?: string; }
|
|
871
|
+
- RuntimeObjectRow (type): type RuntimeObjectRow = { instanceId: string; catalogId: string; position: [number, number, number]; rotationY?: number; parentSpace?: string; flags?: Record<string, unknown>; }
|
|
872
|
+
- RuntimeInventorySlot (type): type RuntimeInventorySlot = { item: string; count: number; slot?: number; }
|
|
873
|
+
- RuntimePlayerRow (type): type RuntimePlayerRow = { userId: string; inventories: Record<string, RuntimeInventorySlot[]>; economy: Record<string, number>; unlocks: string[]; quests?: unknown; social?: unknown; leaderboard?: Record<string, number>; session?: Record<string, unknown>; }
|
|
874
|
+
- RuntimeChunkRow (type): type RuntimeChunkRow = { chunkKey: string; objects: RuntimeObjectRow[]; entities: RuntimeEntityRow[]; flags?: Record<string, unknown>; }
|
|
875
|
+
- RuntimeServerRow (type): type RuntimeServerRow = { entities: RuntimeEntityRow[]; objects: RuntimeObjectRow[]; session: Record<string, unknown>; feeds?: Record<string, unknown[]>; }
|
|
876
|
+
- RuntimeProfileRow (type): type RuntimeProfileRow = { userId: string; gameId: string; player: RuntimePlayerRow; updatedAt: number; }
|
|
877
|
+
- GameRuntimeSnapshot (type): type GameRuntimeSnapshot = { version: number; gameId: string; serverId: string; server: RuntimeServerRow; players: Record<string, RuntimePlayerRow>; chunks: Record<string, RuntimeChunkRow>; revision: number; dirty: { server: boolean; players: string[]; chunks: string[]; }; }
|
|
878
|
+
- runCommand (function): function runCommand<TInput>(snapshot: import("./snapshot").GameRuntimeSnapshot, commands: Record<string, CommandDef<TInput>>, commandName: string, input: TInput, actorUserId: string): RunCommandResult
|
|
879
|
+
- CommandValidationError (type): type CommandValidationError = { reason: string }
|
|
880
|
+
- CommandDef (type): type CommandDef<TInput = unknown> = { validate: (snapshot: import("./snapshot").GameRuntimeSnapshot, input: TInput) => CommandValidationError | null; apply: ( snapshot: import("./snapshot").GameRuntimeSnapshot, input: TInput, ) => import("./snapshot").GameRuntimeSnapshot; }
|
|
881
|
+
- RunCommandResult (type): type RunCommandResult = | { ok: true; snapshot: import("./snapshot").GameRuntimeSnapshot } | { ok: false; reason: string }
|
|
882
|
+
- createGameRuntime (function): function createGameRuntime(definition: GameRuntimeDefinition): GameRuntime
|
|
883
|
+
- ServerLoopHooks (type): type ServerLoopHooks = { onInit?: (ctx: RuntimeInitContext) => void; onNewPlayer?: (ctx: RuntimeLoopContext) => void; onTick?: (ctx: RuntimeLoopContext, dtSeconds: number) => void; }
|
|
884
|
+
- RuntimeInitContext (type): type RuntimeInitContext = { snapshot: GameRuntimeSnapshot; setSnapshot: (snapshot: GameRuntimeSnapshot) => void; }
|
|
885
|
+
- RuntimeLoopContext (type): type RuntimeLoopContext = RuntimeInitContext & { player: { userId: string; isNew: boolean; }; }
|
|
886
|
+
- GameRuntimeDefinition (type): type GameRuntimeDefinition = { gameId: string; save: SaveConfig; commands: Record<string, CommandDef>; loop?: ServerLoopHooks; }
|
|
887
|
+
- HydrateInput (type): type HydrateInput = { gameId: string; serverId: string; serverRow: RuntimeServerRow; playersByUserId: Record<string, RuntimePlayerRow>; chunksByKey: Record<string, RuntimeChunkRow>; }
|
|
888
|
+
- GameRuntime (type): type GameRuntime = { gameId: string; save: SaveConfig; hydrate: (input: HydrateInput) => GameRuntimeSnapshot; runCommand: ( snapshot: GameRuntimeSnapshot, actorUserId: string, commandName: string, input: unknown, ) => ReturnType<typeof runCommand>; tick: (snapshot: GameRuntimeSnapshot, dtSeconds: nu…
|
|
889
|
+
- convex (function): function convex(config?: { topology?: MultiplayerTopology }): MultiplayerAdapterConfig
|
|
890
|
+
- ws (function): function ws(config?: { topology?: MultiplayerTopology; url?: string }): MultiplayerAdapterConfig
|
|
891
|
+
- fly (function): function fly(config: { app: string; topology?: MultiplayerTopology; path?: string }): MultiplayerAdapterConfig
|
|
892
|
+
- socketIo (function): function socketIo(config?: { topology?: MultiplayerTopology; url?: string }): MultiplayerAdapterConfig
|
|
893
|
+
- p2p (function): function p2p(config?: { topology?: MultiplayerTopology; room?: string }): MultiplayerAdapterConfig
|
|
894
|
+
- lan (function): function lan(config?: { topology?: MultiplayerTopology; port?: number; path?: string; }): MultiplayerAdapterConfig
|
|
895
|
+
- offline (function): function offline(): MultiplayerAdapterConfig
|
|
896
|
+
- adapterOf (function): function adapterOf(multiplayer: unknown): MultiplayerAdapterConfig | null
|
|
897
|
+
- multiplayerAdapterKind (function): function multiplayerAdapterKind(multiplayer: unknown): string | null
|
|
898
|
+
- servers (function): function servers(config: ServersPoolConfig): import("/home/runner/work/jgengine/jgengine/packages/core/src/runtime/adapter").ServersPoolConfig
|
|
899
|
+
- MultiplayerTopology (type): type MultiplayerTopology = "shared" | "lobbies" | "private"
|
|
900
|
+
- MultiplayerAdapterConfig (type): type MultiplayerAdapterConfig = | { kind: "convex"; topology?: MultiplayerTopology } | { kind: "ws"; topology?: MultiplayerTopology; url?: string } | { kind: "socketio"; topology?: MultiplayerTopology; url?: string } | { kind: "p2p"; topology?: MultiplayerTopology; room?: string } | { kind: "lan"; t…
|
|
901
|
+
- ServersPoolConfig (type): type ServersPoolConfig = { maxServers: number; slotsPerServer: number; minPlayersToStart?: number; adapter: MultiplayerAdapterConfig; }
|
|
902
|
+
- JoinServerResult (type): type JoinServerResult = { serverId: string; isNew: boolean; }
|
|
903
|
+
- RunCommandArgs (type): type RunCommandArgs = { serverId: string; command: string; input: unknown; }
|
|
904
|
+
- TransportRunCommandResult (type): type TransportRunCommandResult = | { ok: true } | { ok: false; reason: string }
|
|
905
|
+
- GameRuntimeServerView (type): type GameRuntimeServerView = { serverId: string; gameId: string; revision: number; memberUserIds: string[]; serverState: unknown; updatedAt: number; }
|
|
906
|
+
- GameRuntimePlayerView (type): type GameRuntimePlayerView = { userId: string; gameId: string; playerState: unknown; updatedAt: number; }
|
|
907
|
+
- GameRuntimeFeedView (type): type GameRuntimeFeedView = { action: string; entries: unknown[]; }
|
|
908
|
+
- GameRuntimeTransport (type): type GameRuntimeTransport = { joinServer: (args: { gameId: string; serverId?: string }) => Promise<JoinServerResult>; leaveServer: (args: { serverId: string }) => Promise<void>; runCommand: (args: RunCommandArgs) => Promise<TransportRunCommandResult>; }
|
|
909
|
+
- FeedUnsubscribe (type): type FeedUnsubscribe = () => void
|
|
910
|
+
- GameRuntimeFeeds (type): type GameRuntimeFeeds = { subscribeServer: ( serverId: string, onChange: (view: GameRuntimeServerView | null) => void, ) => FeedUnsubscribe; subscribePlayer: ( args: { serverId: string }, onChange: (view: GameRuntimePlayerView | null) => void, ) => FeedUnsubscribe; subscribeFeed: ( args: { serverId:…
|
|
911
|
+
- GameBackend (type): type GameBackend<TPresenceRow = unknown, TPresenceLocation = unknown, TGameId extends string = string> = { transport: GameRuntimeTransport; feeds?: GameRuntimeFeeds; presence?: PresenceTransport<TPresenceRow, TPresenceLocation, TGameId>; }
|
|
912
|
+
- PresencePoseRow (type): type PresencePoseRow = { userId: string; position: { x: number; y: number; z: number }; rotationY: number; rotationPitch: number; lastSeenAt: number; }
|
|
913
|
+
- PresenceSync (type): type PresenceSync = { subscribe: (serverId: string, onChange: (rows: PresencePoseRow[]) => void) => FeedUnsubscribe; syncPose: (serverId: string, pose: PlayerPose) => void; }
|
|
914
|
+
- LiveGameBackend (type): type LiveGameBackend<TPresenceRow = unknown, TPresenceLocation = unknown, TGameId extends string = string> = GameBackend<TPresenceRow, TPresenceLocation, TGameId> & { presenceSync: PresenceSync; pushFeedEntry: (args: { serverId: string; action: string; entry: unknown }) => Promise<void>; chatSyncFor…
|
|
915
|
+
- MultiplayerSession (type): type MultiplayerSession = { gameId: string; userId: string; backend: LiveGameBackend; feedActions: string[]; }
|
|
916
|
+
|
|
917
|
+
### @jgengine/core/input/actionBindings
|
|
918
|
+
|
|
919
|
+
- bindingMatches (function): function bindingMatches<TCode extends string>(code: TCode, binding: ActionBinding<TCode>): boolean
|
|
920
|
+
- resolveBoundAction (function): function resolveBoundAction<TAction extends string, TCode extends string>(code: TCode, bindings: ActionBindingMap<TAction, TCode>): TAction | null — First action (in the map's key order) bound to the given control code, or null.
|
|
921
|
+
- normalizeKeyCode (function): function normalizeKeyCode(code: string): string — Collapse left/right modifier variants of a KeyboardEvent.code so bindings store one logical key.
|
|
922
|
+
- actionRepeatMs (function): function actionRepeatMs(codes: ActionCodes | undefined): number | undefined
|
|
923
|
+
- shouldDispatchAction (function): function shouldDispatchAction({ pressed, down, repeatMs, lastFiredAt, now }: ShouldDispatchActionInput): boolean
|
|
924
|
+
- toActionStateBindingMap (function): function toActionStateBindingMap<TAction extends string, TCode extends string>(map: ActionCodesMap<TAction, TCode>): ActionStateBindingMap<TAction, TCode>
|
|
925
|
+
- hotbarSlotBindings (function): function hotbarSlotBindings(count: number, options?: { action?: (slot: number) => string }): ActionCodesMap
|
|
926
|
+
- hotbarSlotActionIndex (function): function hotbarSlotActionIndex(action: string): number | null — 0-based slot index for a hotbar-slot action name (`slot1`, `hotbarSlot1`), or null.
|
|
927
|
+
- isHotbarSlotAction (function): function isHotbarSlotAction(action: string): boolean
|
|
928
|
+
- resolveActionCommand (function): function resolveActionCommand(action: string, has: (command: string) => boolean, reserved: ReadonlySet<string>): string | null — Convention mapping a bound input action to the command it fires: the action's own name when a command by that name exists, otherwise a `ui.<action>` fallback. Reserved actions (consumed natively by the shell) and hotbar slots yield null so the shell can handle them itself.
|
|
929
|
+
- bindingLabel (function): function bindingLabel(code: string): string
|
|
930
|
+
- actionLabel (function): function actionLabel(map: ActionCodesMap, action: string): string | null — Short display label for an action's first bound code, or null when unbound.
|
|
931
|
+
- createActionStateTracker (function): function createActionStateTracker<TAction extends string, TCode extends string = string>(map: ActionStateBindingMap<TAction, TCode>): ActionStateTracker<TAction>
|
|
932
|
+
- ActionBinding (interface): interface ActionBinding<TCode extends string = string> — Action-binding model: games bind semantic actions ("jump", "interact") to physical control codes; capture layers resolve raw events through this map so gameplay code never sees keycodes. Control codes are plain strings, so the same model serves keyboard codes, mouse buttons, touch controls, or gamepad inputs.
|
|
933
|
+
- ActionBindingMap (type): type ActionBindingMap<TAction extends string, TCode extends string = string> = Record< TAction, ActionBinding<TCode> >
|
|
934
|
+
- ActionBindingModes (interface): interface ActionBindingModes<TCode extends string = string>
|
|
935
|
+
- ActionBindingConfig (type): type ActionBindingConfig<TCode extends string = string> = ActionBinding<TCode>[] | ActionBindingModes<TCode>
|
|
936
|
+
- ActionStateBindingMap (type): type ActionStateBindingMap<TAction extends string, TCode extends string = string> = Record< TAction, ActionBindingConfig<TCode> >
|
|
937
|
+
- ActionCodes (type): type ActionCodes<TCode extends string = string> = | readonly TCode[] | { hold?: readonly TCode[]; toggle?: readonly TCode[]; repeatMs?: number }
|
|
938
|
+
- ActionCodesMap (type): type ActionCodesMap<TAction extends string = string, TCode extends string = string> = Record< TAction, ActionCodes<TCode> >
|
|
939
|
+
- ShouldDispatchActionInput (interface): interface ShouldDispatchActionInput
|
|
940
|
+
- ActionStateTracker (interface): interface ActionStateTracker<TAction extends string>
|
|
941
|
+
|
|
942
|
+
### @jgengine/core/input/axisInput
|
|
943
|
+
|
|
944
|
+
- clampAxis (function): function clampAxis(value: number, range: AxisRange): number
|
|
945
|
+
- rampToward (function): function rampToward(current: number, target: number, rate: number, dt: number): number
|
|
946
|
+
- AxisInput (interface): interface AxisInput
|
|
947
|
+
- NEUTRAL_AXIS (const): const NEUTRAL_AXIS: AxisInput
|
|
948
|
+
- AxisName (type): type AxisName = keyof AxisInput
|
|
949
|
+
- AxisBinding (interface): interface AxisBinding
|
|
950
|
+
- AxisBindingMap (type): type AxisBindingMap = Record<AxisName, AxisBinding>
|
|
951
|
+
- AxisChannelConfig (interface): interface AxisChannelConfig
|
|
952
|
+
- AxisRange (interface): interface AxisRange
|
|
953
|
+
- AXIS_RANGE (const): const AXIS_RANGE: Record<AxisName, AxisRange>
|
|
954
|
+
- AxisChannel (class): class AxisChannel — Analog control channel — distinct from the digital action bindings. Throttle/brake/steer/handbrake are continuous values ramped from held keys (a keyboard feels like a pedal) or driven directly from a gamepad axis via `setAnalog`. `sample(dt, isDown)` folds the current held-key state into the smoothed value; a `setAnalog` override replaces the digital target for that axis until cleared.
|
|
955
|
+
- DRIVE_AXIS_BINDINGS (const): const DRIVE_AXIS_BINDINGS: AxisBindingMap
|
|
956
|
+
|
|
957
|
+
### @jgengine/core/input/gestureSurface
|
|
958
|
+
|
|
959
|
+
- createGestureSurfaceTracker (function): function createGestureSurfaceTracker(bindings: TouchGestureBindings, tuning: GestureSurfaceTuning = DEFAULT_GESTURE_TUNING): GestureSurfaceTracker
|
|
960
|
+
- GestureSurfaceTuning (interface): interface GestureSurfaceTuning
|
|
961
|
+
- DEFAULT_GESTURE_TUNING (const): const DEFAULT_GESTURE_TUNING: GestureSurfaceTuning
|
|
962
|
+
- GestureSurfaceTracker (interface): interface GestureSurfaceTracker
|
|
963
|
+
|
|
964
|
+
### @jgengine/core/input/lookChannel
|
|
965
|
+
|
|
966
|
+
- createLookChannel (function): function createLookChannel({ sensitivity, maxVerticalOffset = Infinity }: LookChannelOptions): LookChannel
|
|
967
|
+
- LookDeltas (interface): interface LookDeltas — Per-frame look channel shared between an event-driven capture layer (writer of raw pointer deltas) and a frame-driven controller (consumer), plus the latest committed pose for same-frame readers such as presence sync. Kept as plain mutable state on purpose: routing per-frame deltas through a reactive store would notify subscribers every frame for state no UI reads.
|
|
968
|
+
- LookChannel (interface): interface LookChannel
|
|
969
|
+
- LookChannelOptions (interface): interface LookChannelOptions
|
|
970
|
+
|
|
971
|
+
### @jgengine/core/input/pointer
|
|
972
|
+
|
|
973
|
+
- aimToPoint (function): function aimToPoint(origin: PointerVec3, point: PointerVec3): Aim — Build an `origin → point` aim for `item.use` / projectiles, firing toward the cursor.
|
|
974
|
+
- moveTargetFromHit (function): function moveTargetFromHit(hit: PointerHit): PointerVec3 — The move-to target of a pointer hit — sugar over `hit.point`.
|
|
975
|
+
- groundOf (function): function groundOf(hit: PointerHit): readonly [number, number] — Project a pointer hit onto the XZ plane for navmesh routing (`findPath` takes `[x, z]`).
|
|
976
|
+
- createDragCapture (function): function createDragCapture(config: DragCaptureConfig = {}): DragCapture
|
|
977
|
+
- PointerVec3 (type): type PointerVec3 = readonly [number, number, number]
|
|
978
|
+
- PointerHit (interface): interface PointerHit — Renderer-free result of a screen→world raycast. The shell's pointer service produces this from the cursor; core-side gameplay (item.use aim, click-to-move, ground-target abilities, pings) consumes it without touching three.js.
|
|
979
|
+
- PointerButton (type): type PointerButton = "primary" | "secondary" | "middle"
|
|
980
|
+
- DragState (interface): interface DragState — Snapshot of an in-progress or just-released drag: origin/current cursor points plus the clamped pull vector.
|
|
981
|
+
- DragResult (type): type DragResult = DragState
|
|
982
|
+
- DragCaptureConfig (interface): interface DragCaptureConfig
|
|
983
|
+
- DragCapture (interface): interface DragCapture — Renderer-agnostic drag-capture / pull-vector state machine for slingshot-style aiming, drawback abilities, and similar gestures.
|
|
984
|
+
|
|
985
|
+
### @jgengine/core/input/touchGestures
|
|
986
|
+
|
|
987
|
+
- createTouchGestureTracker (function): function createTouchGestureTracker(tuning: TouchGestureTuning): TouchGestureTracker
|
|
988
|
+
- TouchGestureTuning (interface): interface TouchGestureTuning — Single-finger gesture disambiguation for touch look controls: one touch stream drives look-drag, tap, and long-press-hold without fighting. Movement past the tap threshold commits the gesture to "look" and cancels tap/hold; a still finger that lifts quickly is a tap; a still finger held past the long-press delay is a hold. Timestamps are injected so the state machine stays pure; the capture layer owns timers and touch identifiers.
|
|
989
|
+
- TouchGestureEnd (type): type TouchGestureEnd = "tap" | "hold-end" | null
|
|
990
|
+
- TouchGestureTracker (interface): interface TouchGestureTracker
|
|
991
|
+
|
|
992
|
+
### @jgengine/core/input/touchScheme
|
|
993
|
+
|
|
994
|
+
- touchCode (function): function touchCode(action: string): string
|
|
995
|
+
- withTouchCodes (function): function withTouchCodes(map: ActionCodesMap | undefined): ActionCodesMap — Every action gains a synthetic touch code alongside its physical codes.
|
|
996
|
+
- touchActionLabel (function): function touchActionLabel(action: string): string
|
|
997
|
+
- deriveTouchScheme (function): function deriveTouchScheme(input: ActionCodesMap | undefined, { reserved, firstPerson, config }: DeriveTouchSchemeOptions): TouchScheme | null — Null means "render no touch controls" — either the game opted out or there is nothing to synthesize.
|
|
998
|
+
- TOUCH_CODE_PREFIX (const): const TOUCH_CODE_PREFIX: "touch:"
|
|
999
|
+
- TouchDragBinding (interface): interface TouchDragBinding
|
|
1000
|
+
- TouchGestureBindings (interface): interface TouchGestureBindings
|
|
1001
|
+
- TouchButtonSpec (interface): interface TouchButtonSpec
|
|
1002
|
+
- TouchControlsConfig (interface): interface TouchControlsConfig
|
|
1003
|
+
- TouchJoystick (interface): interface TouchJoystick
|
|
1004
|
+
- TouchButton (interface): interface TouchButton
|
|
1005
|
+
- TouchScheme (interface): interface TouchScheme
|
|
1006
|
+
- DeriveTouchSchemeOptions (interface): interface DeriveTouchSchemeOptions
|
|
1007
|
+
|
|
1008
|
+
### @jgengine/core/interaction/contextMenu
|
|
1009
|
+
|
|
1010
|
+
- contextVerb (function): function contextVerb(label: string, command: string, args?: Record<string, unknown>): ContextVerb
|
|
1011
|
+
- buildContextMenu (function): function buildContextMenu(input: BuildContextMenuInput): ContextMenu | null — Assemble a menu from a target's catalog verbs; null when the target lists none.
|
|
1012
|
+
- contextVerbInput (function): function contextVerbInput(menu: ContextMenu, verb: ContextVerb): Record<string, unknown> — Command input a chosen verb dispatches: the verb's own args, plus the target id and the world point, so a single handler can walk the actor to the target then perform it.
|
|
1013
|
+
- ContextTargetKind (type): type ContextTargetKind = "entity" | "object"
|
|
1014
|
+
- ContextVerb (interface): interface ContextVerb — One right-click verb: a label plus the command it dispatches (walk-then-act supported by args).
|
|
1015
|
+
- ContextMenu (interface): interface ContextMenu
|
|
1016
|
+
- BuildContextMenuInput (interface): interface BuildContextMenuInput
|
|
1017
|
+
|
|
1018
|
+
### @jgengine/core/interaction/proximityPrompt
|
|
1019
|
+
|
|
1020
|
+
- keybind (function): function keybind(actionId: string): KeybindPromptDisplay
|
|
1021
|
+
- gauge (function): function gauge(gaugeId: string): GaugePromptDisplay
|
|
1022
|
+
- label (function): function label(text: string): LabelPromptDisplay
|
|
1023
|
+
- command (function): function command(name: string, input?: unknown): PromptCommand
|
|
1024
|
+
- proximityPrompt (function): function proximityPrompt({ radius, display, invoke = null }: ProximityPromptConfig): ProximityPrompt
|
|
1025
|
+
- resolveActivePrompt (function): function resolveActivePrompt<T extends PositionedPrompt>(playerPosition: PromptPoint, prompts: readonly T[]): T | null — Nearest prompt strictly within its radius wins; a higher-priority prompt in range beats any lower-priority one regardless of distance; equal priority and distance keep the earliest prompt in the list.
|
|
1026
|
+
- promptDisplaysEqual (function): function promptDisplaysEqual(a: ProximityPromptDisplay, b: ProximityPromptDisplay): boolean
|
|
1027
|
+
- promptCommandsEqual (function): function promptCommandsEqual(a: PromptCommand | null, b: PromptCommand | null): boolean
|
|
1028
|
+
- positionedPromptsEqual (function): function positionedPromptsEqual(a: PositionedPrompt, b: PositionedPrompt): boolean
|
|
1029
|
+
- PromptPoint (interface): interface PromptPoint
|
|
1030
|
+
- KeybindPromptDisplay (interface): interface KeybindPromptDisplay
|
|
1031
|
+
- GaugePromptDisplay (interface): interface GaugePromptDisplay
|
|
1032
|
+
- LabelPromptDisplay (interface): interface LabelPromptDisplay
|
|
1033
|
+
- ProximityPromptDisplay (type): type ProximityPromptDisplay = KeybindPromptDisplay | GaugePromptDisplay | LabelPromptDisplay
|
|
1034
|
+
- PromptCommand (interface): interface PromptCommand
|
|
1035
|
+
- ProximityPrompt (interface): interface ProximityPrompt
|
|
1036
|
+
- ProximityPromptConfig (interface): interface ProximityPromptConfig
|
|
1037
|
+
- PositionedPrompt (interface): interface PositionedPrompt
|
|
1038
|
+
|
|
1039
|
+
### @jgengine/core/interaction/qte
|
|
1040
|
+
|
|
1041
|
+
- evaluateQteSequence (function): function evaluateQteSequence(steps: readonly QteStep[], inputs: readonly QteInputEvent[]): QteOutcome
|
|
1042
|
+
- pendingQteStep (function): function pendingQteStep(steps: readonly QteStep[], elapsedSeconds: number): QteStep | null
|
|
1043
|
+
- qteProgress (function): function qteProgress(steps: readonly QteStep[], elapsedSeconds: number): number
|
|
1044
|
+
- QteStep (interface): interface QteStep
|
|
1045
|
+
- QteInputEvent (interface): interface QteInputEvent
|
|
1046
|
+
- QteOutcome (type): type QteOutcome = | { status: "success" } | { status: "fail"; atStep: string; reason: "missed-window" | "wrong-action" | "too-early" }
|
|
1047
|
+
|
|
1048
|
+
### @jgengine/core/interaction/skillCheck
|
|
1049
|
+
|
|
1050
|
+
- skillCheckMarkerPosition (function): function skillCheckMarkerPosition(config: SkillCheckConfig, elapsedSeconds: number): number
|
|
1051
|
+
- skillCheckZoneAt (function): function skillCheckZoneAt(config: SkillCheckConfig, elapsedSeconds: number): SkillCheckZone
|
|
1052
|
+
- evaluateSkillCheck (function): function evaluateSkillCheck(config: SkillCheckConfig, elapsedSeconds: number): SkillCheckResult
|
|
1053
|
+
- SkillCheckZone (interface): interface SkillCheckZone
|
|
1054
|
+
- SkillCheckConfig (interface): interface SkillCheckConfig
|
|
1055
|
+
- SkillCheckResult (interface): interface SkillCheckResult
|
|
1056
|
+
|
|
1057
|
+
### @jgengine/core/inventory/inventoryModel
|
|
1058
|
+
|
|
1059
|
+
- createEmptyInventory (function): function createEmptyInventory(layout: InventoryLayout): InventoryState
|
|
1060
|
+
- putItem (function): function putItem(state: InventoryState, layout: InventoryLayout, traits: ItemTraits, itemId: string, count: number, options?: { slot?: number }): PutResult
|
|
1061
|
+
- takeItem (function): function takeItem(state: InventoryState, itemId: string, count: number): TakeResult
|
|
1062
|
+
- countItem (function): function countItem(state: InventoryState, itemId: string): number
|
|
1063
|
+
- hasItem (function): function hasItem(state: InventoryState, itemId: string, count: number): boolean
|
|
1064
|
+
- moveItem (function): function moveItem(from: InventoryState, fromSlot: number, to: InventoryState, toLayout: InventoryLayout, traits: ItemTraits, toSlot?: number): MoveResult
|
|
1065
|
+
- createInventorySet (function): function createInventorySet<TId extends string>(layouts: Record<TId, InventoryLayout>, traits: ItemTraits): InventorySet<TId>
|
|
1066
|
+
- ItemTraits (interface): interface ItemTraits
|
|
1067
|
+
- InventoryLayout (interface): interface InventoryLayout
|
|
1068
|
+
- InventorySlot (type): type InventorySlot = { itemId: string; count: number } | null
|
|
1069
|
+
- InventoryState (interface): interface InventoryState
|
|
1070
|
+
- PutResult (type): type PutResult = | { status: "ok"; state: InventoryState } | { status: "rejected"; reason: "no-space" | "wrong-kind" | "slot-occupied" | "invalid-slot" }
|
|
1071
|
+
- TakeResult (type): type TakeResult = { status: "ok"; state: InventoryState } | { status: "rejected"; reason: "insufficient" }
|
|
1072
|
+
- MoveResult (type): type MoveResult = | { status: "ok"; from: InventoryState; to: InventoryState } | { status: "rejected"; reason: "invalid-slot" | "empty-slot" | "wrong-kind" | "no-space" }
|
|
1073
|
+
- InventorySet (interface): interface InventorySet<TId extends string>
|
|
1074
|
+
|
|
1075
|
+
### @jgengine/core/inventory/shapedGrid
|
|
1076
|
+
|
|
1077
|
+
- normalizeFootprint (function): function normalizeFootprint(footprint: Footprint): Footprint
|
|
1078
|
+
- rotateFootprint (function): function rotateFootprint(footprint: Footprint, rotation: Rotation): Footprint
|
|
1079
|
+
- occupiedCells (function): function occupiedCells(footprint: Footprint, origin: Cell, rotation: Rotation): Cell[]
|
|
1080
|
+
- createShapedGrid (function): function createShapedGrid<T>(width: number, height: number): ShapedGrid<T>
|
|
1081
|
+
- canPlace (function): function canPlace<T>(grid: ShapedGrid<T>, footprint: Footprint, origin: Cell, rotation: Rotation, ignoreId?: string): ShapedRejection | null
|
|
1082
|
+
- placeShaped (function): function placeShaped<T>(grid: ShapedGrid<T>, item: ShapedItem<T>, origin: Cell, rotation: Rotation = 0): ShapedResult<T>
|
|
1083
|
+
- removeShaped (function): function removeShaped<T>(grid: ShapedGrid<T>, id: string): ShapedResult<T>
|
|
1084
|
+
- moveShaped (function): function moveShaped<T>(grid: ShapedGrid<T>, id: string, origin: Cell, rotation?: Rotation): ShapedResult<T>
|
|
1085
|
+
- cellOccupant (function): function cellOccupant<T>(grid: ShapedGrid<T>, cell: Cell): string | null
|
|
1086
|
+
- gridAdjacencyQuery (function): function gridAdjacencyQuery<T>(grid: ShapedGrid<T>, options: { diagonal?: boolean } = {}): GridAdjacencyQuery
|
|
1087
|
+
- cellFromPoint (function): function cellFromPoint(point: { x: number; y: number }, cellSize: number, gridOrigin: { x: number; y: number } = { x: 0, y: 0 }): Cell
|
|
1088
|
+
- Cell (type): type Cell = readonly [number, number]
|
|
1089
|
+
- Footprint (type): type Footprint = readonly Cell[]
|
|
1090
|
+
- Rotation (type): type Rotation = 0 | 1 | 2 | 3
|
|
1091
|
+
- ShapedItem (interface): interface ShapedItem<T>
|
|
1092
|
+
- Placement (interface): interface Placement<T>
|
|
1093
|
+
- ShapedGrid (interface): interface ShapedGrid<T>
|
|
1094
|
+
- ShapedRejection (type): type ShapedRejection = "out-of-bounds" | "overlap" | "duplicate-id" | "unknown-id"
|
|
1095
|
+
- ShapedPlaceResult (interface): interface ShapedPlaceResult<T>
|
|
1096
|
+
- ShapedRejected (interface): interface ShapedRejected
|
|
1097
|
+
- ShapedResult (type): type ShapedResult<T> = ShapedPlaceResult<T> | ShapedRejected
|
|
1098
|
+
- GridAdjacencyQuery (interface): interface GridAdjacencyQuery
|
|
1099
|
+
|
|
1100
|
+
### @jgengine/core/inventory/slotModel
|
|
1101
|
+
|
|
1102
|
+
- createSlots (function): function createSlots<T>(size: number): Slot<T>[]
|
|
1103
|
+
- normalizeSlots (function): function normalizeSlots<T>(values: SlotGrid<T>, size: number): Slot<T>[]
|
|
1104
|
+
- placeAt (function): function placeAt<T>(grid: SlotGrid<T>, index: number, value: T): SlotResult<T>
|
|
1105
|
+
- removeAt (function): function removeAt<T>(grid: SlotGrid<T>, index: number): SlotResult<T>
|
|
1106
|
+
- moveSlot (function): function moveSlot<T>(grid: SlotGrid<T>, from: number, to: number): SlotResult<T>
|
|
1107
|
+
- firstEmpty (function): function firstEmpty<T>(grid: SlotGrid<T>): number | null
|
|
1108
|
+
- insertFirst (function): function insertFirst<T>(grid: SlotGrid<T>, value: T): SlotResult<T>
|
|
1109
|
+
- compactSlots (function): function compactSlots<T>(grid: SlotGrid<T>): Slot<T>[]
|
|
1110
|
+
- countFilled (function): function countFilled<T>(grid: SlotGrid<T>): number
|
|
1111
|
+
- indexOfSlot (function): function indexOfSlot<T>(grid: SlotGrid<T>, match: (value: T, index: number) => boolean): number | null
|
|
1112
|
+
- Slot (type): type Slot<T> = T | null
|
|
1113
|
+
- SlotGrid (type): type SlotGrid<T> = readonly Slot<T>[]
|
|
1114
|
+
- SlotResult (type): type SlotResult<T> = | { status: "ok"; grid: Slot<T>[] } | { status: "rejected"; reason: "invalid-slot" | "slot-occupied" | "empty-slot" }
|
|
1115
|
+
|
|
1116
|
+
### @jgengine/core/inventory/storageTier
|
|
1117
|
+
|
|
1118
|
+
- tierOf (function): function tierOf(tiers: Record<string, StorageTier>, inventoryId: string): StorageTier
|
|
1119
|
+
- partitionOnDeath (function): function partitionOnDeath(containers: readonly ContainerSnapshot[]): DeathPartition
|
|
1120
|
+
- createDeliveryQueue (function): function createDeliveryQueue(): DeliveryQueue
|
|
1121
|
+
- insureLost (function): function insureLost(lost: readonly ItemStack[], policy: InsurancePolicy, userId: string, now: number, rng: () => number = Math.random): ScheduledDelivery | null
|
|
1122
|
+
- resolveConsolation (function): function resolveConsolation(policy: ConsolationPolicy, partition: DeathPartition): { loadoutId: string } | null
|
|
1123
|
+
- StorageTier (type): type StorageTier = "carried" | "banked"
|
|
1124
|
+
- ItemStack (interface): interface ItemStack
|
|
1125
|
+
- ContainerSnapshot (interface): interface ContainerSnapshot
|
|
1126
|
+
- DeathPartition (interface): interface DeathPartition
|
|
1127
|
+
- DeliveryEntry (interface): interface DeliveryEntry
|
|
1128
|
+
- ScheduledDelivery (interface): interface ScheduledDelivery
|
|
1129
|
+
- DeliveryQueue (interface): interface DeliveryQueue
|
|
1130
|
+
- InsurancePolicy (interface): interface InsurancePolicy
|
|
1131
|
+
- ConsolationPolicy (interface): interface ConsolationPolicy
|
|
1132
|
+
|
|
1133
|
+
### @jgengine/core/item/affix
|
|
1134
|
+
|
|
1135
|
+
- createAffixRoller (function): function createAffixRoller(config: RollerConfig): AffixRoller
|
|
1136
|
+
- AffixOp (type): type AffixOp = "add" | "mul"
|
|
1137
|
+
- AffixDef (interface): interface AffixDef
|
|
1138
|
+
- AffixPool (interface): interface AffixPool
|
|
1139
|
+
- RarityTier (interface): interface RarityTier
|
|
1140
|
+
- ItemBaseDef (interface): interface ItemBaseDef
|
|
1141
|
+
- RollerConfig (interface): interface RollerConfig
|
|
1142
|
+
- RolledAffix (interface): interface RolledAffix
|
|
1143
|
+
- RolledItem (interface): interface RolledItem
|
|
1144
|
+
- seededRng (function): function seededRng(seed: string | number): () => number
|
|
1145
|
+
- AffixRoller (interface): interface AffixRoller
|
|
1146
|
+
|
|
1147
|
+
### @jgengine/core/item/durability
|
|
1148
|
+
|
|
1149
|
+
- createDurability (function): function createDurability(spec: DurabilitySpec): DurabilityState
|
|
1150
|
+
- durabilityFraction (function): function durabilityFraction(state: DurabilityState): number
|
|
1151
|
+
- isBroken (function): function isBroken(state: DurabilityState): boolean
|
|
1152
|
+
- isDisabled (function): function isDisabled(spec: DurabilitySpec, state: DurabilityState): boolean
|
|
1153
|
+
- wearAmount (function): function wearAmount(spec: DurabilitySpec, kind: WearKind): number
|
|
1154
|
+
- applyWear (function): function applyWear(state: DurabilityState, amount: number): DurabilityState
|
|
1155
|
+
- wear (function): function wear(spec: DurabilitySpec, state: DurabilityState, kind: WearKind, times = 1): DurabilityState
|
|
1156
|
+
- canRepairAt (function): function canRepairAt(spec: DurabilitySpec, stationId?: string): boolean
|
|
1157
|
+
- repairQuote (function): function repairQuote(spec: DurabilitySpec, state: DurabilityState, options?: { to?: number; station?: string }): RepairQuote | null
|
|
1158
|
+
- createDurabilityTracker (function): function createDurabilityTracker(): DurabilityTracker
|
|
1159
|
+
- RepairMaterial (interface): interface RepairMaterial
|
|
1160
|
+
- RepairSpec (interface): interface RepairSpec
|
|
1161
|
+
- DurabilitySpec (interface): interface DurabilitySpec
|
|
1162
|
+
- DurabilityState (interface): interface DurabilityState
|
|
1163
|
+
- WearKind (type): type WearKind = "use" | "hit"
|
|
1164
|
+
- RepairCost (interface): interface RepairCost
|
|
1165
|
+
- RepairQuote (interface): interface RepairQuote
|
|
1166
|
+
- DurabilityTracker (interface): interface DurabilityTracker
|
|
1167
|
+
|
|
1168
|
+
### @jgengine/core/item/modularItem
|
|
1169
|
+
|
|
1170
|
+
- slotById (function): function slotById(def: ModularItemDef, slotId: string): MountSlotDef | null
|
|
1171
|
+
- slotAccepts (function): function slotAccepts(slot: MountSlotDef, category: string): boolean
|
|
1172
|
+
- install (function): function install(def: ModularItemDef, installed: readonly InstalledPart[], slotId: string, part: PartDef): InstallResult
|
|
1173
|
+
- uninstall (function): function uninstall(installed: readonly InstalledPart[], slotId: string): readonly InstalledPart[]
|
|
1174
|
+
- partInSlot (function): function partInSlot(installed: readonly InstalledPart[], slotId: string): PartDef | null
|
|
1175
|
+
- computeEffectiveStats (function): function computeEffectiveStats(def: ModularItemDef, installed: readonly InstalledPart[]): Record<string, number>
|
|
1176
|
+
- missingRequiredSlots (function): function missingRequiredSlots(def: ModularItemDef, installed: readonly InstalledPart[]): string[]
|
|
1177
|
+
- isComplete (function): function isComplete(def: ModularItemDef, installed: readonly InstalledPart[]): boolean
|
|
1178
|
+
- createModularItem (function): function createModularItem(def: ModularItemDef, initial: readonly InstalledPart[] = []): ModularItem
|
|
1179
|
+
- MountSlotDef (interface): interface MountSlotDef
|
|
1180
|
+
- ModularItemDef (interface): interface ModularItemDef
|
|
1181
|
+
- PartDef (interface): interface PartDef
|
|
1182
|
+
- InstalledPart (interface): interface InstalledPart
|
|
1183
|
+
- InstallResult (type): type InstallResult = | { status: "ok"; installed: readonly InstalledPart[] } | { status: "rejected"; reason: "unknown-slot" | "wrong-category" | "slot-occupied" }
|
|
1184
|
+
- ModularItem (interface): interface ModularItem
|
|
1185
|
+
|
|
1186
|
+
### @jgengine/core/item/use
|
|
1187
|
+
|
|
1188
|
+
- createItemUse (function): function createItemUse<TState>(resolveUse: (itemId: string) => string | null | undefined): ItemUse<TState>
|
|
1189
|
+
- ItemUseInput (interface): interface ItemUseInput
|
|
1190
|
+
- ItemUseRejection (interface): interface ItemUseRejection
|
|
1191
|
+
- ItemUseResult (interface): interface ItemUseResult<TState>
|
|
1192
|
+
- ItemUseHandler (interface): interface ItemUseHandler<TState>
|
|
1193
|
+
- ItemUse (interface): interface ItemUse<TState>
|
|
1194
|
+
|
|
1195
|
+
### @jgengine/core/item/weapon
|
|
1196
|
+
|
|
1197
|
+
- getWeaponStat (function): function getWeaponStat(entry: WeaponEntry | null | undefined, stat: string): number | null
|
|
1198
|
+
- createWeaponStats (function): function createWeaponStats(resolveEntry: (itemId: string) => WeaponEntry | null | undefined): WeaponStats
|
|
1199
|
+
- WeaponEntry (interface): interface WeaponEntry
|
|
1200
|
+
- WeaponStats (interface): interface WeaponStats
|
|
1201
|
+
|
|
1202
|
+
### @jgengine/core/meta/changelog
|
|
1203
|
+
|
|
1204
|
+
- VERSION (const): const VERSION: "0.8.0"
|
|
1205
|
+
- ChangelogEntry (interface): interface ChangelogEntry
|
|
1206
|
+
- CHANGELOG (const): const CHANGELOG: Record<string, ChangelogEntry>
|
|
1207
|
+
|
|
1208
|
+
### @jgengine/core/movement/avatarGait
|
|
1209
|
+
|
|
1210
|
+
- advanceGaitPhase (function): function advanceGaitPhase(phase: number, speedUnitsPerSec: number, dtSec: number, tuning: GaitTuning = DEFAULT_GAIT_TUNING): number
|
|
1211
|
+
- gaitBobOffset (function): function gaitBobOffset(phase: number, speedUnitsPerSec: number, tuning: GaitTuning = DEFAULT_GAIT_TUNING): number — Vertical hop per footfall — two peaks per stride cycle.
|
|
1212
|
+
- gaitSwayAngle (function): function gaitSwayAngle(phase: number, speedUnitsPerSec: number, tuning: GaitTuning = DEFAULT_GAIT_TUNING): number — Signed side-to-side lean alternating with each footfall.
|
|
1213
|
+
- GaitTuning (interface): interface GaitTuning
|
|
1214
|
+
- DEFAULT_GAIT_TUNING (const): const DEFAULT_GAIT_TUNING: GaitTuning
|
|
1215
|
+
|
|
1216
|
+
### @jgengine/core/movement/cameraRig
|
|
1217
|
+
|
|
1218
|
+
- clampCameraPitch (function): function clampCameraPitch(pitch: number): number
|
|
1219
|
+
- resolveTargetEyeHeight (function): function resolveTargetEyeHeight(crouching: boolean, grounded: boolean): number
|
|
1220
|
+
- advanceEyeHeight (function): function advanceEyeHeight(currentEyeHeight: number, targetEyeHeight: number, deltaSeconds: number): number
|
|
1221
|
+
- resolveHeadBobRate (function): function resolveHeadBobRate(crouching: boolean, running: boolean): number
|
|
1222
|
+
- resolveHeadBobAmplitude (function): function resolveHeadBobAmplitude(crouching: boolean, running: boolean, grounded: boolean): number
|
|
1223
|
+
- advanceHeadBobTime (function): function advanceHeadBobTime(currentHeadBobTime: number, moving: boolean, grounded: boolean, bobRate: number, deltaSeconds: number): number
|
|
1224
|
+
- resolveHeadBobOffset (function): function resolveHeadBobOffset(headBobTime: number, bobAmplitude: number): number
|
|
1225
|
+
- resolveCameraTrackingBlend (function): function resolveCameraTrackingBlend(deltaSeconds: number): number
|
|
1226
|
+
- CAMERA_PITCH_LIMIT (const): const CAMERA_PITCH_LIMIT: 1.45
|
|
1227
|
+
|
|
1228
|
+
### @jgengine/core/movement/dash
|
|
1229
|
+
|
|
1230
|
+
- iframeActive (function): function iframeActive(config: DashConfig, elapsedMs: number): boolean
|
|
1231
|
+
- dashEase (function): function dashEase(t: number): number
|
|
1232
|
+
- dashOffset (function): function dashOffset(config: DashConfig, dir: DashDirection, elapsedMs: number): [number, number, number]
|
|
1233
|
+
- createDashState (function): function createDashState(config: DashConfig): DashState
|
|
1234
|
+
- DashConfig (interface): interface DashConfig
|
|
1235
|
+
- DashDirection (interface): interface DashDirection
|
|
1236
|
+
- DashRejection (type): type DashRejection = { reason: "no-stamina" | "cooldown" | "dashing" }
|
|
1237
|
+
- DashBurst (interface): interface DashBurst
|
|
1238
|
+
- DashState (interface): interface DashState
|
|
1239
|
+
|
|
1240
|
+
### @jgengine/core/movement/movementModel
|
|
1241
|
+
|
|
1242
|
+
- createEmptyMovementKeys (function): function createEmptyMovementKeys(): MovementKeysState
|
|
1243
|
+
- resolveMovementIntent (function): function resolveMovementIntent(keys: MovementKeysState, canMove: boolean): MovementIntent — Translate the set of held keys into an intent. When `canMove` is false (a menu is open, the world is paused) the avatar is fully idle so it never drifts behind an overlay.
|
|
1244
|
+
- createPlayerMotionState (function): function createPlayerMotionState(): PlayerMotionState
|
|
1245
|
+
- advancePlayerMotion (function): function advancePlayerMotion(motion: PlayerMotionState, intent: MovementIntent, forwardX: number, forwardZ: number, baseSpeed: number, rawDeltaSeconds: number, tuning?: MovementTuningOverrides): MovementFrameStep — Advance one frame of avatar kinematics. Mutates `motion` (velocity, jump, gravity, grounded) and returns the horizontal step to commit through the collision-resolving stepper.
|
|
1246
|
+
- resolveObstacleStep (function): function resolveObstacleStep(current: readonly [number, number, number], stepX: number, stepZ: number, obstacles: readonly CollisionObstacle[], playerRadius: number = DEFAULT_OBSTACLE_PLAYER_RADIUS): MovementFrameStep — Resolve a horizontal step against nearby placed objects with classic axis-separated sliding (try X, then Z against the post-X position) so walking into a wall stops the blocked axis but keeps sliding along the other. Obstacles are treated as circle-vs-AABB in the X/Z plane, inflated by `playerRadius`; an obstacle whose vertical span misses the player's feet-to-head span is skipped entirely. Callers should pre-filter to nearby objects — this also early-exits per obstacle on horizontal distance.
|
|
1247
|
+
- constrainStepToAxis (function): function constrainStepToAxis(stepX: number, stepZ: number, axis: "x" | "z"): MovementFrameStep — Zeroes the off-axis component so travel is locked to a single world axis (`PlayerMovementConfig.mode: "axis"`).
|
|
1248
|
+
- snapPositionToGrid (function): function snapPositionToGrid(x: number, z: number, cellSize: number): [number, number] — Snap a world position to its containing cell's center, matching the bounds-free form of `navGrid`'s and `tacticalGrid`'s `floor` + half-cell convention (`PlayerMovementConfig.mode: "grid"`).
|
|
1249
|
+
- cameraYawToAvatarBodyYaw (function): function cameraYawToAvatarBodyYaw(cameraYaw: number): number — Camera yaw looks along -Z; character mesh faces +Z at body rotation.y = 0.
|
|
1250
|
+
- MovementKey (type): type MovementKey = "w" | "a" | "s" | "d" | "shift" | "control" | "c" | "space" — Movement core (pure model).
|
|
1251
|
+
- MovementKeysState (type): type MovementKeysState = Record<MovementKey, boolean>
|
|
1252
|
+
- MovementIntent (interface): interface MovementIntent — Frame-independent description of what the player is asking the avatar to do. `forward`/`right` are in the range -1..1 in the avatar's local frame; the controller rotates them into world space against the camera each frame.
|
|
1253
|
+
- MOVEMENT_TUNING (const): const MOVEMENT_TUNING: { readonly standEyeHeight: 1.7; readonly crouchEyeHeight: 1.15; readonly walkSpeedMultiplier: 1.75; readonly runSpeedMultiplier: 2.25; readonly crouchSpeedMultiplier: 0.45; readonly groundAcceleration: 26; readonly airAcceleration: 12; readonly groundFriction: 18; readonly jum… — Kinematics + feel tuning for the first-person controller. Centralised here so movement feel lives in one place rather than scattered through the renderer.
|
|
1254
|
+
- PlayerMotionState (interface): interface PlayerMotionState — Mutable kinematic state carried between frames by the controller. Kept here so the velocity / jump / gravity integration is a pure function testable without a renderer — the controller just owns the ref.
|
|
1255
|
+
- MovementFrameStep (interface): interface MovementFrameStep — Horizontal step (world units) the avatar should commit this frame.
|
|
1256
|
+
- MovementTuningOverrides (interface): interface MovementTuningOverrides — Per-game overrides for the gravity/jump feel, sourced from `GameDefinition.physics`. Omitted fields fall back to {@link MOVEMENT_TUNING}.
|
|
1257
|
+
- CollisionObstacle (interface): interface CollisionObstacle — A placed scene object the walking player collides against as a circle-vs-AABB obstacle.
|
|
1258
|
+
- MAX_JUMP_OFFSET (const): const MAX_JUMP_OFFSET: 1.15 — Peak jump height from MOVEMENT_TUNING.jumpVelocity + gravity, with small buffer.
|
|
1259
|
+
|
|
1260
|
+
### @jgengine/core/movement/poseState
|
|
1261
|
+
|
|
1262
|
+
- createPoseState (function): function createPoseState(resolveAllowed: (instanceId: string) => PoseAllowedStates | null | undefined): PoseState
|
|
1263
|
+
- MovementPose (type): type MovementPose = "standing" | "crouch" | "prone" | "running"
|
|
1264
|
+
- AimMode (type): type AimMode = "hip" | "ads"
|
|
1265
|
+
- PoseHitbox (interface): interface PoseHitbox
|
|
1266
|
+
- POSE_HITBOX (const): const POSE_HITBOX: Record<MovementPose, PoseHitbox>
|
|
1267
|
+
- PoseAllowedStates (interface): interface PoseAllowedStates
|
|
1268
|
+
- PoseRejection (interface): interface PoseRejection
|
|
1269
|
+
- PoseState (interface): interface PoseState
|
|
1270
|
+
|
|
1271
|
+
### @jgengine/core/movement/voxelController
|
|
1272
|
+
|
|
1273
|
+
- createVoxelPlayerBody (function): function createVoxelPlayerBody(x: number, y: number, z: number): VoxelPlayerBody
|
|
1274
|
+
- advanceVoxelPlayer (function): function advanceVoxelPlayer(body: VoxelPlayerBody, intent: MovementIntent, forwardX: number, forwardZ: number, baseSpeed: number, rawDeltaSeconds: number, isSolid: SolidQuery, dims: VoxelPlayerDims = DEFAULT_VOXEL_DIMS, tuning?: MovementTuningOverrides, groundHeight?: GroundHeightQuery): void — Advance one frame of voxel-collided player kinematics. Mutates `body` (position, velocity, grounded) in place.
|
|
1275
|
+
- SolidQuery (type): type SolidQuery = (x: number, y: number, z: number) => boolean
|
|
1276
|
+
- VoxelPlayerDims (interface): interface VoxelPlayerDims — Footprint + height of the player box, in world units.
|
|
1277
|
+
- DEFAULT_VOXEL_DIMS (const): const DEFAULT_VOXEL_DIMS: VoxelPlayerDims
|
|
1278
|
+
- VoxelPlayerBody (interface): interface VoxelPlayerBody — Mutable kinematic state carried between frames by the voxel controller.
|
|
1279
|
+
- GroundHeightQuery (type): type GroundHeightQuery = (x: number, z: number) => number — Samples the terrain floor height at a point; unset means flat ground plays no part in support.
|
|
1280
|
+
|
|
1281
|
+
### @jgengine/core/multiplayer/chatContract
|
|
1282
|
+
|
|
1283
|
+
- createLocalChatTransport (function): function createLocalChatTransport(options?: { userId?: string; historyLimit?: number; now?: () => number; }): { transport: ChatTransport; sync: ChatSync; actions: ChatActions; }
|
|
1284
|
+
- ChatSendArgs (interface): interface ChatSendArgs
|
|
1285
|
+
- ChatSendOutcome (interface): interface ChatSendOutcome
|
|
1286
|
+
- ChatActions (interface): interface ChatActions
|
|
1287
|
+
- ChatTransport (interface): interface ChatTransport — Backend seam for remote text chat, mirroring PresenceTransport: the use* members are called as React hooks by consumers, so a mounted transport must never change identity — remount the subtree to switch backends. useMessages returns undefined while the subscription is loading and the channel's recent history once live.
|
|
1288
|
+
- ChatSync (interface): interface ChatSync — Callback seam for backends that cannot host React hooks (e.g. the ws client): subscribe delivers the channel's recent history on every change; send resolves with the host's verdict.
|
|
1289
|
+
|
|
1290
|
+
### @jgengine/core/multiplayer/combatSnapshot
|
|
1291
|
+
|
|
1292
|
+
- serializeBoard (function): function serializeBoard(args: SerializeBoardArgs): BoardSnapshot
|
|
1293
|
+
- cloneSnapshot (function): function cloneSnapshot(snapshot: BoardSnapshot): BoardSnapshot
|
|
1294
|
+
- replayCombat (function): function replayCombat(a: BoardSnapshot, b: BoardSnapshot, rules: CombatRules): ReplayResult
|
|
1295
|
+
- SnapshotUnit (interface): interface SnapshotUnit
|
|
1296
|
+
- BoardSnapshot (interface): interface BoardSnapshot
|
|
1297
|
+
- SerializeBoardArgs (interface): interface SerializeBoardArgs
|
|
1298
|
+
- CombatRules (interface): interface CombatRules
|
|
1299
|
+
- CombatSide (type): type CombatSide = "a" | "b" | "draw"
|
|
1300
|
+
- ReplayBlow (interface): interface ReplayBlow
|
|
1301
|
+
- ReplayResult (interface): interface ReplayResult
|
|
1302
|
+
|
|
1303
|
+
### @jgengine/core/multiplayer/identity
|
|
1304
|
+
|
|
1305
|
+
- sessionPlayer (function): function sessionPlayer(session: AuthSession): PlayerIdentity
|
|
1306
|
+
- resolveGuestSession (function): function resolveGuestSession(seed?: string): AuthSession
|
|
1307
|
+
- AuthSession (interface): interface AuthSession
|
|
1308
|
+
- PlayerIdentity (interface): interface PlayerIdentity
|
|
1309
|
+
|
|
1310
|
+
### @jgengine/core/multiplayer/lagCompensation
|
|
1311
|
+
|
|
1312
|
+
- createPositionHistory (function): function createPositionHistory(config: PositionHistoryConfig): PositionHistory
|
|
1313
|
+
- rewindTimestamp (function): function rewindTimestamp(nowMs: number, rttMs: number, interpDelayMs: number): number
|
|
1314
|
+
- raySphereDistance (function): function raySphereDistance(ray: HitscanRay, center: Vec3, radius: number): number | null
|
|
1315
|
+
- resolveHitscan (function): function resolveHitscan(history: PositionHistory, targets: readonly HitscanTarget[], ray: HitscanRay, atMs: number): HitscanHit | null
|
|
1316
|
+
- Vec3 (interface): interface Vec3
|
|
1317
|
+
- PositionSample (interface): interface PositionSample extends Vec3
|
|
1318
|
+
- PositionHistoryConfig (interface): interface PositionHistoryConfig
|
|
1319
|
+
- PositionHistory (class): class PositionHistory
|
|
1320
|
+
- HitscanRay (interface): interface HitscanRay
|
|
1321
|
+
- HitscanTarget (interface): interface HitscanTarget
|
|
1322
|
+
- HitscanHit (interface): interface HitscanHit
|
|
1323
|
+
|
|
1324
|
+
### @jgengine/core/multiplayer/matchmaking
|
|
1325
|
+
|
|
1326
|
+
- normalizeJoinCode (function): function normalizeJoinCode(code: string): string
|
|
1327
|
+
- generateJoinCode (function): function generateJoinCode(random: () => number, length = 6): string
|
|
1328
|
+
- hasSpace (function): function hasSpace(listing: SessionListing): boolean
|
|
1329
|
+
- matchesFilter (function): function matchesFilter(listing: SessionListing, filter: MatchFilter): boolean
|
|
1330
|
+
- browseSessions (function): function browseSessions(listings: readonly SessionListing[], filter: MatchFilter = {}, options: BrowseOptions = {}): SessionListing[]
|
|
1331
|
+
- findByJoinCode (function): function findByJoinCode(listings: readonly SessionListing[], code: string): SessionListing | null
|
|
1332
|
+
- quickMatch (function): function quickMatch(listings: readonly SessionListing[], filter: MatchFilter = {}): SessionListing | null
|
|
1333
|
+
- SessionVisibility (type): type SessionVisibility = "public" | "private"
|
|
1334
|
+
- SessionStatus (type): type SessionStatus = "open" | "running" | "closed"
|
|
1335
|
+
- SessionListing (interface): interface SessionListing
|
|
1336
|
+
- MatchFilter (interface): interface MatchFilter
|
|
1337
|
+
- BrowseOptions (interface): interface BrowseOptions
|
|
1338
|
+
|
|
1339
|
+
### @jgengine/core/multiplayer/poseSyncGate
|
|
1340
|
+
|
|
1341
|
+
- createPoseSyncGate (function): function createPoseSyncGate(tuning: PoseSyncTuning): PoseSyncGate
|
|
1342
|
+
- PlayerPose (interface): interface PlayerPose
|
|
1343
|
+
- PoseSyncTuning (interface): interface PoseSyncTuning
|
|
1344
|
+
- PoseSyncGate (interface): interface PoseSyncGate
|
|
1345
|
+
|
|
1346
|
+
### @jgengine/core/multiplayer/presenceContract
|
|
1347
|
+
|
|
1348
|
+
- createLocalPresenceTransport (function): function createLocalPresenceTransport<TRow, TLocation, TGameId extends string = string>(): { transport: PresenceTransport<TRow, TLocation, TGameId>; actions: PresenceActions<TGameId>; }
|
|
1349
|
+
- PresencePosition (interface): interface PresencePosition
|
|
1350
|
+
- PresencePose (interface): interface PresencePose
|
|
1351
|
+
- PresenceSession (interface): interface PresenceSession<TGameId extends string = string>
|
|
1352
|
+
- EnsurePresenceArgs (interface): interface EnsurePresenceArgs<TGameId extends string = string>
|
|
1353
|
+
- EnsurePresenceResult (interface): interface EnsurePresenceResult
|
|
1354
|
+
- LeavePresenceArgs (interface): interface LeavePresenceArgs<TGameId extends string = string>
|
|
1355
|
+
- PresenceFeeds (interface): interface PresenceFeeds<TRow, TLocation>
|
|
1356
|
+
- PresenceActions (interface): interface PresenceActions<TGameId extends string = string>
|
|
1357
|
+
- PresenceTransport (interface): interface PresenceTransport<TRow, TLocation, TGameId extends string = string> — Backend seam for multiplayer presence. Feeds are reactive data and change identity whenever any player's pose updates; actions MUST be identity-stable for the lifetime of a mounted session so join/leave lifecycle effects can depend on them without re-running per pose tick. The use* members are called as React hooks by consumers, so a mounted transport must never change identity — remount the subtree to switch backends.
|
|
1358
|
+
|
|
1359
|
+
### @jgengine/core/multiplayer/presenceModel
|
|
1360
|
+
|
|
1361
|
+
- decidePoseSync (function): function decidePoseSync(current: PresencePoseState, incoming: IncomingPose, rules: PoseSyncRules, nowMs: number): PoseSyncDecision
|
|
1362
|
+
- shouldRefreshKeepAlive (function): function shouldRefreshKeepAlive(lastSeenAtMs: number | undefined, nowMs: number, rules: Pick<PoseSyncRules, "keepAliveRefreshMs">): boolean
|
|
1363
|
+
- shouldPersistWorldSnapshot (function): function shouldPersistWorldSnapshot(lastSavedAtMs: number | undefined, nowMs: number, intervalMs: number): boolean
|
|
1364
|
+
- resolveActivePresence (function): function resolveActivePresence<T extends { revokedAt?: number; lastSeenAt?: number }>(rows: readonly T[]): ActivePresenceResolution<T>
|
|
1365
|
+
- isPresenceExpired (function): function isPresenceExpired(lastSeenAtMs: number, nowMs: number, idleCutoffMs: number): boolean
|
|
1366
|
+
- pickReusablePresence (function): function pickReusablePresence<T extends { lastSeenAt?: number }>(rows: readonly T[]): T | undefined — Most-recently-seen row across an actor's rows, to reuse instead of inserting a new one.
|
|
1367
|
+
- PresencePoseState (interface): interface PresencePoseState
|
|
1368
|
+
- IncomingPose (interface): interface IncomingPose
|
|
1369
|
+
- PoseSyncRules (interface): interface PoseSyncRules
|
|
1370
|
+
- PoseSyncDecision (interface): interface PoseSyncDecision
|
|
1371
|
+
- ActivePresenceResolution (interface): interface ActivePresenceResolution<T>
|
|
1372
|
+
|
|
1373
|
+
### @jgengine/core/multiplayer/simultaneousCommit
|
|
1374
|
+
|
|
1375
|
+
- createCommitRound (function): function createCommitRound<TAction>(config: CommitRoundConfig): CommitRound<TAction>
|
|
1376
|
+
- resolveCommits (function): function resolveCommits<TAction, TResult>(reveals: readonly SealedCommit<TAction>[], resolver: (ordered: readonly SealedCommit<TAction>[]) => TResult): TResult
|
|
1377
|
+
- CommitPhase (type): type CommitPhase = "collecting" | "revealed"
|
|
1378
|
+
- SealedCommit (interface): interface SealedCommit<TAction>
|
|
1379
|
+
- CommitRoundConfig (interface): interface CommitRoundConfig
|
|
1380
|
+
- SealResult (type): type SealResult = | { ok: true; allSealed: boolean } | { ok: false; reason: "unknown_participant" | "already_sealed" | "already_revealed" }
|
|
1381
|
+
- CommitRound (class): class CommitRound<TAction>
|
|
1382
|
+
|
|
1383
|
+
### @jgengine/core/multiplayer/voiceContract
|
|
1384
|
+
|
|
1385
|
+
- createLocalVoiceTransport (function): function createLocalVoiceTransport(options?: { userId?: string }): { transport: VoiceTransport; participants(channelId: string): readonly VoiceParticipant[]; }
|
|
1386
|
+
- createPushToTalk (function): function createPushToTalk(config?: { mode?: PushToTalkMode; onChange?: (transmitting: boolean) => void; }): PushToTalk
|
|
1387
|
+
- VoiceParticipant (interface): interface VoiceParticipant
|
|
1388
|
+
- VoiceRoute (interface): interface VoiceRoute
|
|
1389
|
+
- VoiceTransport (interface): interface VoiceTransport — Signaling seam for voice: who is in a channel and which media stream descriptor they published. The media plane (WebRTC, SFU, or anything else that moves audio bytes) stays behind this seam, host-supplied — the engine never touches it. subscribers delivers the channel roster on every change, starting with the current roster.
|
|
1390
|
+
- PushToTalkMode (type): type PushToTalkMode = "hold" | "toggle" | "openMic"
|
|
1391
|
+
- PushToTalkStatus (type): type PushToTalkStatus = "idle" | "keyed" | "open"
|
|
1392
|
+
- PushToTalk (interface): interface PushToTalk
|
|
1393
|
+
|
|
1394
|
+
### @jgengine/core/nav/navConstrain
|
|
1395
|
+
|
|
1396
|
+
- constrainToNavGrid (function): function constrainToNavGrid(grid: NavGrid, options?: NavConstrainOptions): (proposed: NavConstrainProposed, entity: NavConstrainEntity) => NavConstrainProposed | null
|
|
1397
|
+
- NavConstrainProposed (interface): interface NavConstrainProposed
|
|
1398
|
+
- NavConstrainEntity (interface): interface NavConstrainEntity
|
|
1399
|
+
- NavConstrainOptions (interface): interface NavConstrainOptions
|
|
1400
|
+
|
|
1401
|
+
### @jgengine/core/nav/navFromEnvironment
|
|
1402
|
+
|
|
1403
|
+
- populateNavGridFromEnvironment (function): function populateNavGridFromEnvironment(grid: NavObstacleGrid, world: EnvironmentWorldFeature): number — Expands every structure descriptor on an environment world feature into its generated buildings and blocks their footprints on `grid`. Returns the number of buildings blocked.
|
|
1404
|
+
- NavObstacleGrid (interface): interface NavObstacleGrid
|
|
1405
|
+
|
|
1406
|
+
### @jgengine/core/nav/navGrid
|
|
1407
|
+
|
|
1408
|
+
- createNavGrid (function): function createNavGrid(config: NavGridConfig): NavGrid
|
|
1409
|
+
- findPath (function): function findPath(grid: NavGrid, from: NavPoint, to: NavPoint, options: FindPathOptions = {}): NavPoint[] | null — A* over the walkable grid. Returns a polyline of world-space `[x, z]` waypoints from `from` to `to`, or `null` when no route exists. Blocked start/goal snap to the nearest walkable cell so a click on an obstacle still routes to its edge.
|
|
1410
|
+
- slopeStepCost (function): function slopeStepCost(field: { sampleHeight(x: number, z: number): number }, weight = DEFAULT_SLOPE_STEP_WEIGHT): (from: NavPoint, to: NavPoint) => number — `FindPathOptions.stepCost` factory that penalizes steep terrain: cost is `1 + weight * |Δheight| / horizontalDistance`, so with the default weight a 45° slope roughly doubles the step cost.
|
|
1411
|
+
- smoothPath (function): function smoothPath(grid: NavGrid, points: readonly NavPoint[]): NavPoint[] — Remove waypoints the mover can skip because it has clear line-of-sight past them.
|
|
1412
|
+
- NavPoint (type): type NavPoint = readonly [number, number]
|
|
1413
|
+
- NavGridConfig (interface): interface NavGridConfig
|
|
1414
|
+
- NavCell (interface): interface NavCell
|
|
1415
|
+
- FindPathOptions (interface): interface FindPathOptions
|
|
1416
|
+
- NavGrid (interface): interface NavGrid
|
|
1417
|
+
|
|
1418
|
+
### @jgengine/core/nav/pathFollow
|
|
1419
|
+
|
|
1420
|
+
- pathFromNav (function): function pathFromNav(points: readonly NavPoint[], elevation: number | HeightSampler = 0, offset = 0): Waypoint[]
|
|
1421
|
+
- createPathFollow (function): function createPathFollow(config: PathFollowConfig): PathFollowState
|
|
1422
|
+
- advancePathFollow (function): function advancePathFollow(config: PathFollowConfig, state: PathFollowState, dt: number): PathFollowState — Advance a path-follower by `speed * dt` along its authored polyline. Pure — returns the next state. Crosses multiple waypoints in one step, loops when configured, and reports `done` at the end of a non-looping path. No navmesh required (#52); feed it a navmesh route via `pathFromNav` for click-to-move (#51).
|
|
1423
|
+
- Waypoint (type): type Waypoint = readonly [number, number, number]
|
|
1424
|
+
- PathFollowConfig (interface): interface PathFollowConfig
|
|
1425
|
+
- PathFollowState (interface): interface PathFollowState
|
|
1426
|
+
- HeightSampler (interface): interface HeightSampler
|
|
1427
|
+
|
|
1428
|
+
### @jgengine/core/physics/ballisticSweep
|
|
1429
|
+
|
|
1430
|
+
- createBallisticSweep (function): function createBallisticSweep(world: PhysicsWorld, options: BallisticSweepOptions = {}): BallisticSweep — Marches the closed-form arc (constant gravity, straight lateral) through `world` and reports the first sample inside any live body's AABB — sleeping bodies included — refined by one bisection between the last clear sample and the hit sample. Returns `null` when the whole arc is clear.
|
|
1431
|
+
- BallisticSweepHit (interface): interface BallisticSweepHit
|
|
1432
|
+
- BallisticSweep (type): type BallisticSweep = ( origin: readonly [number, number, number], velocity: readonly [number, number, number], gravity: number, maxTime: number, ) => BallisticSweepHit | null
|
|
1433
|
+
- BallisticSweepOptions (interface): interface BallisticSweepOptions
|
|
1434
|
+
|
|
1435
|
+
### @jgengine/core/physics/buoyancy
|
|
1436
|
+
|
|
1437
|
+
- createBuoyantBody (function): function createBuoyantBody(world: PhysicsWorld, config: BuoyantBodyConfig): BuoyantBody
|
|
1438
|
+
- BuoyantBodyConfig (interface): interface BuoyantBodyConfig
|
|
1439
|
+
- BuoyantBody (class): class BuoyantBody — Floats a {@link PhysicsWorld} body on a CPU {@link WaterSurface}: each hull sample point pushes the body up by its submerged depth (Archimedes, coarse), with vertical and horizontal water drag so the hull settles at the waterline and rides the Gerstner waves. Passing an {@link AxisInput} drives it as a boat — throttle thrusts along the heading, steer yaws, a keel bleeds sideways slip. Call `update(dt, time, input?)` before the shared `world.step(dt)`.
|
|
1440
|
+
|
|
1441
|
+
### @jgengine/core/physics/carryable
|
|
1442
|
+
|
|
1443
|
+
- carrySpeedMultiplier (function): function carrySpeedMultiplier(mass: number, carryCapacity: number, owners: number): number — Movement multiplier (1 = unhindered, →0 = crushed) for a body of `mass` carried by `owners`. Pure — the HUD/movement kit reads it to slow a laden hauler (Lethal Company) and to gate items that need 2+ people (R.E.P.O.).
|
|
1444
|
+
- CarryableConfig (interface): interface CarryableConfig
|
|
1445
|
+
- Carryable (class): class Carryable — A grabbed physics object following a moving hold point through a spring constraint (the pick — a raycast — is the caller's/shell's job; core owns the constraint). Supports shared multi-owner carry (the follow point is the average of owners' hold points), an encumbrance read, and drop/throw. Reuses `PhysicsWorld.springJoint` to a world anchor moved each frame.
|
|
1446
|
+
|
|
1447
|
+
### @jgengine/core/physics/damageZones
|
|
1448
|
+
|
|
1449
|
+
- createDamageModel (function): function createDamageModel(config: DamageModelConfig): DamageModel
|
|
1450
|
+
- DamageZoneDef (interface): interface DamageZoneDef
|
|
1451
|
+
- DamageZoneState (interface): interface DamageZoneState
|
|
1452
|
+
- DamageTransition (interface): interface DamageTransition
|
|
1453
|
+
- DamageModelConfig (interface): interface DamageModelConfig
|
|
1454
|
+
- DamageModel (class): class DamageModel — Coarse crash-damage model (issue #86 — stages, not soft-body). Each zone accumulates contact impulse fed from G3's `onCollision` `CollisionEvent`; crossing a threshold bumps the zone to the next discrete stage (the caller swaps the visual/collider), an optional `detachStage` ejects a part as debris once, and a total-impulse `disableAt` flips the whole body to a disabled state. `absorb` returns a transition only when something actually changed, so it doubles as the event source for HUD/feed.
|
|
1455
|
+
|
|
1456
|
+
### @jgengine/core/physics/forceVolume
|
|
1457
|
+
|
|
1458
|
+
- ForceMode (type): type ForceMode = "impulse" | "velocity" | "accelerate"
|
|
1459
|
+
- ForceVolumeConfig (interface): interface ForceVolumeConfig
|
|
1460
|
+
- ForceVolume (class): class ForceVolume — A trigger region that pushes bodies passing through it — boost pads (`impulse` + `once`), conveyors (`velocity`), fans/wind (`accelerate`). Call `apply` each tick; `once` mode fires only on entry by tracking membership between ticks.
|
|
1461
|
+
- PlatformCarryConfig (interface): interface PlatformCarryConfig
|
|
1462
|
+
- PlatformCarry (class): class PlatformCarry — Carries bodies standing on a moving platform by composing their transform with the platform's per-`step` delta — moving/rotating lifts and conveyor floors (Fall Guys, Gang Beasts). The platform is a body the game repositions each frame; riders are detected by overlap on its top face.
|
|
1463
|
+
|
|
1464
|
+
### @jgengine/core/physics/physicsWorld
|
|
1465
|
+
|
|
1466
|
+
- cellCoord (function): function cellCoord(value: number, min: number, cellSize: number, cells: number): number — Grid cell containing a point, clamped into the grid. Pure — exported for tests.
|
|
1467
|
+
- cellIndex (function): function cellIndex(cx: number, cy: number, cz: number, nx: number, ny: number): number — Linear cell index from clamped 3D coords. Pure — exported for tests.
|
|
1468
|
+
- PhysicsBounds (interface): interface PhysicsBounds
|
|
1469
|
+
- PhysicsWorldConfig (interface): interface PhysicsWorldConfig
|
|
1470
|
+
- PhysicsStats (interface): interface PhysicsStats
|
|
1471
|
+
- BodyCommonOptions (interface): interface BodyCommonOptions
|
|
1472
|
+
- BoxBodyOptions (interface): interface BoxBodyOptions extends BodyCommonOptions
|
|
1473
|
+
- SphereBodyOptions (interface): interface SphereBodyOptions extends BodyCommonOptions — The radius fills all three half-extent columns, so broadphase and bounds see the sphere's enclosing AABB.
|
|
1474
|
+
- AddBodyOptions (type): type AddBodyOptions = BoxBodyOptions | SphereBodyOptions
|
|
1475
|
+
- SHAPE_BOX (const): const SHAPE_BOX: 0
|
|
1476
|
+
- SHAPE_SPHERE (const): const SHAPE_SPHERE: 1
|
|
1477
|
+
- JointKind (type): type JointKind = "hinge" | "distance" | "spring" | "fixed" — `hinge`/`fixed`/`distance` are hard bilateral constraints; `spring` is a soft PD constraint. No angular state → `hinge`/`fixed` both pin the shared anchor (`axis` is metadata), `distance` holds a fixed separation, `spring` drives toward `restLength` (suspension, follow-point carry).
|
|
1478
|
+
- JointOptions (interface): interface JointOptions
|
|
1479
|
+
- CollisionEvent (interface): interface CollisionEvent — A contact reported to `onCollision`. The object is reused each call — read/copy, never retain.
|
|
1480
|
+
- MAX_BROADPHASE_CELLS (const): const MAX_BROADPHASE_CELLS: 1000000 — Cap on `nx*ny*nz` broadphase cells — guards a huge-bounds/tiny-cellSize config from hanging `step()`.
|
|
1481
|
+
- PhysicsWorld (class): class PhysicsWorld
|
|
1482
|
+
|
|
1483
|
+
### @jgengine/core/physics/ragdoll
|
|
1484
|
+
|
|
1485
|
+
- createRagdoll (function): function createRagdoll(world: PhysicsWorld, config: RagdollConfig): Ragdoll
|
|
1486
|
+
- RagdollBoneSpec (interface): interface RagdollBoneSpec
|
|
1487
|
+
- RagdollLinkSpec (interface): interface RagdollLinkSpec
|
|
1488
|
+
- RagdollBalanceConfig (interface): interface RagdollBalanceConfig
|
|
1489
|
+
- RagdollConfig (interface): interface RagdollConfig
|
|
1490
|
+
- Ragdoll (class): class Ragdoll — A jointed multi-body character — floppy by default, or active-ragdoll when a balance motor is configured. Bones are `PhysicsWorld` bodies; links are joints on the joint API. `balance(dt, mx, mz)` drives the root toward its target height (staying upright) and pushes it by the move vector.
|
|
1491
|
+
|
|
1492
|
+
### @jgengine/core/physics/spatialGrid
|
|
1493
|
+
|
|
1494
|
+
- SpatialGridConfig (interface): interface SpatialGridConfig
|
|
1495
|
+
- SpatialGrid (class): class SpatialGrid — A uniform-grid broad-phase over the x/z plane, separate from the rigid-body sim, for cheap same-tick proximity across hundreds–thousands of simple movers (swarm enemies). Rebuild each tick from the caller's own position arrays, then `queryCircle` (enemies hitting the player / an AoE) or `forEachPair` (mutual separation). Both are precise: no false negatives, no false positives beyond the exact distance test.
|
|
1496
|
+
|
|
1497
|
+
### @jgengine/core/physics/structure
|
|
1498
|
+
|
|
1499
|
+
- StructureMaterial (interface): interface StructureMaterial
|
|
1500
|
+
- StructureMaterialTable (type): type StructureMaterialTable = Readonly<Record<string, StructureMaterial>>
|
|
1501
|
+
- StructureNodeSpec (interface): interface StructureNodeSpec
|
|
1502
|
+
- StructureEdgeSpec (interface): interface StructureEdgeSpec
|
|
1503
|
+
- CollapseEvent (interface): interface CollapseEvent
|
|
1504
|
+
- DebrisConfig (interface): interface DebrisConfig
|
|
1505
|
+
- StructureGraph (class): class StructureGraph — A structural-integrity graph over a building — nodes are pieces (walls, beams, floors), edges are load-bearing connections, some nodes are anchored foundations. `damage`/`damageEdge` wear pieces and connections down; when a piece shatters or an edge severs, the graph recomputes which pieces still reach an anchor and hands back every newly-disconnected piece as one `CollapseEvent`. Feed that to `toDebris` to sink the fallen pieces into a `PhysicsWorld` as rigid bodies ("The Finals" smooth destruction, Rainbow Six walls). Coarse by design: it replicates the collapse event, not per fragment.
|
|
1506
|
+
|
|
1507
|
+
### @jgengine/core/physics/traversal
|
|
1508
|
+
|
|
1509
|
+
- GrappleConfig (interface): interface GrappleConfig
|
|
1510
|
+
- Grapple (class): class Grapple — A fired-anchor rope on the joint API — grapple (reel toward a hit point), zipline (rigid cable to a far anchor you then slide/reel along), swing (rigid rope + gravity = a pendulum). `fire` attaches a `distance`/`spring` joint from the traveller body to a fixed world point; `reel` shrinks its rest length so the constraint drags the body in; `moveAnchor` re-points it (zipline glide, grapple-to- moving-target). The pick — a raycast to find the anchor — is the caller's; core owns the constraint.
|
|
1511
|
+
- GlideConfig (interface): interface GlideConfig
|
|
1512
|
+
- Glide (class): class Glide — A reduced-gravity, forward-thrust glide over a physics body — wingsuit / glider / paraglider (Enshrouded, Grounded). Call `apply(dt, steerX, steerZ)` each frame *before* `world.step`: it feeds back most of the gravity the sim is about to apply (leaving `gravityScale` of it), pushes the body along the steer vector by `thrust`, and clamps descent to `maxFallSpeed`. Stop calling it to fall normally again — no attach/detach state to leak.
|
|
1513
|
+
|
|
1514
|
+
### @jgengine/core/physics/vehicleBody
|
|
1515
|
+
|
|
1516
|
+
- sampleGripCurve (function): function sampleGripCurve(curve: GripCurve, slip: number): number — Piecewise-linear tire-grip curve: normalized lateral slip → available grip (0..1). Grip peaks near the breakaway slip then falls off as the tire slides — the shape that separates a planted corner from a drift. Points are read in ascending slip order; ends clamp.
|
|
1517
|
+
- createVehicleBody (function): function createVehicleBody(world: PhysicsWorld, config: VehicleBodyConfig): VehicleBody
|
|
1518
|
+
- GripCurve (interface): interface GripCurve
|
|
1519
|
+
- DEFAULT_GRIP_CURVE (const): const DEFAULT_GRIP_CURVE: GripCurve
|
|
1520
|
+
- WheelSpec (interface): interface WheelSpec
|
|
1521
|
+
- WheelState (interface): interface WheelState
|
|
1522
|
+
- VehicleBodyConfig (interface): interface VehicleBodyConfig
|
|
1523
|
+
- VehicleBody (class): class VehicleBody — Arcade vehicle over the {@link PhysicsWorld} rigid-body sim. The chassis is a single box body driven by an {@link AxisInput}: per-wheel suspension is a spring-damper held with G3's `springJoint` against the sampled ground, drive/brake push along the heading, and a tire-grip curve bleeds lateral velocity (cornering, and drift under handbrake). Because the chassis is a physics body it still collides — that contact feeds crash damage. Call `update(dt, input)` before the shared `world.step(dt)`.
|
|
1524
|
+
|
|
1525
|
+
### @jgengine/core/puzzle/cellGrid
|
|
1526
|
+
|
|
1527
|
+
- createCellGrid (function): function createCellGrid<T>(width: number, height: number): CellGrid<T>
|
|
1528
|
+
- inGridBounds (function): function inGridBounds<T>(grid: CellGrid<T>, x: number, y: number): boolean
|
|
1529
|
+
- cellAt (function): function cellAt<T>(grid: CellGrid<T>, x: number, y: number): T | null
|
|
1530
|
+
- withCell (function): function withCell<T>(grid: CellGrid<T>, x: number, y: number, value: T | null): CellGrid<T>
|
|
1531
|
+
- withCells (function): function withCells<T>(grid: CellGrid<T>, entries: readonly { readonly x: number; readonly y: number; readonly value: T | null }[]): CellGrid<T>
|
|
1532
|
+
- fullRows (function): function fullRows<T>(grid: CellGrid<T>): number[]
|
|
1533
|
+
- clearRows (function): function clearRows<T>(grid: CellGrid<T>, rows: readonly number[]): CellGrid<T>
|
|
1534
|
+
- collapseColumns (function): function collapseColumns<T>(grid: CellGrid<T>): CellGrid<T>
|
|
1535
|
+
- findRuns (function): function findRuns<T>(grid: CellGrid<T>, minLength: number, matches: (a: T, b: T) => boolean = (a, b) => a === b): CellRun<T>[]
|
|
1536
|
+
- CellGrid (interface): interface CellGrid<T>
|
|
1537
|
+
- CellRun (interface): interface CellRun<T>
|
|
1538
|
+
|
|
1539
|
+
### @jgengine/core/puzzle/fallingPiece
|
|
1540
|
+
|
|
1541
|
+
- pieceCells (function): function pieceCells<TShape extends string>(table: ShapeTable<TShape>, piece: FallingPiece<TShape>): readonly (readonly [number, number])[]
|
|
1542
|
+
- pieceCollides (function): function pieceCollides<T, TShape extends string>(grid: CellGrid<T>, table: ShapeTable<TShape>, piece: FallingPiece<TShape>): boolean
|
|
1543
|
+
- mergePiece (function): function mergePiece<T, TShape extends string>(grid: CellGrid<T>, table: ShapeTable<TShape>, piece: FallingPiece<TShape>, value: T): CellGrid<T>
|
|
1544
|
+
- dropDistance (function): function dropDistance<T, TShape extends string>(grid: CellGrid<T>, table: ShapeTable<TShape>, piece: FallingPiece<TShape>): number
|
|
1545
|
+
- gravityInterval (function): function gravityInterval(level: number, base = 0.8, perLevel = 0.07, min = 0.05): number
|
|
1546
|
+
- levelForLines (function): function levelForLines(lines: number, linesPerLevel = 10): number
|
|
1547
|
+
- lineScore (function): function lineScore(cleared: number, level: number, table: readonly number[] = CLASSIC_LINE_SCORE): number
|
|
1548
|
+
- createLockDelay (function): function createLockDelay(delaySeconds: number): LockDelayState
|
|
1549
|
+
- stepLockDelay (function): function stepLockDelay(state: LockDelayState, grounded: boolean, dt: number): { state: LockDelayState; locked: boolean }
|
|
1550
|
+
- FallingPiece (interface): interface FallingPiece<TShape extends string = string>
|
|
1551
|
+
- ShapeTable (type): type ShapeTable<TShape extends string = string> = Record< TShape, readonly (readonly (readonly [number, number])[])[] >
|
|
1552
|
+
- LockDelayState (interface): interface LockDelayState
|
|
1553
|
+
|
|
1554
|
+
### @jgengine/core/random/nameGen
|
|
1555
|
+
|
|
1556
|
+
- pickFrom (function): function pickFrom(rng: () => number, bank: readonly string[]): string
|
|
1557
|
+
- createNameGenerator (function): function createNameGenerator(options: NameGeneratorOptions): NameGenerator
|
|
1558
|
+
- fillTemplate (function): function fillTemplate(template: string, vars: Record<string, () => string> | Record<string, string>): string
|
|
1559
|
+
- SyllableBank (interface): interface SyllableBank
|
|
1560
|
+
- NameGeneratorOptions (interface): interface NameGeneratorOptions
|
|
1561
|
+
- NameGenerator (interface): interface NameGenerator
|
|
1562
|
+
|
|
1563
|
+
### @jgengine/core/random/rng
|
|
1564
|
+
|
|
1565
|
+
- seededRng (function): function seededRng(seed: string | number): () => number
|
|
1566
|
+
- seededStreams (function): function seededStreams(seed: string | number): (stream: string) => () => number
|
|
1567
|
+
|
|
1568
|
+
### @jgengine/core/runtime/adapter
|
|
1569
|
+
|
|
1570
|
+
- convex (function): function convex(config?: { topology?: MultiplayerTopology }): MultiplayerAdapterConfig
|
|
1571
|
+
- ws (function): function ws(config?: { topology?: MultiplayerTopology; url?: string }): MultiplayerAdapterConfig
|
|
1572
|
+
- fly (function): function fly(config: { app: string; topology?: MultiplayerTopology; path?: string }): MultiplayerAdapterConfig
|
|
1573
|
+
- socketIo (function): function socketIo(config?: { topology?: MultiplayerTopology; url?: string }): MultiplayerAdapterConfig
|
|
1574
|
+
- p2p (function): function p2p(config?: { topology?: MultiplayerTopology; room?: string }): MultiplayerAdapterConfig
|
|
1575
|
+
- lan (function): function lan(config?: { topology?: MultiplayerTopology; port?: number; path?: string; }): MultiplayerAdapterConfig
|
|
1576
|
+
- offline (function): function offline(): MultiplayerAdapterConfig
|
|
1577
|
+
- adapterOf (function): function adapterOf(multiplayer: unknown): MultiplayerAdapterConfig | null
|
|
1578
|
+
- multiplayerAdapterKind (function): function multiplayerAdapterKind(multiplayer: unknown): string | null
|
|
1579
|
+
- servers (function): function servers(config: ServersPoolConfig): import("/home/runner/work/jgengine/jgengine/packages/core/src/runtime/adapter").ServersPoolConfig
|
|
1580
|
+
- MultiplayerTopology (type): type MultiplayerTopology = "shared" | "lobbies" | "private"
|
|
1581
|
+
- MultiplayerAdapterConfig (type): type MultiplayerAdapterConfig = | { kind: "convex"; topology?: MultiplayerTopology } | { kind: "ws"; topology?: MultiplayerTopology; url?: string } | { kind: "socketio"; topology?: MultiplayerTopology; url?: string } | { kind: "p2p"; topology?: MultiplayerTopology; room?: string } | { kind: "lan"; t…
|
|
1582
|
+
- ServersPoolConfig (type): type ServersPoolConfig = { maxServers: number; slotsPerServer: number; minPlayersToStart?: number; adapter: MultiplayerAdapterConfig; }
|
|
1583
|
+
|
|
1584
|
+
### @jgengine/core/runtime/cameraDirector
|
|
1585
|
+
|
|
1586
|
+
- createCameraDirector (function): function createCameraDirector(): CameraDirector
|
|
1587
|
+
- CameraDirector (interface): interface CameraDirector
|
|
1588
|
+
|
|
1589
|
+
### @jgengine/core/runtime/commandRunner
|
|
1590
|
+
|
|
1591
|
+
- runCommand (function): function runCommand<TInput>(snapshot: import("./snapshot").GameRuntimeSnapshot, commands: Record<string, CommandDef<TInput>>, commandName: string, input: TInput, actorUserId: string): RunCommandResult
|
|
1592
|
+
- CommandValidationError (type): type CommandValidationError = { reason: string }
|
|
1593
|
+
- CommandDef (type): type CommandDef<TInput = unknown> = { validate: (snapshot: import("./snapshot").GameRuntimeSnapshot, input: TInput) => CommandValidationError | null; apply: ( snapshot: import("./snapshot").GameRuntimeSnapshot, input: TInput, ) => import("./snapshot").GameRuntimeSnapshot; }
|
|
1594
|
+
- RunCommandResult (type): type RunCommandResult = | { ok: true; snapshot: import("./snapshot").GameRuntimeSnapshot } | { ok: false; reason: string }
|
|
1595
|
+
|
|
1596
|
+
### @jgengine/core/runtime/gameContext
|
|
1597
|
+
|
|
1598
|
+
- createGameContext (function): function createGameContext<TAssetRef extends ModelAssetRef, TMultiplayer>(options: GameContextOptions<TAssetRef, TMultiplayer>): GameContext
|
|
1599
|
+
- GameContextItemEntry (interface): interface GameContextItemEntry
|
|
1600
|
+
- CatalogEntityRole (type): type CatalogEntityRole = "player" | "enemy" | "hostile" | "npc" | "vehicle"
|
|
1601
|
+
- GameContextEntityEntry (interface): interface GameContextEntityEntry
|
|
1602
|
+
- GameContextObjectEntry (interface): interface GameContextObjectEntry
|
|
1603
|
+
- GameContextContent (interface): interface GameContextContent
|
|
1604
|
+
- GameContextOptions (interface): interface GameContextOptions<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown>
|
|
1605
|
+
- SceneObjectContext (interface): interface SceneObjectContext extends ObjectStore
|
|
1606
|
+
- FloatTextInput (interface): interface FloatTextInput
|
|
1607
|
+
- TelegraphInput (interface): interface TelegraphInput
|
|
1608
|
+
- HitReactionInput (interface): interface HitReactionInput
|
|
1609
|
+
- SceneEntityContext (interface): interface SceneEntityContext
|
|
1610
|
+
- WorldItemPickupResult (type): type WorldItemPickupResult = | { status: "ok"; record: WorldItemRecord } | { status: "rejected"; reason: string }
|
|
1611
|
+
- SceneWorldItemContext (interface): interface SceneWorldItemContext
|
|
1612
|
+
- GameContextCommands (interface): interface GameContextCommands
|
|
1613
|
+
- GameContextFeed (interface): interface GameContextFeed extends Omit<GameFeed, "bind">
|
|
1614
|
+
- GameContextLoot (interface): interface GameContextLoot
|
|
1615
|
+
- GameContextEconomy (interface): interface GameContextEconomy
|
|
1616
|
+
- GameContextItemUse (interface): interface GameContextItemUse
|
|
1617
|
+
- GameContextWorld (interface): interface GameContextWorld
|
|
1618
|
+
- GameContextCards (interface): interface GameContextCards
|
|
1619
|
+
- GameContextTurn (interface): interface GameContextTurn
|
|
1620
|
+
- GameContext (interface): interface GameContext
|
|
1621
|
+
|
|
1622
|
+
### @jgengine/core/runtime/gameRuntime
|
|
1623
|
+
|
|
1624
|
+
- createGameRuntime (function): function createGameRuntime(definition: GameRuntimeDefinition): GameRuntime
|
|
1625
|
+
- ServerLoopHooks (type): type ServerLoopHooks = { onInit?: (ctx: RuntimeInitContext) => void; onNewPlayer?: (ctx: RuntimeLoopContext) => void; onTick?: (ctx: RuntimeLoopContext, dtSeconds: number) => void; }
|
|
1626
|
+
- RuntimeInitContext (type): type RuntimeInitContext = { snapshot: GameRuntimeSnapshot; setSnapshot: (snapshot: GameRuntimeSnapshot) => void; }
|
|
1627
|
+
- RuntimeLoopContext (type): type RuntimeLoopContext = RuntimeInitContext & { player: { userId: string; isNew: boolean; }; }
|
|
1628
|
+
- GameRuntimeDefinition (type): type GameRuntimeDefinition = { gameId: string; save: SaveConfig; commands: Record<string, CommandDef>; loop?: ServerLoopHooks; }
|
|
1629
|
+
- HydrateInput (type): type HydrateInput = { gameId: string; serverId: string; serverRow: RuntimeServerRow; playersByUserId: Record<string, RuntimePlayerRow>; chunksByKey: Record<string, RuntimeChunkRow>; }
|
|
1630
|
+
- GameRuntime (type): type GameRuntime = { gameId: string; save: SaveConfig; hydrate: (input: HydrateInput) => GameRuntimeSnapshot; runCommand: ( snapshot: GameRuntimeSnapshot, actorUserId: string, commandName: string, input: unknown, ) => ReturnType<typeof runCommand>; tick: (snapshot: GameRuntimeSnapshot, dtSeconds: nu…
|
|
1631
|
+
|
|
1632
|
+
### @jgengine/core/runtime/hostPersistence
|
|
1633
|
+
|
|
1634
|
+
- drainPendingLeaderboardIncrements (function): function drainPendingLeaderboardIncrements(session: Record<string, unknown>): { increments: LeaderboardIncrement[]; session: Record<string, unknown>; }
|
|
1635
|
+
- leaderboardRowKey (function): function leaderboardRowKey(row: Omit<LeaderboardRow, "value" | "updatedAt">): string
|
|
1636
|
+
- applyLeaderboardRows (function): function applyLeaderboardRows(rows: Map<string, LeaderboardRow>, gameId: string, entries: LeaderboardIncrement[], now: number): void
|
|
1637
|
+
- topLeaderboardRows (function): function topLeaderboardRows(rows: Iterable<LeaderboardRow>, args: { gameId: string; stat: string; scope: LeaderboardScope; serverId?: string; limit?: number; }): LeaderboardEntry[]
|
|
1638
|
+
- profileLeaderboardStats (function): function profileLeaderboardStats(rows: Iterable<LeaderboardRow>, gameId: string, userId: string): Record<string, number>
|
|
1639
|
+
- trimFeedEntries (function): function trimFeedEntries<T>(entries: T[], limit = FEED_RING_LIMIT): T[]
|
|
1640
|
+
- shouldAutoSave (function): function shouldAutoSave(save: SaveConfig, dirtyAt: number | undefined, lastSavedAt: number | undefined, now: number): boolean
|
|
1641
|
+
- toServerListing (function): function toServerListing(record: GameServerRecord): ServerListing
|
|
1642
|
+
- toOpenServerListings (function): function toOpenServerListings(listings: Iterable<ServerListing>, limit = 20): ServerListing[]
|
|
1643
|
+
- buildHydratePlayers (function): function buildHydratePlayers(server: GameServerRecord, profiles: Record<string, PlayerProfileRecord | null>): Record<string, RuntimePlayerRow>
|
|
1644
|
+
- planServerPersist (function): function planServerPersist(server: GameServerRecord, snapshot: GameRuntimeSnapshot, save: SaveConfig, now: number): ServerPersistPlan
|
|
1645
|
+
- GameServerStatus (type): type GameServerStatus = "open" | "running" | "closed"
|
|
1646
|
+
- SessionVisibility (type): type SessionVisibility = "public" | "private"
|
|
1647
|
+
- SessionAttributes (type): type SessionAttributes = { label?: string; mode?: string; visibility?: SessionVisibility; joinCode?: string; tags?: string[]; }
|
|
1648
|
+
- GameServerRecord (type): type GameServerRecord = { serverId: string; gameId: string; status: GameServerStatus; mode?: string; modeConfig?: unknown; label?: string; visibility?: SessionVisibility; joinCode?: string; tags?: string[]; memberUserIds: string[]; slotsPerServer: number; save: SaveConfig; serverState: RuntimeServer…
|
|
1649
|
+
- PlayerProfileRecord (type): type PlayerProfileRecord = { userId: string; gameId: string; playerState: RuntimePlayerRow; revision: number; updatedAt: number; }
|
|
1650
|
+
- WorldChunkRecord (type): type WorldChunkRecord = { serverId: string; chunkKey: string; snapshot: RuntimeChunkRow; updatedAt: number; }
|
|
1651
|
+
- LeaderboardIncrement (type): type LeaderboardIncrement = { userId: string; stat: string; scope: LeaderboardScope; serverId?: string; by: number; }
|
|
1652
|
+
- LeaderboardEntry (type): type LeaderboardEntry = { userId: string; value: number; }
|
|
1653
|
+
- LEADERBOARD_PENDING_KEY (const): const LEADERBOARD_PENDING_KEY: "leaderboardPending"
|
|
1654
|
+
- LEADERBOARD_TOP_LIMIT (const): const LEADERBOARD_TOP_LIMIT: 100
|
|
1655
|
+
- LeaderboardRow (type): type LeaderboardRow = { gameId: string; stat: string; scope: LeaderboardScope; serverId?: string; userId: string; value: number; updatedAt: number; }
|
|
1656
|
+
- FEED_RING_LIMIT (const): const FEED_RING_LIMIT: 20
|
|
1657
|
+
- ServerListing (type): type ServerListing = { serverId: string; status: GameServerStatus; memberCount: number; slotsPerServer: number; mode?: string; label?: string; visibility?: SessionVisibility; joinCode?: string; tags?: string[]; updatedAt: number; }
|
|
1658
|
+
- HostPersistence (type): type HostPersistence = { savePlan?: (plan: ServerPersistPlan) => Promise<void>; resetScenario?: (reset: NormalizedScenarioReset) => Promise<void>; loadServer: (serverId: string) => Promise<GameServerRecord | null>; saveServer: (record: GameServerRecord) => Promise<void>; listServers: (gameId: string…
|
|
1659
|
+
- ServerPersistPlan (type): type ServerPersistPlan = { server: GameServerRecord; profiles: PlayerProfileRecord[]; chunks: WorldChunkRecord[]; leaderboard: LeaderboardIncrement[]; }
|
|
1660
|
+
|
|
1661
|
+
### @jgengine/core/runtime/inputSnapshot
|
|
1662
|
+
|
|
1663
|
+
- createInputSnapshot (function): function createInputSnapshot(): InputSnapshot
|
|
1664
|
+
- InputSnapshot (interface): interface InputSnapshot
|
|
1665
|
+
|
|
1666
|
+
### @jgengine/core/runtime/motionIntents
|
|
1667
|
+
|
|
1668
|
+
- createMotionIntents (function): function createMotionIntents(): MotionIntents
|
|
1669
|
+
- MotionIntents (interface): interface MotionIntents — Seam for game code to reach the vertical motion the shell's FrameDriver otherwise owns privately (#162.4). Game code calls `impulse`, `setVerticalVelocity`, and/or `setY` from `onTick` or commands; the shell calls `takePending()` once per frame, before integrating gravity, to drain what accumulated. `setY` wins over physics for that frame; impulses add to the vertical velocity the driver is about to integrate; a later `setVerticalVelocity` replaces that velocity outright.
|
|
1670
|
+
- MotionIntentBatch (interface): interface MotionIntentBatch
|
|
1671
|
+
|
|
1672
|
+
### @jgengine/core/runtime/persistenceScope
|
|
1673
|
+
|
|
1674
|
+
- partitionScopes (function): function partitionScopes<T extends Record<string, unknown>>(state: T, schema: ScopeSchema): ScopedState<T>
|
|
1675
|
+
- resetRun (function): function resetRun<T extends Record<string, unknown>>(scoped: ScopedState<T>): ScopedState<T>
|
|
1676
|
+
- mergeScopes (function): function mergeScopes<T extends Record<string, unknown>>(scoped: ScopedState<T>): Partial<T>
|
|
1677
|
+
- clearRunFields (function): function clearRunFields(player: RuntimePlayerRow, runFields: readonly string[]): RuntimePlayerRow
|
|
1678
|
+
- applyRunReset (function): function applyRunReset(profile: PlayerProfileRecord, runFields: readonly string[], now: number): PlayerProfileRecord
|
|
1679
|
+
- planScenarioReset (function): function planScenarioReset(reset: ScenarioReset): NormalizedScenarioReset
|
|
1680
|
+
- PersistenceScope (type): type PersistenceScope = "run" | "meta"
|
|
1681
|
+
- ScopeSchema (interface): interface ScopeSchema
|
|
1682
|
+
- ScopedState (interface): interface ScopedState<T = Record<string, unknown>>
|
|
1683
|
+
- ScenarioReset (interface): interface ScenarioReset
|
|
1684
|
+
- NormalizedScenarioReset (interface): interface NormalizedScenarioReset
|
|
1685
|
+
|
|
1686
|
+
### @jgengine/core/runtime/save
|
|
1687
|
+
|
|
1688
|
+
- parseSaveAutoMs (function): function parseSaveAutoMs(auto: string): number
|
|
1689
|
+
- saveScopeIncludesPlayer (function): function saveScopeIncludesPlayer(scope: SaveScope): boolean
|
|
1690
|
+
- saveScopeIncludesChunks (function): function saveScopeIncludesChunks(scope: SaveScope): boolean
|
|
1691
|
+
- isSaveEnabled (function): function isSaveEnabled(config: SaveConfig): config is Exclude<SaveConfig, "none">
|
|
1692
|
+
- SaveScope (type): type SaveScope = "player" | "chunks" | "player+chunks"
|
|
1693
|
+
- SaveConfig (type): type SaveConfig = | "none" | { auto: string; scope: SaveScope; }
|
|
1694
|
+
|
|
1695
|
+
### @jgengine/core/runtime/snapshot
|
|
1696
|
+
|
|
1697
|
+
- createEmptyServerRow (function): function createEmptyServerRow(): RuntimeServerRow
|
|
1698
|
+
- createEmptyPlayerRow (function): function createEmptyPlayerRow(userId: string): RuntimePlayerRow
|
|
1699
|
+
- createRuntimeSnapshot (function): function createRuntimeSnapshot(args: { gameId: string; serverId: string; server?: RuntimeServerRow; players?: Record<string, RuntimePlayerRow>; chunks?: Record<string, RuntimeChunkRow>; }): GameRuntimeSnapshot
|
|
1700
|
+
- markServerDirty (function): function markServerDirty(snapshot: GameRuntimeSnapshot): GameRuntimeSnapshot
|
|
1701
|
+
- markPlayerDirty (function): function markPlayerDirty(snapshot: GameRuntimeSnapshot, userId: string): GameRuntimeSnapshot
|
|
1702
|
+
- clearDirtyFlags (function): function clearDirtyFlags(snapshot: GameRuntimeSnapshot): GameRuntimeSnapshot
|
|
1703
|
+
- splitProfilePlayer (function): function splitProfilePlayer(player: RuntimePlayerRow): { persistent: RuntimePlayerRow; session: Record<string, unknown>; }
|
|
1704
|
+
- RUNTIME_SNAPSHOT_VERSION (const): const RUNTIME_SNAPSHOT_VERSION: 1
|
|
1705
|
+
- RuntimeEntityRow (type): type RuntimeEntityRow = { instanceId: string; catalogId: string; position?: [number, number, number]; rotationY?: number; parentSpace?: string; group?: string; stats?: Record<string, { current: number; max: number; min?: number }>; targetInstanceId?: string | null; userId?: string; }
|
|
1706
|
+
- RuntimeObjectRow (type): type RuntimeObjectRow = { instanceId: string; catalogId: string; position: [number, number, number]; rotationY?: number; parentSpace?: string; flags?: Record<string, unknown>; }
|
|
1707
|
+
- RuntimeInventorySlot (type): type RuntimeInventorySlot = { item: string; count: number; slot?: number; }
|
|
1708
|
+
- RuntimePlayerRow (type): type RuntimePlayerRow = { userId: string; inventories: Record<string, RuntimeInventorySlot[]>; economy: Record<string, number>; unlocks: string[]; quests?: unknown; social?: unknown; leaderboard?: Record<string, number>; session?: Record<string, unknown>; }
|
|
1709
|
+
- RuntimeChunkRow (type): type RuntimeChunkRow = { chunkKey: string; objects: RuntimeObjectRow[]; entities: RuntimeEntityRow[]; flags?: Record<string, unknown>; }
|
|
1710
|
+
- RuntimeServerRow (type): type RuntimeServerRow = { entities: RuntimeEntityRow[]; objects: RuntimeObjectRow[]; session: Record<string, unknown>; feeds?: Record<string, unknown[]>; }
|
|
1711
|
+
- RuntimeProfileRow (type): type RuntimeProfileRow = { userId: string; gameId: string; player: RuntimePlayerRow; updatedAt: number; }
|
|
1712
|
+
- GameRuntimeSnapshot (type): type GameRuntimeSnapshot = { version: number; gameId: string; serverId: string; server: RuntimeServerRow; players: Record<string, RuntimePlayerRow>; chunks: Record<string, RuntimeChunkRow>; revision: number; dirty: { server: boolean; players: string[]; chunks: string[]; }; }
|
|
1713
|
+
|
|
1714
|
+
### @jgengine/core/runtime/transport
|
|
1715
|
+
|
|
1716
|
+
- JoinServerResult (type): type JoinServerResult = { serverId: string; isNew: boolean; }
|
|
1717
|
+
- RunCommandArgs (type): type RunCommandArgs = { serverId: string; command: string; input: unknown; }
|
|
1718
|
+
- TransportRunCommandResult (type): type TransportRunCommandResult = | { ok: true } | { ok: false; reason: string }
|
|
1719
|
+
- GameRuntimeServerView (type): type GameRuntimeServerView = { serverId: string; gameId: string; revision: number; memberUserIds: string[]; serverState: unknown; updatedAt: number; }
|
|
1720
|
+
- GameRuntimePlayerView (type): type GameRuntimePlayerView = { userId: string; gameId: string; playerState: unknown; updatedAt: number; }
|
|
1721
|
+
- GameRuntimeFeedView (type): type GameRuntimeFeedView = { action: string; entries: unknown[]; }
|
|
1722
|
+
- GameRuntimeTransport (type): type GameRuntimeTransport = { joinServer: (args: { gameId: string; serverId?: string }) => Promise<JoinServerResult>; leaveServer: (args: { serverId: string }) => Promise<void>; runCommand: (args: RunCommandArgs) => Promise<TransportRunCommandResult>; }
|
|
1723
|
+
- FeedUnsubscribe (type): type FeedUnsubscribe = () => void
|
|
1724
|
+
- GameRuntimeFeeds (type): type GameRuntimeFeeds = { subscribeServer: ( serverId: string, onChange: (view: GameRuntimeServerView | null) => void, ) => FeedUnsubscribe; subscribePlayer: ( args: { serverId: string }, onChange: (view: GameRuntimePlayerView | null) => void, ) => FeedUnsubscribe; subscribeFeed: ( args: { serverId:…
|
|
1725
|
+
- GameBackend (type): type GameBackend<TPresenceRow = unknown, TPresenceLocation = unknown, TGameId extends string = string> = { transport: GameRuntimeTransport; feeds?: GameRuntimeFeeds; presence?: PresenceTransport<TPresenceRow, TPresenceLocation, TGameId>; }
|
|
1726
|
+
- PresencePoseRow (type): type PresencePoseRow = { userId: string; position: { x: number; y: number; z: number }; rotationY: number; rotationPitch: number; lastSeenAt: number; }
|
|
1727
|
+
- PresenceSync (type): type PresenceSync = { subscribe: (serverId: string, onChange: (rows: PresencePoseRow[]) => void) => FeedUnsubscribe; syncPose: (serverId: string, pose: PlayerPose) => void; }
|
|
1728
|
+
- LiveGameBackend (type): type LiveGameBackend<TPresenceRow = unknown, TPresenceLocation = unknown, TGameId extends string = string> = GameBackend<TPresenceRow, TPresenceLocation, TGameId> & { presenceSync: PresenceSync; pushFeedEntry: (args: { serverId: string; action: string; entry: unknown }) => Promise<void>; chatSyncFor…
|
|
1729
|
+
- MultiplayerSession (type): type MultiplayerSession = { gameId: string; userId: string; backend: LiveGameBackend; feedActions: string[]; }
|
|
1730
|
+
|
|
1731
|
+
### @jgengine/core/scene/assetCatalog
|
|
1732
|
+
|
|
1733
|
+
- createAssetCatalog (function): function createAssetCatalog<TMeta extends ModelAssetRef = ModelAssetRef>(): AssetCatalog<TMeta>
|
|
1734
|
+
- ModelDims (interface): interface ModelDims
|
|
1735
|
+
- ModelAssetRef (interface): interface ModelAssetRef
|
|
1736
|
+
- AssetCatalog (interface): interface AssetCatalog<TMeta extends ModelAssetRef = ModelAssetRef>
|
|
1737
|
+
|
|
1738
|
+
### @jgengine/core/scene/assetPreload
|
|
1739
|
+
|
|
1740
|
+
- createSceneAssetPreloader (function): function createSceneAssetPreloader(fetcher: SceneAssetFetcher): { preloadUrl(url: string): void; preloadUrls(urls: readonly string[]): void; hasPreloaded(url: string): boolean; reset(): void; }
|
|
1741
|
+
- SceneAssetFetcher (type): type SceneAssetFetcher = (url: string) => void
|
|
1742
|
+
|
|
1743
|
+
### @jgengine/core/scene/autoTarget
|
|
1744
|
+
|
|
1745
|
+
- selectAutoTarget (function): function selectAutoTarget(policy: AutoTargetPolicy, fromId: string, deps: AutoTargetDeps): string | null
|
|
1746
|
+
- createAutoTargeter (function): function createAutoTargeter(policy: AutoTargetPolicy, deps: AutoTargetDeps): AutoTargeter
|
|
1747
|
+
- AutoTargetPolicy (type): type AutoTargetPolicy = | "nearest" | "farthest" | "random" | "strongest" | "weakest" | "first" | "last"
|
|
1748
|
+
- AutoTargetDeps (interface): interface AutoTargetDeps
|
|
1749
|
+
- AutoTargeter (interface): interface AutoTargeter
|
|
1750
|
+
|
|
1751
|
+
### @jgengine/core/scene/behaviors
|
|
1752
|
+
|
|
1753
|
+
- wander (function): function wander({ radius }: { radius: number }): WanderBehavior
|
|
1754
|
+
- patrol (function): function patrol({ waypoints, speed, loop = true, }: { waypoints: readonly Waypoint[]; speed: number; loop?: boolean; }): PatrolBehavior
|
|
1755
|
+
- promptable (function): function promptable(prompt: ProximityPrompt): PromptableBehavior
|
|
1756
|
+
- talkable (function): function talkable(dialogueId: string): PromptableBehavior
|
|
1757
|
+
- player (function): function player(): PlayerBehavior
|
|
1758
|
+
- WanderBehavior (interface): interface WanderBehavior
|
|
1759
|
+
- PatrolBehavior (interface): interface PatrolBehavior
|
|
1760
|
+
- PromptableBehavior (interface): interface PromptableBehavior
|
|
1761
|
+
- PlayerBehavior (interface): interface PlayerBehavior
|
|
1762
|
+
- BehaviorDescriptor (type): type BehaviorDescriptor = | WanderBehavior | PatrolBehavior | PromptableBehavior | PlayerBehavior
|
|
1763
|
+
|
|
1764
|
+
### @jgengine/core/scene/captureCheck
|
|
1765
|
+
|
|
1766
|
+
- captureChance (function): function captureChance(input: CaptureCheckInput): number
|
|
1767
|
+
- rollCapture (function): function rollCapture(input: CaptureCheckInput, rng: () => number = Math.random): boolean
|
|
1768
|
+
- CaptureCheckInput (interface): interface CaptureCheckInput
|
|
1769
|
+
|
|
1770
|
+
### @jgengine/core/scene/entityStats
|
|
1771
|
+
|
|
1772
|
+
- getStatValue (function): function getStatValue(map: StatValueMap, statId: string): StatValue | null
|
|
1773
|
+
- setStatValue (function): function setStatValue(map: StatValueMap, statId: string, patch: StatValuePatch): StatValueMap
|
|
1774
|
+
- applyPoolDelta (function): function applyPoolDelta(map: StatValueMap, statId: string, amount: number): PoolDeltaResult
|
|
1775
|
+
- seedStatValues (function): function seedStatValues(catalogStats: StatCatalog): StatValueMap
|
|
1776
|
+
- createEntityStatsApi (function): function createEntityStatsApi(resolve: (instanceId: string) => StatValueMap | undefined): EntityStatsApi
|
|
1777
|
+
- StatValue (interface): interface StatValue
|
|
1778
|
+
- StatValuePatch (interface): interface StatValuePatch
|
|
1779
|
+
- StatValueMap (type): type StatValueMap = Record<string, StatValue>
|
|
1780
|
+
- StatCatalog (type): type StatCatalog = Record<string, { max: number; min?: number; current?: number }>
|
|
1781
|
+
- PoolDeltaResult (type): type PoolDeltaResult = | { status: "ok"; map: StatValueMap; stat: StatValue; hitMin: boolean; hitMax: boolean } | { status: "rejected"; reason: string }
|
|
1782
|
+
- EntityStatsApi (interface): interface EntityStatsApi
|
|
1783
|
+
|
|
1784
|
+
### @jgengine/core/scene/entityStore
|
|
1785
|
+
|
|
1786
|
+
- groundSpeed (function): function groundSpeed(entity: SceneEntity<unknown>): number — Ground speed (horizontal magnitude of velocity) in world units per second. Scale to km/h or mph in game code.
|
|
1787
|
+
- movedWhileFrozen (function): function movedWhileFrozen(entity: SceneEntity<unknown>, threshold = DEFAULT_FROZEN_MOVE_THRESHOLD): boolean
|
|
1788
|
+
- createEntityStore (function): function createEntityStore<TMeta = undefined>(): EntityStore<TMeta>
|
|
1789
|
+
- EntityRole (type): type EntityRole = "player" | "npc" | "prop"
|
|
1790
|
+
- EntityMovement (interface): interface EntityMovement
|
|
1791
|
+
- EntityPosition (type): type EntityPosition = readonly [number, number, number]
|
|
1792
|
+
- SpawnPositionInput (type): type SpawnPositionInput = EntityPosition | { x: number; y: number; z: number }
|
|
1793
|
+
- SceneEntity (interface): interface SceneEntity<TMeta = undefined>
|
|
1794
|
+
- SpawnOptions (interface): interface SpawnOptions<TMeta = undefined>
|
|
1795
|
+
- EntityPose (interface): interface EntityPose
|
|
1796
|
+
- SpawnPose (interface): interface SpawnPose
|
|
1797
|
+
- EntityStore (interface): interface EntityStore<TMeta = undefined>
|
|
1798
|
+
|
|
1799
|
+
### @jgengine/core/scene/form
|
|
1800
|
+
|
|
1801
|
+
- createForms (function): function createForms(deps: FormsDeps): Forms
|
|
1802
|
+
- FormDef (interface): interface FormDef
|
|
1803
|
+
- FormChangedEvent (interface): interface FormChangedEvent
|
|
1804
|
+
- FormEntities (interface): interface FormEntities
|
|
1805
|
+
- FormTime (interface): interface FormTime
|
|
1806
|
+
- FormEvents (interface): interface FormEvents
|
|
1807
|
+
- FormsDeps (interface): interface FormsDeps
|
|
1808
|
+
- Forms (interface): interface Forms
|
|
1809
|
+
|
|
1810
|
+
### @jgengine/core/scene/mount
|
|
1811
|
+
|
|
1812
|
+
- createMountController (function): function createMountController(): MountController
|
|
1813
|
+
- MountKind (type): type MountKind = "ground" | "boat" | "flying" | "swimming"
|
|
1814
|
+
- MountKit (interface): interface MountKit
|
|
1815
|
+
- MountSeat (interface): interface MountSeat
|
|
1816
|
+
- RideableConfig (interface): interface RideableConfig
|
|
1817
|
+
- SeatRef (interface): interface SeatRef
|
|
1818
|
+
- Occupant (interface): interface Occupant
|
|
1819
|
+
- MountResult (type): type MountResult = { ok: true; seat: MountSeat } | { ok: false; reason: string }
|
|
1820
|
+
- MountController (class): class MountController — Mount / rideable control-transfer (issue #83). Registers rideables (each with one or more seats — a control seat drives, the rest ride) and tracks who is on what. It owns no camera or physics: game code reads `cameraTarget(riderId)` to point the follow camera at the mount, and `driveTarget(riderId)` to route that rider's {@link import("../physics/vehicleBody").AxisInput}-driven input at the mount's movement kit — the same seam a horse, a truck, or a shared multi-seat ship all plug into.
|
|
1821
|
+
|
|
1822
|
+
### @jgengine/core/scene/movementSpeed
|
|
1823
|
+
|
|
1824
|
+
- deriveWalkSpeed (function): function deriveWalkSpeed(base: number, modifiers: MovementSpeedModifiers): number — `base * (multiplier ?? 1) + (flatBonus ?? 0)`, clamped to never go negative.
|
|
1825
|
+
- applyStatDrivenSpeed (function): function applyStatDrivenSpeed(deps: MovementSpeedDeps, instanceId: string, config: MovementSpeedConfig): boolean — Recomputes `movement.walkSpeed` for an entity from its stats and writes it back via `deps.entities.update`. Either stat id is optional; an omitted or unresolved stat contributes its neutral value (multiplier 1, flat bonus 0) rather than failing. Returns false only when the entity itself doesn't exist.
|
|
1826
|
+
- MovementSpeedModifiers (interface): interface MovementSpeedModifiers
|
|
1827
|
+
- MovementSpeedDeps (interface): interface MovementSpeedDeps
|
|
1828
|
+
- MovementSpeedConfig (interface): interface MovementSpeedConfig
|
|
1829
|
+
|
|
1830
|
+
### @jgengine/core/scene/objectQuery
|
|
1831
|
+
|
|
1832
|
+
- raycastObjects (function): function raycastObjects(objects: readonly SceneObject[], input: ObjectRaycastInput): ObjectRaycastHit | null
|
|
1833
|
+
- raycastObjectsAll (function): function raycastObjectsAll(objects: readonly SceneObject[], input: ObjectRaycastInput): ObjectRaycastHit[]
|
|
1834
|
+
- ObjectRaycastInput (interface): interface ObjectRaycastInput
|
|
1835
|
+
- ObjectRaycastHit (interface): interface ObjectRaycastHit
|
|
1836
|
+
|
|
1837
|
+
### @jgengine/core/scene/objectStore
|
|
1838
|
+
|
|
1839
|
+
- createObjectStore (function): function createObjectStore(): ObjectStore
|
|
1840
|
+
- objectVisualScale (function): function objectVisualScale(visual: ObjectVisual | undefined): readonly [number, number, number]
|
|
1841
|
+
- ObjectVisual (interface): interface ObjectVisual
|
|
1842
|
+
- SceneObject (interface): interface SceneObject
|
|
1843
|
+
- PlaceOptions (interface): interface PlaceOptions
|
|
1844
|
+
- ObjectListFilter (interface): interface ObjectListFilter
|
|
1845
|
+
- ObjectStore (interface): interface ObjectStore
|
|
1846
|
+
|
|
1847
|
+
### @jgengine/core/scene/paintLayer
|
|
1848
|
+
|
|
1849
|
+
- createPaintLayer (function): function createPaintLayer(): PaintLayer
|
|
1850
|
+
- PaintStroke (interface): interface PaintStroke
|
|
1851
|
+
- PaintLayer (interface): interface PaintLayer
|
|
1852
|
+
|
|
1853
|
+
### @jgengine/core/scene/possession
|
|
1854
|
+
|
|
1855
|
+
- createPossession (function): function createPossession(deps: PossessionDeps): Possession
|
|
1856
|
+
- PossessionSwappedEvent (interface): interface PossessionSwappedEvent
|
|
1857
|
+
- PossessionEntities (interface): interface PossessionEntities
|
|
1858
|
+
- PossessionEvents (interface): interface PossessionEvents
|
|
1859
|
+
- PossessionDeps (interface): interface PossessionDeps
|
|
1860
|
+
- Possession (interface): interface Possession
|
|
1861
|
+
|
|
1862
|
+
### @jgengine/core/scene/roster
|
|
1863
|
+
|
|
1864
|
+
- createRoster (function): function createRoster(deps: RosterDeps = {}): Roster
|
|
1865
|
+
- RosterEntry (interface): interface RosterEntry
|
|
1866
|
+
- RosterCaptureOptions (interface): interface RosterCaptureOptions
|
|
1867
|
+
- Roster (interface): interface Roster
|
|
1868
|
+
- RosterDeps (interface): interface RosterDeps
|
|
1869
|
+
|
|
1870
|
+
### @jgengine/core/scene/selection
|
|
1871
|
+
|
|
1872
|
+
- screenRect (function): function screenRect(ax: number, ay: number, bx: number, by: number): ScreenRect — Normalize two drag corners (in any order) into a rectangle.
|
|
1873
|
+
- rectContainsPoint (function): function rectContainsPoint(rect: ScreenRect, x: number, y: number): boolean
|
|
1874
|
+
- selectWithinRect (function): function selectWithinRect(candidates: readonly ScreenPoint[], rect: ScreenRect): string[] — Ids of the projected candidates whose screen point falls inside the marquee.
|
|
1875
|
+
- isMarquee (function): function isMarquee(rect: ScreenRect, thresholdPx = 4): boolean — True when the drag is large enough to be a marquee rather than a click.
|
|
1876
|
+
- createSelectionSet (function): function createSelectionSet(initial?: Iterable<string>): SelectionSet — An ordered, deduplicated set of selected instance ids for RTS unit-command routing.
|
|
1877
|
+
- ScreenRect (interface): interface ScreenRect
|
|
1878
|
+
- ScreenPoint (interface): interface ScreenPoint
|
|
1879
|
+
- SelectionSet (interface): interface SelectionSet
|
|
1880
|
+
|
|
1881
|
+
### @jgengine/core/scene/spatial
|
|
1882
|
+
|
|
1883
|
+
- distanceBetween (function): function distanceBetween(a: EntityPosition, b: EntityPosition): number
|
|
1884
|
+
- createSpatialApi (function): function createSpatialApi(options: SpatialApiOptions): SpatialApi
|
|
1885
|
+
- Aim (type): type Aim = | { origin: EntityPosition; direction: EntityPosition } | { yaw: number; pitch: number; spread?: number }
|
|
1886
|
+
- QueryArcOptions (interface): interface QueryArcOptions
|
|
1887
|
+
- MoveTowardOptions (interface): interface MoveTowardOptions
|
|
1888
|
+
- SpatialGridOptions (interface): interface SpatialGridOptions
|
|
1889
|
+
- SpatialApiOptions (interface): interface SpatialApiOptions
|
|
1890
|
+
- SpatialApi (interface): interface SpatialApi
|
|
1891
|
+
|
|
1892
|
+
### @jgengine/core/scene/stationClaim
|
|
1893
|
+
|
|
1894
|
+
- createStationClaim (function): function createStationClaim(controller?: MountController): StationClaim
|
|
1895
|
+
- Station (interface): interface Station
|
|
1896
|
+
- SharedVehicleConfig (interface): interface SharedVehicleConfig
|
|
1897
|
+
- ClaimResult (type): type ClaimResult = | { ok: true; station: Station } | { ok: false; reason: "unknown_vehicle" | "unknown_facet" | "station_taken" | "vehicle_full" }
|
|
1898
|
+
- StationClaim (class): class StationClaim
|
|
1899
|
+
|
|
1900
|
+
### @jgengine/core/scene/targeting
|
|
1901
|
+
|
|
1902
|
+
- createTargeting (function): function createTargeting(options: TargetingOptions): Targeting
|
|
1903
|
+
- TargetRelation (type): type TargetRelation = "hostile" | "friendly"
|
|
1904
|
+
- TargetFilter (type): type TargetFilter = TargetRelation | "any"
|
|
1905
|
+
- TargetingOptions (interface): interface TargetingOptions
|
|
1906
|
+
- CycleTargetOptions (interface): interface CycleTargetOptions
|
|
1907
|
+
- Targeting (interface): interface Targeting
|
|
1908
|
+
|
|
1909
|
+
### @jgengine/core/sensor/concealment
|
|
1910
|
+
|
|
1911
|
+
- colorDistance (function): function colorDistance(a: ColorHex, b: ColorHex): number
|
|
1912
|
+
- concealmentScore (function): function concealmentScore(entityColors: readonly ColorHex[], backgroundColors: readonly ColorHex[]): number
|
|
1913
|
+
- createConcealmentSensor (function): function createConcealmentSensor(config?: { threshold?: number }): ConcealmentSensor
|
|
1914
|
+
- ColorHex (type): type ColorHex = string
|
|
1915
|
+
- ConcealmentTarget (interface): interface ConcealmentTarget
|
|
1916
|
+
- ConcealmentSample (interface): interface ConcealmentSample
|
|
1917
|
+
- ConcealmentSensor (interface): interface ConcealmentSensor
|
|
1918
|
+
|
|
1919
|
+
### @jgengine/core/sensor/freezeMonitor
|
|
1920
|
+
|
|
1921
|
+
- createFreezeMonitor (function): function createFreezeMonitor(config?: { toleranceSpeed?: number; graceSeconds?: number }): FreezeMonitor
|
|
1922
|
+
- FreezeSubject (interface): interface FreezeSubject
|
|
1923
|
+
- FreezeViolation (interface): interface FreezeViolation
|
|
1924
|
+
- FreezeMonitor (interface): interface FreezeMonitor
|
|
1925
|
+
|
|
1926
|
+
### @jgengine/core/sensor/frustumSensor
|
|
1927
|
+
|
|
1928
|
+
- projectToView (function): function projectToView(camera: FrustumCamera, point: EntityPosition): FrustumProjection — Project a world point into the camera's view. Pure geometry — no three.js — so a game can run "is this framed?" checks headlessly (photo-mode scoring, server-side kill-cam validation) as well as from the live render camera.
|
|
1929
|
+
- framingScore (function): function framingScore(projection: FrustumProjection, config?: FramingConfig): number — 0 (not framed) .. 1 (dead-center, ideal distance) framing quality for an in-view projection.
|
|
1930
|
+
- createFrustumSensor (function): function createFrustumSensor(config?: FramingConfig): FrustumSensor — A view-frustum sensor on a held camera object: which entities are in frame, how well framed they are, and how long they've stayed on-screen (photo-mode "is this subject framed", Content Warning-style monster-filming scoring).
|
|
1931
|
+
- FrustumCamera (interface): interface FrustumCamera
|
|
1932
|
+
- FrustumTarget (interface): interface FrustumTarget
|
|
1933
|
+
- FrustumProjection (interface): interface FrustumProjection
|
|
1934
|
+
- FramingConfig (interface): interface FramingConfig
|
|
1935
|
+
- FrustumSample (interface): interface FrustumSample
|
|
1936
|
+
- FrustumSensor (interface): interface FrustumSensor
|
|
1937
|
+
|
|
1938
|
+
### @jgengine/core/sensor/hiddenStateProbe
|
|
1939
|
+
|
|
1940
|
+
- probeHiddenState (function): function probeHiddenState(origin: EntityPosition, sources: readonly HiddenStateSource[], options: SensorProbeOptions): SensorReading | null — A sensor verb: probe a hidden zone/entity state variable in range and surface a reading (EMF reader, spirit box, thermometer, geiger counter). Unlike `proximityPrompt` (a UI affordance), this reads world state the player has no other way to see — the strongest in-range reading wins, mirroring how a real handheld sensor needle settles on the loudest nearby source.
|
|
1941
|
+
- probeHiddenStateAll (function): function probeHiddenStateAll(origin: EntityPosition, sources: readonly HiddenStateSource[], options: SensorProbeOptions): SensorReading[] — All in-range readings for `variableId`, strongest first.
|
|
1942
|
+
- HiddenStateValue (type): type HiddenStateValue = number | string | boolean
|
|
1943
|
+
- HiddenStateSource (interface): interface HiddenStateSource
|
|
1944
|
+
- SensorProbeOptions (interface): interface SensorProbeOptions
|
|
1945
|
+
- SensorReading (interface): interface SensorReading
|
|
1946
|
+
|
|
1947
|
+
### @jgengine/core/sensor/recordingBuffer
|
|
1948
|
+
|
|
1949
|
+
- createRecordingBuffer (function): function createRecordingBuffer<T>(options: RecordingBufferOptions = {}): RecordingBuffer<T> — A session-recording buffer for replay / photo mode / kill-cam: append timestamped snapshots (poses, camera state, whatever `T` is) on game-time, then seek/scrub the recording independent of the live sim. Frames are expected in non-decreasing `t` order (one push per tick); `seek`/`range` binary-search on that assumption.
|
|
1950
|
+
- RecordingFrame (interface): interface RecordingFrame<T>
|
|
1951
|
+
- RecordingBufferOptions (interface): interface RecordingBufferOptions
|
|
1952
|
+
- RecordingBuffer (interface): interface RecordingBuffer<T>
|
|
1953
|
+
|
|
1954
|
+
### @jgengine/core/sensor/revealQuery
|
|
1955
|
+
|
|
1956
|
+
- createRevealQuery (function): function createRevealQuery(options: RevealQueryOptions): RevealQuery — `inRadius` already ignores occlusion; combat's AoE resolution is what layers a line-of-sight filter on top of it (see `effect({ at, radius, los })`). This is the same unfiltered radius query, scoped to catalog-declared tags and shaped for a vision readout (Dark Sight / detective-vision reveal) instead of damage resolution — it never consults an occluder, by design.
|
|
1957
|
+
- RevealHit (interface): interface RevealHit
|
|
1958
|
+
- RevealQueryOptions (interface): interface RevealQueryOptions
|
|
1959
|
+
- RevealQuery (interface): interface RevealQuery
|
|
1960
|
+
|
|
1961
|
+
### @jgengine/core/session/contestedChannel
|
|
1962
|
+
|
|
1963
|
+
- createContestedChannel (function): function createContestedChannel(config: ContestedChannelConfig): ContestedChannel
|
|
1964
|
+
- ContestedPhase (type): type ContestedPhase = "idle" | "active" | "paused" | "contested" | "complete" | "interrupted"
|
|
1965
|
+
- ContestReaction (type): type ContestReaction = "pause" | "decay"
|
|
1966
|
+
- ContestedChannelConfig (interface): interface ContestedChannelConfig
|
|
1967
|
+
- ContestedEventKind (type): type ContestedEventKind = "start" | "tick" | "contested" | "paused" | "complete" | "interrupted"
|
|
1968
|
+
- ContestedEvent (interface): interface ContestedEvent
|
|
1969
|
+
- ContestedSnapshot (interface): interface ContestedSnapshot
|
|
1970
|
+
- ContestedChannel (interface): interface ContestedChannel
|
|
1971
|
+
|
|
1972
|
+
### @jgengine/core/session/extraction
|
|
1973
|
+
|
|
1974
|
+
- createRaidSession (function): function createRaidSession(config: RaidSessionConfig): RaidSession
|
|
1975
|
+
- ExtractPoint (interface): interface ExtractPoint
|
|
1976
|
+
- RaidSessionConfig (interface): interface RaidSessionConfig
|
|
1977
|
+
- RaidStatus (type): type RaidStatus = "in-raid" | "extracting" | "extracted" | "dead"
|
|
1978
|
+
- ExtractionAttempt (interface): interface ExtractionAttempt
|
|
1979
|
+
- ExtractionResult (interface): interface ExtractionResult
|
|
1980
|
+
- DeathResult (interface): interface DeathResult
|
|
1981
|
+
- RaidPlayerSnapshot (interface): interface RaidPlayerSnapshot
|
|
1982
|
+
- RaidSession (interface): interface RaidSession
|
|
1983
|
+
|
|
1984
|
+
### @jgengine/core/session/ring
|
|
1985
|
+
|
|
1986
|
+
- ringSampleAt (function): function ringSampleAt(config: RingConfig, time: number): RingSample
|
|
1987
|
+
- createRing (function): function createRing(config: RingConfig): Ring
|
|
1988
|
+
- RingPoint (type): type RingPoint = [number, number]
|
|
1989
|
+
- RingPhase (interface): interface RingPhase
|
|
1990
|
+
- RingConfig (interface): interface RingConfig
|
|
1991
|
+
- RingSample (interface): interface RingSample
|
|
1992
|
+
- RingHit (interface): interface RingHit
|
|
1993
|
+
- Ring (interface): interface Ring
|
|
1994
|
+
|
|
1995
|
+
### @jgengine/core/session/roles
|
|
1996
|
+
|
|
1997
|
+
- assignRoles (function): function assignRoles(userIds: readonly string[], roles: readonly RoleSpec[], rng: () => number = Math.random): Record<string, string>
|
|
1998
|
+
- RoleSpec (interface): interface RoleSpec
|
|
1999
|
+
|
|
2000
|
+
### @jgengine/core/session/roundState
|
|
2001
|
+
|
|
2002
|
+
- lossBonusFor (function): function lossBonusFor(rule: LossBonusRule | undefined, streak: number): number
|
|
2003
|
+
- createRoundState (function): function createRoundState(config: RoundConfig<RoundPhase>): RoundState<RoundPhase>
|
|
2004
|
+
- RoundPhase (type): type RoundPhase = string
|
|
2005
|
+
- RoundPhaseDurations (interface): interface RoundPhaseDurations — Default phase-duration shape for the built-in buy/live/end cycle; pass a wider `Record<string, number>` when using a custom `phaseOrder`.
|
|
2006
|
+
- LossBonusRule (interface): interface LossBonusRule
|
|
2007
|
+
- RoundTeam (interface): interface RoundTeam — A team entry with an optional role tag (e.g. "attacker", "defender") retrievable via `RoundState.roleOf`.
|
|
2008
|
+
- RoundConfig (interface): interface RoundConfig<TPhase extends string = RoundPhase>
|
|
2009
|
+
- RoundEventKind (type): type RoundEventKind = | "phase.start" | "phase.end" | "round.win" | "round.economy" | "match.end"
|
|
2010
|
+
- RoundEconomy (interface): interface RoundEconomy
|
|
2011
|
+
- RoundEvent (interface): interface RoundEvent<TPhase extends string = RoundPhase>
|
|
2012
|
+
- PhaseEndHook (type): type PhaseEndHook<TPhase extends string = RoundPhase> = ( endingPhase: TPhase, nextPhase: TPhase, round: number, ) => void
|
|
2013
|
+
- RoundSnapshot (interface): interface RoundSnapshot<TPhase extends string = RoundPhase>
|
|
2014
|
+
- RoundState (interface): interface RoundState<TPhase extends string = RoundPhase>
|
|
2015
|
+
|
|
2016
|
+
### @jgengine/core/stats/accumulatorMeter
|
|
2017
|
+
|
|
2018
|
+
- tierAt (function): function tierAt(value: number, tiers: readonly MeterTier[]): string | null
|
|
2019
|
+
- createAccumulatorMeter (function): function createAccumulatorMeter(config: AccumulatorMeterConfig): AccumulatorMeter
|
|
2020
|
+
- MeterTier (interface): interface MeterTier
|
|
2021
|
+
- MeterMode (type): type MeterMode = "hold" | "reset"
|
|
2022
|
+
- AccumulatorMeterConfig (interface): interface AccumulatorMeterConfig
|
|
2023
|
+
- MeterAddResult (interface): interface MeterAddResult
|
|
2024
|
+
- AccumulatorMeter (interface): interface AccumulatorMeter
|
|
2025
|
+
|
|
2026
|
+
### @jgengine/core/stats/eventMeter
|
|
2027
|
+
|
|
2028
|
+
- createEventMeter (function): function createEventMeter(config: EventMeterConfig): EventMeter
|
|
2029
|
+
- EventMeterConfig (interface): interface EventMeterConfig
|
|
2030
|
+
- EventMeterFeedResult (interface): interface EventMeterFeedResult
|
|
2031
|
+
- EventMeter (interface): interface EventMeter
|
|
2032
|
+
|
|
2033
|
+
### @jgengine/core/stats/rollCheck
|
|
2034
|
+
|
|
2035
|
+
- rollCheck (function): function rollCheck(input: CheckInput, rng: () => number = Math.random): CheckResult
|
|
2036
|
+
- CheckAdvantage (type): type CheckAdvantage = "advantage" | "disadvantage" | "normal"
|
|
2037
|
+
- CheckInput (interface): interface CheckInput
|
|
2038
|
+
- CheckResult (interface): interface CheckResult
|
|
2039
|
+
|
|
2040
|
+
### @jgengine/core/stats/statModifiers
|
|
2041
|
+
|
|
2042
|
+
- createStats (function): function createStats<TStat extends string>(base: Record<TStat, number>): Stats<TStat>
|
|
2043
|
+
- StatModifier (interface): interface StatModifier
|
|
2044
|
+
- StatModifierSet (type): type StatModifierSet<TStat extends string> = Partial<Record<TStat, StatModifier>>
|
|
2045
|
+
- Stats (interface): interface Stats<TStat extends string>
|
|
2046
|
+
|
|
2047
|
+
### @jgengine/core/store/changeSignal
|
|
2048
|
+
|
|
2049
|
+
- createChangeSignal (function): function createChangeSignal(): ChangeSignal
|
|
2050
|
+
- notifyAfter (function): function notifyAfter<T extends object, K extends keyof T>(target: T, methods: readonly K[], notify: () => void): T
|
|
2051
|
+
- ChangeSignal (interface): interface ChangeSignal
|
|
2052
|
+
|
|
2053
|
+
### @jgengine/core/store/engineStore
|
|
2054
|
+
|
|
2055
|
+
- EngineStoreConfig (interface): interface EngineStoreConfig<TState, TAction, TEventMap extends object>
|
|
2056
|
+
- EngineStore (class): class EngineStore<TState, TAction, TEventMap extends object>
|
|
2057
|
+
|
|
2058
|
+
### @jgengine/core/store/features
|
|
2059
|
+
|
|
2060
|
+
- composeStateFeatures (function): function composeStateFeatures<TState, TAction, TEventMap extends object>(features: readonly StateFeature<TState, TAction, TEventMap>[]): { reduce(state: TState, action: TAction): TState; derive(state: TState): TState; onTransition(previous: TState, next: TState, emit: <K extends keyof TEventMap>(eve…
|
|
2061
|
+
- StateFeature (interface): interface StateFeature<TState, TAction, TEventMap extends object>
|
|
2062
|
+
|
|
2063
|
+
### @jgengine/core/store/observableKeyedStore
|
|
2064
|
+
|
|
2065
|
+
- createObservableKeyedStore (function): function createObservableKeyedStore<T>(areEqual?: (previous: T, next: T) => boolean): ObservableKeyedStore<T>
|
|
2066
|
+
- ObservableKeyedStore (interface): interface ObservableKeyedStore<T>
|
|
2067
|
+
|
|
2068
|
+
### @jgengine/core/survival/decayMeter
|
|
2069
|
+
|
|
2070
|
+
- createDecayMeterSet (function): function createDecayMeterSet(configs: readonly DecayMeterConfig[]): DecayMeterSet — Named decay meters — hunger, thirst, oxygen, sanity, warmth, stamina. Each drains (or recovers) on game-time `dt` at a configurable rate, refills from consumables or actions, and raises moodle statuses at thresholds. Rate modifiers let the environment drive them (colder → faster warmth loss; toxic biome → oxygen drops), so a game reads an environment field then calls `setRateModifier`.
|
|
2071
|
+
- MeterThreshold (interface): interface MeterThreshold
|
|
2072
|
+
- DecayMeterConfig (interface): interface DecayMeterConfig
|
|
2073
|
+
- DecayMeterState (interface): interface DecayMeterState
|
|
2074
|
+
- DecayMeterSet (interface): interface DecayMeterSet
|
|
2075
|
+
|
|
2076
|
+
### @jgengine/core/survival/moodle
|
|
2077
|
+
|
|
2078
|
+
- stackMoodles (function): function stackMoodles(...groups: readonly (readonly Moodle[])[]): Moodle[] — Merge any number of moodle groups into one stack — meters, ailments, and buffs share this display. Same-id moodles fold together (stacks add, worst severity wins); the result is ordered worst-first so the HUD reads critical statuses at a glance.
|
|
2079
|
+
- createMoodleStack (function): function createMoodleStack(): MoodleStack — A stateful holder for timed status moodles (food buffs, temporary shelter, warmth). Meters and multi-region health derive their own moodles on read; combine all three through `stackMoodles(stack.list(), meterMoodles, ailmentMoodles)` for one display.
|
|
2080
|
+
- MoodleSeverity (type): type MoodleSeverity = "good" | "neutral" | "warning" | "critical"
|
|
2081
|
+
- MoodleSource (type): type MoodleSource = "meter" | "ailment" | "buff"
|
|
2082
|
+
- Moodle (interface): interface Moodle
|
|
2083
|
+
- MOODLE_SEVERITY_ORDER (const): const MOODLE_SEVERITY_ORDER: Record<MoodleSeverity, number>
|
|
2084
|
+
- TimedMoodleInput (interface): interface TimedMoodleInput
|
|
2085
|
+
- MoodleStack (interface): interface MoodleStack
|
|
2086
|
+
|
|
2087
|
+
### @jgengine/core/survival/regionHealth
|
|
2088
|
+
|
|
2089
|
+
- createMultiRegionHealth (function): function createMultiRegionHealth(config: MultiRegionHealthConfig): MultiRegionHealth
|
|
2090
|
+
- HealthRegionConfig (interface): interface HealthRegionConfig
|
|
2091
|
+
- AilmentConfig (interface): interface AilmentConfig
|
|
2092
|
+
- MultiRegionHealthConfig (interface): interface MultiRegionHealthConfig
|
|
2093
|
+
- RegionHealthState (interface): interface RegionHealthState
|
|
2094
|
+
- AilmentInstance (interface): interface AilmentInstance
|
|
2095
|
+
- DamageResult (interface): interface DamageResult
|
|
2096
|
+
- TreatResult (interface): interface TreatResult
|
|
2097
|
+
- MultiRegionHealth (interface): interface MultiRegionHealth
|
|
2098
|
+
|
|
2099
|
+
### @jgengine/core/tactics/fallingGrid
|
|
2100
|
+
|
|
2101
|
+
- gravityIntervalMs (function): function gravityIntervalMs(level: number, config: GravityIntervalConfig = {}): number — Gravity tick interval for a given level: linear speed-up with a floor, generalizing `Games/block-stacker`'s `gravityInterval` (`max(0.05, 0.8 - level * 0.07)` seconds) to milliseconds with configurable base/step/floor.
|
|
2102
|
+
- createFallingGrid (function): function createFallingGrid<TCell>(config: FallingGridConfig): FallingGrid<TCell> — A game-agnostic cell grid for gravity-driven falling-block mechanics: bounded occupancy, footprint placement/collision, line-clear collapse, column-gravity settle (for match-3/sand style cascades), and a pure lock-delay helper. Extracted from `Games/block-stacker`'s tetris logic so any falling-block game can adopt it instead of reimplementing the grid.
|
|
2103
|
+
- FallingGridCell (type): type FallingGridCell = readonly [number, number]
|
|
2104
|
+
- FallingGridConfig (interface): interface FallingGridConfig
|
|
2105
|
+
- LockState (interface): interface LockState — Elapsed grounded time for the lock-delay state machine. `null` means airborne / not timing.
|
|
2106
|
+
- FallingGridSnapshot (interface): interface FallingGridSnapshot<TCell>
|
|
2107
|
+
- FallingGrid (interface): interface FallingGrid<TCell>
|
|
2108
|
+
- GravityIntervalConfig (interface): interface GravityIntervalConfig
|
|
2109
|
+
|
|
2110
|
+
### @jgengine/core/tactics/predictiveQuery
|
|
2111
|
+
|
|
2112
|
+
- predictAreaEffect (function): function predictAreaEffect(deps: PredictiveDeps, input: AreaPredictInput): PredictedTarget[]
|
|
2113
|
+
- predictArcEffect (function): function predictArcEffect(deps: PredictiveDeps, input: ArcPredictInput): PredictedTarget[]
|
|
2114
|
+
- predictTiles (function): function predictTiles(input: TilePredictInput): Tile[]
|
|
2115
|
+
- PredictiveDeps (interface): interface PredictiveDeps
|
|
2116
|
+
- PredictedTarget (interface): interface PredictedTarget
|
|
2117
|
+
- AreaPredictInput (interface): interface AreaPredictInput extends AreaTargetInput
|
|
2118
|
+
- ArcPredictInput (interface): interface ArcPredictInput
|
|
2119
|
+
- TilePredictInput (interface): interface TilePredictInput
|
|
2120
|
+
|
|
2121
|
+
### @jgengine/core/tactics/snapshot
|
|
2122
|
+
|
|
2123
|
+
- deepClone (function): function deepClone<T>(value: T): T
|
|
2124
|
+
- createSnapshotStore (function): function createSnapshotStore(): SnapshotStore
|
|
2125
|
+
- SnapshotSlice (interface): interface SnapshotSlice<T = unknown>
|
|
2126
|
+
- Snapshot (type): type Snapshot = Record<string, unknown>
|
|
2127
|
+
- SnapshotStore (interface): interface SnapshotStore
|
|
2128
|
+
|
|
2129
|
+
### @jgengine/core/tactics/surface
|
|
2130
|
+
|
|
2131
|
+
- createSurfaceLayer (function): function createSurfaceLayer(config: SurfaceLayerConfig): SurfaceLayer
|
|
2132
|
+
- SurfaceKindDef (interface): interface SurfaceKindDef
|
|
2133
|
+
- SurfaceReaction (interface): interface SurfaceReaction
|
|
2134
|
+
- SurfaceLayerConfig (interface): interface SurfaceLayerConfig
|
|
2135
|
+
- SurfacePatch (interface): interface SurfacePatch
|
|
2136
|
+
- SurfaceEvent (type): type SurfaceEvent = | { kind: "apply"; tile: Tile; surface: string } | { kind: "react"; tile: Tile; consumed: readonly [string, string]; produced: string | null } | { kind: "expire"; tile: Tile; surface: string }
|
|
2137
|
+
- SurfaceCellKind (interface): interface SurfaceCellKind
|
|
2138
|
+
- SurfaceCell (interface): interface SurfaceCell
|
|
2139
|
+
- SurfaceLayerSnapshot (type): type SurfaceLayerSnapshot = Record<string, Record<string, number | null>>
|
|
2140
|
+
- SurfaceLayer (interface): interface SurfaceLayer
|
|
2141
|
+
|
|
2142
|
+
### @jgengine/core/tactics/tacticalGrid
|
|
2143
|
+
|
|
2144
|
+
- createTacticalGrid (function): function createTacticalGrid(config: TacticalGridConfig): TacticalGrid
|
|
2145
|
+
- Tile (type): type Tile = readonly [number, number]
|
|
2146
|
+
- TacticalGridWorldConfig (interface): interface TacticalGridWorldConfig — Maps abstract `[col, row]` tiles onto world-space `[x, z]`, mirroring `navGrid`'s bounds+cellSize convention.
|
|
2147
|
+
- TacticalGridConfig (interface): interface TacticalGridConfig
|
|
2148
|
+
- ReachableOptions (interface): interface ReachableOptions
|
|
2149
|
+
- ReachableTile (interface): interface ReachableTile
|
|
2150
|
+
- PushObstacle (type): type PushObstacle = "wall" | "edge"
|
|
2151
|
+
- PushCollision (interface): interface PushCollision
|
|
2152
|
+
- PushMove (interface): interface PushMove
|
|
2153
|
+
- PushResult (interface): interface PushResult
|
|
2154
|
+
- PushOptions (interface): interface PushOptions
|
|
2155
|
+
- TacticalGridSnapshot (interface): interface TacticalGridSnapshot
|
|
2156
|
+
- TacticalGrid (interface): interface TacticalGrid
|
|
2157
|
+
|
|
2158
|
+
### @jgengine/core/time/beatClock
|
|
2159
|
+
|
|
2160
|
+
- createBeatClock (function): function createBeatClock(config: BeatClockConfig, onBeat?: (beatIndex: number) => void): BeatClock
|
|
2161
|
+
- nextBeatTime (function): function nextBeatTime(nowSec: number, beatDurationSec: number, epsilon = QUANTIZE_EPSILON): number — The absolute beat-time (game-seconds) that a press at `nowSec` quantizes to: the same instant if it lands on a beat boundary, else the next one.
|
|
2162
|
+
- createBeatInputBuffer (function): function createBeatInputBuffer<T>(beatDurationSec: number): BeatInputBuffer<T>
|
|
2163
|
+
- BeatClockConfig (interface): interface BeatClockConfig
|
|
2164
|
+
- BeatSnapshot (interface): interface BeatSnapshot
|
|
2165
|
+
- BeatClock (interface): interface BeatClock
|
|
2166
|
+
- BufferedAction (interface): interface BufferedAction<T>
|
|
2167
|
+
- BeatInputBuffer (interface): interface BeatInputBuffer<T>
|
|
2168
|
+
|
|
2169
|
+
### @jgengine/core/time/calendarClock
|
|
2170
|
+
|
|
2171
|
+
- computeGameTime (function): function computeGameTime(gameTimestamp: number): GameTime
|
|
2172
|
+
- computeClientServerOffset (function): function computeClientServerOffset(serverNow: number, clientNow: number): number
|
|
2173
|
+
- getGameClockTimestamp (function): function getGameClockTimestamp(createdAt: number, serverNow: number, clientNow: number, gameTimeScale?: number): number
|
|
2174
|
+
- GameTime (interface): interface GameTime
|
|
2175
|
+
|
|
2176
|
+
### @jgengine/core/time/gameClock
|
|
2177
|
+
|
|
2178
|
+
- sanitizeGameTimeScale (function): function sanitizeGameTimeScale(timeScale?: number | null): number
|
|
2179
|
+
- getScaledElapsedMs (function): function getScaledElapsedMs(createdAt: number, now: number, timeScale?: number | null): number
|
|
2180
|
+
- getCurrentGameTimestamp (function): function getCurrentGameTimestamp(createdAt: number, now: number, timeScale?: number | null): number
|
|
2181
|
+
- computeGameDay (function): function computeGameDay(createdAt: number, now: number = Date.now(), timeScale?: number | null): number
|
|
2182
|
+
- DEFAULT_TIME_SCALE (const): const DEFAULT_TIME_SCALE: 24 — Simulation clock: real time scaled into game time, plus the derived game-day counter.
|
|
2183
|
+
- SECONDS_PER_GAME_DAY (const): const SECONDS_PER_GAME_DAY: number
|
|
2184
|
+
|
|
2185
|
+
### @jgengine/core/time/serverTick
|
|
2186
|
+
|
|
2187
|
+
- planServerTick (function): function planServerTick<TSystemId extends string>(systems: readonly TickSystemDefinition<TSystemId>[], anchors: TickAnchors, now: number): ServerTickPlan<TSystemId> — Decides which systems a heartbeat should run. A system with no anchor runs immediately. Returned anchors carry only systems present in the pipeline, so removed systems clean up.
|
|
2188
|
+
- TickSystemDefinition (interface): interface TickSystemDefinition<TSystemId extends string = string>
|
|
2189
|
+
- TickAnchors (type): type TickAnchors = Record<string, number> — Last-run timestamp per system id.
|
|
2190
|
+
- ServerTickPlan (interface): interface ServerTickPlan<TSystemId extends string = string>
|
|
2191
|
+
|
|
2192
|
+
### @jgengine/core/time/simClock
|
|
2193
|
+
|
|
2194
|
+
- createSimClock (function): function createSimClock(options: SimClockOptions = {}): SimClock
|
|
2195
|
+
- TimeConfig (interface): interface TimeConfig
|
|
2196
|
+
- CalendarTime (interface): interface CalendarTime
|
|
2197
|
+
- ClockSnapshot (interface): interface ClockSnapshot
|
|
2198
|
+
- SimClock (interface): interface SimClock
|
|
2199
|
+
- SimClockOptions (interface): interface SimClockOptions
|
|
2200
|
+
|
|
2201
|
+
### @jgengine/core/turn/commit
|
|
2202
|
+
|
|
2203
|
+
- createCommitController (function): function createCommitController<TAction>(config: CommitControllerConfig): CommitController<TAction>
|
|
2204
|
+
- CommitMode (type): type CommitMode = "immediate" | "simultaneous" | "rewind"
|
|
2205
|
+
- SubmittedAction (interface): interface SubmittedAction<TAction>
|
|
2206
|
+
- CommitOutcome (interface): interface CommitOutcome<TAction>
|
|
2207
|
+
- CommitControllerConfig (interface): interface CommitControllerConfig
|
|
2208
|
+
- CommitController (interface): interface CommitController<TAction>
|
|
2209
|
+
|
|
2210
|
+
### @jgengine/core/turn/intent
|
|
2211
|
+
|
|
2212
|
+
- createIntentBoard (function): function createIntentBoard<TKind extends string = string>(): IntentBoard<TKind>
|
|
2213
|
+
- DeclaredIntent (interface): interface DeclaredIntent<TKind extends string = string>
|
|
2214
|
+
- IntentBoard (interface): interface IntentBoard<TKind extends string = string>
|
|
2215
|
+
|
|
2216
|
+
### @jgengine/core/turn/turnLoop
|
|
2217
|
+
|
|
2218
|
+
- createTurnLoop (function): function createTurnLoop<TAction = unknown>(config: TurnLoopConfig): TurnLoop<TAction>
|
|
2219
|
+
- CommitMode (type): type CommitMode = "immediate" | "simultaneous" | "rewind"
|
|
2220
|
+
- CommitController (interface): interface CommitController<TAction>
|
|
2221
|
+
- PoolConfig (interface): interface PoolConfig
|
|
2222
|
+
- TurnLoopConfig (interface): interface TurnLoopConfig
|
|
2223
|
+
- PoolState (interface): interface PoolState
|
|
2224
|
+
- TurnState (interface): interface TurnState
|
|
2225
|
+
- TurnLoopSnapshot (interface): interface TurnLoopSnapshot
|
|
2226
|
+
- TurnLoop (interface): interface TurnLoop<TAction = unknown>
|
|
2227
|
+
|
|
2228
|
+
### @jgengine/core/world/buildingIndex
|
|
2229
|
+
|
|
2230
|
+
- buildingIndex (function): function buildingIndex(buildings: readonly GeneratedBuilding[]): BuildingIndex
|
|
2231
|
+
- BuildingHit (interface): interface BuildingHit
|
|
2232
|
+
- BuildingIndex (interface): interface BuildingIndex
|
|
2233
|
+
|
|
2234
|
+
### @jgengine/core/world/buildings
|
|
2235
|
+
|
|
2236
|
+
- createBuildingConfig (function): function createBuildingConfig(input: BuildingConfigInput = {}): BuildingConfig
|
|
2237
|
+
- generateBuilding (function): function generateBuilding(input: BuildingConfigInput = {}): GeneratedBuilding
|
|
2238
|
+
- createBuildingGrid (function): function createBuildingGrid(config: BuildingGridConfig): BuildingLot[]
|
|
2239
|
+
- generateBuildingDistrict (function): function generateBuildingDistrict(config: BuildingGridConfig): GeneratedBuilding[]
|
|
2240
|
+
- BuildingSeed (type): type BuildingSeed = number | string
|
|
2241
|
+
- Vec3 (type): type Vec3 = readonly [number, number, number]
|
|
2242
|
+
- BuildingFacade (type): type BuildingFacade = "front" | "back" | "left" | "right" | "roof"
|
|
2243
|
+
- BuildingPartKind (type): type BuildingPartKind = | "wall" | "window" | "awning" | "airConditioner" | "clothesline" | "storefront" | "shutter" | "storeSign" | "roof" | "roofProp" | "guardrail" | "corner"
|
|
2244
|
+
- BuildingPartTag (type): type BuildingPartTag = "ground" | "upper" | "open" | "closed" | "decor" | "structure"
|
|
2245
|
+
- BuildingProbabilities (interface): interface BuildingProbabilities
|
|
2246
|
+
- BuildingVariantCounts (interface): interface BuildingVariantCounts
|
|
2247
|
+
- BuildingConfig (interface): interface BuildingConfig
|
|
2248
|
+
- BuildingConfigInput (type): type BuildingConfigInput = Partial< Omit<BuildingConfig, "probabilities" | "variants"> > & { probabilities?: Partial<BuildingProbabilities>; variants?: Partial<BuildingVariantCounts>; }
|
|
2249
|
+
- BuildingKitSlot (interface): interface BuildingKitSlot
|
|
2250
|
+
- BuildingCellRef (interface): interface BuildingCellRef
|
|
2251
|
+
- BuildingPartPlacement (interface): interface BuildingPartPlacement
|
|
2252
|
+
- GeneratedBuilding (interface): interface GeneratedBuilding
|
|
2253
|
+
- BuildingLot (interface): interface BuildingLot
|
|
2254
|
+
- BuildingGridConfig (interface): interface BuildingGridConfig
|
|
2255
|
+
- DEFAULT_BUILDING_CONFIG (const): const DEFAULT_BUILDING_CONFIG: BuildingConfig
|
|
2256
|
+
|
|
2257
|
+
### @jgengine/core/world/buildPermissions
|
|
2258
|
+
|
|
2259
|
+
- createPlotPermissions (function): function createPlotPermissions(config: PlotPermissionConfig): PlotPermissions
|
|
2260
|
+
- createContributionPool (function): function createContributionPool(goal: ContributionGoal): ContributionPool
|
|
2261
|
+
- BuildRole (type): type BuildRole = "owner" | "editor" | "viewer"
|
|
2262
|
+
- PlotPermissionConfig (interface): interface PlotPermissionConfig
|
|
2263
|
+
- PlotPermissionSnapshot (interface): interface PlotPermissionSnapshot
|
|
2264
|
+
- BuildActor (interface): interface BuildActor
|
|
2265
|
+
- PlotPermissions (interface): interface PlotPermissions
|
|
2266
|
+
- ContributionGoal (type): type ContributionGoal = Readonly<Record<string, number>>
|
|
2267
|
+
- ContributionResult (interface): interface ContributionResult
|
|
2268
|
+
- ContributionPool (interface): interface ContributionPool
|
|
2269
|
+
|
|
2270
|
+
### @jgengine/core/world/carve
|
|
2271
|
+
|
|
2272
|
+
- carvableTerrain (function): function carvableTerrain(base: TerrainField): CarvableField
|
|
2273
|
+
- EMPTY_VOXEL (const): const EMPTY_VOXEL: 0
|
|
2274
|
+
- VoxelMaterial (interface): interface VoxelMaterial
|
|
2275
|
+
- VoxelMaterialTable (type): type VoxelMaterialTable = Readonly<Record<number, VoxelMaterial>>
|
|
2276
|
+
- VoxelVolumeConfig (interface): interface VoxelVolumeConfig
|
|
2277
|
+
- CarveOp (interface): interface CarveOp
|
|
2278
|
+
- DepositOp (interface): interface DepositOp
|
|
2279
|
+
- VoxelVolume (class): class VoxelVolume — A runtime-editable dense voxel grid — the carve/deposit op behind destructible dig worlds (Deep Rock Galactic tunnels, Astroneer terrain). Cells hold a material id (0 = empty); `carve` clears a sphere of solid cells that a tool is strong enough to break and returns how many it removed (feed that to a loot roll), `deposit` fills a sphere with a material. World↔cell mapping is `origin`+`scale`.
|
|
2280
|
+
- CraterOp (interface): interface CraterOp
|
|
2281
|
+
- MoundOp (interface): interface MoundOp
|
|
2282
|
+
- CarvableField (class): class CarvableField implements TerrainField — A `TerrainField` you can write craters and mounds into at runtime — the height-field side of destructible terrain (Helldivers 2 explosion craters, engineer-deposited berms). Wraps a base field and layers smooth radial deformations on top, so `sampleHeight` (and therefore ground-snap, collision, and the shell's terrain mesh) all read the deformed surface. `carve` digs a bowl, `deposit` raises a mound.
|
|
2283
|
+
|
|
2284
|
+
### @jgengine/core/world/connectors
|
|
2285
|
+
|
|
2286
|
+
- socketsCompatible (function): function socketsCompatible(a: ConnectorSocket, b: ConnectorSocket): boolean
|
|
2287
|
+
- socketWorldPosition (function): function socketWorldPosition(socket: ConnectorSocket, origin: ConnectorVec3, rotationY: number): ConnectorVec3
|
|
2288
|
+
- worldSockets (function): function worldSockets(def: ConnectorPieceDef, piece: PlacedPiece): WorldSocket[]
|
|
2289
|
+
- collectWorldSockets (function): function collectWorldSockets(registry: ConnectorRegistry, pieces: readonly PlacedPiece[]): WorldSocket[]
|
|
2290
|
+
- snapToNearest (function): function snapToNearest(registry: ConnectorRegistry, placed: readonly PlacedPiece[], movingDef: ConnectorPieceDef, cursor: ConnectorVec3, options: SnapOptions = {}): SnapResult | null
|
|
2291
|
+
- ConnectorVec3 (type): type ConnectorVec3 = readonly [number, number, number]
|
|
2292
|
+
- ConnectorSocket (interface): interface ConnectorSocket
|
|
2293
|
+
- ConnectorPieceDef (interface): interface ConnectorPieceDef
|
|
2294
|
+
- PlacedPiece (interface): interface PlacedPiece
|
|
2295
|
+
- WorldSocket (interface): interface WorldSocket
|
|
2296
|
+
- SnapResult (interface): interface SnapResult
|
|
2297
|
+
- SnapOptions (interface): interface SnapOptions
|
|
2298
|
+
- ConnectorRegistry (type): type ConnectorRegistry = (kind: string) => ConnectorPieceDef | null
|
|
2299
|
+
|
|
2300
|
+
### @jgengine/core/world/envField
|
|
2301
|
+
|
|
2302
|
+
- createEnvironmentField (function): function createEnvironmentField(config: EnvironmentFieldConfig = {}): EnvironmentField — A sampleable environment field: read temperature, wetness, sun/sky exposure, and ambient light at any world position and time. Built on the same renderer-free footing as terrain/wind/water so meters, spawn gating, and damage-in-sunlight read the world the shell renders — no three.js. Instantaneous and pure (no accumulation); stateful build-up belongs to a decay meter reading this field.
|
|
2303
|
+
- OccluderRect (interface): interface OccluderRect — A sky-occluding footprint — a roof, dense canopy, a cave mouth. `shade` 0..1 = fraction of sky blocked.
|
|
2304
|
+
- HeatSource (interface): interface HeatSource — A localized warmth source — campfire, forge, geothermal vent.
|
|
2305
|
+
- ShadeProvider (type): type ShadeProvider = readonly OccluderRect[] | ((x: number, z: number) => number)
|
|
2306
|
+
- ScalarField (type): type ScalarField = number | ((x: number, z: number, time: number) => number)
|
|
2307
|
+
- EnvironmentFieldConfig (interface): interface EnvironmentFieldConfig
|
|
2308
|
+
- EnvironmentSample (interface): interface EnvironmentSample
|
|
2309
|
+
- EnvironmentField (interface): interface EnvironmentField
|
|
2310
|
+
|
|
2311
|
+
### @jgengine/core/world/environmentSummary
|
|
2312
|
+
|
|
2313
|
+
- resolveStructureBuildings (function): function resolveStructureBuildings(descriptor: BuildingEnvironmentDescriptor): GeneratedBuilding[]
|
|
2314
|
+
- summarizeEnvironment (function): function summarizeEnvironment(feature: EnvironmentWorldFeature): EnvironmentSummary
|
|
2315
|
+
- TerrainHeightStats (interface): interface TerrainHeightStats
|
|
2316
|
+
- TerrainSummary (interface): interface TerrainSummary
|
|
2317
|
+
- StructureSummary (interface): interface StructureSummary
|
|
2318
|
+
- VegetationSummary (interface): interface VegetationSummary
|
|
2319
|
+
- WaterSummary (interface): interface WaterSummary
|
|
2320
|
+
- WeatherSummary (interface): interface WeatherSummary
|
|
2321
|
+
- EnvironmentCounts (interface): interface EnvironmentCounts
|
|
2322
|
+
- EnvironmentSummary (interface): interface EnvironmentSummary
|
|
2323
|
+
|
|
2324
|
+
### @jgengine/core/world/features
|
|
2325
|
+
|
|
2326
|
+
- padFlattenMasks (function): function padFlattenMasks(pads: readonly PadEnvironmentDescriptor[]): readonly TerrainFlattenMask[] — Derives implicit `TerrainFlattenMask`s carving each pad's footprint into the terrain beneath it.
|
|
2327
|
+
- environment (function): function environment(config: EnvironmentWorldConfig = {}): EnvironmentWorldFeature
|
|
2328
|
+
- terrain (function): function terrain(config: TerrainEnvironmentConfig = {}): TerrainEnvironmentDescriptor
|
|
2329
|
+
- sky (function): function sky(config: SkyEnvironmentConfig = {}): SkyEnvironmentDescriptor
|
|
2330
|
+
- rain (function): function rain(config: RainEnvironmentConfig = {}): RainEnvironmentDescriptor
|
|
2331
|
+
- snow (function): function snow(config: SnowEnvironmentConfig = {}): SnowEnvironmentDescriptor
|
|
2332
|
+
- grass (function): function grass(config: GrassEnvironmentConfig = {}): GrassEnvironmentDescriptor
|
|
2333
|
+
- ocean (function): function ocean(config: OceanEnvironmentConfig = {}): OceanEnvironmentDescriptor
|
|
2334
|
+
- building (function): function building(config: BuildingEnvironmentConfig = {}): BuildingEnvironmentDescriptor
|
|
2335
|
+
- pad (function): function pad(config: PadEnvironmentConfig): PadEnvironmentDescriptor
|
|
2336
|
+
- biomes (function): function biomes(config: BiomesWorldConfig): WorldFeature
|
|
2337
|
+
- voxel (function): function voxel(config: VoxelWorldConfig): WorldFeature
|
|
2338
|
+
- plots (function): function plots(config: PlotsWorldConfig = {}): WorldFeature
|
|
2339
|
+
- tilemap (function): function tilemap(config: TilemapWorldConfig): WorldFeature
|
|
2340
|
+
- flat (function): function flat(): WorldFeature
|
|
2341
|
+
- WorldBounds (interface): interface WorldBounds
|
|
2342
|
+
- EnvironmentVec2 (type): type EnvironmentVec2 = readonly [number, number]
|
|
2343
|
+
- EnvironmentArea (interface): interface EnvironmentArea extends WorldBounds
|
|
2344
|
+
- TerrainColors (interface): interface TerrainColors
|
|
2345
|
+
- TerrainFlattenMask (interface): interface TerrainFlattenMask
|
|
2346
|
+
- TerrainEnvironmentConfig (interface): interface TerrainEnvironmentConfig
|
|
2347
|
+
- RainEnvironmentConfig (interface): interface RainEnvironmentConfig
|
|
2348
|
+
- SnowEnvironmentConfig (interface): interface SnowEnvironmentConfig
|
|
2349
|
+
- GrassEnvironmentConfig (interface): interface GrassEnvironmentConfig
|
|
2350
|
+
- OceanEnvironmentConfig (interface): interface OceanEnvironmentConfig
|
|
2351
|
+
- SkyEnvironmentConfig (interface): interface SkyEnvironmentConfig
|
|
2352
|
+
- BuildingEnvironmentConfig (interface): interface BuildingEnvironmentConfig
|
|
2353
|
+
- PadSize (type): type PadSize = readonly [number, number] | { radius: number }
|
|
2354
|
+
- PadEnvironmentConfig (interface): interface PadEnvironmentConfig
|
|
2355
|
+
- TerrainEnvironmentDescriptor (type): type TerrainEnvironmentDescriptor = { kind: "terrain" } & Required< Pick<TerrainEnvironmentConfig, "bounds" | "height"> > & Omit<TerrainEnvironmentConfig, "bounds" | "height">
|
|
2356
|
+
- RainEnvironmentDescriptor (type): type RainEnvironmentDescriptor = { kind: "rain" } & Required< Pick<RainEnvironmentConfig, "area" | "density" | "speed" | "dropLength" | "wind" | "color"> >
|
|
2357
|
+
- SnowEnvironmentDescriptor (type): type SnowEnvironmentDescriptor = { kind: "snow" } & Required< Pick<SnowEnvironmentConfig, "area" | "density" | "speed" | "flakeSize" | "drift" | "wind" | "color"> >
|
|
2358
|
+
- GrassEnvironmentDescriptor (type): type GrassEnvironmentDescriptor = { kind: "grass" } & Required< Pick<GrassEnvironmentConfig, "area" | "density" | "bladeHeight" | "bladeWidth" | "windStrength" | "colors"> > & Pick<GrassEnvironmentConfig, "seed">
|
|
2359
|
+
- OceanEnvironmentDescriptor (type): type OceanEnvironmentDescriptor = { kind: "ocean" } & Required< Pick<OceanEnvironmentConfig, "bounds" | "level" | "waveHeight" | "waveScale" | "waveSpeed" | "color"> > & Pick<OceanEnvironmentConfig, "position">
|
|
2360
|
+
- BuildingEnvironmentDescriptor (type): type BuildingEnvironmentDescriptor = { kind: "building" } & Required< Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style"> > & Pick<BuildingEnvironmentConfig, "seed" | "position">
|
|
2361
|
+
- PadEnvironmentDescriptor (type): type PadEnvironmentDescriptor = { kind: "pad" } & Required< Pick<PadEnvironmentConfig, "center" | "size" | "height" | "color"> > & Pick<PadEnvironmentConfig, "rotationY">
|
|
2362
|
+
- SkyEnvironmentDescriptor (type): type SkyEnvironmentDescriptor = { kind: "sky" } & Required< Pick<SkyEnvironmentConfig, "preset" | "timeOfDay"> > & Omit<SkyEnvironmentConfig, "preset" | "timeOfDay">
|
|
2363
|
+
- WeatherEnvironmentDescriptor (type): type WeatherEnvironmentDescriptor = RainEnvironmentDescriptor | SnowEnvironmentDescriptor
|
|
2364
|
+
- VegetationEnvironmentDescriptor (type): type VegetationEnvironmentDescriptor = GrassEnvironmentDescriptor
|
|
2365
|
+
- WaterEnvironmentDescriptor (type): type WaterEnvironmentDescriptor = OceanEnvironmentDescriptor
|
|
2366
|
+
- StructureEnvironmentDescriptor (type): type StructureEnvironmentDescriptor = BuildingEnvironmentDescriptor
|
|
2367
|
+
- EnvironmentDescriptorList (type): type EnvironmentDescriptorList<T> = T | readonly T[]
|
|
2368
|
+
- EnvironmentWorldConfig (interface): interface EnvironmentWorldConfig
|
|
2369
|
+
- EnvironmentWorldFeature (interface): interface EnvironmentWorldFeature
|
|
2370
|
+
- WorldGridCell (interface): interface WorldGridCell
|
|
2371
|
+
- WorldGridConfig (interface): interface WorldGridConfig — Shared by `biomes()`/`voxel()`/`plots()`/`tilemap()` so the shell can render their declared content as instanced boxes without a hand-written renderer.
|
|
2372
|
+
- BiomesWorldConfig (interface): interface BiomesWorldConfig extends WorldGridConfig
|
|
2373
|
+
- VoxelWorldConfig (interface): interface VoxelWorldConfig extends WorldGridConfig
|
|
2374
|
+
- PlotsWorldConfig (interface): interface PlotsWorldConfig extends WorldGridConfig
|
|
2375
|
+
- TilemapWorldConfig (interface): interface TilemapWorldConfig extends WorldGridConfig
|
|
2376
|
+
- WorldFeature (type): type WorldFeature = | ({ kind: "biomes" } & BiomesWorldConfig) | ({ kind: "voxel" } & VoxelWorldConfig) | ({ kind: "plots" } & PlotsWorldConfig) | ({ kind: "tilemap" } & TilemapWorldConfig) | EnvironmentWorldFeature | { kind: "flat" }
|
|
2377
|
+
|
|
2378
|
+
### @jgengine/core/world/fog
|
|
2379
|
+
|
|
2380
|
+
- createFogField (function): function createFogField(config: FogConfig): FogField
|
|
2381
|
+
- FogBounds (interface): interface FogBounds
|
|
2382
|
+
- FogConfig (interface): interface FogConfig
|
|
2383
|
+
- FogCells (interface): interface FogCells
|
|
2384
|
+
- FogField (interface): interface FogField — Reveal-on-event fog of war over a fixed grid. Walking (`revealAlong`) and digging/acting (`reveal`) clear cells; once revealed a cell stays revealed. Pure and renderer-free — the shell/react map draws `cells()`.
|
|
2385
|
+
|
|
2386
|
+
### @jgengine/core/world/geometry
|
|
2387
|
+
|
|
2388
|
+
- snapToGrid (function): function snapToGrid(point: Vec2, size: number): Vec2
|
|
2389
|
+
- footprintAabb (function): function footprintAabb(center: Vec2, footprint: Footprint, quarterTurns = 0): Aabb
|
|
2390
|
+
- expandAabb (function): function expandAabb(aabb: Aabb, margin: number): Aabb
|
|
2391
|
+
- aabbOverlap (function): function aabbOverlap(a: Aabb, b: Aabb): boolean
|
|
2392
|
+
- aabbContains (function): function aabbContains(outer: Aabb, inner: Aabb): boolean
|
|
2393
|
+
- pointInAabb (function): function pointInAabb(point: Vec2, aabb: Aabb): boolean
|
|
2394
|
+
- clampToAabb (function): function clampToAabb(point: Vec2, aabb: Aabb): Vec2
|
|
2395
|
+
- resolveMove (function): function resolveMove(from: Vec2, delta: Vec2, blockers: readonly Aabb[], options: MoveOptions = {}): Vec2
|
|
2396
|
+
- Vec2 (type): type Vec2 = readonly [number, number]
|
|
2397
|
+
- Footprint (interface): interface Footprint
|
|
2398
|
+
- Aabb (interface): interface Aabb
|
|
2399
|
+
- MoveOptions (interface): interface MoveOptions
|
|
2400
|
+
|
|
2401
|
+
### @jgengine/core/world/gridInstances
|
|
2402
|
+
|
|
2403
|
+
- resolveGridInstances (function): function resolveGridInstances(config: WorldGridConfig): readonly GridInstanceTransform[] — Pure cell→instance math for `biomes()`/`voxel()`/`plots()`/`tilemap()` world features: one extruded box per declared cell, grounded at y=0 and centered on its cell in x/z. Consumed by the shell's grid-world renderer so it can drive a single `THREE.InstancedMesh` without duplicating this math per game.
|
|
2404
|
+
- GridInstanceTransform (interface): interface GridInstanceTransform
|
|
2405
|
+
|
|
2406
|
+
### @jgengine/core/world/interiors
|
|
2407
|
+
|
|
2408
|
+
- createInteriors (function): function createInteriors(config: InteriorsConfig = {}): Interiors
|
|
2409
|
+
- SpaceRef (type): type SpaceRef = { kind: "exterior" } | { kind: "interior"; id: string }
|
|
2410
|
+
- EntityLocation (interface): interface EntityLocation
|
|
2411
|
+
- Interior (interface): interface Interior
|
|
2412
|
+
- Exterior (interface): interface Exterior
|
|
2413
|
+
- InteriorsConfig (interface): interface InteriorsConfig
|
|
2414
|
+
- Interiors (interface): interface Interiors
|
|
2415
|
+
|
|
2416
|
+
### @jgengine/core/world/markers
|
|
2417
|
+
|
|
2418
|
+
- markerKindStyle (function): function markerKindStyle(kind: string, styles: Record<string, MarkerKindStyle> = DEFAULT_MARKER_KINDS): MarkerKindStyle
|
|
2419
|
+
- createMarkerSet (function): function createMarkerSet<TMeta = unknown>(now: () => number = Date.now): MarkerSet<TMeta>
|
|
2420
|
+
- MarkerPosition (type): type MarkerPosition = readonly [number, number, number]
|
|
2421
|
+
- MapMarker (interface): interface MapMarker<TMeta = unknown>
|
|
2422
|
+
- MarkerInput (interface): interface MarkerInput<TMeta = unknown>
|
|
2423
|
+
- MarkerQuery (interface): interface MarkerQuery
|
|
2424
|
+
- MarkerKindStyle (interface): interface MarkerKindStyle — Visual descriptor for a marker kind. Games supply their own palette; the engine ships `DEFAULT_MARKER_KINDS` as a content-agnostic starting set that the react minimap/compass read for colors and glyphs.
|
|
2425
|
+
- DEFAULT_MARKER_KINDS (const): const DEFAULT_MARKER_KINDS: Record<string, MarkerKindStyle>
|
|
2426
|
+
- MarkerSet (interface): interface MarkerSet<TMeta = unknown>
|
|
2427
|
+
|
|
2428
|
+
### @jgengine/core/world/minimap
|
|
2429
|
+
|
|
2430
|
+
- compassBearing (function): function compassBearing(from: WorldXZ, to: WorldXZ): number — Compass bearing (radians, 0 = map north = −Z, increasing clockwise toward +X = east) from one world XZ point to another. Feeds both the minimap direction and the compass strip.
|
|
2431
|
+
- headingToBearing (function): function headingToBearing(yaw: number): number — Bearing of an entity facing direction given its `rotationY` (yaw) in radians.
|
|
2432
|
+
- bearingToCardinal (function): function bearingToCardinal(bearing: number): Cardinal
|
|
2433
|
+
- relativeBearing (function): function relativeBearing(bearing: number, reference: number): number — Signed offset of `bearing` from `reference`, wrapped into (−π, π].
|
|
2434
|
+
- projectToMinimap (function): function projectToMinimap(world: WorldXZ | readonly [number, number, number], view: MinimapView): MinimapPoint — Project a world XZ (or XYZ) point into minimap pixel space. Origin is the top-left of the `size×size` box; north (−Z) maps to −Y (up). Pass `view.rotate` to spin the map under a fixed north-up player arrow.
|
|
2435
|
+
- clampToMinimapEdge (function): function clampToMinimapEdge(point: MinimapPoint, size: number): { x: number; y: number } — Clamp a projected point to the minimap edge, preserving direction (edge markers).
|
|
2436
|
+
- WorldXZ (type): type WorldXZ = readonly [number, number]
|
|
2437
|
+
- Cardinal (type): type Cardinal = "N" | "NE" | "E" | "SE" | "S" | "SW" | "W" | "NW"
|
|
2438
|
+
- MinimapView (interface): interface MinimapView
|
|
2439
|
+
- MinimapPoint (interface): interface MinimapPoint
|
|
2440
|
+
|
|
2441
|
+
### @jgengine/core/world/placedStructureStore
|
|
2442
|
+
|
|
2443
|
+
- createPlacedStructureStore (function): function createPlacedStructureStore(): PlacedStructureStore
|
|
2444
|
+
- StructureVec3 (type): type StructureVec3 = readonly [number, number, number]
|
|
2445
|
+
- PlacedStructure (interface): interface PlacedStructure
|
|
2446
|
+
- AddStructureInput (interface): interface AddStructureInput
|
|
2447
|
+
- StructureFilter (interface): interface StructureFilter
|
|
2448
|
+
- PlacedStructureSnapshot (interface): interface PlacedStructureSnapshot
|
|
2449
|
+
- PlacedStructureStore (interface): interface PlacedStructureStore
|
|
2450
|
+
|
|
2451
|
+
### @jgengine/core/world/placement
|
|
2452
|
+
|
|
2453
|
+
- validatePlacement (function): function validatePlacement(request: PlacementRequest, rules: PlacementRules = {}): PlacementResult
|
|
2454
|
+
- footprintObstacle (function): function footprintObstacle(request: PlacementRequest, id?: string): PlacementObstacle
|
|
2455
|
+
- PlacementObstacle (interface): interface PlacementObstacle
|
|
2456
|
+
- PlacementRequest (interface): interface PlacementRequest
|
|
2457
|
+
- PlacementRules (interface): interface PlacementRules
|
|
2458
|
+
- PlacementResult (type): type PlacementResult = | { status: "ok"; center: Vec2; aabb: Aabb } | { status: "rejected"; reason: "out-of-bounds" } | { status: "rejected"; reason: "overlap"; obstacle: PlacementObstacle; index: number }
|
|
2459
|
+
|
|
2460
|
+
### @jgengine/core/world/placementController
|
|
2461
|
+
|
|
2462
|
+
- quarterTurnsToRotationY (function): function quarterTurnsToRotationY(quarterTurns: number): number
|
|
2463
|
+
- createPlacementController (function): function createPlacementController(config: PlacementControllerConfig): PlacementController
|
|
2464
|
+
- SnapMode (type): type SnapMode = "grid" | "free" | "surface"
|
|
2465
|
+
- PlacementVec3 (type): type PlacementVec3 = readonly [number, number, number]
|
|
2466
|
+
- PlacementHit (interface): interface PlacementHit
|
|
2467
|
+
- PlacementSlot (interface): interface PlacementSlot — A fixed buildable slot the placement controller snaps to instead of grid/free positioning.
|
|
2468
|
+
- PlacementControllerConfig (interface): interface PlacementControllerConfig
|
|
2469
|
+
- PlacementPreview (interface): interface PlacementPreview
|
|
2470
|
+
- PlacementCommit (interface): interface PlacementCommit
|
|
2471
|
+
- PlacementController (interface): interface PlacementController
|
|
2472
|
+
|
|
2473
|
+
### @jgengine/core/world/realm
|
|
2474
|
+
|
|
2475
|
+
- composeRealm (function): function composeRealm(base: RealmBase, cards: readonly RealmCard[]): ComposedRealm — Assemble a played realm instance at runtime from a deck of modifier cards — the Nightingale "realm card" model. A major card is the biome base; minor cards layer weather, day length, and spawn edits. The result recomposes both the environment (into a sampleable field via `environmentField()`) and the spawn table, and it depends on the weather hooks in this group (#92) to turn its `weather` into gameplay modifiers. Cards apply in array order; sort your deck (majors first) before composing.
|
|
2476
|
+
- RealmEnvironmentParams (interface): interface RealmEnvironmentParams — The recomposable environment parameters a realm card can override.
|
|
2477
|
+
- DEFAULT_REALM_ENVIRONMENT (const): const DEFAULT_REALM_ENVIRONMENT: RealmEnvironmentParams
|
|
2478
|
+
- SpawnTableOverride (interface): interface SpawnTableOverride — Spawn-table edits a card layers on: replace weights, add/scale, or remove ids.
|
|
2479
|
+
- RealmCard (interface): interface RealmCard
|
|
2480
|
+
- RealmBase (interface): interface RealmBase
|
|
2481
|
+
- ComposedRealm (interface): interface ComposedRealm
|
|
2482
|
+
|
|
2483
|
+
### @jgengine/core/world/regions
|
|
2484
|
+
|
|
2485
|
+
- createRegionField (function): function createRegionField<T = unknown>(config: RegionFieldConfig<T>): RegionField<T>
|
|
2486
|
+
- isRegionField (function): function isRegionField(field: TerrainField): field is RegionField
|
|
2487
|
+
- Rgb (type): type Rgb = readonly [number, number, number]
|
|
2488
|
+
- ColorInput (type): type ColorInput = string | Rgb
|
|
2489
|
+
- RegionHeight (interface): interface RegionHeight
|
|
2490
|
+
- RegionDef (interface): interface RegionDef<T = unknown> — A content-agnostic region. The engine blends `height` and the visual/physical knobs (`tint`, `water`, `fog`, `speedMultiplier`, …) across region borders and selects the dominant region by nearest center in `selector` space. `data` is an opaque payload the engine never inspects — games hang their own content off it (spawn tables, prop palettes, structure ids, names, …).
|
|
2491
|
+
- RegionSample (interface): interface RegionSample<T = unknown>
|
|
2492
|
+
- RegionField (interface): interface RegionField<T = unknown> extends TerrainField
|
|
2493
|
+
- RegionFieldConfig (interface): interface RegionFieldConfig<T = unknown>
|
|
2494
|
+
|
|
2495
|
+
### @jgengine/core/world/scatter
|
|
2496
|
+
|
|
2497
|
+
- scatterAabb (function): function scatterAabb(area: ScatterArea | Aabb): Aabb
|
|
2498
|
+
- scatter (function): function scatter(config: ScatterConfig): ScatterPoint[]
|
|
2499
|
+
- ScatterArea (interface): interface ScatterArea
|
|
2500
|
+
- ScatterConfig (interface): interface ScatterConfig
|
|
2501
|
+
- ScatterPoint (interface): interface ScatterPoint
|
|
2502
|
+
|
|
2503
|
+
### @jgengine/core/world/scatterItems
|
|
2504
|
+
|
|
2505
|
+
- scatterItems (function): function scatterItems<T>(field: RegionField<T>, area: Aabb, layersFor: (sample: RegionSample<T>) => readonly ScatterLayer[], options: { cell?: number; max?: number; saltKey?: number } = {}): ScatterInstance[] — Deterministically place opaque items across `area`, grounded on a region field. For each grid cell it asks `layersFor` which items may appear in that region and rolls one against their densities. The engine never interprets `item` — a game maps it to a mesh or entity. Content scatter (region-driven density) as opposed to `scatter` in `./scatter`, which is renderer-free geometric point distribution.
|
|
2506
|
+
- pickWeighted (function): function pickWeighted<T>(entries: readonly { value: T; weight: number }[], roll: number): T | null — Weighted pick from opaque entries; `roll` in [0, 1). Returns null when empty.
|
|
2507
|
+
- ScatterLayer (interface): interface ScatterLayer — One placeable class. `item` is an opaque id the caller maps to a mesh/entity.
|
|
2508
|
+
- ScatterInstance (interface): interface ScatterInstance
|
|
2509
|
+
|
|
2510
|
+
### @jgengine/core/world/support
|
|
2511
|
+
|
|
2512
|
+
- solveSupport (function): function solveSupport(pieces: readonly SupportPiece[], links: readonly SupportLink[], config: SupportConfig = {}): SupportResult
|
|
2513
|
+
- toDebrisBodies (function): function toDebrisBodies(pieces: readonly SupportPiece[], collapsedIds: readonly string[], options: DebrisOptions = {}): AddBodyOptions[]
|
|
2514
|
+
- SupportPiece (interface): interface SupportPiece
|
|
2515
|
+
- SupportLink (interface): interface SupportLink
|
|
2516
|
+
- SupportConfig (interface): interface SupportConfig
|
|
2517
|
+
- SupportResult (interface): interface SupportResult
|
|
2518
|
+
- DebrisOptions (interface): interface DebrisOptions
|
|
2519
|
+
|
|
2520
|
+
### @jgengine/core/world/terraform
|
|
2521
|
+
|
|
2522
|
+
- brushWeight (function): function brushWeight(distance: number, radius: number, falloff: TerraformFalloff): number
|
|
2523
|
+
- createEditableTerrain (function): function createEditableTerrain(config: EditableTerrainConfig): EditableTerrain
|
|
2524
|
+
- createTerraformBrush (function): function createTerraformBrush(terrain: Pick<EditableTerrain, "apply">, config: TerraformBrushConfig = {}): TerraformBrush
|
|
2525
|
+
- TerraformMode (type): type TerraformMode = "raise" | "lower" | "flatten" | "paint"
|
|
2526
|
+
- TerraformFalloff (type): type TerraformFalloff = "smooth" | "linear" | "none"
|
|
2527
|
+
- TerraformEdit (interface): interface TerraformEdit
|
|
2528
|
+
- EditableTerrainConfig (interface): interface EditableTerrainConfig
|
|
2529
|
+
- TerraformSnapshot (interface): interface TerraformSnapshot
|
|
2530
|
+
- EditableTerrain (interface): interface EditableTerrain extends TerrainField
|
|
2531
|
+
- TerraformBrushConfig (interface): interface TerraformBrushConfig
|
|
2532
|
+
- TerraformBrush (interface): interface TerraformBrush
|
|
2533
|
+
|
|
2534
|
+
### @jgengine/core/world/terrain
|
|
2535
|
+
|
|
2536
|
+
- valueNoise (function): function valueNoise(x: number, z: number, seed: number): number
|
|
2537
|
+
- fractalNoise (function): function fractalNoise(x: number, z: number, config: FractalNoiseConfig): number
|
|
2538
|
+
- seedFrom (function): function seedFrom(value: string | number | undefined, fallback: number): number
|
|
2539
|
+
- withNormal (function): function withNormal(sampleHeight: (x: number, z: number) => number): TerrainField["sampleNormal"]
|
|
2540
|
+
- flatField (function): function flatField(): TerrainField
|
|
2541
|
+
- noiseField (function): function noiseField(config: NoiseFieldConfig = {}): TerrainField
|
|
2542
|
+
- rollingField (function): function rollingField(config: RollingFieldConfig = {}): TerrainField
|
|
2543
|
+
- arenaField (function): function arenaField(config: ArenaFieldConfig = {}): TerrainField
|
|
2544
|
+
- groundFieldFor (function): function groundFieldFor(world?: WorldFeature): TerrainField
|
|
2545
|
+
- resolveTerrainPalette (function): function resolveTerrainPalette(descriptor: Pick<TerrainEnvironmentConfig, "material" | "colors"> = {}): TerrainPalette
|
|
2546
|
+
- resolveTerrainField (function): function resolveTerrainField(descriptor?: TerrainEnvironmentDescriptor): TerrainField
|
|
2547
|
+
- snapToGround (function): function snapToGround(field: TerrainField, position: readonly [number, number, number], offset = 0): [number, number, number] — Returns `position` with `y` replaced by the field's ground height (plus `offset`) at its `x`/`z`.
|
|
2548
|
+
- snapEntityToGround (function): function snapEntityToGround(entities: GroundSnapEntityStore, id: string, field: TerrainField, offset = 0): boolean — Ground-snaps an already-spawned entity in place; returns false when `id` is unknown.
|
|
2549
|
+
- resolveGroundStep (function): function resolveGroundStep(field: TerrainField, x: number, z: number, stepX: number, stepZ: number, maxSlope = DEFAULT_MAX_WALK_SLOPE): { stepX: number; stepZ: number }
|
|
2550
|
+
- TerrainNormal (type): type TerrainNormal = readonly [number, number, number]
|
|
2551
|
+
- TerrainField (interface): interface TerrainField
|
|
2552
|
+
- FractalNoiseConfig (interface): interface FractalNoiseConfig
|
|
2553
|
+
- FLAT_FIELD (const): const FLAT_FIELD: TerrainField
|
|
2554
|
+
- NoiseFieldConfig (interface): interface NoiseFieldConfig
|
|
2555
|
+
- RollingFieldConfig (interface): interface RollingFieldConfig
|
|
2556
|
+
- ArenaFieldConfig (interface): interface ArenaFieldConfig
|
|
2557
|
+
- ARENA_WATER_LEVEL (const): const ARENA_WATER_LEVEL: -0.9
|
|
2558
|
+
- TerrainPalette (interface): interface TerrainPalette
|
|
2559
|
+
- DEFAULT_TERRAIN_MATERIAL (const): const DEFAULT_TERRAIN_MATERIAL: "grass"
|
|
2560
|
+
- TERRAIN_MATERIAL_PALETTES (const): const TERRAIN_MATERIAL_PALETTES: Record<string, TerrainPalette>
|
|
2561
|
+
- GroundSnapTarget (interface): interface GroundSnapTarget
|
|
2562
|
+
- GroundSnapEntityStore (interface): interface GroundSnapEntityStore
|
|
2563
|
+
- DEFAULT_MAX_WALK_SLOPE (const): const DEFAULT_MAX_WALK_SLOPE: 0.6
|
|
2564
|
+
|
|
2565
|
+
### @jgengine/core/world/voxelField
|
|
2566
|
+
|
|
2567
|
+
- createVoxelField (function): function createVoxelField<T extends string = string>(config?: VoxelFieldConfig): VoxelField<T>
|
|
2568
|
+
- VoxelCoord (type): type VoxelCoord = readonly [number, number, number]
|
|
2569
|
+
- VoxelFace (type): type VoxelFace = "px" | "nx" | "py" | "ny" | "pz" | "nz"
|
|
2570
|
+
- VoxelFieldConfig (interface): interface VoxelFieldConfig
|
|
2571
|
+
- VoxelCell (interface): interface VoxelCell<T extends string = string>
|
|
2572
|
+
- VoxelHit (interface): interface VoxelHit<T extends string = string>
|
|
2573
|
+
- VoxelBounds (interface): interface VoxelBounds
|
|
2574
|
+
- VoxelFieldSummary (interface): interface VoxelFieldSummary<T extends string = string>
|
|
2575
|
+
- VoxelField (interface): interface VoxelField<T extends string = string>
|
|
2576
|
+
- VOXEL_FACES (const): const VOXEL_FACES: readonly VoxelFace[]
|
|
2577
|
+
- VOXEL_FACE_NORMALS (const): const VOXEL_FACE_NORMALS: Record<VoxelFace, VoxelCoord>
|
|
2578
|
+
|
|
2579
|
+
### @jgengine/core/world/walls
|
|
2580
|
+
|
|
2581
|
+
- wallSegments (function): function wallSegments(points: readonly Vec2[], closed: boolean): WallSegment[]
|
|
2582
|
+
- isEnclosed (function): function isEnclosed(points: readonly Vec2[], tolerance = 0.5): boolean
|
|
2583
|
+
- enclosePath (function): function enclosePath(points: readonly Vec2[], tolerance = 0.5): readonly Vec2[]
|
|
2584
|
+
- polygonArea (function): function polygonArea(polygon: readonly Vec2[]): number
|
|
2585
|
+
- footprintFromWalls (function): function footprintFromWalls(points: readonly Vec2[], tolerance = 0.5): EnclosedFootprint | null
|
|
2586
|
+
- autoRoof (function): function autoRoof(footprint: EnclosedFootprint, config: RoofConfig = {}): RoofPlan
|
|
2587
|
+
- createSurfacePaint (function): function createSurfacePaint(): SurfacePaintStore
|
|
2588
|
+
- createWallDrawTool (function): function createWallDrawTool(config: { snap?: number; closeTolerance?: number } = {}): WallDrawTool
|
|
2589
|
+
- wallSegmentBounds (function): function wallSegmentBounds(segment: WallSegment, thickness: number): Aabb
|
|
2590
|
+
- WallVec3 (type): type WallVec3 = readonly [number, number, number]
|
|
2591
|
+
- WallSegment (interface): interface WallSegment
|
|
2592
|
+
- EnclosedFootprint (interface): interface EnclosedFootprint
|
|
2593
|
+
- RoofStyle (type): type RoofStyle = "hip" | "gable" | "flat"
|
|
2594
|
+
- RoofPlan (interface): interface RoofPlan
|
|
2595
|
+
- RoofConfig (interface): interface RoofConfig
|
|
2596
|
+
- PaintTarget (type): type PaintTarget = "floor" | "wall"
|
|
2597
|
+
- SurfacePaintStore (interface): interface SurfacePaintStore
|
|
2598
|
+
- WallDrawTool (interface): interface WallDrawTool
|
|
2599
|
+
|
|
2600
|
+
### @jgengine/core/world/water
|
|
2601
|
+
|
|
2602
|
+
- synthesizeWaves (function): function synthesizeWaves(config: WaterSurfaceConfig = {}): GerstnerWave[]
|
|
2603
|
+
- waterSurface (function): function waterSurface(config: WaterSurfaceConfig = {}): WaterSurface
|
|
2604
|
+
- waterSurfaceFromDescriptor (function): function waterSurfaceFromDescriptor(descriptor: OceanEnvironmentDescriptor, waves?: number): WaterSurface
|
|
2605
|
+
- WaterNormal (type): type WaterNormal = readonly [number, number, number]
|
|
2606
|
+
- GerstnerWave (interface): interface GerstnerWave
|
|
2607
|
+
- WaterSurfaceConfig (interface): interface WaterSurfaceConfig
|
|
2608
|
+
- WaterSurface (interface): interface WaterSurface
|
|
2609
|
+
|
|
2610
|
+
### @jgengine/core/world/weather
|
|
2611
|
+
|
|
2612
|
+
- resolveWeather (function): function resolveWeather(state: WeatherState, table: WeatherModifierTable): ResolvedWeather — Resolve a weather state against a game-owned modifier table into concrete gameplay numbers. Multiplier-style effects (grip, visibility, spread) interpolate from the neutral 1 by intensity; rate-style effects (structureDamage, ignition, chill) scale linearly. Games read `grip`/`visibility` in movement and AI, `structureDamage` on a building tick, and `spread`/`ignition` feed the fire grid.
|
|
2613
|
+
- createFireGrid (function): function createFireGrid(config: FireGridConfig): FireGrid
|
|
2614
|
+
- WeatherKind (type): type WeatherKind = string
|
|
2615
|
+
- WeatherState (interface): interface WeatherState
|
|
2616
|
+
- WeatherModifier (interface): interface WeatherModifier — Per-kind gameplay effect values at full intensity. Intensity interpolates from neutral.
|
|
2617
|
+
- WeatherModifierTable (type): type WeatherModifierTable = Record<WeatherKind, WeatherModifier>
|
|
2618
|
+
- ResolvedWeather (interface): interface ResolvedWeather
|
|
2619
|
+
- FireCellState (type): type FireCellState = "unburnt" | "burning" | "burnt"
|
|
2620
|
+
- FireCell (interface): interface FireCell
|
|
2621
|
+
- FireGridConfig (interface): interface FireGridConfig
|
|
2622
|
+
- FireStepOptions (interface): interface FireStepOptions
|
|
2623
|
+
- FireGrid (interface): interface FireGrid
|
|
2624
|
+
|
|
2625
|
+
### @jgengine/core/world/wind
|
|
2626
|
+
|
|
2627
|
+
- windField (function): function windField(config: WindFieldConfig = {}): WindField
|
|
2628
|
+
- WindVector (type): type WindVector = readonly [number, number]
|
|
2629
|
+
- WindFieldConfig (interface): interface WindFieldConfig
|
|
2630
|
+
- WindField (interface): interface WindField
|
|
2631
|
+
|
|
2632
|
+
## Guides
|
|
2633
|
+
|
|
2634
|
+
# JGengine — API Reference
|
|
2635
|
+
|
|
2636
|
+
The engine ships **verbs and primitives**; your game ships **nouns** (catalogs) and thin handlers. Read this before writing `game.config.ts` or any game content. Companion skills: **`jgengine-newgame`** (master blueprint + phased build to completion) and **`jgengine-verify`** (browserless scene gate) — read them before building. The UI quality bar lives in [`reference/ui-react.md`](reference/ui-react.md); asset sourcing lives in the **Assets** section below.
|
|
2637
|
+
|
|
2638
|
+
The heaviest domains live in on-demand reference modules under [`reference/`](reference/) — load one only when you're building in that domain: [`reference/combat.md`](reference/combat.md) (effects, projectiles, death, feel, abilities), [`reference/world.md`](reference/world.md) (terrain, environment, physics, vehicles, spawn), [`reference/multiplayer.md`](reference/multiplayer.md) (transport, host, persistence, presence), [`reference/ui-react.md`](reference/ui-react.md) (`@jgengine/react` hooks, headless + styled UI kits, the registry install path, and the UI quality bar). Each domain's section below is a one-line pointer to its module.
|
|
2639
|
+
|
|
2640
|
+
## Packages
|
|
2641
|
+
|
|
2642
|
+
All published on npm, source at [github.com/Noisemaker111/jgengine](https://github.com/Noisemaker111/jgengine) (AGPL-3.0):
|
|
2643
|
+
|
|
2644
|
+
| Package | Role | May import |
|
|
2645
|
+
|---------|------|------------|
|
|
2646
|
+
| `@jgengine/core` | Everything below: defineGame, GameContext, scene, combat, game systems, movement, input, world features, runtime/transport contracts | nothing platform-specific — no React, Convex, three.js, browser |
|
|
2647
|
+
| `@jgengine/react` | `GameProvider`, hooks, headless UI primitives | react + core |
|
|
2648
|
+
| `@jgengine/shell` | `GamePlayerShell` — R3F canvas, camera rig library (orbit, first-person, top-down/iso, RTS, over-the-shoulder, lock-on, chase, cinematic + shake channel), input tracking, HUD mounting, `GameUiPreview`, demo game; you supply a `GameRegistry` | react + three + core |
|
|
2649
|
+
| `@jgengine/ws` | Browser-safe `GameBackend` over a pluggable string-pipe transport (WebSocket/socket.io/WebRTC/loopback), protocol codec, HTTP reads, browser-safe authoritative host + router (`host`/`hostRouter`), and P2P (`peer`) | core |
|
|
2650
|
+
| `@jgengine/node` | Node process bindings over `@jgengine/ws`'s host/router: `ws`-package server, socket.io server attach, file persistence (re-exports `createGameHost`/`memoryPersistence` unchanged) | node + ws + core |
|
|
2651
|
+
| `@jgengine/sql` | `HostPersistence` on Postgres (structural pool, no hard `pg` dep) | core |
|
|
2652
|
+
| `@jgengine/convex` | The Convex **adapter** behind the `GameBackend` seam | react + convex + core |
|
|
2653
|
+
|
|
2654
|
+
Import by deep path: `@jgengine/core/<domain>/<file>` (e.g. `@jgengine/core/runtime/gameContext`).
|
|
2655
|
+
|
|
2656
|
+
## Hit a snag? File an issue
|
|
2657
|
+
|
|
2658
|
+
Any hiccup with JGengine — a doc that's wrong, a missing primitive, a rough edge, or a feature/improvement idea — file it fast at [github.com/Noisemaker111/jgengine/issues](https://github.com/Noisemaker111/jgengine/issues). A 30-second issue (what you were building, the glue it forced, the API you wanted) is worth more than a silent workaround — that's the fastest way gaps and doc errors get closed. Don't reverse-engineer around a broken doc in silence; report it.
|
|
2659
|
+
|
|
2660
|
+
## Upgrading? Read the changelog
|
|
2661
|
+
|
|
2662
|
+
All eight packages version in lockstep. When you bump (e.g. `0.6` → `0.7`) to pick up new capabilities, read [`CHANGELOG.md`](https://github.com/Noisemaker111/jgengine/blob/main/CHANGELOG.md) — each release leads with a **Migrate** block listing the concrete steps to move a game onto the new APIs. It ships inside every package too (`node_modules/@jgengine/core/CHANGELOG.md`), and as typed values: `import { VERSION, CHANGELOG } from "@jgengine/core/meta/changelog"` to diff your installed version against the latest programmatically.
|
|
2663
|
+
|
|
2664
|
+
## Concept → Type Reference
|
|
2665
|
+
|
|
2666
|
+
Exact import paths and export names — **do not invent paths**; every row below resolves to a real file under `packages/core/src`. Import the deep path form `@jgengine/core/<path>`.
|
|
2667
|
+
|
|
2668
|
+
| Concept | Import path (`@jgengine/core/…`) | Export(s) |
|
|
2669
|
+
|---------|----------------------------------|-----------|
|
|
2670
|
+
| Game boot | `game/defineGame` | `defineGame`, `GameDefinition`, `GameLoop`, `InventoryDeclaration`, `PhysicsConfig`, `GameServerConfig`, `TimeConfig` |
|
|
2671
|
+
| Simulation clock | `time/simClock` | `createSimClock`, `SimClock`, `TimeConfig`, `ClockSnapshot`, `CalendarTime` |
|
|
2672
|
+
| Runner contract | `game/playableGame` | `PlayableGame`, `GameCameraConfig`, `CameraRigKind`, `TopDownCameraConfig`, `RtsCameraConfig`, `ShoulderCameraConfig`, `LockOnCameraConfig`, `ChaseCameraConfig`, `ObserverCameraConfig`, `CameraShakeConfig`, `CinematicCameraConfig`, `CameraKeyframe`, `EntitySpriteConfig` |
|
|
2673
|
+
| Runtime ctx | `runtime/gameContext` | `createGameContext`, `GameContext`, `GameContextContent`, `GameContextItemEntry`, `GameContextEntityEntry`, `GameContextObjectEntry`, `CatalogEntityRole` |
|
|
2674
|
+
| Behaviour lifecycle | `behaviour/behaviour` | `Behaviour` (`onAwake`→`onEnable`→`onStart`→`onUpdate(dt)`→`onDisable`→`onDestroy`), `BehaviourModule`, `createBehaviourWorld`, `BehaviourWorld`, `JGEngineRegister`, `RegisterField`, `BehaviourModules` — Unity-style lifecycle over an id-keyed node tree (`setActive` cascade, lazy update dispatch); key nodes by entity instance ids. Games augment `JGEngineRegister` via `declare module "@jgengine/core/behaviour/behaviour"` for typed `world.modules`. Three.js binding: `Object3DBehaviour`, `attachObject3D`, `useBehaviourWorld` from `@jgengine/shell/behaviour` |
|
|
2675
|
+
| Reactive keyed store | `store/observableKeyedStore` | `createObservableKeyedStore`, `ObservableKeyedStore` — backs `ctx.game.store` |
|
|
2676
|
+
| Scene instance role | `scene/entityStore` | `EntityRole`, `SceneEntity`, `SpawnOptions`, `EntityPose` |
|
|
2677
|
+
| Object spatial queries | `scene/objectQuery` | `raycastObjects`, `raycastObjectsAll`, `ObjectRaycastInput`, `ObjectRaycastHit` — backs `ctx.scene.object.raycast`/`raycastAll` |
|
|
2678
|
+
| Runtime paint layer | `scene/paintLayer` | `createPaintLayer`, `PaintLayer`, `PaintStroke` — backs `ctx.scene.entity.paint` |
|
|
2679
|
+
| Possession | `scene/possession` | `createPossession`, `Possession`, `PossessionDeps`, `PossessionSwappedEvent` |
|
|
2680
|
+
| Form / shapeshift | `scene/form` | `createForms`, `Forms`, `FormDef`, `FormsDeps`, `FormChangedEvent` |
|
|
2681
|
+
| Multiplayer adapters | `runtime/adapter` | `offline`, `ws`, `convex`, `socketIo`, `p2p`, `lan`, `fly`, `servers`, `MultiplayerTopology`, `ServersPoolConfig` |
|
|
2682
|
+
| Loot | `game/lootTable` | `lootTable`, `LootTableDef`, `LootEntry`, `Drop` |
|
|
2683
|
+
| Dropped-item entity | `game/worldItem` | `WORLD_ITEM_ENTITY_NAME`, `WorldItemRecord`, `WorldItemSpawnInput`, `createWorldItemStore`, `resolveDeathDrops`, `scatterOffset`, `scatterPosition`, `selectNearestWorldItem`, `resolveWorldItemPresentation`, `RarityStyle`, `WorldItemPresentation`, `DEFAULT_RARITY`, `DEFAULT_PICKUP_RADIUS`, `DEFAULT_SCATTER` |
|
|
2684
|
+
| Loot filter | `game/lootFilter` | `lootFilter`, `evaluateLootFilter`, `LootFilterRule`, `LootFilterCondition`, `LootFilterItem`, `LootFilterOverride` |
|
|
2685
|
+
| Loadout | `game/loadout` | `LoadoutDef`, `LoadoutItemEntry`, `Loadouts` |
|
|
2686
|
+
| Cosmetic loadout | `game/cosmetics` | `createCosmetics`, `Cosmetics`, `CosmeticLoadoutDef` |
|
|
2687
|
+
| Quest | `game/quest` | `QuestDef`, `QuestRewards`, `QuestObjective`, `QuestJournal` |
|
|
2688
|
+
| World features | `world/features` | `WorldFeature`, `biomes`, `voxel`, `plots`, `tilemap`, `flat`, `environment`, `terrain`, `rain`, `snow`, `grass`, `ocean`, `building` |
|
|
2689
|
+
| Voxel field | `world/voxelField` | `createVoxelField`, `VoxelField`, `VoxelCell`, `VoxelHit`, `VoxelBounds`, `VoxelFieldSummary`, `VoxelFace`, `VOXEL_FACES`, `VOXEL_FACE_NORMALS` — a chunked block lattice, distinct from the `voxel()` `WorldFeature` descriptor |
|
|
2690
|
+
| Terrain field | `world/terrain` | `TerrainField`, `noiseField`, `resolveTerrainField`, `rollingField`, `fractalNoise`, `valueNoise`, `withNormal`, `arenaField`, `flatField`, `resolveGroundStep`, `snapToGround`, `snapEntityToGround`, `resolveTerrainPalette`, `TERRAIN_MATERIAL_PALETTES` |
|
|
2691
|
+
| Seeded RNG | `random/rng` | `seededRng`, `seededStreams` |
|
|
2692
|
+
| Name generator | `random/nameGen` | `createNameGenerator`, `pickFrom`, `fillTemplate`, `NameGenerator`, `NameGeneratorOptions`, `SyllableBank` |
|
|
2693
|
+
| Regions | `world/regions` | `createRegionField`, `isRegionField`, `RegionDef`, `RegionField`, `RegionSample` |
|
|
2694
|
+
| Wind field | `world/wind` | `windField`, `WindField`, `WindFieldConfig`, `WindVector` |
|
|
2695
|
+
| Water surface | `world/water` | `waterSurface`, `waterSurfaceFromDescriptor`, `synthesizeWaves`, `WaterSurface`, `GerstnerWave` |
|
|
2696
|
+
| Scatter | `world/scatter` | `scatter`, `scatterAabb`, `ScatterConfig`, `ScatterPoint` |
|
|
2697
|
+
| Content scatter | `world/scatterItems` | `scatterItems`, `pickWeighted`, `ScatterLayer`, `ScatterInstance` |
|
|
2698
|
+
| Building generator | `world/buildings` | `generateBuilding`, `generateBuildingDistrict`, `createBuildingGrid`, `GeneratedBuilding` |
|
|
2699
|
+
| Building index | `world/buildingIndex` | `buildingIndex`, `BuildingIndex`, `BuildingHit` |
|
|
2700
|
+
| Scene summary | `world/environmentSummary` | `summarizeEnvironment`, `resolveStructureBuildings`, `EnvironmentSummary` |
|
|
2701
|
+
| Map markers | `world/markers` | `createMarkerSet`, `MarkerSet`, `MapMarker`, `MarkerInput`, `MarkerKindStyle`, `DEFAULT_MARKER_KINDS`, `markerKindStyle` |
|
|
2702
|
+
| Fog of war | `world/fog` | `createFogField`, `FogField`, `FogConfig`, `FogBounds`, `FogCells` |
|
|
2703
|
+
| Minimap math | `world/minimap` | `projectToMinimap`, `clampToMinimapEdge`, `compassBearing`, `headingToBearing`, `bearingToCardinal`, `relativeBearing`, `MinimapView` |
|
|
2704
|
+
| Ping | `game/ping` | `createPingSystem`, `classifyPing`, `PingSystem`, `PingPayload`, `PingCategory`, `PingCategoryDef`, `DEFAULT_PING_CATEGORIES`, `PING_FEED_ACTION` |
|
|
2705
|
+
| Proximity prompt | `interaction/proximityPrompt` | `proximityPrompt`, `ProximityPrompt`, `ProximityPromptDisplay`, `keybind`, `gauge`, `label`, `command` |
|
|
2706
|
+
| Skill-check minigame | `interaction/skillCheck` | `evaluateSkillCheck`, `skillCheckMarkerPosition`, `skillCheckZoneAt`, `SkillCheckConfig`, `SkillCheckZone`, `SkillCheckResult` |
|
|
2707
|
+
| QTE sequencer | `interaction/qte` | `evaluateQteSequence`, `pendingQteStep`, `qteProgress`, `QteStep`, `QteInputEvent`, `QteOutcome` |
|
|
2708
|
+
| Item use | `item/use` | `createItemUse`, `ItemUseHandler`, `ItemUseInput`, `ItemUseResult`, `ItemUseRejection` |
|
|
2709
|
+
| Durability | `item/durability` | `createDurability`, `wear`, `repairQuote`, `isDisabled`, `createDurabilityTracker`, `DurabilitySpec`, `DurabilityState`, `RepairSpec`, `RepairQuote` |
|
|
2710
|
+
| Affix roller | `item/affix` | `createAffixRoller`, `seededRng`, `AffixRoller`, `RollerConfig`, `AffixPool`, `AffixDef`, `RarityTier`, `ItemBaseDef`, `RolledItem`, `RolledAffix` |
|
|
2711
|
+
| Modular item | `item/modularItem` | `createModularItem`, `install`, `computeEffectiveStats`, `missingRequiredSlots`, `ModularItemDef`, `MountSlotDef`, `PartDef`, `InstalledPart` |
|
|
2712
|
+
| Storage tier | `inventory/storageTier` | `partitionOnDeath`, `createDeliveryQueue`, `insureLost`, `resolveConsolation`, `tierOf`, `StorageTier`, `ContainerSnapshot`, `DeathPartition`, `DeliveryQueue`, `InsurancePolicy`, `ConsolationPolicy` |
|
|
2713
|
+
| Contested channel | `session/contestedChannel` | `createContestedChannel`, `ContestedChannel`, `ContestedChannelConfig`, `ContestedEvent`, `ContestedPhase`, `ContestedSnapshot` |
|
|
2714
|
+
| Round state | `session/roundState` | `createRoundState`, `lossBonusFor`, `RoundState`, `RoundConfig`, `RoundPhase`, `RoundTeam`, `RoundEvent`, `RoundEconomy`, `RoundSnapshot`, `LossBonusRule` |
|
|
2715
|
+
| Shrinking ring | `session/ring` | `createRing`, `ringSampleAt`, `Ring`, `RingConfig`, `RingPhase`, `RingSample`, `RingHit`, `RingPoint` |
|
|
2716
|
+
| Extraction session | `session/extraction` | `createRaidSession`, `RaidSession`, `RaidSessionConfig`, `ExtractPoint`, `ExtractionResult`, `DeathResult`, `RaidStatus` |
|
|
2717
|
+
| Role assignment | `session/roles` | `assignRoles`, `RoleSpec` |
|
|
2718
|
+
| Downed / revive | `combat/downed` | `createDownedState`, `DownedState`, `DownedConfig`, `DownedPhase`, `DownedEntry`, `DownedEvent` |
|
|
2719
|
+
| Persistence scopes | `runtime/persistenceScope` | `partitionScopes`, `resetRun`, `mergeScopes`, `clearRunFields`, `applyRunReset`, `planScenarioReset`, `ScopeSchema`, `ScenarioReset`, `PersistenceScope` |
|
|
2720
|
+
| Inventory | `inventory/inventoryModel` | `InventoryLayout`, `InventorySet`, `ItemTraits` |
|
|
2721
|
+
| Progression | `game/progression` | `curve`, `evalCurve`, `leveling`, `Curve`, `LevelingTrack`, `LevelProgress` |
|
|
2722
|
+
| Inventory slots | `inventory/slotModel` | `createSlots`, `placeAt`, `removeAt`, `moveSlot`, `firstEmpty`, `compactSlots`, `Slot`, `SlotGrid` |
|
|
2723
|
+
| Shaped inventory | `inventory/shapedGrid` | `createShapedGrid`, `placeShaped`, `moveShaped`, `removeShaped`, `canPlace`, `rotateFootprint`, `occupiedCells`, `gridAdjacencyQuery`, `cellFromPoint`, `ShapedGrid`, `Footprint`, `Placement`, `Rotation` |
|
|
2724
|
+
| Card piles | `cards/cardPile` | `createCardPile`, `createCardPileState`, `draw`, `moveCards`, `shuffleZone`, `pileRng`, `CardPile`, `CardPileState`, `CardPileConfig` |
|
|
2725
|
+
| Modifier pipeline | `cards/modifierPipeline` | `createModifierPipeline`, `runPipeline`, `Modifier`, `TraceStep`, `PipelineResult` |
|
|
2726
|
+
| Lane board | `board/laneBoard` | `createLaneBoard`, `laneAggregate`, `laneOutcome`, `boardTotals`, `lanesWon`, `LaneBoard`, `LaneRule`, `LaneBoardConfig` |
|
|
2727
|
+
| Timeline board | `board/timelineBoard` | `createTimelineBoard`, `tickTimeline`, `TimelineBoard`, `TimelineSlot`, `TimelineFire` |
|
|
2728
|
+
| World geometry | `world/geometry` | `footprintAabb`, `aabbOverlap`, `snapToGrid`, `resolveMove`, `Aabb`, `Footprint` |
|
|
2729
|
+
| Placement | `world/placement` | `validatePlacement`, `footprintObstacle`, `PlacementRules`, `PlacementResult` |
|
|
2730
|
+
| Placement ghost | `world/placementController` | `createPlacementController`, `PlacementController`, `PlacementPreview`, `PlacementCommit`, `SnapMode`, `quarterTurnsToRotationY` |
|
|
2731
|
+
| Connector sockets | `world/connectors` | `snapToNearest`, `socketsCompatible`, `worldSockets`, `socketWorldPosition`, `ConnectorSocket`, `ConnectorPieceDef`, `PlacedPiece`, `SnapResult` |
|
|
2732
|
+
| Structural support | `world/support` | `solveSupport`, `toDebrisBodies`, `SupportPiece`, `SupportLink`, `SupportResult` |
|
|
2733
|
+
| Wall/roof authoring | `world/walls` | `createWallDrawTool`, `footprintFromWalls`, `autoRoof`, `wallSegments`, `createSurfacePaint`, `WallDrawTool`, `RoofPlan`, `EnclosedFootprint` |
|
|
2734
|
+
| Placed structures | `world/placedStructureStore` | `createPlacedStructureStore`, `PlacedStructure`, `PlacedStructureStore`, `PlacedStructureSnapshot` |
|
|
2735
|
+
| Terraform | `world/terraform` | `createEditableTerrain`, `createTerraformBrush`, `brushWeight`, `EditableTerrain`, `TerraformBrush`, `TerraformEdit`, `TerraformMode` |
|
|
2736
|
+
| Build permissions | `world/buildPermissions` | `createPlotPermissions`, `createContributionPool`, `PlotPermissions`, `ContributionPool`, `BuildRole`, `ContributionGoal` |
|
|
2737
|
+
| Interiors | `world/interiors` | `createInteriors`, `Interior`, `Exterior`, `SpaceRef` |
|
|
2738
|
+
| Game clock | `time/gameClock` | `getScaledElapsedMs`, `computeGameDay`, `SECONDS_PER_GAME_DAY` |
|
|
2739
|
+
| Scene behaviors | `scene/behaviors` | `wander`, `patrol`, `promptable`, `talkable`, `player` |
|
|
2740
|
+
| Capture check | `scene/captureCheck` | `captureChance`, `rollCapture`, `CaptureCheckInput` |
|
|
2741
|
+
| Owned roster | `scene/roster` | `createRoster`, `Roster`, `RosterEntry`, `RosterCaptureOptions` |
|
|
2742
|
+
| Economy wallet | `economy/wallet` | `createEmptyWallet`, `balance`, `grant`, `charge`, `canAfford`, `chargeAll` |
|
|
2743
|
+
| Tech tree | `economy/techTree` | `createTechTree`, `TechTree`, `TechNodeDef`, `canUnlockTech`, `availableTech`, `unlockedRecipes`, `grantTech`, `techPrerequisitesMet` |
|
|
2744
|
+
| Recipe graph | `crafting/recipe` | `createRecipeGraph`, `RecipeGraph`, `RecipeDef`, `RecipeItem`, `canCraft`, `craft`, `missingInputs`, `stationSatisfied`, `craftSeconds` |
|
|
2745
|
+
| Production building | `crafting/production` | `productionBuilding`, `ProductionBuildingDef`, `createProductionState`, `tickProduction`, `feedProduction`, `drainOutput`, `advanceTransport`, `resolvePowerGrid` |
|
|
2746
|
+
| Crop tile / farming | `crafting/crop` | `createCropField`, `CropField`, `CropDef`, `CropTileState`, `tillTile`, `plantCrop`, `waterTile`, `advanceCropDay`, `harvestCrop`, `applyToolToTiles`, `squarePattern`, `diamondPattern`, `createDayTicker` |
|
|
2747
|
+
| Skill-check roll | `stats/rollCheck` | `rollCheck`, `CheckInput`, `CheckResult`, `CheckAdvantage` |
|
|
2748
|
+
| Input bindings (full) | `input/actionBindings` | `hotbarSlotBindings`, `actionLabel`, `bindingLabel`, `resolveActionCommand`, `bindingMatches`, `createActionStateTracker` |
|
|
2749
|
+
| Touch controls | `input/touchScheme` | `deriveTouchScheme`, `touchCode`, `touchActionLabel`, `withTouchCodes`, `TouchControlsConfig`, `TouchGestureBindings`, `TouchDragBinding`, `TouchButtonSpec`, `TouchScheme`, `TouchJoystick`, `TouchButton` |
|
|
2750
|
+
| Pointer hit | `input/pointer` | `PointerHit`, `PointerButton`, `aimToPoint`, `moveTargetFromHit`, `groundOf`, `PointerVec3` |
|
|
2751
|
+
| Navmesh + A* | `nav/navGrid` | `createNavGrid`, `findPath`, `smoothPath`, `NavGrid`, `NavGridConfig`, `NavPoint`, `FindPathOptions` |
|
|
2752
|
+
| Path follow | `nav/pathFollow` | `createPathFollow`, `advancePathFollow`, `pathFromNav`, `PathFollowConfig`, `PathFollowState`, `Waypoint` |
|
|
2753
|
+
| Nav-grid movement constraint | `nav/navConstrain` | `constrainToNavGrid`, `NavConstrainProposed`, `NavConstrainEntity`, `NavConstrainOptions` — a standalone walkable-pass-through + wall-slide helper; adapt its `(proposed, entity)` shape to `PlayerMovementConfig.beforeCommit`'s `(frame) => [x,y,z]` with a small closure |
|
|
2754
|
+
| Selection set | `scene/selection` | `createSelectionSet`, `SelectionSet`, `screenRect`, `selectWithinRect`, `rectContainsPoint`, `isMarquee`, `ScreenRect` |
|
|
2755
|
+
| Context menu | `interaction/contextMenu` | `contextVerb`, `buildContextMenu`, `contextVerbInput`, `ContextVerb`, `ContextMenu` |
|
|
2756
|
+
| Shared / group wallet | `economy/sharedWallet` | `createWalletBook`, `WalletBook`, `WalletScope`, `userScope`, `groupScope`, `balanceIn`, `grantTo`, `chargeFrom`, `contributionOf`, `contributorsOf` |
|
|
2757
|
+
| Analog axis input | `input/axisInput` | `AxisInput`, `AxisChannel`, `AxisBindingMap`, `DRIVE_AXIS_BINDINGS`, `clampAxis`, `rampToward`, `NEUTRAL_AXIS` |
|
|
2758
|
+
| Raw control polling | `runtime/inputSnapshot` | `createInputSnapshot`, `InputSnapshot` — backs `ctx.input` |
|
|
2759
|
+
| Physics world | `physics/physicsWorld` | `PhysicsWorld`, `PhysicsWorldConfig`, `PhysicsBounds`, `PhysicsStats`, `AddBodyOptions` (`{ shape: "box", halfExtents }` \| `{ shape: "sphere", radius }`), `JointOptions`, `JointKind`, `CollisionEvent` |
|
|
2760
|
+
| Ballistic collision sweep | `physics/ballisticSweep` | `createBallisticSweep`, `BallisticSweep`, `BallisticSweepHit`, `BallisticSweepOptions` |
|
|
2761
|
+
| Tweening / easing | `anim/easing` | `Easing`, `lerp`, `clamp01`, `smoothstep`, `easeInQuad`, `easeOutQuad`, `easeInOutQuad`, `easeInCubic`, `easeOutCubic`, `easeInOutCubic`, `easeOutBack`, `easeOutElastic`, `tween`, `timedProgress` |
|
|
2762
|
+
| Async data source | `data/dataSource` | `createDataSource`, `DataSource`, `DataSourceState`, `DataSourceStatus`, `DataSourceOptions`, `DataSourceClock`, `RefreshOptions` |
|
|
2763
|
+
| JSON fetch | `data/fetchJson` | `fetchJson`, `FetchJsonOptions`, `FetchImpl`, `HttpStatusError`, `JsonParseError` |
|
|
2764
|
+
| JSON data source | `data/jsonDataSource` | `createJsonDataSource`, `JsonDataSourceOptions` |
|
|
2765
|
+
| Dev proxy routing | `data/devProxy` | `proxiedUrl`, `parseDevProxyTable`, `DevProxyTable`, `ProxiedUrlOptions`, `DEFAULT_DEV_PROXY_PREFIX` |
|
|
2766
|
+
| Grid-cell world rendering | `world/gridInstances` | `resolveGridInstances`, `GridInstanceTransform` |
|
|
2767
|
+
| Turn loop | `turn/turnLoop` | `createTurnLoop`, `TurnLoop`, `TurnLoopConfig`, `TurnState`, `PoolConfig`, `PoolState`, `TurnLoopSnapshot` |
|
|
2768
|
+
| Declared-action intent board | `turn/intent` | `createIntentBoard`, `IntentBoard`, `DeclaredIntent` — `declare(participantId, intent)`, `peek`, `all`, `consume`, `clear` |
|
|
2769
|
+
| Commit modes | `turn/commit` | `createCommitController`, `CommitController`, `CommitMode`, `CommitOutcome`, `SubmittedAction` |
|
|
2770
|
+
| Intent board | `turn/intent` | `createIntentBoard`, `IntentBoard`, `DeclaredIntent` |
|
|
2771
|
+
| Tactical grid | `tactics/tacticalGrid` | `createTacticalGrid`, `TacticalGrid`, `TacticalGridConfig`, `Tile`, `ReachableTile`, `PushResult`, `PushCollision` |
|
|
2772
|
+
| Predictive query | `tactics/predictiveQuery` | `predictAreaEffect`, `predictArcEffect`, `predictTiles`, `PredictiveDeps`, `PredictedTarget` |
|
|
2773
|
+
| Sim snapshot | `tactics/snapshot` | `createSnapshotStore`, `SnapshotStore`, `SnapshotSlice`, `Snapshot`, `deepClone` |
|
|
2774
|
+
| Surfaces | `tactics/surface` | `createSurfaceLayer`, `SurfaceLayer`, `SurfaceLayerConfig`, `SurfaceKindDef`, `SurfaceReaction`, `SurfaceEvent` |
|
|
2775
|
+
| Area targeting | `combat/effects` | `resolveAreaTargets`, `AreaTarget`, `AreaTargetInput` (shared AoE targeting behind `effect` + the predictive query) |
|
|
2776
|
+
| Environment field | `world/envField` | `createEnvironmentField`, `EnvironmentField`, `EnvironmentSample`, `EnvironmentFieldConfig`, `OccluderRect`, `HeatSource` |
|
|
2777
|
+
| Weather + fire | `world/weather` | `resolveWeather`, `WeatherState`, `WeatherModifier`, `WeatherModifierTable`, `ResolvedWeather`, `createFireGrid`, `FireGrid`, `FireCell`, `FireGridConfig` |
|
|
2778
|
+
| Realm composition | `world/realm` | `composeRealm`, `RealmCard`, `RealmBase`, `ComposedRealm`, `RealmEnvironmentParams`, `SpawnTableOverride` |
|
|
2779
|
+
| Decay meters | `survival/decayMeter` | `createDecayMeterSet`, `DecayMeterSet`, `DecayMeterConfig`, `MeterThreshold`, `DecayMeterState` |
|
|
2780
|
+
| Status moodles | `survival/moodle` | `createMoodleStack`, `stackMoodles`, `MoodleStack`, `Moodle`, `MoodleSeverity`, `TimedMoodleInput` |
|
|
2781
|
+
| Multi-region health | `survival/regionHealth` | `createMultiRegionHealth`, `MultiRegionHealth`, `HealthRegionConfig`, `AilmentConfig`, `RegionHealthState`, `AilmentInstance` |
|
|
2782
|
+
| Audio contract | `audio/audioFalloff` | `computeFalloffGain`, `resolveEmitterGain`, `distance3`, `AudioFalloffConfig`, `FalloffCurve`, `SoundDef`, `AudioBusDef`, `AudioBusId` |
|
|
2783
|
+
| Beat clock | `time/beatClock` | `createBeatClock`, `createBeatInputBuffer`, `nextBeatTime`, `BeatClock`, `BeatClockConfig`, `BeatSnapshot`, `BeatInputBuffer`, `BufferedAction` |
|
|
2784
|
+
| Spawn director | `ai/spawnDirector` | `createSpawnDirectorState`, `advanceSpawnDirector`, `advanceWave`, `raiseAlert`, `pickSpawnPoint`, `SpawnDirectorConfig`, `WaveManifest`, `SpawnEntry`, `SpawnRequest`, `DirectorContext` |
|
|
2785
|
+
| Threat table | `ai/threat` | `createThreatTable`, `ThreatTable`, `ThreatTableConfig`, `ThreatEntry`, `HighestThreatOptions` |
|
|
2786
|
+
| Job board | `ai/jobBoard` | `createJobBoard`, `JobBoard`, `JobDef`, `Job`, `JobPhase`, `WorkerState`, `JobReport`, `JobTickContext` |
|
|
2787
|
+
| Crowd flow | `ai/crowd` | `computeFlowField`, `createCrowdField`, `selectPoi`, `FlowField`, `FlowFieldOptions`, `CrowdField`, `Poi`, `SelectPoiOptions` |
|
|
2788
|
+
| Physics actors | `physics/ragdoll`, `physics/carryable`, `physics/forceVolume`, `physics/spatialGrid` | `createRagdoll`, `Ragdoll`, `Carryable`, `carrySpeedMultiplier`, `ForceVolume`, `PlatformCarry`, `SpatialGrid` |
|
|
2789
|
+
| Traversal (grapple/glide) | `physics/traversal` | `Grapple`, `GrappleConfig`, `Glide`, `GlideConfig` |
|
|
2790
|
+
| Structural destruction | `physics/structure` | `StructureGraph`, `StructureNodeSpec`, `StructureEdgeSpec`, `StructureMaterial`, `StructureMaterialTable`, `CollapseEvent`, `DebrisConfig` |
|
|
2791
|
+
| Destructible terrain | `world/carve` | `VoxelVolume`, `VoxelMaterial`, `VoxelMaterialTable`, `CarvableField`, `carvableTerrain`, `CarveOp`, `DepositOp`, `CraterOp`, `MoundOp`, `EMPTY_VOXEL` |
|
|
2792
|
+
| Vehicle body | `physics/vehicleBody` | `createVehicleBody`, `VehicleBody`, `VehicleBodyConfig`, `WheelSpec`, `GripCurve`, `sampleGripCurve`, `DEFAULT_GRIP_CURVE` |
|
|
2793
|
+
| Buoyant boat | `physics/buoyancy` | `createBuoyantBody`, `BuoyantBody`, `BuoyantBodyConfig` |
|
|
2794
|
+
| Crash damage | `physics/damageZones` | `createDamageModel`, `DamageModel`, `DamageZoneDef`, `DamageTransition` |
|
|
2795
|
+
| Mounts / rideables | `scene/mount` | `createMountController`, `MountController`, `MountKit`, `MountSeat`, `RideableConfig` |
|
|
2796
|
+
| Shared-vehicle stations | `scene/stationClaim` | `createStationClaim`, `StationClaim`, `Station`, `SharedVehicleConfig`, `ClaimResult` |
|
|
2797
|
+
| Lag compensation | `multiplayer/lagCompensation` | `createPositionHistory`, `PositionHistory`, `rewindTimestamp`, `resolveHitscan`, `raySphereDistance`, `HitscanRay`, `HitscanTarget` |
|
|
2798
|
+
| Simultaneous commit | `multiplayer/simultaneousCommit` | `createCommitRound`, `CommitRound`, `SealedCommit`, `resolveCommits` |
|
|
2799
|
+
| Combat-snapshot replay | `multiplayer/combatSnapshot` | `serializeBoard`, `cloneSnapshot`, `replayCombat`, `BoardSnapshot`, `SnapshotUnit`, `CombatRules`, `ReplayResult` |
|
|
2800
|
+
| Session matchmaking | `multiplayer/matchmaking` | `browseSessions`, `findByJoinCode`, `quickMatch`, `matchesFilter`, `normalizeJoinCode`, `generateJoinCode`, `SessionListing`, `MatchFilter` |
|
|
2801
|
+
| Auth identity | `multiplayer/identity` | `AuthSession`, `PlayerIdentity`, `sessionPlayer`, `resolveGuestSession` |
|
|
2802
|
+
| Text chat | `game/chat`, `multiplayer/chatContract` | `createChat`, `Chat`, `ChatMessage`, `ChatChannelDef`, `whisperChannelId`, `createChatRateLimiter`, `ChatTransport`, `ChatSync`, `createLocalChatTransport` |
|
|
2803
|
+
| Voice seam | `multiplayer/voiceContract` | `VoiceTransport`, `VoiceParticipant`, `VoiceRoute`, `createLocalVoiceTransport`, `createPushToTalk`, `PushToTalkMode` |
|
|
2804
|
+
| Race state | `game/race` | `raceTrack`, `RaceTrack`, `createRaceState`, `RaceState`, `RaceEvent`, `RaceWinCondition`, `firstPastPost`, `topK`, `lastStanding`, `everyoneFinishes` |
|
|
2805
|
+
| Reveal query | `sensor/revealQuery` | `createRevealQuery`, `RevealQuery`, `RevealQueryOptions`, `RevealHit` |
|
|
2806
|
+
| Hidden-state probe | `sensor/hiddenStateProbe` | `probeHiddenState`, `probeHiddenStateAll`, `HiddenStateSource`, `HiddenStateValue`, `SensorProbeOptions`, `SensorReading` |
|
|
2807
|
+
| View-frustum sensor | `sensor/frustumSensor` | `createFrustumSensor`, `projectToView`, `framingScore`, `FrustumCamera`, `FrustumTarget`, `FrustumProjection`, `FrustumSample`, `FrustumSensor`, `FramingConfig` |
|
|
2808
|
+
| Recording buffer | `sensor/recordingBuffer` | `createRecordingBuffer`, `RecordingBuffer`, `RecordingFrame`, `RecordingBufferOptions` |
|
|
2809
|
+
| Concealment scoring | `sensor/concealment` | `colorDistance`, `concealmentScore`, `createConcealmentSensor`, `ColorHex`, `ConcealmentTarget`, `ConcealmentSample`, `ConcealmentSensor` |
|
|
2810
|
+
| Freeze violation monitor | `sensor/freezeMonitor` | `createFreezeMonitor`, `FreezeMonitor`, `FreezeSubject`, `FreezeViolation` |
|
|
2811
|
+
| Animation SM | `combat/animationState` | `createAnimationState`, `AnimationState`, `AnimationClip`, `FramePhase`, `FrameRange`, `phasesAtFrame`, `activeRangeAtFrame`, `frameAtMs` |
|
|
2812
|
+
| Accumulator meter | `stats/accumulatorMeter` | `createAccumulatorMeter`, `AccumulatorMeter`, `AccumulatorMeterConfig`, `MeterTier`, `MeterAddResult`, `tierAt` |
|
|
2813
|
+
| Stagger / buildup | `combat/breakMeters` | `createStaggerMeter`, `createBuildupMeter`, `StaggerMeter`, `BuildupMeter`, `BuildupProc` |
|
|
2814
|
+
| Attack tags | `combat/attackTags` | `attackMeta`, `AttackTag`, `AttackMeta`, `hasTag`, `isBlockable`, `isParryable`, `isDodgeable`, `counters` |
|
|
2815
|
+
| Defensive window | `combat/defensiveWindow` | `createDefensiveWindow`, `resolveDefense`, `DefensiveWindowConfig`, `DefenseKind`, `DefenseOutcome`, `windowActiveAt`, `iframeActiveAt` |
|
|
2816
|
+
| Combo string | `combat/comboString` | `createComboRunner`, `advanceCombo`, `ComboString`, `ComboStep`, `AdvanceComboResult` |
|
|
2817
|
+
| Hit reaction | `combat/hitReaction` | `resolveHitReaction`, `HitReaction`, `HitReactionConfig`, `CameraShake`, `applyImpulse` |
|
|
2818
|
+
| Telegraph | `combat/telegraph` | `pointInTelegraph`, `telegraphProgress`, `telegraphFired`, `telegraphTurnProgress`, `telegraphFiredAtTurn`, `telegraphTurnsRemaining`, `TelegraphShape`, `TelegraphConfig` |
|
|
2819
|
+
| Dash / dodge | `movement/dash` | `createDashState`, `DashState`, `DashConfig`, `DashBurst`, `iframeActive`, `dashOffset` |
|
|
2820
|
+
| Ability kit | `combat/abilityKit` | `createAbilityKit`, `AbilityKit`, `AbilitySlotConfig`, `AbilitySlotSnapshot`, `AbilitySlotState`, `AbilityCastType`, `AbilityCastResult`, `AbilitySlotRetune` |
|
|
2821
|
+
| Event meter | `stats/eventMeter` | `createEventMeter`, `EventMeter`, `EventMeterConfig`, `EventMeterFeedResult` |
|
|
2822
|
+
| Auto-target policy | `scene/autoTarget` | `selectAutoTarget`, `createAutoTargeter`, `AutoTargetPolicy`, `AutoTargeter`, `AutoTargetDeps` |
|
|
2823
|
+
| Resistance matrix | `combat/resistance` | `resolveResistance`, `resistanceScale`, `ResistanceMatrix`, `ResistVerdict`, `ResistanceResult` |
|
|
2824
|
+
| Run draft | `game/runDraft` | `createRunDraft`, `createRunModifierStack`, `RunDraft`, `RunModifierStack`, `RunModifierOffer` |
|
|
2825
|
+
| Uniform-cell grid | `puzzle/cellGrid` | `CellGrid`, `CellRun`, `createCellGrid`, `cellAt`, `inGridBounds`, `withCell`, `withCells`, `fullRows`, `clearRows`, `collapseColumns`, `findRuns` |
|
|
2826
|
+
| Falling piece | `puzzle/fallingPiece` | `FallingPiece`, `ShapeTable`, `LockDelayState`, `pieceCells`, `pieceCollides`, `mergePiece`, `dropDistance`, `gravityInterval`, `levelForLines`, `lineScore`, `createLockDelay`, `stepLockDelay` |
|
|
2827
|
+
| Falling tile grid | `tactics/fallingGrid` | `createFallingGrid`, `FallingGrid`, `FallingGridConfig`, `FallingGridCell`, `FallingGridSnapshot`, `LockState`, `gravityIntervalMs`, `GravityIntervalConfig` — a generic tile-drop grid (any `TCell` payload), distinct from `puzzle/cellGrid`+`puzzle/fallingPiece`'s row-clear/shape-table pair |
|
|
2828
|
+
| Spawn/respawn points | `game/spawnPoints` | `createSpawnPoints`, `SpawnPoints`, `SpawnPointPose`, `RespawnTarget` |
|
|
2829
|
+
| Level sequence | `game/levelSequence` | `createLevelSequence`, `LevelSequence`, `LevelSequenceConfig`, `LevelDescriptor`, `CurrentLevel`, `LevelSequenceStatus`, `LevelSequenceProgress` |
|
|
2830
|
+
| Devtools overlay + tunables | `devtools/devtools` | `devtools`, `createDevtools`, `tunable`, `snapshotDevtools`, `instrumentLatency`, `Tunable`, `TunableOptions`, `TunableAccessor`, `DevtoolsControl`, `DiscoveredEntry`, `DevtoolsOverrides`, `DevtoolsSnapshot` |
|
|
2831
|
+
| Tunable auto-discovery | `devtools/transformTunables` | `transformTunableExports`, `tunableModuleTable`, `tunableDiscoveryPlugin`, `TunableTransformResult` |
|
|
2832
|
+
|
|
2833
|
+
## Getting started (new project)
|
|
2834
|
+
|
|
2835
|
+
```sh
|
|
2836
|
+
bun add @jgengine/core @jgengine/react @jgengine/shell react react-dom three three-stdlib @react-three/fiber @react-three/drei
|
|
2837
|
+
bun add -d @tailwindcss/vite tailwindcss # HUD styling (Vite + Tailwind v4)
|
|
2838
|
+
```
|
|
2839
|
+
|
|
2840
|
+
A single game's standalone entry mounts `GameHost` (`@jgengine/shell/GameHost`) over the `game` your `game.config.ts` exports. The full standalone harness is four small files plus a script — this is exactly the shape every `Games/*` game ships, so `bun dev` plays it on its own with no host app:
|
|
2841
|
+
|
|
2842
|
+
```html
|
|
2843
|
+
<!-- index.html -->
|
|
2844
|
+
<!doctype html>
|
|
2845
|
+
<html lang="en">
|
|
2846
|
+
<head>
|
|
2847
|
+
<meta charset="UTF-8" />
|
|
2848
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
2849
|
+
<title>My Game</title>
|
|
2850
|
+
</head>
|
|
2851
|
+
<body>
|
|
2852
|
+
<div id="root"></div>
|
|
2853
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
2854
|
+
</body>
|
|
2855
|
+
</html>
|
|
2856
|
+
```
|
|
2857
|
+
|
|
2858
|
+
```ts
|
|
2859
|
+
// vite.config.ts — monorepo-aware: the alias branch only fires when this folder
|
|
2860
|
+
// sits inside the engine repo checkout; copied anywhere else, @jgengine/* resolves
|
|
2861
|
+
// from npm dist and the alias list is empty
|
|
2862
|
+
import { existsSync } from "node:fs";
|
|
2863
|
+
import { fileURLToPath } from "node:url";
|
|
2864
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
2865
|
+
import react from "@vitejs/plugin-react";
|
|
2866
|
+
import { defineConfig } from "vite";
|
|
2867
|
+
|
|
2868
|
+
const engineSrc = (pkg: string) => fileURLToPath(new URL(`../../packages/${pkg}/src`, import.meta.url));
|
|
2869
|
+
|
|
2870
|
+
export default defineConfig({
|
|
2871
|
+
plugins: [react(), tailwindcss()],
|
|
2872
|
+
resolve: {
|
|
2873
|
+
alias: existsSync(engineSrc("core"))
|
|
2874
|
+
? [
|
|
2875
|
+
{ find: /^@jgengine\/core\/(.*)$/, replacement: `${engineSrc("core")}/$1` },
|
|
2876
|
+
{ find: /^@jgengine\/react\/(.*)$/, replacement: `${engineSrc("react")}/$1` },
|
|
2877
|
+
{ find: /^@jgengine\/ws\/(.*)$/, replacement: `${engineSrc("ws")}/$1` },
|
|
2878
|
+
{ find: /^@jgengine\/shell\/(.*)$/, replacement: `${engineSrc("shell")}/$1` },
|
|
2879
|
+
{ find: /^@jgengine\/assets$/, replacement: `${engineSrc("assets")}/index.ts` },
|
|
2880
|
+
{ find: /^@jgengine\/assets\/(.*)$/, replacement: `${engineSrc("assets")}/$1` },
|
|
2881
|
+
]
|
|
2882
|
+
: [],
|
|
2883
|
+
},
|
|
2884
|
+
});
|
|
2885
|
+
```
|
|
2886
|
+
|
|
2887
|
+
```css
|
|
2888
|
+
/* src/index.css */
|
|
2889
|
+
@import "tailwindcss";
|
|
2890
|
+
@source "../node_modules/@jgengine/react/dist";
|
|
2891
|
+
@source "../node_modules/@jgengine/shell/dist";
|
|
2892
|
+
```
|
|
2893
|
+
|
|
2894
|
+
Inside the engine repo the two `@source` lines point at `../../../packages/react/src` and `../../../packages/shell/src` instead (see any `Games/*/src/index.css`) — same file, different `@source` targets depending on where dist lives.
|
|
2895
|
+
|
|
2896
|
+
```tsx
|
|
2897
|
+
// main.tsx
|
|
2898
|
+
import "./index.css";
|
|
2899
|
+
|
|
2900
|
+
import { createRoot } from "react-dom/client";
|
|
2901
|
+
import { GameHost } from "@jgengine/shell/GameHost";
|
|
2902
|
+
import { game } from "./game.config";
|
|
2903
|
+
|
|
2904
|
+
const root = document.getElementById("root");
|
|
2905
|
+
if (root === null) throw new Error("main: missing #root mount element");
|
|
2906
|
+
createRoot(root).render(<GameHost playable={game} />);
|
|
2907
|
+
```
|
|
2908
|
+
|
|
2909
|
+
Add `"dev": "vite"` to `package.json`'s `scripts` — `bun dev` then launches the game standalone.
|
|
2910
|
+
|
|
2911
|
+
`GameHost` resolves multiplayer itself from `game.multiplayer` (falling back to offline when the adapter can't resolve, with a console warning) — pass `multiplayer` (a prebuilt `ShellMultiplayer | null`, used as-is with no resolution attempted) or `resolveMultiplayer` (`(args) => ShellMultiplayer | null`, tried before the built-in resolver, falling back to it on `null`) only when the host app needs to supply its own session, e.g. trying several transports in sequence.
|
|
2912
|
+
|
|
2913
|
+
A multi-game host (a launcher, a dev registry) wires `GamePlayer` over a `GameRegistry`:
|
|
2914
|
+
|
|
2915
|
+
```tsx
|
|
2916
|
+
import { GamePlayer } from "@jgengine/shell/GamePlayer";
|
|
2917
|
+
import type { GameRegistry } from "@jgengine/shell/registry";
|
|
2918
|
+
|
|
2919
|
+
const games: GameRegistry = {
|
|
2920
|
+
"my-game": () => import("./my-game").then((m) => m.game),
|
|
2921
|
+
};
|
|
2922
|
+
|
|
2923
|
+
function App() {
|
|
2924
|
+
return <GamePlayer gameId="my-game" registry={games} loading={<p>Loading…</p>} />;
|
|
2925
|
+
}
|
|
2926
|
+
```
|
|
2927
|
+
|
|
2928
|
+
`GamePlayer({ gameId, registry, fallbackGameId?, loading?, multiplayer? })` (`@jgengine/shell/GamePlayer`) is `GamePlayerShell` plus the lazy-load glue: it looks up `gameId` in `registry`, awaits the loader, renders `loading` (default `null`) until it resolves, then mounts `GamePlayerShell playable={...} multiplayer={...}`; switching `gameId` re-triggers the load, and an in-flight load is discarded if the id changes again first. `resolveGameLoader(registry, gameId, fallbackGameId?)` (`@jgengine/shell/registry`) is the underlying lookup — `registry[gameId] ?? registry[fallbackGameId]` — for hosts that want the fallback behavior without the component.
|
|
2929
|
+
|
|
2930
|
+
HUD styling is Tailwind v4 via the `index.css` above — without its `@source` lines the HUD renders unstyled. Then build the game itself under `src/` per the layout below — `src/game.config.ts` is the single entry, defined with `defineGame` from `@jgengine/shell/defineGame`.
|
|
2931
|
+
|
|
2932
|
+
## Scope
|
|
2933
|
+
|
|
2934
|
+
This file documents engine primitives and conventions only — never game domain. Example ids (`iron_block`, `mob_grunt`, `shop_town`) are placeholders, not content to copy.
|
|
2935
|
+
|
|
2936
|
+
| Engine owns | Your game owns |
|
|
2937
|
+
|-------------|----------------|
|
|
2938
|
+
| Weighted loot RNG, trade validation, loadout application, quest journal state, social graph, stat clamp math, effect absorption, projectile geometry, death resolution, event bus, feeds, leaderboards, input capture, pose hitboxes | Catalog entries and ids, effect id names, XP curves, shop/item/quest/loadout definitions, use-handlers, AI logic, UI content |
|
|
2939
|
+
|
|
2940
|
+
**Rules:**
|
|
2941
|
+
|
|
2942
|
+
1. **Catalog-first** — shape and behavior of every id lives in game-owned catalog files. Runtime calls pass ids, positions, instance keys.
|
|
2943
|
+
2. **Three buckets** — inventory items, scene objects, scene entities. Never merge them.
|
|
2944
|
+
3. **Dumb place/spawn** — no behaviors on `place()`/`spawn()`; the catalog owns them.
|
|
2945
|
+
4. **Commands for verbs** — input maps to actions, actions to commands/handlers; no raw keys in game logic.
|
|
2946
|
+
5. **Primitives over glue** — a loop several games need (loot roll, shop buy, kit seeding) belongs in the engine, not copy-pasted per game.
|
|
2947
|
+
6. **No speculative config** — `defineGame` fields exist only with a live engine consumer.
|
|
2948
|
+
7. **This file stays domain-free.**
|
|
2949
|
+
|
|
2950
|
+
## The three buckets
|
|
2951
|
+
|
|
2952
|
+
| Bucket | What | API |
|
|
2953
|
+
|--------|------|-----|
|
|
2954
|
+
| **Inventory** | Stackable ids in containers | `ctx.player.inventory.put / take / move / has / count` |
|
|
2955
|
+
| **Scene object** | Static world content | `ctx.scene.object.place / remove / move / rotate / list` |
|
|
2956
|
+
| **Scene entity** | Movers driven per tick | `ctx.scene.entity.spawn / despawn / setPose / effect / …` |
|
|
2957
|
+
|
|
2958
|
+
A voxel block is an object. A rack is an object with a slot inventory. A GPU is an inventory item inside it. A player, mob, or car is an entity. A dropped-item lying on the ground is also an entity — `ctx.scene.worldItem` (position + item ref + rarity, spawned under `game/worldItem`'s `WORLD_ITEM_ENTITY_NAME`) — never a fourth bucket and never merged into inventory or object.
|
|
2959
|
+
|
|
2960
|
+
## Game repo layout
|
|
2961
|
+
|
|
2962
|
+
Every game is one shape, enforced by `check-game-shape` (part of `check-types`): the top of `src/` holds only the skeleton, and every game-specific module, UI component, and test lives under `src/game/`. Dense files — one `catalog.ts` per domain, never one file per entry.
|
|
2963
|
+
|
|
2964
|
+
```
|
|
2965
|
+
src/
|
|
2966
|
+
game.config.ts single entry — export const game = defineGame({...}) from "@jgengine/shell/defineGame"
|
|
2967
|
+
index.tsx barrel — export { game } from "./game.config" (+ any UI-preview scenario re-export)
|
|
2968
|
+
main.tsx standalone host — mounts <GameHost playable={game}/> from "@jgengine/shell/GameHost"
|
|
2969
|
+
loop.ts onInit, onNewPlayer, onTick
|
|
2970
|
+
world.ts WorldFeature + PhysicsConfig (only for games that have one)
|
|
2971
|
+
game/
|
|
2972
|
+
keybinds.ts ActionCodesMap — named actions + hotbarSlotBindings(n)
|
|
2973
|
+
inventories.ts inventory declarations
|
|
2974
|
+
assets.ts Render catalog
|
|
2975
|
+
content.ts itemById / entityById lookups over all catalogs
|
|
2976
|
+
loadouts.ts Loadout ids → items/economy/unlocks per inventory
|
|
2977
|
+
world/ zones.ts, setup.ts (place/spawn from onInit)
|
|
2978
|
+
items/ <domain>/catalog.ts + use-handlers.ts
|
|
2979
|
+
objects/ catalog.ts (+ loot tables beside their domain)
|
|
2980
|
+
entities/ players/ enemies/ npcs/ — catalog.ts per role (never actors/)
|
|
2981
|
+
quests/catalog.ts when using game.quest
|
|
2982
|
+
progression/ curves.ts — game-owned XP curve numbers fed to game/progression
|
|
2983
|
+
ui/GameUI.tsx ALL layout/positioning
|
|
2984
|
+
ui/components/ content-only pieces GameUI places
|
|
2985
|
+
```
|
|
2986
|
+
|
|
2987
|
+
## `defineGame` — the single authoring entry
|
|
2988
|
+
|
|
2989
|
+
`@jgengine/shell/defineGame` is the game-authoring entry: one call in `game.config.ts` takes both engine fields (`name`, `assets`, `world`, `physics`, `inventories`, `input`, `server`, `save`, `time`, `feed`, `multiplayer`) and presentation fields (`content`, `loop`, `GameUI`, `camera`, `environment`, `WorldOverlay`, `renderEntity`, `renderObject`, `entitySprites`, `entityModels`, `objectModels`, `hotbarSelection`, `prompts`, `pointer`, `touch`, `worldHealthBars`, `audio`, `entitySounds`, `objectSounds`, `worldItem`, `shadows`, `collision`, `movement`, `devtools`) and returns a ready `PlayableGame` — no separate object to assemble. It is a thin wrapper over the core `defineGame` primitive (below) plus the `PlayableGame` runner assembly; see `packages/shell/src/defineGame.tsx` for the exact accepted fields. Never game tuning (walk speeds, damage, prompts — those live in catalogs).
|
|
2990
|
+
|
|
2991
|
+
**Smart defaults** — omit any of these and the call still resolves: `multiplayer` → `offline()`; `assets` → an empty asset catalog; `loop` hooks (`onInit`/`onNewPlayer`/`onTick`) → no-ops; `content` → `{}`; `GameUI` → an empty component; `camera` → third-person orbit; `feed` → 20-entry ring buffers per action; a `world` of kind `environment()` auto-renders as the backdrop with no `environment` component supplied — a non-`environment()` world (`flat()`, `voxel()`, …) still needs the game to hand it one.
|
|
2992
|
+
|
|
2993
|
+
```ts
|
|
2994
|
+
// game.config.ts — imports only, nothing inline
|
|
2995
|
+
import { defineGame } from "@jgengine/shell/defineGame";
|
|
2996
|
+
import { assets } from "./game/assets";
|
|
2997
|
+
import { content } from "./game/content";
|
|
2998
|
+
import { GameUI } from "./game/ui/GameUI";
|
|
2999
|
+
import { inventories } from "./game/inventories";
|
|
3000
|
+
import { keybinds } from "./game/keybinds";
|
|
3001
|
+
import { loop } from "./loop";
|
|
3002
|
+
import { physics, world } from "./world";
|
|
3003
|
+
|
|
3004
|
+
export const game = defineGame({
|
|
3005
|
+
name: "My Game",
|
|
3006
|
+
assets,
|
|
3007
|
+
world,
|
|
3008
|
+
physics,
|
|
3009
|
+
inventories,
|
|
3010
|
+
input: keybinds,
|
|
3011
|
+
server: "persistent", // or { mode: "ffa", scoreLimit: 30 } — rules live in game code
|
|
3012
|
+
save: { auto: "5m", scope: "player+chunks" }, // or "none"
|
|
3013
|
+
multiplayer: offline(), // or ws({ topology, url? }) / fly({ app }) / convex({ topology }) / socketIo({ topology, url? }) / p2p({ room? }) / lan({ port?, path? }) / servers({ …, adapter }) — defaults to offline()
|
|
3014
|
+
content,
|
|
3015
|
+
loop, // Partial<GameLoop<GameContext>> — missing hooks default to no-ops
|
|
3016
|
+
GameUI,
|
|
3017
|
+
camera: { perspective: "third" }, // optional — this is the default
|
|
3018
|
+
});
|
|
3019
|
+
```
|
|
3020
|
+
|
|
3021
|
+
```ts
|
|
3022
|
+
// game/keybinds.ts — named actions + generated hotbar slots; one key, one action
|
|
3023
|
+
import { hotbarSlotBindings, type ActionCodesMap } from "@jgengine/core/input/actionBindings";
|
|
3024
|
+
|
|
3025
|
+
export const keybinds: ActionCodesMap = {
|
|
3026
|
+
moveForward: ["KeyW"], moveBack: ["KeyS"], moveLeft: ["KeyA"], moveRight: ["KeyD"],
|
|
3027
|
+
jump: ["Space"], sprint: ["ShiftLeft"],
|
|
3028
|
+
interact: ["KeyE"],
|
|
3029
|
+
crouch: { hold: ["KeyC"], toggle: ["KeyZ"] },
|
|
3030
|
+
aim: { hold: ["mouse2"], toggle: ["KeyV"] },
|
|
3031
|
+
tabTarget: ["Tab"], clearTarget: ["Escape"],
|
|
3032
|
+
...hotbarSlotBindings(9), // hotbarSlot1..9 → Digit1..9 (a 10th slot gets Digit0)
|
|
3033
|
+
};
|
|
3034
|
+
```
|
|
3035
|
+
|
|
3036
|
+
```ts
|
|
3037
|
+
// game/inventories.ts
|
|
3038
|
+
import type { InventoryDeclaration } from "@jgengine/core/game/defineGame";
|
|
3039
|
+
export const inventories: Record<string, InventoryDeclaration> = {
|
|
3040
|
+
hotbar: { slots: 9, hud: "hotbar" },
|
|
3041
|
+
backpack: { slots: 28, traits: itemTraits },
|
|
3042
|
+
equipment: { slots: 4, accepts: ["weapon", "armor"], applyModifiers: true },
|
|
3043
|
+
};
|
|
3044
|
+
|
|
3045
|
+
// world.ts — top of src/, not under game/
|
|
3046
|
+
import type { PhysicsConfig } from "@jgengine/core/game/defineGame";
|
|
3047
|
+
import { biomes, type WorldFeature } from "@jgengine/core/world/features";
|
|
3048
|
+
export const world: WorldFeature = biomes({ map: "world/biomes", zones: "world/zones" });
|
|
3049
|
+
export const physics: PhysicsConfig = { gravity: -32 };
|
|
3050
|
+
```
|
|
3051
|
+
|
|
3052
|
+
- `PhysicsConfig.gravity`/`jumpVelocity` tune the shell's built-in walk controller's fall/jump feel (defaults ~`-24`/`7.1`) — the only two levers on gravity and jump height; everything else about movement (speed, poses) stays catalog `movement` fields.
|
|
3053
|
+
- Input bindings are string arrays (hold semantics) or `{ hold, toggle, repeatMs? }` for the same verb. `repeatMs` turns a held action into an auto-repeat fire (build-mode place-on-drag, rapid-fire without a separate `wasPressed`/interval combo in game code) — the shell refires the command every `repeatMs` while the binding stays down, on top of its normal press-edge fire.
|
|
3054
|
+
- **Keybind → command convention.** The shell fires a command for any bound action that isn't reserved: pressing an action runs a command of the **same name** if one is defined, else a `ui.<action>` fallback (so `openBackpack` → `ui.openBackpack`). Just declare the binding and a matching command — no per-game `keydown` listener. Reserved actions the shell consumes natively and never routes to a command: `moveForward/moveBack/moveLeft/moveRight`, `turnLeft/turnRight`, `sprint`, `jump`, `tabTarget`, `clearTarget`, `useAbility`, `interact`, and any `hotbarSlotN`/`slotN`. `tabTarget`/`clearTarget` run `target.cycle`/`target.clear` (native `cycleTarget`/`setTarget` fallback).
|
|
3055
|
+
- **`interact`** is special: pressing it resolves the active proximity prompt from the `prompts` field of `defineGame({...})` and runs that prompt's `invoke` command. A prompt with `invoke: null` is display-only and does nothing on the key.
|
|
3056
|
+
- UI keybind badges derive from `keybinds` via `actionLabel(keybinds, "openBackpack")` — `bindingLabel` maps codes to short labels (`Digit1`→`1`, `KeyB`→`B`, `mouse0`→`LMB`, `Escape`→`Esc`). Never hardcode label strings; they drift the moment a binding changes.
|
|
3057
|
+
- `server.mode` is a string your loop/commands interpret — the engine ships no gamemode presets.
|
|
3058
|
+
- Never in defineGame: player tuning, catalog helpers (`defineItems` etc.), game nouns, behaviors, prompts, or inline binding/inventory/world blobs. The one exception is `physics.gravity`/`physics.jumpVelocity` — global controller tuning, not a catalog value (see "Controller kinematics" below).
|
|
3059
|
+
- `assets` may be omitted for a game with no models (a HUD-only card/board game, say) — `defineGame` injects an empty catalog, so `GameDefinition.assets` is always present downstream with no per-caller `?.` checks.
|
|
3060
|
+
- `devtools` defaults to `true` — every game gets the F2-toggled debug overlay (Perf/Tune/Logs/Net/Keys) for free, and every top-level `export const` number/boolean/color and every exported flat table of them under `src/` is auto-discovered into the Tune tab with zero game code; set `false` to disable the toggle entirely. See "Devtools — F2 overlay and tunables" below.
|
|
3061
|
+
|
|
3062
|
+
### `@jgengine/core/game/defineGame` — the underlying primitive
|
|
3063
|
+
|
|
3064
|
+
The low-level engine boot call the shell `defineGame` composes internally: engine fields only (`name`, `assets`, `world`, `physics`, `inventories`, `input`, `server`, `save`, `time`, `feed`, `multiplayer`, `loop`) — no `content`/`GameUI`/`camera`/render fields, those are the shell layer's job.
|
|
3065
|
+
|
|
3066
|
+
```ts
|
|
3067
|
+
import { defineGame as defineEngineGame } from "@jgengine/core/game/defineGame";
|
|
3068
|
+
import { offline } from "@jgengine/core/runtime/adapter";
|
|
3069
|
+
|
|
3070
|
+
const game = defineEngineGame({
|
|
3071
|
+
name: "My Game",
|
|
3072
|
+
assets, world, physics, inventories,
|
|
3073
|
+
input: keybinds,
|
|
3074
|
+
server: "persistent",
|
|
3075
|
+
save: { auto: "5m", scope: "player+chunks" },
|
|
3076
|
+
multiplayer: offline(),
|
|
3077
|
+
loop, // GameLoop<GameContext>
|
|
3078
|
+
});
|
|
3079
|
+
```
|
|
3080
|
+
|
|
3081
|
+
Reach for this directly only outside a React host — a headless server, a non-shell runner; a browser game authors through `@jgengine/shell/defineGame` above, which calls this and returns the `PlayableGame` a runner needs.
|
|
3082
|
+
|
|
3083
|
+
## `PlayableGame` — how a game plugs into a runner
|
|
3084
|
+
|
|
3085
|
+
The type `@jgengine/shell/defineGame` returns and every runner (`GameHost`, `GamePlayerShell`) consumes. A game never builds this object by hand — `defineGame({...})` assembles it from the merged config. Source type at `@jgengine/core/game/playableGame`:
|
|
3086
|
+
|
|
3087
|
+
```ts
|
|
3088
|
+
export type PlayableGame<TUi = unknown> = {
|
|
3089
|
+
game: GameDefinition;
|
|
3090
|
+
content: GameContextContent; // { itemById?, entityById?, objectById? }
|
|
3091
|
+
loop: Required<GameLoop<GameContext>>; // onInit, onNewPlayer, onTick
|
|
3092
|
+
GameUI: TUi; // React component in web runners
|
|
3093
|
+
prompts?: (ctx: GameContext) => readonly PositionedPrompt[]; // interact-key + HUD source
|
|
3094
|
+
};
|
|
3095
|
+
```
|
|
3096
|
+
|
|
3097
|
+
`prompts` is the **single source** of positioned proximity prompts: the shell reads it to fire the `interact` key, and the HUD should read the same list through `useActivePrompt(playable.prompts?.(ctx))` rather than building its own — one list, no drift. A prompt is only actionable if its `invoke` is non-null.
|
|
3098
|
+
|
|
3099
|
+
Optional render/world fields the shell also reads: `entitySprites` / `entityModels` (billboards / GLBs keyed by entity kind), `objectModels` (GLBs keyed by object catalog id), `renderObject` (per-object visual override — return your own mesh for a placed object and the shell still positions it; falls back to `objectModels` → colored box), `WorldOverlay` (canvas-layer VFX), `environment` (canvas-layer scenery — ground/sky/structures; when set, replaces the default ground plane + debug grid + rock field), `camera`, `shadows` (cast/receive shadows across the R3F canvas; default true), and `worldHealthBars` (`boolean | { statId?, roles? }` — `roles` restricts bars to entities whose catalog `role` is in the given `CatalogEntityRole` list, e.g. skip friendly NPCs). A model value is a catalog id (`string`, resolved via `game.assets`) or an inline `ModelConfig { url, scale?, y?, anchor?, dims?, material?, animation? }` (`material` overrides color/metalness/roughness/emissive/emissiveIntensity on the cloned mesh, leaving shared GLTF caches untouched; `animation?: { clip?, loop?, timeScale?, paused?, time? }` plays a GLTF clip — `clip` defaults to the first clip, `loop` defaults true — and `paused: true` + `time: <seconds>` holds the rig on one fixed frame, a pose library for inventory previews or held cutscene poses). Catalog-resolved models carry measured `dims` (`catalog.resolve(id).dims = { footprint:{w,d}, center:{x,z}, minY }`); with the default `anchor: "center"` the shell centers the footprint on the placement point and ground-snaps `minY` to it, so corner-pivot kit models place correctly with no per-game pivot math. Applies through both `entityModels` and `objectModels`.
|
|
3100
|
+
|
|
3101
|
+
`renderEntity?: (entity: SceneEntity) => ReactNode` and `renderObject?: (object: SceneObject) => ReactNode` hand you the mesh for one entity/object while the shell still positions it and keeps it tagged for picking/selection; return null/undefined to fall through to model → sprite/box. `objectStyles?: Record<catalogId, { color?, opacity?, hidden? }>` styles the default colored-box object render — `color` overrides the hash color, `opacity < 1` sets transparent, `hidden` skips the mesh but keeps the picking tag.
|
|
3102
|
+
|
|
3103
|
+
**Presentation mode.** `PlayableGame.presentation`: `"3d"` (default) mounts the canvas, camera rig, and pointer; `"hud"` mounts none of that — the game is `GameUI` plus the command/input loop, for board/card/menu games that need no 3D camera at all.
|
|
3104
|
+
|
|
3105
|
+
**Auto environment.** When `world` is an `environment()` descriptor and `PlayableGame.environment` is unset, the shell renders that descriptor as the backdrop automatically — no manual `environment` wiring needed for the common case. Set `environment` explicitly only to override that default (a custom canvas component always wins). The same auto-render convention covers grid-cell worlds (`biomes`/`voxel`/`plots`/`tilemap`) — see "World features" below.
|
|
3106
|
+
|
|
3107
|
+
**Lighting and backdrop.** `PlayableGame.lighting` (`LightingConfig`, `@jgengine/core/game/playableGame`) replaces the shell's hardcoded ambient/directional default when present, regardless of world kind: `ambient?: { color?, intensity? }`, `directional?: { color?, intensity?, position, castShadow? }[]`, `hemisphere?: { skyColor?, groundColor?, intensity? }`. `PlayableGame.backdrop` (`BackdropConfig`) is a generic background/sky/fog for **any** world kind, including a custom `environment` component: `background?: string` (CSS color), `sky?: SkyEnvironmentConfig` (same descriptor `environment()`'s `sky` field takes), `fog?: { color?, near?, far?, density? }` (`density` set switches to exponential `FogExp2` and `near`/`far` are ignored). Both are optional and additive to whatever the world/`environment` renderer already draws.
|
|
3108
|
+
|
|
3109
|
+
**Player movement tuning** — the `movement` field (`PlayerMovementConfig`) tunes the shell's local-player walk controller beyond `physics.gravity`/`jumpVelocity`: `mode` (`"free"` camera-relative default, `"axis"` locks travel to one world `axis`, `"grid"` snaps each committed step to `cellSize` centers), `collideObjects` (collide against placed scene objects as unit-box AABBs even without `collision.voxel`), and `beforeCommit(frame)` — an escape hatch called each frame with `{ entityId, current, next, dt }` that can return a replacement `[x, y, z]` to constrain or redirect the step (rails, bounds, custom collision) before it commits and before `onTick` runs.
|
|
3110
|
+
|
|
3111
|
+
The runner boots `createGameContext({ definition, content, player: { userId, isNew } })`, calls `loop.onInit(ctx)` then `loop.onNewPlayer(ctx)`, and drives `loop.onTick(ctx, dt)` per frame. **Convention: `onNewPlayer` spawns the player entity with `id === ctx.player.userId`** — bounded stats, targeting, and kill attribution key off that.
|
|
3112
|
+
|
|
3113
|
+
### Object spatial queries, entity patching, and surface sampling
|
|
3114
|
+
|
|
3115
|
+
```ts
|
|
3116
|
+
ctx.scene.object.at(x, y, z) // cell lookup, cell size 1, most-recent wins
|
|
3117
|
+
ctx.scene.object.inBox(min, max) // inclusive AABB query
|
|
3118
|
+
ctx.scene.object.raycast({ origin, direction, maxDistance, halfExtents?, filter? }) // → nearest hit or null
|
|
3119
|
+
ctx.scene.object.raycastAll({ origin, direction, maxDistance, halfExtents?, filter? }) // → hits, nearest-first
|
|
3120
|
+
ctx.scene.entity.update(id, patch) // name/position/rotations/role/movement/behaviors/meta; false for unknown id
|
|
3121
|
+
```
|
|
3122
|
+
|
|
3123
|
+
Placed objects are unit boxes (half-extents `[0.5, 0.5, 0.5]`) centered on position, matching the shell's default render, so `raycast`/`raycastAll` (`scene/objectQuery`) match what a player sees. `entity.update` notifies subscribers and bumps `ctx.version()` the same as `spawn`/`despawn`/`setPose` — it's the general-purpose patch the more specific methods (`setPose`, `form.shapeshift`, `possession.possess`) build on.
|
|
3124
|
+
|
|
3125
|
+
`ctx.scene.object.place(catalogId, x, y, z, { instanceId?, parentSpace?, rotation?, visual? })` takes an optional `visual: ObjectVisual` (`@jgengine/core/scene/objectStore`) — `{ scale?: number | [x,y,z], color?, opacity? }` — a per-instance render override independent of the catalog entry; `ctx.scene.object.setVisual(instanceId, visual | undefined)` changes it after placement (`undefined` clears back to the catalog default). Distinct from `objectStyles` on `PlayableGame` (styles a catalog id for every instance); `visual`/`setVisual` targets one placed instance (a damaged crate, a dyed banner, a resized prop).
|
|
3126
|
+
|
|
3127
|
+
`pointerService.worldHitCenter()` (shell) casts from the viewport center regardless of cursor presence (pointer-lock aim) — combine with `pointer.worldHit()` (cursor-driven) to support both mouse-look and free-cursor games from the same probe. `PointerHit` also carries an optional `uv?: { u, v }` on UV-mapped mesh hits (absent for the ground fallback), `material?: { color, metalness?, roughness? } | null` sampled off the hit mesh's `MeshStandardMaterial` (`null`/unset for non-standard materials, e.g. the ground plane) — combine `uv` + `material` for paint tools, decals, and material-aware interaction — and `instanceId?: number`, the hit index when the intersected mesh is a `THREE.InstancedMesh` (grid-world cells, `InstancedBodies` debris, any instanced render), absent otherwise.
|
|
3128
|
+
|
|
3129
|
+
**Runtime paint layer** (`ctx.scene.entity.paint`, backed by `scene/paintLayer`) — a `PaintLayer` keyed by instance id (entity or object): `paint(instanceId, { u, v, radius, color })`, `strokes(instanceId)`, `clear(instanceId?)`, `version(instanceId)` (bumps per paint/clear), `subscribe(listener)`. The shell auto-renders painted instances through a lazily-created 512×512 canvas texture kept in sync — no per-game render wiring. Clearing refills with the material's base color; the original texture pixels are not restored.
|
|
3130
|
+
|
|
3131
|
+
### Audio — positional emitters, listener falloff, buses
|
|
3132
|
+
Catalog-first, no per-game audio glue. The `audio` field of `defineGame({...})` — `{ sounds: Record<string, SoundDef>, buses?: Record<string, AudioBusDef> }` — declares the sound catalog (`SoundDef = { id, url, bus, gain?, loop?, positional?, falloff? }`) and mix buses (`music`/`sfx`/`ambient`/…, `AudioBusDef = { id, gain? }`) — both types from `@jgengine/core/audio/audioFalloff`. `entitySounds?: Record<string, string>` maps an entity **kind name** (same convention as `entitySprites`/`entityModels`) to a sound id: while a matching entity exists, the shell keeps a looping positional emitter on it, repositioned every frame. `objectSounds?: Record<string, string>` does the same keyed by placed-object catalog id. The pure distance→gain math (`computeFalloffGain(distance, config)`, curves `"linear" | "inverse" | "none"`) lives in core so it is unit-tested without a browser; `@jgengine/shell` (`shell/audio/audioEngine`, `shell/audio/AudioComponents`) is the only package that touches Web Audio — it owns an `AudioContext`, mounts `AudioListener` on the camera every frame, and `EntityAudioEmitters`/`ObjectAudioEmitters` drive per-instance emitter gain from the core falloff function. `GamePlayerShell` wires all of this automatically from `playable.audio`/`entitySounds`/`objectSounds` — a game never touches `AudioContext` directly.
|
|
3133
|
+
### Camera rigs (`camera` field of `defineGame({...})`)
|
|
3134
|
+
The shell ships a **rig library**; a game picks and tunes one through `camera` config, never by writing camera positions from `onTick`. Select with `camera.rig` (or the `perspective: "third" | "first"` shorthand) — or by config block alone (#207.8): supplying `camera.<rig>` selects that rig with no redundant `rig` field, checked in the table's order; an explicit `rig` wins when several blocks are present:
|
|
3135
|
+
| `rig` | For | Key config (`camera.<rig>`) |
|
|
3136
|
+
|-------|-----|------------------------------|
|
|
3137
|
+
| `orbit` (default) | Third-person chase | `initialDistance`, `targetHeight`, `min/maxPolarAngle`, drag/zoom |
|
|
3138
|
+
| `first` | FPS mouse-look | `firstPerson: { eyeHeight, sensitivity, maxPitch, reticle, viewmodel }` |
|
|
3139
|
+
| `topDown` | ARPG iso / top-down (Diablo IV, Hades II) | `topDown: { height, pitch, yaw, followSmoothing, zoom }` — decoupled follow; `pitch` is camera elevation (PI/2 = straight down, near 0 = grazing and boom-distance blows up past `frustum.far`) |
|
|
3140
|
+
| `rts` | Free-pan / edge-scroll (The Sims, Manor Lords) | `rts: { panSpeed, edgeScroll, rotateSpeed, bounds, start, pan }` — `pan: false` turns it into a static backdrop camera: no WASD/arrow pan, no edge-scroll, no Q/E rotate, no wheel zoom, still re-centers on `followEntityId` if one resolves |
|
|
3141
|
+
| `shoulder` | Over-the-shoulder (Helldivers 2, Remnant II) | `shoulder: { shoulderOffset, distance, ads, side }` — ADS + shoulder-swap (V) |
|
|
3142
|
+
| `lockOn` | Souls-like strafe (Elden Ring) | `lockOn: { targetEntityId?, distance, framingBias, yawSmoothing }` — yaw binds to player→target; WASD becomes strafe |
|
|
3143
|
+
| `chase` | Vehicle chase (Forza, Rocket League) | `chase: { distance, springDamping, fov: { base, max, speedForMax }, shakePerSpeed, view: "chase"|"cockpit"|"hood"|"rear" }` |
|
|
3144
|
+
| `sideScroll` | Fixed lateral follow — 2.5D platformer/beat-'em-up | `sideScroll: { distance, height, lookHeight, axis: "x"\|"z", followSmoothing, fov }` — reads no player input, follows like the other follow rigs (defaults to the local player) |
|
|
3145
|
+
| `observer` | Detached spectator/photo/kill-cam (#120) | `observer: { bind: { kind: "entity", entityId } \| { kind: "point", position }, distance, height, orbitSpeed }` — reads no player input, auto-orbits the bound subject |
|
|
3146
|
+
| `inspection` | Model-viewer / data-viz orbit (#207.7) | `inspection: { anchor: "target"\|"cursor"\|"center", target, initialDistance, initialPosition, min/maxDistance, min/maxPolarAngle, pan, rotateSpeed, zoomSpeed, dampingFactor }` — left-drag orbit, middle/right-drag pan (pan defaults on for this rig only), scroll zoom toward the anchor (`cursor` = zoom-to-cursor); orbits a fixed `target`, never reads player/entity state |
|
|
3147
|
+
| `none` | No camera rig mounted | HUD-only presentations or a game that manages its own camera; see `presentation: "hud"` below |
|
|
3148
|
+
**Frustum:** `camera.frustum: { fov?, near?, far? }` overrides the canvas camera; `far` defaults to 300, so any world whose content spans more than a few hundred units must raise it or distant settlements/terrain silently clip out of view. **Every rig accepts `followEntityId: null`** so avatar-less games (city-builders, card games, auto-battlers) still mount a camera. Leave `followEntityId` unset and the shell defaults it to `ctx.player.possession.active(userId)` every frame, so a possession swap (party control-swap, BG3-style) or a form's mesh/camera-relevant change re-targets the camera automatically — set it explicitly only to override that default. **Shake / trauma (#28):** every rig reads a shake channel; feed it from anywhere with `import { cameraShake } from "@jgengine/shell/camera"` — `cameraShake(amplitude, decayPerSecond?)` (amplitude 0..1) — or from React via `useCameraShake()`. Tune with `camera.shake: { maxOffset, maxRoll, decayPerSecond, exponent, frequency }`. **Cinematic (#29):** set `camera.cinematic: { keyframes: [{ position, lookAt, fov?, duration?, ease? }], loop? }` to play a scripted path over the active rig, and `camera.transitionSeconds` cross-fades the camera when the rig changes so mode swaps don't hard-cut. The pure rig math (shake decay, spring-arm, speed→FOV, offset/strafe, keyframe lerp) is exported from `@jgengine/shell/camera` for testing.
|
|
3149
|
+
|
|
3150
|
+
## `GameContext` — the ctx surface
|
|
3151
|
+
|
|
3152
|
+
`createGameContext` (in `@jgengine/core/runtime/gameContext`) wires every system:
|
|
3153
|
+
|
|
3154
|
+
```
|
|
3155
|
+
ctx.scene.object place, remove, move, rotate, get, list, subscribe,
|
|
3156
|
+
at, inBox, raycast, raycastAll, catalog
|
|
3157
|
+
ctx.scene.entity spawn, despawn, setPose, update, get, list,
|
|
3158
|
+
stats.{get,set,delta}, setTarget, getTarget, cycleTarget,
|
|
3159
|
+
canReceive, preview, effect, paint,
|
|
3160
|
+
willHitProjectile, fireProjectile, settleProjectile,
|
|
3161
|
+
distance, inRadius, hasLineOfSight, queryArc, moveToward,
|
|
3162
|
+
spawnPoseOf, resetToSpawn, resetAllToSpawn,
|
|
3163
|
+
form.{register,get,active,abilities,shapeshift,revert}
|
|
3164
|
+
ctx.game commands, events, feed, loot, trade, quest, social, chat,
|
|
3165
|
+
unlocks, economy, leaderboard, roster, store, cards, turn
|
|
3166
|
+
ctx.game.social friends, party, presence, emotes.play, worldInvites
|
|
3167
|
+
ctx.game.store set, delete, get, has, subscribe, mapSnapshot, arraySnapshot — game-defined
|
|
3168
|
+
keyed reactive store slot (any value type); mutations bump ctx.version()
|
|
3169
|
+
ctx.game.cards pile(id, config?) — lazily creates (config required on first call) or returns
|
|
3170
|
+
the existing notify-wrapped CardPile for id
|
|
3171
|
+
ctx.game.turn loop(id, config?) — lazily creates (config required on first call) or returns
|
|
3172
|
+
the existing notify-wrapped TurnLoop for id
|
|
3173
|
+
ctx.player userId, isNew, inventory, stats (modifiers), loadout,
|
|
3174
|
+
applyLoadout, movement (pose/aim), motion (impulse/setVerticalVelocity/setY/takePending),
|
|
3175
|
+
possession, cosmetics
|
|
3176
|
+
ctx.player.motion impulse(vy), setVerticalVelocity(vy), setY(y), takePending() — game-code
|
|
3177
|
+
seam into the shell's vertical-motion integrator; drained once per frame
|
|
3178
|
+
before gravity, so a jump pad or grapple release calls this from
|
|
3179
|
+
onTick/commands instead of touching y directly
|
|
3180
|
+
ctx.item use, weapon
|
|
3181
|
+
ctx.input publish(held), isDown(action), held() — per-frame held-action snapshot, polled from onTick
|
|
3182
|
+
ctx.world ground (TerrainField), groundHeightAt(x, z) — the canonical
|
|
3183
|
+
sampler for the game's declared world; environment worlds
|
|
3184
|
+
resolve their terrain field, every other world kind is 0.
|
|
3185
|
+
Use it for every spawn/placement/waypoint y — never
|
|
3186
|
+
hand-roll a noise sampler or hardcode y = 0 on relief
|
|
3187
|
+
ctx.camera follow(entityId | null), followedEntityId(), setCinematic(config), cinematic(),
|
|
3188
|
+
subscribe — runtime camera-follow/cinematic override; the shell reads
|
|
3189
|
+
followedEntityId() each frame, falling back to the static
|
|
3190
|
+
playable.camera.followEntityId when it returns undefined
|
|
3191
|
+
ctx.time advance, now, calendar, snapshot; pause, play, toggle,
|
|
3192
|
+
setSpeed, cycleSpeed; after, every, at (game-time timers)
|
|
3193
|
+
ctx.subscribe / ctx.version change signal — UI layers bind via useSyncExternalStore
|
|
3194
|
+
```
|
|
3195
|
+
|
|
3196
|
+
`content.itemById(id)` supplies `{ use?, weapon?, trade? }`; `content.entityById(id)` supplies `{ stats?, receive?, onDeath?, movement?, role? }`; `content.objectById(id)` supplies `GameContextObjectEntry` `{ proximityPrompt?, breakable?, slotInventory? }`. Build all three from your catalogs in `content.ts`. A placed object resolves its catalog entry via `ctx.scene.object.catalog(instanceId)`; `ctx.scene.object.at(x, y, z, tolerance?)` finds placed objects near a point (grid interaction, click resolution beyond the pointer service). `ctx.scene.entity.update(id, patch)` writes a shallow patch onto a spawned entity's mutable fields (e.g. `movement.walkSpeed`) without a full respawn — `scene/movementSpeed`'s `applyStatDrivenSpeed(deps, id, { baseSpeed, multiplierStat?, flatBonusStat? })` is the catalog-driven helper that recomputes and writes `movement.walkSpeed` from a stat-modifier pair each time a buff changes.
|
|
3197
|
+
|
|
3198
|
+
### Two tiers: `ctx` runtime vs pure factories
|
|
3199
|
+
|
|
3200
|
+
The `ctx` surface above is the **stateful runtime** — it's what game code uses. Every subsystem it wires is *also* exported as a **pure factory** that `createGameContext` composes internally: `createTradeSystem`, `createDeathSystem`, `createEffectSystem`, `createProjectileSystem`, `createSpatialApi`, `createEntityStatsApi`, `createEntityStore`, `createObjectStore`, `createStats`, `createLoadouts`, `createLootRegistry`, `createQuestJournal`, `createSocial`, `createSlots`, `createInteriors` (plus stateless helpers beside each — `canAffordCosts`/`resolveBuy` in `game/trade`, `getStatValue`/`applyPoolDelta` in `scene/entityStats`, and so on). **Build a game through `ctx`, not these** — reach for the factories only for unit tests of pure game math, headless servers, or a custom runtime. Import the domain deep path (`@jgengine/core/combat/death`, `@jgengine/core/game/trade`, `@jgengine/core/stats/statModifiers`, …) and read the `.d.ts`; each is a real export in the published package.
|
|
3201
|
+
|
|
3202
|
+
`createSpatialApi`'s optional `grid: { cellSize }` opts `inRadius`/`queryArc` into a lazily-built x/z broadphase index over `candidates()` instead of a linear scan — worth it once candidate counts run into the hundreds+. The index is reused across calls until `invalidate()` is called, so call it after any position change (move, spawn, despawn); a candidate outside the index at query time still resolves exactly (never silently skipped), only a *moved* one can be missed until invalidated.
|
|
3203
|
+
|
|
3204
|
+
## `loop` — lifecycle
|
|
3205
|
+
|
|
3206
|
+
```ts
|
|
3207
|
+
export function onInit(ctx: GameContext) {
|
|
3208
|
+
ctx.item.use.register(itemUseHandlers);
|
|
3209
|
+
ctx.player.loadout.register(loadouts);
|
|
3210
|
+
for (const table of lootTables) ctx.game.loot.register(table);
|
|
3211
|
+
ctx.game.quest.register(quests);
|
|
3212
|
+
ctx.game.quest.bind("entity.died");
|
|
3213
|
+
ctx.game.feed.bind("entity.died");
|
|
3214
|
+
ctx.game.events.on("entity.died", (evt) => onEntityDied(ctx, evt));
|
|
3215
|
+
setupWorld(ctx);
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
export function onNewPlayer(ctx: GameContext) {
|
|
3219
|
+
ctx.scene.entity.spawn("player_default", { id: ctx.player.userId, position: spawnPoint });
|
|
3220
|
+
if (ctx.player.isNew) ctx.player.applyLoadout(ctx.player.userId, "starterKit");
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
export function onTick(ctx: GameContext, dt: number) {
|
|
3224
|
+
// AI, regen, respawn timers — dt is GAME time (see ctx.time). Never death detection (see entity.died)
|
|
3225
|
+
}
|
|
3226
|
+
```
|
|
3227
|
+
|
|
3228
|
+
`onInit` runs once per boot; register everything there. Loot tables register through `ctx.game.loot.register` — `lootTable()` is a pure validating factory, there is no global side-effect registry.
|
|
3229
|
+
|
|
3230
|
+
## `ctx.time` — the simulation clock
|
|
3231
|
+
|
|
3232
|
+
`onTick`'s `dt` is **game time, not real time**: the shell scales each frame's real delta by `definition.time.scale` (real→game seconds at 1×) and the live speed multiplier, so writing decay/regen/AI as `rate * dt` makes it obey pause and fast-forward for free — never read wall-clock in a tick. Configure via `defineGame({ time: { scale?, speeds?, dayLength?, start?, startPaused?, daysPerYear?, seasons? } })` (all optional; default is real-time 1:1 with speeds `[1,2,3,4]`, 365-day years).
|
|
3233
|
+
|
|
3234
|
+
- **Continuous** work scales through `dt`. **Scheduled** work uses game-time timers: `ctx.time.after(sec, cb)`, `ctx.time.every(sec, cb)`, `ctx.time.at(gameSec, cb)` — measured in game-seconds, so 4× fires them 4× sooner and pause freezes them. Each returns a cancel handle.
|
|
3235
|
+
- **Controls** (drive from a HUD or a command): `pause()`, `play()`, `toggle()`, `setSpeed(mult)` (0 pauses), `cycleSpeed()`. Read state with `ctx.time.snapshot()` / `ctx.time.calendar()` (`{ day, hour, minute, second, dayFraction, year, dayOfYear, yearFraction, season? }`), or in React with `useGameClock()` → snapshot + `controls`. Speeding to 4× or pausing affects **everything** on the tick — no per-system wiring.
|
|
3236
|
+
- **Calendar year/season** rides the same day counter, no separate clock: `year`/`dayOfYear` fall out of `day` divided by `TimeConfig.daysPerYear` (default 365), `yearFraction` is progress through the current year (0..1). Setting `TimeConfig.seasons: string[]` (e.g. `["spring","summer","fall","winter"]`) slices the year into equal named segments and populates `calendar().season`; omit `seasons` and the field is absent — a living-world sim names its own season boundaries this way instead of a hand-rolled `dayOfYear % ...` module.
|
|
3237
|
+
|
|
3238
|
+
### Beat clock — BPM signal + input quantization
|
|
3239
|
+
|
|
3240
|
+
`@jgengine/core/time/beatClock` is a separate, purpose-built signal from `simClock` — a BPM tick generator for rhythm games (Hi-Fi Rush–style quantized combat), not a day/pause clock. `createBeatClock({ bpm, beatsPerBar? }, onBeat?)` returns a `BeatClock`: call `advance(gameDt)` from `onTick` with the same **game-time** `dt` (never wall-clock) — it fires `onBeat(beatIndex)` once per newly crossed integer beat and returns a `BeatSnapshot` (`beat`, `beatIndex`, `bar`, `beatInBar`, `phase`). `createBeatInputBuffer<T>(beatDurationSec)` is the auto-correct input buffer: `buffer(action, nowSec)` quantizes an off-beat press to fire on the next beat tick (or immediately if pressed exactly on one); `advance(nowSec)` drains and returns every action whose beat has arrived. `nextBeatTime(nowSec, beatDurationSec)` is the underlying pure quantization function. Feed a music track's actual BPM in; the buffer is what makes an early/late input still land on-beat.
|
|
3241
|
+
|
|
3242
|
+
## Content catalogs
|
|
3243
|
+
|
|
3244
|
+
### Object catalog fields
|
|
3245
|
+
|
|
3246
|
+
| Field | Purpose |
|
|
3247
|
+
|-------|---------|
|
|
3248
|
+
| `id`, `model` | Canonical id, asset key |
|
|
3249
|
+
| `footprint` | `{ w, h, d }` placement bounds |
|
|
3250
|
+
| `snap` | `"grid"` \| `"free"` \| `"wall"` |
|
|
3251
|
+
| `solid` | Blocks movement |
|
|
3252
|
+
| `breakable` | `false` or `{ baseBreakTime, harvest, drops, dropsWhenUnmet }` |
|
|
3253
|
+
| `proximityPrompt` | Float UI + optional command invoke |
|
|
3254
|
+
| `slotInventory` | Attached container `{ slots, accepts }` created at place time (`object:<instanceId>`) |
|
|
3255
|
+
|
|
3256
|
+
Break resolution: `duration = baseBreakTime / (tool?.breakSpeed ?? 1)`; drops per `when` (`always` / `harvestMet` / `silkTouch` / `playerKill`); then `inventory.put` + `object.remove`.
|
|
3257
|
+
|
|
3258
|
+
### Item catalog fields
|
|
3259
|
+
|
|
3260
|
+
| Field | Purpose |
|
|
3261
|
+
|-------|---------|
|
|
3262
|
+
| `id`, `kind`, `stack`, `model` | Basics; `stack` feeds `itemTraits.stackLimit` |
|
|
3263
|
+
| `use` | Game handler name dispatched by `item.use` (`"fireGun"`, `"castBolt"`, `"drinkPotion"`) |
|
|
3264
|
+
| `weapon` | Stats the handler reads via `item.weapon.getStat` — `damage`, `heal`, `reach`, `manaCost`, `projectile.{mass,gravity,fuseTime,settleOn}`, `explosion.{radius}` … |
|
|
3265
|
+
| `trade` | `{ buy?: {coins: 80}, sell?, shops?: ["shop_town"] }` |
|
|
3266
|
+
| `requires` | Unlock ids gating purchase/use |
|
|
3267
|
+
| `placesObject` | Object id placed from hotbar |
|
|
3268
|
+
| `rarity`, `baseType` | Read by the `worldItem` rarity render binding + loot filter when this item drops to the ground (#32/#33); `baseType` defaults to the item id when absent |
|
|
3269
|
+
|
|
3270
|
+
### Entity catalog fields
|
|
3271
|
+
|
|
3272
|
+
| Field | Purpose |
|
|
3273
|
+
|-------|---------|
|
|
3274
|
+
| `movement` | `walkSpeed` (reaches spawn automatically), `poses?: ["standing","crouch","prone","running"]`, `aim?: ["hip","ads"]`, `frozen?: boolean` — a scene-instance-level movement lock (cutscenes, stuns, mount transitions); `movedWhileFrozen(entity)` (`scene/entityStore`) flags an entity whose velocity moved anyway despite `frozen: true`, catching a system that bypassed the lock |
|
|
3275
|
+
| `role` | `CatalogEntityRole` = `"player"` \| `"enemy"` \| `"hostile"` \| `"npc"` \| `"vehicle"` — catalog hostility class for targeting (`"enemy"`/`"hostile"` classify hostile in `cycleTarget`). Distinct from the scene *instance* `EntityRole` (`"player"` \| `"npc"` \| `"prop"`, in `scene/entityStore`) which drives input/camera binding — **possession** (`ctx.player.possession`) flips this instance role between `"player"`/`"npc"` on every control swap, so exactly one owned entity is ever the input/camera target |
|
|
3276
|
+
| `stats` | Stat declarations — bounded values: `{ health: { max: 120, min: 0 }, level: { max: 60, min: 1, current: 1 }, … }` — `current` optional, defaults to `max` |
|
|
3277
|
+
| `receive` | Per-effect absorption: `{ damage: { order: ["shield","health"], modifiers? }, heal: { order: ["health"] } }` — keyed by **game-defined effect ids**; presence = can receive |
|
|
3278
|
+
| `onDeath` | `{ drops: "table_id" }` or reason-aware `{ drops: [{ table, when: { reason: "player_kill" } }], command?: { name, when? } }` |
|
|
3279
|
+
| `wander`, `talkable` | AI descriptor; dialogue id sugar for a talk prompt |
|
|
3280
|
+
|
|
3281
|
+
### Dialogue catalog
|
|
3282
|
+
|
|
3283
|
+
`entities/npcs/dialogues.ts` — `{ id, lines: [{ speaker, text } | { choices: [{ label, invoke: { command, args } | null }] }] }`. Choices invoke `quest.accept`, `trade.open`, etc. Types ship from `@jgengine/react/components` (`DialogueDef`, `DialogueChoice`, `DialogueLine`) so a game imports them rather than redeclaring — the `DialogueBox` component renders the same shape it types.
|
|
3284
|
+
|
|
3285
|
+
A choice may gate its branch behind a roll: `{ check: { modifier, dc, advantage? }, onSuccess?, onFailure? }` (`onSuccess`/`onFailure` default to `invoke` when omitted). `DialogueBox` rolls via `@jgengine/core/stats/rollCheck`'s `rollCheck({ modifier, dc, advantage }, rng?)` (d20 by default; `advantage`/`disadvantage` roll twice and take the high/low; a natural 1 or max-die result reports `critical`) when the player clicks a checked choice, then calls `onChoice(choice, result)`; game code resolves which command to run with `resolveDialogueInvoke(choice, result)` (also exported from `@jgengine/react/components`).
|
|
3286
|
+
|
|
3287
|
+
## `scene.entity.stats` — bounded stats
|
|
3288
|
+
|
|
3289
|
+
```ts
|
|
3290
|
+
stats.get(instanceId, statId) // → { current, max, min } | null
|
|
3291
|
+
stats.set(instanceId, statId, { current?, max?, min? })
|
|
3292
|
+
stats.delta(instanceId, statId, n) // → null | { reason } — clamps into [min, max]
|
|
3293
|
+
```
|
|
3294
|
+
|
|
3295
|
+
Health, mana, xp, level, energy — any stat id declared on the catalog. Spawn seeds from the catalog (`current ?? max`). Combat writes through effects; non-combat (regen ticks, XP grants) calls `delta` directly.
|
|
3296
|
+
|
|
3297
|
+
**XP/level use the engine progression primitive.** `@jgengine/core/game/progression` ships `curve()`/`evalCurve()` (evaluate a game-owned XP-per-level curve *definition*) and `leveling()` (a level track over the bounded `xp`/`level` stats that reports overflow). You own the curve *numbers* in a catalog; the engine owns the overflow math — on level-up bump `level.current`, reset `xp.max` from the curve, push a `stat.levelUp` feed entry. Hand-rolling `xpForLevel`/`levelFromXp`/`xpToNextLevel` is the anti-pattern — those already exist. `LevelingConfig.thresholdMode` picks how the curve is read: `"perLevel"` (default) treats `xpForLevel(N)` as the incremental N-1→N cost, summed internally; `"cumulative"` treats `xpForLevel(N)` as the total lifetime XP to reach level N (0 at/below `startLevel`) and compares `xp.current` straight against those totals — pick this for a design that quotes "total XP to level N" tables.
|
|
3298
|
+
|
|
3299
|
+
`leveling({ …, thresholdMode: "cumulative" })` switches to lifetime-total semantics: `xp` is a running total instead of a per-level bank, and `resolve(level, xp)` walks upward from `level` to the highest level whose cumulative threshold `xp` clears — it never demotes, and clamps the returned `xp` to the max-level threshold once capped. Default is `"perLevel"` (unchanged, fully back-compat) — pick `"cumulative"` for a total-XP display (MMO-style "12,450 XP") instead of a resettable per-level bank.
|
|
3300
|
+
|
|
3301
|
+
`ctx.player.stats` is a different thing: **modifiers** (buffs, ADS zoom, walk-speed bonuses) via `base/add/remove/get` with expiries — never bounded current/max values.
|
|
3302
|
+
|
|
3303
|
+
## Targeting (MMO tab-target)
|
|
3304
|
+
|
|
3305
|
+
Persistent per-entity session state — never a per-use input field.
|
|
3306
|
+
|
|
3307
|
+
```ts
|
|
3308
|
+
ctx.scene.entity.setTarget(fromId, toId | null)
|
|
3309
|
+
ctx.scene.entity.getTarget(fromId) // → instanceId | null
|
|
3310
|
+
ctx.scene.entity.cycleTarget(fromId, { filter: "hostile" | "friendly" | "any", direction? })
|
|
3311
|
+
```
|
|
3312
|
+
|
|
3313
|
+
Hostility comes from catalog `role` (`"enemy"`/`"hostile"` classify hostile). Input `tabTarget`/`clearTarget` actions route here. Handlers always read `getTarget(input.from)` — `ItemUseInput` deliberately has **no `to` field** (single source of truth, no client-supplied target to validate, three targeting models stay clean: aim for shooters, `queryArc` for melee, `getTarget` for MMO).
|
|
3314
|
+
|
|
3315
|
+
## `item.use` — one verb for all usable items
|
|
3316
|
+
|
|
3317
|
+
```ts
|
|
3318
|
+
ctx.item.use.register(handlers) // once in onInit; duplicate names throw
|
|
3319
|
+
ctx.item.use.can(ctx, input) // → { reason } | null
|
|
3320
|
+
ctx.item.use.use(ctx, input) // dispatches catalog `use` → your handler
|
|
3321
|
+
|
|
3322
|
+
type ItemUseInput = { from: string; itemId: string; inventoryId?: string; aim?: Aim };
|
|
3323
|
+
type ItemUseHandler<GameContext> = {
|
|
3324
|
+
can?(ctx, input): { reason: string } | null;
|
|
3325
|
+
apply(ctx, input): { state: GameContext; error?: string };
|
|
3326
|
+
};
|
|
3327
|
+
```
|
|
3328
|
+
|
|
3329
|
+
**Handlers receive the full `GameContext` as state** and mutate through it. Handlers own ammo, cooldowns, range checks, and effect ids; the engine owns projectile geometry, stat clamp math, and `canReceive`.
|
|
3330
|
+
|
|
3331
|
+
| Handler | Engine calls |
|
|
3332
|
+
|---------|--------------|
|
|
3333
|
+
| gun | spend ammo → `fireProjectile` → `settleProjectile` |
|
|
3334
|
+
| grenade | `fireProjectile` (ballistic) → settle → `effect({ at, radius })` |
|
|
3335
|
+
| melee | `queryArc` + reach from `getStat` → `effect` per hit |
|
|
3336
|
+
| MMO cast | `getTarget(from)` → `stats.delta(mana)` → `effect({ to })` |
|
|
3337
|
+
| consumable | `effect({ to: from, effect: "heal", via: { amount: -n } })` |
|
|
3338
|
+
|
|
3339
|
+
Banned in the engine: `weapon.fire`, `consumable.use`, `game.combat.*`, per-weapon commands.
|
|
3340
|
+
|
|
3341
|
+
### Skill-checks and QTE (timed/rolled minigames)
|
|
3342
|
+
|
|
3343
|
+
`@jgengine/core/interaction/skillCheck` models a moving-target-zone minigame (casting/reeling, active-reload): `evaluateSkillCheck({ trackWidth, zone, markerPeriod, window, zoneDriftPerSecond? }, elapsedSeconds)` bounces a marker back and forth over `markerPeriod` seconds and returns `{ success, timedOut, markerPosition, zone }` — `zone` itself can drift when `zoneDriftPerSecond` is set. It is pure: an `item.use` handler starts a session by recording `ctx.time.now()` (game-time, so pause/fast-forward apply for free) the first time it's pressed, and evaluates `evaluateSkillCheck` against the elapsed time on the next press to lock in success/fail — the session bookkeeping (a `Map<instanceId, startedAt>`) is game-owned, same pattern as an ability-cooldown map.
|
|
3344
|
+
|
|
3345
|
+
`@jgengine/core/interaction/qte` sequences discrete timed prompts: `evaluateQteSequence(steps: QteStep[], inputs: QteInputEvent[])` walks `{ id, action, windowStart, windowEnd }` steps against `{ action, at }` presses and returns `{ status: "success" }` or `{ status: "fail", atStep, reason }`; `pendingQteStep`/`qteProgress` read the currently-active step and fraction complete for UI.
|
|
3346
|
+
|
|
3347
|
+
`@jgengine/react` ships matching headless UI: `SkillCheckBar({ config, startedAt })` and `QteTrack({ steps, startedAt })` self-tick via `requestAnimationFrame` and read `ctx.time.now()` each frame — pass `className`/`trackClassName`/`zoneClassName`/`markerClassName` (or `stepClassName`/`activeClassName`/`doneClassName` for `QteTrack`) for the moving-zone/timing visuals the UI quality bar requires.
|
|
3348
|
+
|
|
3349
|
+
### Capture and owned roster
|
|
3350
|
+
|
|
3351
|
+
`@jgengine/core/scene/captureCheck` — `captureChance({ hpFraction, catchPower, difficulty? })` returns a 0..1 probability (lower `hpFraction` and higher `catchPower` raise it, higher `difficulty` lowers it); `rollCapture(input, rng?)` rolls it. `@jgengine/core/scene/roster` — `createRoster()` is a persisted, per-owner store (`capture`, `release`, `list`, `get`, `setEquipped`, `equippedList`, `snapshot`/`hydrate`) wired onto the runtime as `ctx.game.roster`, distinct from `game.social.party` (session-ephemeral) — roster entries persist and are optionally equipped (deployed) independent of party membership.
|
|
3352
|
+
|
|
3353
|
+
A capture item's `item.use` handler composes the primitives instead of forking them: read the wild target's hp via `ctx.scene.entity.stats.get(target, "health")`, roll `rollCapture({ hpFraction, catchPower })`, and on success call `ctx.scene.entity.despawn(target)` + `ctx.game.roster.capture(ownerId, catalogId)` — the wild scene entity is removed and re-parented into the owner's persisted roster; the react `CaptureOdds({ chance })` component shows the live odds meter the UI quality bar requires.
|
|
3354
|
+
|
|
3355
|
+
## Combat — effects, projectiles, death, feel, abilities
|
|
3356
|
+
|
|
3357
|
+
Combat primitives — effects & projectiles, death handling, melee/defense/telegraph feel, and abilities/resources/auto-target/resistance/run drafts. Full surface: **[reference/combat.md](reference/combat.md)**.
|
|
3358
|
+
|
|
3359
|
+
## Loot
|
|
3360
|
+
|
|
3361
|
+
```ts
|
|
3362
|
+
lootTable({ id, rolls?, entries: [{ item? | currency?, count: n | [min,max], weight }] })
|
|
3363
|
+
ctx.game.loot.register(table) // in onInit
|
|
3364
|
+
ctx.game.loot.has(id) / roll(id, rng?) / grantToPlayer(userId, drops, source?)
|
|
3365
|
+
```
|
|
3366
|
+
|
|
3367
|
+
Tables colocate with their domain (`entities/enemies/loot-tables.ts`, `objects/loot-tables.ts`). Entities reference them via `onDeath.drops`; chests via a `loot.open` command arg. `grantToPlayer` fills declared inventories, grants currencies, and emits `loot.granted`.
|
|
3368
|
+
|
|
3369
|
+
## Card, board & shaped-inventory primitives
|
|
3370
|
+
Pure, renderer-free structures for card, board, and deckbuilder games — they sit **beside** the slot inventory, not in place of it. All are immutable-reducer + thin-controller pairs, mirroring the two-tier ctx/factory model: use the `create*` controller in game code, reach for the exported pure functions (`draw`, `moveCards`, `tickTimeline`, `laneAggregate`, `runPipeline`, `placeShaped`) for unit tests and headless servers.
|
|
3371
|
+
```ts
|
|
3372
|
+
// cards/cardPile — named ordered zones (deck/hand/discard/exhaust); seeded shuffle, hand limit, reshuffle-on-empty
|
|
3373
|
+
const pile = ctx.game.cards.pile("deck", { zones: ["deck","hand","discard","exhaust"], drawFrom:"deck", handZone:"hand", discardTo:"discard", handLimit:7, reshuffleFrom:"discard" });
|
|
3374
|
+
pile.reset(createCardPileState(pileConfig, { deck: ids })); // seed zone contents once, from onInit
|
|
3375
|
+
pile.shuffle("deck", seed); // seeded Fisher–Yates via pileRng — deterministic under the same seed
|
|
3376
|
+
pile.draw(5); // deck → hand, clamped to handLimit, reshuffles discard when deck runs dry
|
|
3377
|
+
pile.discard(ids); pile.exhaust(ids, "exhaust"); // Slay the Spire / Balatro lifecycle
|
|
3378
|
+
// cards/modifierPipeline — ordered { source, apply(value) → value } with an inspectable per-step trace
|
|
3379
|
+
const score = runPipeline({ chips: 10, mult: 1 }, jokers); // score.value + score.trace[i].{before,after,changed} for Balatro-style scoring readouts
|
|
3380
|
+
// board/laneBoard — N lanes, per-side power aggregate + optional per-lane LaneRule modifier (Marvel Snap / Inscryption)
|
|
3381
|
+
board.aggregate(lane, "player").total; board.outcome(lane).winner; board.lanesWon();
|
|
3382
|
+
// board/timelineBoard — N slots each on an independent cooldown, resolving in expiry order (The Bazaar auto-battlers)
|
|
3383
|
+
board.tick(dtMs); // → fires[] sorted by expiry time then slot index; multiple fires per slot per tick
|
|
3384
|
+
// inventory/shapedGrid — polyomino footprints, rotate, overlap-check, adjacency (Backpack Hero / Tetris inventory)
|
|
3385
|
+
placeShaped(grid, { id, value, footprint }, [col,row], rotation); // rotateFootprint / canPlace guard overlap + bounds
|
|
3386
|
+
gridAdjacencyQuery(grid).neighborsOf(id); // feeds synergy effects
|
|
3387
|
+
```
|
|
3388
|
+
Reuse the engine's seeded RNG (`pileRng`) for anything random — never `Math.random()` in game logic. The React drag/rotate/drop/snap gesture layer over these lives in `@jgengine/react` (see UI section).
|
|
3389
|
+
|
|
3390
|
+
`ctx.game.cards.pile(id, config?)` is the runtime-wired accessor for `createCardPile`: lazily creates the pile on first call (`config` required then) or returns the existing one for `id` on every later call, and every mutation notifies `ctx.subscribe`/bumps `ctx.version()` — so a `useEngineState`-bound hand/discard view re-renders without a game-owned store. Reach for `createCardPile` directly only for a headless test or server; game code goes through `ctx.game.cards.pile`.
|
|
3391
|
+
|
|
3392
|
+
## Puzzle primitives — cell grids and falling pieces
|
|
3393
|
+
|
|
3394
|
+
Two pure, renderer-free `@jgengine/core` primitives for cell-based puzzle games (Tetris wells, match-3 boards); tile art and the drop-cadence loop are the shell's/game's job.
|
|
3395
|
+
|
|
3396
|
+
- **`puzzle/cellGrid`** — a generic immutable `CellGrid<T>` for uniform typed-cell boards. Row 0 is the top; `y` grows downward. `createCellGrid`, `cellAt`, `withCell`/`withCells` (immutable single/batch writes), `fullRows`/`clearRows` (line-clear + compaction), `collapseColumns` (match-3 cascade gravity), `findRuns` (run detection with an optional custom matcher).
|
|
3397
|
+
- **`puzzle/fallingPiece`** — the falling-piece layer over a `CellGrid`: `ShapeTable<TShape>` maps rotation states to cell offsets; `pieceCells`/`pieceCollides`/`mergePiece` place, test, and commit a piece; `dropDistance` computes the ghost-piece landing row; `gravityInterval`/`levelForLines`/`lineScore` are the classic Tetris drop-speed/level/score curves (overridable); `createLockDelay`/`stepLockDelay` is the grounded→countdown→lock stepper (`delaySeconds: 0` locks instantly on touchdown).
|
|
3398
|
+
- **`tactics/fallingGrid`** — a generic tile-drop grid over any `TCell` payload (distinct from the `cellGrid`/`fallingPiece` row-clear pair): `createFallingGrid(config)`, `gravityIntervalMs(level, config?)` for the drop-speed curve, and a `FallingGridSnapshot`/`LockState` shape for the grounded→lock stepper.
|
|
3399
|
+
|
|
3400
|
+
## Dropped items — `worldItem` and the loot filter
|
|
3401
|
+
A `worldItem` is a scene **entity** (position + item ref + rarity), never an inventory item or object — see the three buckets. `onDeath.dropMode: "world"` (above) is the usual producer; games can also hand-place ground loot (chests, quest drops).
|
|
3402
|
+
ctx.scene.worldItem.spawn({ itemId, position, rarity?, baseType?, count?, affixTier?, source? })
|
|
3403
|
+
ctx.scene.worldItem.get(instanceId) / list() / nearestInRadius(from, radius, filter?)
|
|
3404
|
+
ctx.scene.worldItem.pickup(instanceId, userId) // grants to inventory + despawns, emits worldItem.picked_up
|
|
3405
|
+
Click-to-grab is engine-owned: setting `pointer.grabWorldItems: true` in `defineGame({...})` makes `@jgengine/shell`'s `GamePlayerShell` resolve `pointer.worldHit()` on primary click, and — when the hit entity is a `worldItem` within the `worldItem.pickupRadius` (default `DEFAULT_PICKUP_RADIUS`) configured on `defineGame({...})` of the local player — calls `pickup` directly, no game command needed. `@jgengine/react`'s `useWorldItems()` / `useNearestWorldItem(radius)` drive a HUD pickup prompt off the same store.
|
|
3406
|
+
Presentation is a two-layer render binding, both engine-owned (rendered by `@jgengine/shell`'s `WorldItems`) over **game-supplied data**:
|
|
3407
|
+
1. **Rarity baseline** — the `worldItem.rarityStyle: Record<rarity, { color?, beam?, label? }>` field of `defineGame({...})`, the game's rarity palette (Borderlands/Diablo-style beam + color coding).
|
|
3408
|
+
2. **Loot filter overlay** (#33) — the `worldItem.filter: LootFilterRule[]` field of `defineGame({...})`, built with `lootFilter([{ id, when: { rarity?, baseType?, minAffixTier?, maxAffixTier? }, hide?, color?, beam?, label? }])` from `game/lootFilter`. **First matching rule wins** (PoE/Last Epoch block semantics); a rule only overrides the fields it sets, everything else falls back to the rarity baseline. `resolveWorldItemPresentation(item, rarityStyle, rules)` composes both layers and is what the shell calls per item.
|
|
3409
|
+
## Gear systems — durability, affixes, modular items, storage tiers
|
|
3410
|
+
Four pure primitives that hang off item **instances** (not the stackable catalog id) — all catalog-first (specs are game-supplied config) and renderer-free. Item instances that carry durability/affix/modular state key off a game-assigned instance id, the same way targeting keys off entity instance ids.
|
|
3411
|
+
**Durability** (`item/durability`) — per-instance wear + repair. `DurabilitySpec` (`{ max, wearPerUse?, wearPerHit?, disableAtZero?, repair? }`) is catalog data; `createDurability(spec)` seeds a `DurabilityState`, `wear(spec, state, "use" | "hit", times?)` decrements (floors at 0), `isDisabled(spec, state)` gates use at zero, `durabilityFraction` feeds a HUD bar. Repair is quote-then-apply: `repairQuote(spec, state, { station?, to? })` returns the `{ item, count }[]` material cost (scaled by points restored) + the post-repair state (optional `qualityLossPerRepair` shrinks `max` each repair, Tarkov-style) — the game charges the materials through inventory, then commits the quote's `state`. `createDurabilityTracker()` keeps `DurabilityState` per instance id for the runtime.
|
|
3412
|
+
**Affix roller** (`item/affix`) — procgen `base × rarity → { rolled affixes, computed stats, name }`. `createAffixRoller({ pools, rarities })` over rarity-weighted `AffixPool`s. `roll(base, rarityId, rng)` draws `affixCount` distinct affixes without replacement (weighted, via the engine's `pickWeighted`), computes stats (base × `rarity.statScale`, then `op: "add"` affixes, then `op: "mul"`), and composes a name from `rarity.namePart` + prefix/suffix parts. `rollRarity(rng)` picks a weighted tier; `rollRandom(base, rng)` chains both. Pass `seededRng(seed)` for deterministic drops; any `() => number` rng works (same contract as `loot.roll`). `seededRng` lives in `random/rng` (re-exported here) alongside `seededStreams(seed)`, which derives independent named streams from one seed — `streams("worldgen")` vs `streams("history")` — so simulation draws never perturb generation (intervening in a run cannot change the map).
|
|
3413
|
+
**Modular item** (`item/modularItem`) — a whole assembled from parts in typed mount slots (guns, mechs). `ModularItemDef` has `slots: MountSlotDef[]` (`{ id, accepts, required? }`); `install(def, installed, slotId, part)` validates the slot exists, accepts the part's `category`, and is empty; `computeEffectiveStats(def, installed)` rolls part `stats` (additive) then `multipliers` over `baseStats`; `missingRequiredSlots`/`isComplete` gate a buildable whole. `createModularItem(def)` is the stateful wrapper (`install`/`uninstall`/`effectiveStats`/`partInSlot`).
|
|
3414
|
+
**Storage tiers + insurance** (`inventory/storageTier`) — the extraction-economy inventory half. Inventory containers carry a `tier: "carried" | "banked"` (`InventoryDeclaration.tier`; a Tarkov secure container is just a `banked` container on the body). `partitionOnDeath(containers)` splits a death snapshot into `{ kept, lost }` (banked survives, carried is dropped, stacks merged). `createDeliveryQueue()` is the delayed-delivery (insurance) hook: `schedule` a `ScheduledDelivery` with a game-time `deliverAt`, then `due(now)` / `claimDue(now)` drain it on the tick clock. `insureLost(lost, policy, userId, now, rng?)` filters the lost set to insured items and stamps a delayed `deliverAt` → feed straight into the queue. `resolveConsolation(policy, partition)` returns a baseline loadout id (apply via `applyLoadout`) — the death consolation grant, optionally gated on `if-carried-empty`. *(Session/round machines — extraction hold-to-leave, raid banking — consume this tier; see the objective-machine group.)*
|
|
3415
|
+
## Objective, round & session machines
|
|
3416
|
+
Content-agnostic state machines for competitive/session shapes — plant/defuse, buy/live/end rounds, downed/revive, the battle-royale ring, extraction raids, run-vs-meta persistence. All pure `core`; every timer takes a **game-time** `dt`/`now` (`ctx.time`), so pause and fast-forward apply for free. Drive them from `loop.onTick` and pipe their events into `ctx.game.feed`/`events`; render their snapshots as HUD (per the UI quality bar in [`reference/ui-react.md`](reference/ui-react.md) — the downed banner, ring warning, and extraction timer are required HUD).
|
|
3417
|
+
**Contested channel** (`session/contestedChannel`) — the interrupt-on-damage progress objective behind plant/defuse, cash-out, urn deposit, banishing, and hold-to-extract. `createContestedChannel({ duration, interruptOnDamage?, resetOnInterrupt?, favorability?, ratePerOccupant?, contested?, decayRate? })`: `start(team)` begins the channel, `tick(dt, occupants)` advances it against per-team occupancy (`Record<teamId, count>`) and emits `start`/`tick`/`contested`/`paused`/`complete` events, `damage(reason?)` interrupts (keeps or zeroes progress per `resetOnInterrupt`). `favorability[team]` scales fill rate (Deadlock deposit); `ratePerOccupant` fills faster with more owners present; `contested: "pause" | "decay"` chooses whether an opposing occupant freezes or reverses progress (The Finals contest). The owner leaving pauses it. Extraction hold-to-leave reuses this primitive verbatim.
|
|
3418
|
+
**Round state** (`session/roundState`) — the buy→live→end match machine (Valorant/CS). `createRoundState({ phases, teams, phaseOrder?, winCondition?, maxRounds?, winReward?, lossBonus? })`: `tick(dt)` runs the phase timer and auto-advances (emitting `phase.start`/`phase.end`, rolling the last phase back into the next round's first), `concludeRound(winner)` records the win on any "conclude-eligible" phase (any phase but the first/last in the cycle), settles `round.economy` (winner gets `winReward`, losers get an escalating `lossBonus` via `lossBonusFor(rule, streak)` clamped to `max`), and moves to the next phase. `onPhaseEnd(hook)` fires commerce/spawn gates on each transition; `match.end` fires at `maxRounds`. `server.mode` stays a game string — this is the timer/economy engine under it.
|
|
3419
|
+
|
|
3420
|
+
Two extras beyond the default buy/live/end cycle: `phaseOrder?: string[]` overrides the phase names/cycle entirely (a wider `Record<string, number>` `phases` shape to match) — a draft→ban→play→score cycle is the same machine with different phase names. `teams: (string | { id, role? })[]` accepts a plain id or a `{ id, role }` pair; `roleOf(team)` reads the tag back (`"attacker"`/`"defender"`, Valorant side assignment) without a parallel lookup table. `winCondition?: (snapshot: RoundSnapshot) => string | null` lets `evaluate()` (call it from `onTick` alongside `tick(dt)`) auto-conclude the round the instant a score/objective condition is met, instead of the game hand-calling `concludeRound` — return a team id to end it, `null` to keep playing; `RoundSnapshot` is `{ round, phase, timeLeft, scores, lossStreaks, roles, matchOver }`.
|
|
3421
|
+
**Role assignment** (`session/roles`) — `assignRoles(players, specs: RoleSpec[])` distributes fixed-count or proportional roles (hider/seeker, spy/operative, prop/hunter) across a player list — the allocation half of an asymmetric session mode; `RoundConfig.teams`' per-team `role` is the lighter-weight alternative when a round machine already tracks the roster.
|
|
3422
|
+
**Downed / revive** (`combat/downed`) — the 3-state alive→downed→dead chain (Apex/Helldivers). `createDownedState({ bleedoutSeconds, reviveSeconds?, reviveHealthFraction?, banner? })`: `down(id)` starts the bleedout, `tick(dt)` counts it down (→ `died`, optionally spawning a `banner`), `revive(id, dt)` accumulates an ally's hold time (→ `revived` with the health fraction the game restores), `finish(id)` executes a downed enemy, and `respawnFromBanner(id)` brings a banner-holder back at a beacon. It sits **in front of** the engine death resolution: on lethal damage call `down` instead of dying; on `died`/`bleedout` run the real `resolveDeath`. No banner ⇒ death is terminal.
|
|
3423
|
+
**Shrinking ring** (`session/ring`) — the battle-royale safe zone with out-of-bounds DoT. A catalog `RingConfig` is `{ center, phases: RingPhase[] }` where each phase is `{ startTime, shrinkDuration, fromRadius, toRadius, damagePerSecond, center? }` on the game clock. `ringSampleAt(config, t)` / `createRing(config).at(t)` returns the live `{ center, radius, damagePerSecond, shrinking }` (radius/center interpolate during each shrink window, hold between phases); `isOutside(t, pos)` / `distanceOutside(t, pos)` test a point, and `damageOutside(t, dt, positions)` returns per-entity `{ id, damage }` for everyone beyond the wall — feed those into `scene.entity.stats.delta`/`effect` each tick.
|
|
3424
|
+
**Extraction session** (`session/extraction`) — the raid-scoped "reach an extract and leave to bank what you carried" wrapper (Tarkov/DMZ/Helldivers), composed from the contested channel + `inventory/storageTier`. `createRaidSession({ extracts, insurance?, consolation? })`: `beginExtract(userId, extractId, team?)` opens a hold-to-leave channel, `tickExtract`/`damage` drive it, and on completion `resolveExtraction(userId, containers)` banks everything carried. `resolveDeath(userId, containers, now, rng?)` runs `partitionOnDeath` (banked kept, carried lost), schedules insured items through the built-in delivery queue (`claimDeliveries(now)` drains it on the clock), and yields the consolation loadout id. `playerSnapshot(userId)` feeds the extraction-timer HUD.
|
|
3425
|
+
**Persistence scopes** (`runtime/persistenceScope`) — the run-vs-meta split with explicit reset boundaries (Icarus mission wipe, Once Human season reset). `partitionScopes(state, { run })` splits a flat record into `{ meta, run }` by key; `resetRun` clears the run half while meta (talents/blueprints/account currency) survives; `clearRunFields(playerRow, runFields)` and `applyRunReset(profile, runFields, now)` do the same over `RuntimePlayerRow`/`PlayerProfileRecord`. `planScenarioReset({ gameId, serverId?, wipeChunks?, wipeServerSession?, resetPlayers?, runFields? })` normalizes a scenario/season reset that `HostPersistence.resetScenario?(reset)` applies — `@jgengine/sql` implements it (deletes the server's chunks + session, run-resets each profile in one transaction), keeping account meta intact.
|
|
3426
|
+
|
|
3427
|
+
## Trade
|
|
3428
|
+
|
|
3429
|
+
Catalog `trade` fields drive everything — no duplicate price lists.
|
|
3430
|
+
|
|
3431
|
+
```ts
|
|
3432
|
+
ctx.game.trade.canBuy(itemId, shopId, count?) // → reason | null
|
|
3433
|
+
ctx.game.trade.canSell(itemId, count?)
|
|
3434
|
+
ctx.game.trade.buy(itemId, count, { shop, inventoryId }) // charge → put, rolls back on failure
|
|
3435
|
+
ctx.game.trade.sell(itemId, count, { shop, inventoryId })
|
|
3436
|
+
ctx.game.trade.tradableAt(shopId, allItemIds) // derive stock from catalogs
|
|
3437
|
+
```
|
|
3438
|
+
|
|
3439
|
+
## Economy and unlocks
|
|
3440
|
+
|
|
3441
|
+
```ts
|
|
3442
|
+
ctx.game.economy.balance(userId, currencyId) / grant(...) / charge(...) // charge → { reason } | null
|
|
3443
|
+
ctx.game.unlocks.has(userId, id) / grant(userId, id) / list(userId) / tree(categoryId)
|
|
3444
|
+
```
|
|
3445
|
+
|
|
3446
|
+
Catalog `requires: [unlockId]` gates validate at command time.
|
|
3447
|
+
|
|
3448
|
+
## Crafting, tech tree & production
|
|
3449
|
+
|
|
3450
|
+
Four **pure** primitives (no ctx, no renderer) for survival-crafting, tech-tree, factory, and farming games. All are catalog-first: recipes, tech nodes, production rates, and crop stages are game **data** you feed the primitive — the engine owns the graph math, the timers ride `ctx.time` (game-seconds), never wall-clock.
|
|
3451
|
+
|
|
3452
|
+
**Recipe graph** — `@jgengine/core/crafting/recipe`. A `RecipeDef` is `{ id, inputs: RecipeItem[], outputs: RecipeItem[], seconds?, station?, stationRange?, requires? }` — inputs + optional required-workstation-in-range + time → outputs. `craft(state, layout, traits, recipe, context)` consumes inputs and produces outputs on an `InventoryState` **atomically** (rejects `missing-inputs` / `no-station` / `locked` / `no-output-space` without mutating on failure); `canCraft(...)` is the dry-run. `context = { origin?, stations?, unlocked? }`: `stationSatisfied` checks a matching placed workstation (`{ catalogId, position }`) within `stationRange` of `origin`, and `requires` gates on `unlocked(id)` (wire it to `ctx.game.unlocks.has` or the tech tree). `createRecipeGraph(defs)` indexes recipes by `producing(itemId)` / `using(itemId)` / `category`. Long crafts schedule completion with `ctx.time.after(craftSeconds(recipe), …)`.
|
|
3453
|
+
|
|
3454
|
+
**Tech tree** — `@jgengine/core/economy/techTree`. **Generalizes flat `unlocks`, does not duplicate it**: a `TechNodeDef extends UnlockDef` adds `requires` (prerequisite node/unlock ids), an optional `recipe` payload, and `grants` (extra flat unlock ids). A node id **is** an unlock id, so flat unlocks are just tech nodes with no `requires`. `createTechTree(defs)` wraps `createUnlocks` internally and gates grants on prerequisites: `unlock(userId, id)` refuses until every `requires` is met, `available(userId)` is the reachable frontier, `recipes(userId)` lists the recipe payloads a player has unlocked (feed them to the recipe graph). `tree(categoryId)` and per-user `has`/`list`/`snapshot`/`hydrate` mirror `unlocks`.
|
|
3455
|
+
|
|
3456
|
+
**Production building** — `@jgengine/core/crafting/production`. `productionBuilding({ id, inputs, outputs, rate, power?, bufferMultiplier? })` — a placed building that consumes buffered inputs and emits outputs on a timer. `rate` is production **cycles per game-second**; `tickProduction(def, state, { dt, powered? })` advances continuously through `dt` (so pause/fast-forward apply for free) and completes as many cycles as the buffer allows. `feedProduction` / `drainOutput` move items in and out of the internal buffers (a puller/conveyor). `advanceTransport(path, items, dt)` slides items along a belt and splits off `delivered`. `resolvePowerGrid(supply, consumers)` powers demands greedily until supply is exhausted — gate a building's tick on `powered`.
|
|
3457
|
+
|
|
3458
|
+
**Farming** — `@jgengine/core/crafting/crop`. `CropTileState` is a soil state machine (`untilled` → `tilled` → planted); `tillTile` / `plantCrop` / `waterTile` are pure tile transitions and `advanceCropDay(def, tile)` runs the **day tick** — a `CropDef { stages, regrowDays?, needsDailyWater?, harvest? }` advances a growth stage per watered day and sets `harvestable`; `harvestCrop` yields and either clears the tile or resets a regrow crop. `applyToolToTiles(tiles, center, pattern, apply)` applies a tool across a tile pattern under the cursor — `singleTile()`, `squarePattern(r)`, `diamondPattern(r)`, `rectPattern(w,d)` (watering-can / hoe AoE). `createCropField(catalog)` is the stateful wrapper over a tile grid (`till`/`plant`/`water`/`harvest`/`advanceDay`); drive `advanceDay()` off the calendar day rolling over — `createDayTicker(startDay)` reports how many days `ctx.time.calendar().day` has crossed.
|
|
3459
|
+
|
|
3460
|
+
## `applyLoadout`
|
|
3461
|
+
|
|
3462
|
+
```ts
|
|
3463
|
+
ctx.player.loadout.register(loadouts) // onInit
|
|
3464
|
+
ctx.player.applyLoadout(userId, loadoutId) // → null | { reason }
|
|
3465
|
+
```
|
|
3466
|
+
|
|
3467
|
+
`LoadoutDef = { inventories?: { hotbar: [{ item, count, slot? }], … }, stats?, economy?, unlocks? }`. Application is **all-or-nothing**: every inventory put dry-runs first; any rejection applies nothing. Starter kits gate on `ctx.player.isNew`; class/respawn kits run from commands. Never scatter raw `put`/`grant` calls for a kit.
|
|
3468
|
+
|
|
3469
|
+
## Quests
|
|
3470
|
+
|
|
3471
|
+
```ts
|
|
3472
|
+
ctx.game.quest.register(catalog) // onInit
|
|
3473
|
+
canAccept / accept / abandon / canTurnIn / turnIn / grant / revoke
|
|
3474
|
+
progress(userId, questId, objectiveId, delta)
|
|
3475
|
+
list(userId) / has(questId)
|
|
3476
|
+
bind("entity.died") // kill objectives match objective.target === catalogId
|
|
3477
|
+
bind("inventory.added") // collect objectives match objective.item
|
|
3478
|
+
```
|
|
3479
|
+
|
|
3480
|
+
Catalog: `{ id, title, giver?, turnIn?, requires?, objectives: [{ id, kind, target?/item?, count, partyShare? }], rewards? }`. `requires` is satisfied by a completed quest of that id or an unlock. `turnIn` applies declarative `QuestRewards` — `{ xp?: { amount }, economy?: Record<string, number>, items?: { item, count, inventory }[], unlocks?: string[], quests?: string[] }` — note `xp` takes an `{ amount }` wrapper (applied via `stats.delta` + your level-up loop) and each reward `item` names the `inventory` it fills; chained `quests` are auto-offered if acceptable. Events: `quest.accepted` / `quest.updated` / `quest.completed`. `partyShare: { radius, credit: "all" | "tagger" }` extends kill credit to nearby party members.
|
|
3481
|
+
|
|
3482
|
+
## Social
|
|
3483
|
+
|
|
3484
|
+
```ts
|
|
3485
|
+
ctx.game.social.friends.canRequest / request / accept / decline / remove / block / list / requestsFor // persisted
|
|
3486
|
+
ctx.game.social.party.register({ maxMembers }) // then canInvite / invite / accept / decline / kick / leave / promote / list / membersOf / invitesFor
|
|
3487
|
+
ctx.game.social.presence.get(userId) // { online, serverId?, zoneId?, instanceId? }
|
|
3488
|
+
ctx.game.social.emotes.play(fromUserId, emoteId, radius?) // → { from, emoteId, at, recipients } | { reason }
|
|
3489
|
+
ctx.game.social.worldInvites.invite(fromUserId, toUserId, { serverId, joinCode? }) // then canInvite / accept / decline / listFor
|
|
3490
|
+
```
|
|
3491
|
+
|
|
3492
|
+
Party is ephemeral session state (invites expire; leader leaving promotes the next member). Events: `social.friend.added`, `social.party.joined`, `social.party.left`.
|
|
3493
|
+
|
|
3494
|
+
**World invites** bridge friends and `multiplayer/matchmaking`: an invite carries the `{ serverId, joinCode? }` of the session you're in (the same fields as a `SessionListing`); `accept(userId, inviteId)` → `{ target }` is the join target you hand to your backend's `joinServer`/`joinByCode` — the invite never joins anything itself. Invites are ephemeral like party invites (TTL via `SocialDeps.worldInviteTtlMs`, default 60s; blocked users can't invite either direction). Events: `social.world.invited`, `social.world.accepted`. React: `useWorldInvites()` lists pending invites for the local player.
|
|
3495
|
+
|
|
3496
|
+
`emotes.play` reuses `scene.entity.inRadius` to find nearby **player**-role entities (default radius 20) and emits `emote.played` — never build a parallel proximity broadcast. Emote ids are game-defined strings (no registration, same convention as effect ids). Bind it into the existing feed primitive for a HUD feed: `ctx.game.feed.bind("emote.played")` + `useFeed({ action: "emote.played" })` — no dedicated emote hook exists or is needed.
|
|
3497
|
+
|
|
3498
|
+
## Chat
|
|
3499
|
+
|
|
3500
|
+
```ts
|
|
3501
|
+
ctx.game.chat.send(fromUserId, channelId, body) // → { message, recipients } | { reason }
|
|
3502
|
+
ctx.game.chat.whisper(fromUserId, toUserId, body) // stable per-pair channel "whisper:<a>:<b>"
|
|
3503
|
+
ctx.game.chat.history(channelId, { limit?, viewerUserId? }) // viewer filter drops blocked senders
|
|
3504
|
+
ctx.game.chat.register({ id, kind, radius?, historyLimit?, rateLimit? }) // custom channels
|
|
3505
|
+
ctx.game.chat.channels() / snapshot() / hydrate(data)
|
|
3506
|
+
```
|
|
3507
|
+
|
|
3508
|
+
Built-in channels: `global` (everyone), `party` (reuses `social.party.membersOf`; rejects "not in a party"), `proximity` (reuses the same spatial/entity seam as emotes, default radius 20, **player**-role entities only). `kind` picks the recipient resolution; custom channels pick one of the three kinds. Sends are trimmed, capped (500 chars), and rate-limited per user per channel (default 10/10s, sliding window — `createChatRateLimiter` is the reusable pure primitive). Mute rides social's blocked set: blocked pairs can't whisper, and blocked senders are dropped from party/proximity recipients and from `history` when `viewerUserId` is passed. Every send emits `chat.message` (recipients omitted = broadcast). History is a bounded ring per channel (default 100) with `snapshot`/`hydrate` like `Friends`.
|
|
3509
|
+
|
|
3510
|
+
**Remote chat seam** (`multiplayer/chatContract`): `ChatTransport` is the hook-shaped contract (`useMessages(channelId | "skip")` / `useActions()`, identity-stable like `PresenceTransport`); `ChatSync` is the callback shape for backends that can't host React hooks. Bindings: ws — `createWsBackend(...).chatSync` / `.chatSyncFor(serverId)` over `chatSend` frames + a `chat` update channel (host relays per-channel rings, validates length + rate limit); Convex — `@jgengine/convex/convexChatTransport` `createConvexChatTransport({ messages, sendMessage })` (one live query + one mutation); local/dev — `createLocalChatTransport()`. React lifts a `ChatSync` via `chatTransportFromSync`.
|
|
3511
|
+
|
|
3512
|
+
## Cosmetic loadout
|
|
3513
|
+
|
|
3514
|
+
```ts
|
|
3515
|
+
ctx.player.cosmetics.register(defs) // onInit — Record<loadoutId, { slots: Record<slot, cosmeticId> }>
|
|
3516
|
+
ctx.player.cosmetics.apply(userId, loadoutId) // merges the preset's slots
|
|
3517
|
+
ctx.player.cosmetics.equip(userId, slot, cosmeticId | null) // set/clear one slot directly
|
|
3518
|
+
ctx.player.cosmetics.get(userId) // Record<slot, cosmeticId>
|
|
3519
|
+
```
|
|
3520
|
+
|
|
3521
|
+
A per-player appearance layer distinct from `applyLoadout` (which grants inventory/stats/economy/unlocks) — cosmetics never touch gameplay state, only equipped slot ids for your renderer to read. Emits `cosmetics.changed`.
|
|
3522
|
+
|
|
3523
|
+
## Possession
|
|
3524
|
+
|
|
3525
|
+
```ts
|
|
3526
|
+
ctx.player.possession.own(userId, entityId) / disown / owns / listOwned(userId)
|
|
3527
|
+
ctx.player.possession.active(userId) // → entityId, defaults to userId itself
|
|
3528
|
+
ctx.player.possession.possess(userId, entityId) // → null | { reason } — must be owned + spawned
|
|
3529
|
+
```
|
|
3530
|
+
|
|
3531
|
+
A player can own N scene entities (party members, vehicles, a possessed creature) and control exactly one at a time — distinct from `game.social.party`, which is a social grouping, not a control model. `possess` flips the previous/next entity's scene `EntityRole` between `"player"`/`"npc"` and emits `possession.swapped`; `@jgengine/shell`'s `GamePlayerShell` reads `active(userId)` every frame to rebind WASD movement, tab-targeting, hotbar `from`, and the camera rig's `followEntityId` to whichever entity is currently controlled — a game never wires this rebind itself.
|
|
3532
|
+
|
|
3533
|
+
## Form / shapeshift
|
|
3534
|
+
|
|
3535
|
+
```ts
|
|
3536
|
+
ctx.scene.entity.form.register(defs) // onInit — FormDef[] = { id, movement?, abilities?, model? }
|
|
3537
|
+
ctx.scene.entity.form.shapeshift(instanceId, formId, durationSeconds?) // → null | { reason }
|
|
3538
|
+
ctx.scene.entity.form.active(instanceId) // → formId | null
|
|
3539
|
+
ctx.scene.entity.form.abilities(instanceId) // → readonly string[] | null
|
|
3540
|
+
ctx.scene.entity.form.revert(instanceId) // early revert
|
|
3541
|
+
```
|
|
3542
|
+
|
|
3543
|
+
A `form` bundles movement params + an ability-id list + a mesh into one swappable unit (shapeshift/transformation — V Rising bear/wolf/bat, Wukong's boss transformation). `model` reuses the entity's catalog `name` (the same key `entityModels`/`entitySprites` resolve against), so the mesh swap rides the existing render lookup — no parallel mesh field. `durationSeconds` is **game time**: it schedules the automatic revert through `ctx.time.after`, so it obeys pause and fast-forward like everything else on the clock. Emits `form.changed`.
|
|
3544
|
+
|
|
3545
|
+
## Events, feed, leaderboard
|
|
3546
|
+
|
|
3547
|
+
```ts
|
|
3548
|
+
ctx.game.events.on(name, handler) // register in onInit; typed GameEventMap
|
|
3549
|
+
ctx.game.feed.bind(action) // pipe an engine event into a ring buffer (default 20)
|
|
3550
|
+
ctx.game.feed.push(action, entry) // manual channels (chat, crafting)
|
|
3551
|
+
ctx.game.feed.recent(action, { limit? })
|
|
3552
|
+
ctx.game.leaderboard.track({ stat, scope: "global" | "server" | "profile" }) // onInit
|
|
3553
|
+
ctx.game.leaderboard.increment(userId, stat, { scope, by? }) / getTop / getProfile
|
|
3554
|
+
```
|
|
3555
|
+
|
|
3556
|
+
`ctx.game.commands.define(name, { validate?(ctx, input), apply(ctx, input) })` registers a verb (`has`/`names`/`run` round it out); `run(name, input)` returns `{ status: "applied", state } | { status: "rejected", reason } | { status: "unknown-command" }`. `apply` may either **return** the next state (the classic reducer shape) or mutate `ctx` in place and return **nothing** — `run` keeps the current `ctx` as `state` when `apply` returns `void`, so a handler that only calls other `ctx` methods (spawn, effect, loot.grantToPlayer, …) doesn't need a pointless `return ctx`. **Event handlers use `ctx` directly** the same way (side effects: leaderboard, economy, scheduling) and never reassign state. One feed primitive for kill feeds, loot logs, quest updates — no per-domain feed hooks.
|
|
3557
|
+
|
|
3558
|
+
## `ctx.game.store` — reactive game state
|
|
3559
|
+
|
|
3560
|
+
```ts
|
|
3561
|
+
ctx.game.store.set("health", 100) // any key, any value type
|
|
3562
|
+
ctx.game.store.get("health") // T | undefined
|
|
3563
|
+
ctx.game.store.has("health")
|
|
3564
|
+
ctx.game.store.delete("health")
|
|
3565
|
+
ctx.game.store.subscribe(listener) // change-signal fires on set/delete
|
|
3566
|
+
ctx.game.store.mapSnapshot() / arraySnapshot()
|
|
3567
|
+
```
|
|
3568
|
+
|
|
3569
|
+
A reactive per-game keyed store (`ObservableKeyedStore<unknown>`) attached to `GameContext` — reach for it instead of a module-level singleton store for ad-hoc reactive game state (turn trackers, deck UIs, anything that doesn't already have a `ctx` surface). `set`/`delete` bump `ctx.version()` and notify `ctx.subscribe` listeners; `get`/`has` are plain reads. Unlike a per-slot handle, there is no `define`/seed step — a key simply doesn't exist until the first `set`.
|
|
3570
|
+
|
|
3571
|
+
## `ctx.game.cards` / `ctx.game.turn` — lazily-created piles and turn loops
|
|
3572
|
+
|
|
3573
|
+
`ctx.game.cards.pile(id, config?)` and `ctx.game.turn.loop(id, config?)` lazily create (config required on first call) or return the existing notify-wrapped `CardPile`/`TurnLoop` for `id` — call with just the id after the first `onInit` seed to fetch the same instance; every mutating method is wrapped so it bumps `ctx.version()`/notifies `ctx.subscribe` automatically, same as every other `ctx` surface. This replaces manually constructing `createCardPile`/`createTurnLoop` and wiring notification yourself.
|
|
3574
|
+
|
|
3575
|
+
## Movement, pose, input
|
|
3576
|
+
|
|
3577
|
+
```ts
|
|
3578
|
+
ctx.player.movement.getPose(id) / setPose(id, "crouch") // validates catalog movement.poses
|
|
3579
|
+
ctx.player.movement.getAim(id) / setAim(id, "ads") // ADS = aim state + zoom modifier, not a pose
|
|
3580
|
+
```
|
|
3581
|
+
|
|
3582
|
+
Poses (`standing/crouch/prone/running`) change the collision capsule (`POSE_HITBOX`); aim pairs with a `player.stats` zoom modifier on `"reticle"`. Game code reads action names only (`isDown("aim")`, `wasPressed("interact")`) — hold vs toggle is resolved by the binding config, never by raw key branches.
|
|
3583
|
+
|
|
3584
|
+
### `ctx.input` — polling the raw controls
|
|
3585
|
+
|
|
3586
|
+
`ctx.input` (`@jgengine/core/runtime/inputSnapshot`) is a per-frame held-action snapshot for `onTick` to poll, distinct from the command-dispatch path (bound actions still run commands the normal way): `publish(held: readonly string[])` (the shell calls this once per frame before `onTick`), `isDown(action)`, `held()` for the full list. Publishing never bumps `ctx.version()` — it's a poll surface, not reactive state.
|
|
3587
|
+
|
|
3588
|
+
**`repeatMs`** — bind an action as `{ hold: [...], repeatMs: 150 }` (`input/actionBindings`) and the shell fires its command on the down edge, then again every `repeatMs` while held, resetting on release (hotbar-style repeat-fire without a per-game timer).
|
|
3589
|
+
|
|
3590
|
+
**Aim on generic commands** — every command resolved from a bound action now runs with `{ yaw, pitch, aim: { yaw, pitch } }` in its payload, so a handler can read the camera-relative aim without going through `pointer.worldHit()`.
|
|
3591
|
+
|
|
3592
|
+
### Controller kinematics — movement config, physics tuning, respawn
|
|
3593
|
+
|
|
3594
|
+
`PlayableGame.movement` (`PlayerMovementConfig`) tunes the shell's built-in walk controller (never touch `PhysicsWorld` for ordinary player movement):
|
|
3595
|
+
|
|
3596
|
+
```ts
|
|
3597
|
+
movement: {
|
|
3598
|
+
mode?: "free" | "axis" | "grid"; // "free" (default) camera-relative; "axis" locks travel to one world axis; "grid" snaps each committed position to cell centers
|
|
3599
|
+
axis?: "x" | "z"; // world axis for mode "axis". Default "x"
|
|
3600
|
+
cellSize?: number; // cell size for mode "grid". Default 1
|
|
3601
|
+
collideObjects?: boolean; // collide the walking player against placed scene objects (unit-box AABBs) even without collision.voxel
|
|
3602
|
+
beforeCommit?: (frame: MovementCommitFrame) => readonly [number, number, number] | undefined | void; // intercepts each frame's resolved position before the pose commits; return a replacement to constrain/redirect the step
|
|
3603
|
+
}
|
|
3604
|
+
```
|
|
3605
|
+
|
|
3606
|
+
`nav/navConstrain`'s `constrainToNavGrid(grid, { y? })` is a standalone walkable-pass-through + wall-sliding helper over a `nav/navGrid`; its `(proposed, entity)` shape doesn't match `beforeCommit`'s `(frame) => [x,y,z]` signature directly, so wire it in with a small adapter closure rather than passing it straight through.
|
|
3607
|
+
|
|
3608
|
+
`defineGame({ physics: { gravity, jumpVelocity } })` drives the kinematics controller directly: `gravity` (signed, e.g. `-24`) and `jumpVelocity` override the built-in tuning; omit either to keep the defaults. This is the one global exception to "never player tuning in `defineGame`" — it configures the shared controller, not a catalog entry. It is still **distinct** from `physics/physicsWorld`'s standalone rigid-body sim (see below) — `defineGame.physics` never touches that sim.
|
|
3609
|
+
|
|
3610
|
+
**Vertical motion intents** — `ctx.player.motion` (`@jgengine/core/runtime/motionIntents`): `impulse(vy)` adds to the vertical velocity the shell's controller is about to integrate, `setVerticalVelocity(vy)` replaces it outright, `setY(y)` wins over physics for that frame. The shell calls `takePending()` once per frame, before integrating gravity, to drain what accumulated; this is not reactive state (jump pads, launch abilities, bounce pads).
|
|
3611
|
+
|
|
3612
|
+
**`collision: { voxel: true }` — object lattice as solids.** When set, the shell's local player uses a voxel body whose `isSolid(x,y,z)` is rebuilt from `ctx.scene.object.list()` as exact `` `${x},${y},${z}` `` keys (integer cell queries). Integer-placed objects are walkable/blocking; fractional-coord objects decorate without colliding. Removing an object opens a real trapdoor under gravity — do not fake the fall with `setPose`. `visual.scale` does not shrink the collider (always a unit cell). The voxel body is created once; prefer `motion.setY` / `impulse` for vertical relocation — full XY teleport of the local voxel body is not supported. Solid cache rebuilds when object **count** changes. Recipe: `jgengine-newgame` → voxel trapdoor board.
|
|
3613
|
+
|
|
3614
|
+
**Respawn** — `ctx.scene.entity.spawnPoseOf(id)` reads the spawn pose, `resetToSpawn(id)` teleports back to it with zero velocity, `resetAllToSpawn(filter?)` does it for every entity matching an optional filter and returns the count (round resets, out-of-bounds recovery).
|
|
3615
|
+
|
|
3616
|
+
## Touch & mobile
|
|
3617
|
+
|
|
3618
|
+
Every game is touch-playable with zero per-game input code. On a coarse-pointer device the shell derives a `TouchScheme` from the game's `input` bindings (`deriveTouchScheme`, `@jgengine/core/input/touchScheme`): a virtual joystick binds whichever of `moveForward`/`moveBack`/`moveLeft`/`moveRight` (or `turnLeft`/`turnRight`) are bound, on-screen buttons cover the remaining actions, and drag-to-look mounts automatically for `first`-person camera rigs. Touch controls feed synthetic `touch:<action>` codes into the same `ActionStateTracker` the keyboard uses — game code reads `isDown`/`wasPressed` and never branches on input source.
|
|
3619
|
+
|
|
3620
|
+
Refine the derived scheme with the `touch` field of `defineGame({...})` (`TouchControlsConfig`, all optional):
|
|
3621
|
+
|
|
3622
|
+
```ts
|
|
3623
|
+
touch: {
|
|
3624
|
+
gestures: {
|
|
3625
|
+
tap: "rotateCw",
|
|
3626
|
+
swipeUp: "hold",
|
|
3627
|
+
swipeDown: "hardDrop",
|
|
3628
|
+
drag: { left: "shiftLeft", right: "shiftRight" },
|
|
3629
|
+
},
|
|
3630
|
+
buttons: [
|
|
3631
|
+
{ action: "rotateCcw", label: "CCW" },
|
|
3632
|
+
{ action: "softDrop", label: "Soft" },
|
|
3633
|
+
],
|
|
3634
|
+
},
|
|
3635
|
+
```
|
|
3636
|
+
|
|
3637
|
+
- **`gestures`** — bind `tap` / `swipeUp` / `swipeDown` / `swipeLeft` / `swipeRight` / `drag` (`{ left?, right?, up?, down?, stepPx? }`, repeats its action every `stepPx` of travel) on the play surface. An action consumed by a gesture is removed from the derived button set.
|
|
3638
|
+
- **`buttons`** — curate the on-screen cluster (order preserved; bare string or `{ action, label?, icon? }`); omit to auto-derive one button per remaining bound action. Buttons render a glyph, not text: `iconForAction` (`@jgengine/react/gameIcons`) resolves the action name to a `GameIconName` (`jump`, `sprint`, `rotateCw`, `hardDrop`, `swap`, `hand`, `restart`, arrows, …), the `label` becomes the `aria-label`; set `icon: "<GameIconName>"` to pick one explicitly or `icon: false` to force the text label.
|
|
3639
|
+
- **`hidden`** — actions to drop from the derived buttons without gesture-binding them.
|
|
3640
|
+
- **`movement: false`** — suppress the virtual joystick even when movement actions are bound.
|
|
3641
|
+
- **`look` / `lookSensitivity`** — drag-to-look on the play surface; defaults to `true` for `first`-person camera rigs, `0.005` radians/px.
|
|
3642
|
+
- **`touch: false`** — opt out entirely when the game's own DOM UI is already touch-native.
|
|
3643
|
+
|
|
3644
|
+
`useDisplayProfile()` (`@jgengine/react/display`) reports `{ coarsePointer, compact, portrait }` — live media-query state, SSR-safe — for adaptive HUD layout; see the mobile/touch rules in [`reference/ui-react.md`](reference/ui-react.md).
|
|
3645
|
+
|
|
3646
|
+
## Interaction — `proximityPrompt`
|
|
3647
|
+
|
|
3648
|
+
One primitive for all float UI: `{ radius, display, invoke }` where `display` is `{ kind: "keybind", actionId }` | `{ kind: "gauge", gaugeId }` | `{ kind: "label", text }` and `invoke` is `{ command, args? }` or null (display-only). `talkable: "dialogue_id"` on an entity expands to a talk prompt. Engine picks the nearest prompt in radius (priority tie-break). Never build per-game hint resolver chains.
|
|
3649
|
+
|
|
3650
|
+
## Pointer-driven input and navigation
|
|
3651
|
+
The **pointer is a service, not per-game glue**. Opt in with `camera` plus a `pointer` config in `defineGame({...})`; the shell casts the cursor into the world and dispatches commands you define — verbs stay commands, catalogs stay data.
|
|
3652
|
+
- **`pointer.worldHit()` (shell service).** The shell raycasts the cursor to `{ point, normal, entity, object }` (a renderer-free `PointerHit` from `@jgengine/core/input/pointer`) — entity/object are the topmost instance ids under the cursor, else `null`, with a ground-plane fallback for open terrain. Consume it renderer-free: `aimToPoint(origin, point)` builds an `Aim` for `item.use`/projectiles (ground-target skillshots, twin-stick), `groundOf(hit)` drops to `[x, z]` for routing. `pointer.worldHitCenter()` is the same raycast pinned to the viewport center instead of the live cursor — the reticle-aim query a locked/hidden-cursor rig (first-person, gamepad) needs when there is no cursor position to read.
|
|
3653
|
+
- **The `pointer` field of `defineGame({...})`** (all optional): `moveCommand` (left-click ground → `run(cmd, { point, entity, object })`, click-to-move), `select` (left-drag marquee + single-click box-select of entities), `orderCommand` (right-click ground → `run(cmd, { selection, point })`, issue a command to the selection), `contextMenu` (right-click an entity/object → its catalog `verbs` menu), `secondaryCommand` (right-click ground/entity/object → `run(cmd, { point, entity, object, aim })` when neither `orderCommand` nor `contextMenu` claims the click — a generic right-click verb for games with no selection/RTS model), `aim` (route the primary ability's aim to the cursor), `grabWorldItems` (left-click a `worldItem` within pickup radius → engine-owned `ctx.scene.worldItem.pickup`, no game command). Enabling `select`/`moveCommand` frees the left button for verbs; orbit moves to middle-drag.
|
|
3654
|
+
- **`createDragCapture({ maxPull?, grabRadius? })`** (`@jgengine/core/input/pointer`) — a renderer-agnostic slingshot/drawback state machine: `begin(origin, at)` starts a pull (rejected outside `grabRadius`), `update(at)` tracks the cursor, `release()` returns the final `DragState { origin, current, pull, magnitude, fraction }` (pull clamped to `maxPull`), `cancel()` aborts. Angry Birds-style slingshots, bow drawback, throwable wind-up — pair with `aimToPoint` to fire.
|
|
3655
|
+
- **Selection math** (`scene/selection`) is pure and testable: `createSelectionSet()`, `screenRect`/`selectWithinRect`/`isMarquee` over projected screen points.
|
|
3656
|
+
- **Context menu** (`interaction/contextMenu`): a catalog entity/object carries `verbs: contextVerb(label, command, args?)[]`; the shell builds the menu with `buildContextMenu` and dispatches the chosen command via `contextVerbInput` (verb args + `target`/`point`, so one handler can walk-then-act).
|
|
3657
|
+
- **Navmesh + A\*** (`nav/navGrid`): `createNavGrid({ bounds, cellSize, diagonal? })` → mark obstacles with `blockAabb`/`setWalkable`, or `populateNavGridFromEnvironment(grid, world)` to block every generated building's footprint on an `environment()` world's `structures` in one call (returns the count blocked) instead of hand-walking the district. `findPath(grid, from, to, { clearance?, smooth?, stepCost? })` returns a string-pulled `[x, z]` polyline (blocked start/goal snap to the nearest walkable cell) feeding **both click-to-move and AI routing**; `stepCost?(from, to)` multiplies the base cost of a grid step — `slopeStepCost(terrainField, weight?)` is the ready-made factory that penalizes steep terrain (routes around cliffs instead of over them). Renderer-free — AI and gameplay consume it without the shell.
|
|
3658
|
+
- **`pathFollow`** (`nav/pathFollow`): the lighter authored-polyline mover for tower-defense creeps that needs no navmesh — `createPathFollow({ waypoints, speed, loop? })` + pure `advancePathFollow(config, state, dt)` (crosses multiple waypoints per tick, reports `done`/`heading`/`distanceTravelled`). Feed it a navmesh route with `pathFromNav(route, elevation, offset?)` and the same follower drives click-to-move — `elevation` is either a fixed `y` or a `{ sampleHeight(x, z) }` field (any `TerrainField` qualifies), so a route across relief rides the ground instead of a flat plane.
|
|
3659
|
+
- **`constrainToNavGrid(grid, { y? })`** (`nav/navConstrain`) is a standalone walkable-pass-through + wall-slide helper: it passes through walkable moves, slides along walls at the navmesh boundary instead of stopping dead, and optionally remaps `y` to the grid. Its `(proposed, entity)` shape doesn't match `PlayerMovementConfig.beforeCommit`'s `(frame) => [x,y,z]` signature, so wire it in with a small adapter closure (see "Controller kinematics" above) to wall a player/AI to the same navmesh `findPath` already routes against.
|
|
3660
|
+
## AI — director, threat, jobs, crowds (`ai/*`)
|
|
3661
|
+
Renderer-free AI over the same navmesh (`findPath`/`pathFollow`) gameplay already uses. Everything ticks on **game-time `dt`** (the `ctx.time` simClock delta), so it obeys pause and fast-forward for free. Manifests, patrol routes, job definitions, threat weights, and POIs are **game data** — the primitives own the loop, the catalog owns the content.
|
|
3662
|
+
- **Spawn director** (`ai/spawnDirector`) — budgets and escalates spawns for wave shooters and difficulty directors (Brotato, Bloons TD 6, Risk of Rain 2, Helldivers 2, Deep Rock Galactic). `createSpawnDirectorState(config)` then pure `advanceSpawnDirector(config, state, dt, { alive, players? })` → `{ state, spawns: SpawnRequest[] }`. Each `WaveManifest` grants a `budget` spent on affordable weighted `SpawnEntry`s (`cost`/`weight`/`minWave`), capped by `maxAlive`; `duration` auto-advances waves (or call `advanceWave` on "wave cleared"). Budget also trickles via `budgetPerSecond`, ramps a difficulty curve with `escalationPerSecond` (grows with sim-time), scales with `playerBudgetPerSecond`, and surges on `raiseAlert(state, amount)` decaying over time (bug-breach/dropship escalation). Seeded (`seed`) so ticks are deterministic. `pickSpawnPoint(points, players, { roll, bias })` biases placement toward (or away from) players. `config.spawnPoints?: NavPoint[]` lets the director pick a point itself: each `SpawnRequest` then also carries `point` (the chosen `[x, z]`, biased by `config.spawnPointBias`) and `laneId` (the point's index into `spawnPoints`) — feed multiple named lanes/portals and read `laneId` back to route the spawned entity down its lane instead of correlating positions by hand.
|
|
3663
|
+
- **Threat table** (`ai/threat`) — MMO/extraction aggro (Escape from Tarkov, WoW-style tanking). `createThreatTable({ decayPerSecond?, max?, forgetBelow? })`: `add(source, amount)` accumulates, `decay(dt)` bleeds off per game-second and forgets emptied sources, `highest({ current?, stickiness? })` returns the top-threat source to feed `scene/targeting` — `stickiness` (e.g. 1.1) keeps the current target until another exceeds it by that factor, so aggro doesn't jitter. `ranked()` for a threat meter.
|
|
3664
|
+
- **Patrol** (`scene/behaviors`) — `patrol({ waypoints, speed, loop? })` is a `BehaviorDescriptor` (a route is data) that layers a fixed beat on top of `wander`; drive it with `createPathFollow`/`advancePathFollow` (lane creeps, scav patrols in Deadlock/Tarkov). Route waypoints between guard posts with `findPath`.
|
|
3665
|
+
- **Job board** (`ai/jobBoard`) — colony/companion task assignment (Palworld stations, Schedule I employees, Sons of the Forest directives). `createJobBoard()`: `post(job)` a `JobDef` (`station`, `work` seconds, `priority`, `arriveRadius`, `repeat`), `claim(worker)` auto-pulls the highest-priority queued job or `assign(worker, jobId)` for a player order (steals it from its holder), `release` requeues. Per tick `advance(worker, dt, { distanceToStation })` runs the state machine `travelling → working → done` (path to `station(worker)` via `findPath`, occupy, run the loop), returning a `JobReport` on completion; `repeat` jobs re-run as a production loop and report each cycle.
|
|
3666
|
+
- **Crowd flow** (`ai/crowd`) — many agents routing to their own points of interest with congestion (Two Point Museum corridors, Dave the Diver seating). `computeFlowField(grid, goals, { clearance?, congestion? })` runs Dijkstra from the goals over the walkable grid → `direction(point)`/`next(point)` steer any agent toward the nearest goal (no per-agent A*). `createCrowdField(grid)` tracks per-cell occupancy (`enter`/`leave`/`count`); pass `crowd.penalty(weight)` as the field's `congestion` to reroute flow around crowded cells each tick. `selectPoi(pois, from, { roll, occupancy?, distanceBias?, distance? })` weights a POI by appeal and proximity, skips ones at `capacity`, and accepts a `distance` override (e.g. `findPath` length) to choose over the navmesh, not line-of-sight.
|
|
3667
|
+
## Map, fog of war & ping
|
|
3668
|
+
Minimap/world-map/fog/compass state is renderer-free core (`world/*`), the top-down terrain image bakes in the shell, and the minimap/compass/world-map are react components. Ping rides the existing party + feed — it is not a new channel.
|
|
3669
|
+
- **Markers** (`world/markers`): `createMarkerSet()` is a reactive keyed set of `MapMarker { id, kind, position, label?, owner?, expiresAt?, meta? }` — `add`/`remove`/`get`/`list`/`query({ kind, owner, near, radius })`/`prune(now)`/`subscribe`. `kind` is a game-owned catalog string; `DEFAULT_MARKER_KINDS` (objective/enemy/loot/location/danger/ping/player/ally) supplies colors + glyphs the react map reads (override with your own `MarkerKindStyle` palette). Objective/entity/loot markers all live here.
|
|
3670
|
+
- **Fog of war** (`world/fog`): `createFogField({ bounds, cellSize })` is reveal-on-event — `reveal(x, z, radius?)` (a dig/act), `revealAlong(from, to, radius?)` (a walked trail); once a cell is revealed it stays revealed. `isRevealed`/`fraction`/`cells()` (stable snapshot for rendering)/`reset`/`subscribe`.
|
|
3671
|
+
- **Minimap math** (`world/minimap`): pure projection + bearings — `projectToMinimap(worldPoint, { center, worldRadius, size, rotate? })` → pixel `{ x, y, inside, distance }` (north = −Z maps up), `clampToMinimapEdge` for off-map markers, `compassBearing(from, to)`/`headingToBearing(yaw)`/`bearingToCardinal`/`relativeBearing` for the compass strip.
|
|
3672
|
+
- **Ping** (`game/ping`): `classifyPing(hit, { roleOf, categoryOf }, options?)` turns a G1 `pointer.worldHit()` `PointerHit` into a category (hostile entity → `enemy`, tagged object → its catalog category, open ground/ally → `location`). `createPingSystem({ markers, feed, party?, ttlMs?, classify, classifyOptions? })` composes classify + broadcast: `ping(from, hit, category?)` classifies, drops a categorized marker, and pushes the `PingPayload` to the party feed under `PING_FEED_ACTION` (`"party.ping"`) — the shell's feed bridge fans it to the squad. `DEFAULT_PING_CATEGORIES` is the enemy/loot/location/danger wheel. Enable the verb with the `pointer.pingCommand` field of `defineGame({...})`: the shell binds the `ping` input action → `worldHit()` → runs your command with `{ point, entity, object, normal }`.
|
|
3673
|
+
- **Shell render** (`@jgengine/shell/map`): `bakeTerrainMap(field, bounds, { resolution? })` renders a `TerrainField`/`RegionField` to a top-down PNG data-URL for the map background; `MapMarkerBeacons({ markers })` renders world-space beacons (the visible side of a ping) — wire via the `WorldOverlay` field of `defineGame({...})`. See the `extraction-map` demo game.
|
|
3674
|
+
## Sensors, vision & observer tools (`sensor/`)
|
|
3675
|
+
Pure `@jgengine/core/sensor/*` primitives for querying and surfacing world state the player can't normally see or reach through the standard occlusion/proximity rules — reveal vision, hidden-state sensors, photo-mode framing, and session replay. Shell renderers/HUD pieces live in `@jgengine/shell/vision` and `@jgengine/shell/replay`.
|
|
3676
|
+
| Primitive | Answers |
|
|
3677
|
+
|-----------|---------|
|
|
3678
|
+
| `createRevealQuery({ resolvePosition, resolveTags, candidates })` → `RevealQuery` | `inRadius(center, radius, tags)` — occlusion-ignoring tagged-entity radius query (Dark Sight / detective-vision reveal, #115). `inRadius` already never checks occlusion (only combat's AoE `effect()` layers a LoS filter on top of it) — this is that same query shaped for a vision readout: scoped to catalog-declared tags, sorted nearest-first |
|
|
3679
|
+
| `probeHiddenState(origin, sources, { range, variableId, falloff? })` / `probeHiddenStateAll(...)` → `SensorReading \| null` | A sensor verb: reads a hidden zone/entity state variable (EMF/thermometer/geiger, #116) in range, strongest reading first; `strength` falls off linearly with distance by default |
|
|
3680
|
+
| `projectToView(camera, point)` → `FrustumProjection` | Pure camera-frustum projection (no three.js) — `inView`, `screenX/screenY` (-1..1), `distance` |
|
|
3681
|
+
| `framingScore(projection, config?)` → `number` | 0..1 framing quality from screen-center placement + distance-to-ideal (photo-mode "is this subject framed", #117) |
|
|
3682
|
+
| `createFrustumSensor(config?)` → `FrustumSensor` | `tick(camera, targets, dt)` — per-target in-view + framing + `dwellSeconds` (resets the instant a target leaves frame); a view-frustum sensor on a held camera object (Content Warning-style monster-filming scoring) |
|
|
3683
|
+
| `createRecordingBuffer(options?)` → `RecordingBuffer<T>` | `append(t, data)` / `seek(t)` / `range(fromT, toT)` — a session-recording buffer for replay/photo mode/kill-cam (#120), keyed on game-time so pause/fast-forward scrub consistently |
|
|
3684
|
+
| `colorDistance(a, b)` / `concealmentScore(entityColors, backgroundColors)` / `createConcealmentSensor(config?)` → `ConcealmentSensor` | Camouflage/blend-in scoring — how well an entity's palette matches its surroundings (hide-and-seek, stealth camo checks) against a `threshold` |
|
|
3685
|
+
| `createFreezeMonitor(config?)` → `FreezeMonitor` | Detects a tracked subject moving past a tolerance speed during a "freeze" window (red-light-green-light, statue games) and reports `FreezeViolation`s |
|
|
3686
|
+
Shell wiring: `@jgengine/shell/vision/RevealVision` (`RevealHighlights` — depth-test-disabled 3D highlight meshes for tagged entities in radius, meant for `WorldOverlay`; `RevealScreenTint` — full-screen CSS tint for "vision mode is on", meant for `GameUI`), `@jgengine/shell/vision/HiddenStateProbeHud` (`SensorReadoutMeter` — needle-strength HUD readout), `@jgengine/shell/vision/FrustumSensorHud` (`FrustumSensorReadout` — drives the sensor off the live render camera via `useThree`/`useFrame`, portals its HUD through drei's `Html fullscreen`), `@jgengine/shell/replay/useSessionRecorder` (records an entity's pose into a `RecordingBuffer` every frame; drive an observer-cam ghost, scrubber, or kill-cam export from it). The detached spectator/photo cam itself is the `observer` camera rig (see Camera rigs above) — bind it to any entity or fixed point.
|
|
3687
|
+
|
|
3688
|
+
## World features
|
|
3689
|
+
|
|
3690
|
+
Renderer-free world surface — query primitives, environment fields + weather + realm composition, survival meters/moodles, interactive building & terraform, the optional headless physics world, vehicles/mounts/racing, and spawn placement. Full surface: **[reference/world.md](reference/world.md)**.
|
|
3691
|
+
|
|
3692
|
+
## Turn-based & tactics (renderer-free)
|
|
3693
|
+
|
|
3694
|
+
Pure-`core` primitives for turn-based, grid-tactics, and card games — every one is a stateful factory with matching pure math, and every stateful piece exposes `capture()`/`restore()` so it plugs straight into the snapshot store. Overlays and tile art are the shell's/game's job; these ship the logic.
|
|
3695
|
+
|
|
3696
|
+
- **`turn/turnLoop` — `createTurnLoop(config)`.** An initiative machine over an ordered participant list with optional `phases` and per-turn action-economy `pools`. `advanceTurn()` walks the order (round++ on wrap) and **resets the entering participant's pools**; `advancePhase()` steps phases then rolls into the next turn. Pools are catalog data (`{ id, max, start? }`) — a single Slay-the-Spire energy pool or BG3's Action/Bonus/Movement/Reaction set, spent independently via `spend/canSpend/gain/refill`. `setOrder`/`addParticipant`/`removeParticipant` re-roll initiative without losing the active pointer. `config.onTurnStart?(participantId)`/`onTurnEnd?(participantId)` fire on every `advanceTurn()` transition (start also fires once for the initial participant at construction) — hang status-effect ticks, "your turn" banners, or AI-turn kickoff here instead of diffing `state()` between ticks yourself. `ctx.game.turn.loop(id, config?)` is the runtime-wired accessor: lazily creates (config required the first call) or returns the existing notify-wrapped loop for `id`, so a HUD bound to `ctx.subscribe` re-renders on every turn/phase/pool change with no separate store to wire.
|
|
3697
|
+
- **`turn/commit` — `createCommitController({ mode })`**, also hosted at `turnLoop.commit`. Three commit modes: `immediate` (submit resolves now), `simultaneous` (sealed hidden submissions → `reveal()` once `allReady()`, deterministic order — Marvel Snap), and `rewind` (visible `pending()` → `rewind()` to discard or `commit()` to finalize).
|
|
3698
|
+
- **`turn/intent` — `createIntentBoard()`.** A minimal per-participant "what will you do" board, lighter than a full commit round: `declare(participantId, { kind, magnitude?, targetId?, note? })` records one intent per participant (overwriting any prior undeclared one), `peek(participantId)` reads without clearing, `all()` lists every declared `[participantId, intent]` pair (for an enemy-intent HUD row, Slay-the-Spire style), `consume(participantId)` reads and clears in one call, `clear(participantId?)` clears one or everyone. Reach for this when you need visible declared-but-not-yet-resolved intents (telegraphed enemy actions) without the full simultaneous-reveal machinery of `turn/commit`.
|
|
3699
|
+
- **`tactics/tacticalGrid` — `createTacticalGrid({ width, height, blocked?, diagonal?, world? })`.** Tile occupancy (one unit per tile), `reachable(from, budget)` flood-fill (respects walls + occupants), `path(from, to)` shortest route, and `push(id, dir, { distance, chain })` discrete knockback-to-tile — chained collisions transfer momentum through struck units (Into the Breach), or stop with a recorded `PushCollision` against `wall`/`edge`/another unit. `world: { origin: [x, z], tileSize }` (mirroring `navGrid`'s bounds+cellSize convention) turns on `worldToTile(x, z)` (world point → `Tile | null`, null outside the grid) and `tileToWorld(tile)` (a tile's world-space center) — the render/pointer-hit round trip between the tactics grid and the 3D scene; omit `world` for a grid used purely as abstract logic (both throw if called without it).
|
|
3700
|
+
- **`tactics/predictiveQuery` — `predictAreaEffect`/`predictArcEffect`/`predictTiles`.** A "would-this-effect-hit" query for pre-commit overlays and enemy-intent telegraphs. It reuses the **exact** AoE/LoS targeting behind `ctx.scene.entity.effect` (`combat/effects` `resolveAreaTargets`) so the predicted target set matches what the effect would actually drain — without committing any state change.
|
|
3701
|
+
- **`tactics/snapshot` — `createSnapshotStore()`.** Cheap, repeatable turn-undo: `register(id, slice)` any `capture()/restore()` slice (the grid, surfaces, and turn loop all qualify), then `capture()/restore()` a deep-cloned snapshot or use the `push()/pop()` undo stack. `deepClone` handles objects/arrays/Map/Set so a held snapshot is immune to later mutation.
|
|
3702
|
+
- **`tactics/surface` — `createSurfaceLayer({ kinds, reactions })`.** A stateful tile surface layer with its own `tick(dt)` (timed surfaces decay + expire) and a **combination matrix** — `reactions` is data (`{ when: [a, b], result }`), so grease+fire→fire and water+lightning→electrified are catalog entries, not hard-coded. Distinct from terrain/water; drive its tick from `onTick`'s game-time `dt`.
|
|
3703
|
+
|
|
3704
|
+
## External data — `data/dataSource` and the dev proxy
|
|
3705
|
+
|
|
3706
|
+
Renderer-free async-state primitives (`@jgengine/core/data`) for a game that reads a live external source (a leaderboard API, a session browser, remote config) — distinct from `ctx.game.store`/multiplayer, which are for the game's own authoritative state.
|
|
3707
|
+
|
|
3708
|
+
- **`createDataSource(load, options?)`** (`data/dataSource`) → `DataSource<T>` wraps one `load(signal)` async call as `{ status: "idle"|"loading"|"ready"|"error", data, error }`. `getState()` reads the current snapshot, `subscribe(listener)` fires on every change, `refresh({ force? })` re-runs `load` (de-duplicates a call already in flight unless `force`; aborts the prior call first when forced), `startPolling(intervalMs?)`/`stopPolling()` run `refresh` on an interval (`intervalMs` falls back to the one passed at construction; throws if neither is given), `dispose()` tears down polling and in-flight requests for good. Pass `options.clock` (`{ setInterval, clearInterval }`) to swap the timer source in tests.
|
|
3709
|
+
- **`fetchJson<T>(url, options?)`** (`data/fetchJson`) — `fetch` + JSON-parse in one call; throws `HttpStatusError` (`status`, `statusText`, `url`) on a non-OK response and `JsonParseError` (`url`, `cause`) on unparsable JSON, so a `DataSource`'s `error` is always one of these two typed shapes, never a bare `Error`. `options.fetchImpl` swaps the fetch implementation for tests/SSR.
|
|
3710
|
+
- **`createJsonDataSource<T>(url, options?)`** (`data/jsonDataSource`) — sugar combining the two above: a `DataSource<T>` whose `load` calls `fetchJson(url, options)`.
|
|
3711
|
+
- **Dev proxy (`data/devProxy`)** — same-origin routing for external APIs during `bun dev` so browser CORS never blocks a game's `fetchJson` call against a third-party host. `parseDevProxyTable(raw)` parses a `VITE_JGENGINE_DEV_PROXY` env value (a JSON object of `{ routeName: "https://api.example.com" }`) into a `DevProxyTable`; `proxiedUrl(target, { dev?, table?, prefix? })` rewrites a `target` URL whose prefix matches a table entry into `/proxy/<routeName>/<rest>` (default prefix `/proxy`) when `dev` is true (defaults to `import.meta.env.DEV`) — else returns `target` unchanged, so the same call hits the real host in production. `apps/dev`'s `vite.config.ts` reads the same env var and wires a matching Vite server `proxy` entry per route (`changeOrigin: true`, strips the `/proxy/<routeName>` prefix) — set `VITE_JGENGINE_DEV_PROXY` once and both sides (the URL rewrite and the actual proxy route) agree.
|
|
3712
|
+
|
|
3713
|
+
## Multiplayer and the backend seam
|
|
3714
|
+
|
|
3715
|
+
The transport/host/persistence seam — `createWsBackend`, protocol codec, browser-safe authoritative host + router, WebRTC P2P, the Node/Convex/SQL adapters, presence, and save cadence. Full surface: **[reference/multiplayer.md](reference/multiplayer.md)**.
|
|
3716
|
+
|
|
3717
|
+
## UI — `@jgengine/react`
|
|
3718
|
+
|
|
3719
|
+
The React layer — `GameProvider`, the hooks table, headless className-passthrough primitives (incl. map components), the identity/chat/voice/social/drag-layer kits, the shadcn registry install path for visual HUD components (`npx shadcn@latest add https://jgengine.com/r/<name>.json`), the screen-layout rule, and the **UI quality bar** (required, not optional polish). Full surface: **[reference/ui-react.md](reference/ui-react.md)**.
|
|
3720
|
+
|
|
3721
|
+
## Devtools — F2 overlay and tunables
|
|
3722
|
+
|
|
3723
|
+
`@jgengine/shell`'s `GamePlayerShell` mounts an F2-toggled debug overlay (`shell/src/devtools/DevtoolsOverlay.tsx`) over every game automatically — `defineGame({ devtools: false })` is the only way to turn the toggle off (default `true`). Five tabs:
|
|
3724
|
+
|
|
3725
|
+
| Tab | Shows |
|
|
3726
|
+
|-----|-------|
|
|
3727
|
+
| Perf | fps, frame/sim ms, draw calls, triangles, entity/object counts, state notifies/s, registered probes |
|
|
3728
|
+
| Tune | every discovered tunable — checklist grouped by source file or table export name; check one to control it live |
|
|
3729
|
+
| Logs | captured `console.log`/`info`/`warn`/`error` |
|
|
3730
|
+
| Net | observed backend round-trip latency (fed by `instrumentLatency`) |
|
|
3731
|
+
| Keys | the game's `ActionCodesMap` bindings |
|
|
3732
|
+
|
|
3733
|
+
**Tunables are zero-annotation.** Write plain code under `Games/<id>/src/**` — no import, no wrapper — and it's discoverable:
|
|
3734
|
+
|
|
3735
|
+
```ts
|
|
3736
|
+
// loop.ts — top-level export const, nothing else
|
|
3737
|
+
export const GRAVITY = -22;
|
|
3738
|
+
export const SKY_COLOR = "#87ceeb";
|
|
3739
|
+
export const GOD_MODE = false;
|
|
3740
|
+
|
|
3741
|
+
// game/content.ts — an exported flat table of numbers/booleans/colors
|
|
3742
|
+
export const TUNING = { reach: 6, spawnRate: 0.4, fogColor: "#334455" };
|
|
3743
|
+
```
|
|
3744
|
+
|
|
3745
|
+
The dev runner's Vite plugin, `tunableDiscoveryPlugin` (`@jgengine/core/devtools/transformTunables`, wired in `apps/dev/vite.config.ts`), rewrites each top-level `export const <number|boolean|"#hex">` literal to `export let` and binds it into the devtools registry as the module loads (`transformTunableExports` is the pure string transform underneath; `tunableModuleTable(id)` derives the table id from the file path, skipping `main.tsx` and `*.test.*`). Table exports need no transform at all — after each game module loads, the dev app calls `devtools.discover.scanModule(moduleExports)`, which walks every export's own properties for a flat plain-object table of numbers/booleans/`"#rrggbb"` strings.
|
|
3746
|
+
|
|
3747
|
+
F2 → Tune tab lists every discovered entry as a checklist, grouped by source file (top-level constants) or by table export name (object tables). Checking an entry hands it a live slider/toggle/color picker; unchecking resets it to its initial value. Kind is inferred from the value: `number` → slider, `boolean` → toggle, a `"#rgb"`/`"#rrggbb"`/`"#rrggbbaa"` string → color.
|
|
3748
|
+
|
|
3749
|
+
**Liveness.** An edit applies live wherever the code reads the constant/table entry at use time. A value captured once at init — passed into a function call, baked into worldgen — only picks up the new value on reload. Overrides persist in `localStorage` per game (key `jg-devtools:<game name>`) and are re-applied *before* `loop.onInit` runs, so even an init-baked constant respects its override after a refresh — *if* the read happens at or after `onInit`. A read that happens earlier than that (see below) never sees the override, reload or not.
|
|
3750
|
+
|
|
3751
|
+
**Default assumption: almost every gameplay number, boolean, and color is a tunable, not a hardcoded fact.** Walk speed, jump height, gravity, damage, cooldowns, spawn rates, drop chances, radii, durations, thresholds, multipliers, colors — if it's a scalar a designer would plausibly want to nudge while playing, it belongs in a place discovery can see (a top-level `export const`, or a direct scalar field on a catalog def object like `PlayerDef`/`EnemyDef`) — never buried as a bare literal inside a deeper nested object with no named export, and never computed once and thrown away. Treat "should this be tunable" as opt-out, not opt-in.
|
|
3752
|
+
|
|
3753
|
+
**Catalog-derived content must read fields live, not bake them at import time.** A common trap: a `content.ts` (or any module implementing `GameContextContent`) that loops over a catalog array *once at module scope* and copies scalar fields into a separately cached `Map`:
|
|
3754
|
+
|
|
3755
|
+
```ts
|
|
3756
|
+
// WRONG — copies walkSpeed by value at import time, before devtools can even scan exports
|
|
3757
|
+
const entityEntries = new Map<string, GameContextEntityEntry>();
|
|
3758
|
+
for (const p of players) {
|
|
3759
|
+
entityEntries.set(p.id, { stats: p.stats, movement: { poses: p.poses, walkSpeed: p.walkSpeed } });
|
|
3760
|
+
}
|
|
3761
|
+
export const content: GameContextContent = {
|
|
3762
|
+
entityById: (id) => entityEntries.get(id) ?? null,
|
|
3763
|
+
};
|
|
3764
|
+
```
|
|
3765
|
+
|
|
3766
|
+
This runs during module import — earlier than `discoverGameTunables`/override-application in `apps/dev/src/main.tsx`, and earlier than any `loop.onInit`. The catalog object (`p`) still gets live-mutated by devtools, but nothing ever re-reads it, so the baked `walkSpeed` is permanently stale: no F2 edit and no persisted override ever reaches gameplay, reload or not.
|
|
3767
|
+
|
|
3768
|
+
```ts
|
|
3769
|
+
// RIGHT — map ids to the catalog def itself; build the entry fresh on every lookup
|
|
3770
|
+
const playersById = new Map(players.map((p) => [p.id, p]));
|
|
3771
|
+
function entityById(id: string): GameContextEntityEntry | null {
|
|
3772
|
+
const p = playersById.get(id);
|
|
3773
|
+
return p === undefined ? null : { stats: p.stats, movement: { poses: p.poses, walkSpeed: p.walkSpeed } };
|
|
3774
|
+
}
|
|
3775
|
+
export const content: GameContextContent = { entityById };
|
|
3776
|
+
```
|
|
3777
|
+
|
|
3778
|
+
Same shape, same call site — the only change is *when* `p.walkSpeed` is read: at lookup time (each spawn) instead of once at import. Objects (`stats`, `receive`) are already reference-safe to pass through either way; this only matters for scalars (numbers/booleans/strings) copied out of a catalog def.
|
|
3779
|
+
|
|
3780
|
+
**`tunable()` still exists — an optional low-level primitive, not the recommended path.** Reach for it only when you need explicit bounds, an `options` select, or a change subscription that discovery can't infer:
|
|
3781
|
+
|
|
3782
|
+
```ts
|
|
3783
|
+
import { tunable } from "@jgengine/core/devtools/devtools";
|
|
3784
|
+
|
|
3785
|
+
const gravity = tunable("physics/gravity", -22, { min: -60, max: 0 });
|
|
3786
|
+
```
|
|
3787
|
+
|
|
3788
|
+
Read `gravity.value` at use time (or `gravity.subscribe(listener)`) — never destructure once at module load. A `"group/label"` name (e.g. `"physics/gravity"`) groups the control under `group` in the Tune tab; `devtools.controls.register` is the same call underneath. Real example: `Games/voxel-mine/src/loop.ts` — `tunable("mining/reach", REACH, { min: 2, max: 16, step: 1 })`, read via a getter passed to `createEditorHandlers`.
|
|
3789
|
+
|
|
3790
|
+
**Agent loop.** The overlay's "Copy report" button copies a JSON `DevtoolsSnapshot`; from a browser session an agent can instead call `window.__JG_DEVTOOLS.snapshot()` directly (or `snapshotDevtools()` from game code) for the same shape — frame stats, render sample, latency stats, captured logs, probe values, every registered control's current + initial value, and a `discovered` array (`id`, `kind`, `value`, `enabled`) covering every auto-discovered tunable whether or not it's enabled — a single call to check "is this actually working" without a screenshot. `window.__JG_DEVTOOLS.discover` is exposed directly too (`list`/`enable`/`disable`/`bind`/`scanTable`/`scanModule`/`clear`) so an agent can flip a discovered tunable on and read/write it from the console without touching the UI.
|
|
3791
|
+
|
|
3792
|
+
`devtools.probes.register("name", () => value)` (`@jgengine/core/devtools/devtools`) surfaces a game-specific gauge (entity count, queue depth, whatever) in both the Perf tab and the snapshot; call the returned unregister function to remove it.
|
|
3793
|
+
|
|
3794
|
+
## Assets — real art from day one
|
|
3795
|
+
|
|
3796
|
+
Squares as enemies, colored boxes as buildings, and a flat grid floor read as *broken*, not unfinished. The blueprint's **Asset plan** names the packs before the first edit; a pass does not end while any default-material primitive, unstyled ground plane, or debug grid is visible in the staged screenshot — and that includes 2D HUD art: a first-letter tile, emoji, or one generic shape reused per slot is a placeholder exactly like a graybox enemy. Primitive stand-ins are allowed only *mid-pass* as scaffolding.
|
|
3797
|
+
|
|
3798
|
+
**Sources** (CC0 — public domain, commercial use, no attribution — unless noted):
|
|
3799
|
+
|
|
3800
|
+
| Source | What you get |
|
|
3801
|
+
|--------|--------------|
|
|
3802
|
+
| [Kenney.nl](https://kenney.nl) | 40,000+ CC0 assets: characters, buildings, nature, vehicles, weapons, UI, audio — the broadest single library |
|
|
3803
|
+
| [Quaternius](https://quaternius.com) / [KayKit](https://kaylousberg.itch.io) | CC0 low-poly packs incl. **rigged + animated characters**: medieval, sci-fi, dungeons, animals, adventurers |
|
|
3804
|
+
| [Poly Haven](https://polyhaven.com) / [ambientCG](https://ambientcg.com) | CC0 PBR textures, HDRIs, materials — the floor comes from here, never a flat color |
|
|
3805
|
+
| [Poly Pizza](https://poly.pizza) | Search engine over thousands of CC0 low-poly models for one specific thing |
|
|
3806
|
+
| [Game-Icons.net](https://game-icons.net) (CC BY 3.0 — credit it) / Kenney UI packs (CC0) | 4,000+ item/ability **icon silhouettes** — the registry `game-icon` item covers common HUD glyphs first |
|
|
3807
|
+
| [itch.io CC0 3D tag](https://itch.io/game-assets/assets-cc0/tag-3d) / [OpenGameArt](https://opengameart.org) | Long tail — **check the license per asset**, CC0 filter first |
|
|
3808
|
+
| [Mixamo](https://www.mixamo.com) | Free humanoid animations (Adobe license — fine for shipped games, not CC0) |
|
|
3809
|
+
| Kenney audio / [freesound CC0 filter](https://freesound.org) | Hit sounds, UI clicks, ambience |
|
|
3810
|
+
|
|
3811
|
+
**Rules:**
|
|
3812
|
+
|
|
3813
|
+
1. **One style family per game.** Kenney + Quaternius + KayKit low-poly mix fine; low-poly models on photoreal PBR ground reads broken. Name the family in the blueprint.
|
|
3814
|
+
2. **License discipline.** CC0 needs nothing; anything else gets a line in `src/game/assets-credits.md` (source, author, license). Never ship an asset you can't name the license of.
|
|
3815
|
+
3. **Wire through the engine seams.** GLB models live in the game's `src/game/assets.ts` render catalog keyed by catalog id; billboards via `entitySprites`, real meshes via `entityModels`/`objectModels` in `defineGame({...})`; ground/skies belong to the world layer. Catalog `model` fields reference asset keys — never file paths in game logic. Source models through **`@jgengine/assets`** (`buildCatalog({ basePath })` → resolve ids/aliases → urls); `pull` packs into your app's `public/models/` (extracts Kenney's shared `Textures/` alongside the GLBs so models render textured). Network-restricted: `pull` falls back through `--mirror <baseUrl>` / `JGENGINE_ASSETS_MIRROR`, and `--offline` fails fast — see the package README for the fallback order and add/import flow.
|
|
3816
|
+
4. **Coverage follows the content budget.** Every entity family, placed object, and held item maps to a real asset *before* the catalog entry ships. If the pack lacks a model, restyle the noun to one it has — rename the fantasy, don't ship a cube.
|
|
3817
|
+
5. **Scale/pivot sanity.** `@jgengine/assets` measures each model's footprint/center/`minY` at reindex and ships them on the catalog entry (`catalog.resolve(id).dims`); with `objectModels` anchor `"center"` (the default) the shell centers the footprint on the placement point and ground-snaps the lowest vertex — so `object.place(id, cellX, y, cellZ, { rotation })` renders centered + grounded with no pivot math and no `dimensions.ts`. Anchor `"origin"` opts back into the raw GLB origin. Check the first placement of each model against its catalog `footprint`; one wrong pivot repeated 100 times is a rebuild.
|
|
3818
|
+
6. **Item and ability icons are assets too.** Every hotbar/inventory/ability slot renders a real, distinct icon — the registry `game-icon` catalog (`iconForItemId`/`iconForAction`) or a Game-Icons/Kenney silhouette — per the UI quality bar's real-icons rule.
|
|
3819
|
+
|
|
3820
|
+
## Genre cheat sheet
|
|
3821
|
+
|
|
3822
|
+
- **Voxel/crafting**: objects for blocks/machines, `voxel()`, `object.break`/`object.placeFromInventory`.
|
|
3823
|
+
- **Tycoon/lab**: objects + `slotInventory`, `plots()`, configure via prompt → command.
|
|
3824
|
+
- **Shooter**: `fireProjectile`/`settleProjectile`; grenades settle → `effect({ at, radius })`; `movement.poses`/`aim` + zoom modifier; `servers({ … })` + game-owned `server.mode`; loadout classes from commands.
|
|
3825
|
+
- **MMO/RPG**: bounded stats + `leveling()` over a game XP curve; `tabTarget` → `cycleTarget`; handlers read `getTarget`; quests bound to `entity.died`/`inventory.added`; social party + `partyShare`; `server: "persistent"`.
|
|
3826
|
+
- **All combat games**: react to `entity.died` (feed/leaderboard/score) — never poll HP.
|
|
3827
|
+
|
|
3828
|
+
## Anti-patterns
|
|
3829
|
+
|
|
3830
|
+
| Wrong | Right |
|
|
3831
|
+
|-------|-------|
|
|
3832
|
+
| Player tuning in `defineGame` | Entity catalog `movement` + stats |
|
|
3833
|
+
| `behaviors: […]` on place/spawn | Catalog entry |
|
|
3834
|
+
| Engine `weapon.fire` / `consumable.use` / `combat.*` | `item.use` + catalog `use` → game handler |
|
|
3835
|
+
| `ItemUseInput.to` for targets | `getTarget(from)` in handlers |
|
|
3836
|
+
| `effect({ to })` for gunshots | `fireProjectile` + `settleProjectile` |
|
|
3837
|
+
| Polling HP in `onTick` for kills | `entity.died` event |
|
|
3838
|
+
| `combat.lootTable` / `loot.enemy` | `onDeath` on the entity that died |
|
|
3839
|
+
| Hand-rolled `Math.random()` loot in commands | `lootTable()` + `ctx.game.loot.roll` |
|
|
3840
|
+
| Hand-rolled `xpForLevel`/`levelFromXp` | `game/progression` `curve()` + `leveling()` |
|
|
3841
|
+
| Hardcoded shop arrays | `item.trade.shops` + `tradableAt` |
|
|
3842
|
+
| Kit seeding via scattered `put`/`grant` | `applyLoadout` |
|
|
3843
|
+
| Per-user quest state hand-rolled | `game.quest.register` + binds |
|
|
3844
|
+
| `useKillFeed` / per-domain feed hooks | `useFeed({ action })` |
|
|
3845
|
+
| Raw keys in game logic | `defineGame` input actions |
|
|
3846
|
+
| Positioning inside `ui/components/` or on primitives (`CurrencyPill className="absolute …"`) | Screen wrappers in `GameUI.tsx` only |
|
|
3847
|
+
| Game UI classes without `@source` in host CSS | `@source` entries for your game dirs + `node_modules/@jgengine/{shell,react}` |
|
|
3848
|
+
| One file per catalog entry / per brand | Dense `<domain>/catalog.ts` |
|
|
3849
|
+
| Convex mutations called from game code | `commands.run` through the `GameBackend` transport |
|
|
3850
|
+
| Half a system: quest without tracker, cooldown without sweep, keybind never shown, stub "coming soon" modal | Finish the system end to end — or cut it whole (see `jgengine-newgame`) |
|
|
3851
|
+
| Game-side workaround for a missing engine primitive | File the gap at github.com/Noisemaker111/jgengine/issues (or PR the primitive) and cut or scope the dependent system honestly |
|
|
3852
|
+
| Game nouns in this skill | Engine primitives + placeholder ids only |
|
|
3853
|
+
|
|
3854
|
+
## New-game definition of done
|
|
3855
|
+
|
|
3856
|
+
This is a gate, not a suggestion — every box, in one pass (workflow: **`jgengine-newgame`** skill). "Compiles and the hooks are wired" is not done; a declared system with no UI, no feedback, or no way to exercise it is not done — finish the system or cut it whole.
|
|
3857
|
+
|
|
3858
|
+
- [ ] `game.config.ts` (`defineGame` from `@jgengine/shell/defineGame`) + `index.tsx` (barrel) + `main.tsx` (standalone host) + `loop.ts` + `game/content.ts`
|
|
3859
|
+
- [ ] Catalogs: `game/entities/<role>/catalog.ts`, `game/items/<domain>/catalog.ts`, `game/objects/catalog.ts`; loot tables beside their domain
|
|
3860
|
+
- [ ] Entity `stats` + `receive` orders aligned on the same stat ids; `role` set (drives targeting + camera)
|
|
3861
|
+
- [ ] `game/items/use-handlers.ts` registered in `onInit`; handlers read `getTarget`/`aim`, never a target input
|
|
3862
|
+
- [ ] `game/loadouts.ts` + `applyLoadout` in `onNewPlayer` (gated on `isNew`)
|
|
3863
|
+
- [ ] `game/quests/catalog.ts` + binds; if using xp/level, a game-owned curve fed to `game/progression` (`curve`/`leveling`) — **with their HUD/tracker, or cut**
|
|
3864
|
+
- [ ] `onInit`: register handlers/loadouts/loot/quests, event listeners, feed binds, leaderboard tracks; `setupWorld`
|
|
3865
|
+
- [ ] Player spawns with `id === ctx.player.userId`
|
|
3866
|
+
- [ ] `game/ui/GameUI.tsx` owns layout; components use `@jgengine/react` hooks
|
|
3867
|
+
- [ ] UI passes the **quality bar** above (contrast, scale, framing, genre fit) — not just hook wiring
|
|
3868
|
+
- [ ] Camera tuned via `camera` in `defineGame({...})` — defaults untouched means the feel was never checked
|
|
3869
|
+
- [ ] For an `environment()` world: a `<game>.world.test.ts` asserts `summarizeEnvironment(world)` (`@jgengine/core/world/environmentSummary`) is non-empty with the expected counts — the browserless scene-correctness gate
|
|
3870
|
+
- [ ] HUD screenshotted over a staged `GameUiPreview` scenario and **judged by looking at the image** against the UI quality bar in [`reference/ui-react.md`](reference/ui-react.md) — the final human glance, not the verification loop
|
|
3871
|
+
- [ ] Co-located bun tests for pure game math (curves, cooldowns, spawn logic)
|
|
3872
|
+
- [ ] Multiplayer via adapter config only; no direct backend calls
|
|
3873
|
+
|
|
3874
|
+
## Quick reference
|
|
3875
|
+
|
|
3876
|
+
```
|
|
3877
|
+
defineGame (shell) engine fields (assets, world, physics, inventories, input, server, save, time, feed, multiplayer)
|
|
3878
|
+
+ presentation fields (content, loop, GameUI, camera, environment, shadows, movement, devtools, …) in one call — smart defaults fill the rest
|
|
3879
|
+
defineGame (core) the underlying engine-only primitive: assets, world, physics, inventories, input, server, save, time, feed, multiplayer, loop
|
|
3880
|
+
PlayableGame { game, content, loop, GameUI, camera, … } — the runner contract `defineGame` (shell) returns
|
|
3881
|
+
GameContext ctx.scene / ctx.game / ctx.player / ctx.item / ctx.camera / ctx.input + subscribe/version
|
|
3882
|
+
scene.object place, remove, move, rotate, at, setVisual (per-instance ObjectVisual: scale/color/opacity override)
|
|
3883
|
+
scene.entity spawn (anchor/offset), despawn, setPose, update; stats; targeting; effects;
|
|
3884
|
+
projectiles (object-aware raycasts); spatial queries (opt-in grid broadphase)
|
|
3885
|
+
entity.stats get / set / delta — bounded stats (health, mana, xp, level) on instances
|
|
3886
|
+
progression game/progression — curve() / leveling() over bounded xp/level stats
|
|
3887
|
+
item.use catalog `use` → GameContext handler; no input.to
|
|
3888
|
+
effects drain-signed magnitudes; receive.<effect>.order; AoE = effect + at/radius/los
|
|
3889
|
+
projectiles willHit → fire → settle; ballistic via weapon.projectile
|
|
3890
|
+
death onDeath (reason-aware drops/command), entity.died, auto kill attribution + drop grant
|
|
3891
|
+
game.loot register / has / roll / grantToPlayer (lootTable() = pure factory)
|
|
3892
|
+
game.trade canBuy / canSell / buy / sell / tradableAt
|
|
3893
|
+
game.quest register, accept…turnIn, bind(entity.died | inventory.added), declarative rewards
|
|
3894
|
+
game.social friends (persisted, requests listable), party (ephemeral, invites listable), presence, emotes (nearby broadcast), worldInvites (accept → join target)
|
|
3895
|
+
game.chat send / whisper / history / register — global/party/proximity channels, rate-limited, mute via blocked set
|
|
3896
|
+
game.roster capture / release / list / setEquipped — persisted owned-creature roster
|
|
3897
|
+
game.store/cards/turn store: keyed reactive slot; cards.pile(id): lazy CardPile; turn.loop(id): lazy TurnLoop
|
|
3898
|
+
game.events/feed/leaderboard on / bind+push+recent / track+increment+getTop
|
|
3899
|
+
devtools F2 overlay (Perf/Tune/Logs/Net/Keys); zero-annotation — top-level export const number/boolean/color + exported flat tables auto-discover into Tune; tunable() is the optional low-level escape hatch; snapshotDevtools()/window.__JG_DEVTOOLS.snapshot() → DevtoolsSnapshot (+ discovered[]); probes.register(name, read) adds a Perf gauge
|
|
3900
|
+
applyLoadout all-or-nothing kit seeding per userId
|
|
3901
|
+
player.movement pose (hitboxes) + aim (zoom modifier)
|
|
3902
|
+
player.motion impulse / setVerticalVelocity / setY — vertical-motion seam into the shell's frame driver
|
|
3903
|
+
player.possession own/disown/owns/listOwned + active + possess — control-swap, rebinds shell camera
|
|
3904
|
+
player.cosmetics register + apply/equip + get — per-player appearance slots, no gameplay effect
|
|
3905
|
+
scene.entity.form register + shapeshift/revert + active/abilities — movement+ability+mesh bundle, game-time duration
|
|
3906
|
+
proximityPrompt { radius, display: {kind}, invoke } — one float-UI primitive
|
|
3907
|
+
skillCheck/qte evaluateSkillCheck (moving zone + window) / evaluateQteSequence (timed steps)
|
|
3908
|
+
captureCheck captureChance / rollCapture — hp% + catchPower → probability
|
|
3909
|
+
dialogue check DialogueChoice.check (roll vs DC + advantage/disadvantage) → onSuccess/onFailure
|
|
3910
|
+
world features biomes / voxel / plots / tilemap / flat descriptors
|
|
3911
|
+
physics/physicsWorld optional headless rigid-body sim (PhysicsWorld) — not the defineGame physics field (gravity/jumpVelocity, honored by the kinematics controller); bodies are box (halfExtents) or sphere (radius)
|
|
3912
|
+
physics/ballisticSweep createBallisticSweep(world) → arc-vs-body hit test; wire into ProjectileSystemDeps.sweepBallistic
|
|
3913
|
+
anim/easing lerp/clamp01/smoothstep/tween/timedProgress + easeIn/Out/InOut Quad/Cubic + easeOutBack/Elastic — pure 0..1 tweening math
|
|
3914
|
+
data/dataSource createDataSource/createJsonDataSource — idle/loading/ready/error async state + polling; data/fetchJson + data/devProxy for CORS-safe dev fetches
|
|
3915
|
+
audio/audioFalloff computeFalloffGain / resolveEmitterGain — pure distance→gain curve; shell plays it
|
|
3916
|
+
time/beatClock createBeatClock (BPM ticks) + createBeatInputBuffer (buffered action → next beat)
|
|
3917
|
+
ws/voiceChannel createVoiceChannelRouter — positional falloff + simultaneous non-positional channels
|
|
3918
|
+
multiplayer/identity AuthSession + sessionPlayer + resolveGuestSession — Clerk/better-auth via react structural adapters
|
|
3919
|
+
multiplayer/chatContract ChatTransport (hooks) / ChatSync (callbacks) — ws + convex bindings, local for dev
|
|
3920
|
+
multiplayer/voiceContract VoiceTransport (join/leave/publish/subscribers) + createPushToTalk — media plane host-supplied
|
|
3921
|
+
GameBackend { transport, feeds?, presence? } — Convex is one adapter (createConvexBackend)
|
|
3922
|
+
adapter kinds offline / ws / convex / socketIo / p2p / lan (+ fly({app}) ws sugar) — runtime/adapter
|
|
3923
|
+
ws/pipe TransportPipe/TransportPipeFactory — any bidirectional string channel (webSocketPipe default)
|
|
3924
|
+
ws/host, ws/hostRouter browser-safe createGameHost + createHostRouter/loopbackPipe (node re-exports both)
|
|
3925
|
+
ws/peer createPeerHost/createPeerGuest — WebRTC P2P, host tab authoritative, copy-paste signal codes
|
|
3926
|
+
ws/socketIoPipe, node/socketIoServer socketIoPipe/createSocketIoBackend + attachGameSocketIoServer
|
|
3927
|
+
@jgengine/react GameProvider + hooks + headless primitives (incl. identity/chat/voice/social kits); layout only in GameUI.tsx
|
|
3928
|
+
```
|
|
3929
|
+
|
|
3930
|
+
Engine ships verbs and primitives. Your game ships nouns.
|
|
3931
|
+
|
|
3932
|
+
# jgengine-api — Combat
|
|
3933
|
+
|
|
3934
|
+
Reference module for the [`jgengine-api`](../SKILL.md) skill. Load this when you need the combat surface — effects, projectiles, death, feel, and abilities.
|
|
3935
|
+
|
|
3936
|
+
## Effects and projectiles
|
|
3937
|
+
|
|
3938
|
+
Effect ids are **game-defined strings**. Magnitudes **drain** stats: positive subtracts down `receive.<effect>.order` (spilling to the next stat in the order), negative restores. Heals pass a negative amount (`via: { amount: -flashHeal }`, typically read from a `weapon.heal` stat).
|
|
3939
|
+
|
|
3940
|
+
```ts
|
|
3941
|
+
ctx.scene.entity.canReceive(instanceId, effect, magnitude?) // null | reason — reads catalog receive
|
|
3942
|
+
ctx.scene.entity.preview({ from, to, effect, via }) // magnitude, no state change
|
|
3943
|
+
ctx.scene.entity.effect({ from, to, effect, via }) // single target
|
|
3944
|
+
ctx.scene.entity.effect({ from, effect, via, at, radius, falloff?, los? }) // AoE at a point
|
|
3945
|
+
```
|
|
3946
|
+
|
|
3947
|
+
AoE: `inRadius(at, radius)` → LoS filter (default on) → `canReceive` per target → absorption; `falloff: "linear" | "none"`. `via` = `{ item }` (magnitude from weapon stats) or `{ amount }`. `canReceive`'s `pools-depleted` reason checks headroom in the effect's direction: a positive (draining) magnitude needs a stat above its min, a negative (restorative) magnitude needs a stat below its max — so a heal can still raise a stat sitting at its minimum. Omitting `magnitude` assumes the draining direction.
|
|
3948
|
+
|
|
3949
|
+
`canReceive`'s optional signed `magnitude` makes the check direction-aware: positive (or omitted) keeps the standard `"pools-depleted"` check; negative checks the opposite direction and returns `"pools-full"` only when every stat in the receive order is already at max — so a heal (`via: { amount: -n }`) correctly reaches a fully-depleted target and is rejected only when there's nothing left to restore.
|
|
3950
|
+
|
|
3951
|
+
**Projectiles** (aim-based — no target ids):
|
|
3952
|
+
|
|
3953
|
+
```ts
|
|
3954
|
+
willHitProjectile({ from, via, aim, effect }) // prediction only, for crosshair UI
|
|
3955
|
+
fireProjectile({ from, via, aim, effect }) // → shotId (pending)
|
|
3956
|
+
settleProjectile(shotId) // authoritative → { at, hits } | rejection
|
|
3957
|
+
```
|
|
3958
|
+
|
|
3959
|
+
`Aim = { origin, direction } | { yaw, pitch, spread? }`. Hitscan settles into per-hit effects; ballistic shots (`weapon.projectile` with `fuseTime`/`settleOn`) settle to a landing point — the handler then calls `effect({ at: settle.at, radius })`. Settling twice rejects. Prediction is never authority.
|
|
3960
|
+
|
|
3961
|
+
Raycasts are **object-aware**: the default raycast checks placed scene objects as well as entities, discriminated by `RaycastHit.kind` (`"entity" | "object"` — an `ObjectRaycastHit` also carries `catalogId`). A crate or wall between shooter and target blocks or absorbs the shot instead of every projectile passing through scenery; supply `ProjectileSystemDeps.objects` (`{ list(), halfExtents?(catalogId) }`, matching `ObjectStore.list()` structurally) to opt in, with a `[0.5, 0.5, 0.5]` half-extent default per object.
|
|
3962
|
+
|
|
3963
|
+
## Death
|
|
3964
|
+
|
|
3965
|
+
Resolved **once** by the engine when the last stat in the receive order hits min. No HP polling in `onTick`, ever.
|
|
3966
|
+
|
|
3967
|
+
- `entity.died` is emitted (before despawn — handlers can still read the victim's stats), then reason-matching `onDeath` entries run.
|
|
3968
|
+
- `DeathReason = { kind: "player_kill", killerUserId, via? } | { kind: "environment", source } | { kind: "self", source }`. Kills by the local player attribute automatically.
|
|
3969
|
+
- `onDeath.drops` tables are rolled and **granted to the killer** on player kills (emits `loot.granted`) when `onDeath.dropMode` is `"grant"` (default); `onDeath.command` runs through `ctx.game.commands`.
|
|
3970
|
+
- `onDeath.dropMode: "world"` routes item drops through a scatter impulse into ground `worldItem`s instead of straight to inventory (currency drops still grant directly) — tune the impulse with `onDeath.scatter: { radius, minRadius?, height? }` (defaults from `game/worldItem`'s `DEFAULT_SCATTER`).
|
|
3971
|
+
- Respawning under the same instance id revives it (it can die again). Same-id respawn must not happen synchronously inside the `entity.died` handler — defer a tick.
|
|
3972
|
+
- `quest.bind("entity.died")` credits kill objectives from the same event; leaderboards and kill feeds hang off it too.
|
|
3973
|
+
|
|
3974
|
+
## Combat feel (melee, defense, telegraphs)
|
|
3975
|
+
|
|
3976
|
+
Layered on top of effects/projectiles/death — none of it replaces them, it adds **feel**. All models are renderer-free pure `@jgengine/core` factories a game composes per entity (like the `ctx`-vs-factory split above); the shell renders the telegraphs, styled damage numbers, hitstop shake.
|
|
3977
|
+
|
|
3978
|
+
- **Animation state machine** (`combat/animationState`) is the root the rest hangs on. A `AnimationClip` is catalog data — `{ frames, fps, ranges }` where each `FrameRange` tags a window `windup | active | recovery | cancel` (cancel may overlap recovery). `createAnimationState({ clips })` gives a per-entity SM: `play(clipId)`, `tick(dt)` → `{ entered, exited, completed }`, and queries combat/defense subscribe to — `inPhase("active")`, `isActive()`, `canCancel()`, `activeWindowMs()`. Frame ranges are the "commit frame" contract for delayed/feinted attacks.
|
|
3979
|
+
- **Attack tags** (`combat/attackTags`) — `attackMeta(["unblockable" | "thrust" | "sweep" | "grab" | …], { effect, power })`. Defense logic reads them: `isBlockable`, `isParryable`, `isDodgeable`, `counters(meta, "mikiri")`. A grab beats all defenses; an unblockable is parry/dodge-only.
|
|
3980
|
+
- **Defensive window** (`combat/defensiveWindow`) — a parry/block/dodge with `{ startupMs, activeMs, recoveryMs, iframes }`. `resolveDefense({ config, elapsedMs, attack })` is the pure overlap of the defender's window against the moment the attacker's `active` frames land → `parry | block | iframe | hit`. `createDefensiveWindow(config)` tracks the open time (`open(now)` / `evaluate(now, attack)` / `isInvulnerable(now)`).
|
|
3981
|
+
- **Combo strings** (`combat/comboString`) — `ComboStep`s with `cancelInto` + `cancelPhases` + optional `stance`, over the anim SM. `advanceCombo(...)` (pure) accepts the next attack only inside the current step's cancel window and matching stance; `createComboRunner(combo, anim)` drives the SM.
|
|
3982
|
+
- **Meters share one accumulator** (`stats/accumulatorMeter`) — `createAccumulatorMeter({ max, mode: "hold" | "reset", decayPerSecond, decayDelayMs, tiers })`: fill via `add(n)` → `MeterAddResult { fired, overflow, tier, tierChanged }`, `tick(dt)` decays after an idle grace. `combat/breakMeters` builds two on it: `createStaggerMeter` (mode `hold` — fills from hits, `broke()` stays true until `recover()` after a riposte/deathblow) and `createBuildupMeter` (mode `reset` — `add(n)` returns a `BuildupProc { status, durationMs }` at threshold for bleed/frost/rot, then decays). The same base backs G6's ult/streak meters.
|
|
3983
|
+
- **Dash / dodge** (`movement/dash`) — `createDashState({ distance, durationMs, iframes, staminaCost, staminaMax, staminaRegenPerSecond, cooldownMs })`: `tryDash(dir, now)` → burst or `{ reason: "no-stamina" | "cooldown" | "dashing" }`, `isInvulnerable(now)` for the i-frame window, `offset(now)` for the burst displacement, `tick(dt, now)` regens stamina.
|
|
3984
|
+
- **Hit reaction** (`combat/hitReaction`) — `resolveHitReaction(config, { attackerPos, targetPos, power })` (pure) → `{ hitstopMs, impulse, shake }`. Wired on `ctx`: `ctx.scene.entity.hitReaction({ from, to, config, power? })` knocks the target back and emits `combat.hitReaction` (the shell reads `shake` for a trauma channel — feeds a G2 camera-shake rig when present, else the shell's own kick).
|
|
3985
|
+
- **Telegraph** (`combat/telegraph`) — a `TelegraphShape` (`circle | ring | cone | line`) + `windupMs`. `ctx.scene.entity.telegraph({ from, shape, at, dir?, windupMs, kind?, effect? })` emits `combat.telegraph` for the shell to draw a ground decal that fills over the windup, and — if `effect` is bound — applies that effect to everyone inside the shape (`pointInTelegraph`) at activation. Returns a cancel handle. **Turn-scoped variant**, for tactics/turn-based enemy intents instead of a real-time windup: set `TelegraphConfig.turns` (a count of turns instead of `windupMs`), then read `telegraphTurnProgress(config, startedTurn, currentTurn)` (0..1 fill fraction), `telegraphFiredAtTurn(config, startedTurn, currentTurn)` (has it resolved), and `telegraphTurnsRemaining(config, startedTurn, currentTurn)` — the same "enemy will hit this tile" decal, advanced by `turnLoop.round()`/`active()` instead of a clock.
|
|
3986
|
+
- **Damage-number typing** — `ctx.scene.entity.floatText({ …, crit?, element?, hitType?, scale? })` (and the `entity.floatText` event) carry hit metadata; the shell's `resolveFloatTextStyle` (`@jgengine/shell/world/floatTextStyle`) maps crit/element to color + scale + glow.
|
|
3987
|
+
|
|
3988
|
+
## Abilities, resources, auto-target, resistance, run drafts
|
|
3989
|
+
|
|
3990
|
+
Genre systems layered over the same effects/projectiles/targeting/loot primitives — all renderer-free pure `@jgengine/core` factories a game holds per player and ticks on **game-time** `dt` (so pause/fast-forward carry through). The ability kit is deliberately **separate from inventory items**: an item is a stackable id, an ability slot is cooldown/charge/resource state the HUD's four slot-states bind to.
|
|
3991
|
+
|
|
3992
|
+
- **Ability kit** (`combat/abilityKit`) — `createAbilityKit([{ id, cooldownMs, chargesMax?, resourceCost?, castType?, flashMs? }])`. `state(id, resourceAvailable?)` → `AbilitySlotSnapshot { state: "ready" | "cooldown" | "no-resource" | "just-cast", charges, chargesMax, cooldownRemainingMs, cooldownFraction, justCast, ready }`; `cast(id, resourceAvailable?)` consumes a charge, starts the recharge, and flashes just-cast; `canCast` / `tick(dt)` / `reset` / `retuneSlot(id, { cooldownMs?, resourceCost? })` (rebalance a slot at runtime — patches the config in place without touching charges or an in-flight cooldown timer, returns `false` for an unknown slot). The kit is resource-**agnostic** — it reports `no-resource` by comparing `resourceCost` to a supplied `resourceAvailable` (a mana stat, or an ult meter), and never spends the resource itself; the game's handler spends it and calls `cast`. Charges recharge one at a time; the four states drive the hotbar slot art. Cooldowns tick on `dt`, so hang `kit.tick(dt)` in `onTick`.
|
|
3993
|
+
- **Event-fed meters** (`stats/eventMeter`, built on `stats/accumulatorMeter`) — `createEventMeter({ max, mode, gains, resets?, tiers?, decayPerSecond? })`. `feed(tag, scale?)` maps a tagged combat event to a gain (or a reset when `tag ∈ resets`) → `EventMeterFeedResult { fired, ready, tier, tierChanged, reset }`. Mode `"hold"` is the **ult/adrenaline** economy — charges off `{ damageDealt, damageTaken, kill }` events, `ready()` at full, `consume()` spends it (Overwatch/Marvel Rivals). Mode `"reset"` is the **streak/combo** meter — builds on `kill`, resets on a break tag like `damageTaken`, climbs ascending `tiers` (Returnal adrenaline, DMC style rank). One primitive, two catalog configs.
|
|
3994
|
+
- **Auto-target policy** (`scene/autoTarget`) — `selectAutoTarget(policy, fromId, deps)` / `createAutoTargeter(policy, deps)` evaluated each tick with zero input: `"nearest" | "farthest" | "random" | "strongest" | "weakest" | "first" | "last"`. `deps` supplies `candidates`/`distance` and optional `strength` (health/threat) + `progress` (path progress for first/last-on-path). Bullet-heaven auto-fire (Vampire Survivors nearest), Bloons tower priority (first/last on path). Feed the picked id to `abilityKit.cast` + `effect`.
|
|
3995
|
+
- **Resistance matrix** (`combat/resistance`) — a damage-category × target-property table distinct from `combat/attackTags` (those are defense tags). `resolveResistance(matrix, category, targetProperties)` → `{ verdict: "immune" | "resist" | "normal" | "vulnerable", multiplier, immune }`; `resistanceScale` returns just the multiplier (default `immune:0, resist:0.5, normal:1, vulnerable:2`, overridable). Immune on any property wins; resists stack multiplicatively. Sits over the `receive` gate — the handler multiplies its effect `amount` by the scale (Bloons lead/camo immunities, elemental RPG weaknesses).
|
|
3996
|
+
- **Run draft** (`game/runDraft`, built on `world/scatterItems` `pickWeighted` + `stats/statModifiers`) — `createRunModifierStack(offers)` accumulates stacking picks (`add`, `count`, `atMax`, `total(stat)` aggregating adds × exponentiated multiplies, `apply(stats)` onto a `createStats` source). `createRunDraft({ offers, rng? })` adds the pause-flow: `present(n)` draws N distinct weighted offers (excluding maxed-out ones), `choose(id)` applies to the stack. Pause/resume is game-side (`ctx.time.pause()` before `present`, `play()` after `choose`). Vampire Survivors level-up picks, Hades boons, Risk of Rain stacking items.
|
|
3997
|
+
|
|
3998
|
+
# jgengine-api — World features
|
|
3999
|
+
|
|
4000
|
+
Reference module for the [`jgengine-api`](../SKILL.md) skill. Load this when you need the renderer-free world surface.
|
|
4001
|
+
|
|
4002
|
+
## World features
|
|
4003
|
+
|
|
4004
|
+
Descriptors from `@jgengine/core/world/features` — config data the runner/world layer interprets:
|
|
4005
|
+
|
|
4006
|
+
| Feature | Use |
|
|
4007
|
+
|---------|-----|
|
|
4008
|
+
| `biomes({ map, zones, bounds? })` | Region atmosphere/rules layering; zones reference biome ids |
|
|
4009
|
+
| `voxel({ seed, generate?, streaming? })` | Block worlds |
|
|
4010
|
+
| `plots(config)` | Shared city + instanced interiors |
|
|
4011
|
+
| `tilemap({ map })` | 2D/2.5D levels |
|
|
4012
|
+
| `flat()` | Plain arena |
|
|
4013
|
+
| `environment({ terrain, sky, weather, vegetation, water, structures, pads })` | Composable outdoor scene — terrain + sky/time-of-day + rain/snow + grass + ocean + buildings + ground pads. Each field takes the matching descriptor: `terrain()`, `sky()`, `rain()`/`snow()`, `grass()`, `ocean()`, `building()`, `pad()`. `building()` and `ocean()` take `position: [x, z]` to site a cluster/water body away from the origin (several settlements, an offset lake); building clusters ground-snap to the terrain field per building; each `pad()` (a flat platform/paved patch — `{ center, size: [w,d] | { radius }, height?, color?, rotationY? }`) implicitly flattens the terrain beneath it via a `TerrainFlattenMask`, so a building pad or spawn circle never fights the noise field underneath |
|
|
4014
|
+
|
|
4015
|
+
`biomes`/`voxel`/`plots`/`tilemap` share a `WorldGridConfig` (`cells?: WorldGridCell[]`, `cellSize?`, `baseHeight?`, `defaultColor?`) — a `WorldGridCell` is `{ x, z, height?, color? }`, one extruded box per cell. `resolveGridInstances(config)` (`@jgengine/core/world/gridInstances`) is the pure cell→instance math (position, scale, color per cell); the shell renders the result as a single `THREE.InstancedMesh` **automatically whenever `PlayableGame.environment` is unset and `game.world` is one of these four grid kinds** — no manual render wiring for a cell-based world, same auto-render convention as `environment()` worlds.
|
|
4016
|
+
|
|
4017
|
+
`terrain()`'s `material` (a named palette — `"grass" | "sand" | "snow" | "rock" | "ash"`, resolved via `resolveTerrainPalette`/`TERRAIN_MATERIAL_PALETTES` in `world/terrain`) sets the default low/high/waterline colors; `colors: { low?, high?, waterline? }` overrides any of them field-by-field, and `segments` tunes the render mesh's subdivision. `flatten: TerrainFlattenMask[]` (`{ center, radius, height?, falloff? }`) carves explicit flat circles into the noise field independent of pads — building foundations, spawn circles, roads — blending back to the noise height over `falloff` (default `radius * 0.5`). `sky({ preset?, timeOfDay?, horizonColor?, zenithColor?, sunIntensity?, ambientIntensity?, fog? })` — `preset: "day" | "dusk" | "night"` (default `"day"`) is the static look; `timeOfDay: true` instead drives sun position, sky colors, and fog from the world clock's `calendar().dayFraction` every frame (`@jgengine/shell`'s `TimeOfDayDaylight` mounts this automatically for an `environment()` world with `sky` set — no per-game render wiring). **Gotcha:** `sunIntensity` / `ambientIntensity` are honored only on the **day** keyframe (`daylightCycle` builds dusk/night/dawn from fixed constants). Raising intensities under `preset: "dusk"` or `"night"` does nothing — use `preset: "day"` plus warm `horizonColor`/`zenithColor` when the first screenshot must be bright and readable (see `jgengine-newgame`'s first-shot art recipe).
|
|
4018
|
+
|
|
4019
|
+
`parentSpace` positions are local to that space — convert at seams only.
|
|
4020
|
+
|
|
4021
|
+
### Query primitives (renderer-free, for gameplay)
|
|
4022
|
+
|
|
4023
|
+
Pure `@jgengine/core` functions so gameplay reads the same world the shell renders — no three.js needed:
|
|
4024
|
+
|
|
4025
|
+
| Primitive | Answers |
|
|
4026
|
+
|-----------|---------|
|
|
4027
|
+
| `resolveTerrainField(terrain(...))` / `noiseField(cfg)` → `TerrainField` | `sampleHeight(x,z)`, `sampleNormal(x,z)`, `waterLevel` — ground-snap, collision, camera. `resolveGroundStep` slope-limits movement |
|
|
4028
|
+
| `snapToGround(field, position, offset?)` → `[x,y,z]` | Replaces a position's `y` with the field's sampled ground height (+ `offset`) at its `x`/`z` — the pure version of a spawn/placement ground-snap |
|
|
4029
|
+
| `snapEntityToGround(entities, id, field, offset?)` → `boolean` | Ground-snaps an already-spawned entity in place via `entities.setPose` — false when `id` is unknown; the imperative sibling of `snapToGround` for "drop this entity onto the terrain right now" (mount dismount, teleport, respawn) |
|
|
4030
|
+
| `windField(cfg)` → `WindField` | `at(t)`, `atPoint(x,z,t)`, `strengthAt` — one wind source for weather sway, grass, sailing, fire spread |
|
|
4031
|
+
| `waterSurface(cfg)` / `waterSurfaceFromDescriptor(ocean(...))` → `WaterSurface` | `height(x,z,t)`, `normal`, `displace` — buoyancy, floating, shoreline (CPU Gerstner matching the ocean shader) |
|
|
4032
|
+
| `scatter(cfg)` → `ScatterPoint[]` | Seeded, overlap-aware point distribution — vegetation, props, lots, spawn points (`minDistance`, `avoid` rects) |
|
|
4033
|
+
| `createRegionField({ regions })` → `RegionField` | `sampleRegion(x,z)` blends content-agnostic biomes by nearest selector — height + `tint`/`water`/`fog`/`speedMultiplier` + opaque `data`. Extends `TerrainField`, so it ground-snaps too |
|
|
4034
|
+
| `scatterItems(field, area, layersFor)` → `ScatterInstance[]` | Region-driven content scatter — density per region, grounded, above-water/slope-aware. `pickWeighted` for weighted rolls. (vs `scatter`'s pure geometric points) |
|
|
4035
|
+
| `buildingIndex(district)` → `BuildingIndex` | `at`/`within`/`nearest`/`isInside`/`blockers` over a generated district — placement avoidance, pathfinding |
|
|
4036
|
+
|
|
4037
|
+
**Voxel field (`world/voxelField`).** `createVoxelField<T extends string>({ chunkSize? })` (default 16) is a logical block lattice for voxel games and instanced renderers — distinct from the `voxel()` `WorldFeature` descriptor above (that's the runner-level world kind; this is the block data structure a voxel game's gameplay reads and writes). `set`/`remove`/`get`/`has`/`fill`/`clear`/`count`/`cells`/`bounds` are the block CRUD (`set` returns `false` only when the identical type is already there — a no-op write). `neighbors(x, y, z)` returns the 6-adjacent occupied cells; `exposedFaces(x, y, z)` returns the `VoxelFace`s (`"px"|"nx"|"py"|"ny"|"pz"|"nz"`) not touching another voxel — feed that straight into greedy-meshing/face-culling. `raycast(origin, direction, maxDistance)` runs a 3D DDA and returns `{ x, y, z, type, face, adjacent, distance }`, where `adjacent` is the empty cell just in front of the hit — the placement target for block-place tools. Renderers dirty-track via `chunkOf(x, y, z)` + `chunkVersion(chunk)` and `subscribe(listener)`, so an instanced mesh only rebuilds the chunks that changed. For a non-`environment()` voxel world, assert on `field.summary()` (`{ blocks, types, bounds }`) the same way an `environment()` world asserts on `summarizeEnvironment` (see `jgengine-verify`).
|
|
4038
|
+
|
|
4039
|
+
**Destructible terrain (`world/carve`).** Two runtime-editable primitives for dig/carve worlds. `VoxelVolume` is a dense grid of material ids (0 = empty) — `carve({ center, radius, toolStrength })` clears a sphere of solid cells the tool is strong enough to break and returns the count removed (feed a loot roll), `deposit({ center, radius, material })` fills one (Deep Rock tunnels, Astroneer terrain add); `solidAtWorld` reads it back for collision. `CarvableField` (via `carvableTerrain(base)`) wraps any `TerrainField` and writes craters/mounds into its height — `carve({ x, z, radius, depth })`/`deposit({ x, z, radius, height })` — so ground-snap, collision, and the shell mesh all read the deformed surface (Helldivers 2 explosion craters). Cell strengths come from a `VoxelMaterial` table (DATA). Renders through `@jgengine/shell/terrain/CarvedTerrain`.
|
|
4040
|
+
|
|
4041
|
+
Renderers for these descriptors live in `@jgengine/shell` (`shell/terrain`, `shell/water`, `shell/weather`, `shell/structures`).
|
|
4042
|
+
|
|
4043
|
+
### Environment fields, weather hooks & realm composition
|
|
4044
|
+
Renderer-free survival/environment primitives that extend the world query layer — meters, spawn gating, and damage-in-sunlight read the same world the shell renders, all ticking on game-time `dt`.
|
|
4045
|
+
- **Environment field** (`world/envField`): `createEnvironmentField({ dayLength, baseTemperature, nightDrop, altitudeLapse, terrain, rain, occluders, heatSources, ambientFloor, temperatureAt })` → `EnvironmentField`. Sample **temperature**, **wetness**, **lightExposure** (direct sun/sky), and **ambientLight** (spawn gating) at any `(x, z, time)` — `sample(x, z, time, y?)` returns all four plus `sheltered`. Occluders (roofs/canopy) shade sun and shelter from rain; heat sources (campfires) warm nearby positions; `sunElevation(time)` drives the day cycle. Sun damages a vampire, cold forces campfires, low ambient light spawns mobs — the field answers "am I in sun vs. shade / cold vs. warm / dark vs. lit". Pure and instantaneous; stateful build-up belongs to a decay meter reading the field.
|
|
4046
|
+
- **Weather → gameplay** (`world/weather`): `resolveWeather(state, table)` turns a `WeatherState { kind, intensity }` into concrete `ResolvedWeather` (`grip`, `visibility`, `structureDamage`, `chill`, `ignition`, `spread`) via a game-owned `WeatherModifierTable` — multipliers interpolate from neutral by intensity, rate effects scale linearly. Read `grip`/`visibility` in movement and AI, `structureDamage` on a building tick.
|
|
4047
|
+
- **Fire spread** (`world/weather`): `createFireGrid({ cols, rows, cellSize, origin, fuelAt, spreadRate, burnRate, wind, windBias })` → `FireGrid` is a **coarse cellular** propagation (not a fluid solver): `ignite(x, z)` / `igniteCell(col, row)`, then `step(dt, { spread, wetnessAt })` transfers heat to neighbours biased by wind, consumes fuel (`unburnt → burning → burnt`), and honours firebreaks (zero-fuel cells) and rain/wetness suppression. `resolveWeather(...).spread` feeds the step; `@jgengine/shell/weather` `FireSpreadLayer` renders the burning/scorched cells.
|
|
4048
|
+
- **Realm composition** (`world/realm`): `composeRealm(base, cards)` assembles a played instance at runtime from a deck of modifier **cards** (Nightingale realm cards) — a `major` card is the biome base, `minor` cards layer environment param overrides, a `WeatherState`, and spawn-table edits (`set`/`add`/`scale`/`remove`). The result recomposes both the environment (into a sampleable field via `composed.environmentField(extra?)`) and the `spawnTable`, and depends on the weather hooks above to turn its `weather` into gameplay modifiers.
|
|
4049
|
+
### Survival meters, moodles & multi-region health
|
|
4050
|
+
The `survival/` domain — decay-over-time meters and per-part health, both feeding one stacking **moodle** status display distinct from numeric bars.
|
|
4051
|
+
- **Decay meters** (`survival/decayMeter`): `createDecayMeterSet([{ id, max, min?, start?, rate, thresholds }])` → `DecayMeterSet`. Each named meter (hunger, thirst, oxygen, sanity, warmth, stamina) drains/recovers on `tick(dt)` at `rate`, refills from consumables/actions via `refill(id, amount)`, and raises threshold moodles (`below`/`above`). `setRateModifier(id, mult)` lets the environment drive them — read an env field, then speed warmth loss when cold or oxygen loss in a toxic biome.
|
|
4052
|
+
- **Moodles** (`survival/moodle`): the shared status stack, distinct from raw bars. `stackMoodles(...groups)` folds meter, ailment, and buff `Moodle[]` into one worst-first display (same-id stacks add, worst severity wins). `createMoodleStack()` holds timed buffs (`add({ id, label, duration })` — Valheim's concurrent food buffs) and expires them on `tick(dt)`.
|
|
4053
|
+
- **Multi-region health** (`survival/regionHealth`): `createMultiRegionHealth({ regions, ailments })` → `MultiRegionHealth` gives per-part pools (head/thorax/arms/legs, Tarkov/DayZ style) — `damage(regionId, amount)` scales by `vulnerability` and kills when a `vital` part empties; a stacking **ailment queue** (`applyAilment`, `tick(dt)` drains like bleed) carries per-injury treatment (`treat(itemId)` clears wounds via bandage/tourniquet/splint). `ailmentMoodles()` shares the moodle display with the meters (#78 + #90).
|
|
4054
|
+
### Interactive building & terraform (renderer-free tools)
|
|
4055
|
+
Turn data-only placement into the build tooling of Valheim/Enshrouded/The Sims/Fortnite/Dinkum. All pure `@jgengine/core/world`; the shell renders the ghost/tint/brush (`shell/structures/PlacementGhost`, `shell/terrain/EditableGround`, `shell/terrain/TerraformBrushCursor`) driven by `pointer.worldHit()`.
|
|
4056
|
+
| Primitive | Answers |
|
|
4057
|
+
|-----------|---------|
|
|
4058
|
+
| `createPlacementController({ footprint, rules, snapMode, grid })` | Owns the ghost: `hover(hit)` → `PlacementPreview` (`valid` tint wraps `validatePlacement`), `rotate()`, `setSnapMode`/`cycleSnapMode` (`"grid"`/`"free"`/`"surface"`), `commit()` → `PlacementCommit` (`rotationY` via `quarterTurnsToRotationY`). Feed it `pointer.worldHit()`. |
|
|
4059
|
+
| `snapToNearest(registry, placed, movingDef, cursor, { snapDistance })` | Typed connector sockets — snaps a piece's socket onto the nearest **compatible** placed socket (`socketsCompatible` = both sides `accept` the other type). `worldSockets`/`socketWorldPosition` expand a piece's sockets to world space. |
|
|
4060
|
+
| `solveSupport(pieces, links, { maxDistance })` → `SupportResult` | Walks the connector graph to any `grounded` piece: `supported` stays, `unsupported` collapses, `distance` (hops-to-ground) drives the white→red decay tint. `toDebrisBodies(pieces, unsupported)` → `AddBodyOptions[]` for the `PhysicsWorld` debris sink. |
|
|
4061
|
+
| `createWallDrawTool({ snap, closeTolerance })` | Drag wall points → auto-encloses when the path returns to the start (`isEnclosed`), `footprint()` derives the room `EnclosedFootprint`, `roof()` auto-fits a hip/gable/flat `RoofPlan`. `createSurfacePaint()` stores per-tile floor/wall surfaces. |
|
|
4062
|
+
| `createPlacedStructureStore()` | Save/load a built layout: `add`/`move`/`rotate`/`remove`/`select`, `snapshot()`↔`load()` round-trip (survives reload), `subscribe` for the renderer. |
|
|
4063
|
+
| `createEditableTerrain({ bounds, base, cellSize })` → `EditableTerrain` | A `TerrainField` you can **write back to**: `apply(edit: TerraformEdit)` raises/lowers/flattens/paints under a cursor and re-samples `sampleHeight`; `surfaceAt`, `snapshot`/`restore`, `reset`. `createTerraformBrush(field)` is the cursor tool (`raise`/`lower`/`flatten`/`paint`, radius/strength). This write-back grid is the shared terrain-edit pattern. |
|
|
4064
|
+
| `createPlotPermissions({ plotId, ownerId, guildId? })` + `createContributionPool(goal)` | Per-plot/guild edit authority (`canEdit`/`canView`, `grant`/`revoke` `BuildRole`, guild inheritance) for co-op building, plus a pooled-resource contribution model (`contribute` caps at the goal, reports overflow, `isComplete`, per-contributor totals). |
|
|
4065
|
+
|
|
4066
|
+
### Physics world (optional, headless)
|
|
4067
|
+
|
|
4068
|
+
`physics/physicsWorld` `PhysicsWorld` is a standalone fixed-capacity rigid-body sim (SoA buffers, spatial-hash broadphase, sleeping) — **not** the `defineGame` `physics: { gravity, jumpVelocity }` field, which the built-in walk controller reads directly every frame (see "Controller kinematics" above; both values are real and honored, not dead config). Reach for `PhysicsWorld` when a game needs many colliding dynamic bodies (piles, debris, stress scenes): `new PhysicsWorld({ capacity, bounds, … })`, `addBody({ position, mass?, ...shape })`, then `step(dt)` per tick → `PhysicsStats`. Core owns the sim; `@jgengine/shell/world/InstancedBodies` renders its bodies. Most games never need it — the character controller covers ordinary movement. The broadphase grid (`nx*ny*nz` cells from `bounds`/`cellSize`) throws at construction if it would exceed a sane cell cap — shrink `bounds` or raise `cellSize` (same guard on `physics/spatialGrid`'s `SpatialGrid`).
|
|
4069
|
+
|
|
4070
|
+
**Body shape: box or sphere.** `AddBodyOptions` is a discriminated union — `{ shape?: "box", halfExtents }` (box is the default, `shape` omittable) or `{ shape: "sphere", radius }` (the radius fills all three half-extent columns, so broadphase/bounds see the sphere's enclosing AABB). `world.shape[i]` (`SHAPE_BOX` / `SHAPE_SPHERE`) reports a live body's shape for a consumer walking the raw SoA arrays. Sphere-sphere and sphere-box pairs resolve with a proper radial normal (not the axis-aligned box/box path) — balls, projectile bodies, and rolling debris collide correctly against both boxes and each other.
|
|
4071
|
+
|
|
4072
|
+
**Collision shapes are box/sphere/voxel only.** Every collider in the engine — `PhysicsWorld` bodies (box `halfExtents` or sphere `radius`), object/entity picking (`scene/objectQuery` raycasts against unit boxes), and `world/voxelField`/`world/carve` blocks — is an axis-aligned box, a sphere, or a voxel cell. Arbitrary authored level-mesh collision (a sculpted GLB as a collider) is not supported. The seams for custom collision are `movement.beforeCommit` (steer or replace the walk controller's resolved step) and object raycasts (query the scene yourself and react) — not a mesh collider.
|
|
4073
|
+
|
|
4074
|
+
**Ballistic collision sweep (`physics/ballisticSweep`).** `createBallisticSweep(world, { step?, radius? })` → `BallisticSweep`, a `(origin, velocity, gravity, maxTime) => BallisticSweepHit | null` function that marches the closed-form arc (constant gravity, straight lateral) through a `PhysicsWorld` and reports the first sample inside any live body's AABB (sleeping bodies included), refined by one bisection step; `null` means the whole arc is clear. `step` (default 1/60) is the march interval in seconds, `radius` (default 0) inflates every body's AABB before the point test — pass the projectile's own radius. Wire it into `combat/projectiles` via `ProjectileSystemDeps.sweepBallistic`: when set, a ballistic shot settles at the swept impact point instead of the closed-form landing; omitted or `null` falls back to that closed-form arc.
|
|
4075
|
+
|
|
4076
|
+
**Removing and moving bodies.** `removeBody(id)` tombstones a body — it drops out of integration/broadphase and its slot is queued for the next `addBody` — without moving or invalidating any other body's `id` (ids are raw SoA slots, stored as-is in joints and game state, so nothing ever gets swapped). It conservatively wakes any sleeping body whose AABB touched the removed one's (no persistent contact set to consult, so this errs toward waking too much, never too little). `setVelocity(id, x, y, z)` and `setPosition(id, x, y, z)` write a body's velocity/position directly (instead of poking the public `velX`/`posX` SoA arrays) and wake it if asleep; `teleport(id, x, y, z)` is `setPosition` plus a hard velocity reset (respawn/teleporter, vs. sliding). `isAlive(id)`/`highWater` (one past the highest slot ever handed out) let a consumer that iterates the raw SoA arrays skip tombstoned holes correctly instead of assuming `count` is a dense `0..count` range.
|
|
4077
|
+
|
|
4078
|
+
**Joints & constraints.** `hingeJoint`/`fixedJoint`/`distanceJoint`/`springJoint(opts)` connect two bodies, or a body to a fixed world point (omit `bodyB`). The sim is translational (no angular DOF), so `hinge`/`fixed` pin the shared anchor (the `axis` is retained metadata), `distance` holds a fixed separation, and `spring` drives toward `restLength` with `stiffness`/`damping` (suspension, follow-point carry). `removeJoint(id)`, `setJointAnchor(id, x, y, z)` (move anchor B — a world anchor's follow point, or body B's local offset), `setJointAnchorA(id, x, y, z)` (move anchor A's local offset — e.g. re-rotating a suspension mount each frame as the chassis turns), `setJointRest`, and `readJointSegments(out)` for `@jgengine/shell/world/InstancedJoints` (debug line render). This is the foundation under vehicles, ragdolls, grapples, and carry.
|
|
4079
|
+
|
|
4080
|
+
**Collision → gameplay events.** `world.onCollision(listener, minApproachSpeed?)` delivers every impacting contact — `CollisionEvent { a, b, nx, ny, nz, approachSpeed, impulse }` — to game code during `step` (the object is reused; read/copy it, never retain). This is the seam crash-damage and destruction read; pass `null` to detach.
|
|
4081
|
+
|
|
4082
|
+
**Actors on top of the sim:** `physics/ragdoll` (`createRagdoll(world, { bones, links, balance? })` — jointed bones, floppy or active-ragdoll via a balance motor), `physics/carryable` (`Carryable` — grab a body to a follow point, shared multi-owner carry, `carrySpeedMultiplier` encumbrance, drop/throw; the raycast pick is the caller's job, core owns the constraint), `physics/forceVolume` (`ForceVolume` — impulse/velocity/accelerate trigger region, `once` for boost pads; `PlatformCarry` — carry bodies standing on a moving platform by its per-`step` delta). Separately, `physics/spatialGrid` `SpatialGrid` is a broad-phase grid over the x/z plane, **distinct** from the rigid-body sim, for cheap same-tick proximity across hundreds–thousands of simple movers — `rebuild(count, xs, zs)` then `queryCircle` (swarm enemies hitting a player/AoE) or `forEachPair` (mutual separation).
|
|
4083
|
+
|
|
4084
|
+
**Traversal (`physics/traversal`).** `Grapple` fires a rope from a body to a fixed world point on the joint API — `fire(x,y,z)` attaches a `distance` (rigid) or `elastic` (spring) joint, `reel(dt)`/`payOut(dt)` shorten/lengthen the rope to pull the traveller in, `moveAnchor` re-points it (ziplines, grapple-to-moving-target). Grapple/zipline/swing (Sekiro, Deep Rock, Just Cause) are all the same primitive; the raycast that finds the anchor is the caller's. `Glide` is a reduced-gravity, forward-thrust wingsuit/glider over a body — call `apply(dt, steerX, steerZ)` each frame before `step` to feed back most of gravity (`gravityScale`), thrust along the steer vector, and clamp descent; stop calling it to fall normally, no attach/detach state.
|
|
4085
|
+
**Structural destruction (`physics/structure`).** `StructureGraph` models a building as nodes (pieces) + load-bearing edges with some nodes `anchor`ed (foundations). `damage(id, n)`/`damageEdge(a,b,n)`/`severEdge(a,b)` wear pieces and connections; when one breaks, the graph recomputes reachability to an anchor and returns a single `CollapseEvent { fell }` — every piece the loss disconnected. `toDebris(world, event)` sinks the fallen pieces into a `PhysicsWorld` as rigid bodies (The Finals, Rainbow Six). It is coarse by design: **replicate the collapse event (the `fell` id list), not each fragment's physics** — game clients re-derive the debris locally. Piece integrity and edge strength default from a `StructureMaterial` table (DATA).
|
|
4086
|
+
### Vehicles, mounts, crash damage & racing
|
|
4087
|
+
Five primitives layer a driving/racing game over the physics sim and `world/water`. All are **data-first** (spec the chassis/wheels/grip curve, damage thresholds, and checkpoint layout as catalog data) and pure `@jgengine/core`; renderers live in the game/shell. Each `update(dt, …)` runs **before** the shared `world.step(dt)`.
|
|
4088
|
+
- **Analog input — `input/axisInput`.** `AxisInput { throttle, brake, steer, handbrake }` is a continuous channel, **distinct from the digital action bindings**. `new AxisChannel({ bindings, smoothing })` ramps held keys into pedal-like analog values (`sample(dt, isDown)`), or `setAnalog(axis, value)` drives it straight from a gamepad axis. `DRIVE_AXIS_BINDINGS` is a ready WASD/arrow map.
|
|
4089
|
+
- **`physics/vehicleBody`.** `createVehicleBody(world, config)` is an arcade car: a chassis box body with per-wheel suspension held by G3's `springJoint` against the sampled `groundHeight`, drive/brake along the heading, and a `GripCurve` (`sampleGripCurve`) that bleeds lateral velocity for cornering — and, under `handbrake`, drift. `update(dt, axisInput)` then `world.step`. Because the chassis is a real body it still collides, which feeds crash damage. Rocket League, Trackmania, Wreckfest.
|
|
4090
|
+
- **`physics/buoyancy`.** `createBuoyantBody(world, { body, water, … })` floats a body on a CPU `WaterSurface` (Archimedes per hull point + water drag) so it settles at the waterline and rides the Gerstner waves; pass an `AxisInput` to `update(dt, time, input?)` and it drives as a boat (thrust + yaw + keel). Sea of Thieves, BOTW rafts.
|
|
4091
|
+
- **`scene/mount`.** `createMountController()` transfers control to a driven entity: `register({ id, kit, seats })`, `mount(riderId, mountId, seatId?)`, `dismount`. Read `cameraTarget(riderId)` to point the follow camera and `driveTarget(riderId)` to route that rider's `AxisInput` at the mount — the control seat drives, passenger seats ride (multi-seat shared vehicles), and an un-mounted rider drives themselves. `driver(mountId)`/`occupants(mountId)`/`kitOf`. Palworld mounts, V Rising horse, a crewed ship.
|
|
4092
|
+
- **`scene/stationClaim`.** `createStationClaim(controller?)` layers **facet stations** on `scene/mount` for a vehicle several players crew at once: `register({ id, kit, stations })` where each `Station` tags a seat with a `facet` (`"steer"`/`"sails"`/`"cannon"`). `claim(playerId, vehicleId, facetOrStationId)`, `release`, `controllerOf(vehicleId, facet)` (who mans it), `facetOf(playerId)`, `openFacets`, `crew`. Only a `control` station operates the hull (`driver`/`driveTarget`); the rest ride but command their own facet. Sea of Thieves helm + sails + cannons.
|
|
4093
|
+
- **`physics/damageZones`.** `createDamageModel({ zones, disableAt })` maps accumulated contact impulse (from `onCollision`) to **coarse discrete stages** (not soft-body): `absorb(zoneId, impulse)` / `routeCollision(event, resolveZone)` bump a zone's stage (caller swaps the visual/collider), an optional `detachStage` ejects a part as debris once, and crossing `disableAt` flips a whole-vehicle `disabled` state. Wreckfest crumple/derby.
|
|
4094
|
+
- **`game/race`.** `raceTrack({ checkpoints, laps })` is an ordered ring of AABB checkpoint volumes (the final one is the finish line); `createRaceState({ track, win })` — driven each tick by `update(now, positions)` on game time — emits `checkpoint.hit` / `lap.completed` / `position.changed` / `race.finished`, keeps split times, resolves a pluggable `RaceWinCondition` (`firstPastPost`, `topK` round-cut, `everyoneFinishes`, `lastStanding` derby), and `resetToCheckpoint(id)` hands back a respawn pose. `removeRacer(id)` drops a racer mid-race and renumbers the remaining standings; `reset()` clears all racer progress/finish state back to construction time so the same instance replays without rebuilding it. Trackmania, Mario Kart, Fall Guys.
|
|
4095
|
+
|
|
4096
|
+
### Spawn placement
|
|
4097
|
+
|
|
4098
|
+
`spawn(catalogId, { id?, position | anchor, offset?, parentSpace?, group? })` — anchor `{ kind: "entity" | "zone", id }` with offset `{ radius, pattern }` or `{ xyz }`. Catalog supplies movement/model; no behaviors on spawn.
|
|
4099
|
+
|
|
4100
|
+
**Named spawn/respawn points** (`game/spawnPoints`) — `createSpawnPoints()`: `record(id, { x, y, z, rotationY? })` names a point (level bounds, team spawns, checkpoints), `get(id)`/`list()` read them back, `respawn(entities, entityId, spawnId)` teleports an existing entity to a recorded point via `setPose` in one call — the id-keyed alternative to threading raw coordinates through respawn logic by hand.
|
|
4101
|
+
|
|
4102
|
+
**Level sequence** (`game/levelSequence`) — `createLevelSequence({ levels: [{ id, config }], retriesPerLevel? })` is a pure, deterministic campaign machine: `start()` enters level 0, `clear()` marks the current level cleared, `advance()` moves to the next (or `"complete"` after the last), `fail()` consumes an attempt and returns `"retry"` while `retriesPerLevel` remain else `"failed"`, `retry()` restarts after a retry-eligible failure. `current()` → `{ id, index, config, attempt } | null`; `progress()` → `{ index, total, cleared }`; `reset()` rewinds to idle. Mirrors the reducer style of `game/race` and `ai/spawnDirector` — a level-select/roguelike-run campaign shell without hand-rolling the state machine per game.
|