@jgengine/shell 0.11.0 → 0.12.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 (130) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/CombatPresentation.d.ts +5 -0
  3. package/dist/CombatPresentation.js +8 -0
  4. package/dist/GameHost.d.ts +25 -1
  5. package/dist/GameHost.js +60 -4
  6. package/dist/GamePlayer.d.ts +5 -0
  7. package/dist/GamePlayer.js +5 -0
  8. package/dist/GamePlayerShell.d.ts +5 -0
  9. package/dist/GamePlayerShell.js +48 -358
  10. package/dist/GameUiPreview.js +3 -0
  11. package/dist/Shell3dPresentation.d.ts +63 -0
  12. package/dist/Shell3dPresentation.js +302 -0
  13. package/dist/ShellChrome.d.ts +51 -0
  14. package/dist/ShellChrome.js +51 -0
  15. package/dist/ShellHudPresentation.d.ts +41 -0
  16. package/dist/ShellHudPresentation.js +22 -0
  17. package/dist/audio/audioEngine.d.ts +4 -0
  18. package/dist/audio/audioEngine.js +111 -26
  19. package/dist/audio/audioWire.d.ts +22 -4
  20. package/dist/audio/audioWire.js +46 -3
  21. package/dist/audio/loopParams.d.ts +20 -0
  22. package/dist/audio/loopParams.js +28 -0
  23. package/dist/camera/GameCameraRig.js +6 -1
  24. package/dist/camera/cameraRigs.d.ts +3 -1
  25. package/dist/camera/cameraRigs.js +21 -3
  26. package/dist/camera/rigMath.d.ts +20 -0
  27. package/dist/camera/rigMath.js +29 -0
  28. package/dist/defineGame.d.ts +15 -1
  29. package/dist/defineGame.js +26 -4
  30. package/dist/devtools/DevtoolsOverlay.d.ts +4 -4
  31. package/dist/devtools/DevtoolsOverlay.js +28 -12
  32. package/dist/devtools/agentBridge.js +4 -0
  33. package/dist/devtools/perfDiagnose.js +1 -1
  34. package/dist/diagnostics/RuntimeDiagnostics.d.ts +7 -0
  35. package/dist/diagnostics/RuntimeDiagnostics.js +23 -12
  36. package/dist/environment/EnvironmentScene.js +12 -3
  37. package/dist/environment/terrainGroundColor.d.ts +9 -0
  38. package/dist/environment/terrainGroundColor.js +22 -0
  39. package/dist/gameKit.d.ts +27 -0
  40. package/dist/gameKit.js +29 -0
  41. package/dist/map/MapMarkerBeacons.d.ts +3 -2
  42. package/dist/postfx/PostProcessing.d.ts +9 -1
  43. package/dist/postfx/PostProcessing.js +11 -4
  44. package/dist/presentationResolve.d.ts +35 -0
  45. package/dist/presentationResolve.js +51 -0
  46. package/dist/registry.d.ts +1 -0
  47. package/dist/render/assetBase.d.ts +17 -0
  48. package/dist/render/assetBase.js +35 -0
  49. package/dist/render/modelLoad.js +4 -1
  50. package/dist/render/resolveModel.d.ts +15 -0
  51. package/dist/render/resolveModel.js +37 -2
  52. package/dist/scatter/InstancedScatter.js +5 -0
  53. package/dist/scatter/scatterProxies.js +24 -0
  54. package/dist/scene/AuthoredScene.d.ts +3 -1
  55. package/dist/scene/AuthoredScene.js +7 -2
  56. package/dist/scene/CityRenderer.d.ts +2 -0
  57. package/dist/scene/CityRenderer.js +1031 -0
  58. package/dist/scene/GrassFieldRenderer.js +53 -5
  59. package/dist/scene/PoleLineRenderer.d.ts +2 -0
  60. package/dist/scene/PoleLineRenderer.js +287 -0
  61. package/dist/scene/SoilPatchRenderer.js +70 -7
  62. package/dist/scene/WaterRenderer.js +34 -10
  63. package/dist/scene/builtinSceneKindRenderers.js +4 -0
  64. package/dist/scene/cityWindowMaterial.d.ts +16 -0
  65. package/dist/scene/cityWindowMaterial.js +81 -0
  66. package/dist/scene/sceneKindRenderers.d.ts +2 -0
  67. package/dist/settings/appliedSettings.d.ts +2 -1
  68. package/dist/settings/appliedSettings.js +4 -2
  69. package/dist/structures/TransformGizmo.d.ts +11 -1
  70. package/dist/structures/TransformGizmo.js +9 -5
  71. package/dist/terrain/GrassField.d.ts +11 -3
  72. package/dist/terrain/GrassField.js +16 -6
  73. package/dist/terrain/grassGeometry.d.ts +33 -1
  74. package/dist/terrain/grassGeometry.js +83 -21
  75. package/dist/terrain/grassMaterial.d.ts +18 -0
  76. package/dist/terrain/grassMaterial.js +66 -13
  77. package/dist/terrain/heightfieldGeometry.d.ts +34 -0
  78. package/dist/terrain/heightfieldGeometry.js +109 -0
  79. package/dist/terrain/index.d.ts +1 -0
  80. package/dist/terrain/index.js +1 -0
  81. package/dist/terrain/soilPatchMaterial.d.ts +15 -2
  82. package/dist/terrain/soilPatchMaterial.js +46 -8
  83. package/dist/terrain/terrainDetailMaterial.js +15 -2
  84. package/dist/water/Ocean.d.ts +7 -1
  85. package/dist/water/Ocean.js +16 -3
  86. package/dist/water/OceanConfig.d.ts +9 -0
  87. package/dist/water/OceanConfig.js +6 -0
  88. package/dist/water/OceanMaterial.d.ts +12 -0
  89. package/dist/water/OceanMaterial.js +9 -0
  90. package/dist/water/OceanShader.d.ts +2 -2
  91. package/dist/water/OceanShader.js +78 -10
  92. package/dist/world/PlaceScene.d.ts +11 -0
  93. package/dist/world/PlaceScene.js +27 -0
  94. package/dist/world/WorldScene.js +29 -2
  95. package/dist/worldSky.d.ts +7 -1
  96. package/dist/worldSky.js +11 -1
  97. package/package.json +4 -4
  98. package/skills/game-design/SKILL.md +28 -0
  99. package/skills/game-design/agents/openai.yaml +4 -0
  100. package/skills/game-design/references/application-playbook.md +170 -0
  101. package/skills/game-design/references/field-guide.md +288 -0
  102. package/skills/jgengine/SKILL.md +7 -4
  103. package/skills/jgengine/api.md +172 -79
  104. package/skills/jgengine/capabilities.md +104 -2
  105. package/skills/jgengine/recipes/minimal-game.md +88 -0
  106. package/skills/jgengine-ui/SKILL.md +15 -10
  107. package/skills/jgengine-ui/api.md +202 -47
  108. package/skills/jgengine-ui/capabilities.md +71 -2
  109. package/skills/jgengine-ui/recipes/portable-minimap.md +150 -0
  110. package/skills/jgengine-ui/reference.md +20 -7
  111. package/skills/jgengine-ui/references/visual-scorecard.md +1 -1
  112. package/skills/jgengine-world/SKILL.md +13 -0
  113. package/skills/jgengine-world/api.md +419 -15
  114. package/skills/jgengine-world/capabilities.md +108 -2
  115. package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
  116. package/skills/jgengine-world/reference.md +43 -4
  117. package/skills/level-design/SKILL.md +54 -0
  118. package/skills/level-design/agents/openai.yaml +4 -0
  119. package/skills/level-design/references/application-playbook.md +192 -0
  120. package/skills/level-design/references/field-guide.md +337 -0
  121. package/skills/level-design/references/live-editor-loop.md +179 -0
  122. package/dist/cartridge/runtime.d.ts +0 -15
  123. package/dist/cartridge/runtime.js +0 -556
  124. package/dist/cartridge/spec.d.ts +0 -188
  125. package/dist/cartridge/spec.js +0 -19
  126. package/dist/cartridge/validate.d.ts +0 -2
  127. package/dist/cartridge/validate.js +0 -78
  128. package/dist/cartridge.d.ts +0 -145
  129. package/dist/cartridge.js +0 -246
  130. package/skills/jgengine/reference-cartridge.md +0 -72
@@ -63,16 +63,30 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
63
63
 
64
64
  - `defineAttackMoveOrder` (function) · `import { defineAttackMoveOrder } from "@jgengine/core/world"`
65
65
 
66
- ## authored-objects — place catalog mesh props from an editor document
66
+ ## authored-entities — spawn authored mob/boss markers from the editor document at runtime
67
67
 
68
+ - `authoredEntitySpawns` (function) · `import { authoredEntitySpawns } from "@jgengine/core/world/authoredEntities"`
69
+
70
+ ## authored-objects — marker kinds resolved as entity spawns, not static props
71
+
72
+ - `ENTITY_MARKER_KINDS` (const) · `import { ENTITY_MARKER_KINDS } from "@jgengine/core/world/authoredObjects"`
68
73
  - `markerCatalogId` (function) · `import { markerCatalogId } from "@jgengine/core/world"`
69
74
  - `placeAuthoredObjects` (function) · `import { placeAuthoredObjects } from "@jgengine/core/world"`
70
75
  - `resolveAuthoredObjects` (function) · `import { resolveAuthoredObjects } from "@jgengine/core/world"`
71
76
 
72
- ## authored-triggers — schema'd on/action vocabulary on volumes and markers with runtime dispatch
77
+ ## authored-spawn — read spawn points and markers from the editor document
78
+
79
+ - `authoredSpawnPosition` (function) · `import { authoredSpawnPosition } from "@jgengine/core/world/authoredSpawn"`
80
+ - `authoredSpawnRotation` (function) · `import { authoredSpawnRotation } from "@jgengine/core/world/authoredSpawn"`
81
+ - `markersOfKind` (function) · `import { markersOfKind } from "@jgengine/core/world/authoredSpawn"`
73
82
 
83
+ ## authored-triggers — built-in announce/win/advance actions authorable on any marker or volume
84
+
85
+ - `BUILTIN_TRIGGER_ACTIONS` (const) · `import { BUILTIN_TRIGGER_ACTIONS } from "@jgengine/core/scene/authoredTriggers"`
74
86
  - `collectAuthoredTriggers` (function) · `import { collectAuthoredTriggers } from "@jgengine/core/world"`
75
87
  - `createAuthoredTriggerRuntime` (function) · `import { createAuthoredTriggerRuntime } from "@jgengine/core/world"`
88
+ - `createTriggerOutcome` (function) · `import { createTriggerOutcome } from "@jgengine/core/scene/authoredTriggers"`
89
+ - `registerBuiltinTriggerActions` (function) · `import { registerBuiltinTriggerActions } from "@jgengine/core/scene/authoredTriggers"`
76
90
  - `registerTriggerAction` (function) · `import { registerTriggerAction } from "@jgengine/core/world"`
77
91
 
78
92
  ## behavior-control — pause/disable/resume/seek/inspect behavior instances per entity
@@ -87,6 +101,27 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
87
101
 
88
102
  - `createBodyBind` (function) · `import { createBodyBind } from "@jgengine/core/world"`
89
103
 
104
+ ## build-footprint-cells — grid cells a snapped build piece footprint covers (footprintGrid occupancy bridge)
105
+
106
+ - `footprintCells` (function) · `import { footprintCells } from "@jgengine/core/world/buildSockets"`
107
+
108
+ ## build-socket-catalog — data-driven named-socket snap catalog for modular base building
109
+
110
+ - `createBuildSocketCatalog` (function) · `import { createBuildSocketCatalog } from "@jgengine/core/world/buildSockets"`
111
+
112
+ ## city-district — polygon math for road-derived blocks, parcels, and buildable footprints
113
+
114
+ - `Vec2` (type) · `import { Vec2 } from "@jgengine/core/world/cityGeometry"`
115
+ - `resolveCityObject` (function) · `import { resolveCityObject } from "@jgengine/core/world"`
116
+
117
+ ## companion-intent — resolve a companion's command + threat context into a per-tick follow/hold/engage/assist intent
118
+
119
+ - `resolveCompanionIntent` (function) · `import { resolveCompanionIntent } from "@jgengine/core/scene/companion"`
120
+
121
+ ## companion-roster — manage tamed companions — allegiance, commands, per-companion stats/leveling, and mobBrain-shaped intent
122
+
123
+ - `createCompanionRoster` (function) · `import { createCompanionRoster } from "@jgengine/core/scene/companion"`
124
+
90
125
  ## contour-interval — readable auto-spacing for elevation contours
91
126
 
92
127
  - `chooseContourInterval` (function) · `import { chooseContourInterval } from "@jgengine/core/world"`
@@ -111,6 +146,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
111
146
 
112
147
  - `entityMetaOf` (function) · `import { entityMetaOf } from "@jgengine/core/world"`
113
148
 
149
+ ## flight-dynamics — simulate fixed-wing, helicopter, and VTOL aircraft
150
+
151
+ - `createAircraftDynamics` (function) · `import { createAircraftDynamics } from "@jgengine/core/world"`
152
+
114
153
  ## follow-trail — trailing follower/snake formation that chases a leader
115
154
 
116
155
  - `createLeaderTrail` (function) · `import { createLeaderTrail } from "@jgengine/core/world"`
@@ -159,6 +198,12 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
159
198
 
160
199
  - `createGrappleSwing` (function) · `import { createGrappleSwing } from "@jgengine/core/world"`
161
200
 
201
+ ## gravity-field — sample configurable uniform, radial, or combined gravity
202
+
203
+ - `combineGravity` (function) · `import { combineGravity } from "@jgengine/core/world"`
204
+ - `pointGravity` (function) · `import { pointGravity } from "@jgengine/core/world"`
205
+ - `uniformGravity` (function) · `import { uniformGravity } from "@jgengine/core/world"`
206
+
162
207
  ## hold-order — stand-ground order that holds position until preempted or canceled
163
208
 
164
209
  - `defineHoldOrder` (function) · `import { defineHoldOrder } from "@jgengine/core/world"`
@@ -191,6 +236,23 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
191
236
 
192
237
  - `generateLock` (function) · `import { generateLock } from "@jgengine/core/world"`
193
238
 
239
+ ## mesh-hitboxes — shots pass through holes in concave models — opted-in catalog assets raycast their actual triangles instead of the fitted box.
240
+
241
+ - `ColliderShape` (type) · `import { ColliderShape } from "@jgengine/core/scene/colliders"`
242
+
243
+ ## mesh-movement — the walking player slides against a mesh collider's compound `boxes` — a capsule walks THROUGH an opted-in archway opening and is stopped by its pillars/lintel instead of one outer box.
244
+
245
+ - `ColliderShape` (type) · `import { ColliderShape } from "@jgengine/core/scene/colliders"`
246
+
247
+ ## minimap-bake — rasterize authored terrain + zones into a top-down minimap image
248
+
249
+ - `bakeMinimapImage` (function) · `import { bakeMinimapImage } from "@jgengine/core/world"`
250
+ - `minimapBakeToPngDataUri` (function) · `import { minimapBakeToPngDataUri } from "@jgengine/core/world"`
251
+
252
+ ## minimap-track — project a world point to a 0..1 fraction along a linear track axis
253
+
254
+ - `trackFraction` (function) · `import { trackFraction } from "@jgengine/core/world/minimapTrack"`
255
+
194
256
  ## model-sockets — named attachment points read from a model
195
257
 
196
258
  - `ModelNode` (interface) · `import { ModelNode } from "@jgengine/core/world"`
@@ -219,6 +281,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
219
281
 
220
282
  - `pathLength` (function) · `import { pathLength } from "@jgengine/core/world"`
221
283
 
284
+ ## path-network — build a deterministic road/track graph (nodes, edges, streets, junctions, bridges, tunnels) from sliders
285
+
286
+ - `buildPathNetwork` (function) · `import { buildPathNetwork } from "@jgengine/core/world/pathNetwork"`
287
+
222
288
  ## path-terrain — apply flatten/grade/carve/retaining path profiles to a terrain field
223
289
 
224
290
  - `applyPathProfiles` (function) · `import { applyPathProfiles } from "@jgengine/core/world"`
@@ -228,6 +294,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
228
294
 
229
295
  - `definePatrolOrder` (function) · `import { definePatrolOrder } from "@jgengine/core/world"`
230
296
 
297
+ ## piece-sockets-from-model — derive build-piece snap sockets from a model's named attach points
298
+
299
+ - `pieceSocketsFromModel` (function) · `import { pieceSocketsFromModel } from "@jgengine/core/world/buildSockets"`
300
+
231
301
  ## place-asset — resolve a placement commit into a shared asset placement payload
232
302
 
233
303
  - `PlaceAssetResult` (interface) · `import { PlaceAssetResult } from "@jgengine/core/world"`
@@ -248,10 +318,26 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
248
318
  - `parseRotationPolicy` (function) · `import { parseRotationPolicy } from "@jgengine/core/world"`
249
319
  - `snapHeading` (function) · `import { snapHeading } from "@jgengine/core/world"`
250
320
 
321
+ ## pole-line — editor-authorable poles + sagging cables along a path
322
+
323
+ - `POLE_LINE_KIND` (const) · `import { POLE_LINE_KIND } from "@jgengine/core/world/poleLineKind"`
324
+
325
+ ## population-director — ambient per-region creature population manager with timed weighted respawns and caps
326
+
327
+ - `createPopulationDirector` (function) · `import { createPopulationDirector } from "@jgengine/core/ai/populationDirector"`
328
+
329
+ ## portable-marker-source — project caller-owned arrays or subscribable stores into cached minimap marker snapshots without a MarkerSet
330
+
331
+ - `createMarkerSource` (function) · `import { createMarkerSource } from "@jgengine/core/world"`
332
+
251
333
  ## proximity-prompt — a "press E" contextual prompt shown near an interactable
252
334
 
253
335
  - `resolveActivePrompt` (function) · `import { resolveActivePrompt } from "@jgengine/core/world"`
254
336
 
337
+ ## pursuit — chase-to-reach cooldown-gated attack with optional leash-to-home; owns the attack cooldown
338
+
339
+ - `advancePursuit` (function) · `import { advancePursuit } from "@jgengine/core/ai/pursuit"`
340
+
255
341
  ## qte — a quick-time-event timed input sequence with hit windows
256
342
 
257
343
  - `evaluateQteSequence` (function) · `import { evaluateQteSequence } from "@jgengine/core/world"`
@@ -260,6 +346,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
260
346
 
261
347
  - `tierForStanding` (function) · `import { tierForStanding } from "@jgengine/core/world"`
262
348
 
349
+ ## resource-node — depletable, respawning harvest nodes with tool-dependent yields
350
+
351
+ - `createResourceNodeField` (function) · `import { createResourceNodeField } from "@jgengine/core/world/resourceNode"`
352
+
263
353
  ## rts-control-groups — map Ctrl+digit / digit / double-tap presses onto selection-bookmark bind / recall / focus intents
264
354
 
265
355
  - `controlGroupKey` (function) · `import { controlGroupKey } from "@jgengine/core/world"`
@@ -357,6 +447,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
357
447
 
358
448
  - `terrainContourGuides` (function) · `import { terrainContourGuides } from "@jgengine/core/world"`
359
449
 
450
+ ## terrain-raycast — pick a point on a heightfield along a camera/pointer ray
451
+
452
+ - `raycastHeightField` (function) · `import { raycastHeightField } from "@jgengine/core/world/terrain"`
453
+
360
454
  ## touch-controls — default on-screen button silhouette for a touch action
361
455
 
362
456
  - `touchButtonShape` (function) · `import { touchButtonShape } from "@jgengine/core/input/touchScheme"`
@@ -368,3 +462,15 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
368
462
  ## volumetric-clouds — raymarched cloud layer sky option
369
463
 
370
464
  - `VolumetricCloudsConfig` (interface) · `import { VolumetricCloudsConfig } from "@jgengine/core/world"`
465
+
466
+ ## world-environment — compose editor/preset-derived outdoor dressing — terrain, weather, water, structures — into a legacy environment feature
467
+
468
+ - `environment` (function) · `import { environment } from "@jgengine/core/world"`
469
+
470
+ ## world-place — declare the place a game happens in — flat/round/voxel/board ground, surface laws, per-place physics
471
+
472
+ - `world` (function) · `import { world } from "@jgengine/core/world"`
473
+
474
+ ## world-seed — derive a deterministic generator/scatter seed from world id + save/run seed
475
+
476
+ - `seedForPlace` (function) · `import { seedForPlace } from "@jgengine/core/world"`
@@ -0,0 +1,128 @@
1
+ # Recipe — commanding entities (select → order → tick → resolve)
2
+
3
+ **What this wires:** direct command over a group of entities — pick some, issue
4
+ intent, watch them carry it out and interact on arrival. This is a *composition*,
5
+ not a genre. The exact same wiring is the command layer of a base-defense wave, a
6
+ squad-tactics turn, a creature-herding puzzle, or the topdown-survival phase of a
7
+ game that was a card game a minute ago. You swap content, camera, and
8
+ win-condition — never this architecture.
9
+
10
+ It composes four primitives across three seams. The engine never touches your
11
+ entity store directly; you supply narrow adapters, so the same command loop runs
12
+ over any movement/combat model.
13
+
14
+ ## The seams
15
+
16
+ - **Pick → ids.** `createSelectionSet` is an ordered, de-duplicated set of entity
17
+ ids. What *fills* it is up to you — a pointer marquee, a card that names a lane,
18
+ an ability that grabs everything in a radius, an AI planner. Selection is just
19
+ ids; it does not know or care how they were chosen.
20
+ - **Intent → per-entity queue.** Build one shared `createOrderRegistry` and give
21
+ each commandable entity its own `createOrderQueue`. Register verbs as *data*
22
+ (`defineMoveOrder`, `defineAttackMoveOrder`, `defineTargetedOrder`, or your
23
+ own). Every verb composes over two adapters **you** implement against your
24
+ entity store: `OrderMover` (`position` / `moveToward` / `halt`) and
25
+ `OrderTargeting` (`acquire` / `positionOf`). This adapter boundary is what makes
26
+ the loop genre-free.
27
+ - **Resolve → your effect.** In `onTick`, advance each queue. Engagement verbs
28
+ write `{ engaging, inRange }` into `order.state`; you read that flag and resolve
29
+ the actual interaction your way — damage, harvest, repair, heal, dialogue —
30
+ through the combat seam `ctx.scene.entity.effect(...)`. Orders decide *when*;
31
+ the game decides *what*.
32
+
33
+ ## Connected wiring
34
+
35
+ ```ts
36
+ import { createSelectionSet } from "@jgengine/core/scene/selection";
37
+ import {
38
+ createOrderRegistry,
39
+ createOrderQueue,
40
+ defineMoveOrder,
41
+ defineAttackMoveOrder,
42
+ defineTargetedOrder,
43
+ } from "@jgengine/core/world";
44
+
45
+ // --- once, at setup -------------------------------------------------------
46
+
47
+ // Adapters bind orders to YOUR entity store — the only game-specific glue.
48
+ const mover = {
49
+ position: (id) => entities.get(id).pos,
50
+ moveToward: (id, to, dt) => stepToward(entities.get(id), to, dt), // your movement
51
+ halt: (id) => (entities.get(id).vel = [0, 0, 0]),
52
+ };
53
+ const targeting = {
54
+ positionOf: (id) => entities.get(id)?.pos,
55
+ acquire: (id) => nearestEnemyOf(id), // your target pick (kd-tree, grid, faction…)
56
+ };
57
+
58
+ const registry = createOrderRegistry();
59
+ registry.register(defineMoveOrder({ kind: "move", mover }));
60
+ registry.register(defineAttackMoveOrder({ kind: "attack-move", mover, targeting }));
61
+ registry.register(defineTargetedOrder({ kind: "attack", mover, targeting }));
62
+
63
+ const queues = new Map(); // entityId -> OrderQueue
64
+ const queueOf = (id) => {
65
+ let q = queues.get(id);
66
+ if (!q) queues.set(id, (q = createOrderQueue(registry)));
67
+ return q;
68
+ };
69
+
70
+ const selection = createSelectionSet();
71
+
72
+ // --- on a command (pointer marquee, a card play, an ability, an AI plan) --
73
+
74
+ function commandSelection(kind, payload) {
75
+ for (const id of selection) {
76
+ queueOf(id).issue({ kind, payload, policy: "replace" }); // shift-queue => "append"
77
+ }
78
+ }
79
+ // e.g. commandSelection("attack-move", { point: worldHit });
80
+
81
+ // --- every frame ----------------------------------------------------------
82
+
83
+ function onTick(ctx, dt) {
84
+ for (const [id, q] of queues) {
85
+ q.tick(ctx, dt); // activates/advances/completes orders, drives the mover
86
+ const active = q.active;
87
+ if (active?.state?.inRange) {
88
+ // Engagement verb says a target is in range — resolve it the game's way.
89
+ ctx.scene.entity.effect({ from: id, to: active.state.target, effect: "damage" });
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ That is the whole command loop. Everything genre-specific lives in the three
96
+ functions you supplied (`stepToward`, `nearestEnemyOf`, and the `"damage"` effect)
97
+ — never in the order/selection primitives.
98
+
99
+ ## Composing it into different games (and hybrids)
100
+
101
+ The wiring above is fixed; four independent knobs turn it into different play:
102
+
103
+ - **Who fills the selection.** Pointer marquee for an RTS; a targeting card for a
104
+ card-driven skirmish; an AoE ability for a spell; an AI director for autobattle.
105
+ Same `selection`, different caller.
106
+ - **Which camera renders it.** `topDown`, `rts`, `shoulder`, or `first` — camera is
107
+ presentation and reads none of this. Swapping the rig never touches the loop.
108
+ - **What the effect does.** `"damage"` for combat; register a `defineTargetedOrder`
109
+ with a `"harvest"` / `"repair"` / `"talk"` effect for non-combat command. The
110
+ engagement seam is generic; combat is one instance.
111
+ - **When intent arrives.** A **mutating** game reuses one set of queues across
112
+ phases: the card-game phase calls `issue()` from card plays; when it turns into
113
+ a topdown-survival phase, the *same* queues now take `move` / `attack-move` from
114
+ pointer input. You changed who calls `issue()`, not the architecture.
115
+
116
+ ## Where to go next
117
+
118
+ - Marquee/box-select and right-click-to-command come from the shell pointer seam
119
+ (`PlayableGame.pointer`: `select`, `selectFilter`, `orderCommand`) — that is the
120
+ pointer *picker* that fills `selection` and calls `commandSelection`; see
121
+ `jgengine-ui` for the pointer wiring.
122
+ - Pathing for `moveToward` can use `findPath` (`@jgengine/core/nav/navGrid`) over a
123
+ walkable grid; the mover adapter is where it plugs in.
124
+ - Formations, order preemption policies, save/replication of in-flight orders, and
125
+ the full order-kind contract are in [../reference.md](../reference.md) under
126
+ *Entity orders (command queue)*.
127
+ - Damage numbers, death, and rewards are `jgengine-combat`; the effect call above
128
+ is the seam into it.
@@ -2,9 +2,35 @@
2
2
 
3
3
  Reference module for the [`jgengine-world` API](SKILL.md) skill. Load this when you need the renderer-free world surface.
4
4
 
5
- ## World features
5
+ ## World = place (the definition of a world)
6
6
 
7
- Descriptors from `@jgengine/core/world/features` — config data the runner/world layer interprets:
7
+ A world is the place a game happens in — substrate + laws — declared with `world()` from
8
+ `@jgengine/core/world/place`. It is never a coded diorama: sky look, foliage scatter, props, and
9
+ sculpt are scene content authored in the editor (`jgengine-editor`), and the engine renders its
10
+ default sky when the document has none.
11
+
12
+ ```ts
13
+ import { world } from "@jgengine/core/world/place";
14
+
15
+ export const overworld = world({
16
+ id: "overworld", // place identity: saves, seeds, switching
17
+ ground: { mode: "flat", size: { x: Infinity, z: Infinity } },
18
+ physics: { gravity: -24 }, // laws of THIS place
19
+ });
20
+ ```
21
+
22
+ - `ground.mode` discriminates `size`: `flat` takes `{ x, z }` (or `{ x, y, z }`; `Infinity` = unbounded axis), `round` takes `{ radius }` only, `voxel` takes the generator domain `{ x, y, z }` (axes may be `Infinity`), `board` takes `{ x, y }` in cells/layout units (2D surface you look at — grid games, solitaire, tabletop; `stage` is a `board` alias). TS rejects a `radius` on `flat` and `x`/`z` on `round`.
23
+ - `ground.surface` is optional matter/feel law (`{ matter?, friction?, restitution?, traits? }`) systems and audio read — metal vs slime vs felt changes the feel of the *same* rule systems, never forks them. Not a theme kit.
24
+ - `ground.generator` (procedural modes) is algorithm params only — never a seed, never a genre/franchise preset.
25
+ - **No `seed` in a world definition, ever.** The engine derives seeds from world `id` + save/run via `seedForPlace(id, runSeed)` and injects them into generators and scatter.
26
+ - Multiple worlds per game are first-class: one `world()` per place, each with its own ground + physics (`defineGameDefinition` resolves the active place's physics over the game default). Pure UI/rules games omit `world` entirely.
27
+ - Thin defaults: `flat` + infinite axes + default physics for 3D, `board` for 2D, or no world at all. Never scaffold a "meadow with sky preset and seeded grass" — that is editor content.
28
+
29
+ ## Legacy world features
30
+
31
+ Descriptors from `@jgengine/core/world/features` — legacy code-declared world shapes kept for
32
+ existing games. Do not start new worlds here; `environment()` is now the consumption target for
33
+ editor/preset-written scene data (`environmentContentFromDocument`), not a world definition:
8
34
 
9
35
  | Feature | Use |
10
36
  |---------|-----|
@@ -25,6 +51,11 @@ Descriptors from `@jgengine/core/world/features` — config data the runner/worl
25
51
 
26
52
  `parentSpace` positions are local to that space — convert at seams only.
27
53
 
54
+ Display-only map consumers use the portable `MarkerView` / `MarkerSource`
55
+ contracts in `@jgengine/core/world/markers`; the React integration and complete
56
+ external-store walkthrough are owned by the
57
+ [portable minimap recipe](../jgengine-ui/recipes/portable-minimap.md).
58
+
28
59
  ### Query primitives (renderer-free, for gameplay)
29
60
 
30
61
  Pure `@jgengine/core` functions so gameplay reads the same world the shell renders — no three.js needed:
@@ -84,7 +115,7 @@ Turn data-only placement into the build tooling of Valheim/Enshrouded/The Sims/F
84
115
 
85
116
  **Body shape: box or sphere.** `AddBodyOptions` is a discriminated union — `{ shape?: "box", halfExtents }` (box is the default, `shape` omittable) or `{ shape: "sphere", radius }` (the radius fills all three half-extent columns, so broadphase/bounds see the sphere's enclosing AABB). `world.shape[i]` (`SHAPE_BOX` / `SHAPE_SPHERE`) reports a live body's shape for a consumer walking the raw SoA arrays. Sphere-sphere and sphere-box pairs resolve with a proper radial normal (not the axis-aligned box/box path) — balls, projectile bodies, and rolling debris collide correctly against both boxes and each other.
86
117
 
87
- **Collision shapes are box/sphere/voxel only.** Every collider in the engine — `PhysicsWorld` bodies (box `halfExtents` or sphere `radius`), object/entity picking (`scene/objectQuery` raycasts against unit boxes), and `world/voxelField`/`world/carve` blocks — is an axis-aligned box, a sphere, or a voxel cell. Arbitrary authored level-mesh collision (a sculpted GLB as a collider) is not supported. The seams for custom collision are `movement.beforeCommit` (steer or replace the walk controller's resolved step) and object raycasts (query the scene yourself and react) — not a mesh collider.
118
+ **Collision shapes are box/sphere/voxel — plus opt-in triangle meshes for raycasts and mesh-accurate walk obstruction.** Every collider in the engine — `PhysicsWorld` bodies (box `halfExtents` or sphere `radius`), object/entity picking (`scene/objectQuery` raycasts against unit boxes), and `world/voxelField`/`world/carve` blocks — is an axis-aligned box, a sphere, or a voxel cell. The one exception is opted-in catalog assets: an asset listed in `@jgengine/assets` `COLLISION_MESH_ASSET_IDS` ships a compact quantized triangle mesh in the generated index (`scene/collisionMesh` `CollisionMeshData`, extracted from the GLB at reindex — never from the rendered scene, so a headless host resolves the same shape) **plus a deterministic compound-box decomposition** (`voxelizeToBoxes` → `CollisionMeshData.boxes`, a voxel-rasterize + column-fill + greedy-merge of the model's solid volume). Collider auto-fit then (a) raycasts the actual triangles through a precomputed BVH — shots pass through a torus hole or an archway instead of hitting the fitted box; and (b) exposes the compound boxes on the fitted `mesh` `ColliderShape` (`ColliderShape.mesh.boxes`, scaled/translated onto the placement like the mesh). The `collideObjects` walk controller consumes these in `stepPlayerMovement` (host and client alike, so parity is automatic): it gathers obstacles from resolved blocking physical colliders via a bounded `object.inBox` broadphase (reach ≥ radius + max half-extent, so a wide wall's full span blocks, not a phantom 1 m box), carries each collider's real `halfExtents`/`offset` — or, for a mesh collider, the compound sub-boxes — yaw-rotated onto the obstacle, and slides the capsule against **each** sub-box (`resolveObstacleStep`), so a capsule walks THROUGH an archway opening and is stopped by its pillars/lintel/sill. `PhysicsWorld` and overlap tests still see the conservative fitted AABB; objects with no resolved collider keep today's default box, objects whose colliders don't block stop obstructing, and non-opted assets keep the fitted box everywhere. The seams for custom *walk* collision remain `movement.beforeCommit` and object raycasts.
88
119
 
89
120
  **Ballistic collision sweep (`physics/ballisticSweep`).** `createBallisticSweep(world, { step?, radius? })` → `BallisticSweep`, a `(origin, velocity, gravity, maxTime) => BallisticSweepHit | null` function that marches the closed-form arc (constant gravity, straight lateral) through a `PhysicsWorld` and reports the first sample inside any live body's AABB (sleeping bodies included), refined by one bisection step; `null` means the whole arc is clear. `step` (default 1/60) is the march interval in seconds, `radius` (default 0) inflates every body's AABB before the point test — pass the projectile's own radius. Wire it into `combat/projectiles` via `ProjectileSystemDeps.sweepBallistic`: when set, a ballistic shot settles at the swept impact point instead of the closed-form landing; omitted or `null` falls back to that closed-form arc.
90
121
 
@@ -160,6 +191,15 @@ runtime.step({
160
191
  // types: TriggerEvent · TriggerActionDefinition · AuthoredTrigger · TriggerHandlers · TriggerSourceKind · AuthoredTriggerRuntime
161
192
  ```
162
193
 
194
+ **Built-in actions** — every game gets `announce` / `win` / `advance` for free, so "enter this zone → win" is authored entirely in the editor with no bespoke action code. `registerBuiltinTriggerActions()` exposes them in the inspector; `createTriggerOutcome()` returns a headless, serializable `TriggerOutcome` (`{ won, message, tone, objective, revision }`) plus the `handlers` to pass the runtime — the HUD reads it via `useSyncExternalStore`. That pair is the whole zero-code "reach the goal to win" wiring the create scaffold ships (a `goal` marker with `{ on: enter, action: win }`).
195
+
196
+ ```ts
197
+ registerBuiltinTriggerActions(); // announce/win/advance in the editor
198
+ const outcome = createTriggerOutcome(); // { get, subscribe, handlers, reset }
199
+ const runtime = createAuthoredTriggerRuntime({ document: editorLayers, handlers: outcome.handlers });
200
+ // HUD: const status = useSyncExternalStore(outcome.subscribe, outcome.get, outcome.get); // status.won / status.message
201
+ ```
202
+
163
203
  Multi-triggers: `meta.triggers: [{ on, action, ...params }]`. Editor inspector renders action params from the declared schema when the game has registered actions.
164
204
 
165
205
  ### Continuous area effects (source-following membership)
@@ -219,4 +259,3 @@ Path progress adapter (`nav/pathFollow`): `PathProgress` is `{ kind: "normalized
219
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.
220
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.
221
261
 
222
-
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: level-design
3
+ description: Design, audit, author, implement, and validate readable levels, worlds, encounters, navigation, and pacing.
4
+ ---
5
+
6
+ # Level design
7
+
8
+ ## Ownership
9
+
10
+ Own the spatial realization of game design: player metrics, topology, routes, landmarks, gates, encounter geometry, challenge sequencing, exploration, pacing, recovery, and spatial storytelling. Use `game-design` for the player promise and loops, `jgengine-editor` to author space, and `jgengine-world` to consume it.
11
+
12
+ All authorable world content belongs in `editor.scene.json`, changed through the editor GUI or RPC/CLI. Never hand-edit the document or keep geometry, paths, spawns, zones, objectives, terrain, foliage, encounters, or coordinate arrays in code. File an editor `[FEATURE]` issue before any fallback.
13
+
14
+ Read [references/application-playbook.md](references/application-playbook.md) only when the task explicitly calls for spatial design depth — a level/world design brief, an audit or redesign, or "make this fun/better" work; it is not a mandatory step of creating a game. Read [references/live-editor-loop.md](references/live-editor-loop.md) whenever live captures, an editor-agent handoff, or iterative scene improvement is involved. Read [references/field-guide.md](references/field-guide.md) for metric families, topology, pacing, genre lenses, accessibility, and research.
15
+
16
+ ## Choose the mode
17
+
18
+ ### Greenfield
19
+
20
+ Measure the real controller, camera, interaction, combat, AI, and network constraints in an authored metric gym. Produce a topology graph, critical/optional route model, beat sheet, learning sequence, encounter cards, authoring plan, and falsifiable completion scenario before art production.
21
+
22
+ ### Existing game
23
+
24
+ Start with a spatial-truth audit. Inventory every source of coordinates and semantics; report any gameplay placement outside `editor.scene.json` as a blocking ownership defect. Reconstruct the critical path and representative optional path from scene data and live play. Measure travel, decision, encounter, retry, and recovery time. Inspect first-look orientation, reverse navigation, affordances, spawn safety, combat positions, pacing contrast, and visual hierarchy.
25
+
26
+ “Improve with level design” is an authoring and implementation request. Do not stop at a map critique: select the highest-leverage route/zone/encounter slice, author the change through editor operations, connect runtime semantics, and prove it in play.
27
+
28
+ For a live editor handoff, send evidence and an executable authoring contract—not an aesthetic request. Require the editor agent to inspect authority, document revision, ids, layers, assets, runtime state, and tool support before mutation; apply one coherent hypothesis through undoable operations; then return the exact revision/change log for replay and comparison. Stop when the live editor is not connected to the runtime's authoritative scene or the required operation is unsupported.
29
+
30
+ ## Required output contract
31
+
32
+ Make every level-design pass yield:
33
+
34
+ 1. **Spatial truth audit:** scene counts and semantic kinds plus every code-owned coordinate table, generated placement, or duplicated position that must migrate.
35
+ 2. **Metric sheet:** measured movement, camera, interaction, weapon/AI, readability, density, performance, multiplayer, and retry constraints with units.
36
+ 3. **Topology and journey:** nodes/edges, critical and optional routes, gates, returns, landmarks, expected traversal times, and first-minute/mission/campaign beats.
37
+ 4. **Zone/encounter diagnosis:** purpose, entry knowledge/state, choices, geometry, actors, escalation, resources, exits, failure/reset, reward, and downstream change.
38
+ 5. **Bottleneck ranking:** observed symptom, player consequence, root cause, evidence, reach, confidence, effort, dependencies, and ownership.
39
+ 6. **Editor change set:** exact semantic ids/kinds and operations to add/move/remove for paths, markers, zones, spawns, objectives, checkpoints, props, and terrain—without putting coordinates in code.
40
+ 7. **Acceptance evidence:** document assertions, route/encounter tests, fresh-player play scenario, pacing/wayfinding thresholds, inspected screenshots, and before/after comparison.
41
+
42
+ ## Execution rules
43
+
44
+ - Graph before geometry; graybox before art; gameplay and wayfinding before decoration.
45
+ - Present decision-relevant information before commitment. Make alternate routes differ in risk, cost, verbs, information, reward, or experience.
46
+ - Sequence teaching through cue, safe use, feedback, independent test, combination, transformation, and mastery as the audience requires.
47
+ - Align composition, landmark, value/color, motion, audio, affordance, path shape, and optional UI guidance. No marker can repair contradictory space.
48
+ - Design pacing across separate threat, motor, cognitive, navigation, emotional, resource, and social axes. Constant pressure or empty traversal is not pacing.
49
+ - Make every encounter support intended verbs, counterplay, retreat, fair spawns, recovery, and bounded performance.
50
+ - Populate only after the graybox survives play. Preserve silhouettes, safe footing, goals, threats, interactables, teams, and traversal boundaries through art passes.
51
+
52
+ ## Completion
53
+
54
+ An audit or prettier screenshot is not completion for a build/improve request. Finish when the scene document owns spatial truth; a fresh target player can orient, choose, act, recover, and complete the scenario without coaching; the route has intentional decisions and pacing; encounter exploits and alternate states are tested; and `jgengine-verify` records document, gameplay, and inspected visual evidence.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Level Design"
3
+ short_description: "Design readable, paced, playable spaces"
4
+ default_prompt: "Use $level-design to observe this world live, brief and drive the editor agent through an authoritative scene change, then replay the same journey and prove the result."