@jgengine/shell 0.13.0 → 0.15.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +529 -1
  2. package/dist/CombatPresentation.js +2 -1
  3. package/dist/GamePlayerShell.js +25 -5
  4. package/dist/Shell3dPresentation.d.ts +6 -6
  5. package/dist/Shell3dPresentation.js +4 -4
  6. package/dist/camera/ControllerCameraShake.d.ts +31 -0
  7. package/dist/camera/ControllerCameraShake.js +34 -0
  8. package/dist/camera/GameCameraRig.js +10 -5
  9. package/dist/camera/GameOrbitCamera.js +1 -10
  10. package/dist/camera/cameraRigs.js +16 -4
  11. package/dist/camera/index.d.ts +1 -0
  12. package/dist/camera/index.js +1 -0
  13. package/dist/camera/rigMath.d.ts +2 -0
  14. package/dist/camera/rigMath.js +7 -0
  15. package/dist/devtools/CollisionDebugWorld.js +15 -1
  16. package/dist/devtools/DevtoolsOverlay.js +2 -0
  17. package/dist/devtools/collisionDebugMath.d.ts +8 -0
  18. package/dist/devtools/collisionDebugMath.js +14 -5
  19. package/dist/drivers/FrameDriver.d.ts +4 -1
  20. package/dist/drivers/FrameDriver.js +5 -3
  21. package/dist/environment/DayNightSky.d.ts +33 -0
  22. package/dist/environment/DayNightSky.js +51 -0
  23. package/dist/environment/Daylight.d.ts +3 -1
  24. package/dist/environment/Daylight.js +7 -3
  25. package/dist/environment/EnvironmentScene.js +6 -6
  26. package/dist/environment/RoadJunctions.d.ts +8 -4
  27. package/dist/environment/RoadJunctions.js +16 -15
  28. package/dist/environment/RoadRibbons.d.ts +42 -6
  29. package/dist/environment/RoadRibbons.js +150 -27
  30. package/dist/environment/VolumetricClouds.js +3 -3
  31. package/dist/environment/index.d.ts +1 -0
  32. package/dist/environment/index.js +1 -0
  33. package/dist/gameKit.d.ts +7 -4
  34. package/dist/gameKit.js +8 -5
  35. package/dist/inputSink.js +14 -0
  36. package/dist/materialOverride.js +30 -0
  37. package/dist/postfx/PostProcessing.d.ts +3 -2
  38. package/dist/postfx/PostProcessing.js +38 -5
  39. package/dist/postfx/ScreenEffectsOverlay.d.ts +32 -0
  40. package/dist/postfx/ScreenEffectsOverlay.js +70 -0
  41. package/dist/postfx/postfxOverlay.d.ts +24 -0
  42. package/dist/postfx/postfxOverlay.js +35 -0
  43. package/dist/presentationResolve.d.ts +3 -2
  44. package/dist/presentationResolve.js +11 -6
  45. package/dist/render/CascadedShadows.d.ts +13 -0
  46. package/dist/render/CascadedShadows.js +96 -0
  47. package/dist/render/EntityPreview.d.ts +53 -0
  48. package/dist/render/EntityPreview.js +23 -0
  49. package/dist/render/EnvironmentLighting.d.ts +23 -7
  50. package/dist/render/EnvironmentLighting.js +69 -10
  51. package/dist/render/PartMotion.js +4 -4
  52. package/dist/render/SceneLighting.js +7 -0
  53. package/dist/render/SceneModels.d.ts +37 -1
  54. package/dist/render/SceneModels.js +52 -10
  55. package/dist/render/measureBounds.js +4 -0
  56. package/dist/render/measureCollisionMesh.d.ts +26 -0
  57. package/dist/render/measureCollisionMesh.js +108 -0
  58. package/dist/render/modelLoad.d.ts +41 -5
  59. package/dist/render/modelLoad.js +85 -18
  60. package/dist/render/sceneCapture.d.ts +27 -0
  61. package/dist/render/sceneCapture.js +43 -0
  62. package/dist/render/useDisposable.d.ts +16 -0
  63. package/dist/render/useDisposable.js +24 -0
  64. package/dist/render/useModelAnimation.js +47 -4
  65. package/dist/scene/AuthoredScene.js +14 -24
  66. package/dist/scene/CityRenderer.js +103 -36
  67. package/dist/scene/GrassFieldRenderer.js +4 -1
  68. package/dist/scene/PoleLineRenderer.js +7 -16
  69. package/dist/settings/QuickControls.js +5 -3
  70. package/dist/settings/SettingsMenu.js +6 -3
  71. package/dist/settings/appliedSettings.d.ts +3 -1
  72. package/dist/settings/appliedSettings.js +17 -5
  73. package/dist/settings/settingsController.js +11 -5
  74. package/dist/shellMovement.d.ts +15 -1
  75. package/dist/shellMovement.js +15 -1
  76. package/dist/structures/GeneratedBuilding.js +4 -5
  77. package/dist/structures/TransformGizmo.js +6 -5
  78. package/dist/terrain/CarvedTerrain.js +3 -7
  79. package/dist/terrain/GrassField.js +2 -2
  80. package/dist/terrain/ProceduralGround.js +3 -5
  81. package/dist/terrain/grassMaterial.d.ts +8 -2
  82. package/dist/terrain/grassMaterial.js +24 -5
  83. package/dist/terrain/index.d.ts +1 -1
  84. package/dist/terrain/terrainDetailMaterial.js +11 -2
  85. package/dist/terrain/terrainMath.d.ts +38 -1
  86. package/dist/terrain/terrainMath.js +86 -64
  87. package/dist/touch/TouchControlsOverlay.d.ts +6 -2
  88. package/dist/touch/TouchControlsOverlay.js +136 -17
  89. package/dist/vfx/ParticleField.d.ts +32 -0
  90. package/dist/vfx/ParticleField.js +91 -0
  91. package/dist/vfx/WorldFloatingText.d.ts +32 -0
  92. package/dist/vfx/WorldFloatingText.js +32 -0
  93. package/dist/water/Ocean.js +3 -6
  94. package/dist/weather/LightningStrike.js +3 -9
  95. package/dist/weather/RainField.js +3 -7
  96. package/dist/weather/SnowField.js +3 -7
  97. package/dist/world/DataObjects.js +3 -6
  98. package/dist/world/GridWorldScene.js +3 -6
  99. package/dist/world/InstancedBodies.js +3 -6
  100. package/dist/world/InstancedJoints.js +2 -2
  101. package/dist/world/SpriteBatch.js +3 -6
  102. package/dist/world/WorldEntityFrames.d.ts +47 -0
  103. package/dist/world/WorldEntityFrames.js +75 -0
  104. package/dist/world/WorldHud.js +7 -5
  105. package/dist/world/WorldPings.d.ts +26 -0
  106. package/dist/world/WorldPings.js +68 -0
  107. package/dist/world/WorldScene.js +9 -4
  108. package/dist/world/pingPulse.d.ts +21 -0
  109. package/dist/world/pingPulse.js +17 -0
  110. package/package.json +5 -5
  111. package/skills/ce-handoff/SKILL.md +149 -0
  112. package/skills/game-design/SKILL.md +2 -0
  113. package/skills/jgengine/SKILL.md +7 -3
  114. package/skills/jgengine/api.md +17 -7
  115. package/skills/jgengine/capabilities.md +12 -0
  116. package/skills/jgengine/recipes/minimal-game.md +7 -4
  117. package/skills/jgengine-ui/SKILL.md +8 -3
  118. package/skills/jgengine-ui/api.md +732 -23
  119. package/skills/jgengine-ui/capabilities.md +371 -2
  120. package/skills/jgengine-ui/recipes/overhead-entity-frames.md +136 -0
  121. package/skills/jgengine-ui/reference.md +7 -4
  122. package/skills/jgengine-world/api.md +408 -12
  123. package/skills/jgengine-world/capabilities.md +127 -2
  124. package/skills/jgengine-world/reference.md +15 -1
@@ -4,6 +4,18 @@
4
4
 
5
5
  Reach for these before hand-rolling. Each row is *the thing you need* → *the primitive that already does it*.
6
6
 
7
+ ## ai-driver — difficulty-aware chase/route driving step producing throttle/brake/steer for the vehicle sim
8
+
9
+ - `driveStep` (function) · `import { driveStep } from "@jgengine/core/ai/driver"`
10
+
11
+ ## ai-driver-path-target — pure-pursuit lookahead target on a road/route polyline for street following
12
+
13
+ - `pathTargetAhead` (function) · `import { pathTargetAhead } from "@jgengine/core/ai/driver"`
14
+
15
+ ## ai-driver-state — fresh serializable state for one difficulty-aware AI driver
16
+
17
+ - `createDriverState` (function) · `import { createDriverState } from "@jgengine/core/ai/driver"`
18
+
7
19
  ## area-effect-field — source-following area membership with enter/refresh/leave edges, stacking, and cleanup
8
20
 
9
21
  - `createAreaEffectField` (function) · `import { createAreaEffectField } from "@jgengine/core/world"`
@@ -70,6 +82,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
70
82
  ## authored-objects — marker kinds resolved as entity spawns, not static props
71
83
 
72
84
  - `ENTITY_MARKER_KINDS` (const) · `import { ENTITY_MARKER_KINDS } from "@jgengine/core/world/authoredObjects"`
85
+ - `markerAnimation` (function) · `import { markerAnimation } from "@jgengine/core/world/authoredObjects"`
73
86
  - `markerCatalogId` (function) · `import { markerCatalogId } from "@jgengine/core/world"`
74
87
  - `placeAuthoredObjects` (function) · `import { placeAuthoredObjects } from "@jgengine/core/world"`
75
88
  - `resolveAuthoredObjects` (function) · `import { resolveAuthoredObjects } from "@jgengine/core/world"`
@@ -113,11 +126,23 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
113
126
 
114
127
  - `deriveBuildingLots` (function) · `import { deriveBuildingLots } from "@jgengine/core/world/buildingLots"`
115
128
 
129
+ ## camera-shake — seeded serializable trauma-based camera shake/impulse controller (hit/explosion/landing juice) with pooled per-frame translation+rotation offset and snapshot/restore
130
+
131
+ - `createCameraShake` (function) · `import { createCameraShake } from "@jgengine/core/world"`
132
+
116
133
  ## city-district — polygon math for road-derived blocks, parcels, and buildable footprints
117
134
 
118
135
  - `Vec2` (type) · `import { Vec2 } from "@jgengine/core/world/cityGeometry"`
136
+ - `extractGraphBlocks` (function) · `import { extractGraphBlocks } from "@jgengine/core/world/cityBlocks"`
137
+ - `rectClearsPolyline` (function) · `import { rectClearsPolyline } from "@jgengine/core/world/cityGeometry"`
138
+ - `rectsSeparated` (function) · `import { rectsSeparated } from "@jgengine/core/world/cityGeometry"`
119
139
  - `resolveCityObject` (function) · `import { resolveCityObject } from "@jgengine/core/world"`
120
140
 
141
+ ## city-generator — subdivide street blocks into size-tiered street-fronting plots
142
+
143
+ - `deriveCityPlots` (function) · `import { deriveCityPlots } from "@jgengine/core/world"`
144
+ - `resolveCityLotContent` (function) · `import { resolveCityLotContent } from "@jgengine/core/world"`
145
+
121
146
  ## companion-intent — resolve a companion's command + threat context into a per-tick follow/hold/engage/assist intent
122
147
 
123
148
  - `resolveCompanionIntent` (function) · `import { resolveCompanionIntent } from "@jgengine/core/scene/companion"`
@@ -130,14 +155,58 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
130
155
 
131
156
  - `chooseContourInterval` (function) · `import { chooseContourInterval } from "@jgengine/core/world"`
132
157
 
158
+ ## countdown-timers — named countdown/countup timer set on an injected clock — round timers, respawn clocks, ability cooldown/charge; read remaining/elapsed/progress + expiry, snapshot/restore
159
+
160
+ - `createTimerSet` (function) · `import { createTimerSet } from "@jgengine/core/world"`
161
+
133
162
  ## crouch-prone — stance/pose transitions that change the hitbox
134
163
 
135
164
  - `createPoseState` (function) · `import { createPoseState } from "@jgengine/core/world"`
136
165
 
166
+ ## damage-direction — serializable hit-from damage-direction indicator brain — pooled directional arcs that flare on `registerHit(angle, intensity)` and fade on an injected clock, with optional same-direction merging
167
+
168
+ - `createDamageDirectionTracker` (function) · `import { createDamageDirectionTracker } from "@jgengine/core/world"`
169
+
170
+ ## day-night-cycle — one wired model gives a moving day-night cycle — running clock plus keyframed phase labels and tint/light colors a game drives an existing sky/daylight seam from
171
+
172
+ - `createDayNightCycle` (function) · `import { createDayNightCycle } from "@jgengine/core/world"`
173
+
137
174
  ## decay-meter — survival meters that drain/refill over game time (hunger, water, oxygen, stamina)
138
175
 
139
176
  - `createDecayMeterSet` (function) · `import { createDecayMeterSet } from "@jgengine/core/procedural"`
140
177
 
178
+ ## difficulty-ability-gate — spend-or-hold ability decision from a scored opportunity window
179
+
180
+ - `shouldUseAbility` (function) · `import { shouldUseAbility } from "@jgengine/core/ai/difficulty"`
181
+
182
+ ## difficulty-execution-error — symmetric aim/lead/timing error scaled by the profile
183
+
184
+ - `executionError` (function) · `import { executionError } from "@jgengine/core/ai/difficulty"`
185
+
186
+ ## difficulty-lookahead — depth/width-bounded deterministic planner for turn-based or sequential decisions
187
+
188
+ - `planLookahead` (function) · `import { planLookahead } from "@jgengine/core/ai/difficulty"`
189
+
190
+ ## difficulty-pick — noisy argmax over scored options; expert picks best, easy blunders
191
+
192
+ - `pickScored` (function) · `import { pickScored } from "@jgengine/core/ai/difficulty"`
193
+
194
+ ## difficulty-profile — derive a per-game decision-quality profile from a canonical tier
195
+
196
+ - `difficultyProfile` (function) · `import { difficultyProfile } from "@jgengine/core/ai/difficulty"`
197
+
198
+ ## difficulty-reaction-gate — delay acting on new information by the profile's reaction time
199
+
200
+ - `advanceReactionGate` (function) · `import { advanceReactionGate } from "@jgengine/core/ai/difficulty"`
201
+
202
+ ## difficulty-reaction-gate-state — fresh serializable reaction-gate state for one observed value
203
+
204
+ - `createReactionGate` (function) · `import { createReactionGate } from "@jgengine/core/ai/difficulty"`
205
+
206
+ ## difficulty-tiers — canonical easy/standard/expert decision-quality profiles for any opponent
207
+
208
+ - `DIFFICULTY_TIERS` (const) · `import { DIFFICULTY_TIERS } from "@jgengine/core/ai/difficulty"`
209
+
141
210
  ## elevation-readout — cursor height and delta-from-reference feedback
142
211
 
143
212
  - `sampleElevation` (function) · `import { sampleElevation } from "@jgengine/core/world"`
@@ -150,6 +219,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
150
219
 
151
220
  - `entityMetaOf` (function) · `import { entityMetaOf } from "@jgengine/core/world"`
152
221
 
222
+ ## fast-travel — network of fast-travel destinations with discovery, distance-sorted queries, nearest lookup, a travel gate, and serializable state
223
+
224
+ - `createFastTravelNetwork` (function) · `import { createFastTravelNetwork } from "@jgengine/core/world"`
225
+
153
226
  ## flight-dynamics — simulate fixed-wing, helicopter, and VTOL aircraft
154
227
 
155
228
  - `createAircraftDynamics` (function) · `import { createAircraftDynamics } from "@jgengine/core/world"`
@@ -208,10 +281,18 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
208
281
  - `pointGravity` (function) · `import { pointGravity } from "@jgengine/core/world"`
209
282
  - `uniformGravity` (function) · `import { uniformGravity } from "@jgengine/core/world"`
210
283
 
284
+ ## grounded — ground an XZ point onto the world surface as a pose triple
285
+
286
+ - `grounded` (function) · `import { grounded } from "@jgengine/core/world"`
287
+
211
288
  ## hold-order — stand-ground order that holds position until preempted or canceled
212
289
 
213
290
  - `defineHoldOrder` (function) · `import { defineHoldOrder } from "@jgengine/core/world"`
214
291
 
292
+ ## interaction-prompt — observable nearest-interactable prompt registry over the proximity-prompt resolver — register/resolve/active with priority+range and change-notified snapshot/restore
293
+
294
+ - `createPromptRegistry` (function) · `import { createPromptRegistry } from "@jgengine/core/world"`
295
+
215
296
  ## interest-census — running active/dormant gate tally folded into the caller's tick loop
216
297
 
217
298
  - `createInterestCensus` (function) · `import { createInterestCensus } from "@jgengine/core/world"`
@@ -228,6 +309,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
228
309
 
229
310
  - `interestPhase` (function) · `import { interestPhase } from "@jgengine/core/world"`
230
311
 
312
+ ## key-rebinding — conflict-aware key-remap session over the action-binding model — capture/reassign, conflict detection, reset-to-defaults, persist overrides, snapshot/restore
313
+
314
+ - `createRebindSession` (function) · `import { createRebindSession } from "@jgengine/core/input/rebindSession"`
315
+
231
316
  ## limb-health — per-body-part/region health tracked separately
232
317
 
233
318
  - `createMultiRegionHealth` (function) · `import { createMultiRegionHealth } from "@jgengine/core/procedural"`
@@ -240,6 +325,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
240
325
 
241
326
  - `generateLock` (function) · `import { generateLock } from "@jgengine/core/world"`
242
327
 
328
+ ## map-annotations — player-drawn map annotation layer — freehand strokes, area shapes, and pinned notes, projected into the map's routes/zones props and serializable
329
+
330
+ - `createAnnotationLayer` (function) · `import { createAnnotationLayer } from "@jgengine/core/world"`
331
+
243
332
  ## mesh-hitboxes — shots pass through holes in concave models — opted-in catalog assets raycast their actual triangles instead of the fitted box.
244
333
 
245
334
  - `ColliderShape` (type) · `import { ColliderShape } from "@jgengine/core/scene/colliders"`
@@ -273,6 +362,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
273
362
 
274
363
  - `createOrderRegistry` (function) · `import { createOrderRegistry } from "@jgengine/core/world"`
275
364
 
365
+ ## particle-system — deterministic pooled particle emitter with SoA render buffers, burst/continuous emission, gravity/drag, size/color/alpha-over-life, and serializable state
366
+
367
+ - `createParticleSystem` (function) · `import { createParticleSystem } from "@jgengine/core/world"`
368
+
276
369
  ## path-follow-progress — read normalized/distance/segment progress from follow state
277
370
 
278
371
  - `pathFollowProgress` (function) · `import { pathFollowProgress } from "@jgengine/core/world"`
@@ -355,6 +448,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
355
448
  - `controlGroupKey` (function) · `import { controlGroupKey } from "@jgengine/core/world"`
356
449
  - `resolveControlGroupIntent` (function) · `import { resolveControlGroupIntent } from "@jgengine/core/world"`
357
450
 
451
+ ## scatter-coverage — shared density/budget semantics for scatterable kinds
452
+
453
+ - `ScatterCoverageKind` (type) · `import { ScatterCoverageKind } from "@jgengine/core/world"`
454
+
455
+ ## screen-effects — serializable, pooled screen-feedback controller — transient flash/vignette and sustained low-health-style pulses with eased opacities, free-string kinds the game styles, and snapshot/restore
456
+
457
+ - `createScreenEffects` (function) · `import { createScreenEffects } from "@jgengine/core/world"`
458
+
358
459
  ## selection-bookmark-recall — fold a saved set into the active selection with stale-ref pruning and a caller focus hook
359
460
 
360
461
  - `recallSelectionBookmark` (function) · `import { recallSelectionBookmark } from "@jgengine/core/world"`
@@ -363,6 +464,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
363
464
 
364
465
  - `createSelectionBookmarks` (function) · `import { createSelectionBookmarks } from "@jgengine/core/world"`
365
466
 
467
+ ## sequence-director — data-driven cutscene timeline — fire typed camera/dialogue/event cues on one injected clock with play/pause/seek/skip and snapshot/restore
468
+
469
+ - `createSequenceDirector` (function) · `import { createSequenceDirector } from "@jgengine/core/world"`
470
+
471
+ ## shared-annotations — broadcast local map-drawing edits over the party feed and mirror inbound edits — collaborative annotations over any replicated feed
472
+
473
+ - `createSharedAnnotations` (function) · `import { createSharedAnnotations } from "@jgengine/core/world"`
474
+
366
475
  ## skill-check — a timing-bar skill check where you release inside a zone
367
476
 
368
477
  - `skillCheckZoneAt` (function) · `import { skillCheckZoneAt } from "@jgengine/core/world"`
@@ -415,7 +524,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
415
524
 
416
525
  - `defineStopOrder` (function) · `import { defineStopOrder } from "@jgengine/core/world"`
417
526
 
418
- ## street-generator — build a deterministic street/track graph (nodes, edges, streets, junctions, bridges, tunnels) from sliders
527
+ ## street-generator — build a deterministic street/track graph (nodes, edges, streets, junctions, sidewalks, bridges, tunnels) from sliders
419
528
 
420
529
  - `generateStreets` (function) · `import { generateStreets } from "@jgengine/core/world/streetGenerator"`
421
530
 
@@ -455,8 +564,9 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
455
564
 
456
565
  - `raycastHeightField` (function) · `import { raycastHeightField } from "@jgengine/core/world/terrain"`
457
566
 
458
- ## touch-controls — default on-screen button silhouette for a touch action
567
+ ## touch-controls — switch the on-screen touch control set when gameplay context changes (enter/exit vehicle, mount, build mode)
459
568
 
569
+ - `setTouchControlsMode` (function) · `import { setTouchControlsMode } from "@jgengine/core/input/touchControlsMode"`
460
570
  - `touchButtonShape` (function) · `import { touchButtonShape } from "@jgengine/core/input/touchScheme"`
461
571
 
462
572
  ## visibility-field — per-viewer-group observed/remembered/hidden fog-of-war knowledge with terrain memory and authoritative entity disclosure
@@ -467,10 +577,25 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
467
577
 
468
578
  - `VolumetricCloudsConfig` (interface) · `import { VolumetricCloudsConfig } from "@jgengine/core/world"`
469
579
 
580
+ ## wave-runner — observable serializable wave/spawn runner over the seeded spawn director — WAVE N, wave progress, budget/alert readout, onSpawn sink, snapshot/restore
581
+
582
+ - `createWaveRunner` (function) · `import { createWaveRunner } from "@jgengine/core/world"`
583
+
584
+ ## waypoint-store — serializable player waypoint layer — place/track/clear pins, mirror to a MarkerSet, and on-screen bearing/distance guidance for the tracked waypoint
585
+
586
+ - `createWaypointStore` (function) · `import { createWaypointStore } from "@jgengine/core/world"`
587
+
470
588
  ## world-environment — compose editor/preset-derived outdoor dressing — terrain, weather, water, structures — into a legacy environment feature
471
589
 
472
590
  - `environment` (function) · `import { environment } from "@jgengine/core/world"`
473
591
 
592
+ ## world-intersections — connect sidewalks and lane guidance through generated intersections
593
+
594
+ - `buildIntersectionMarkings` (function) · `import { buildIntersectionMarkings } from "@jgengine/core/world/roads"`
595
+ - `buildJunctionConnector` (function) · `import { buildJunctionConnector } from "@jgengine/core/world/roads"`
596
+ - `buildTrimmedIntersections` (function) · `import { buildTrimmedIntersections } from "@jgengine/core/world"`
597
+ - `trimBandAtJunctions` (function) · `import { trimBandAtJunctions } from "@jgengine/core/world"`
598
+
474
599
  ## world-place — declare the place a game happens in — flat/round/voxel/board ground, surface laws, per-place physics
475
600
 
476
601
  - `world` (function) · `import { world } from "@jgengine/core/world"`
@@ -71,7 +71,7 @@ Pure `@jgengine/core` functions so gameplay reads the same world the shell rende
71
71
  | `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 |
72
72
  | `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) |
73
73
  | `resolveScatterRegion(region, terrain?)` / `resolveScatter(doc, terrain?)` | Deterministic instanced placements for a **freehand polygon** scatter region (the editor's foliage lasso): `density`/`minSpacing`/`seed`, `minScale`/`maxScale`, weighted `palette`, `maxSlope`/`minHeight`/`maxHeight` masks, `edgeFalloff` feathering, `alignToNormal`. Regions ride editor paths of `kind` `SCATTER_PATH_KIND` — `scatterRegionFromPath`/`readScatterRules`/`readScatterPalette` build one from a path's meta, `scatterRegionEstimate` gives the live count readout, `resolveScatter` walks every scatter path in a document. Polygon helpers: `pointInPolygon`, `distanceToPolygonEdge`, `polygonArea`, `polygonBounds`, `isScatterPath`. Same seed → same field; GPU-instanced, never one node per instance. |
74
- | `resolveAuthoredObjects(doc)` / `placeAuthoredObjects(store, objects, sampleHeight, opts?)` / `placeAuthoredObjectsFromDocument` / `markerCatalogId(marker)` | Catalog mesh props from editor markers: every marker with first-class `catalogId` or `meta.catalogId` → `{ catalogId, x, z, rotationY, instanceId, verticalOffset }[]`. `placeAuthoredObjects` grounds on `sampleHeight` and calls `store.place` (WorldScene renders via `objectModels`). Shell: `<AuthoredObjects document field />` or `<AuthoredScene placeObjects />`. Never hand-loop `document.markers` for prop placement. |
74
+ | `resolveAuthoredObjects(doc)` / `placeAuthoredObjects(store, objects, sampleHeight, opts?)` / `placeAuthoredObjectsFromDocument` / `markerCatalogId(marker)` / `markerAnimation(marker)` | Catalog mesh props from editor markers: every marker with first-class `catalogId` or `meta.catalogId` → `{ catalogId, x, z, rotationY, instanceId, verticalOffset, animation? }[]`. A placement's authored `marker.meta.animation` (#1274) is read by `markerAnimation`, carried as the optional `AuthoredObject.animation`, and threaded through `placeAuthoredObjects` → `store.place` onto the placed object's `ModelConfig.animation`, so a per-placement rig override (role→clip, auto/none, speeds, fade, one-shots) applies at play time (#1276); absent, animation stays catalog-resolved. `placeAuthoredObjects` grounds on `sampleHeight` and calls `store.place` (WorldScene renders via `objectModels`). Shell: `<AuthoredObjects document field />` or `<AuthoredScene placeObjects />`. Never hand-loop `document.markers` for prop placement. |
75
75
  | `buildingIndex(district)` → `BuildingIndex` | `at`/`within`/`nearest`/`isInside`/`blockers` over a generated district — placement avoidance, pathfinding |
76
76
 
77
77
  **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`).
@@ -259,3 +259,17 @@ Path progress adapter (`nav/pathFollow`): `PathProgress` is `{ kind: "normalized
259
259
  - **Preemption policy** on each issue: `replace` (preempt active + clear pending), `append` (shift-queue), `front` (jump the queue without dropping the active order), `reject` (refuse while busy). Mark an order `uninterruptible` and a replacing order waits until it finishes; explicit `cancelActive` still forces it.
260
260
  - **Order kinds** are `OrderKind<Ctx, Payload>` with `start`/`update`/`finish`, composed over two narrow adapters — `OrderMover` (`position`/`moveToward`/`halt`) and `OrderTargeting` (`acquire`/`positionOf`). Built-ins ship as ordinary compositions with a configurable `kind` string so a game re-skins a verb ("harvest") without engine edits: `defineMoveOrder`, `defineStopOrder`, `defineHoldOrder`, `defineAttackMoveOrder`, `defineTargetedOrder`, `definePatrolOrder`. Engagement verbs write `{ engaging, inRange }` into `order.state`; the game reads it to run the actual attack/effect, keeping combat resolution game-side.
261
261
 
262
+
263
+ ### AI decision quality (difficulty tiers)
264
+
265
+ `ai/difficulty` — one serializable `DifficultyProfile` (reaction time, decision noise, execution jitter, ability discipline, perception scale, plan depth/width) owns *how well* any agent decides; the substrates above own *what* it can do. `DIFFICULTY_TIERS` ships canonical `easy`/`standard`/`expert`; `difficultyProfile(tier, overrides)` derives per-game variants. Appliers sit at the universal decision seams, all deterministic under an injected rng:
266
+
267
+ - **React** — `createReactionGate(initial)` + `advanceReactionGate(state, dt, desired, profile)`: new information (best target, last-seen position) only takes effect after `reactionSeconds` of stability; feed the *returned* value into pursuit/aim, not the raw `desired`.
268
+ - **Choose** — `pickScored(options, profile, rng)`: noisy argmax over any caller-scored list (acquisition candidates, abilities, retreat routes); expert plays optimally, easy blunders at `decisionNoise` rate.
269
+ - **Execute** — `executionError(profile, rng, scale)`: symmetric aim/lead/timing error to add to a yaw, lead, or release time.
270
+ - **Spend** — `shouldUseAbility(opportunity, profile, rng)`: score the moment `0..1` game-side (targets clumped, player mid-animation); low-discipline profiles waste cooldowns, high-discipline ones hold for strong windows.
271
+ - **Plan** — `planLookahead(root, domain, profile, rng)`: depth/width-bounded negamax over a game-owned `LookaheadDomain` (`moves`/`apply`/`evaluate`, `adversarial: false` for own-sequence planning like rotations or build orders). Easy is greedy and walks into forks; expert finds multi-ply tactics. Run at decision cadence, not per frame.
272
+
273
+ Compose, don't replace: scale `MobBrainConfig.aggroRadius` by `perceptionScale`, route `acquireTarget` scoring through `pickScored`, gate `advancePursuit` target swaps behind the reaction gate.
274
+
275
+ **Drive** — `ai/driver` is the vehicle consumer of the profile: `driveStep(state, dt, pose, goal, profile, tuning, rng, obstacleAhead?)` turns a pose + goal into the shared `AxisInput` (`throttle`/`brake`/`steer`) for `tickDrivableVehicle`. The profile decides how well the car drives: the steering target is a delay-line resampled every `reactionSeconds` (easy chasers cut to where the target *was*), corners shed speed instead of orbiting at full throttle, obstacle distance is perceived late by `speed × reactionSeconds`, the wheel wobbles by `executionJitter` (resampled on an interval, so it reads as a loose wheel), and a car grinding a wall backs out with counter-steer after a tier-scaled delay (brake past standstill is the sim's reverse). `createDriverState` is one small serializable object; the caller owns the sim, collision, and all spatial queries. For streets and circuits, `pathTargetAhead(path, x, z, lookahead, loop?)` walks a road/route polyline to the pure-pursuit point ahead — feed it back in as the goal each tick and the same brain follows authored roads; give sharper tiers a longer lookahead.