@jgengine/core 0.26.0 → 0.28.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 +174 -0
- package/dist/ai/policeResponse.d.ts +253 -0
- package/dist/ai/policeResponse.js +421 -0
- package/dist/anim/actionLayer.d.ts +47 -0
- package/dist/anim/actionLayer.js +63 -0
- package/dist/anim/ikSolver.d.ts +14 -0
- package/dist/anim/ikSolver.js +37 -0
- package/dist/anim/locomotionGraph.d.ts +58 -3
- package/dist/anim/locomotionGraph.js +142 -15
- package/dist/anim/ragdollRig.d.ts +53 -0
- package/dist/anim/ragdollRig.js +132 -0
- package/dist/anim/skeletonStandard.d.ts +140 -0
- package/dist/anim/skeletonStandard.js +326 -0
- package/dist/combat/damageRuntime.d.ts +14 -1
- package/dist/combat/damageRuntime.js +62 -4
- package/dist/combat/death.js +7 -3
- package/dist/combat/deathReason.d.ts +7 -1
- package/dist/combat/effects.d.ts +25 -0
- package/dist/combat/effects.js +16 -0
- package/dist/combat/statusRuntime.d.ts +2 -0
- package/dist/combat/statusRuntime.js +1 -0
- package/dist/economy/loanBook.d.ts +182 -0
- package/dist/economy/loanBook.js +303 -0
- package/dist/economy/market.d.ts +161 -0
- package/dist/economy/market.js +248 -0
- package/dist/economy/transactionLedger.d.ts +162 -0
- package/dist/economy/transactionLedger.js +301 -0
- package/dist/editor/creatorStorage.js +3 -1
- package/dist/editor/simulation.d.ts +5 -2
- package/dist/editor/simulation.js +29 -19
- package/dist/game/cameraConfig.d.ts +14 -0
- package/dist/game/clipRoles.d.ts +5 -4
- package/dist/game/clipRoles.js +34 -4
- package/dist/game/events.d.ts +27 -0
- package/dist/game/gameSave.d.ts +142 -0
- package/dist/game/gameSave.js +174 -0
- package/dist/game/playableGame.d.ts +74 -6
- package/dist/game/saveSlotIndex.d.ts +54 -0
- package/dist/game/saveSlotIndex.js +71 -0
- package/dist/game/saveSlots.d.ts +2 -0
- package/dist/game/saveSlots.js +1 -1
- package/dist/game/saveStore.d.ts +8 -0
- package/dist/game/saveStore.js +16 -0
- package/dist/gameplay.d.ts +17 -2
- package/dist/gameplay.js +15 -0
- package/dist/inventory/carryLoad.d.ts +48 -0
- package/dist/inventory/carryLoad.js +44 -0
- package/dist/inventory/equipment.d.ts +95 -0
- package/dist/inventory/equipment.js +169 -0
- package/dist/inventory/inventoryModel.d.ts +15 -6
- package/dist/inventory/inventoryModel.js +54 -5
- package/dist/inventory/shapedGrid.d.ts +26 -0
- package/dist/inventory/shapedGrid.js +47 -0
- package/dist/loot/lootGen.d.ts +186 -0
- package/dist/loot/lootGen.js +437 -0
- package/dist/loot/lootWeapon.d.ts +35 -0
- package/dist/loot/lootWeapon.js +70 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +156 -1
- package/dist/mission/compositions.d.ts +85 -0
- package/dist/mission/compositions.js +130 -0
- package/dist/mission/mission.d.ts +435 -0
- package/dist/mission/mission.js +770 -0
- package/dist/mission/triggers.d.ts +61 -0
- package/dist/mission/triggers.js +108 -0
- package/dist/nav/laneGraph.d.ts +178 -0
- package/dist/nav/laneGraph.js +671 -0
- package/dist/nav/traffic.d.ts +222 -0
- package/dist/nav/traffic.js +918 -0
- package/dist/physics/kinematicVehicle.d.ts +36 -0
- package/dist/physics/kinematicVehicle.js +15 -1
- package/dist/physics/vehicleDamage.d.ts +136 -0
- package/dist/physics/vehicleDamage.js +214 -0
- package/dist/physics/vehicleParts.d.ts +62 -0
- package/dist/physics/vehicleParts.js +65 -0
- package/dist/progression/gearScore.d.ts +56 -0
- package/dist/progression/gearScore.js +59 -0
- package/dist/progression/skillTree.d.ts +129 -0
- package/dist/progression/skillTree.js +265 -0
- package/dist/progression/xpCurve.d.ts +60 -0
- package/dist/progression/xpCurve.js +141 -0
- package/dist/runtime/context/combat.js +11 -0
- package/dist/scene/builtinSceneKinds.js +2 -0
- package/dist/settings/accessibilitySettings.d.ts +81 -0
- package/dist/settings/accessibilitySettings.js +152 -0
- package/dist/settings/graphicsProfile.d.ts +3 -0
- package/dist/settings/graphicsProfile.js +4 -0
- package/dist/settings/settingsModel.d.ts +1 -1
- package/dist/settings/settingsModel.js +1 -0
- package/dist/sim/nearAgents.d.ts +8 -1
- package/dist/sim/nearAgents.js +34 -17
- package/dist/sim/offScreen.d.ts +2 -1
- package/dist/sim/offScreen.js +14 -3
- package/dist/sim/population.d.ts +147 -0
- package/dist/sim/population.js +182 -0
- package/dist/sim/queue.d.ts +90 -0
- package/dist/sim/queue.js +171 -0
- package/dist/sim/rides.d.ts +287 -0
- package/dist/sim/rides.js +471 -0
- package/dist/sim/routine.js +19 -11
- package/dist/ui/loadingTips.d.ts +41 -0
- package/dist/ui/loadingTips.js +64 -0
- package/dist/ui/menuNav.d.ts +79 -0
- package/dist/ui/menuNav.js +147 -0
- package/dist/ui/menuTransition.d.ts +50 -0
- package/dist/ui/menuTransition.js +67 -0
- package/dist/ui.d.ts +2 -0
- package/dist/ui.js +2 -0
- package/dist/vfx/effectLibrary.d.ts +98 -0
- package/dist/vfx/effectLibrary.js +181 -0
- package/dist/vfx/particleBudget.d.ts +40 -0
- package/dist/vfx/particleBudget.js +39 -0
- package/dist/vfx/particleDirector.d.ts +16 -0
- package/dist/vfx/particleDirector.js +18 -0
- package/dist/world/authoredSimulation.js +7 -1
- package/dist/world/captureCamera.d.ts +30 -0
- package/dist/world/captureCamera.js +19 -0
- package/dist/world/decalKind.d.ts +48 -0
- package/dist/world/decalKind.js +86 -0
- package/dist/world/features.d.ts +18 -5
- package/dist/world/features.js +1 -0
- package/dist/world/grassKind.d.ts +5 -0
- package/dist/world/grassKind.js +3 -0
- package/dist/world/terraform.d.ts +8 -0
- package/dist/world/terrain.d.ts +1 -0
- package/dist/world/terrain.js +2 -0
- package/dist/world.d.ts +7 -2
- package/dist/world.js +5 -0
- package/package.json +1 -1
- package/skills/jgengine/api.md +2 -2
- package/skills/jgengine-combat/SKILL.md +9 -1
- package/skills/jgengine-combat/api.md +5 -4
- package/skills/jgengine-combat/reference.md +4 -0
- package/skills/jgengine-gameplay/SKILL.md +86 -0
- package/skills/jgengine-gameplay/api.md +485 -16
- package/skills/jgengine-gameplay/capabilities.md +131 -4
- package/skills/jgengine-gameplay/reference-systems.md +118 -0
- package/skills/jgengine-world/SKILL.md +8 -0
- package/skills/jgengine-world/api.md +243 -11
- package/skills/jgengine-world/capabilities.md +61 -1
- package/skills/jgengine-world/recipes/character-animation.md +25 -2
- package/skills/jgengine-world/recipes/vehicle-feel.md +5 -0
- package/skills/jgengine-world/reference.md +1 -1
|
@@ -34,15 +34,21 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
34
34
|
- `parseAnimGraph` (function) · `import { parseAnimGraph } from "@jgengine/core/anim/animGraph"`
|
|
35
35
|
- `validateAnimGraph` (function) · `import { validateAnimGraph } from "@jgengine/core/anim/animGraph"`
|
|
36
36
|
|
|
37
|
-
## animation —
|
|
37
|
+
## animation — build a masked one-shot overlay layer for upper-body actions and hit reactions
|
|
38
38
|
|
|
39
|
+
- `actionLayer` (function) · `import { actionLayer } from "@jgengine/core/anim/actionLayer"`
|
|
39
40
|
- `inferLegChains` (function) · `import { inferLegChains } from "@jgengine/core/anim/footPlacement"`
|
|
40
41
|
- `lookAt` (function) · `import { lookAt } from "@jgengine/core/anim/ikSolver"`
|
|
42
|
+
- `multiplyQuaternions` (function) · `import { multiplyQuaternions } from "@jgengine/core/anim/ikSolver"`
|
|
41
43
|
- `placeFeet` (function) · `import { placeFeet } from "@jgengine/core/anim/footPlacement"`
|
|
42
44
|
- `planBoneTexture` (function) · `import { planBoneTexture } from "@jgengine/core/anim/boneTexture"`
|
|
45
|
+
- `quaternionFromUnitVectors` (function) · `import { quaternionFromUnitVectors } from "@jgengine/core/anim/ikSolver"`
|
|
46
|
+
- `ragdollBoneOrientations` (function) · `import { ragdollBoneOrientations } from "@jgengine/core/anim/ragdollRig"`
|
|
47
|
+
- `ragdollConfigFromPoses` (function) · `import { ragdollConfigFromPoses } from "@jgengine/core/anim/ragdollRig"`
|
|
43
48
|
- `sampleBoneTexture` (function) · `import { sampleBoneTexture } from "@jgengine/core/anim/boneTexture"`
|
|
44
49
|
- `solveFabrik` (function) · `import { solveFabrik } from "@jgengine/core/anim/ikSolver"`
|
|
45
50
|
- `solveTwoBone` (function) · `import { solveTwoBone } from "@jgengine/core/anim/ikSolver"`
|
|
51
|
+
- `withActionLayers` (function) · `import { withActionLayers } from "@jgengine/core/anim/actionLayer"`
|
|
46
52
|
|
|
47
53
|
## area-effect-field — source-following area membership with enter/refresh/leave edges, stacking, and cleanup
|
|
48
54
|
|
|
@@ -218,6 +224,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
218
224
|
|
|
219
225
|
- `createCameraShake` (function) · `import { createCameraShake } from "@jgengine/core/world"`
|
|
220
226
|
|
|
227
|
+
## capture — force a framed capture camera over a game that owns its camera
|
|
228
|
+
|
|
229
|
+
- `installCaptureCamera` (function) · `import { installCaptureCamera } from "@jgengine/core/world/captureCamera"`
|
|
230
|
+
|
|
221
231
|
## character-controller — capsule walk over the physics backend — slide, step-up, slopes, crouch, platforms
|
|
222
232
|
|
|
223
233
|
- `createCharacterController` (function) · `import { createCharacterController } from "@jgengine/core/movement/characterController"`
|
|
@@ -312,6 +322,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
312
322
|
|
|
313
323
|
- `tickDrivableVehicle` (function) · `import { tickDrivableVehicle } from "@jgengine/core/world"`
|
|
314
324
|
|
|
325
|
+
## effect-resolve — turn a named library effect into emitter config, GPU sprite texture, blending and budget priority with per-call overrides
|
|
326
|
+
|
|
327
|
+
- `resolveEffect` (function) · `import { resolveEffect } from "@jgengine/core/vfx/effectLibrary"`
|
|
328
|
+
|
|
315
329
|
## elevation-readout — cursor height and delta-from-reference feedback
|
|
316
330
|
|
|
317
331
|
- `sampleElevation` (function) · `import { sampleElevation } from "@jgengine/core/world"`
|
|
@@ -340,6 +354,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
340
354
|
|
|
341
355
|
- `createFeedbackMixer` (function) · `import { createFeedbackMixer } from "@jgengine/core/vfx/feedbackMixer"`
|
|
342
356
|
|
|
357
|
+
## find-effect — look up one named particle effect in the shared library, or undefined when unknown
|
|
358
|
+
|
|
359
|
+
- `findEffect` (function) · `import { findEffect } from "@jgengine/core/vfx/effectLibrary"`
|
|
360
|
+
|
|
343
361
|
## fire-input — bind a key or mouse button to a named action that runs the same-named command with the aim; repeatMs auto-fires while held
|
|
344
362
|
|
|
345
363
|
- `ActionCodesMap` (type) · `import { ActionCodesMap } from "@jgengine/core/input/actionBindings"`
|
|
@@ -462,6 +480,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
462
480
|
|
|
463
481
|
- `createKinematicVehicle` (function) · `import { createKinematicVehicle } from "@jgengine/core/world"`
|
|
464
482
|
|
|
483
|
+
## lane-graph — directed lane topology with junctions, signals and parking for vehicle traffic
|
|
484
|
+
|
|
485
|
+
- `createLaneGraph` (function) · `import { createLaneGraph } from "@jgengine/core/world"`
|
|
486
|
+
|
|
487
|
+
## lane-graph-from-scene — read an authored lane graph, signals and parking out of the scene document
|
|
488
|
+
|
|
489
|
+
- `laneGraphFromDocument` (function) · `import { laneGraphFromDocument } from "@jgengine/core/world"`
|
|
490
|
+
|
|
465
491
|
## lean-metrics — measure a motorcycle's lean — steady lean, time to lean, countersteer tip-in
|
|
466
492
|
|
|
467
493
|
- `measureLean` (function) · `import { measureLean } from "@jgengine/core/physics/handlingProbe"`
|
|
@@ -662,10 +688,22 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
662
688
|
- `parseRotationPolicy` (function) · `import { parseRotationPolicy } from "@jgengine/core/world"`
|
|
663
689
|
- `snapHeading` (function) · `import { snapHeading } from "@jgengine/core/world"`
|
|
664
690
|
|
|
691
|
+
## plan-particle-budget — cap live particles to a graphics tier and drop whole effects lowest-priority-first when over budget
|
|
692
|
+
|
|
693
|
+
- `planParticleBudget` (function) · `import { planParticleBudget } from "@jgengine/core/vfx/particleBudget"`
|
|
694
|
+
|
|
665
695
|
## pole-line — editor-authorable poles + sagging cables along a path
|
|
666
696
|
|
|
667
697
|
- `POLE_LINE_KIND` (const) · `import { POLE_LINE_KIND } from "@jgengine/core/world/poleLineKind"`
|
|
668
698
|
|
|
699
|
+
## police-response — witness-gated heat, tiered police response, last-sighting search areas and lane-graph roadblocks
|
|
700
|
+
|
|
701
|
+
- `createPoliceResponse` (function) · `import { createPoliceResponse } from "@jgengine/core/world"`
|
|
702
|
+
|
|
703
|
+
## police-roads — lane-graph roadblock sites and an indexed nearby-lane query for search areas
|
|
704
|
+
|
|
705
|
+
- `policeRoadsFromLaneGraph` (function) · `import { policeRoadsFromLaneGraph } from "@jgengine/core/world"`
|
|
706
|
+
|
|
669
707
|
## population-director — ambient per-region creature population manager with timed weighted respawns and caps
|
|
670
708
|
|
|
671
709
|
- `createPopulationDirector` (function) · `import { createPopulationDirector } from "@jgengine/core/ai/populationDirector"`
|
|
@@ -747,6 +785,16 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
747
785
|
|
|
748
786
|
- `createSharedAnnotations` (function) · `import { createSharedAnnotations } from "@jgengine/core/world"`
|
|
749
787
|
|
|
788
|
+
## skeleton-standard — map a rig on the standard scheme onto the standard's slots
|
|
789
|
+
|
|
790
|
+
- `KAYKIT_SCHEME` (const) · `import { KAYKIT_SCHEME } from "@jgengine/core/anim/skeletonStandard"`
|
|
791
|
+
- `SKELETON_STANDARD` (const) · `import { SKELETON_STANDARD } from "@jgengine/core/anim/skeletonStandard"`
|
|
792
|
+
- `mapRigToStandard` (function) · `import { mapRigToStandard } from "@jgengine/core/anim/skeletonStandard"`
|
|
793
|
+
- `retargetBoneScales` (function) · `import { retargetBoneScales } from "@jgengine/core/anim/skeletonStandard"`
|
|
794
|
+
- `retargetTracks` (function) · `import { retargetTracks } from "@jgengine/core/anim/skeletonStandard"`
|
|
795
|
+
- `runtimeBoneName` (function) · `import { runtimeBoneName } from "@jgengine/core/anim/skeletonStandard"`
|
|
796
|
+
- `standardBoneMask` (function) · `import { standardBoneMask } from "@jgengine/core/anim/skeletonStandard"`
|
|
797
|
+
|
|
750
798
|
## skill-check — a timing-bar skill check where you release inside a zone
|
|
751
799
|
|
|
752
800
|
- `skillCheckZoneAt` (function) · `import { skillCheckZoneAt } from "@jgengine/core/world"`
|
|
@@ -897,6 +945,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
897
945
|
- `setTouchControlsMode` (function) · `import { setTouchControlsMode } from "@jgengine/core/input/touchControlsMode"`
|
|
898
946
|
- `touchButtonShape` (function) · `import { touchButtonShape } from "@jgengine/core/input/touchScheme"`
|
|
899
947
|
|
|
948
|
+
## traffic — lane-graph traffic with car following, signals, right-of-way, lane changes and parking
|
|
949
|
+
|
|
950
|
+
- `createTraffic` (function) · `import { createTraffic } from "@jgengine/core/world"`
|
|
951
|
+
|
|
900
952
|
## ui-sound-cues — default procedural click/hover/open/close/confirm/deny/notify cues on a "ui" bus, overridable per game
|
|
901
953
|
|
|
902
954
|
- `withUiCueSounds` (function) · `import { withUiCueSounds } from "@jgengine/core/audio/uiCues"`
|
|
@@ -913,10 +965,18 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
913
965
|
|
|
914
966
|
- `createVehicleBackendLink` (function) · `import { createVehicleBackendLink } from "@jgengine/core/physics/vehicleBackendLink"`
|
|
915
967
|
|
|
968
|
+
## vehicle-damage — fixed-zone crash damage that changes handling and shows a bounded dent, serializable
|
|
969
|
+
|
|
970
|
+
- `createVehicleDamage` (function) · `import { createVehicleDamage } from "@jgengine/core/world"`
|
|
971
|
+
|
|
916
972
|
## vehicle-dynamics — force-based car handling — tires, load transfer, drivetrain, assists — tuned with physical numbers
|
|
917
973
|
|
|
918
974
|
- `createVehicleDynamics` (function) · `import { createVehicleDynamics } from "@jgengine/core/physics/vehicleDynamics"`
|
|
919
975
|
|
|
976
|
+
## vehicle-parts — compose data-driven tuning parts (tires, engine, brakes) onto a vehicle tuning and read the resulting feel
|
|
977
|
+
|
|
978
|
+
- `applyVehicleParts` (function) · `import { applyVehicleParts } from "@jgengine/core/world"`
|
|
979
|
+
|
|
920
980
|
## vehicle-seats — enter, exit and swap seats in a car or mount (GTA-style boarding) with driver and passenger slots
|
|
921
981
|
|
|
922
982
|
- `createVehicleSeats` (function) · `import { createVehicleSeats } from "@jgengine/core/world"`
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
## The seams
|
|
6
6
|
|
|
7
|
+
- **Skeleton standard and retargeting.** `SKELETON_STANDARD` (`@jgengine/core/anim/skeletonStandard`) names one humanoid bone vocabulary slot by slot: `root`, `hips`, `spine`, `chest`, `head`, `upperarm.l/r`, `lowerarm.l/r`, `hand.l/r`, `handslot.l/r`, `upperleg.l/r`, `lowerleg.l/r`, `foot.l/r`, `toes.l/r`, parented in that order (`SKELETON_PARENT`). It is the CC0 KayKit character rig — one armature shared by `kaykit-adventurers/Barbarian|Knight|Mage|Rogue|Rogue_Hooded` and `kaykit-skeletons/Skeleton_Mage|Minion|Rogue|Warrior`, so all nine characters play one authored graph, one foot-IK rig, one attachment slot and one ragdoll layout. It is a naming standard, not a rig choice: a game still picks its own characters and clips.
|
|
8
|
+
- `mapRigToStandard(boneNames, scheme)` maps a rig on another scheme onto those slots through `RIG_SCHEMES` (`kaykit-humanoid`, `mixamo`, `quaternius-alien`) or a game-supplied `RigScheme`. Matching ignores case and punctuation and strips the `mixamorig`/`Armature` scope prefixes. The result is `{ slots, missing, unmapped }`: slots the rig cannot fill are reported, never borrowed, and extra bones (helpers, IK targets, props) come back in `unmapped`.
|
|
9
|
+
- The Quaternius sci-fi aliens are `Root` + `Spine_1…Spine_6` + `Neck` + `Head` with no arms and no legs, so `QUATERNIUS_ALIEN_SCHEME` fills `root`/`hips`/`spine`/`chest`/`head` and reports every limb slot missing. That is the honest mapping — the pack's own clip set is `Alien_Idle` alone.
|
|
10
|
+
- `retargetTracks(tracks, map)` renames a clip's tracks onto the mapped rig and drops tracks whose bone the rig lacks; it emits the **runtime** bone name (`upperlegl`), which is the spelling both the loaded hierarchy and the loaded clips use, since GLTFLoader strips `.` from node names. `runtimeBoneName` gives that spelling for an authored name. `retargetBoneScales(source, target)` gives the per-slot length ratio so a retargeted clip moves both rigs the same distance, not the same angle.
|
|
11
|
+
- Retargeting here is track renaming plus proportion scaling. It does not transfer a pose, compensate a different bind orientation, or supply IK; a rig whose rest orientation differs needs its own correction.
|
|
7
12
|
- **Clips on a model.** `ModelConfig.animation` on an entity kind. A catalog model with indexed `clips` animates automatically through semantic clip roles; `"none"` holds the bind pose. `{ clip, paused, time }` holds one pose. Equivalent same-order animation data survives ordinary React rerenders without restarting gait, pending triggers or visual variant choices. The hook retains a private serialized snapshot; intentional clip, playback or graph edits restart that model’s playback. Keep authored one-shot order: it decides priority among simultaneous non-death triggers. Changing the scene, imported clips, context or instance identity starts a new animation lifecycle.
|
|
8
13
|
- **Playback clock.** An entity-bound model (`instanceId` with a game context) holds its animation while game time is paused or has zero timescale. Resume continues the existing mixer/graph and queued triggers. Omitted `animation.clock` or `clock: "real"` retains real-time playback. Choose `{ auto: true, clock: "game" }` for a character whose gait and clip progress should follow locally accepted simulation time. The shell samples `ctx.time.advancedSeconds()` rather than multiplying the raw render delta: clamped frames, fixed steps with no accepted tick and dropped catch-up time cannot advance clips ahead of movement. Accepted seconds already include play speed and timescale, exclude calendar compression, and never hydrate or serialize with saved clock offsets. Held frames checkpoint progress so resume does not replay their elapsed time; `animation.timeScale` remains a separate authored visual rate. Context-hosted unbound previews and previews without a context keep using real time.
|
|
9
14
|
- `createSimClock` supplies the allocation-free progress getter. The getter is optional on external structural clocks; selecting game playback without one produces a repair warning and holds the pose. Provide that clock capability or choose real-time playback.
|
|
@@ -16,18 +21,26 @@
|
|
|
16
21
|
- **Movement authoring.** A game-named movement catalog can expose the motor's existing jump buffer, coyote grace, release factor, apex/fall gravity and landing recovery controls through supplied `createMovementSchema` defaults. Deeper controls appear in a collapsed **Jump response** group; the common setup does not acquire unused controls. Consume the saved row with `bindAuthoredMovement` to tune `movement.feel` live while retaining the game's callbacks and physics. These are physical movement controls; graph clips and transition choices remain authored separately.
|
|
17
22
|
- **Graph.** `animation.graph` is an `AnimGraph` (`@jgengine/core/anim/animGraph`): layers, each a state machine of `clip`, `blend1D` and `blend2D` states joined by transitions with `when` conditions, `trigger`s, crossfade `duration` and `exitTime`. It is plain data, so it saves, diffs and can be authored in the editor.
|
|
18
23
|
- `locomotionGraph({ idle, walk, run, walkSpeed, runSpeed, fadeSec, oneShots })` builds the idle/walk/run blend plus one-shots; `states`/`oneShots` configs are converted through it. One-shot arrays retain every variant. `death` takes priority over simultaneous triggers and holds until the graph is reset; later hit/attack triggers cannot release it.
|
|
24
|
+
- The same builder takes the directional set: `strafeLeft`/`strafeRight` add a `strafe` state blended on the `direction` parameter (`-1` fully left, `+1` fully right), `backward` adds a `backward` state behind `facing` (the dot of facing with motion), and every graph carries a `turn` state blended on `turnRate` that pivots on `walk` when no turn clip is authored. Each joins the idle/walk/run blend through a crossfaded transition with `strafeGate`/`strafeRelease`, `backwardGate`/`backwardRelease`, `turnGate`/`turnFull`/`turnSpeed` hysteresis, so a direction change blends instead of popping. A custom mover feeds `direction`, `facing` and `turnRate` under `ANIM_PARAMS_KEY`; a rig resolved with `animation: "auto"` gets the KayKit directional clips derived for it (`Running_Strafe_Left/Right`, `Walking_Backwards`). Supplying none of them leaves the speed-only tree exactly as before.
|
|
19
25
|
- Upper-body actions (aim, reload, wave) go on a second layer with `mask: ["spine", "arm", ...]` bone prefixes, `additive` for recoil or breathing on top of the base.
|
|
20
26
|
- Each layer owns its clip times and weights. Within one layer, two states using the same clip still resolve to one clip time; blending separate times of that same clip is unsupported.
|
|
21
27
|
- `createAnimGraphRuntime(graph, { rng })` runs the same graph headless: `advance(dt, params, clips)`, `trigger`, `snapshot`/`restore`, `retune`. A clip state's `variants` are chosen once on entry; snapshots preserve that choice. Omit `rng` for the first variant. Runtime models use independent seeded visual streams keyed by instance id and imported clip names, so rendering or culling models cannot advance gameplay randomness. Remounting restarts that visual stream; gameplay-critical clip choices belong in a caller-owned headless runtime and its saved state. Outgoing clips keep advancing during crossfades. Test transitions with it before looking at pixels.
|
|
22
28
|
- **Authoring in the editor.** A placement's graph is inspected, previewed and stored from the editor's Animation workspace (see `jgengine-editor` reference, "Placement animation"). `animGraphFromConfig(config)` gives the graph any config plays, so tools and tests see what the shell plays. A missing/blank idle role leaves a named single clip active (or the bind pose if none is named); a missing/blank walk role holds the named idle at walking speeds. The inspector seeds supported semantic roles from the actual asset when enabling locomotion; imported assets without such roles need explicit choices. `validateAnimGraph(json)` rejects malformed saved graphs with located repairs, preserving valid graph data and transition order. `parseAnimGraph(json)` remains the permissive import path that drops malformed entries.
|
|
23
29
|
- **Static character placements.** Catalog placements validate saved `meta.animation` with the same strict value reader as dynamic characters and the inspector. A malformed override is omitted so the original catalog animation remains active; the saved document and placement stay unchanged. `resolveAuthoredObjectsWithDiagnostics(document)` returns the ordinary objects plus located repairs in one scan, and `<AuthoredObjects>` reports those repairs at runtime. Valid partial roles, empty maps, signed rates, ordered variants and extension fields keep their authored meaning. Asset clip and rig compatibility still belongs to the model diagnostics.
|
|
24
30
|
- **Dynamic character authoring.** Store animation on the game-selected marker at `meta.animation` through ordinary editor commands, then use `createAuthoredAnimationReader(() => getDocumentLiveSync()?.getDocument() ?? editorLayers, markerId, gameDefault)` (`@jgengine/core/world/authoredAnimation`). A game-owned `ModelConfig.animation` getter returns `read().animation`; the game chooses the actor-to-marker association, rig and defaults. The reader validates only after immutable document identity changes. Authored objects replace defaults rather than merging their roles, graph or combat triggers; explicit `"auto"`/`"none"` and in-progress empty/partial roles survive save/reload. Omitted or malformed overrides retain the exact game default, and `read().diagnostics` supplies document paths and repairs. Surface those diagnostics during inspection; asset clip and rig compatibility still use `diagnoseModelAnimation`.
|
|
25
|
-
- **Parameters.** The shell feeds the entity's smoothed ground speed as `speed`. For shared player movement it also reads physically resolved `grounded`, `verticalSpeed` and `crouched` through `playerMovementTelemetry(ctx, entityId)`; these values override conflicting blackboard extras. Custom movers retain their authored extras under `ANIM_PARAMS_KEY` when telemetry is unavailable. `readModelAnimationParams(ctx, entityId, speed, out)` gives custom render hosts the same rule, clearing and reusing their own dictionary without writing game state. The names are exported as `LOCOMOTION_SPEED_PARAM`, `LOCOMOTION_GROUNDED_PARAM`, `LOCOMOTION_VERTICAL_SPEED_PARAM` and `
|
|
31
|
+
- **Parameters.** The shell feeds the entity's smoothed ground speed as `speed`. For shared player movement it also reads physically resolved `grounded`, `verticalSpeed` and `crouched` through `playerMovementTelemetry(ctx, entityId)`; these values override conflicting blackboard extras. It derives the directional set from the entity's own motion each visual sample: `direction` (the motion's share along the character's right axis), `facing` (its share along the facing axis) and `turnRate` (its yaw change per second), from the entity's `position` and `rotationY`. Those override conflicting extras too, so a rig with the directional states strafes and backpedals with no game-side code. A step past the live `snapDistance` is a teleport, so the sample reports no direction and zero speed instead of inventing a lateral. Custom movers retain their authored extras under `ANIM_PARAMS_KEY` when telemetry is unavailable. `readModelAnimationParams(ctx, entityId, speed, out, step)` gives custom render hosts the same rule, clearing and reusing their own dictionary without writing game state; omit `step` to leave direction to the authored extras. The names are exported as `LOCOMOTION_SPEED_PARAM`, `LOCOMOTION_GROUNDED_PARAM`, `LOCOMOTION_VERTICAL_SPEED_PARAM`, `LOCOMOTION_CROUCHED_PARAM`, `LOCOMOTION_DIRECTION_PARAM`, `LOCOMOTION_FACING_PARAM` and `LOCOMOTION_TURN_RATE_PARAM`.
|
|
26
32
|
- Jump clips remain explicit rig choices in the game's graph. A supported rig can enter its start clip on `!grounded && verticalSpeed > 0`, its air loop at the physical apex, and its landing clip on ground contact. Let an airborne or landing state return to takeoff when vertical speed becomes positive so a buffered re-jump is visible even if rendering misses the grounded frame. Use `verticalSpeed < 0` for falling from locomotion so unavailable movement data does not invent a fall. Verify those transitions against that rig and the game's physical motor; no automatic jump mapping or universal compatibility is supplied.
|
|
27
33
|
- **Placement discontinuities.** The shell uses the live simulation `snapDistance` to discard inferred gait speed when a character moves farther than that distance in three dimensions between visual samples. Retune it through `ctx.sim.retune({ snapDistance })` with the game's existing interpolation policy; `Infinity` permits continuous fast motion. Sampling resets the speed history while preserving graph time, triggers, variants and terminal states. The shared motor clears its retained state when its driven actor despawns. Raw position updates retain physical motor state; a reconstruction policy can call `forgetPlayerMovement` explicitly.
|
|
34
|
+
- **Upper-body action layers.** `actionLayer` (`@jgengine/core/anim/actionLayer`) builds one masked overlay from plain data: a resting clip plus a one-shot state per entry in `actions`, each joined by a consumed-trigger transition and returning on `exitTime`. `withActionLayers(graph, layers)` appends them to a locomotion graph, rejecting a duplicate layer id instead of shadowing the base.
|
|
35
|
+
- `additive: true` makes it a recoil/breathing overlay on top of the base pose: the shell runs `AnimationUtils.makeClipAdditive`, which makes the rest clip's first frame the zero reference, so the rest state is held at `speed: 0` and contributes nothing while the base keeps animating. Without `additive` the layer replaces the masked bones.
|
|
36
|
+
- `mask` is required and nonempty, because an overlay that names no bones would drive the whole rig. `UPPER_BODY_MASK` and `LOWER_BODY_MASK` (`@jgengine/core/anim/skeletonStandard`) are the masks for a standard rig, derived from the standard's own slots; `standardBoneMask(slots)` builds one from any slot list.
|
|
37
|
+
- Aim, reload, wave and hit reactions are the same shape, so they are one builder rather than a hand-written state machine per game. `ctx.game.playEntityAnimation(id, name)` arms the trigger.
|
|
28
38
|
- **Triggers.** `ctx.game.playEntityAnimation(id, name)` arms a trigger; `hit` and `death` arm from their combat events.
|
|
29
39
|
- **Events.** `graph.events: [{ clip, atSec, name }]` emit `animation.event` when an influencing clip crosses that time. Full-cycle frames still fire; multiple missed cycles coalesce to one event per advance. Hook footstep audio, the damage frame of a swing or the magazine swap of a reload to the event, not to a timer.
|
|
30
|
-
- **Root motion.** `rootMotion: true` extracts local root-bone travel into `output.rootDelta` and plays the rig in place horizontally. The in-place flag remains active while a fading-out root-motion clip influences the pose, including interrupted fades and restored graph snapshots. The renderer never changes entity position: the movement/collision owner must consume the headless delta, transform it through the imported rig's parent/facing/scale with `takeRootMotion`, and resolve it through its controller. The bone's vertical motion remains animated. `createGraphPose.apply(output.clips, output.rootMotion)` gives editor/custom previews the same in-place policy. Existing graphs that relied on renderer-driven movement must migrate to a collision-authoritative host; no automatic vault or dodge motor is supplied. Extraction currently assumes the first bone has a matching `.position` track. Root deltas sample the current state's travel; they do not integrate outgoing travel or transition fade weights. Hosts needing collision-authoritative blended root travel must implement that policy explicitly; the in-place visual guarantee does not imply blended root-delta support.
|
|
40
|
+
- **Root motion.** `rootMotion: true` extracts local root-bone travel into `output.rootDelta` and plays the rig in place horizontally. `locomotionGraph({ ..., rootMotion: true })` (or `states.rootMotion`) marks the whole locomotion set — idle/walk/run, strafe, backward and turn — so one authored locomotion graph carries its own travel and the movement owner resolves it; a rig whose clips are authored in place leaves the flag off and reports no travel. The in-place flag remains active while a fading-out root-motion clip influences the pose, including interrupted fades and restored graph snapshots. The renderer never changes entity position: the movement/collision owner must consume the headless delta, transform it through the imported rig's parent/facing/scale with `takeRootMotion`, and resolve it through its controller. The bone's vertical motion remains animated. `createGraphPose.apply(output.clips, output.rootMotion)` gives editor/custom previews the same in-place policy. Existing graphs that relied on renderer-driven movement must migrate to a collision-authoritative host; no automatic vault or dodge motor is supplied. Extraction currently assumes the first bone has a matching `.position` track. Root deltas sample the current state's travel; they do not integrate outgoing travel or transition fade weights. Hosts needing collision-authoritative blended root travel must implement that policy explicitly; the in-place visual guarantee does not imply blended root-delta support.
|
|
41
|
+
- **Ragdoll on death.** `ragdollConfigFromPoses(map, poses, { scale })` (`@jgengine/core/anim/ragdollRig`) turns a mapped rig's world poses at the moment of death into a `RagdollConfig`: one box body per standard slot that has a shape from `HUMANOID_RAGDOLL_BODIES`, sized to the character, and one distance link per body to its nearest shaped ancestor, holding the bones' own separation. `handSlot.l/r` carry no body (prop sockets, not body parts); a slot the rig lacks is skipped and its children link to the nearest present ancestor.
|
|
42
|
+
- `useRagdoll(scene, { map, world, active, height })` (`@jgengine/shell/render/useRagdoll`) arms it: the rig's world poses become the bodies, and every frame after, each bone's world position and aim come from its body. The engine's `PhysicsWorld` bodies carry translation only, so `ragdollBoneOrientations(rest, positions)` gives the skeleton its orientation by rotating each bone's death-pose direction-to-child onto the current one — that is what makes the result a body rather than a pile of boxes.
|
|
43
|
+
- Give the ragdoll its own `PhysicsWorld` (the hook steps it), and stop the character's IK while it is active (`ModelConfig.ik` unset) or the two write the same joints. Deactivating removes the bodies and leaves the rig where the ragdoll put it. This is the death path for a rig whose death clip would otherwise freeze a pose in mid-air.
|
|
31
44
|
- **Diagnostics.** `diagnoseModelAnimation(scene, config, clips)` checks incomplete locomotion roles, missing active clip mappings, masks that match no used tracks and unsupported root-track assumptions. Explicit graphs take precedence over dormant role/single-clip settings, so inactive settings do not produce misleading warnings. Runtime and graph previews warn with the same repair guidance. An explicit invalid single clip retains the bind pose rather than playing an arbitrary first clip.
|
|
32
45
|
- Inspector and Graph panels validate saved `meta.animation` with the same strict reader. A malformed override shows its document path and repair, leaves the saved data intact, and blocks ordinary playback edits and graph preview. Diagnostics remain visible without imported clip metadata. Explicit replacement discards the invalid override through an undoable marker edit; Custom derivation requires the rig's real clip list. Valid partial roles, signed playback values, variant order and extension fields survive unrelated edits.
|
|
33
46
|
- **Imported rig slots.** `ModelConfig.attachments[].slot` uses an exact runtime node name first, then a unique original imported name (`userData.name`). GLTFLoader can sanitize `handslot.r` to `handslotr`; authored slot references keep working without renaming animated bones.
|
|
@@ -36,6 +49,9 @@
|
|
|
36
49
|
- This resolves node identity; it does not retarget animations or make different rigs share attachment proportions.
|
|
37
50
|
- **Foot IK.** `ModelConfig.ik` runs after the mixer each frame.
|
|
38
51
|
- `ik: "auto"` finds thigh → shin → foot chains by bone name. Pass `{ feet: [{ root, mid, tip }] }` when the rig's names are unusual, and `feet: []` to keep only `lookAt`.
|
|
52
|
+
- `lookAt: { bone }` turns a bone (the head) toward the camera. `lookAt.target` aims it at an entity (`{ entity: "mark" }`, offset by `entityEyeHeight`, default 1.5) or a fixed world point (`{ point: [x, y, z] }`) instead; `maxYaw`/`maxPitch` bound the turn in radians and `weight` (default 1) scales how much of the solved turn reaches the bone. An entity target that is not spawned keeps the last known direction, so a despawn does not snap the head back to the camera. `lookAt` with `feet: []` is look-at on its own.
|
|
53
|
+
- `@jgengine/core/anim/ikSolver`'s `lookAt({ from, target, up, maxYaw, maxPitch })` is the pure solver, for a custom host or a non-head chain (a turret, a neck).
|
|
54
|
+
- `ModelIkConfig.lookAt` is also the place a game points a character at a thing; nothing else in the shell reads a look-at target.
|
|
39
55
|
- Explicit joint, `pelvis` and `lookAt.bone` references use exact runtime names first, then the same unique original imported-name lookup as attachments. A name such as `upperleg.r` can resolve after GLTFLoader sanitizes it to `upperlegr`; this preserves node identity and does not supply rig retargeting.
|
|
40
56
|
- Each named joint must resolve uniquely and descend from the preceding joint; intermediate helper transforms are allowed. Missing, ambiguous, disconnected or overlapping chains produce a resolution warning and are skipped. Missing `pelvis` and `lookAt.bone` names also warn. Automatic pelvis correction uses the resolved legs' common ancestor; an explicit `pelvis` retains the authored selection. Auto detection is a naming convention, not compatibility with every rig.
|
|
41
57
|
- Each foot keeps its animated swing and moves by its ground's height above or below the model origin. The pelvis drops so the lower leg can reach, soles never end under the ground, and planted feet tilt to the ground normal (`alignToGround`, `0..1`). The correction fades out while the feet are clear of the ground.
|
|
@@ -53,6 +69,13 @@
|
|
|
53
69
|
| Symptom | Knob |
|
|
54
70
|
| --- | --- |
|
|
55
71
|
| Feet slide at walk or run speed | `walkSpeed`/`runSpeed` to the clip's authored stride speed, or blend1D `at` points |
|
|
72
|
+
| Character slides sideways or backwards with no clip change | `strafeLeft`/`strafeRight`/`backward` states, and feed `direction`/`facing` under `ANIM_PARAMS_KEY` |
|
|
73
|
+
| Character pivots with planted feet | a `turn` clip, or accept the `walk` pivot step; tune `turnGate`/`turnFull` |
|
|
74
|
+
| A clip from one CC0 pack will not play on another rig | map the rig with `mapRigToStandard` and rename the clip with `retargetTracks`; check `missing` before blaming the clip |
|
|
75
|
+
| The head never turns, or turns the wrong way | `ik.lookAt.bone`, and `lookAt.target` when it should track an entity or a point rather than the camera; bound it with `maxYaw`/`maxPitch` |
|
|
76
|
+
| A hit reaction replaces the walk instead of layering on it | `actionLayer({ additive: true, mask: UPPER_BODY_MASK, ... })` and `withActionLayers` |
|
|
77
|
+
| A corpse freezes in mid-air, or a death clip holds a standing pose | `useRagdoll` on the death event, with `ik` unset while it is active |
|
|
78
|
+
| A locomotion set's travel does not match the feet | `locomotionGraph({ rootMotion: true })`, then resolve `rootDelta` through the collision owner |
|
|
56
79
|
| Pop between states | transition `duration`; `exitTime` on one-shots returning to locomotion |
|
|
57
80
|
| Feet float or sink on slopes and steps | `ik: "auto"` |
|
|
58
81
|
| Knees buckle on steep ground | lower `ik.maxAdjust` |
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
## The seams
|
|
6
6
|
|
|
7
7
|
- **Sim.** `createVehicleDynamics(tuning, { surfaceFriction?, clampMove?, groundHeight? })` from `@jgengine/core/physics/vehicleDynamics`. Yaw comes from tire forces, so balance, slides and recovery are outcomes of the numbers, not special cases. It has `snapshot()`/`restore()` for prediction and replay, `retune()` for upgrades and damage, and per-tick modifiers (`driveScale`, `gripScale`, `steerScale`, `brakeScale`, `thrust` for boost).
|
|
8
|
+
- **Arcade sim.** `createKinematicVehicle(tuning, { surfaceFriction?, dragAt?, clampMove? })` from `@jgengine/core/physics/kinematicVehicle` is the cheap steer-yaw model for chases and ambient cars. Its grip is a `GripCurve` (slip → force) sampled each tick against surface friction, handbrake and downforce; `weightTransfer: { comHeight, wheelbase, trackWidth, sensitivity? }` cuts that curve as weight moves under braking/traction/cornering and reports `weightTransfer`/`loadGrip`. `retune()` swaps handling in play, and `gripScale` on the per-tick modifiers is the one-frame hook. Use the force model above when balance has to come out of the numbers.
|
|
9
|
+
- **Parts.** `applyVehicleParts(base, parts)` (`@jgengine/core/physics/vehicleParts`) folds data-driven parts onto a tuning — `{ id, slot, effects: { engineAccel: { scale, add } }, gripCurve? }` — in install order, and returns the composed tuning plus a comparable `VehicleFeel`. `withPart` swaps one part in its slot, `feelOf(tuning)` reads the numbers out of anything, and `vehicle.retune(setup.tuning)` installs it mid-play without losing momentum.
|
|
10
|
+
- **Damage.** `createVehicleDamage({ zones, disableAt?, onTransition? })` (`@jgengine/core/physics/vehicleDamage`) takes local hit points and impulses, steps fixed zones through thresholds, and returns bounded `deformation()` (a per-zone offset the renderer applies, plus `detached`) and `effect()` (grip/drive/brake/top-speed scales, `steerPull`, `drag`). Feed `effect()` through `damagedTuning(base, effect)` + `retune` for lasting damage, or `modifiers()` for one frame; `damagedAxis(axis, effect)` applies the bent-frame pull. It serializes, so a save or a host correction resumes mid-crash. There is no mesh editing: every zone has one authored `deform` limit.
|
|
11
|
+
- The dent is rendered by the game's own `renderEntity` (it already receives the entity each render) — the zone's `offset` displaces that part, and `detached` hides it. No shell seam is needed.
|
|
8
12
|
- **Terrain.** Add a `suspension` block (spring and damper rates, travel, ride height, anti-roll) and pass `groundHeight` for ramps, hills and jumps. Each wheel then samples the ground, the body heaves, pitches and rolls on real springs, slopes pull the car downhill, and the step reports `airborne`, `landingSpeed` and per-corner `wheelLoads`. Without the block the car stays on a flat plane.
|
|
9
13
|
- **Input.** Put `createAxisShaper` (`@jgengine/core/input/axisShaper`) between `ctx.input.axis(...)` and the sim. Keys get `riseRate`/`returnRate`, so a tap steers a little and a hold builds up; sticks get `deadzone`/`curve` with no lag. Pass `{ analog: analogAxes(bindings, ctx.input.analog()) }` each frame so each axis picks the right profile.
|
|
10
14
|
- **Pad feel.** Set the stick deadzone, curve and trigger deadzone once in `defineGame({ gamepad: { deadzone, curve, triggerDeadzone } })`. The shell applies it before any analog value reaches `ctx.input`, so per-axis shaping in `createAxisShaper` only adds ramps and speed scaling on top.
|
|
@@ -36,6 +40,7 @@
|
|
|
36
40
|
- **Sound.** Play a one-shot on `step.landingSpeed` (thud, suspension clunk).
|
|
37
41
|
- **Proof.** For jumps and air control, `measureAir` reports apex height and time, double-jump height, and air rotation rates.
|
|
38
42
|
- **Proof.** `measureHandling(() => createVehicleDynamics(tuning), options?)` from `@jgengine/core/physics/handlingProbe` returns deterministic numbers. Assert them in a test. For a sprung car, `measureRide` adds settle time, brake dive, roll per g and landing bounces.
|
|
43
|
+
- **Proof.** For the arcade sim, `feelOf(setup.tuning)` gives comparable numbers per build, and a test drives the same scripted inputs on two `surfaceFriction` values (or before/after `damagedTuning`) and asserts the grip difference — slip and lateral speed retained, and top speed reached.
|
|
39
44
|
|
|
40
45
|
## Workflow
|
|
41
46
|
|
|
@@ -77,7 +77,7 @@ editor/preset-written scene data (`environmentContentFromDocument`), not a world
|
|
|
77
77
|
|
|
78
78
|
`GrassField` keeps its seeded tuft attributes and blade-budget prefix while partitioning a patch into at most 64 shared-template draws. Each render camera rejects offscreen chunks using conservative sampled-height/blade/wind bounds, and rejects a chunk by distance only when all transformed tuft roots are beyond the fade end. Shadow passes restore their own budget rather than inheriting a main-camera rejection. `distanceFade: false` (or an empty/reversed band) disables distance rejection; `frustumCulled: false` disables chunk frustum rejection. Material colors, wind and caller mesh transforms stay caller-owned. Fully faded blades now collapse width and wind displacement with height; fading blades narrow, and near blades with `keep = 1` retain their appearance. Chunk geometry/template resources and the shared material dispose on replacement/unmount.
|
|
79
79
|
|
|
80
|
-
`terrain()`'s `material` (a named palette — `"grass" | "sand" | "snow" | "rock" | "ash"`, resolved via `resolveTerrainPalette`/`TERRAIN_MATERIAL_PALETTES` in `world/terrain`) sets the default low/high/waterline colors; `colors: { low?, high?, waterline? }` overrides any of them field-by-field, and `segments` tunes the render mesh's subdivision. `biomeBands: BiomeBand[]` (`{ z, fade?, material?, colors? }`) cross-fades the **ground palette along the world's z axis** — ordered zones (vale → marsh → peaks) that blend across a `fade`-wide window (default 64) centered on each boundary; it's the linear counterpart to the radial `materialRegions` (which paint on top; each region is a `TerrainCircleRegion` (default), `TerrainPolylineRegion` (roads/rivers), or `TerrainRectRegion` (rotatable districts), sharing a `TerrainRegionStyle`). Pure/testable via `createTerrainPaletteSampler`/`createBiomeBandSampler` in `world/terrain`; the shell renders it through the same per-vertex `paletteAt` seam as regions, no extra wiring. (Per-zone **fog/sky** cross-fade isn't wired yet — three.js fog is scene-global — so bands drive ground color today.) `flatten: TerrainFlattenMask[]` (`{ center, radius, height?, falloff? }`) carves explicit flat circles into the noise field independent of pads — building foundations, spawn circles, roads — blending back to the noise height over `falloff` (default `radius * 0.5`). `detail: TerrainDetailConfig` (`{ rockColor?, sandColor?, snowColor?, rockSlopeStart?, snowHeight?, waterLevel?, detailScale?, macroScale?, roughness?, strength? }`) swaps the flat vertex-colour ground for a procedural noise shader (`@jgengine/shell`'s `createTerrainDetailMaterial`, defaults via `resolveTerrainDetail`): it keeps the biome-tinted base and blends noise-broken **rock by slope**, **sand by waterline**, and **snow by height** over it — textured-reading terrain with no image assets, full PBR (lit/shadowed/fogged), composing with the post-processing chain. Omit for the flat look. `detail.material: { maps, repeat?, strength? }` (`TerrainDetailMaterialConfig`) is the real-texture seam: `maps`
|
|
80
|
+
`terrain()`'s `material` (a named palette — `"grass" | "sand" | "snow" | "rock" | "ash"`, resolved via `resolveTerrainPalette`/`TERRAIN_MATERIAL_PALETTES` in `world/terrain`) sets the default low/high/waterline colors; `colors: { low?, high?, waterline? }` overrides any of them field-by-field, and `segments` tunes the render mesh's subdivision. `biomeBands: BiomeBand[]` (`{ z, fade?, material?, colors? }`) cross-fades the **ground palette along the world's z axis** — ordered zones (vale → marsh → peaks) that blend across a `fade`-wide window (default 64) centered on each boundary; it's the linear counterpart to the radial `materialRegions` (which paint on top; each region is a `TerrainCircleRegion` (default), `TerrainPolylineRegion` (roads/rivers), or `TerrainRectRegion` (rotatable districts), sharing a `TerrainRegionStyle`). Pure/testable via `createTerrainPaletteSampler`/`createBiomeBandSampler` in `world/terrain`; the shell renders it through the same per-vertex `paletteAt` seam as regions, no extra wiring. (Per-zone **fog/sky** cross-fade isn't wired yet — three.js fog is scene-global — so bands drive ground color today.) `flatten: TerrainFlattenMask[]` (`{ center, radius, height?, falloff? }`) carves explicit flat circles into the noise field independent of pads — building foundations, spawn circles, roads — blending back to the noise height over `falloff` (default `radius * 0.5`). `detail: TerrainDetailConfig` (`{ rockColor?, sandColor?, snowColor?, rockSlopeStart?, snowHeight?, waterLevel?, detailScale?, macroScale?, roughness?, strength? }`) swaps the flat vertex-colour ground for a procedural noise shader (`@jgengine/shell`'s `createTerrainDetailMaterial`, defaults via `resolveTerrainDetail`): it keeps the biome-tinted base and blends noise-broken **rock by slope**, **sand by waterline**, and **snow by height** over it — textured-reading terrain with no image assets, full PBR (lit/shadowed/fogged), composing with the post-processing chain. Omit for the flat look. `detail.material: { maps, repeat?, strength?, tint? }` (`TerrainDetailMaterialConfig`) is the real-texture seam: `maps` comes from `libraryTerrainMaterial(name).maps` in `@jgengine/assets` (or any URLs with the same `{ color, normal, roughness?, ao?, displacement? }` shape; nothing samples `displacement`, so omit it) - `resolveTerrainDetail` fills `repeat` (world units per tile, default 4), `strength` (blend 0..1, default 1) and `tint` (how much of the scan's own colour replaces the authored ground colour, default 1). The shared library's macro ground entries (`foliage/grass-macro`, `ground/dirt-macro`) exist for cameras 50 m and out, where a 2 m tile averages to one flat colour; `libraryTerrainMaterial` carries their tile size and their grain-only tint for you., and the shell's `createTerrainDetailMaterial(detail, textures)` tiles the loaded color/roughness/ao/normal by world position and blends them over the same procedural rock/sand/snow result, never replacing it. The shell resolves the map URLs to `THREE.Texture`s itself (`useTexture`/`useLoader`, e.g. in the game's `environment` component) — `core` only ever sees the URL strings. Detail noise is coordinate-stable at any world position — the fragment hash is integer/position-based, so `detailScale` keeps full fine detail far from origin (a playfield at `|x|≈550` reads the same as one near origin; the old sin-hash flattened past `worldPos/scale ≳ 100`). `sky({ preset?, timeOfDay?, horizonColor?, zenithColor?, sunIntensity?, ambientIntensity?, radius?, hazeStrength?, sunGlowStrength?, fog? })` — `preset: "day" | "dusk" | "night"` (default `"day"`) is the static look; `timeOfDay: true` instead drives sun position, sky colors, and fog from the world clock's `calendar().dayFraction` every frame (`@jgengine/shell`'s `TimeOfDayDaylight` mounts this automatically for an `environment()` world with `sky` set — no per-game render wiring). The sky dome renders a bright HDR sun disc + warm glow (aligned to the sun light, so it blooms through a `postProcessing` chain), a mid-sky procedural cloud band, and a horizon haze — no HDRI assets; richer than a flat gradient with zero config. The dome **follows the camera every frame** (XZ), so it never leaves the viewer — but its `radius` (default 260) still has to clear your `fog.far` and any far scenery; a playfield sited far from the origin (or with distant landmarks) sets `radius` up to a few thousand so nothing pokes through the shell. `hazeStrength` (default 0.5) sizes the dusty horizon band and `sunGlowStrength` (default 1) the bloom around the sun disc — leave both unset for the stock look, raise/lower to dial a dusk/dust-storm horizon (the-robots uses `radius: 2600, hazeStrength: 0.62, sunGlowStrength: 0.6`). No game needs its own sky-dome mesh; configure `sky()` instead. **Gotcha:** `sunIntensity` / `ambientIntensity` are honored only on the **day** keyframe (`daylightCycle` builds dusk/night/dawn from fixed constants). Raising intensities under `preset: "dusk"` or `"night"` does nothing — use `preset: "day"` plus warm `horizonColor`/`zenithColor` when the first screenshot must be bright and readable (see `jgengine`'s first-shot art recipe).
|
|
81
81
|
|
|
82
82
|
**Look presets.** Unset `look` is `"neutral"`; `defineGame({ look: "cinematic" | "photoreal" | "comic" | ... })` gives a 3D game a real sky + shadow-casting sun/hemisphere rig + a tuned post stack. The preset **defers to a world that declares its own `sky`** — an `environment({ sky })` world keeps exactly the dome/time-of-day/lighting it configured; the preset only supplies a sky (and the post stack) to worlds that have none. So `sky()` tuning below still owns the look for scenes that set it. Full rig + resolver: `jgengine-ui` → "Rendering".
|
|
83
83
|
|