@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.
Files changed (143) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/dist/ai/policeResponse.d.ts +253 -0
  3. package/dist/ai/policeResponse.js +421 -0
  4. package/dist/anim/actionLayer.d.ts +47 -0
  5. package/dist/anim/actionLayer.js +63 -0
  6. package/dist/anim/ikSolver.d.ts +14 -0
  7. package/dist/anim/ikSolver.js +37 -0
  8. package/dist/anim/locomotionGraph.d.ts +58 -3
  9. package/dist/anim/locomotionGraph.js +142 -15
  10. package/dist/anim/ragdollRig.d.ts +53 -0
  11. package/dist/anim/ragdollRig.js +132 -0
  12. package/dist/anim/skeletonStandard.d.ts +140 -0
  13. package/dist/anim/skeletonStandard.js +326 -0
  14. package/dist/combat/damageRuntime.d.ts +14 -1
  15. package/dist/combat/damageRuntime.js +62 -4
  16. package/dist/combat/death.js +7 -3
  17. package/dist/combat/deathReason.d.ts +7 -1
  18. package/dist/combat/effects.d.ts +25 -0
  19. package/dist/combat/effects.js +16 -0
  20. package/dist/combat/statusRuntime.d.ts +2 -0
  21. package/dist/combat/statusRuntime.js +1 -0
  22. package/dist/economy/loanBook.d.ts +182 -0
  23. package/dist/economy/loanBook.js +303 -0
  24. package/dist/economy/market.d.ts +161 -0
  25. package/dist/economy/market.js +248 -0
  26. package/dist/economy/transactionLedger.d.ts +162 -0
  27. package/dist/economy/transactionLedger.js +301 -0
  28. package/dist/editor/creatorStorage.js +3 -1
  29. package/dist/editor/simulation.d.ts +5 -2
  30. package/dist/editor/simulation.js +29 -19
  31. package/dist/game/cameraConfig.d.ts +14 -0
  32. package/dist/game/clipRoles.d.ts +5 -4
  33. package/dist/game/clipRoles.js +34 -4
  34. package/dist/game/events.d.ts +27 -0
  35. package/dist/game/gameSave.d.ts +142 -0
  36. package/dist/game/gameSave.js +174 -0
  37. package/dist/game/playableGame.d.ts +74 -6
  38. package/dist/game/saveSlotIndex.d.ts +54 -0
  39. package/dist/game/saveSlotIndex.js +71 -0
  40. package/dist/game/saveSlots.d.ts +2 -0
  41. package/dist/game/saveSlots.js +1 -1
  42. package/dist/game/saveStore.d.ts +8 -0
  43. package/dist/game/saveStore.js +16 -0
  44. package/dist/gameplay.d.ts +17 -2
  45. package/dist/gameplay.js +15 -0
  46. package/dist/inventory/carryLoad.d.ts +48 -0
  47. package/dist/inventory/carryLoad.js +44 -0
  48. package/dist/inventory/equipment.d.ts +95 -0
  49. package/dist/inventory/equipment.js +169 -0
  50. package/dist/inventory/inventoryModel.d.ts +15 -6
  51. package/dist/inventory/inventoryModel.js +54 -5
  52. package/dist/inventory/shapedGrid.d.ts +26 -0
  53. package/dist/inventory/shapedGrid.js +47 -0
  54. package/dist/loot/lootGen.d.ts +186 -0
  55. package/dist/loot/lootGen.js +437 -0
  56. package/dist/loot/lootWeapon.d.ts +35 -0
  57. package/dist/loot/lootWeapon.js +70 -0
  58. package/dist/meta/changelog.d.ts +1 -1
  59. package/dist/meta/changelog.js +156 -1
  60. package/dist/mission/compositions.d.ts +85 -0
  61. package/dist/mission/compositions.js +130 -0
  62. package/dist/mission/mission.d.ts +435 -0
  63. package/dist/mission/mission.js +770 -0
  64. package/dist/mission/triggers.d.ts +61 -0
  65. package/dist/mission/triggers.js +108 -0
  66. package/dist/nav/laneGraph.d.ts +178 -0
  67. package/dist/nav/laneGraph.js +671 -0
  68. package/dist/nav/traffic.d.ts +222 -0
  69. package/dist/nav/traffic.js +918 -0
  70. package/dist/physics/kinematicVehicle.d.ts +36 -0
  71. package/dist/physics/kinematicVehicle.js +15 -1
  72. package/dist/physics/vehicleDamage.d.ts +136 -0
  73. package/dist/physics/vehicleDamage.js +214 -0
  74. package/dist/physics/vehicleParts.d.ts +62 -0
  75. package/dist/physics/vehicleParts.js +65 -0
  76. package/dist/progression/gearScore.d.ts +56 -0
  77. package/dist/progression/gearScore.js +59 -0
  78. package/dist/progression/skillTree.d.ts +129 -0
  79. package/dist/progression/skillTree.js +265 -0
  80. package/dist/progression/xpCurve.d.ts +60 -0
  81. package/dist/progression/xpCurve.js +141 -0
  82. package/dist/runtime/context/combat.js +11 -0
  83. package/dist/scene/builtinSceneKinds.js +2 -0
  84. package/dist/settings/accessibilitySettings.d.ts +81 -0
  85. package/dist/settings/accessibilitySettings.js +152 -0
  86. package/dist/settings/graphicsProfile.d.ts +3 -0
  87. package/dist/settings/graphicsProfile.js +4 -0
  88. package/dist/settings/settingsModel.d.ts +1 -1
  89. package/dist/settings/settingsModel.js +1 -0
  90. package/dist/sim/nearAgents.d.ts +8 -1
  91. package/dist/sim/nearAgents.js +34 -17
  92. package/dist/sim/offScreen.d.ts +2 -1
  93. package/dist/sim/offScreen.js +14 -3
  94. package/dist/sim/population.d.ts +147 -0
  95. package/dist/sim/population.js +182 -0
  96. package/dist/sim/queue.d.ts +90 -0
  97. package/dist/sim/queue.js +171 -0
  98. package/dist/sim/rides.d.ts +287 -0
  99. package/dist/sim/rides.js +471 -0
  100. package/dist/sim/routine.js +19 -11
  101. package/dist/ui/loadingTips.d.ts +41 -0
  102. package/dist/ui/loadingTips.js +64 -0
  103. package/dist/ui/menuNav.d.ts +79 -0
  104. package/dist/ui/menuNav.js +147 -0
  105. package/dist/ui/menuTransition.d.ts +50 -0
  106. package/dist/ui/menuTransition.js +67 -0
  107. package/dist/ui.d.ts +2 -0
  108. package/dist/ui.js +2 -0
  109. package/dist/vfx/effectLibrary.d.ts +98 -0
  110. package/dist/vfx/effectLibrary.js +181 -0
  111. package/dist/vfx/particleBudget.d.ts +40 -0
  112. package/dist/vfx/particleBudget.js +39 -0
  113. package/dist/vfx/particleDirector.d.ts +16 -0
  114. package/dist/vfx/particleDirector.js +18 -0
  115. package/dist/world/authoredSimulation.js +7 -1
  116. package/dist/world/captureCamera.d.ts +30 -0
  117. package/dist/world/captureCamera.js +19 -0
  118. package/dist/world/decalKind.d.ts +48 -0
  119. package/dist/world/decalKind.js +86 -0
  120. package/dist/world/features.d.ts +18 -5
  121. package/dist/world/features.js +1 -0
  122. package/dist/world/grassKind.d.ts +5 -0
  123. package/dist/world/grassKind.js +3 -0
  124. package/dist/world/terraform.d.ts +8 -0
  125. package/dist/world/terrain.d.ts +1 -0
  126. package/dist/world/terrain.js +2 -0
  127. package/dist/world.d.ts +7 -2
  128. package/dist/world.js +5 -0
  129. package/package.json +1 -1
  130. package/skills/jgengine/api.md +2 -2
  131. package/skills/jgengine-combat/SKILL.md +9 -1
  132. package/skills/jgengine-combat/api.md +5 -4
  133. package/skills/jgengine-combat/reference.md +4 -0
  134. package/skills/jgengine-gameplay/SKILL.md +86 -0
  135. package/skills/jgengine-gameplay/api.md +485 -16
  136. package/skills/jgengine-gameplay/capabilities.md +131 -4
  137. package/skills/jgengine-gameplay/reference-systems.md +118 -0
  138. package/skills/jgengine-world/SKILL.md +8 -0
  139. package/skills/jgengine-world/api.md +243 -11
  140. package/skills/jgengine-world/capabilities.md +61 -1
  141. package/skills/jgengine-world/recipes/character-animation.md +25 -2
  142. package/skills/jgengine-world/recipes/vehicle-feel.md +5 -0
  143. package/skills/jgengine-world/reference.md +1 -1
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The graphics-tier particle budget. A tier caps the live particle count across every effect on
3
+ * screen; when the requested effects do not fit, whole effects are dropped in a defined order
4
+ * (lowest {@link ParticleBudgetEntry.priority} first) rather than every effect thinning out.
5
+ */
6
+ /** One effect asking for particle capacity this frame. */
7
+ export interface ParticleBudgetEntry {
8
+ /** Emitter or burst id; unique within the request. */
9
+ id: string;
10
+ /** Drop order: higher survives, lower is dropped first. */
11
+ priority: number;
12
+ /** Particles this effect wants (its pool maximum, or a burst's count). */
13
+ requested: number;
14
+ }
15
+ /** An admitted effect and the particles it may hold. */
16
+ export interface ParticleBudgetGrant {
17
+ id: string;
18
+ particles: number;
19
+ }
20
+ /** What the tier cap admits, and what it drops. */
21
+ export interface ParticleBudgetPlan {
22
+ /** Admitted effects in priority order, each with its granted particle count. */
23
+ grants: ParticleBudgetGrant[];
24
+ /** Dropped effect ids, lowest priority first — the defined drop order. */
25
+ dropped: string[];
26
+ /** Sum of the granted particle counts, never above the cap. */
27
+ particles: number;
28
+ }
29
+ /**
30
+ * Allocate a tier's particle cap across the effects that want it.
31
+ *
32
+ * Effects are visited highest priority first (ties keep authoring order) and each takes
33
+ * `min(requested, remaining)`. An effect that receives nothing is dropped, so the drop list comes
34
+ * out lowest priority first: ambient dressing (dust, weather) yields before gameplay-loud effects
35
+ * (explosions, muzzle flashes). The granted total never exceeds the cap, so the live particle
36
+ * count cannot either.
37
+ *
38
+ * @capability plan-particle-budget cap live particles to a graphics tier and drop whole effects lowest-priority-first when over budget
39
+ */
40
+ export declare function planParticleBudget(entries: readonly ParticleBudgetEntry[], cap: number): ParticleBudgetPlan;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The graphics-tier particle budget. A tier caps the live particle count across every effect on
3
+ * screen; when the requested effects do not fit, whole effects are dropped in a defined order
4
+ * (lowest {@link ParticleBudgetEntry.priority} first) rather than every effect thinning out.
5
+ */
6
+ function count(value, fallback = 0) {
7
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : fallback;
8
+ }
9
+ /**
10
+ * Allocate a tier's particle cap across the effects that want it.
11
+ *
12
+ * Effects are visited highest priority first (ties keep authoring order) and each takes
13
+ * `min(requested, remaining)`. An effect that receives nothing is dropped, so the drop list comes
14
+ * out lowest priority first: ambient dressing (dust, weather) yields before gameplay-loud effects
15
+ * (explosions, muzzle flashes). The granted total never exceeds the cap, so the live particle
16
+ * count cannot either.
17
+ *
18
+ * @capability plan-particle-budget cap live particles to a graphics tier and drop whole effects lowest-priority-first when over budget
19
+ */
20
+ export function planParticleBudget(entries, cap) {
21
+ const limit = count(cap);
22
+ const ordered = entries
23
+ .map((entry, index) => ({ entry, index, priority: Number.isFinite(entry.priority) ? entry.priority : 0, wanted: count(entry.requested) }))
24
+ .sort((a, b) => (b.priority - a.priority) || (a.index - b.index));
25
+ const grants = [];
26
+ const dropped = [];
27
+ let remaining = limit;
28
+ for (const { entry, wanted } of ordered) {
29
+ const particles = Math.min(wanted, remaining);
30
+ if (particles <= 0) {
31
+ dropped.push(entry.id);
32
+ continue;
33
+ }
34
+ grants.push({ id: entry.id, particles });
35
+ remaining -= particles;
36
+ }
37
+ dropped.reverse();
38
+ return { grants, dropped, particles: limit - remaining };
39
+ }
@@ -1,3 +1,4 @@
1
+ import { type EffectOverride } from "./effectLibrary.js";
1
2
  import { type EmitterConfig, type ParticleEvent, type Vec3 } from "./particles.js";
2
3
  /** How a particle effect composites on screen: `additive` for sparks/fire/glow, `normal` for smoke/dust. */
3
4
  export type ParticleBlendHint = "additive" | "normal";
@@ -17,6 +18,8 @@ export interface ParticleBurst {
17
18
  blending?: ParticleBlendHint;
18
19
  emitterId?: string;
19
20
  options?: ParticleAttachOptions;
21
+ /** Graphics-tier drop order; higher survives. Default 0. */
22
+ priority?: number;
20
23
  }
21
24
  /** A bounded collision child effect; children cannot spawn further collision children. */
22
25
  export interface ParticleCollisionEffect {
@@ -43,6 +46,8 @@ export interface ParticleAttachOptions {
43
46
  maxAcceleration?: number;
44
47
  mask?: number;
45
48
  };
49
+ /** Graphics-tier drop order: higher survives, lower is dropped first. Default 0. */
50
+ priority?: number;
46
51
  }
47
52
  /** A keyed continuous emitter owned by the director until detached. */
48
53
  export interface ParticleEmitterSpec extends ParticleAttachOptions {
@@ -52,6 +57,11 @@ export interface ParticleEmitterSpec extends ParticleAttachOptions {
52
57
  offset?: Vec3;
53
58
  blending?: ParticleBlendHint;
54
59
  }
60
+ /** A library effect played by name: where it plays, plus per-call overrides. */
61
+ export interface EffectRequest extends EffectOverride {
62
+ /** Entity id a standing emitter tracks each frame (plus `offset`). */
63
+ follow?: string;
64
+ }
55
65
  /** A cosmetic simulation event, labelled with its emitting effect. */
56
66
  export interface ParticleDirectorEvent extends ParticleEvent {
57
67
  emitterId: string;
@@ -87,6 +97,12 @@ export interface ParticleDirector {
87
97
  stop(id: string): void;
88
98
  /** Emit once through a named emitter's existing pool, including when stopped. */
89
99
  burstNamed(id: string, count: number): void;
100
+ /** Play a library effect by name once, as a one-shot burst at the request's position. */
101
+ play(name: string, request?: EffectRequest): void;
102
+ /** Create or replace a standing emitter from a library effect name under `id`. */
103
+ attachEffect(id: string, name: string, request?: EffectRequest): void;
104
+ /** The named effects a caller can play, from the shared library. */
105
+ effectNames(): readonly string[];
90
106
  /** Renderer-to-game cosmetic event exchange, bounded by the director's event limit. */
91
107
  reportEvents(id: string, events: readonly ParticleEvent[]): void;
92
108
  drainEvents(): ParticleDirectorEvent[];
@@ -1,3 +1,4 @@
1
+ import { effectNames, resolveEffect } from "./effectLibrary.js";
1
2
  import { validateEmitterConfig } from "./particles.js";
2
3
  /**
3
4
  * Create the particle intent registry a `GameContext` exposes as `ctx.particles`.
@@ -24,6 +25,7 @@ export function createParticleDirector(options = {}) {
24
25
  bursts.push({ seq: nextSeq++, config: structuredClone(config), count: wanted,
25
26
  ...(blending === undefined ? {} : { blending }),
26
27
  ...(emitterId === undefined ? {} : { emitterId }),
28
+ priority: Number.isFinite(attachOptions?.priority) ? attachOptions.priority : 0,
27
29
  ...(attachOptions === undefined ? {} : { options: structuredClone(attachOptions) }) });
28
30
  notify();
29
31
  }
@@ -64,6 +66,20 @@ export function createParticleDirector(options = {}) {
64
66
  const { id: _, config, ...options } = spec;
65
67
  queue(config, count, spec.blending, id, options);
66
68
  },
69
+ play(name, request) {
70
+ const effect = resolveEffect(name, request);
71
+ const follow = request?.follow ?? effect.options.follow;
72
+ queue(effect.config, effect.burst, effect.blending, undefined, { ...effect.options, ...(follow === undefined ? {} : { follow }) });
73
+ },
74
+ attachEffect(id, name, request) {
75
+ const effect = resolveEffect(name, request);
76
+ if (!emitters.has(id) && emitters.size >= maxEmitters)
77
+ throw new RangeError(`Particle emitter capacity ${maxEmitters} exceeded by ${id}`);
78
+ const follow = request?.follow ?? effect.options.follow;
79
+ emitters.set(id, { id, config: effect.config, ...effect.options, ...(follow === undefined ? {} : { follow }) });
80
+ notify();
81
+ },
82
+ effectNames,
67
83
  reportEvents(id, next) {
68
84
  for (let i = 0, n = Math.min(next.length, maxEvents - events.length); i < n; i++) {
69
85
  events.push({ ...structuredClone(next[i]), emitterId: id });
@@ -129,6 +145,8 @@ export function validateParticleAttachOptions(value) {
129
145
  errors.push("space must be world or local");
130
146
  if (options.active !== undefined && typeof options.active !== "boolean")
131
147
  errors.push("active must be boolean");
148
+ if (options.priority !== undefined && (typeof options.priority !== "number" || !Number.isFinite(options.priority)))
149
+ errors.push("priority must be a finite number");
132
150
  const output = (render, blending, prefix) => {
133
151
  if (blending !== undefined && blending !== "normal" && blending !== "additive")
134
152
  errors.push(`${prefix}blending must be normal or additive`);
@@ -89,7 +89,13 @@ export function createAuthoredSimulation(options) {
89
89
  const previous = config.emitters?.find((entry) => entry.id === emitter.id);
90
90
  if (emitterIds.includes(id) && JSON.stringify(previous) === JSON.stringify(emitter))
91
91
  continue;
92
- options.particles?.attach(id, { ...emitter.config, position: [emitter.position.x, emitter.position.y, emitter.position.z] }, { ...emitter.options, ...(emitter.fireAreaId === undefined ? {} : { active: emitter.options?.active !== false && nextFires.get(emitter.fireAreaId).burning > 0 }) });
92
+ const position = [emitter.position.x, emitter.position.y, emitter.position.z];
93
+ const fire = emitter.fireAreaId === undefined ? undefined : { active: emitter.options?.active !== false && nextFires.get(emitter.fireAreaId).burning > 0 };
94
+ const request = { position, options: { ...emitter.options, ...(fire ?? {}) } };
95
+ if (emitter.effect !== undefined)
96
+ options.particles?.attachEffect(id, emitter.effect, request);
97
+ else
98
+ options.particles?.attach(id, { ...emitter.config, position }, request.options);
93
99
  }
94
100
  emitterIds = nextEmitterIds;
95
101
  document = nextDocument;
@@ -0,0 +1,30 @@
1
+ import type { CameraRigKind } from "../game/playableGame.js";
2
+ import type { CameraRigConfig } from "../runtime/cameraDirector.js";
3
+ /**
4
+ * Per-session capture-camera override — the harness counterpart of `?cam=`/`?look=`, and the same
5
+ * install-style seam as `world/spawnOverride`: the runner installs one, the shell reads it, nothing
6
+ * changes until one is installed.
7
+ *
8
+ * It exists because a game that owns its camera wins by other routes: a static `camera` config
9
+ * swapped by the harness only reaches the rig until the game selects its own rig at runtime
10
+ * (`ctx.camera.setRig`, a persisted settings camera, a director cinematic), and the capture then
11
+ * silently photographs the game's camera. This override is read above every one of those, so a
12
+ * framed capture stays framed; a game's own camera is untouched the moment the page loads without
13
+ * the param.
14
+ */
15
+ export interface CaptureCameraOverride {
16
+ kind: CameraRigKind;
17
+ config: CameraRigConfig;
18
+ }
19
+ /**
20
+ * Install (or, with `null`, clear) the capture camera. Idempotent; last write wins. It outranks the
21
+ * game's static camera config and any runtime `ctx.camera` rig selection, and it is never saved —
22
+ * it is a property of this capture session, not of the game.
23
+ *
24
+ * @capability capture force a framed capture camera over a game that owns its camera
25
+ */
26
+ export declare function installCaptureCamera(override: CaptureCameraOverride | null): void;
27
+ /** Clear any installed capture camera. */
28
+ export declare function clearCaptureCamera(): void;
29
+ /** The installed capture camera, or `null` when the game's own camera should render. */
30
+ export declare function readCaptureCamera(): CaptureCameraOverride | null;
@@ -0,0 +1,19 @@
1
+ let installed = null;
2
+ /**
3
+ * Install (or, with `null`, clear) the capture camera. Idempotent; last write wins. It outranks the
4
+ * game's static camera config and any runtime `ctx.camera` rig selection, and it is never saved —
5
+ * it is a property of this capture session, not of the game.
6
+ *
7
+ * @capability capture force a framed capture camera over a game that owns its camera
8
+ */
9
+ export function installCaptureCamera(override) {
10
+ installed = override;
11
+ }
12
+ /** Clear any installed capture camera. */
13
+ export function clearCaptureCamera() {
14
+ installed = null;
15
+ }
16
+ /** The installed capture camera, or `null` when the game's own camera should render. */
17
+ export function readCaptureCamera() {
18
+ return installed;
19
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * `decal` marker: a surface-grime decal authored as a placed marker (dirt, cracks, paint, blood).
3
+ * Its position, facing (`rotationY`), tilt (0 lies on the ground, 90 stands on a wall), size, opacity
4
+ * and seed live in the scene document, so decals are placed in the editor like any other authored
5
+ * content and a game never hardcodes a stain. Pure config here; the procedural texture and the
6
+ * draped/standing quad live in the `shell` renderer.
7
+ *
8
+ * @capability decal editor-authored surface decal
9
+ */
10
+ import { type ParamSchema, type SceneKindObject } from "../scene/sceneKinds.js";
11
+ /** The editor marker kind marking a surface decal. */
12
+ export declare const DECAL_KIND = "decal";
13
+ /** The four authored decal looks; each has its own deterministic procedural texture. */
14
+ export declare const DECAL_STYLES: readonly ["dirt", "cracks", "paint", "blood"];
15
+ /** One of the four authored decal looks. */
16
+ export type DecalStyle = (typeof DECAL_STYLES)[number];
17
+ /** Fully-defaulted decal params parsed from a marker's `meta`. */
18
+ export interface DecalRules {
19
+ style: DecalStyle;
20
+ /** Decal width/height in metres. */
21
+ size: number;
22
+ /** 0 lies flat on the ground, 90 stands on a wall; 45 leans. */
23
+ tilt: number;
24
+ /** Vertical offset of the decal centre above the placed point, in metres. */
25
+ lift: number;
26
+ /** Push along the decal's own normal, so a wall decal clears the wall it sits on. */
27
+ offset: number;
28
+ /** 0..1 overall alpha. */
29
+ opacity: number;
30
+ /** Deterministic shape seed. */
31
+ seed: string;
32
+ }
33
+ /** Default decal rules: a small dirt smudge lying on the ground. */
34
+ export declare const DECAL_DEFAULTS: DecalRules;
35
+ /** The decal parameter schema — drives the inspector and the `meta` parse. */
36
+ export declare const DECAL_SCHEMA: ParamSchema;
37
+ /** Parse a marker's `meta` into fully-defaulted decal rules. @internal */
38
+ export declare function readDecalRules(meta: Record<string, unknown> | undefined): DecalRules;
39
+ /** A resolved decal: its placed point/facing plus fully-defaulted rules. */
40
+ export interface ResolvedDecal {
41
+ position: readonly [number, number, number];
42
+ rotationY: number;
43
+ rules: DecalRules;
44
+ }
45
+ /** Resolve a decal marker into a placed, fully-defaulted decal; null without a position. @internal */
46
+ export declare function resolveDecalObject(object: SceneKindObject): ResolvedDecal | null;
47
+ /** Registers the `decal` scene kind. Called by {@link registerBuiltinSceneKinds}. @internal */
48
+ export declare function registerDecalKind(): void;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * `decal` marker: a surface-grime decal authored as a placed marker (dirt, cracks, paint, blood).
3
+ * Its position, facing (`rotationY`), tilt (0 lies on the ground, 90 stands on a wall), size, opacity
4
+ * and seed live in the scene document, so decals are placed in the editor like any other authored
5
+ * content and a game never hardcodes a stain. Pure config here; the procedural texture and the
6
+ * draped/standing quad live in the `shell` renderer.
7
+ *
8
+ * @capability decal editor-authored surface decal
9
+ */
10
+ import { registerSceneKind } from "../scene/sceneKinds.js";
11
+ /** The editor marker kind marking a surface decal. */
12
+ export const DECAL_KIND = "decal";
13
+ /** The four authored decal looks; each has its own deterministic procedural texture. */
14
+ export const DECAL_STYLES = ["dirt", "cracks", "paint", "blood"];
15
+ /** Default decal rules: a small dirt smudge lying on the ground. */
16
+ export const DECAL_DEFAULTS = {
17
+ style: "dirt",
18
+ size: 2.4,
19
+ tilt: 0,
20
+ lift: 0.02,
21
+ offset: 0.04,
22
+ opacity: 0.9,
23
+ seed: "",
24
+ };
25
+ /** The decal parameter schema — drives the inspector and the `meta` parse. */
26
+ export const DECAL_SCHEMA = {
27
+ fields: [
28
+ { type: "select", key: "style", label: "style", options: DECAL_STYLES.map((value) => ({ value })), default: DECAL_DEFAULTS.style },
29
+ { type: "range", key: "size", label: "size", min: 0.3, max: 24, step: 0.1, default: DECAL_DEFAULTS.size, unit: "m" },
30
+ { type: "range", key: "tilt", label: "tilt", min: 0, max: 90, step: 1, default: DECAL_DEFAULTS.tilt, unit: "°" },
31
+ { type: "range", key: "lift", label: "height", min: 0, max: 30, step: 0.05, default: DECAL_DEFAULTS.lift, unit: "m" },
32
+ { type: "range", key: "offset", label: "surface offset", min: 0, max: 2, step: 0.01, default: DECAL_DEFAULTS.offset, unit: "m" },
33
+ { type: "range", key: "opacity", label: "opacity", min: 0.05, max: 1, step: 0.01, default: DECAL_DEFAULTS.opacity },
34
+ { type: "seed", key: "seed", label: "seed", default: DECAL_DEFAULTS.seed },
35
+ ],
36
+ };
37
+ function number(meta, key, fallback) {
38
+ const value = meta?.[key];
39
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
40
+ }
41
+ function text(meta, key, fallback) {
42
+ const value = meta?.[key];
43
+ return typeof value === "string" ? value : fallback;
44
+ }
45
+ /** Parse a marker's `meta` into fully-defaulted decal rules. @internal */
46
+ export function readDecalRules(meta) {
47
+ const style = text(meta, "style", DECAL_DEFAULTS.style);
48
+ return {
49
+ style: DECAL_STYLES.includes(style) ? style : DECAL_DEFAULTS.style,
50
+ size: Math.min(24, Math.max(0.3, number(meta, "size", DECAL_DEFAULTS.size))),
51
+ tilt: Math.min(90, Math.max(0, number(meta, "tilt", DECAL_DEFAULTS.tilt))),
52
+ lift: Math.max(0, number(meta, "lift", DECAL_DEFAULTS.lift)),
53
+ offset: Math.max(0, number(meta, "offset", DECAL_DEFAULTS.offset)),
54
+ opacity: Math.min(1, Math.max(0.05, number(meta, "opacity", DECAL_DEFAULTS.opacity))),
55
+ seed: text(meta, "seed", DECAL_DEFAULTS.seed),
56
+ };
57
+ }
58
+ /** Resolve a decal marker into a placed, fully-defaulted decal; null without a position. @internal */
59
+ export function resolveDecalObject(object) {
60
+ const position = object.position ?? object.center;
61
+ if (position === undefined)
62
+ return null;
63
+ return {
64
+ position: [position.x, position.y, position.z],
65
+ rotationY: object.rotationY ?? 0,
66
+ rules: readDecalRules(object.meta),
67
+ };
68
+ }
69
+ /** Registers the `decal` scene kind. Called by {@link registerBuiltinSceneKinds}. @internal */
70
+ export function registerDecalKind() {
71
+ registerSceneKind({
72
+ kind: DECAL_KIND,
73
+ target: "marker",
74
+ label: "Decal",
75
+ addCategory: "Studios",
76
+ accent: "#b5793c",
77
+ schema: DECAL_SCHEMA,
78
+ resolve: (object) => resolveDecalObject(object),
79
+ note: (object) => {
80
+ const resolved = resolveDecalObject(object);
81
+ if (resolved === null)
82
+ return "Place the marker on a surface.";
83
+ return `${resolved.rules.style} · ${resolved.rules.size.toFixed(1)} m ${resolved.rules.tilt >= 45 ? "wall" : "ground"} decal`;
84
+ },
85
+ });
86
+ }
@@ -70,16 +70,20 @@ export interface TerrainDetailConfig {
70
70
  }
71
71
  /**
72
72
  * PBR map URLs for a real ground texture — the same shape
73
- * `buildMaterialCatalog({ basePath }).resolve(id)!.maps` from `@jgengine/assets` returns. Kept
74
- * dependency-free here so `core` never imports the assets package; any URLs (pulled maps, a CDN, a
75
- * data URI) satisfy it. Omitted roughness keeps the terrain scalar; omitted AO skips occlusion sampling.
73
+ * `libraryTerrainMaterial(name).maps` from `@jgengine/assets` returns. Kept dependency-free here so
74
+ * `core` never imports the assets package; any URLs (pulled maps, a CDN, a data URI) satisfy it.
75
+ * Omitted roughness keeps the terrain scalar; omitted AO skips occlusion sampling.
76
76
  */
77
77
  export interface TerrainMaterialMaps {
78
78
  color: string;
79
79
  normal: string;
80
80
  roughness?: string;
81
81
  ao?: string;
82
- displacement: string;
82
+ /**
83
+ * Height map for callers that displace geometry themselves. The terrain detail shader never
84
+ * samples it, so pass the URL only when the pulled set actually ships one — never invent it.
85
+ */
86
+ displacement?: string;
83
87
  }
84
88
  /**
85
89
  * Real PBR texture applied over the ground surface — the seam that lets a game put a
@@ -102,6 +106,13 @@ export interface TerrainDetailMaterialConfig {
102
106
  * hand-tinted world wants from a stock PBR set.
103
107
  */
104
108
  tint?: number;
109
+ /**
110
+ * What a failed map load does. Scanned maps are pulled by a game's asset step, not committed, so a
111
+ * checkout without them looks exactly like a broken one. `"procedural"` (default) keeps the
112
+ * authored procedural ground and logs one warning naming the map; `"fail"` hands the error to the
113
+ * presentation retry screen, which is what a view that cannot render without the scan asks for.
114
+ */
115
+ missing?: "procedural" | "fail";
105
116
  }
106
117
  export interface TerrainFlattenMask {
107
118
  center: EnvironmentVec2;
@@ -288,6 +299,8 @@ export interface GrassEnvironmentConfig {
288
299
  colorVariation?: number;
289
300
  /** 0..1 blend of blade normals toward straight-up (shell default 0.6). Low values light each blade plane individually and go dark when side-lit; high values light the field like a soft carpet. */
290
301
  normalLift?: number;
302
+ /** 0..1 strength of the turf ground-cover mat painted under the blades (shell default 0.9); 0 leaves the ground bare. */
303
+ groundCover?: number;
291
304
  }
292
305
  export interface OceanEnvironmentConfig {
293
306
  bounds?: WorldBounds;
@@ -459,7 +472,7 @@ export type SnowEnvironmentDescriptor = {
459
472
  } & Required<Pick<SnowEnvironmentConfig, "area" | "density" | "speed" | "flakeSize" | "drift" | "wind" | "color" | "opacity">>;
460
473
  export type GrassEnvironmentDescriptor = {
461
474
  kind: "grass";
462
- } & Required<Pick<GrassEnvironmentConfig, "area" | "density" | "bladeHeight" | "bladeWidth" | "windStrength" | "colors">> & Pick<GrassEnvironmentConfig, "seed" | "bladeBend" | "tuftRadius" | "colorVariation" | "normalLift">;
475
+ } & Required<Pick<GrassEnvironmentConfig, "area" | "density" | "bladeHeight" | "bladeWidth" | "windStrength" | "colors">> & Pick<GrassEnvironmentConfig, "seed" | "bladeBend" | "tuftRadius" | "colorVariation" | "normalLift" | "groundCover">;
463
476
  export type OceanEnvironmentDescriptor = {
464
477
  kind: "ocean";
465
478
  } & Required<Pick<OceanEnvironmentConfig, "bounds" | "level" | "waveHeight" | "waveScale" | "waveSpeed" | "color">> & Pick<OceanEnvironmentConfig, "position" | "levelAt">;
@@ -201,6 +201,7 @@ export function grass(config = {}) {
201
201
  ...(config.tuftRadius === undefined ? {} : { tuftRadius: config.tuftRadius }),
202
202
  ...(config.colorVariation === undefined ? {} : { colorVariation: config.colorVariation }),
203
203
  ...(config.normalLift === undefined ? {} : { normalLift: config.normalLift }),
204
+ ...(config.groundCover === undefined ? {} : { groundCover: config.groundCover }),
204
205
  };
205
206
  }
206
207
  /** Declares an ocean water body for `environment()` — bounds, level, and wave tuning. */
@@ -40,6 +40,11 @@ export interface GrassRules {
40
40
  windGust: number;
41
41
  /** Blade material roughness, 0..1. */
42
42
  roughness: number;
43
+ /**
44
+ * Ground-cover mat strength, 0..1: the patch paints a turf surface over its footprint instead of
45
+ * leaving the ground beneath the blades bare. 0 keeps blades only.
46
+ */
47
+ groundCover: number;
43
48
  /** Seed string; same seed reproduces the same blade layout. */
44
49
  seed: string;
45
50
  }
@@ -28,6 +28,7 @@ export const GRASS_DEFAULTS = {
28
28
  windSpeed: 1.6,
29
29
  windGust: 0.16,
30
30
  roughness: 0.85,
31
+ groundCover: 0.9,
31
32
  seed: "",
32
33
  };
33
34
  /** The grass parameter schema — drives the inspector and `meta` parse via the studio seam. */
@@ -48,6 +49,7 @@ export const GRASS_SCHEMA = {
48
49
  { type: "range", key: "windSpeed", label: "wind speed", min: 0, max: 4, step: 0.05, default: GRASS_DEFAULTS.windSpeed },
49
50
  { type: "range", key: "windGust", label: "gust scale", min: 0, max: 1, step: 0.02, default: GRASS_DEFAULTS.windGust },
50
51
  { type: "range", key: "roughness", label: "roughness", min: 0, max: 1, step: 0.02, default: GRASS_DEFAULTS.roughness },
52
+ { type: "range", key: "groundCover", label: "ground cover", min: 0, max: 1, step: 0.02, default: GRASS_DEFAULTS.groundCover },
51
53
  { type: "seed", key: "seed", label: "seed", default: "" },
52
54
  ],
53
55
  };
@@ -81,6 +83,7 @@ export function readGrassRules(meta) {
81
83
  windSpeed: metaNumber(meta, "windSpeed", GRASS_DEFAULTS.windSpeed),
82
84
  windGust: metaNumber(meta, "windGust", GRASS_DEFAULTS.windGust),
83
85
  roughness: metaNumber(meta, "roughness", GRASS_DEFAULTS.roughness),
86
+ groundCover: Math.max(0, Math.min(1, metaNumber(meta, "groundCover", GRASS_DEFAULTS.groundCover))),
84
87
  seed: metaString(meta, "seed", GRASS_DEFAULTS.seed),
85
88
  };
86
89
  }
@@ -57,6 +57,14 @@ export interface TerrainMaterialLayer {
57
57
  tint?: string;
58
58
  /** Layer master opacity 0..1 — scales this layer's blend weight everywhere. */
59
59
  opacity?: number;
60
+ /**
61
+ * Material library name (e.g. `"foliage/grass"`) whose scanned PBR color map paints this layer,
62
+ * as authored. The editor resolves it into {@link TerrainMaterialLayer.materialUrl} so the runtime
63
+ * needs no asset-library lookup.
64
+ */
65
+ material?: string;
66
+ /** Resolved URL of the layer's scanned color map, written by the editor from {@link TerrainMaterialLayer.material}. */
67
+ materialUrl?: string;
60
68
  }
61
69
  export interface TerraformSnapshot {
62
70
  bounds: Aabb;
@@ -201,6 +201,7 @@ export interface ResolvedTerrainDetailMaterial {
201
201
  repeat: number;
202
202
  strength: number;
203
203
  tint: number;
204
+ missing: "procedural" | "fail";
204
205
  }
205
206
  /** A {@link TerrainDetailConfig} with every field resolved to a concrete value — the shape the shell's detail material consumes. */
206
207
  export type ResolvedTerrainDetail = Required<Omit<TerrainDetailConfig, "waterLevel" | "material" | "sweeps">> & {
@@ -487,6 +487,7 @@ const IMPLICIT_TERRAIN_DETAIL = {
487
487
  const DEFAULT_TERRAIN_MATERIAL_REPEAT = 4;
488
488
  const DEFAULT_TERRAIN_MATERIAL_STRENGTH = 1;
489
489
  const DEFAULT_TERRAIN_MATERIAL_TINT = 1;
490
+ const DEFAULT_TERRAIN_MATERIAL_MISSING = "procedural";
490
491
  /** Sand-band level for a terrain with no water: far below any ground, so no beach is painted. */
491
492
  const NO_WATER_SAND_LEVEL = -1e6;
492
493
  /** Fill a `TerrainDetailConfig` with defaults; `waterLevel` falls back to the terrain's own water level,
@@ -520,6 +521,7 @@ export function resolveTerrainDetail(config, terrainWaterLevel) {
520
521
  repeat: config.material.repeat ?? DEFAULT_TERRAIN_MATERIAL_REPEAT,
521
522
  tint: clamp01(config.material.tint ?? DEFAULT_TERRAIN_MATERIAL_TINT),
522
523
  strength: clamp01(config.material.strength ?? DEFAULT_TERRAIN_MATERIAL_STRENGTH),
524
+ missing: config.material.missing ?? DEFAULT_TERRAIN_MATERIAL_MISSING,
523
525
  },
524
526
  }),
525
527
  };
package/dist/world.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- export { type HeatConfig, type HeatGain, type HeatLevelDef, type HeatState } from "./ai/heatSystem.js";
1
+ export { type HeatConfig, type HeatGain, type HeatLevelDef, type HeatPoint, type HeatState } from "./ai/heatSystem.js";
2
2
  export { advanceInterestGate, createInterestCensus, createInterestGateState, interestPhase, type InterestCensus, type InterestCensusAccumulator, type InterestGateInput, type InterestGateState, type InterestGateStep, type InterestSchedulerConfig, type InterestState, type InterestTier, } from "./ai/interestScheduler.js";
3
3
  export { type Job, type JobDef, type JobReport } from "./ai/jobBoard.js";
4
+ export { DEFAULT_POLICE_TIERS, DEFAULT_POLICE_TUNING, createPoliceResponse, policeRoadsFromLaneGraph, type PoliceLaneHit, type PoliceResponse, type PoliceResponseConfig, type PoliceResponseKind, type PoliceResponseState, type PoliceRoadblockSite, type PoliceRoads, type PoliceRoadsOptions, type PoliceSearchArea, type PoliceSighting, type PoliceStats, type PoliceStep, type PoliceTickContext, type PoliceTier, type PoliceTuning, type PoliceWitness, } from "./ai/policeResponse.js";
4
5
  export { createRvoCrowd, type RvoAgentSpec, type RvoAgentView, type RvoCrowd, type RvoCrowdOptions, type RvoSnapshot, type RvoTunables, } from "./ai/rvo.js";
5
6
  export { advanceSpawnDirector, advanceWave, createSpawnDirectorState, pickSpawnPoint, raiseAlert, type SpawnDirectorConfig, type SpawnDirectorState, type SpawnEntry, type SpawnRequest, type WaveManifest, } from "./ai/spawnDirector.js";
6
7
  export { acquireTarget, createTargetAcquirer, type AcquisitionEnvelope, type AcquisitionPolicy, type AcquisitionResult, type AcquisitionRetention, type TargetAcquirer, } from "./ai/targetAcquisition.js";
@@ -31,6 +32,7 @@ export { MOVEMENT_TUNING, type CollisionObstacle } from "./movement/movementMode
31
32
  export { resolvePlayerMovementTuning, stepPlayerMovement } from "./movement/playerMovement.js";
32
33
  export { POSE_HITBOX, createPoseState, type MovementPose } from "./movement/poseState.js";
33
34
  export { steerYaw } from "./movement/steering.js";
35
+ export { createLaneGraph, laneGraphFromDocument, type LaneEdgeConfig, type LaneGraph, type LaneGraphConfig, type LaneGraphDocumentOptions, type LaneIntersection, type LaneIntersectionConfig, type LaneNode, type LanePose, type LaneRightOfWay, type LaneSignalProgram, type LaneSpan, type LaneSpot, } from "./nav/laneGraph.js";
34
36
  export { constrainToNavGrid } from "./nav/navConstrain.js";
35
37
  export { createNavCrowd, type NavCrowd, type NavCrowdAgentSpec, type NavCrowdAgentView, type NavCrowdOptions, type NavCrowdRevalidate, type NavCrowdSnapshot, type NavCrowdStats, } from "./nav/navCrowd.js";
36
38
  export { populateNavGridFromEnvironment } from "./nav/navFromEnvironment.js";
@@ -38,6 +40,7 @@ export { createNavGrid, findPath, slopeStepCost, type NavGrid, type NavPoint } f
38
40
  export { buildNavAdjacency, closestPoint, createNavMeshQuery, raycastNav, type NavMeshAdjacency, type NavMeshData, type NavMeshLink, type NavMeshPath, type NavMeshQuery, type NavMeshQueryOptions, type NavMeshQuerySnapshot, } from "./nav/navMesh.js";
39
41
  export { createNavRuntime, navMeshFromBake, type AuthoredNavLink, type NavLinkKind, type NavRebuildStats, type NavRegion, type NavRuntime, type NavRuntimeOptions, type NavRuntimeSnapshot, type SceneBake, } from "./nav/navRuntime.js";
40
42
  export { advancePathFollow, createPathFollow, pathFollowProgress, pathFollowSeek, pathFromNav, pathLength, type PathFollowConfig, type PathFollowProgress, type PathFollowState, type PathProgress, type Waypoint, } from "./nav/pathFollow.js";
43
+ export { createTraffic, type Traffic, type TrafficCarView, type TrafficOptions, type TrafficRoutePolicy, type TrafficSignalState, type TrafficSnapshot, type TrafficStats, type TrafficTuning, } from "./nav/traffic.js";
41
44
  export { defineAttackMoveOrder, defineHoldOrder, defineMoveOrder, definePatrolOrder, defineStopOrder, defineTargetedOrder, type AttackMoveOrderPayload, type EmptyOrderPayload, type EngagementKindConfig, type EngagementOrderState, type MoveOrderPayload, type OrderKindConfig, type OrderMoveResult, type OrderMover, type OrderTargeting, type PatrolOrderPayload, type PatrolOrderState, type TargetedOrderPayload, } from "./orders/orderKinds.js";
42
45
  export { createOrderQueue, createOrderRegistry, type Order, type OrderCancelReason, type OrderEvent, type OrderIssueResult, type OrderKind, type OrderOutcome, type OrderPhase, type OrderProgress, type OrderQueue, type OrderQueueOptions, type OrderQueuePolicy, type OrderQueueState, type OrderRegistry, type OrderRejection, type OrderRequest, type OrderStartResult, type OrderTickReport, type OrderVec3, } from "./orders/orderQueue.js";
43
46
  export { createBallisticSweep, type BallisticSweep, type BallisticSweepHit } from "./physics/ballisticSweep.js";
@@ -48,14 +51,16 @@ export { tickDrivableVehicle } from "./physics/drivableVehicle.js";
48
51
  export { createAircraftDynamics, type AircraftAxis, type AircraftDynamics, type AircraftKind, type AircraftOptions, type AircraftStep, type AircraftTuning, type FlightControlInput, type FlightControlRates, type FlightVector, } from "./physics/flightDynamics.js";
49
52
  export { ForceVolume, PlatformCarry } from "./physics/forceVolume.js";
50
53
  export { combineGravity, pointGravity, uniformGravity, type GravityField, type GravityVector, type PointGravityOptions, } from "./physics/gravityField.js";
51
- export { createKinematicVehicle, type KinematicChassisTuning, type KinematicDynamicsTuning, type KinematicPowertrainTuning, type KinematicSteeringTuning, type KinematicVehicle, type KinematicVehicleStep, type KinematicVehicleTuning, } from "./physics/kinematicVehicle.js";
54
+ export { createKinematicVehicle, type KinematicChassisTuning, type KinematicDynamicsTuning, type KinematicPowertrainTuning, type KinematicSteeringTuning, type KinematicVehicle, type KinematicVehicleStep, type KinematicVehicleTuning, type KinematicWeightTransferTuning, } from "./physics/kinematicVehicle.js";
52
55
  export { PhysicsWorld, SHAPE_BOX, SHAPE_SPHERE, type AddBodyOptions, type CollisionEvent, type PhysicsStats, } from "./physics/physicsWorld.js";
53
56
  export { createRagdoll } from "./physics/ragdoll.js";
54
57
  export { SpatialGrid } from "./physics/spatialGrid.js";
55
58
  export { StructureGraph, type CollapseEvent, type StructureMaterial } from "./physics/structure.js";
56
59
  export { Glide, Grapple } from "./physics/traversal.js";
57
60
  export { DEFAULT_GRIP_CURVE, createVehicleBody, sampleGripCurve, type GripCurve } from "./physics/vehicleBody.js";
61
+ export { createVehicleDamage, damagedAxis, damagedTuning, type VehicleDamage, type VehicleDamageConfig, type VehicleDamageEffect, type VehicleDamageEffectResolved, type VehicleDamageState, type VehicleDamageTransition, type VehicleDamageZone, type VehicleDamageZoneState, type VehicleDeformation, } from "./physics/vehicleDamage.js";
58
62
  export { createVehicleObstacleClamp, type VehicleImpact, type VehicleObstacleClamp, } from "./physics/vehicleObstacles.js";
63
+ export { applyVehicleParts, feelOf, withPart, type VehicleFeel, type VehiclePart, type VehiclePartEffect, type VehicleSetup, type VehicleStat, } from "./physics/vehicleParts.js";
59
64
  export { type ObjectSlotRejection, type ObjectSlotResult, type ObjectSlotTransfer, type SceneObjectSlots, } from "./runtime/context/objectSlots.js";
60
65
  export { createAssetCatalog, type AssetCatalog, type ModelAssetRef, type ModelDims } from "./scene/assetCatalog.js";
61
66
  export { partsBounds, registerAssetGenerator, type GeneratedAsset, type GeneratedPart } from "./scene/assetGenerator.js";
package/dist/world.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export {} from "./ai/heatSystem.js";
2
2
  export { advanceInterestGate, createInterestCensus, createInterestGateState, interestPhase, } from "./ai/interestScheduler.js";
3
3
  export {} from "./ai/jobBoard.js";
4
+ export { DEFAULT_POLICE_TIERS, DEFAULT_POLICE_TUNING, createPoliceResponse, policeRoadsFromLaneGraph, } from "./ai/policeResponse.js";
4
5
  export { createRvoCrowd, } from "./ai/rvo.js";
5
6
  export { advanceSpawnDirector, advanceWave, createSpawnDirectorState, pickSpawnPoint, raiseAlert, } from "./ai/spawnDirector.js";
6
7
  export { acquireTarget, createTargetAcquirer, } from "./ai/targetAcquisition.js";
@@ -31,6 +32,7 @@ export { MOVEMENT_TUNING } from "./movement/movementModel.js";
31
32
  export { resolvePlayerMovementTuning, stepPlayerMovement } from "./movement/playerMovement.js";
32
33
  export { POSE_HITBOX, createPoseState } from "./movement/poseState.js";
33
34
  export { steerYaw } from "./movement/steering.js";
35
+ export { createLaneGraph, laneGraphFromDocument, } from "./nav/laneGraph.js";
34
36
  export { constrainToNavGrid } from "./nav/navConstrain.js";
35
37
  export { createNavCrowd, } from "./nav/navCrowd.js";
36
38
  export { populateNavGridFromEnvironment } from "./nav/navFromEnvironment.js";
@@ -38,6 +40,7 @@ export { createNavGrid, findPath, slopeStepCost } from "./nav/navGrid.js";
38
40
  export { buildNavAdjacency, closestPoint, createNavMeshQuery, raycastNav, } from "./nav/navMesh.js";
39
41
  export { createNavRuntime, navMeshFromBake, } from "./nav/navRuntime.js";
40
42
  export { advancePathFollow, createPathFollow, pathFollowProgress, pathFollowSeek, pathFromNav, pathLength, } from "./nav/pathFollow.js";
43
+ export { createTraffic, } from "./nav/traffic.js";
41
44
  export { defineAttackMoveOrder, defineHoldOrder, defineMoveOrder, definePatrolOrder, defineStopOrder, defineTargetedOrder, } from "./orders/orderKinds.js";
42
45
  export { createOrderQueue, createOrderRegistry, } from "./orders/orderQueue.js";
43
46
  export { createBallisticSweep } from "./physics/ballisticSweep.js";
@@ -55,7 +58,9 @@ export { SpatialGrid } from "./physics/spatialGrid.js";
55
58
  export { StructureGraph } from "./physics/structure.js";
56
59
  export { Glide, Grapple } from "./physics/traversal.js";
57
60
  export { DEFAULT_GRIP_CURVE, createVehicleBody, sampleGripCurve } from "./physics/vehicleBody.js";
61
+ export { createVehicleDamage, damagedAxis, damagedTuning, } from "./physics/vehicleDamage.js";
58
62
  export { createVehicleObstacleClamp, } from "./physics/vehicleObstacles.js";
63
+ export { applyVehicleParts, feelOf, withPart, } from "./physics/vehicleParts.js";
59
64
  export {} from "./runtime/context/objectSlots.js";
60
65
  export { createAssetCatalog } from "./scene/assetCatalog.js";
61
66
  export { partsBounds, registerAssetGenerator } from "./scene/assetGenerator.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jgengine/core",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "jgengine core — pure-TypeScript game framework SDK (npm @jgengine/core). Entity stores, commands, combat, inventory, multiplayer seams. Zero dependencies. Not an ECS. Agent skills ship in the tarball. https://jgengine.com",
5
5
  "keywords": [
6
6
  "jgengine",
@@ -6,7 +6,7 @@
6
6
 
7
7
  - `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
8
8
  - `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
9
- - `VERSION` (const): const VERSION: "0.26.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
9
+ - `VERSION` (const): const VERSION: "0.28.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
10
10
 
11
11
  ## @jgengine/core/authoring
12
12
 
@@ -285,7 +285,7 @@
285
285
 
286
286
  - `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
287
287
  - `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
288
- - `VERSION` (const): const VERSION: "0.26.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
288
+ - `VERSION` (const): const VERSION: "0.28.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
289
289
 
290
290
  ## @jgengine/core/runtime/adapter
291
291