@jgengine/shell 0.18.1 → 0.21.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 +504 -7
- package/dist/GameHost.d.ts +12 -1
- package/dist/GameHost.js +14 -1
- package/dist/GamePlayerShell.js +132 -17
- package/dist/Shell3dPresentation.d.ts +5 -11
- package/dist/Shell3dPresentation.js +73 -11
- package/dist/ShellChrome.d.ts +2 -1
- package/dist/ShellChrome.js +22 -6
- package/dist/ShellHudPresentation.d.ts +20 -2
- package/dist/ShellHudPresentation.js +41 -6
- package/dist/audio/AudioComponents.js +6 -2
- package/dist/audio/audioEngine.d.ts +24 -0
- package/dist/audio/audioEngine.js +231 -83
- package/dist/audio/audioWire.d.ts +3 -0
- package/dist/audio/audioWire.js +9 -3
- package/dist/audio/loopParams.d.ts +39 -0
- package/dist/audio/loopParams.js +43 -0
- package/dist/audio/synthEngine.d.ts +7 -1
- package/dist/audio/synthEngine.js +103 -38
- package/dist/camera/GameCameraRig.js +3 -2
- package/dist/camera/GameFirstPersonCamera.d.ts +6 -4
- package/dist/camera/GameFirstPersonCamera.js +68 -27
- package/dist/camera/Viewports.d.ts +34 -0
- package/dist/camera/Viewports.js +123 -0
- package/dist/camera/cameraRigs.js +76 -62
- package/dist/camera/chaseHeading.d.ts +9 -0
- package/dist/camera/chaseHeading.js +10 -0
- package/dist/camera/customMuzzle.d.ts +14 -0
- package/dist/camera/customMuzzle.js +68 -0
- package/dist/camera/index.d.ts +1 -0
- package/dist/camera/index.js +1 -0
- package/dist/camera/orbitCameraMath.js +2 -2
- package/dist/camera/rigMath.d.ts +82 -4
- package/dist/camera/rigMath.js +133 -15
- package/dist/camera/rigResolve.d.ts +3 -0
- package/dist/camera/rigResolve.js +11 -0
- package/dist/combat/ProjectileModels.d.ts +31 -0
- package/dist/combat/ProjectileModels.js +70 -0
- package/dist/commandSink.d.ts +1 -2
- package/dist/commandSink.js +3 -2
- package/dist/defineGame.d.ts +19 -3
- package/dist/defineGame.js +39 -7
- package/dist/devtools/DevtoolsOverlay.d.ts +2 -69
- package/dist/devtools/DevtoolsOverlay.js +15 -167
- package/dist/devtools/DevtoolsRuntime.d.ts +7 -0
- package/dist/devtools/DevtoolsRuntime.js +61 -0
- package/dist/devtools/agentBridge.d.ts +6 -0
- package/dist/devtools/agentBridge.js +16 -2
- package/dist/devtools/devtoolsReports.d.ts +68 -0
- package/dist/devtools/devtoolsReports.js +100 -0
- package/dist/devtools/latencyInstrumentation.d.ts +3 -0
- package/dist/devtools/latencyInstrumentation.js +11 -0
- package/dist/devtools/playtestProbe.d.ts +4 -0
- package/dist/devtools/playtestProbe.js +22 -0
- package/dist/diagnostics/FpsCounter.d.ts +17 -0
- package/dist/diagnostics/FpsCounter.js +32 -0
- package/dist/drivers/FrameDriver.js +35 -20
- package/dist/drivers/FrameRateLimiter.d.ts +9 -0
- package/dist/drivers/FrameRateLimiter.js +39 -0
- package/dist/drivers/HudOnlyDriver.d.ts +6 -1
- package/dist/drivers/HudOnlyDriver.js +32 -14
- package/dist/environment/DayNightSky.js +5 -5
- package/dist/environment/Daylight.d.ts +35 -1
- package/dist/environment/Daylight.js +98 -28
- package/dist/environment/EnvironmentScene.js +11 -10
- package/dist/environment/VolumetricClouds.js +20 -2
- package/dist/environment/daylightCycle.d.ts +20 -0
- package/dist/environment/daylightCycle.js +59 -7
- package/dist/environment/sunShadowMath.d.ts +45 -0
- package/dist/environment/sunShadowMath.js +50 -0
- package/dist/input/gamepadPoll.d.ts +47 -0
- package/dist/input/gamepadPoll.js +142 -0
- package/dist/input/gamepadSource.d.ts +11 -1
- package/dist/input/gamepadSource.js +117 -71
- package/dist/input/mouseLook.js +2 -1
- package/dist/input/padHaptics.d.ts +17 -0
- package/dist/input/padHaptics.js +32 -0
- package/dist/input/pointerLock.d.ts +11 -0
- package/dist/input/pointerLock.js +19 -0
- package/dist/inputSink.d.ts +6 -6
- package/dist/inputSink.js +60 -21
- package/dist/lazyShell3dPresentation.d.ts +9 -0
- package/dist/lazyShell3dPresentation.js +24 -0
- package/dist/materialOverride.d.ts +42 -0
- package/dist/materialOverride.js +95 -36
- package/dist/multiplayer.d.ts +33 -3
- package/dist/multiplayer.js +105 -22
- package/dist/pointer/pointerService.d.ts +2 -0
- package/dist/pointer/pointerService.js +12 -2
- package/dist/postfx/PostProcessing.d.ts +11 -1
- package/dist/postfx/PostProcessing.js +14 -42
- package/dist/postfx/gradeShader.d.ts +1 -1
- package/dist/postfx/gradeShader.js +30 -2
- package/dist/postfx/shadowPrepass.d.ts +3 -0
- package/dist/postfx/shadowPrepass.js +18 -0
- package/dist/postfx/stylizePass.d.ts +23 -0
- package/dist/postfx/stylizePass.js +216 -0
- package/dist/presentationRecovery.d.ts +75 -0
- package/dist/presentationRecovery.js +204 -0
- package/dist/presentationRecovery3d.d.ts +4 -0
- package/dist/presentationRecovery3d.js +32 -0
- package/dist/presentationResolve.d.ts +8 -1
- package/dist/presentationResolve.js +5 -2
- package/dist/render/CascadedShadows.d.ts +7 -2
- package/dist/render/CascadedShadows.js +98 -20
- package/dist/render/EnvironmentLighting.js +74 -35
- package/dist/render/HairCards.d.ts +7 -0
- package/dist/render/HairCards.js +23 -0
- package/dist/render/SceneModels.js +52 -78
- package/dist/render/SkinnedInstances.d.ts +91 -0
- package/dist/render/SkinnedInstances.js +249 -0
- package/dist/render/TextureFiltering.d.ts +16 -0
- package/dist/render/TextureFiltering.js +64 -0
- package/dist/render/authoredSurfaceMaterial.d.ts +43 -0
- package/dist/render/authoredSurfaceMaterial.js +108 -0
- package/dist/render/displayColor.d.ts +33 -0
- package/dist/render/displayColor.js +138 -0
- package/dist/render/materialAppearanceSignals.d.ts +23 -0
- package/dist/render/materialAppearanceSignals.js +58 -0
- package/dist/render/materialAsset.d.ts +98 -0
- package/dist/render/materialAsset.js +454 -0
- package/dist/render/measureBounds.js +16 -3
- package/dist/render/measureCollisionMesh.js +8 -1
- package/dist/render/modelAssets.d.ts +8 -0
- package/dist/render/modelAssets.js +43 -0
- package/dist/render/modelBindPose.d.ts +5 -0
- package/dist/render/modelBindPose.js +35 -0
- package/dist/render/modelLoad.d.ts +32 -0
- package/dist/render/modelLoad.js +70 -1
- package/dist/render/modelNodeVisibility.d.ts +6 -0
- package/dist/render/modelNodeVisibility.js +18 -0
- package/dist/render/modelRender.d.ts +18 -1
- package/dist/render/modelRender.js +81 -3
- package/dist/render/projectedOverdraw.d.ts +35 -0
- package/dist/render/projectedOverdraw.js +204 -0
- package/dist/render/resolveModel.d.ts +12 -2
- package/dist/render/resolveModel.js +25 -4
- package/dist/render/rigNode.d.ts +26 -0
- package/dist/render/rigNode.js +32 -0
- package/dist/render/spriteRender.d.ts +4 -0
- package/dist/render/spriteRender.js +5 -0
- package/dist/render/staticShapeInstances.d.ts +34 -0
- package/dist/render/staticShapeInstances.js +78 -0
- package/dist/render/useFootIk.d.ts +53 -4
- package/dist/render/useFootIk.js +262 -70
- package/dist/render/useModelAnimation.d.ts +50 -0
- package/dist/render/useModelAnimation.js +245 -99
- package/dist/render/useModelInstance.d.ts +25 -0
- package/dist/render/useModelInstance.js +43 -0
- package/dist/scatter/scatterModels.d.ts +3 -2
- package/dist/scatter/scatterModels.js +15 -7
- package/dist/scene/AuthoredScene.d.ts +23 -6
- package/dist/scene/AuthoredScene.js +46 -9
- package/dist/scene/CityRenderer.js +6 -6
- package/dist/scene/GeneratedAssetRenderer.js +11 -42
- package/dist/settings/QuickControls.js +1 -1
- package/dist/settings/SettingsMenu.js +11 -18
- package/dist/settings/SettingsRuntime.d.ts +5 -0
- package/dist/settings/SettingsRuntime.js +29 -2
- package/dist/settings/appliedSettings.d.ts +9 -3
- package/dist/settings/appliedSettings.js +6 -5
- package/dist/settings/settingsController.d.ts +3 -0
- package/dist/settings/settingsController.js +47 -3
- package/dist/shellControlSuspension.d.ts +18 -0
- package/dist/shellControlSuspension.js +28 -0
- package/dist/shellInputPublication.d.ts +28 -0
- package/dist/shellInputPublication.js +49 -0
- package/dist/shellPointerInput.d.ts +18 -0
- package/dist/shellPointerInput.js +49 -0
- package/dist/structures/BuildingKitBatch.d.ts +3 -1
- package/dist/structures/BuildingKitBatch.js +35 -56
- package/dist/structures/BuildingRenderBudget.d.ts +23 -0
- package/dist/structures/BuildingRenderBudget.js +54 -0
- package/dist/structures/GeneratedBuilding.d.ts +3 -15
- package/dist/structures/GeneratedBuilding.js +33 -22
- package/dist/structures/buildingKitMaterials.d.ts +20 -0
- package/dist/structures/buildingKitMaterials.js +70 -0
- package/dist/structures/buildingSpatialBatch.d.ts +11 -0
- package/dist/structures/buildingSpatialBatch.js +45 -0
- package/dist/structures/buildingSurface.d.ts +21 -0
- package/dist/structures/buildingSurface.js +134 -0
- package/dist/terrain/GrassField.d.ts +2 -1
- package/dist/terrain/GrassField.js +59 -5
- package/dist/terrain/grassGeometry.d.ts +10 -0
- package/dist/terrain/grassGeometry.js +55 -0
- package/dist/terrain/grassMaterial.js +2 -2
- package/dist/terrain/terrainDetailMaterial.d.ts +3 -3
- package/dist/terrain/terrainDetailMaterial.js +7 -7
- package/dist/touch/TouchControlsOverlay.js +4 -4
- package/dist/useShellMultiplayerSync.d.ts +1 -1
- package/dist/useShellMultiplayerSync.js +9 -3
- package/dist/vfx/ParticleField.d.ts +23 -20
- package/dist/vfx/ParticleField.js +168 -50
- package/dist/visibility/CullingProvider.js +2 -62
- package/dist/visibility/cullingDriver.d.ts +9 -0
- package/dist/visibility/cullingDriver.js +68 -0
- package/dist/weather/AuthoredWeatherLayer.d.ts +16 -0
- package/dist/weather/AuthoredWeatherLayer.js +31 -0
- package/dist/weather/DustField.d.ts +2 -1
- package/dist/weather/DustField.js +17 -3
- package/dist/weather/FireSpreadLayer.d.ts +4 -2
- package/dist/weather/FireSpreadLayer.js +5 -11
- package/dist/weather/LightningStrike.d.ts +4 -1
- package/dist/weather/LightningStrike.js +13 -17
- package/dist/weather/RainField.d.ts +2 -1
- package/dist/weather/RainField.js +17 -3
- package/dist/weather/RainImpactField.d.ts +16 -0
- package/dist/weather/RainImpactField.js +89 -0
- package/dist/weather/SnowField.d.ts +2 -1
- package/dist/weather/SnowField.js +17 -3
- package/dist/weather/WeatherLayer.d.ts +11 -2
- package/dist/weather/WeatherLayer.js +30 -6
- package/dist/weather/index.d.ts +2 -0
- package/dist/weather/index.js +2 -0
- package/dist/weather/weatherMath.d.ts +2 -0
- package/dist/weather/weatherMath.js +8 -0
- package/dist/weather/weatherShelter.d.ts +18 -0
- package/dist/weather/weatherShelter.js +75 -0
- package/dist/weather/weatherUniforms.d.ts +40 -1
- package/dist/weather/weatherUniforms.js +29 -9
- package/dist/world/AuthoredFlocks.d.ts +22 -0
- package/dist/world/AuthoredFlocks.js +87 -0
- package/dist/world/StaticObjectModels.d.ts +8 -0
- package/dist/world/StaticObjectModels.js +75 -0
- package/dist/world/WorldHud.d.ts +14 -6
- package/dist/world/WorldHud.js +25 -12
- package/dist/world/WorldParticles.d.ts +10 -23
- package/dist/world/WorldParticles.js +210 -62
- package/dist/world/WorldScene.d.ts +3 -1
- package/dist/world/WorldScene.js +41 -26
- package/dist/world/staticObjectBatches.d.ts +35 -0
- package/dist/world/staticObjectBatches.js +163 -0
- package/dist/world/worldBarSamples.d.ts +37 -33
- package/dist/world/worldBarSamples.js +117 -75
- package/package.json +4 -4
- package/skills/ce-handoff/SKILL.md +2 -2
- package/skills/game-design/SKILL.md +2 -2
- package/skills/jgengine/SKILL.md +28 -6
- package/skills/jgengine/api.md +139 -99
- package/skills/jgengine/capabilities.md +28 -0
- package/skills/jgengine/recipes/minimal-game.md +10 -2
- package/skills/jgengine-ui/SKILL.md +16 -17
- package/skills/jgengine-ui/api.md +603 -365
- package/skills/jgengine-ui/capabilities.md +131 -1
- package/skills/jgengine-ui/reference-primitives.md +179 -0
- package/skills/jgengine-ui/reference.md +4 -81
- package/skills/jgengine-world/SKILL.md +25 -4
- package/skills/jgengine-world/api.md +1045 -655
- package/skills/jgengine-world/capabilities.md +246 -9
- package/skills/jgengine-world/recipes/character-animation.md +62 -0
- package/skills/jgengine-world/recipes/controls.md +48 -0
- package/skills/jgengine-world/recipes/flight-feel.md +76 -0
- package/skills/jgengine-world/recipes/npc-senses.md +55 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +87 -0
- package/skills/jgengine-world/reference.md +180 -4
- package/skills/jgengine-world/visibility.md +139 -0
- package/skills/level-design/SKILL.md +1 -1
- package/dist/render/testFixtures/proceduralSpriteAtlas.d.ts +0 -15
- package/dist/render/testFixtures/proceduralSpriteAtlas.js +0 -30
- package/dist/render/testFixtures/proceduralTileLayer.d.ts +0 -13
- package/dist/render/testFixtures/proceduralTileLayer.js +0 -30
|
@@ -44,10 +44,43 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
44
44
|
|
|
45
45
|
- `ActionTooltip` (function) · `import { ActionTooltip } from "@jgengine/react"`
|
|
46
46
|
|
|
47
|
+
## animation-motion-params — read resolved player movement into reusable animation graph parameters
|
|
48
|
+
|
|
49
|
+
- `readModelAnimationParams` (function) · `import { readModelAnimationParams } from "@jgengine/shell/render/useModelAnimation"`
|
|
50
|
+
|
|
51
|
+
## authored-flocks — render authored habitat agents through caller-selected animated species models with velocity orientation and bounded model lifetime
|
|
52
|
+
|
|
53
|
+
- `AuthoredFlocks` (function) · `import { AuthoredFlocks } from "@jgengine/shell/world/AuthoredFlocks"`
|
|
54
|
+
|
|
55
|
+
## authored-solids — collision for studio-authored world content such as city volumes
|
|
56
|
+
|
|
57
|
+
- `AuthoredSolids` (function) · `import { AuthoredSolids } from "@jgengine/shell/scene"`
|
|
58
|
+
|
|
59
|
+
## authored-surface-material — mount an owned PBR surface material on a mesh
|
|
60
|
+
|
|
61
|
+
- `AuthoredSurfaceMaterial` (function) · `import { AuthoredSurfaceMaterial } from "@jgengine/shell/render/authoredSurfaceMaterial"`
|
|
62
|
+
- `useAuthoredSurfaceMaterial` (function) · `import { useAuthoredSurfaceMaterial } from "@jgengine/shell/render/authoredSurfaceMaterial"`
|
|
63
|
+
|
|
64
|
+
## authored-weather-rendering — Render authored weather from shared gameplay samples, simulation time and shelter volumes.
|
|
65
|
+
|
|
66
|
+
- `AuthoredWeatherLayer` (function) · `import { AuthoredWeatherLayer } from "@jgengine/shell/weather"`
|
|
67
|
+
|
|
47
68
|
## auto-scroll — pin a log/chat/console panel to its newest line as entries arrive — no hand-rolled scrollTop effects
|
|
48
69
|
|
|
49
70
|
- `useAutoScroll` (function) · `import { useAutoScroll } from "@jgengine/react"`
|
|
50
71
|
|
|
72
|
+
## bounded-spatial-audio — play positional or flat cues with listener orientation, voice budgets, and per-source cleanup
|
|
73
|
+
|
|
74
|
+
- `createAudioEngine` (function) · `import { createAudioEngine } from "@jgengine/shell/audio/audioEngine"`
|
|
75
|
+
|
|
76
|
+
## browser-suspension — observe blur, hidden-page and pointer-lock loss while the game owns pause and input policy
|
|
77
|
+
|
|
78
|
+
- `useBrowserSuspension` (function) · `import { useBrowserSuspension } from "@jgengine/react"`
|
|
79
|
+
|
|
80
|
+
## browser-suspension-observer — observe browser interruptions outside React with caller-owned pause policy and event sources
|
|
81
|
+
|
|
82
|
+
- `observeBrowserSuspension` (function) · `import { observeBrowserSuspension } from "@jgengine/react"`
|
|
83
|
+
|
|
51
84
|
## camera-shake-meter — HUD readout for a core camera-shake controller — a trauma meter plus current impact-kind label, reskinnable and kind-labelled
|
|
52
85
|
|
|
53
86
|
- `CameraShakeMeter` (function) · `import { CameraShakeMeter } from "@jgengine/react"`
|
|
@@ -72,6 +105,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
72
105
|
|
|
73
106
|
- `StatList` (function) · `import { StatList } from "@jgengine/react"`
|
|
74
107
|
|
|
108
|
+
## chase-heading — keep camera-relative input heading independent of body facing while retaining vehicle and seat camera follow
|
|
109
|
+
|
|
110
|
+
- `resolveChaseHeading` (function) · `import { resolveChaseHeading } from "@jgengine/shell/camera/chaseHeading"`
|
|
111
|
+
|
|
75
112
|
## clock-format — format a signed time gap like a race split (+/- m:ss.ff)
|
|
76
113
|
|
|
77
114
|
- `formatDelta` (function) · `import { formatDelta } from "@jgengine/core/ui"`
|
|
@@ -117,6 +154,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
117
154
|
|
|
118
155
|
- `CreditsScreen` (function) · `import { CreditsScreen } from "@jgengine/react/creditsScreen"`
|
|
119
156
|
|
|
157
|
+
## current-target — read the player's selected target (click or tab target) to drive a target frame
|
|
158
|
+
|
|
159
|
+
- `useTarget` (function) · `import { useTarget } from "@jgengine/react"`
|
|
160
|
+
|
|
161
|
+
## custom-viewmodel-muzzle — align tracer origins with a custom viewmodel's live muzzle without changing authoritative shots
|
|
162
|
+
|
|
163
|
+
- `registerFirstPersonMuzzle` (function) · `import { registerFirstPersonMuzzle } from "@jgengine/shell/camera"`
|
|
164
|
+
|
|
120
165
|
## cutscene-letterbox — reskinnable cinematic letterbox + skip overlay for a cutscene — animated bars, caption/dialogue slot, progress line, Skip button
|
|
121
166
|
|
|
122
167
|
- `CutsceneLetterbox` (function) · `import { CutsceneLetterbox } from "@jgengine/react"`
|
|
@@ -141,6 +186,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
141
186
|
|
|
142
187
|
- `DEFAULT_WALK_CODES` (const) · `import { DEFAULT_WALK_CODES } from "@jgengine/shell/shellMovement"`
|
|
143
188
|
|
|
189
|
+
## dialog-behavior — controlled dialog focus and keyboard behavior without owning markup or screen state
|
|
190
|
+
|
|
191
|
+
- `useDialogBehavior` (function) · `import { useDialogBehavior } from "@jgengine/react"`
|
|
192
|
+
|
|
144
193
|
## dialogue-view — drop-in branching conversation UI — speaker + portrait slot, current line, clickable response choices that advance a DialogueRun
|
|
145
194
|
|
|
146
195
|
- `DialogueView` (function) · `import { DialogueView } from "@jgengine/react"`
|
|
@@ -157,6 +206,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
157
206
|
|
|
158
207
|
- `useDomEvent` (function) · `import { useDomEvent } from "@jgengine/react"`
|
|
159
208
|
|
|
209
|
+
## drag-layer — composable pointer-owned drag, drop targets and cancellation for custom cards and inventory UI
|
|
210
|
+
|
|
211
|
+
- `useDragLayer` (function) · `import { useDragLayer } from "@jgengine/react"`
|
|
212
|
+
|
|
160
213
|
## entity-frames — overlay that renders caller-composed frames over on-screen entities
|
|
161
214
|
|
|
162
215
|
- `EntityFrames` (function) · `import { EntityFrames } from "@jgengine/react"`
|
|
@@ -205,6 +258,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
205
258
|
|
|
206
259
|
- `GameContextBridge` (function) · `import { GameContextBridge } from "@jgengine/react"`
|
|
207
260
|
|
|
261
|
+
## heavy-models — models over the per-model triangle budget, for perf probes and smoke assertions
|
|
262
|
+
|
|
263
|
+
- `heavyModels` (function) · `import { heavyModels } from "@jgengine/shell/render/modelLoad"`
|
|
264
|
+
|
|
208
265
|
## heightfield-mesh-update — in-place partial update of a displaced ground plane mesh
|
|
209
266
|
|
|
210
267
|
- `displaceHeightfieldGeometry` (function) · `import { displaceHeightfieldGeometry } from "@jgengine/shell/terrain"`
|
|
@@ -282,6 +339,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
282
339
|
|
|
283
340
|
- `createI18n` (function) · `import { createI18n } from "@jgengine/core/ui"`
|
|
284
341
|
|
|
342
|
+
## imported-rig-node — resolve original imported bone and slot names with ambiguity diagnostics
|
|
343
|
+
|
|
344
|
+
- `resolveRigNode` (function) · `import { resolveRigNode } from "@jgengine/shell/render/rigNode"`
|
|
345
|
+
|
|
285
346
|
## interaction-prompt-host — screen-anchored "press E to…" callout rendering a prompt registry's active interactable — keybind cap+label, gauge hold bar, or plain label, theme- and per-prompt-accent skinnable
|
|
286
347
|
|
|
287
348
|
- `InteractionPrompt` (function) · `import { InteractionPrompt } from "@jgengine/react"`
|
|
@@ -314,6 +375,37 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
314
375
|
|
|
315
376
|
- `MapLegend` (function) · `import { MapLegend } from "@jgengine/react"`
|
|
316
377
|
|
|
378
|
+
## material-appearance-signals — update one owned material from wetness and local card exposure without global lighting edits
|
|
379
|
+
|
|
380
|
+
- `applyMaterialAppearanceSignals` (function) · `import { applyMaterialAppearanceSignals } from "@jgengine/shell/render/materialAppearanceSignals"`
|
|
381
|
+
- `captureMaterialAppearanceBaseline` (function) · `import { captureMaterialAppearanceBaseline } from "@jgengine/shell/render/materialAppearanceSignals"`
|
|
382
|
+
- `restoreMaterialAppearanceBaseline` (function) · `import { restoreMaterialAppearanceBaseline } from "@jgengine/shell/render/materialAppearanceSignals"`
|
|
383
|
+
- `setHairCardLightExposure` (function) · `import { setHairCardLightExposure } from "@jgengine/shell/render/materialAsset"`
|
|
384
|
+
|
|
385
|
+
## material-assets — render authored physical surfaces, woven detail and approximate hair-card appearance
|
|
386
|
+
|
|
387
|
+
- `MaterialAssetSurface` (function) · `import { MaterialAssetSurface } from "@jgengine/shell/render/materialAsset"`
|
|
388
|
+
- `applyMaterialAsset` (function) · `import { applyMaterialAsset } from "@jgengine/shell/render/materialAsset"`
|
|
389
|
+
- `applyMaterialAssignments` (function) · `import { applyMaterialAssignments } from "@jgengine/shell/render/materialAsset"`
|
|
390
|
+
- `materialAssetSurface` (function) · `import { materialAssetSurface } from "@jgengine/shell/render/materialAsset"`
|
|
391
|
+
- `useMaterialAssetMaterial` (function) · `import { useMaterialAssetMaterial } from "@jgengine/shell/render/materialAsset"`
|
|
392
|
+
|
|
393
|
+
## material-resource-metrics — inspect loaded texture byte bounds and physical, coverage and transmission participation
|
|
394
|
+
|
|
395
|
+
- `materialResourceMetrics` (function) · `import { materialResourceMetrics } from "@jgengine/shell/render/materialAsset"`
|
|
396
|
+
|
|
397
|
+
## material-slot-selection — preflight native model assignments without allocating or changing imported resources
|
|
398
|
+
|
|
399
|
+
- `validateMaterialAssignmentTargets` (function) · `import { validateMaterialAssignmentTargets } from "@jgengine/shell/render/materialAsset"`
|
|
400
|
+
|
|
401
|
+
## material-slots — discover exact imported mesh and material selectors with UV and tangent prerequisites
|
|
402
|
+
|
|
403
|
+
- `inspectModelMaterialSlots` (function) · `import { inspectModelMaterialSlots } from "@jgengine/shell/render/materialAsset"`
|
|
404
|
+
|
|
405
|
+
## material-texture-metadata — configure owned map views with semantic color space, packed channels, UV transforms and sampling
|
|
406
|
+
|
|
407
|
+
- `configureMaterialTexture` (function) · `import { configureMaterialTexture } from "@jgengine/shell/render/materialAsset"`
|
|
408
|
+
|
|
317
409
|
## menu-router — screen stack for a game front-end — push/back/replace/reset with optional Escape-to-back
|
|
318
410
|
|
|
319
411
|
- `useMenuRouter` (function) · `import { useMenuRouter } from "@jgengine/react/menuRouter"`
|
|
@@ -346,14 +438,28 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
346
438
|
|
|
347
439
|
- `createModalStack` (function) · `import { createModalStack } from "@jgengine/core/ui"`
|
|
348
440
|
|
|
441
|
+
## model-animation-diagnostics — inspect imported clip mappings, layer masks and root-track compatibility
|
|
442
|
+
|
|
443
|
+
- `diagnoseModelAnimation` (function) · `import { diagnoseModelAnimation } from "@jgengine/shell/render/useModelAnimation"`
|
|
444
|
+
|
|
349
445
|
## model-fallbacks — models the loader replaced with a placeholder, for capture and smoke assertions
|
|
350
446
|
|
|
351
447
|
- `modelLoadFallbacks` (function) · `import { modelLoadFallbacks } from "@jgengine/shell/render/modelLoad"`
|
|
352
448
|
|
|
449
|
+
## model-instance — clone a loaded model for independent styling and animation
|
|
450
|
+
|
|
451
|
+
- `cloneModelScene` (function) · `import { cloneModelScene } from "@jgengine/shell/render/modelRender"`
|
|
452
|
+
- `disposeModelScene` (function) · `import { disposeModelScene } from "@jgengine/shell/render/modelRender"`
|
|
453
|
+
- `useModelInstance` (function) · `import { useModelInstance } from "@jgengine/shell/render/useModelInstance"`
|
|
454
|
+
|
|
353
455
|
## model-load-idle — how long the shared GLB loader has been idle, for capture settle waits
|
|
354
456
|
|
|
355
457
|
- `modelLoadIdleMs` (function) · `import { modelLoadIdleMs } from "@jgengine/shell/render/modelLoad"`
|
|
356
458
|
|
|
459
|
+
## multiplayer-p2p — bootstrap a playable authoritative world for browser peer co-op
|
|
460
|
+
|
|
461
|
+
- `resolvePeerShellMultiplayer` (function) · `import { resolvePeerShellMultiplayer } from "@jgengine/shell/multiplayer"`
|
|
462
|
+
|
|
357
463
|
## nameplates — billboarded name + HP bar over nearby entities
|
|
358
464
|
|
|
359
465
|
- `WorldNameplates` (function) · `import { WorldNameplates } from "@jgengine/shell/world/WorldHud"`
|
|
@@ -423,7 +529,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
423
529
|
|
|
424
530
|
- `togglePanel` (function) · `import { togglePanel } from "@jgengine/core/ui"`
|
|
425
531
|
|
|
426
|
-
## particle-field — render a core particle
|
|
532
|
+
## particle-field — render a core particle pool as soft points, velocity streaks, connected ribbons, flakes, flames, smoke sprites, or ground ripples
|
|
427
533
|
|
|
428
534
|
- `ParticleField` (function) · `import { ParticleField } from "@jgengine/shell/vfx/ParticleField"`
|
|
429
535
|
|
|
@@ -447,6 +553,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
447
553
|
|
|
448
554
|
- `Popover` (function) · `import { Popover } from "@jgengine/react"`
|
|
449
555
|
|
|
556
|
+
## projected-overdraw — sample actual mesh/camera fragment overlap with native coverage and explicit depth/blending/transmission limits
|
|
557
|
+
|
|
558
|
+
- `measureProjectedOverdraw` (function) · `import { measureProjectedOverdraw } from "@jgengine/shell/render/projectedOverdraw"`
|
|
559
|
+
|
|
560
|
+
## projectile-models — render authoritative projectile flights with caller models or opt-in shaft/head geometry, velocity orientation, bounded instances, and settlement cleanup
|
|
561
|
+
|
|
562
|
+
- `ProjectileModels` (function) · `import { ProjectileModels } from "@jgengine/shell/combat/ProjectileModels"`
|
|
563
|
+
|
|
450
564
|
## quest-tracker — compact quest/objective HUD tracker — titles + labelled objective progress from describeTrackedQuest views
|
|
451
565
|
|
|
452
566
|
- `QuestTracker` (function) · `import { QuestTracker } from "@jgengine/react"`
|
|
@@ -467,6 +581,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
467
581
|
|
|
468
582
|
- `useRafLoop` (function) · `import { useRafLoop } from "@jgengine/react"`
|
|
469
583
|
|
|
584
|
+
## rain-ground-ripples — Render bounded rain impacts on exposed terrain using the shared precipitation clock.
|
|
585
|
+
|
|
586
|
+
- `RainImpactField` (function) · `import { RainImpactField } from "@jgengine/shell/weather"`
|
|
587
|
+
|
|
470
588
|
## resolve-game-look — expand a look preset into concrete lighting/backdrop/post knobs
|
|
471
589
|
|
|
472
590
|
- `resolveGameLook` (function) · `import { resolveGameLook } from "@jgengine/core/ui"`
|
|
@@ -487,6 +605,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
487
605
|
|
|
488
606
|
- `ScreenEffectsOverlay` (function) · `import { ScreenEffectsOverlay } from "@jgengine/shell/postfx/ScreenEffectsOverlay"`
|
|
489
607
|
|
|
608
|
+
## scroll-rail — reachable horizontal controls with visible overflow navigation and keyboard focus reveal
|
|
609
|
+
|
|
610
|
+
- `ScrollRail` (function) · `import { ScrollRail } from "@jgengine/react"`
|
|
611
|
+
|
|
490
612
|
## selection-collection — accessible portrait-strip / group-chip renderer over a selection model
|
|
491
613
|
|
|
492
614
|
- `SelectionCollectionChrome` (function) · `import { SelectionCollectionChrome } from "@jgengine/react"`
|
|
@@ -515,6 +637,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
515
637
|
|
|
516
638
|
- `formatSpeed` (function) · `import { formatSpeed } from "@jgengine/core/ui"`
|
|
517
639
|
|
|
640
|
+
## split-screen-hud — Per-seat HUD roots positioned over each split-screen viewport.
|
|
641
|
+
|
|
642
|
+
- `ViewportHuds` (function) · `import { ViewportHuds } from "@jgengine/shell/camera/Viewports"`
|
|
643
|
+
|
|
518
644
|
## sprite-2d — animate a sprite atlas clip from headless or rendered game code
|
|
519
645
|
|
|
520
646
|
- `createSpriteClipPlayer` (function) · `import { createSpriteClipPlayer } from "@jgengine/core/render/sprite2d"`
|
|
@@ -528,6 +654,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
528
654
|
|
|
529
655
|
- `StartScreen` (function) · `import { StartScreen } from "@jgengine/react"`
|
|
530
656
|
|
|
657
|
+
## static-shape-instances — render authored primitive transforms as static material-compatible instance batches
|
|
658
|
+
|
|
659
|
+
- `StaticShapeInstances` (function) · `import { StaticShapeInstances } from "@jgengine/shell/render/staticShapeInstances"`
|
|
660
|
+
|
|
531
661
|
## status-effect-bar — timeline HUD row of active statuses — icon + radial countdown ring + stack badge, driven off the status model, HudTheme-skinned
|
|
532
662
|
|
|
533
663
|
- `StatusEffectBar` (function) · `import { StatusEffectBar } from "@jgengine/react"`
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# JGengine UI — primitives and rendering reference
|
|
2
|
+
|
|
3
|
+
On-demand detail behind [reference.md](reference.md) §6. Search [capabilities.md](capabilities.md) by intent first; open this file when you need how a shipped block composes.
|
|
4
|
+
|
|
5
|
+
## Browser interruptions
|
|
6
|
+
|
|
7
|
+
`useBrowserSuspension(onSuspend, { blur, hidden, pointerLockLost })` from
|
|
8
|
+
`@jgengine/react/browserLifecycle` observes all three triggers by default and
|
|
9
|
+
calls the latest callback with `"blur"`, `"hidden"`, or `"pointer-lock-lost"`.
|
|
10
|
+
Pointer lock acquisition and visible-page events do not notify. Mounting while
|
|
11
|
+
unlocked or hidden does not notify either. The game owns its pause guards,
|
|
12
|
+
modal handling, input reset and multiplayer policy; this hook never changes
|
|
13
|
+
simulation state. `observeBrowserSuspension` offers the same behavior outside
|
|
14
|
+
React and returns listener cleanup; its optional sources support iframe windows
|
|
15
|
+
and isolated tests.
|
|
16
|
+
|
|
17
|
+
**Voice transmission state.** `useVoice` (`@jgengine/react/voice`) reads its mode, mute, status and transmission from the push-to-talk controller. Initial `mode: "openMic"` enables transmission on the first render; granted audio tracks adopt the current mode and mute state before publication, including changes made while capture is pending. Transport readiness preserves explicitly enabled hold/toggle sessions. Use `setMode` and `setMuted` for policy changes; microphone permission and transport stay caller-owned.
|
|
18
|
+
|
|
19
|
+
**Microphone lifetime.** `requestMic()` returns `false` for requests retired by unmount, a later request, or channel/transport replacement. Their returned tracks stop without publication or stale error updates. Successful replacement stops the previous capture; a current permission denial leaves an already accepted capture available. Accepted capture survives transport replacement and joins the replacement channel with its current stream id. Retained callbacks from a retired channel/transport cannot start capture; a grant without live audio is rejected. Current publication errors return `false` and populate `micError` while retaining accepted capture for retry; retired publication results cannot update the current owner.
|
|
20
|
+
|
|
21
|
+
**Keyboard ownership.** The shell leaves prevented key events and focused text, dialog, grid, gridcell, and toolbar controls to the HUD, clearing held gameplay input at that boundary. Custom keyboard widgets call `preventDefault()` for the keys they handle. Opening a window does not pause simulation or change pointer lock; keep those policies in the game and use `suspendPlayControls(ctx)` when the window should suspend player controls.
|
|
22
|
+
|
|
23
|
+
**Push-to-talk ownership.** `PushToTalkButton` (`@jgengine/react/voice`) owns one primary pointer or focused Enter/Space activation. Matching release, cancellation, capture loss, browser suspension and button unmount release its hold; mouse departure and keyboard focus loss also retire their activation. Secondary pointers, auxiliary buttons and key repeats cannot change it. Compose a caller-owned `VoiceState`; toggle/open-mic policy and holds started elsewhere remain caller-owned.
|
|
24
|
+
|
|
25
|
+
## 6. Game UI primitives
|
|
26
|
+
|
|
27
|
+
Prefer small headless or lightly styled primitives over a giant universal design system. Useful concepts include:
|
|
28
|
+
|
|
29
|
+
- `HudAnchor`
|
|
30
|
+
- `StatReadout`
|
|
31
|
+
- `Meter`
|
|
32
|
+
- `ObjectiveTracker`
|
|
33
|
+
- `ActionPrompt`
|
|
34
|
+
- `Reticle`
|
|
35
|
+
- `MinimapFrame`
|
|
36
|
+
- `DialoguePlate`
|
|
37
|
+
- `Countdown`
|
|
38
|
+
- `BossBar`
|
|
39
|
+
- `ItemPickup`
|
|
40
|
+
- `DamageIndicator`
|
|
41
|
+
- `PauseScreen`
|
|
42
|
+
- `ResultsScreen`
|
|
43
|
+
- `VirtualControlZone`
|
|
44
|
+
- `ScreenTransition`
|
|
45
|
+
|
|
46
|
+
A primitive should expose game-oriented choices such as shape, material, urgency, placement, hierarchy, entry motion, icon treatment, compactness, and diegetic-versus-overlay presentation.
|
|
47
|
+
|
|
48
|
+
**Atomic bar fills.** An explicit `fill` prop wins over a bar skin and semantic palette colors. Without it, `HudSkin.bar.fill` wins over the normal or low-health palette token. `hudThemeVars` resets an inherited bar skin when the nested theme has none; `hudSkinVars` alone applies only the supplied skin fields and preserves inheritance for the rest.
|
|
49
|
+
|
|
50
|
+
**Portable minimap adoption.** Existing React games pass `MarkerView[]`, a
|
|
51
|
+
`MarkerSource`, or their existing `MarkerSet` to `Minimap`/`Compass`/`WorldMap`.
|
|
52
|
+
Use `createMarkerSource` (`@jgengine/core/world/markers`) to cache a projection
|
|
53
|
+
from an external ECS/store snapshot; it needs no `GameProvider`, Three.js, or
|
|
54
|
+
React Three Fiber. Follow [the portable minimap recipe](recipes/portable-minimap.md)
|
|
55
|
+
instead of mirroring external entities into `createMarkerSet`.
|
|
56
|
+
|
|
57
|
+
**Fullscreen map gestures.** `FullscreenMap` owns one primary pan or drawing pointer. Cancellation, touch capture loss, closing, tool changes and unmount discard unfinished gestures and route drafts. Mouse release outside the viewport also retires ownership; auxiliary buttons do not finish a primary drag. Custom overlay controls, including shadow DOM controls, keep their native input. Compose `onWorldClick` for navigation or `onStrokeComplete` with a caller-owned annotation layer; the game keeps route policy, composition and skin.
|
|
58
|
+
|
|
59
|
+
**Minimap** is already shipped, not hand-rolled: `Minimap` / `WorldMap` / `Compass` from `@jgengine/react/map` render a framed SVG minimap over a `MarkerSet` (`createMarkerSet`, `@jgengine/core/world/markers`) and optional `FogField`, projecting via `@jgengine/core/world/minimap` (`projectToMinimap`, `headingToBearing`, edge-clamp). A game feeds flat props (`markers`, `center: [x,z]`, `worldRadius`, `size`, `facingYaw`, `rotate`) and repopulates the marker set each HUD tick from live entities — no custom canvas painter. `MinimapPanel` (same module) composes zone-label + clock header slots and a `Compass` strip around a `Minimap` — pass `zoneLabel`/`clock` nodes (e.g. from `useGameClock().calendar`) and `showCompass`/`compassProps`; omit either header slot and it collapses, `showCompass={false}` drops the strip. **Baked terrain background**: to draw the authored terrain under the markers, pass `background={document.minimap?.background}` and `mapBounds={document.minimap?.bounds}` to `Minimap`/`WorldMap` (or `MinimapPanel`, which forwards both through) — the editor's `bake_minimap` action stores a top-down terrain PNG + world bounds on `EditorDocument.minimap` (see `jgengine-editor`), and runtime reads that stored PNG with no re-rasterization. Both are undefined until baked, so an unbaked game renders the plain map unchanged. Rolling a **custom** minimap around your own course/route content (not the full `Minimap` panel)? `MinimapChrome` is the headless chrome layer other games were re-drawing by hand: nest it inside your own `<svg>` for the ring `frame`, and pass `markers: MinimapChromeMarker[]` (`{ id, position, heading?, color?, radius?, glyph? }`) for edge-clamped dots that draw as a rotated arrow whenever a marker carries a `heading` (the compass-arrow / player-blip layer) — never hand-roll `clampToMinimapEdge` + a `<polygon>` again. **Nameplates**: `WorldNameplates` (`@jgengine/shell/world/WorldHud`) billboards a name + optional 78×6px HP bar over every nearby non-local entity — turn it on declaratively via `defineGame({ nameplates: { roles, maxDistance } })` (mirrors `worldHealthBars`) rather than mounting it by hand; skin every part via `className`/`data-*` slots (`nameplateClassName`, `nameClassName`, `barClassName`, `fillClassName`) or swap the whole markup with `renderNameplate`. Bars and nameplates respect blocking object, wall, and terrain geometry from the actual render camera by default; use `occlude: false` only for an intentional reveal mechanic. They filter hidden/depleted-health entities and renderer membership, use camera distance, and sample at most 64 nearby candidates with visibility rays per refresh (bars 80ms, plates 120ms); retained bar anchors reproject every frame without additional queries or rays. Both declarative configs and direct components expose `maxSamples` (capped at 256) and `tickMs`. Nameplates retain human names but suppress id-equal, lowercase machine names and names containing `_`, `:`, or `/`; supply `nameplates.resolveName(entity)` for authored catalog or player labels, returning `null` to omit a plate. When the game does **not** run the JGengine entity store — it owns enemies in its own array/ECS/store — reach for the data-first **entity-frame** seam instead of `WorldNameplates`: `EntityFrames` (`@jgengine/react/entityFrames`, `entity-frames` capability) takes a caller-owned `{ id, worldPosition, ... }[]` plus a `project` (world→screen) and a `renderFrame` that composes the shipped `HealthBar` + a name (reskinned via `barTokens`/`HudTheme`); it owns only the reusable behavior — projection offset, off-screen/behind-camera culling, and nearest-on-top stacking (`layoutEntityFrames` is the pure, unit-tested core). An R3F game gets the projector for free from `WorldEntityFrames` / `useWorldProjection` (`@jgengine/shell/world/WorldEntityFrames`), which samples the live camera and composes the react primitive — overhead nameplates/health bars from your own array in a few lines, no store mirroring. Follow [the overhead entity-frames recipe](recipes/overhead-entity-frames.md). **Selection rings**: `WorldObjectHighlights` (`@jgengine/shell/world/WorldHud`) draws a ground ring over every `ctx.scene.object.selection`-ed placed object — the object-layer counterpart to `WorldEntityBars`/`WorldNameplates`; mount it once in the game's scene (`color`/`radius`/`y` props, `radius` otherwise derived from the object's catalog `halfExtents`) instead of hand-rolling a selection highlight through `WorldOverlay` against external state. **Playing cards** are already shipped, not hand-rolled: `CardFace` / `StackedPile` / `CardStack` (with `Suit`, `Rank`, `PlayingCard`) from `@jgengine/react/cards` render a French-deck face (rank + suit pips, patterned back when `faceDown`) and an overlapping fan. `StackedPile` composes the existing drag system (`useDragLayer`/`DraggableCard`/`DropZone`) — pass a `layer` for drag and `dropId` for a drop target; `CardStack` binds a fan to a headless `CardPileState` zone (`@jgengine/core/cards/cardPile`). Never draw cards on a canvas or reinvent pointer handling. **Swing timer**: `swingTimerState(player, target, prevPeriod, prevTimer)` (`@jgengine/core/ui/swingTimer`) is the pure, parameter-in/next-state-out core for a melee swing bar (recovers period on the reset edge; hidden unless auto-attacking a live non-object target) — thread the returned `nextPeriod`/`nextTimer` back via refs, render a thin `Meter`.
|
|
60
|
+
|
|
61
|
+
**HUD widgets split into three reusable layers** — data, renderer, chrome — so a second presentation drops in without a forked widget. **Chrome**: `HudFrame` (`@jgengine/react/hudFrame`, `hud-frame` capability) is the single framed-panel primitive — `<HudFrame variation="glass"|"plate"|"retro"|"themed" shape="rounded"|"circle"|"square" title aside padding width style>` renders a `data-hud-frame` div with an optional small-caps `title`/`aside` header; `glass` is byte-identical to the built-in widgets' dark-glass panel, `plate` a heavier opaque plate, `retro` a hard black-outlined frame, `themed` driven by the `--jg-frame-*` `HudTheme` tokens (the token-skinning path SKILL.md promotes). Need only the CSS on your own element? spread `hudFrameStyle(variation, shape)`. Reach for it instead of hand-rolling a bordered panel `<div>` per widget. **Renderer**: `MinimapTrack` (`@jgengine/react/map`, `minimap-track-hud` capability) is a structurally-different second minimap — a horizontal linear progress rail (vs the radial `Minimap`) with colored zone `spans` and gate/exit/player `pips` placed by 0..1 fraction, driven by core `trackFraction([x,z], { from, to })` (`@jgengine/core/world/minimapTrack`, `minimap-track` capability — the 1D analogue of `projectToMinimap`). It reads no store; the caller supplies fractions. Use it for corridor/route progress; use the radial `Minimap` for open worlds — both share `HudFrame` chrome. **Data**: `useLiveMarkers(rebuild, { intervalMs?, deps? })` (`@jgengine/react/hooks`, `live-markers` capability) returns a self-ticking `MarkerSet` that each HUD tick clears and re-runs your `rebuild(markers, ctx)` against the live scene — replaces the hand-rolled `useState` ticker + `useEffect` + `createMarkerSet` + `markers.clear()` + rescan boilerplate; feed the returned set straight into `Minimap`/`MinimapPanel`.
|
|
62
|
+
|
|
63
|
+
**Heat/streak gauges** are already shipped, not hand-rolled `clamp(value/max)`: build the gauge with `createEventMeter` (`@jgengine/core/stats/eventMeter`, `event-meter` capability — ult/adrenaline `"hold"` mode or streak/combo `"reset"` mode over tagged event gains) and bind it to a component with `useEventMeter(meter)` (`@jgengine/react`, `event-meter-hud` capability) — it self-ticks a heartbeat and returns `{ value, fraction, tier, ready }` for a `Meter`/`BossBar`-style fill, no manual `setInterval` polling of `meter.value()`.
|
|
64
|
+
|
|
65
|
+
**Action cards / command bars / ability bars** are a headless data → renderer → chrome split, not a hand-rolled cooldown grid. The model is caller-owned data: build `ActionDef[]` (`@jgengine/core/ui/actionModel` — `id`, optional `label`/`icon`/`hotkey`/`group`/`active`/`disabled`, plus `cooldown` via `actionCooldown`/`actionCooldownFromFraction` and `costs` via `actionCost`); `resolveAction`/`resolveActionCollection` compute `enabled` + ordered blocking `reasons` (cooldown, then unmet cost, then caller reasons). React side (`@jgengine/react`): `useActionBar(defs, { columns, wrap, hotkeys, onActivate })` is the DATA/HOOK layer (`action-bar-model` — focus, hover, `moveGridFocus` keyboard/controller grid nav, `actionByHotkey` routing); `ActionBarChrome` is the composable RENDERER (`action-bar-chrome` — `role="toolbar"`, roving tabindex, `layout: "grid" | "list"`, swap per-item chrome with `renderItem`); `ActionButton` is the default item chrome (`action-button` — radial cooldown wipe, cost line, `Keycap` hotkey, full rest/hover/focus/pressed/active/disabled states, `aria-disabled`/`aria-pressed`/`aria-keyshortcuts`); `ActionBar` is the batteries-included skinned default. A radial UI ignores the chrome and lays out `ActionButton`s from the model. Bind a combat `AbilityKit` with `actionFromAbilitySlot(slot, { label, icon, hotkey, resourceId })` over `useAbilitySlots`. Tooltips use the shared seam (no more DOM `title=` hand-rolling): `actionTooltip(resolved)` → `TooltipContent`, rendered by `ActionTooltip` inside a `Popover` (`popover` — pure `placePopover` viewport flip/clamp math from `@jgengine/core/ui/tooltipModel`, SSR-safe). **Selection panels / party frames / squad summaries** are the same split for entities: feed caller-owned `EntitySummaryDef[]` (`@jgengine/core/ui/selectionModel` — `id`, `name`, `kind`, `icon`, `vitals`, `tags`) to `summarizeSelection` (primary focus, `kind` buckets, `grouped` past a threshold, `selectionWindow` virtualization) or the `useSelectionView` hook (`selection-model` — focus stepping + keyboard nav). Renderers (`@jgengine/react`): `EntityPortrait` (`entity-portrait` selectable tile + health strip), `EntitySummary` (`entity-summary` primary detail with `VitalBar` rows), `SelectionGroupChip` (`selection-group-chip` icon+count), `SelectionCollectionChrome` (`selection-collection` portrait strip that flips to group chips when large, roving tabindex), and `SelectionPanel` (`selection-panel` batteries-included primary-summary-beside-strip default). Keep layout, dimensions, hotkeys, catalog-id mapping, renderer, and chrome caller-owned — the defaults are examples, not fixed RTS rules; never fork the model to reskin.
|
|
66
|
+
|
|
67
|
+
**Inventory grid** is already shipped, not hand-rolled: `<InventoryGrid inventoryId columns>` (`@jgengine/react`, backed by the `useInventoryGrid` hook) is a drop-in NxN grid bound to a live inventory — it renders the bound inventory's slots and wires drag-to-move / swap / stack / split through the built-in `inventory.move` / `inventory.split` commands, so a bag is one element plus a reskin, never a re-derived slot matrix over raw divs. It is distinct from the atomic `EquipmentSlots` / `WeaponSlots`, which are single-slot targets for paperdoll and equip layouts — reach for `InventoryGrid` for the bag/backpack surface and the atomic slots for the worn/equipped layout. Reskin via `className`/`data-*` slots and HudTheme tokens; the mechanics stay caller-agnostic.
|
|
68
|
+
|
|
69
|
+
**Drag ownership.** `useDragLayer` (`@jgengine/react/dragLayer`) owns one primary pointer until release or cancellation. Compose `DraggableCard`, `DropZone`, and `DragGhost`; registered zones resolve from the pointer position even under touch capture. Releasing outside finishes with a null target; cancellation, Escape, blur, lost capture, and source or owner unmount release listeners without invoking drop policy. A removed zone cannot receive a stale drop. The layer releases ownership before calling `onDrop`; games retain their move, discard, rotation, and stack rules. Cards own the touch gesture, while inventory keyboard move and split controls remain available.
|
|
70
|
+
|
|
71
|
+
**Toggleable game windows** are already shipped, not hand-rolled open/close state: `usePanels(defs)` + `<PanelHost>` + `<Window>` (`@jgengine/react`) over the headless `@jgengine/core/ui/panelModel` are THE way to build openable windows — bag (`B`), character (`C`), spellbook, quest log — with keybind toggles, drag-to-move, close buttons, ESC-to-close, and z-stack ordering all handled. Declare each window in `defs` (id, keybind, title), mount one `<PanelHost>`, and render each panel's body inside a `<Window>`; the game owns the body content and skin, the model owns focus/stacking/keybind wiring. Never re-derive window toggling from local `useState` booleans and hand-rolled keydown listeners.
|
|
72
|
+
|
|
73
|
+
Window title-bar movement belongs to one primary pointer. Release, cancellation, capture loss, blur, and window unmount retire that gesture's listeners; another pointer cannot move it. Controlled positions and panel z-order remain caller-owned.
|
|
74
|
+
|
|
75
|
+
**Character sheet / paperdoll** is already shipped, not hand-rolled: `<CharacterSheet>` / `<Paperdoll>` / `<StatList>` (`@jgengine/react`) compose the paperdoll equip layout (worn `EquipmentSlots` arranged around a portrait) and the derived-stat readout into one window body. `Paperdoll` is the equip-slot arrangement, `StatList` the derived-stat rows, `CharacterSheet` the batteries-included default that pairs them — the game composes and reskins them (slot layout, stat labels/terminology, skin) rather than re-deriving a paperdoll from raw divs and manual stat math.
|
|
76
|
+
|
|
77
|
+
**Toast/announcer stacks**: two supported sources, pick the one that matches where the message comes from. An engine event already bound onto `ctx.game.feed` (kill feed, loot log, quest update) renders with `@jgengine/react`'s `ToastStack({ action })` directly. A game-raised one-off message with its own lifetime (boss-intro banner, objective callout) belongs in a `createToastQueue` (`@jgengine/core/game/toasts`, `toast-feed` capability — see `jgengine-gameplay`) rendered from `queue.list()`; don't reimplement the append-cap-plus-TTL-prune loop as a local `Toast`/`pushToast` pair.
|
|
78
|
+
|
|
79
|
+
Do not create a primitive whose only value is wrapping a `div` with border radius.
|
|
80
|
+
|
|
81
|
+
## Rendering — post-processing, lighting, shadows
|
|
82
|
+
|
|
83
|
+
**Look presets.** Unset `look` is `"neutral"` (#1719): the game's own `lighting`/`backdrop`/`postProcessing` with an AgX tone-map and no styling. One field picks a whole art direction: `"photoreal"` (high-contrast PBR post), `"cinematic"` (day sky, view-following shadow-casting sun, tone-map/bloom/SSAO/vignette), `"toon"` (clean graphic grade), `"comic"` (ink outlines + cel bands + saturated grade — Borderlands-style), `"retro"` (pixelated, posterized, no smoothing), and `"flat"` (bare ambient+directional rig, no post). Non-neutral presets add a day sky unless the world declares one. The preset only fills knobs you didn't set — any explicit `lighting`/`backdrop`/`postProcessing` always wins. `resolveGameLook` (`@jgengine/core/render/lookPreset`) is the pure resolver; assert on it in a `bun test` (the post/sky layers are not visible to `summarizeEnvironment`). HUD-presentation and no-camera games are unaffected.
|
|
84
|
+
|
|
85
|
+
The post chain itself is never hand-wired render passes. Set `defineGame({ postProcessing })` (`PostProcessingConfig` from `@jgengine/core/render/postProcessing`) and the shell mounts an `EffectComposer` and owns the render: RenderPass → AO → Bloom → tone-map output → Stylize → Grade. Each stage is a config object, `false` to skip, or omitted for its tuned default:
|
|
86
|
+
|
|
87
|
+
- `toneMapping: "aces" | "agx" | "reinhard" | "cineon" | "linear" | "none"` (default `aces`) + `exposure`.
|
|
88
|
+
- `bloom: { strength, radius, threshold }` — HDR glow around bright pixels (defaults 0.32 / 0.55 / 0.85).
|
|
89
|
+
- `grade: { lift, gain, gamma, saturation, vignette, grain }` — display-space colour grade: cool-shadow/warm-highlight split, vignette, animated film grain.
|
|
90
|
+
- `ao: { radius, intensity, distanceFalloff, blend }` — ground-truth ambient occlusion; heavier than the rest, omit or `false` on low-end targets.
|
|
91
|
+
- `stylize: { outline, bands, pixelSize }` — art style without touching materials. `outline: { color, thickness, opacity, depthThreshold, normalThreshold, fadeDistance }` traces ink lines from depth and normal edges (one extra normal/depth scene render, shadow maps reused); `bands` quantizes brightness into cel bands keeping hue; `pixelSize` snaps the frame to retro blocks. Mix freely: outline alone is a clean comic ink, `bands: 3` plus outline is cartoon, `pixelSize` plus `bands` is retro. Mark custom overlay effects with `POSTFX_OVERLAY_USERDATA` so they are not outlined; transparent, alpha-cut, points, lines and sprites are skipped automatically.
|
|
92
|
+
|
|
93
|
+
AO and depth-of-field prepasses reuse the beauty pass's shadow maps; they do not redraw shadow casters. Prepass failure restores shadow update policy and marked overlay visibility. The composer resizes passes through `setSize`, and graph cleanup calls each pass's `dispose` once; avoid reading a pass's internal targets or materials.
|
|
94
|
+
|
|
95
|
+
**Orbit camera occlusion:** the third-person orbit rig takes `camera: { collision: { enabled, padding?, minTargetDistance? } }` — a spring-arm that raycasts target→camera each frame and pulls the boom in past walls/terrain so the camera never clips inside geometry. Off by default (unchanged chase feel); enable it for any world with interiors or dense structures.
|
|
96
|
+
|
|
97
|
+
Lighting is `defineGame({ lighting })` (`LightingConfig`): ambient / hemisphere / directional, replacing the shell's default lights when set. A `DirectionalLightingConfig` with `castShadow` takes `shadowMapSize`, `shadowCameraSize`, `shadowBias`, `shadowNormalBias` for crisp contact shadows. Sky-lit worlds (a `sky()` world feature) get a high-res sun whose shadow camera follows the view each frame, so grounded shadows stay sharp under the player anywhere in a large world.
|
|
98
|
+
|
|
99
|
+
## First-person viewmodel, WorldOverlay props, render cues (#542)
|
|
100
|
+
|
|
101
|
+
Three composable-chrome seams for FPS/TPS polish — a placement hook plus a good default, never a forced look:
|
|
102
|
+
|
|
103
|
+
- **Custom viewmodel.** `defineGame({ viewmodel })` supplies a component rendered inside the shell's camera-locked, muzzle-tracked anchor in place of the built-in three-mesh gun — read when the active rig is first-person. It receives `ViewmodelProps` (`@jgengine/shell/camera`): `{ cuesRef }`, a live `MutableRefObject<EntityRenderCues>` for the followed entity — read `cuesRef.current` inside your own `useFrame` to drive bob/recoil/reload poses; never store it as render state (it updates every frame). `camera.firstPerson.viewmodel: false` renders no viewmodel at all regardless of `defineGame`'s `viewmodel` field — the explicit opt-out always wins. `readFirstPersonMuzzle(target: THREE.Vector3, camera?: THREE.Camera)` (also `@jgengine/shell/camera`) writes the live presentation muzzle world position into `target`; pass the active Three.js camera to isolate canvases. It returns `false` without changing `target` when no muzzle is available — the seam for muzzle-flash lights, tracer origins, or shell casings.
|
|
104
|
+
- **Weapon pose.** `camera.weapon: (entityId) => { handling, presentation } | null` hands the rigs the held weapon's `createWeaponHandling` frame and a `WeaponPresentationTuning` (`@jgengine/core/combat/weaponPresentation`). First person poses the viewmodel anchor from it (hip and ADS offsets, viewmodel FOV, `adsZoom`, sway from look speed, bob from `cuesRef`, recoil kick) and adds the recoil to the look; `shoulder` takes its ADS blend from `adsProgress` and adds the same recoil. A custom `viewmodel` rides the posed anchor. `bun run drive weapon-handling` (and `weapon-handling-shoulder`) is the dev demo.
|
|
105
|
+
- **`WorldOverlay` receives `{ ctx }`.** `defineGame({ WorldOverlay })` components take `WorldOverlayProps` (`@jgengine/core/game/playableGame`) — `ctx: GameContext` handed directly, no extra hook or module-global workaround needed to reach engine state from canvas-layer VFX. Shared bars/nameplates mounted there inherit the renderer's visibility context; both omit the local account and its currently possessed body. Occlusion still uses the render camera and the authored overlay anchor, with `occlude: false` as an explicit through-wall reveal. Custom geometry retains the overlay's camera-transparent policy.
|
|
106
|
+
- **Render cues for any custom rig.** `useEntityRenderCues(instanceId, tuning?)` (`@jgengine/shell/render/useEntityRenderCues`) returns the same `cuesRef` the viewmodel gets, for a custom `renderEntity` component: velocity-driven `bobPhase`, `firing`/`recoil` from `ctx.game.playEntityAnimation(instanceId, "fire")`, `reloading` from `"reload"`/`"reloadEnd"`, `hit` from `combat.hitReaction`, `dead` from `entity.died` — no diffing the parent group's position, no game-side module map for attack timing. `RenderCueTuning` (`@jgengine/core/combat/renderCues`) overrides the bob rate / recoil decay / pulse durations; see `jgengine-combat`'s `combat/renderCues` for the underlying pure math.
|
|
107
|
+
A custom `WorldOverlay` rig can keep `camera.firstPerson.viewmodel: false` and
|
|
108
|
+
register its own live muzzle for the shell's tracer presentation:
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
import { useEffect, useRef } from "react";
|
|
112
|
+
import { useThree } from "@react-three/fiber";
|
|
113
|
+
import type { Object3D } from "three";
|
|
114
|
+
import { registerFirstPersonMuzzle } from "@jgengine/shell/camera";
|
|
115
|
+
|
|
116
|
+
function CustomRig() {
|
|
117
|
+
const camera = useThree((state) => state.camera);
|
|
118
|
+
const muzzle = useRef<Object3D>(null);
|
|
119
|
+
useEffect(() => registerFirstPersonMuzzle(camera, (target) => {
|
|
120
|
+
const tip = muzzle.current;
|
|
121
|
+
if (tip === null) return false;
|
|
122
|
+
tip.getWorldPosition(target);
|
|
123
|
+
return true;
|
|
124
|
+
}), [camera]);
|
|
125
|
+
return <group>{/* game-owned animated weapon geometry */}<object3D ref={muzzle} /></group>;
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Place the marker at the game's actual barrel tip and move it with the custom
|
|
130
|
+
rig's recoil, aim, bob, reload, and equipment poses. Returning the registration's
|
|
131
|
+
cleanup from the effect releases only that owner, including StrictMode remounts.
|
|
132
|
+
The newest available custom reader wins over that camera's tracked stock muzzle;
|
|
133
|
+
return `false` when unequipped or unavailable. A failed/invalid read preserves
|
|
134
|
+
the caller's fallback origin. Registrations are limited to 64 per camera per
|
|
135
|
+
priority; legacy one-argument reads consider the newest 64 globally per priority.
|
|
136
|
+
Callbacks run on demand over a snapshot of at most 64 readers per priority.
|
|
137
|
+
Disposed readers are skipped before their callback starts; newly registered
|
|
138
|
+
readers join the next read. Callbacks must write finite world coordinates and should not throw.
|
|
139
|
+
|
|
140
|
+
This is presentation only: projectile prediction, collision, authority, and
|
|
141
|
+
`projectile.settled.origin` remain simulation-owned. Scrap Signal's
|
|
142
|
+
`FerralonViewmodel` is the concrete adopter: its `flash` marker already shares
|
|
143
|
+
`muzzleOffsetZ(gun.family)` with the actual barrel geometry. Its game-side
|
|
144
|
+
registration waits for the coordinator's verified published SDK release; no
|
|
145
|
+
source aliases or native verification of unpublished APIs are implied.
|
|
146
|
+
|
|
147
|
+
`@jgengine/shell/terrain`'s `ProceduralGround`/`CarvedTerrain`/`GrassField` are the game-facing terrain surface; the height-field math underneath (`arenaField`, `flatField`, `fractalNoise`, `resolveGroundStep`, `valueNoise`, `withNormal`, re-exported from `@jgengine/core/world/terrain`) and `GamePlayerShell`'s physics helpers (`applyMotionImpulses`, `nearbyObstacles`, `resolvePhysicsTuning`, `hasEnvironmentTerrain`, sourced from `core/runtime/motionIntents` / `core/movement`) are internal building blocks the shell composes for you — reach for `defineGame({ movement, world })` and the ground primitives above instead of calling these directly.
|
|
148
|
+
|
|
149
|
+
## 15. Compact implementation API appendix
|
|
150
|
+
|
|
151
|
+
Use the main `jgengine` skill for the authoritative engine API routing. The React package exposes `GameProvider`, hooks, and headless primitives from `@jgengine/react` and its documented subpaths. Common hooks include player/game state, entities, stats, inventory, quests, prompts, clocks, markers, fog, and engine stores. The shell provides `GameHost` (the game mount), input integration, devtools, and `GameUiPreview`; `@jgengine/shell/GamePlayerShell` (the internal player `GameHost` composes) also re-exports the movement primitives it wires up internally (`applyMotionImpulses`, `nearbyObstacles`, `resolvePhysicsTuning`, `hasEnvironmentTerrain`) for a host that needs to replicate its physics loop, and `@jgengine/shell/terrain` re-exports the `jgengine-world` field builders (`arenaField`, `flatField`, `fractalNoise`, `resolveGroundStep`, `valueNoise`, `withNormal`) alongside its own `CarvedTerrain`/`GrassField` components so a shell consumer never needs a second import from core.
|
|
152
|
+
|
|
153
|
+
Use these APIs to bind state; do not let API wiring dictate visual composition. Keybind labels should derive from the game’s binding table, and UI actions should dispatch through game commands rather than existing only as click handlers.
|
|
154
|
+
|
|
155
|
+
The shipped building blocks — inventory grids, toggleable windows, character sheets, bars — are legitimate to compose and are the right starting point; reach for them instead of re-deriving inventory, windowing, or a paperdoll. But neither they nor the headless seams are finished design on their own: the game must still arrange, place, name, and art-direct them into its own look. Compose the building blocks, then skin and lay them out — don't ship them unarranged.
|
|
156
|
+
|
|
157
|
+
## 2D sprite presentation
|
|
158
|
+
|
|
159
|
+
Core owns renderer-independent atlas data and animation state. Convert Aseprite or TexturePacker JSON with `fromAseprite` / `fromTexturePacker` from `@jgengine/core/assets/spriteAtlas`, then drive a clip with `createSpriteClipPlayer` from `@jgengine/core/render/sprite2d`. Persist `snapshot()` and restore it on load; use `retune({ speed })` for runtime playback changes. Use `sortingOrder(layers, layer, offset)` when a game maps named presentation layers to renderer order.
|
|
160
|
+
|
|
161
|
+
The shell's `EntitySprite` changes atlas UVs without uploading the image again. Configuration changes request a frame; frozen shell posters keep their demand rendering behavior.
|
|
162
|
+
|
|
163
|
+
## Model instances
|
|
164
|
+
|
|
165
|
+
Custom R3F model components use `useModelInstance` (`@jgengine/shell/render/useModelInstance`) inside Canvas and Suspense. Pass the model's URL, placement, shadows and animation; render its returned `scene` with `position` and uniform `scale`. The imported `content` stays inside that placement frame, so imported pivots/transforms and root animation survive normalization. The hook shares the diagnostic loader and bind-pose placement used by `EntityModel`, and releases cloned materials and bone textures on replacement/unmount. It works without a game provider. Placement retuning uses the initially configured bind frame even after root animation has moved the content. Geometry/textures retained by the loader remain shared; resources introduced or replaced by `configure` stay caller-owned.
|
|
166
|
+
|
|
167
|
+
Use the optional `configure(content)` callback for initial caller-owned styling or deformation; memoize it, since a changed callback rebuilds the clone. Live mood colors or custom motion can update `content` in caller effects without replacing it. Texture loading, material-map application and extra geometry remain caller-owned; `EntityModel` supplies those for ordinary model configurations. Each clone owns its pose, bone textures and cloned materials, with one skeleton/material per shared source skeleton/material. Geometry and image textures stay in the loader cache. Apply material overrides with `clone: false` to an instance that already owns its materials. Attachments own their separate cleanup.
|
|
168
|
+
|
|
169
|
+
For non-hook consumers, `cloneModelScene` and `disposeModelScene` (`@jgengine/shell/render/modelRender`) provide the same resource ownership. `cloneMaterials: false` borrows source materials; do not mutate them.
|
|
170
|
+
|
|
171
|
+
`useModelInstance` already mounts `useModelAnimation`; do not mount a second driver on that instance. Other custom scene owners use `useModelAnimation` (`@jgengine/shell/render/useModelAnimation`) instead of creating a mixer in game code. It releases mixer bindings on replacement/unmount and skips updates for paused or completed single clips. Configuration and animation events request a frame without continuously waking a frozen poster.
|
|
172
|
+
|
|
173
|
+
`EntityModel` starts resolved part, attachment and material-map requests alongside the root model through the existing loader cache. Loaded maps apply before their next draw, without eagerly uploading an unconfigured color texture. Keep per-part error boundaries; a failed texture or attachment still reports its own fallback.
|
|
174
|
+
|
|
175
|
+
Cascaded lighting patches streamed meshes on attachment and material replacement instead of scanning the scene each frame. Replacing or unmounting a cascaded light releases its lights, targets, shadow maps and shader bindings while retaining authored material hooks. Authored cascade counts and map sizes keep priority over graphics-profile defaults.
|
|
176
|
+
|
|
177
|
+
**Scrolling controls.** `ScrollRail` (`@jgengine/react/scrollRail`) wraps caller-owned items with native horizontal scrolling, overflow-only 44px navigation buttons, and automatic focus reveal. Supply `label` for navigation names, prevent items from shrinking, and keep selection and keyboard semantics in the caller. Shared settings rows use it; short sidebar settings remain vertically scrollable. Touch movement sits below the HUD so the floating joystick capture zone cannot intercept an interactive panel; touch action clusters keep their higher layer.
|
|
178
|
+
|
|
179
|
+
**Controlled dialogs.** `useDialogBehavior({ open, onClose?, initialFocus?, focusKey? })` (`@jgengine/react/dialogBehavior`) returns a ref for the game's own dialog element. It owns initial focus, Tab cycling, Escape close requests, and return focus; it does not create modal state or markup. Set `role="dialog"`, `aria-modal`, an accessible name, and `tabIndex={-1}` on the root. Use `initialFocus: "root"` to focus the dialog itself. Omit `onClose` to disable Escape, or leave `open` true when the game rejects a close. `focusKey` refocuses changed content while keeping the original return target. Replacing an open root preserves its dialog order and original return target; a portalled child whose opener was removed returns to the replacement parent root. Nested dialogs may render through portals; only the most recently opened dialog handles keys. `ModalHost` uses the same behavior for its stack, so game-owned screens need no mirrored `ModalStack`.
|