@jgengine/core 0.11.0 → 0.13.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 +52 -0
- package/dist/ai/populationDirector.d.ts +103 -0
- package/dist/ai/populationDirector.js +190 -0
- package/dist/ai/pursuit.d.ts +56 -0
- package/dist/ai/pursuit.js +53 -0
- package/dist/authoring.d.ts +1 -1
- package/dist/authoring.js +1 -1
- package/dist/combat/effects.d.ts +11 -4
- package/dist/combat/effects.js +24 -17
- package/dist/combat/magazine.d.ts +13 -0
- package/dist/combat/magazine.js +27 -0
- package/dist/combat/vfxPresets.d.ts +176 -0
- package/dist/combat/vfxPresets.js +74 -0
- package/dist/combat/weaponFire.d.ts +114 -0
- package/dist/combat/weaponFire.js +96 -0
- package/dist/combat.d.ts +5 -2
- package/dist/combat.js +4 -1
- package/dist/devtools/controls.d.ts +38 -0
- package/dist/devtools/controls.js +181 -0
- package/dist/devtools/devtools.d.ts +5 -197
- package/dist/devtools/devtools.js +38 -774
- package/dist/devtools/discover.d.ts +32 -0
- package/dist/devtools/discover.js +240 -0
- package/dist/devtools/fallbackSeams.d.ts +50 -0
- package/dist/devtools/fallbackSeams.js +111 -0
- package/dist/devtools/frame.d.ts +15 -0
- package/dist/devtools/frame.js +247 -0
- package/dist/devtools/logs.d.ts +15 -0
- package/dist/devtools/logs.js +86 -0
- package/dist/devtools/overrides.d.ts +16 -0
- package/dist/devtools/overrides.js +66 -0
- package/dist/devtools/types.d.ts +212 -0
- package/dist/devtools/types.js +1 -0
- package/dist/devtools/urlFlags.d.ts +22 -0
- package/dist/devtools/urlFlags.js +62 -0
- package/dist/economy/auctionBook.d.ts +122 -0
- package/dist/economy/auctionBook.js +213 -0
- package/dist/economy/priceHistory.d.ts +44 -0
- package/dist/economy/priceHistory.js +89 -0
- package/dist/economy/unlockPoints.d.ts +92 -0
- package/dist/economy/unlockPoints.js +103 -0
- package/dist/editor/commandApply.d.ts +5 -0
- package/dist/editor/commandApply.js +755 -0
- package/dist/editor/commands.d.ts +48 -3
- package/dist/editor/commands.js +40 -754
- package/dist/editor/directives.d.ts +94 -0
- package/dist/editor/directives.js +206 -0
- package/dist/editor/document.d.ts +37 -2
- package/dist/editor/document.js +422 -6
- package/dist/editor/entityCatalog.d.ts +42 -0
- package/dist/editor/entityCatalog.js +101 -0
- package/dist/editor/environment.d.ts +74 -0
- package/dist/editor/environment.js +169 -0
- package/dist/editor/index.d.ts +26 -1
- package/dist/editor/index.js +8 -1
- package/dist/editor/minimap.d.ts +31 -0
- package/dist/editor/minimap.js +73 -0
- package/dist/editor/streamer.d.ts +66 -0
- package/dist/editor/streamer.js +68 -0
- package/dist/editor/types.d.ts +143 -0
- package/dist/editor/world.d.ts +136 -0
- package/dist/editor/world.js +303 -0
- package/dist/game/breeding.d.ts +161 -0
- package/dist/game/breeding.js +181 -0
- package/dist/game/cameraConfig.d.ts +451 -0
- package/dist/game/cameraConfig.js +1 -0
- package/dist/game/clipRoles.d.ts +51 -0
- package/dist/game/clipRoles.js +142 -0
- package/dist/game/defineGame.d.ts +16 -9
- package/dist/game/defineGame.js +14 -6
- package/dist/game/defineSystem.d.ts +2 -0
- package/dist/game/defineSystem.js +2 -0
- package/dist/game/events.d.ts +28 -0
- package/dist/game/partAnimation.d.ts +78 -0
- package/dist/game/partAnimation.js +147 -0
- package/dist/game/playableGame.d.ts +72 -440
- package/dist/game/playableGame.js +1 -1
- package/dist/game/progression.d.ts +35 -0
- package/dist/game/progression.js +22 -1
- package/dist/game/quest.js +1 -3
- package/dist/game/tribe.d.ts +184 -0
- package/dist/game/tribe.js +432 -0
- package/dist/gameplay.d.ts +3 -1
- package/dist/gameplay.js +3 -1
- package/dist/inventory/encumbrance.d.ts +69 -0
- package/dist/inventory/encumbrance.js +68 -0
- package/dist/inventory/inventoryModel.d.ts +24 -8
- package/dist/inventory/inventoryModel.js +47 -8
- package/dist/item/durability.js +1 -3
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +48 -1
- package/dist/movement/movementModel.d.ts +15 -1
- package/dist/movement/movementModel.js +33 -15
- package/dist/movement/playerMovement.js +102 -2
- package/dist/multiplayer/combatSnapshot.js +13 -8
- package/dist/multiplayer/presenceModel.d.ts +2 -0
- package/dist/multiplayer/presenceModel.js +8 -0
- package/dist/physics/drivableVehicle.d.ts +2 -0
- package/dist/physics/drivableVehicle.js +1 -1
- package/dist/physics/flightDynamics.d.ts +82 -0
- package/dist/physics/flightDynamics.js +165 -0
- package/dist/physics/gravityField.d.ts +25 -0
- package/dist/physics/gravityField.js +41 -0
- package/dist/physics/kinematicVehicle.d.ts +87 -0
- package/dist/physics/kinematicVehicle.js +193 -11
- package/dist/physics/physicsWorld.d.ts +26 -0
- package/dist/physics/physicsWorld.js +109 -117
- package/dist/physics/vehicleObstacles.d.ts +44 -0
- package/dist/physics/vehicleObstacles.js +115 -0
- package/dist/random/rng.d.ts +4 -1
- package/dist/random/rng.js +4 -1
- package/dist/runtime/adapter.d.ts +13 -0
- package/dist/runtime/adapter.js +13 -0
- package/dist/runtime/context/combat.d.ts +51 -0
- package/dist/runtime/context/combat.js +131 -0
- package/dist/runtime/context/combatFx.js +15 -5
- package/dist/runtime/context/deathLoot.d.ts +30 -0
- package/dist/runtime/context/deathLoot.js +39 -0
- package/dist/runtime/context/player.d.ts +48 -0
- package/dist/runtime/context/player.js +125 -0
- package/dist/runtime/context/scene.d.ts +60 -0
- package/dist/runtime/context/scene.js +329 -0
- package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
- package/dist/runtime/context/snapshotCodecs.js +26 -0
- package/dist/runtime/descriptors/baseline.js +26 -3
- package/dist/runtime/descriptors/features.d.ts +5 -0
- package/dist/runtime/descriptors/features.js +20 -2
- package/dist/runtime/gameContext.d.ts +2 -428
- package/dist/runtime/gameContext.js +133 -500
- package/dist/runtime/gameContextTypes.d.ts +500 -0
- package/dist/runtime/gameContextTypes.js +1 -0
- package/dist/runtime/headlessRunner.d.ts +17 -2
- package/dist/runtime/headlessRunner.js +15 -0
- package/dist/runtime/hostPolicy.d.ts +51 -0
- package/dist/runtime/hostPolicy.js +68 -0
- package/dist/runtime/hostedGameRunner.d.ts +3 -1
- package/dist/runtime/hostedGameRunner.js +1 -0
- package/dist/runtime/hostedWorldSession.d.ts +3 -1
- package/dist/runtime/hostedWorldSession.js +1 -0
- package/dist/runtime/worldSnapshot.d.ts +13 -1
- package/dist/runtime/worldSnapshot.js +15 -3
- package/dist/scene/assetCatalog.d.ts +7 -0
- package/dist/scene/assetSpace.js +1 -3
- package/dist/scene/authoredTriggers.d.ts +50 -0
- package/dist/scene/authoredTriggers.js +114 -0
- package/dist/scene/builtinSceneKinds.js +8 -3
- package/dist/scene/colliders.d.ts +79 -0
- package/dist/scene/colliders.js +170 -0
- package/dist/scene/collisionMesh.d.ts +94 -0
- package/dist/scene/collisionMesh.js +699 -0
- package/dist/scene/companion.d.ts +147 -0
- package/dist/scene/companion.js +193 -0
- package/dist/scene/entityStats.d.ts +7 -9
- package/dist/scene/entityStats.js +19 -15
- package/dist/scene/sceneKinds.d.ts +16 -0
- package/dist/scene/sceneKinds.js +4 -0
- package/dist/scene/sceneRaycast.js +15 -0
- package/dist/stats/spawnLevelStats.d.ts +110 -0
- package/dist/stats/spawnLevelStats.js +140 -0
- package/dist/stats/statModifiers.d.ts +16 -0
- package/dist/stats/statModifiers.js +30 -1
- package/dist/stats/statPool.d.ts +70 -0
- package/dist/stats/statPool.js +62 -0
- package/dist/store/changeSignal.d.ts +3 -0
- package/dist/store/changeSignal.js +2 -0
- package/dist/store/observableKeyedStore.d.ts +2 -0
- package/dist/store/observableKeyedStore.js +1 -0
- package/dist/survival/decayMeter.js +1 -3
- package/dist/survival/regionHealth.js +1 -3
- package/dist/ui/hudLayout.d.ts +5 -0
- package/dist/ui/hudLayout.js +17 -1
- package/dist/ui/panelModel.d.ts +120 -0
- package/dist/ui/panelModel.js +168 -0
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +1 -0
- package/dist/world/authoredEntities.d.ts +29 -0
- package/dist/world/authoredEntities.js +29 -0
- package/dist/world/authoredObjects.d.ts +15 -1
- package/dist/world/authoredObjects.js +13 -1
- package/dist/world/authoredSpawn.d.ts +39 -0
- package/dist/world/authoredSpawn.js +37 -0
- package/dist/world/buildSockets.d.ts +152 -0
- package/dist/world/buildSockets.js +188 -0
- package/dist/world/buildingLots.d.ts +84 -0
- package/dist/world/buildingLots.js +119 -0
- package/dist/world/buildings.d.ts +6 -0
- package/dist/world/cityBlocks.d.ts +152 -0
- package/dist/world/cityBlocks.js +670 -0
- package/dist/world/cityContent.d.ts +77 -0
- package/dist/world/cityContent.js +261 -0
- package/dist/world/cityGenerator.d.ts +41 -0
- package/dist/world/cityGenerator.js +58 -0
- package/dist/world/cityGeometry.d.ts +114 -0
- package/dist/world/cityGeometry.js +573 -0
- package/dist/world/cityKind.d.ts +314 -0
- package/dist/world/cityKind.js +1578 -0
- package/dist/world/environmentSummary.js +57 -1
- package/dist/world/features.d.ts +53 -5
- package/dist/world/features.js +15 -3
- package/dist/world/grassKind.js +2 -2
- package/dist/world/markers.d.ts +50 -4
- package/dist/world/markers.js +49 -0
- package/dist/world/massing.js +1 -1
- package/dist/world/minimapBake.d.ts +78 -0
- package/dist/world/minimapBake.js +208 -0
- package/dist/world/minimapTrack.d.ts +17 -0
- package/dist/world/minimapTrack.js +22 -0
- package/dist/world/place.d.ts +166 -0
- package/dist/world/place.js +96 -0
- package/dist/world/placeAsset.js +3 -1
- package/dist/world/poleLineKind.d.ts +40 -0
- package/dist/world/poleLineKind.js +97 -0
- package/dist/world/polyline.js +9 -2
- package/dist/world/resourceNode.d.ts +109 -0
- package/dist/world/resourceNode.js +140 -0
- package/dist/world/roads.d.ts +19 -2
- package/dist/world/roads.js +60 -3
- package/dist/world/scatter.js +2 -20
- package/dist/world/streetGenerator.d.ts +159 -0
- package/dist/world/streetGenerator.js +684 -0
- package/dist/world/streets.d.ts +103 -1
- package/dist/world/streets.js +273 -0
- package/dist/world/terraform.js +58 -88
- package/dist/world/terrain.d.ts +65 -1
- package/dist/world/terrain.js +119 -2
- package/dist/world.d.ts +14 -5
- package/dist/world.js +12 -3
- package/package.json +1 -1
- package/skills/game-design/SKILL.md +28 -0
- package/skills/game-design/agents/openai.yaml +4 -0
- package/skills/game-design/references/application-playbook.md +170 -0
- package/skills/game-design/references/field-guide.md +288 -0
- package/skills/jgengine/SKILL.md +8 -4
- package/skills/jgengine/api.md +182 -81
- package/skills/jgengine/capabilities.md +104 -2
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-combat/SKILL.md +19 -1
- package/skills/jgengine-combat/api.md +75 -2
- package/skills/jgengine-combat/capabilities.md +61 -1
- package/skills/jgengine-combat/recipes/portable-damage-effects.md +197 -0
- package/skills/jgengine-combat/recipes/portable-runtime-state.md +154 -0
- package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
- package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +156 -0
- package/skills/jgengine-combat/recipes/visible-attacks.md +81 -0
- package/skills/jgengine-combat/reference.md +33 -4
- package/skills/jgengine-gameplay/SKILL.md +8 -0
- package/skills/jgengine-gameplay/api.md +182 -26
- package/skills/jgengine-gameplay/capabilities.md +96 -2
- package/skills/jgengine-gameplay/recipes/portable-xp-leveling.md +168 -0
- package/skills/jgengine-gameplay/reference-systems.md +10 -5
- package/skills/jgengine-world/SKILL.md +13 -0
- package/skills/jgengine-world/api.md +453 -21
- package/skills/jgengine-world/capabilities.md +112 -2
- package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
- package/skills/jgengine-world/reference.md +43 -4
- 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-
|
|
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-
|
|
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"`
|
|
82
|
+
|
|
83
|
+
## authored-triggers — built-in announce/win/advance actions authorable on any marker or volume
|
|
73
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,31 @@ 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
|
+
## building-lots — derive street-facing building lots from road frontage
|
|
113
|
+
|
|
114
|
+
- `deriveBuildingLots` (function) · `import { deriveBuildingLots } from "@jgengine/core/world/buildingLots"`
|
|
115
|
+
|
|
116
|
+
## city-district — polygon math for road-derived blocks, parcels, and buildable footprints
|
|
117
|
+
|
|
118
|
+
- `Vec2` (type) · `import { Vec2 } from "@jgengine/core/world/cityGeometry"`
|
|
119
|
+
- `resolveCityObject` (function) · `import { resolveCityObject } from "@jgengine/core/world"`
|
|
120
|
+
|
|
121
|
+
## companion-intent — resolve a companion's command + threat context into a per-tick follow/hold/engage/assist intent
|
|
122
|
+
|
|
123
|
+
- `resolveCompanionIntent` (function) · `import { resolveCompanionIntent } from "@jgengine/core/scene/companion"`
|
|
124
|
+
|
|
125
|
+
## companion-roster — manage tamed companions — allegiance, commands, per-companion stats/leveling, and mobBrain-shaped intent
|
|
126
|
+
|
|
127
|
+
- `createCompanionRoster` (function) · `import { createCompanionRoster } from "@jgengine/core/scene/companion"`
|
|
128
|
+
|
|
90
129
|
## contour-interval — readable auto-spacing for elevation contours
|
|
91
130
|
|
|
92
131
|
- `chooseContourInterval` (function) · `import { chooseContourInterval } from "@jgengine/core/world"`
|
|
@@ -111,6 +150,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
111
150
|
|
|
112
151
|
- `entityMetaOf` (function) · `import { entityMetaOf } from "@jgengine/core/world"`
|
|
113
152
|
|
|
153
|
+
## flight-dynamics — simulate fixed-wing, helicopter, and VTOL aircraft
|
|
154
|
+
|
|
155
|
+
- `createAircraftDynamics` (function) · `import { createAircraftDynamics } from "@jgengine/core/world"`
|
|
156
|
+
|
|
114
157
|
## follow-trail — trailing follower/snake formation that chases a leader
|
|
115
158
|
|
|
116
159
|
- `createLeaderTrail` (function) · `import { createLeaderTrail } from "@jgengine/core/world"`
|
|
@@ -159,6 +202,12 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
159
202
|
|
|
160
203
|
- `createGrappleSwing` (function) · `import { createGrappleSwing } from "@jgengine/core/world"`
|
|
161
204
|
|
|
205
|
+
## gravity-field — sample configurable uniform, radial, or combined gravity
|
|
206
|
+
|
|
207
|
+
- `combineGravity` (function) · `import { combineGravity } from "@jgengine/core/world"`
|
|
208
|
+
- `pointGravity` (function) · `import { pointGravity } from "@jgengine/core/world"`
|
|
209
|
+
- `uniformGravity` (function) · `import { uniformGravity } from "@jgengine/core/world"`
|
|
210
|
+
|
|
162
211
|
## hold-order — stand-ground order that holds position until preempted or canceled
|
|
163
212
|
|
|
164
213
|
- `defineHoldOrder` (function) · `import { defineHoldOrder } from "@jgengine/core/world"`
|
|
@@ -191,6 +240,23 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
191
240
|
|
|
192
241
|
- `generateLock` (function) · `import { generateLock } from "@jgengine/core/world"`
|
|
193
242
|
|
|
243
|
+
## mesh-hitboxes — shots pass through holes in concave models — opted-in catalog assets raycast their actual triangles instead of the fitted box.
|
|
244
|
+
|
|
245
|
+
- `ColliderShape` (type) · `import { ColliderShape } from "@jgengine/core/scene/colliders"`
|
|
246
|
+
|
|
247
|
+
## 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.
|
|
248
|
+
|
|
249
|
+
- `ColliderShape` (type) · `import { ColliderShape } from "@jgengine/core/scene/colliders"`
|
|
250
|
+
|
|
251
|
+
## minimap-bake — rasterize authored terrain + zones into a top-down minimap image
|
|
252
|
+
|
|
253
|
+
- `bakeMinimapImage` (function) · `import { bakeMinimapImage } from "@jgengine/core/world"`
|
|
254
|
+
- `minimapBakeToPngDataUri` (function) · `import { minimapBakeToPngDataUri } from "@jgengine/core/world"`
|
|
255
|
+
|
|
256
|
+
## minimap-track — project a world point to a 0..1 fraction along a linear track axis
|
|
257
|
+
|
|
258
|
+
- `trackFraction` (function) · `import { trackFraction } from "@jgengine/core/world/minimapTrack"`
|
|
259
|
+
|
|
194
260
|
## model-sockets — named attachment points read from a model
|
|
195
261
|
|
|
196
262
|
- `ModelNode` (interface) · `import { ModelNode } 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"`
|
|
@@ -325,6 +415,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
325
415
|
|
|
326
416
|
- `defineStopOrder` (function) · `import { defineStopOrder } from "@jgengine/core/world"`
|
|
327
417
|
|
|
418
|
+
## street-generator — build a deterministic street/track graph (nodes, edges, streets, junctions, bridges, tunnels) from sliders
|
|
419
|
+
|
|
420
|
+
- `generateStreets` (function) · `import { generateStreets } from "@jgengine/core/world/streetGenerator"`
|
|
421
|
+
|
|
328
422
|
## surface-drape — lift an XZ polyline onto sculpted terrain
|
|
329
423
|
|
|
330
424
|
- `drapePolyline` (function) · `import { drapePolyline } from "@jgengine/core/world"`
|
|
@@ -357,6 +451,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
357
451
|
|
|
358
452
|
- `terrainContourGuides` (function) · `import { terrainContourGuides } from "@jgengine/core/world"`
|
|
359
453
|
|
|
454
|
+
## terrain-raycast — pick a point on a heightfield along a camera/pointer ray
|
|
455
|
+
|
|
456
|
+
- `raycastHeightField` (function) · `import { raycastHeightField } from "@jgengine/core/world/terrain"`
|
|
457
|
+
|
|
360
458
|
## touch-controls — default on-screen button silhouette for a touch action
|
|
361
459
|
|
|
362
460
|
- `touchButtonShape` (function) · `import { touchButtonShape } from "@jgengine/core/input/touchScheme"`
|
|
@@ -368,3 +466,15 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
368
466
|
## volumetric-clouds — raymarched cloud layer sky option
|
|
369
467
|
|
|
370
468
|
- `VolumetricCloudsConfig` (interface) · `import { VolumetricCloudsConfig } from "@jgengine/core/world"`
|
|
469
|
+
|
|
470
|
+
## world-environment — compose editor/preset-derived outdoor dressing — terrain, weather, water, structures — into a legacy environment feature
|
|
471
|
+
|
|
472
|
+
- `environment` (function) · `import { environment } from "@jgengine/core/world"`
|
|
473
|
+
|
|
474
|
+
## world-place — declare the place a game happens in — flat/round/voxel/board ground, surface laws, per-place physics
|
|
475
|
+
|
|
476
|
+
- `world` (function) · `import { world } from "@jgengine/core/world"`
|
|
477
|
+
|
|
478
|
+
## world-seed — derive a deterministic generator/scatter seed from world id + save/run seed
|
|
479
|
+
|
|
480
|
+
- `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
|
|
5
|
+
## World = place (the definition of a world)
|
|
6
6
|
|
|
7
|
-
|
|
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
|
|
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 whose colliders don't block stop obstructing, and non-opted assets keep the fitted box everywhere. Objects with no index `dims` are not stuck with the default unit box either: the shell measures what `renderObject`/dims-less models actually mount and reports it through `ctx.scene.object.reportBounds`, so a custom-rendered prop obstructs (and raycasts) as its real rendered footprint; only objects with nothing measurable keep the default box. 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
|
-
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# jgengine-api — Cartridge
|
|
2
|
-
|
|
3
|
-
Reference module for the [`jgengine`](./SKILL.md) skill. Load this when building a game as a **cartridge** — one declarative config instead of hand-wired loop/state/UI files. A cartridge game is ~75% smaller than the classic shape and nearly all of it is data; reach for `defineGame` + `defineSystem` ([reference-systems.md](./reference-systems.md)) when the simulation outgrows cartridge archetypes — same single authoring model, no rewrite cliff.
|
|
4
|
-
|
|
5
|
-
## Why
|
|
6
|
-
|
|
7
|
-
Across the classic games, 78–91% of each game's LOC was generic wiring restated per game: a run-state store with subscribe/notify, chase-and-contact enemy loops, auto-fire weapon routines, magnet pickups, draft plumbing, phase-gated HUD trees, and tests for all of it. The cartridge layer owns every one of those; the game ships only its **data** (catalogs, tuning, layout) and any genuinely bespoke rules through typed escape hatches.
|
|
8
|
-
|
|
9
|
-
## Shape
|
|
10
|
-
|
|
11
|
-
`game.config.ts` is the whole game — `cartridge({...})` from `@jgengine/shell/cartridge` compiles the config into a `PlayableGame` (validating it first — a bad reference throws at import with a list of problems). `loop.ts` and `world.ts` disappear; the spec carries the world feature and the loop is engine-owned. `main.tsx` is 5 lines via `mountGame` (`@/components/ui/mount`, registry). Sprites/SVG art stay in `src/game/assets.ts`; the game keeps one test file under `src/game/`.
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
import { cartridge, type CartridgeConfig } from "@jgengine/shell/cartridge";
|
|
15
|
-
import { standardCartridgePanels } from "@/components/ui/cartridge-panels";
|
|
16
|
-
|
|
17
|
-
export const config: CartridgeConfig = {
|
|
18
|
-
name, seed, panels: standardCartridgePanels, assets, entitySprites,
|
|
19
|
-
flow: { start: "gate", countdownSeconds: 3, restart: true }, // press-to-start → 3-2-1 → playing; "restart" command
|
|
20
|
-
player: { kind, health, walkSpeed }, // compiled into content + spawned per player
|
|
21
|
-
enemies: { id: { label, health, walkSpeed, xp, contact: { damage, intervalSeconds }, behavior: "chase" | "none" } },
|
|
22
|
-
combat: { contactRadius },
|
|
23
|
-
spawning: { director: SpawnDirectorConfig, placement: { kind: "ring", radius } },
|
|
24
|
-
weapons: { id: { kind: "projectile" | "orbit" | "pulse" | "custom", damage, cooldownMs, maxLevel, ... } },
|
|
25
|
-
progression: { xp: Curve, maxLevel, draft: { choices, upgrades } },
|
|
26
|
-
fields: { magnetRadius, damageMultiplier }, // named run-scalars upgrades mutate
|
|
27
|
-
xpGems: { collectRadius, pullSpeed, rarityThresholds, defaultRarity },
|
|
28
|
-
rules: { win: { kind: "survive", seconds }, lose: { kind: "playerDeath" } | { kind: "custom", check }, killLeaderboardStat },
|
|
29
|
-
world, physics, camera, worldItem, theme, hud, screens, // screens: start/win/lose
|
|
30
|
-
};
|
|
31
|
-
export const game = cartridge(config);
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
The run is a phase machine every game gets for free: `start → countdown → playing → won | lost`. `flow.start: "gate"` renders `screens.start` (TitleScreen binding) until begin; `flow.countdownSeconds` renders the big-number countdown; `flow.restart: true` registers a `restart` command that resets the run in place, clears cartridge entities, and restores player stats — no hand-rolled session store, phase union, or reset function. `run.playingSeconds` is the run clock (drives the survive rule and the timer panel), so pauses and pre-game phases never skew timing.
|
|
35
|
-
|
|
36
|
-
## Cartridge surface (`@jgengine/shell/cartridge/`)
|
|
37
|
-
|
|
38
|
-
The survivor-shaped defaults (XP gems, contact-radius melee, orbit/pulse/projectile weapons) are a genre archetype, not a core primitive — the whole cartridge implementation lives in `@jgengine/shell` (opt-in, alongside its React binding), never in `@jgengine/core`. `core` only supplies the generic pieces it composes: `spawnDirector`, `abilityKit`, `leveling`/`progression`, `runDraft`, `autoTarget`. A puzzle or tactics game imports none of this.
|
|
39
|
-
|
|
40
|
-
- `@jgengine/shell/cartridge/spec` — the `CartridgeSpec` types, `Leveled` values (`number | { base, perLevel, min?, max? } | { table }` resolved by `leveled(value, level)`), `WASD_KEYBINDS` (the default when `input` is omitted).
|
|
41
|
-
- `@jgengine/shell/cartridge/runtime` — `createCartridge(spec)` → `{ content, loop, run(ctx), weaponKit(ctx), chooseUpgrade(ctx, offerId) }`. Owns the run store (outcome/kills/fields/weapon levels, subscribe/notify), spawn director advance + ring placement, chase + contact-damage enemies (terrain-grounded), the three weapon archetypes with auto-targeting, magnet xp-gem pickups feeding `leveling()`, pause-and-draft level-ups, kill → gem drop + leaderboard, and win/lose rules.
|
|
42
|
-
- `@jgengine/shell/cartridge/validate` — `validateCartridge(spec)` → problem list: spawn waves must reference declared enemies, upgrades must reference declared weapons/fields, stacks can't exceed weapon `maxLevel`, tuning must be positive, rarity thresholds descending. The game's test asserts it returns `[]`.
|
|
43
|
-
|
|
44
|
-
Weapon archetypes: `projectile` (auto-target nearest in `range`, travel-time bolt fx), `orbit` (leveled blade count/radius sweeping `hitRadius`), `pulse` (radial AoE with linear falloff and an expanding-ring fx). Upgrade effects: `weaponLevel` (+1 level per stack), `statBonus` (+max and +current on pick), `fieldAdd` / `fieldMultiply` (recomputed from stacks). Weapon damage is multiplied by the `damageMultiplier` field when declared.
|
|
45
|
-
|
|
46
|
-
## Presentation (shell + registry)
|
|
47
|
-
|
|
48
|
-
`hud.panels` is a schema — items `vital | xp | timer | score | abilityBar | component`, anchored via the movable HUD layout; `screens.win/lose` render the results/death screens; the upgrade-draft modal appears whenever a draft is pending. The shell renders the frame and injects concrete components through the `panels` seam — `standardCartridgePanels` (registry `cartridge-panels`) binds the jgengine UI kit; swap any binding for a custom component without touching the engine. Weapon fx render engine-side from the run store's bolt/pulse feeds (`fxColor`/`fxEmissive` per weapon). `theme` is the `--jg-*` CSS var block — hand-pick it or derive from a few seed colors with `deriveJgTheme` (registry `jg-theme`).
|
|
49
|
-
|
|
50
|
-
## Escape hatches — bespoke logic without leaving the cartridge
|
|
51
|
-
|
|
52
|
-
- `weapons.<id> = { kind: "custom", fire(ctx, run, args) }` — cooldown/level/damage handled for you; the fire shape is yours.
|
|
53
|
-
- `spawning.placement = { kind: "custom", position(ctx, run) }`.
|
|
54
|
-
- `rules.win = { kind: "custom", check(ctx, run) }`.
|
|
55
|
-
- `progression.draft.upgrades[].effect = { kind: "custom", apply(ctx, run, stacks) }`.
|
|
56
|
-
- `systems: [(ctx, run, dt) => ...]` — extra per-tick simulation after the built-ins, only while playing.
|
|
57
|
-
- `hud` items `{ kind: "component", Component }` — arbitrary React panels alongside the schema ones.
|
|
58
|
-
|
|
59
|
-
A game that is *mostly* bespoke simulation (custom physics, session machines) should keep `defineGame` and write its systems — the cartridge is for games whose shape the archetypes already cover.
|
|
60
|
-
|
|
61
|
-
## Testing
|
|
62
|
-
|
|
63
|
-
Cartridge behaviors are engine-tested once (`packages/shell/src/cartridge/runtime.test.ts`); the game test is one call plus any game-specific assertions:
|
|
64
|
-
|
|
65
|
-
```ts
|
|
66
|
-
import { bootCartridge, cartridgeSmokeTest, tickCartridge } from "@jgengine/shell/cartridge/testkit";
|
|
67
|
-
cartridgeSmokeTest(config); // validate + world summary + headless run/spawn/kill/gem smoke
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
`bootCartridge`/`tickCartridge` build a headless `GameContext` and drive the loop (auto-choosing drafts) for custom assertions — see `@jgengine/shell/cartridge/testkit`. The testkit imports `bun:test`; import it from test files only.
|
|
71
|
-
|
|
72
|
-
Barrel surface tokens: createKeyValueStore createUnlocks durabilityFraction isComplete isOverdrawn memorySaveBackend partInSlot pruneToasts policyProjectsViewers projectByVisibleIds projectEntitiesForViewer projectPerUserForViewer visibleEntityIds applyDeltaToSnapshot applySurfaceDeltaToSnapshot boundaryNeighbors catenaryCurve distanceToPolygonEdge footprintObstacles hasValidAdjacency pointInPolygon polygonArea polygonBounds populateNavGridFromEnvironment readScatterPalette readScatterRules revertDeltaFromSnapshot revertSurfaceDeltaFromSnapshot slopeStepCost solveLock solveLockPath
|