@jgengine/core 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/combat/effects.d.ts +11 -4
- package/dist/combat/effects.js +24 -17
- package/dist/combat/magazine.d.ts +13 -0
- package/dist/combat/magazine.js +27 -0
- package/dist/combat/vfxPresets.d.ts +176 -0
- package/dist/combat/vfxPresets.js +74 -0
- package/dist/combat/weaponFire.d.ts +114 -0
- package/dist/combat/weaponFire.js +96 -0
- package/dist/combat.d.ts +4 -2
- package/dist/combat.js +3 -1
- package/dist/devtools/urlFlags.d.ts +22 -0
- package/dist/devtools/urlFlags.js +62 -0
- package/dist/game/clipRoles.d.ts +51 -0
- package/dist/game/clipRoles.js +142 -0
- package/dist/game/defineGame.d.ts +2 -2
- package/dist/game/partAnimation.d.ts +78 -0
- package/dist/game/partAnimation.js +147 -0
- package/dist/game/playableGame.d.ts +38 -5
- package/dist/game/progression.d.ts +2 -2
- package/dist/game/progression.js +4 -2
- package/dist/inventory/inventoryModel.d.ts +24 -8
- package/dist/inventory/inventoryModel.js +47 -8
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +25 -1
- package/dist/runtime/context/combat.js +2 -1
- package/dist/runtime/context/combatFx.js +15 -5
- package/dist/runtime/context/scene.d.ts +2 -1
- package/dist/runtime/context/scene.js +39 -1
- package/dist/runtime/gameContext.js +50 -4
- package/dist/runtime/gameContextTypes.d.ts +37 -4
- package/dist/runtime/headlessRunner.d.ts +13 -0
- package/dist/runtime/headlessRunner.js +14 -0
- package/dist/scene/assetCatalog.d.ts +2 -0
- package/dist/scene/colliders.d.ts +21 -0
- package/dist/scene/colliders.js +62 -0
- package/dist/scene/entityStats.js +9 -1
- package/dist/stats/statModifiers.d.ts +16 -0
- package/dist/stats/statModifiers.js +30 -1
- package/dist/stats/statPool.js +5 -2
- package/dist/ui/panelModel.d.ts +120 -0
- package/dist/ui/panelModel.js +168 -0
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +1 -0
- package/dist/world/buildingLots.d.ts +84 -0
- package/dist/world/buildingLots.js +119 -0
- package/dist/world/buildings.d.ts +6 -0
- package/dist/world/cityGenerator.d.ts +41 -0
- package/dist/world/cityGenerator.js +58 -0
- package/dist/world/cityKind.d.ts +2 -2
- package/dist/world/cityKind.js +4 -4
- package/dist/world/environmentSummary.js +57 -1
- package/dist/world/features.d.ts +35 -2
- package/dist/world/features.js +5 -2
- package/dist/world/roads.d.ts +19 -2
- package/dist/world/roads.js +60 -3
- package/dist/world/{pathNetwork.d.ts → streetGenerator.d.ts} +37 -37
- package/dist/world/{pathNetwork.js → streetGenerator.js} +9 -9
- package/dist/world/streets.d.ts +74 -1
- package/dist/world/streets.js +218 -0
- package/dist/world/terrain.d.ts +20 -1
- package/dist/world/terrain.js +41 -2
- package/package.json +1 -1
- package/skills/jgengine/SKILL.md +1 -0
- package/skills/jgengine/api.md +13 -5
- package/skills/jgengine-combat/SKILL.md +15 -1
- package/skills/jgengine-combat/api.md +39 -2
- package/skills/jgengine-combat/capabilities.md +17 -1
- package/skills/jgengine-combat/recipes/portable-damage-effects.md +197 -0
- package/skills/jgengine-combat/recipes/portable-runtime-state.md +154 -0
- package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +156 -0
- package/skills/jgengine-combat/recipes/visible-attacks.md +81 -0
- package/skills/jgengine-combat/reference.md +22 -3
- package/skills/jgengine-gameplay/SKILL.md +8 -0
- package/skills/jgengine-gameplay/api.md +35 -5
- package/skills/jgengine-gameplay/capabilities.md +18 -0
- package/skills/jgengine-gameplay/recipes/portable-xp-leveling.md +168 -0
- package/skills/jgengine-gameplay/reference-systems.md +9 -0
- package/skills/jgengine-world/api.md +52 -24
- package/skills/jgengine-world/capabilities.md +8 -4
- package/skills/jgengine-world/reference.md +1 -1
|
@@ -11,6 +11,10 @@ This skill owns non-combat rules and state: systems, catalogs, inventory/items,
|
|
|
11
11
|
|
|
12
12
|
Use [capabilities.md](capabilities.md) for intent discovery, [api.md](api.md) for signatures, and [reference-systems.md](reference-systems.md) for scheduling details before hand-rolling a subsystem.
|
|
13
13
|
|
|
14
|
+
Existing projects can keep player state and saves behind `LevelingStatAccess`.
|
|
15
|
+
Follow the [portable XP/leveling recipe](recipes/portable-xp-leveling.md) for
|
|
16
|
+
custom stat ids, immutable store writes, multiple level events, and JSON resume.
|
|
17
|
+
|
|
14
18
|
## Canonical workflow
|
|
15
19
|
|
|
16
20
|
1. Define plain serializable state and stable ids.
|
|
@@ -30,6 +34,10 @@ Prefer `defineSystem` and `defineGame({ systems })` for scheduled capabilities.
|
|
|
30
34
|
- Random selection accepts injected RNG and stable ordering.
|
|
31
35
|
- Large collections use indexes, queues, or bounded reconciliation rather than repeated scans.
|
|
32
36
|
|
|
37
|
+
## Part-composed character motion
|
|
38
|
+
|
|
39
|
+
A character kit-bashed from primitives/`ModelPart`s (no skeleton, no clips) animates procedurally: tag parts with `role` (`leg.l`, `arm.r`, `head`, `tail`, `wing.l`…) and the shell's part-motion rig drives counter-phase limb swing from the entity's live movement speed, idle breathe/sway, hit flinch on `combat.hitReaction`, and a death topple on `entity.died` — no game-side frame loops. Tune with `ModelConfig.partMotion`; curves are pure and deterministic (`@jgengine/core/game/partAnimation`), so crowds de-sync by instance-id phase and replays stay stable. Soft characters (blobs, slimes) opt into volume-conserving squash-and-stretch with `partMotion: { squashAmp }` — footfall squash, jelly idle breathe, flinch pulse — and `deathStyle: "splat"` flattens out on death instead of the default sideways topple. Rigged GLBs keep using `ModelConfig.animation` — roles are only for rig-less compositions.
|
|
40
|
+
|
|
33
41
|
## Traps
|
|
34
42
|
|
|
35
43
|
- A bare export mention is not an example; use capabilities for discovery and real imports/tests for adoption.
|
|
@@ -336,6 +336,16 @@
|
|
|
336
336
|
- `createChatFilter` (function): function createChatFilter(config: ChatFilterConfig): ChatFilter — ⚠ undocumented
|
|
337
337
|
- `normalizeChatText` (function): function normalizeChatText(text: string): string — ⚠ undocumented
|
|
338
338
|
|
|
339
|
+
## @jgengine/core/game/clipRoles
|
|
340
|
+
|
|
341
|
+
- `ClipRole` (type): type ClipRole = | "idle" | "walk" | "run" | "attack" | "hit" | "death" | "jump" | "spawn" | "interact" | "cheer" — Semantic animation roles a GLB clip name can map to. Rigged packs name the same motion differently (`Idle` / `Walking_A` / `Armature|Run`); roles are the pack-agnostic vocabulary the engine defaults from.
|
|
342
|
+
- `ClipRoleTable` (interface): interface ClipRoleTable — Keyword table driving {@link classifyClip}: lowercase keywords matched against a clip name's tokens (exact token match beats prefix match). Extend or replace per game/pack — the table is plain data, no genre assumptions.
|
|
343
|
+
- `DEFAULT_CLIP_ROLE_TABLE` (const): const DEFAULT_CLIP_ROLE_TABLE: ClipRoleTable — Default vocabulary covering KayKit, Quaternius, and Mixamo-style clip naming.
|
|
344
|
+
- `classifyClip` (function): function classifyClip(name: string, table: ClipRoleTable = DEFAULT_CLIP_ROLE_TABLE): ClipRole | null — Classify a single GLB clip name into a {@link ClipRole}, or `null` when nothing matches (or an exclusion token like `pose` disqualifies it). Deterministic: exact token matches beat prefix matches; remaining ties resolve by fixed role priority (death > hit > attack > … > idle).
|
|
345
|
+
- `defaultAnimationForClips` (function): function defaultAnimationForClips(clips: readonly string[], table: ClipRoleTable = DEFAULT_CLIP_ROLE_TABLE): ModelAnimationConfig | undefined — Build the default `ModelConfig.animation` for a rigged asset from its catalog clip names: speed-driven idle/walk/run states plus hit/death/attack one-shots where identifiable. Returns `undefined` when no idle clip is identifiable — an ambiguous rig renders its bind pose rather than guessing. Walk falls back to idle when the pack has no walk clip.
|
|
346
|
+
- `resolveAnimationConfig` (function): function resolveAnimationConfig(animation: ModelAnimationConfig | "auto" | "none" | undefined, clips: readonly string[] | undefined, table: ClipRoleTable = DEFAULT_CLIP_ROLE_TABLE): ModelAnimationConfig | undefined — Collapse `ModelConfig.animation`'s widened union to a concrete playback config: explicit configs pass through, `"none"`/absent render the bind pose, and `"auto"` derives states and one-shots from the model's clip names via {@link defaultAnimationForClips}. The shell calls this with the loaded GLB's actual clip names, so `"auto"` works on any rigged model — catalog-resolved ids are stamped `"auto"` automatically.
|
|
347
|
+
- `rolesFromClips` (function): function rolesFromClips(clips: readonly string[], table: ClipRoleTable = DEFAULT_CLIP_ROLE_TABLE): Partial<Record<ClipRole, string[]>> — Group a GLB's clip names by role. Each role's variants are sorted simplest-first — fewest name tokens, then lexicographic — so the canonical variant (`Idle` over `2H_Melee_Idle`, KayKit `Walking_A` over `Walking_B`) is always first: stable output for the same clip set regardless of source order.
|
|
348
|
+
|
|
339
349
|
## @jgengine/core/game/connectedPlayers
|
|
340
350
|
|
|
341
351
|
- `ConnectedPlayer` (interface): interface ConnectedPlayer — A player currently joined to a hosted world — the unit a shared-world loop iterates instead of `ctx.player`. Frozen by the registry (see {@link ConnectedPlayers.get}); fields are `readonly` so a caller can't edit its own copy and assume the change stuck.
|
|
@@ -367,7 +377,7 @@
|
|
|
367
377
|
- `InventoryDeclaration` (interface): interface InventoryDeclaration — Shape of one named inventory a game declares — slot count, accepted item types, HUD binding.
|
|
368
378
|
- `LifecycleConfig` (interface): interface LifecycleConfig<TState = unknown> — Declarative start/restart run lifecycle: the state transitions a game's run phase every genre repeats (title screen → live run → live run → title screen again), expressed as pure functions over one typed {@link StoreHandle} slot instead of hand-rolled `commands.define("start"/"restart")` glue that re-derives phase after every mutation. `start`/`restart` receive the store's own value type — the store's `TState`, never `ctx.game.store.get(key) as T` — and return the next value; the runtime writes it back and derives {@link GamePhase} from it via `phaseOf` in one place, so every adopting game gets identical, correct phase-sync for free.
|
|
369
379
|
- `LoopPlayer` (interface): interface LoopPlayer — Identity of a player joining or leaving a hosted world — passed to the multiplayer loop hooks.
|
|
370
|
-
- `PersistConfig` (interface): interface PersistConfig — Tunes offline whole-world save (`defineGameDefinition({ persist })`). Defaults: continuous `autosave` to `localStorage`, one slot, no version.
|
|
380
|
+
- `PersistConfig` (interface): interface PersistConfig — Tunes offline whole-world save (`defineGameDefinition({ persist })`). Offline games already autosave to `localStorage` by default — pass this only to tune it (or `persist: false` to opt out). Defaults: continuous `autosave` to `localStorage`, one slot, no version.
|
|
371
381
|
- `PhysicsConfig` (interface): interface PhysicsConfig — World gravity and jump tuning, plus scene-object collision opt-ins, for the game's physics step.
|
|
372
382
|
- `defineGameDefinition` (function): function defineGameDefinition<TAssetRef extends ModelAssetRef, TMultiplayer>(config: GameDefinitionConfig<TAssetRef, TMultiplayer>): GameDefinition<TAssetRef, TMultiplayer> — Host-level constructor for a bare `GameDefinition`: fills in `scene` and default `assets`, validates `name`, OR-merges `features` from installed systems, and composes `loop` from `systems` + any classic hooks. Games author through `defineGame` from `@jgengine/shell/defineGame`, which calls this and adds the presentation layer; call this directly only from headless hosts that mount no shell.
|
|
373
383
|
|
|
@@ -556,6 +566,17 @@
|
|
|
556
566
|
- `resolveSelection` (function): function resolveSelection(base: Readonly<Record<string, number>>, registry: LayerRegistry, ids: LayerSelection): { readonly snapshot: ParamSnapshot; readonly unknown: readonly string[] } — Reproducible session setup in one call: resolve a saved {@link LayerSelection} through a registry, fold the found layers over `base`, and return the snapshot alongside any unknown ids. The same base + registry + selection always yields the same snapshot.
|
|
557
567
|
- `validateLayers` (function): function validateLayers(layers: readonly ParamLayer[]): readonly LayerConflict[] — Detect design conflicts in a layer set before resolving: duplicate ids, and competing `set` ops on the same parameter at the same priority (order-dependent, so worth surfacing). Returns an empty array when the set is clean, so callers can gate a difficulty/session build on it.
|
|
558
568
|
|
|
569
|
+
## @jgengine/core/game/partAnimation
|
|
570
|
+
|
|
571
|
+
- `PartMotionInput` (interface): interface PartMotionInput — Live signals a driver feeds the samplers each frame. All values are render-side only.
|
|
572
|
+
- `PartMotionParams` (interface): interface PartMotionParams — Tuning for {@link samplePartPose}/{@link sampleBodyPose}; every field has a sensible default.
|
|
573
|
+
- `PartPose` (interface): interface PartPose — Sampled offsets to add onto a part's authored transform (or the character root). `scale` is multiplicative (1 = unchanged).
|
|
574
|
+
- `PartRole` (type): type PartRole = "head" | "arm.l" | "arm.r" | "leg.l" | "leg.r" | "tail" | "wing.l" | "wing.r" — Procedural animation for part-composed (rig-less) models — characters kit-bashed from primitives/`ModelPart`s with no skeleton or clips. Pure, deterministic curve sampling: the same (time, speed, phase, flinch, death) input always yields the same pose, so replays and multi-client renders stay in lockstep and the hot path allocates nothing when an `out` pose is reused.
|
|
575
|
+
- `createPartPose` (function): function createPartPose(): PartPose — A reusable identity pose (zero offsets, unit scale).
|
|
576
|
+
- `partMotionPhase` (function): function partMotionPhase(id: string): number — Deterministic per-character stride phase in [0, 1) from an id (e.g. the entity instance id), so a crowd of identical part-composed characters doesn't animate in lockstep.
|
|
577
|
+
- `sampleBodyPose` (function): function sampleBodyPose(input: PartMotionInput, params?: PartMotionParams, out: PartPose = createPartPose()): PartPose — Sample the character-root pose: walk bob, idle breathe/sway, backward hit flinch, and death — a sideways topple by default, or a flatten-out splat with `deathStyle: "splat"`. With `squashAmp > 0` the body also squashes and stretches (footfall squash, jelly idle breathe, flinch squash pulse), volume-conserving. A driver applies this to the group wrapping the whole composition (base model plus parts) — limbs ride along, as they would on a real torso.
|
|
578
|
+
- `samplePartPose` (function): function samplePartPose(role: PartRole, input: PartMotionInput, params?: PartMotionParams, out: PartPose = createPartPose()): PartPose — Sample one part's pose offset by role: counter-phase leg/arm swing driven by speed, a head counter-sway, tail wag, and wing flap. Offsets are added onto the part's authored position/rotation. Motion fades to zero as `death` ramps so the topple reads as one piece.
|
|
579
|
+
|
|
559
580
|
## @jgengine/core/game/ping
|
|
560
581
|
|
|
561
582
|
- `DEFAULT_PING_CATEGORIES` (const): const DEFAULT_PING_CATEGORIES: Record<PingCategory, PingCategoryDef> — Content-agnostic default ping wheel: enemy / loot / location / danger.
|
|
@@ -629,10 +650,10 @@
|
|
|
629
650
|
- `LevelProgress` (interface): interface LevelProgress — Result of resolving a level+xp pair: the settled `level`, leftover `xp` into the current level, the `xpMax` threshold for the next level, and how many `levelsGained` this resolution produced.
|
|
630
651
|
- `LevelingConfig` (interface): interface LevelingConfig — Configuration for a {@link leveling} track: the `xpForLevel` {@link Curve}, the `maxLevel` cap, and optional `startLevel`, stat ids (`xpStat`/`levelStat`, default `"xp"`/`"level"`), and `thresholdMode` — `"perLevel"` (each level costs its own curve value) or `"cumulative"` (curve gives the total xp to reach a level).
|
|
631
652
|
- `LevelingStatAccess` (interface): interface LevelingStatAccess — Adapter a {@link LevelingTrack} uses to read and write a user's level/xp stats, so the track stays decoupled from any concrete stats store.
|
|
632
|
-
- `LevelingTrack` (interface): interface LevelingTrack — A resolved leveling track: the immutable `maxLevel`/`startLevel`, the `xpForLevel` threshold lookup, a pure `resolve`, and `grantXp` which writes back through a {@link LevelingStatAccess} and fires
|
|
653
|
+
- `LevelingTrack` (interface): interface LevelingTrack — A resolved leveling track: the immutable `maxLevel`/`startLevel`, the `xpForLevel` threshold lookup, a pure `resolve`, and `grantXp` which writes back through a {@link LevelingStatAccess} and fires `onLevelUp` once for every reached level in ascending order.
|
|
633
654
|
- `curve` (function): function curve(spec: Curve): (x: number) => number — Bind a {@link Curve} into a reusable `(x) => value` evaluator — a partial application of {@link evalCurve}.
|
|
634
655
|
- `evalCurve` (function): function evalCurve(spec: Curve, x: number): number — Evaluate a progression {@link Curve} at position `x`, applying its rounding mode and min/max clamp. This is the shared numeric backbone for XP-per-level, cost ramps, difficulty scaling, and any other level/rank → value mapping — use it instead of hand-rolling growth formulas.
|
|
635
|
-
- `leveling` (function): function leveling(config: LevelingConfig): LevelingTrack — Build a leveling track from an xp-per-level {@link Curve} and a max level. The returned {@link LevelingTrack} resolves how many levels a given xp total earns (rolling over surplus xp, capping at `maxLevel`) and, via `grantXp`, writes the new level/xp back through a {@link LevelingStatAccess} and fires `onLevelUp` for each level gained. Reach for this instead of hand-tracking xp thresholds; pair it with `resource-pool` (mana/stamina) and `ability-bar` (cooldowns) for a hero.
|
|
656
|
+
- `leveling` (function): function leveling(config: LevelingConfig): LevelingTrack — Build a leveling track from an xp-per-level {@link Curve} and a max level. The returned {@link LevelingTrack} resolves how many levels a given xp total earns (rolling over surplus xp, capping at `maxLevel`) and, via `grantXp`, writes the new level/xp back through a {@link LevelingStatAccess} and fires `onLevelUp` for each level gained in ascending order. Reach for this instead of hand-tracking xp thresholds; pair it with `resource-pool` (mana/stamina) and `ability-bar` (cooldowns) for a hero.
|
|
636
657
|
|
|
637
658
|
## @jgengine/core/game/quest
|
|
638
659
|
|
|
@@ -1007,7 +1028,7 @@
|
|
|
1007
1028
|
- `LevelProgress` (interface): interface LevelProgress — Result of resolving a level+xp pair: the settled `level`, leftover `xp` into the current level, the `xpMax` threshold for the next level, and how many `levelsGained` this resolution produced.
|
|
1008
1029
|
- `LevelSequence` (interface): interface LevelSequence<TLevelConfig> — ⚠ undocumented
|
|
1009
1030
|
- `LevelingConfig` (interface): interface LevelingConfig — Configuration for a {@link leveling} track: the `xpForLevel` {@link Curve}, the `maxLevel` cap, and optional `startLevel`, stat ids (`xpStat`/`levelStat`, default `"xp"`/`"level"`), and `thresholdMode` — `"perLevel"` (each level costs its own curve value) or `"cumulative"` (curve gives the total xp to reach a level).
|
|
1010
|
-
- `LevelingTrack` (interface): interface LevelingTrack — A resolved leveling track: the immutable `maxLevel`/`startLevel`, the `xpForLevel` threshold lookup, a pure `resolve`, and `grantXp` which writes back through a {@link LevelingStatAccess} and fires
|
|
1031
|
+
- `LevelingTrack` (interface): interface LevelingTrack — A resolved leveling track: the immutable `maxLevel`/`startLevel`, the `xpForLevel` threshold lookup, a pure `resolve`, and `grantXp` which writes back through a {@link LevelingStatAccess} and fires `onLevelUp` once for every reached level in ascending order.
|
|
1011
1032
|
- `LifecycleConfig` (interface): interface LifecycleConfig<TState = unknown> — Declarative start/restart run lifecycle: the state transitions a game's run phase every genre repeats (title screen → live run → live run → title screen again), expressed as pure functions over one typed {@link StoreHandle} slot instead of hand-rolled `commands.define("start"/"restart")` glue that re-derives phase after every mutation. `start`/`restart` receive the store's own value type — the store's `TState`, never `ctx.game.store.get(key) as T` — and return the next value; the runtime writes it back and derives {@link GamePhase} from it via `phaseOf` in one place, so every adopting game gets identical, correct phase-sync for free.
|
|
1012
1033
|
- `LightingConfig` (interface): interface LightingConfig — Declarative lighting replacing the shell's hardcoded ambient/directional default (#207.5); mounts regardless of world kind, only when supplied.
|
|
1013
1034
|
- `Listing` (interface): interface Listing — One active post in a {@link ListingBook}: an item stack a seller offered at a fixed price until it expires.
|
|
@@ -1320,7 +1341,7 @@
|
|
|
1320
1341
|
- `jobById` (function): function jobById<TSpec, TReserve>(state: WorkQueueState<TSpec, TReserve>, id: JobId): Job<TSpec, TReserve> | null — Look up a job by id, or `null` if absent/terminal.
|
|
1321
1342
|
- `jobProgress` (function): function jobProgress<TSpec, TReserve>(job: Job<TSpec, TReserve>): number — Fractional progress of a job (0…1); a zero-duration job reads as complete.
|
|
1322
1343
|
- `lapDurations` (function): function lapDurations(splits: readonly number[], gatesPerLap: number): number[] — Per-lap durations from a cumulative split book with `gatesPerLap` checkpoints per lap — each lap's time is its finish-gate split minus the previous lap's finish. Only complete laps are returned.
|
|
1323
|
-
- `leveling` (function): function leveling(config: LevelingConfig): LevelingTrack — Build a leveling track from an xp-per-level {@link Curve} and a max level. The returned {@link LevelingTrack} resolves how many levels a given xp total earns (rolling over surplus xp, capping at `maxLevel`) and, via `grantXp`, writes the new level/xp back through a {@link LevelingStatAccess} and fires `onLevelUp` for each level gained. Reach for this instead of hand-tracking xp thresholds; pair it with `resource-pool` (mana/stamina) and `ability-bar` (cooldowns) for a hero.
|
|
1344
|
+
- `leveling` (function): function leveling(config: LevelingConfig): LevelingTrack — Build a leveling track from an xp-per-level {@link Curve} and a max level. The returned {@link LevelingTrack} resolves how many levels a given xp total earns (rolling over surplus xp, capping at `maxLevel`) and, via `grantXp`, writes the new level/xp back through a {@link LevelingStatAccess} and fires `onLevelUp` for each level gained in ascending order. Reach for this instead of hand-tracking xp thresholds; pair it with `resource-pool` (mana/stamina) and `ability-bar` (cooldowns) for a hero.
|
|
1324
1345
|
- `listRuleEffects` (function): function listRuleEffects(): RuleEffectDefinition[] — Every declared rule effect, for populating inspector dropdowns and validating a content set.
|
|
1325
1346
|
- `loadBindingOverrides` (function): function loadBindingOverrides(gameId: string, storage: Pick<WebStorageLike, "getItem"> | null | undefined = defaultStorage()): BindingOverrides — ⚠ undocumented
|
|
1326
1347
|
- `localSaveBackend` (function): function localSaveBackend(storage?: KeyValueStorage | null): SaveBackend — A {@link SaveBackend} over a synchronous {@link KeyValueStorage} — the browser's `localStorage` by default (offline, on-device saves), a test stub, or `null` for memory-only. Storage errors (quota exceeded, private mode, no DOM) degrade to no-ops, so a save never throws into a game tick.
|
|
@@ -1420,7 +1441,16 @@
|
|
|
1420
1441
|
- `ItemTraits` (interface): interface ItemTraits — ⚠ undocumented
|
|
1421
1442
|
- `MoveResult` (type): type MoveResult = | { status: "ok"; from: InventoryState; to: InventoryState } | { status: "rejected"; reason: "invalid-slot" | "empty-slot" | "wrong-kind" | "no-space" } — ⚠ undocumented
|
|
1422
1443
|
- `PutResult` (type): type PutResult = | { status: "ok"; state: InventoryState } | { status: "rejected"; reason: "no-space" | "wrong-kind" | "slot-occupied" | "invalid-slot" } — ⚠ undocumented
|
|
1444
|
+
- `SplitResult` (type): type SplitResult = | { status: "ok"; state: InventoryState } | { status: "rejected"; reason: "invalid-slot" | "empty-slot" | "invalid-amount" | "no-space" | "slot-occupied" } — Outcome of {@link splitStack}: the new state, or a rejection reason.
|
|
1423
1445
|
- `TakeResult` (type): type TakeResult = { status: "ok"; state: InventoryState } | { status: "rejected"; reason: "insufficient" } — ⚠ undocumented
|
|
1446
|
+
- `countItem` (function): function countItem(state: InventoryState, itemId: string): number — Total quantity of `itemId` summed across every stack in the inventory.
|
|
1447
|
+
- `createEmptyInventory` (function): function createEmptyInventory(layout: InventoryLayout): InventoryState — A bag of stackable items supporting add, remove, count, and move — the base inventory model.
|
|
1448
|
+
- `createInventorySet` (function): function createInventorySet<TId extends string>(layouts: Record<TId, InventoryLayout>, traits: ItemTraits): InventorySet<TId> — Build a keyed set of named inventories from their `layouts`, sharing one `traits` table — the stateful façade over put/take/move/count.
|
|
1449
|
+
- `hasItem` (function): function hasItem(state: InventoryState, itemId: string, count: number): boolean — True when the inventory holds at least `count` of `itemId`.
|
|
1450
|
+
- `moveItem` (function): function moveItem(from: InventoryState, fromSlot: number, to: InventoryState, toLayout: InventoryLayout, traits: ItemTraits, toSlot?: number): MoveResult — Move the stack at `fromSlot` to `toSlot` (or auto-stack when omitted) — swapping, merging with remainder, or relocating across two inventories. Rejects on empty source, wrong kind, or no space.
|
|
1451
|
+
- `putItem` (function): function putItem(state: InventoryState, layout: InventoryLayout, traits: ItemTraits, itemId: string, count: number, options?: { slot?: number }): PutResult — Add `count` of `itemId` — into a specific `options.slot`, or auto-stacked across free/partial slots. Rejects on no space or wrong kind.
|
|
1452
|
+
- `splitStack` (function): function splitStack(state: InventoryState, slot: number, amount: number, toSlot?: number): SplitResult — Splits `amount` off the stack in `slot` into `toSlot` (or the first empty slot when `toSlot` is omitted). Limit-agnostic: it does not consult {@link ItemTraits}, so a same-item target merges by simple addition. Rejects an empty source, `amount <= 0`, `amount >= stack count` (a split must leave both sides non-empty), no free slot, or a target holding a different item.
|
|
1453
|
+
- `takeItem` (function): function takeItem(state: InventoryState, itemId: string, count: number): TakeResult — Remove `count` of `itemId`, draining from the last matching stacks first. Rejects if fewer than `count` are held.
|
|
1424
1454
|
|
|
1425
1455
|
## @jgengine/core/inventory/shapedGrid
|
|
1426
1456
|
|
|
@@ -36,6 +36,11 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
36
36
|
|
|
37
37
|
- `totalLoad` (function) · `import { totalLoad } from "@jgengine/core/inventory/encumbrance"`
|
|
38
38
|
|
|
39
|
+
## clip-roles — map a rigged asset's pack-specific clip name to its semantic animation role
|
|
40
|
+
|
|
41
|
+
- `classifyClip` (function) · `import { classifyClip } from "@jgengine/core/game/clipRoles"`
|
|
42
|
+
- `defaultAnimationForClips` (function) · `import { defaultAnimationForClips } from "@jgengine/core/game/clipRoles"`
|
|
43
|
+
|
|
39
44
|
## compose-game-loop — fold composable systems into the game loop without a manual tick fan-out
|
|
40
45
|
|
|
41
46
|
- `composeGameLoop` (function) · `import { composeGameLoop } from "@jgengine/core/gameplay"`
|
|
@@ -123,6 +128,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
123
128
|
|
|
124
129
|
- `incubationViable` (function) · `import { incubationViable } from "@jgengine/core/game/breeding"`
|
|
125
130
|
|
|
131
|
+
## inventory-grid — a bag of stackable items with add, remove, and move
|
|
132
|
+
|
|
133
|
+
- `createEmptyInventory` (function) · `import { createEmptyInventory } from "@jgengine/core/inventory/inventoryModel"`
|
|
134
|
+
|
|
135
|
+
## inventory-split — split a stack into a target or first-empty slot
|
|
136
|
+
|
|
137
|
+
- `splitStack` (function) · `import { splitStack } from "@jgengine/core/inventory/inventoryModel"`
|
|
138
|
+
|
|
126
139
|
## item-generation — compose constraints, weighted pools, transforms, and provenance into a deterministic generated item
|
|
127
140
|
|
|
128
141
|
- `generate` (function) · `import { generate } from "@jgengine/core/gameplay"`
|
|
@@ -234,6 +247,11 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
234
247
|
- `resolveSelection` (function) · `import { resolveSelection } from "@jgengine/core/gameplay"`
|
|
235
248
|
- `validateLayers` (function) · `import { validateLayers } from "@jgengine/core/gameplay"`
|
|
236
249
|
|
|
250
|
+
## part-motion — sample the root bob/sway/squash/flinch/death pose for a rig-less part-composed character
|
|
251
|
+
|
|
252
|
+
- `sampleBodyPose` (function) · `import { sampleBodyPose } from "@jgengine/core/game/partAnimation"`
|
|
253
|
+
- `samplePartPose` (function) · `import { samplePartPose } from "@jgengine/core/game/partAnimation"`
|
|
254
|
+
|
|
237
255
|
## pause-scheduled-rule — stop a scheduled rule from firing while retaining its cursor
|
|
238
256
|
|
|
239
257
|
- `pauseRule` (function) · `import { pauseRule } from "@jgengine/core/gameplay"`
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Add XP and leveling to an existing store
|
|
2
|
+
|
|
3
|
+
Use this recipe when an existing JavaScript/TypeScript project owns its player
|
|
4
|
+
state and save format but wants JGengine's deterministic XP curves,
|
|
5
|
+
multi-level grants, and per-level events. No `GameContext`, renderer, React,
|
|
6
|
+
entity store, or JGengine loop is required.
|
|
7
|
+
|
|
8
|
+
## Install and focused imports
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
bun add @jgengine/core
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import {
|
|
16
|
+
leveling,
|
|
17
|
+
type LevelingStatAccess,
|
|
18
|
+
} from "@jgengine/core/game/progression";
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Caller-owned save data
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
interface SavedPool {
|
|
25
|
+
current: number;
|
|
26
|
+
max: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface PlayerSave {
|
|
30
|
+
experience: SavedPool;
|
|
31
|
+
rank: SavedPool;
|
|
32
|
+
unlockedAbilities: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ExistingSave {
|
|
36
|
+
players: Record<string, PlayerSave>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let save: ExistingSave = {
|
|
40
|
+
players: {
|
|
41
|
+
player_1: {
|
|
42
|
+
experience: { current: 0, max: 100 },
|
|
43
|
+
rank: { current: 1, max: 20 },
|
|
44
|
+
unlockedAbilities: [],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Minimal adapter
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
const stats: LevelingStatAccess = {
|
|
54
|
+
get(userId, statId) {
|
|
55
|
+
const player = save.players[userId];
|
|
56
|
+
if (player === undefined) return null;
|
|
57
|
+
if (statId === "experience") return player.experience;
|
|
58
|
+
if (statId === "rank") return player.rank;
|
|
59
|
+
return null;
|
|
60
|
+
},
|
|
61
|
+
set(userId, statId, patch) {
|
|
62
|
+
const player = save.players[userId];
|
|
63
|
+
if (player === undefined) return false;
|
|
64
|
+
const previous = statId === "experience"
|
|
65
|
+
? player.experience
|
|
66
|
+
: statId === "rank"
|
|
67
|
+
? player.rank
|
|
68
|
+
: null;
|
|
69
|
+
if (previous === null) return false;
|
|
70
|
+
const next = {
|
|
71
|
+
current: patch.current ?? previous.current,
|
|
72
|
+
max: patch.max ?? previous.max,
|
|
73
|
+
};
|
|
74
|
+
const nextPlayer = statId === "experience"
|
|
75
|
+
? { ...player, experience: next }
|
|
76
|
+
: { ...player, rank: next };
|
|
77
|
+
save = {
|
|
78
|
+
...save,
|
|
79
|
+
players: {
|
|
80
|
+
...save.players,
|
|
81
|
+
[userId]: nextPlayer,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
return true;
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The adapter may call a Zustand `set`, dispatch a Redux action, write an ECS
|
|
90
|
+
component, or replace plain data as above. It never mirrors the player into a
|
|
91
|
+
JGengine store.
|
|
92
|
+
|
|
93
|
+
## Configure and grant XP
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const emitted: unknown[] = [];
|
|
97
|
+
const existingEvents = {
|
|
98
|
+
emit(name: string, payload: unknown) {
|
|
99
|
+
emitted.push({ name, payload });
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const playerLevel = leveling({
|
|
104
|
+
xpForLevel: {
|
|
105
|
+
kind: "power",
|
|
106
|
+
base: 80,
|
|
107
|
+
exponent: 1.35,
|
|
108
|
+
round: "floor",
|
|
109
|
+
},
|
|
110
|
+
maxLevel: 20,
|
|
111
|
+
xpStat: "experience",
|
|
112
|
+
levelStat: "rank",
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const levelEvents: number[] = [];
|
|
116
|
+
const levelsGained = playerLevel.grantXp(
|
|
117
|
+
stats,
|
|
118
|
+
"player_1",
|
|
119
|
+
900,
|
|
120
|
+
(level) => {
|
|
121
|
+
levelEvents.push(level);
|
|
122
|
+
existingEvents.emit("player-level-up", { playerId: "player_1", level });
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`levelsGained` is the count returned to the caller. The callback fires once for
|
|
128
|
+
every reached level in ascending order, so a large reward can unlock each
|
|
129
|
+
milestone deterministically instead of collapsing them into one final event.
|
|
130
|
+
|
|
131
|
+
Use `playerLevel.resolve(level, xp)` when prediction/UI needs the same math
|
|
132
|
+
without writing through the adapter.
|
|
133
|
+
|
|
134
|
+
## Save and restore
|
|
135
|
+
|
|
136
|
+
The adapter's authoritative values already live in the existing save:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const encoded = JSON.stringify(save);
|
|
140
|
+
save = JSON.parse(encoded) as ExistingSave;
|
|
141
|
+
|
|
142
|
+
// Recreate an adapter over the restored store, then continue normally.
|
|
143
|
+
playerLevel.grantXp(stats, "player_1", 75, (level) => {
|
|
144
|
+
existingEvents.emit("player-level-up", { playerId: "player_1", level });
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The leveling track is immutable configuration; recreate it from the same curve
|
|
149
|
+
after load. Version curve changes in the existing project's migration layer.
|
|
150
|
+
|
|
151
|
+
## Ownership
|
|
152
|
+
|
|
153
|
+
The existing project retains player identity, store updates, save schema,
|
|
154
|
+
curve/content choice, reward sources, event bus, unlock handling, UI, backend,
|
|
155
|
+
and when XP is granted. JGengine owns curve evaluation, surplus rollover,
|
|
156
|
+
level caps, ordered level events, and the two-method stat adapter contract.
|
|
157
|
+
|
|
158
|
+
## Common traps
|
|
159
|
+
|
|
160
|
+
- Initialize both configured stat ids; `grantXp` returns `0` when either is
|
|
161
|
+
missing.
|
|
162
|
+
- Treat positive and negative XP policy as game-owned input validation;
|
|
163
|
+
sanitize rewards before calling when XP loss is not allowed.
|
|
164
|
+
- The default threshold mode is `perLevel`. Use `cumulative` only when the curve
|
|
165
|
+
represents total lifetime XP required to reach each level.
|
|
166
|
+
- Keep `rank.max` aligned with `maxLevel` in the caller's schema.
|
|
167
|
+
- Apply milestone rewards from each callback invocation; do not assume a large
|
|
168
|
+
grant emits only the final reached level.
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Single public authoring path: `defineGame({ systems, loop?, … })` from `@jgengine/shell/defineGame`. Systems are meaningful capabilities — not micro-ticks.
|
|
4
4
|
|
|
5
|
+
## Portable XP and leveling
|
|
6
|
+
|
|
7
|
+
`leveling` (`@jgengine/core/game/progression`) is usable without a scheduled
|
|
8
|
+
system or game context. Its `LevelingStatAccess` reads and replaces two
|
|
9
|
+
caller-named pools in an existing store; `grantXp` writes settled XP/threshold
|
|
10
|
+
and level state, then emits every reached level in ascending order. State stays
|
|
11
|
+
in the caller's save format and round-trips as ordinary JSON. See the
|
|
12
|
+
[portable XP/leveling recipe](recipes/portable-xp-leveling.md).
|
|
13
|
+
|
|
5
14
|
## API
|
|
6
15
|
|
|
7
16
|
```ts
|
|
@@ -943,6 +943,7 @@
|
|
|
943
943
|
- `DEFAULT_ENTITY_HIT_RADIUS` (const): const DEFAULT_ENTITY_HIT_RADIUS: 0.5 — ⚠ undocumented
|
|
944
944
|
- `DEFAULT_OBJECT_HALF_EXTENTS` (const): const DEFAULT_OBJECT_HALF_EXTENTS: EntityPosition — ⚠ undocumented
|
|
945
945
|
- `EntityColliderSet` (interface): interface EntityColliderSet — ⚠ undocumented
|
|
946
|
+
- `MeasuredBounds` (interface): interface MeasuredBounds — Entity-local AABB of what the renderer actually mounted for a kind — the runtime counterpart to index-measured `dims`. Reported through `ctx.scene.entity.reportBounds` / `ctx.scene.object.reportBounds` (the shell does this automatically for custom `renderEntity`/`renderObject` content and models without index dims) so collider resolution can wrap the real rendered shape instead of a fixed-size guess.
|
|
946
947
|
- `ModelBodySource` (interface): interface ModelBodySource — The render-config subset collider fitting reads — structurally satisfied by a resolved `ModelConfig`, so the shell can hand its render config straight to the fitting math without a conversion step.
|
|
947
948
|
- `ResolvedCollider` (interface): interface ResolvedCollider — ⚠ undocumented
|
|
948
949
|
|
|
@@ -1448,7 +1449,7 @@
|
|
|
1448
1449
|
- `BoundsSpec` (type): type BoundsSpec = | { readonly kind: "sphere"; readonly radius: number; readonly offset?: Vec3 } | { readonly kind: "aabb"; readonly half: Vec3; readonly offset?: Vec3 } | { readonly kind: "rect"; readonly halfWidth: number; readonly halfDepth: number; readonly halfHeight?: number; readonly offset?:… — How a renderable declares its extent. AABB, bounding sphere, and 2D rectangle cover the common cases; `point` is the degenerate zero-size default for objects that never override. `offset` shifts the volume from the object origin (e.g. a tall model whose pivot is at its feet).
|
|
1449
1450
|
- `BoxFormationOptions` (interface): interface BoxFormationOptions — Options for {@link boxFormation}.
|
|
1450
1451
|
- `BuildRole` (type): type BuildRole = "owner" | "editor" | "viewer" — ⚠ undocumented
|
|
1451
|
-
- `BuildingEnvironmentDescriptor` (type): type BuildingEnvironmentDescriptor = { kind: "building" } & Required< Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style"> > & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette"> — ⚠ undocumented
|
|
1452
|
+
- `BuildingEnvironmentDescriptor` (type): type BuildingEnvironmentDescriptor = { kind: "building" } & Required< Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style"> > & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette" | "along"> — ⚠ undocumented
|
|
1452
1453
|
- `BuildingIndex` (interface): interface BuildingIndex — ⚠ undocumented
|
|
1453
1454
|
- `BuildingPaletteOverrides` (type): type BuildingPaletteOverrides = Partial<BuildingPalette> — ⚠ undocumented
|
|
1454
1455
|
- `BuildingStyle` (type): type BuildingStyle = | "generic" | "capital" | "village" | "desert" | "industrial" | "coastal" | "neon" | "ruin" | "frontier" | "aerial" — ⚠ undocumented
|
|
@@ -1849,7 +1850,7 @@
|
|
|
1849
1850
|
- `boxRegion` (function): function boxRegion(min: Point3, max: Point3): SampleRegion<Point3> — An axis-aligned box `[min..max]` in 3D. Uniform density; draws x, y, z in order.
|
|
1850
1851
|
- `buildContextMenu` (function): function buildContextMenu(input: BuildContextMenuInput): ContextMenu | null — Assemble a menu from a target's catalog verbs; null when the target lists none.
|
|
1851
1852
|
- `buildRoadRibbon` (function): function buildRoadRibbon(path: readonly RoadPoint[], width: number, sampleHeight: (x: number, z: number) => number, options: RoadRibbonOptions = {}): RoadRibbon — Triangulate a road centerline into a ground-draped ribbon mesh: the polyline is subdivided, each vertex is offset half a `width` along the local perpendicular, and every vertex sits at `sampleHeight(x, z) + elevation`. Pure geometry — the shell (or any renderer) turns the result into a mesh, and tests can assert on it directly.
|
|
1852
|
-
- `building` (function): function building(config: BuildingEnvironmentConfig = {}): BuildingEnvironmentDescriptor — Declares a cluster of procedurally-massed buildings for `environment()` — count, footprint, stories, style.
|
|
1853
|
+
- `building` (function): function building(config: BuildingEnvironmentConfig = {}): BuildingEnvironmentDescriptor — Declares a cluster of procedurally-massed buildings for `environment()` — count, footprint, stories, style. Pass `along` to line road frontage instead of gridding around `position`.
|
|
1853
1854
|
- `buildingIndex` (function): function buildingIndex(buildings: readonly GeneratedBuilding[]): BuildingIndex — ⚠ undocumented
|
|
1854
1855
|
- `cappedStacks` (function): function cappedStacks<P>(limit: number, magnitudeOf?: MagnitudeOf<P>): AreaStackPolicy<P> — Keep at most `limit` memberships per `stackKey` (the highest-magnitude ones when `magnitudeOf` is given, else the first-seen). Models capped stacks — e.g. a poison that stacks up to 5 times.
|
|
1855
1856
|
- `carrySpeedMultiplier` (function): function carrySpeedMultiplier(mass: number, carryCapacity: number, owners: number): number — Movement multiplier (1 = unhindered, →0 = crushed) for a body of `mass` carried by `owners`. Pure — the HUD/movement kit reads it to slow a laden hauler (Lethal Company) and to gate items that need 2+ people (R.E.P.O.).
|
|
@@ -1921,7 +1922,7 @@
|
|
|
1921
1922
|
- `createVisibilitySystem` (function): function createVisibilitySystem(options: VisibilitySystemOptions): VisibilitySystem — ⚠ undocumented
|
|
1922
1923
|
- `createVoxelField` (function): function createVoxelField<T extends string = string>(config?: VoxelFieldConfig): VoxelField<T> — ⚠ undocumented
|
|
1923
1924
|
- `customRegion` (function): function customRegion<P extends SamplePoint>(spec: { dimensions: 2 | 3; sample: (rng: () => number) => P; contains: (point: P) => boolean; isEmpty?: boolean; }): SampleRegion<P> — Wrap a caller-defined sampler and bounds test as a {@link SampleRegion} — the escape hatch for regions the built-ins do not cover (a navmesh cell, a heightfield mask, a spline tube).
|
|
1924
|
-
- `dashSegments` (function): function dashSegments(path: readonly RoadPoint[], dashLength = 3, gapLength = 3): readonly (readonly RoadPoint[])[] — Split a centerline into dash sub-polylines for lane markings: `dashLength` of painted line, `gapLength` of asphalt, repeated along the path's arc length. Feed each returned sub-path back through {@link buildRoadRibbon} with a thin width to mesh the dashes.
|
|
1925
|
+
- `dashSegments` (function): function dashSegments(path: readonly RoadPoint[], dashLength = 3, gapLength = 3, exclude: readonly DashExclusion[] = []): readonly (readonly RoadPoint[])[] — Split a centerline into dash sub-polylines for lane markings: `dashLength` of painted line, `gapLength` of asphalt, repeated along the path's arc length. Feed each returned sub-path back through {@link buildRoadRibbon} with a thin width to mesh the dashes. Pass `exclude` circles (junction patches) to interrupt the center line through intersections — any dash whose midpoint lands inside an exclusion is dropped.
|
|
1925
1926
|
- `defineAttackMoveOrder` (function): function defineAttackMoveOrder<TCtx extends OrderMover & OrderTargeting>(config: EngagementKindConfig = {}): OrderKind<TCtx, AttackMoveOrderPayload> — Attack-move: advance toward a destination but break off to engage any hostile acquired within `aggroRadius`, pursuing it to `attackRange` and writing the engagement intent into `order.state` for the game to act on. Completes on reaching the destination with nothing to engage.
|
|
1926
1927
|
- `defineHoldOrder` (function): function defineHoldOrder<TCtx extends OrderMover>(config: OrderKindConfig = {}): OrderKind<TCtx, EmptyOrderPayload> — Hold position: stand ground indefinitely (never completes on its own) until preempted or canceled — the "guard here" verb.
|
|
1927
1928
|
- `defineMoveOrder` (function): function defineMoveOrder<TCtx extends OrderMover>(config: OrderKindConfig & { arriveRadius?: number } = {}): OrderKind<TCtx, MoveOrderPayload> — The plain "go here" verb: step toward a point each tick, complete on arrival, fail if the entity despawns. The completion predicate is arrival within `arriveRadius`.
|
|
@@ -2190,6 +2191,14 @@
|
|
|
2190
2191
|
- `BuildingIndex` (interface): interface BuildingIndex — ⚠ undocumented
|
|
2191
2192
|
- `buildingIndex` (function): function buildingIndex(buildings: readonly GeneratedBuilding[]): BuildingIndex — ⚠ undocumented
|
|
2192
2193
|
|
|
2194
|
+
## @jgengine/core/world/buildingLots
|
|
2195
|
+
|
|
2196
|
+
- `BuildingLotOptions` (interface): interface BuildingLotOptions — Options for {@link deriveBuildingLots}.
|
|
2197
|
+
- `LotArea` (interface): interface LotArea — Rectangular clip region lots must fall within (world center + half-extents).
|
|
2198
|
+
- `PlacedBuildingLot` (interface): interface PlacedBuildingLot — One placed building lot: where a building stands and how it is turned to face its road.
|
|
2199
|
+
- `RoadFrontage` (interface): interface RoadFrontage — One road the frontage placer lines with buildings: a centerline polyline and its full width.
|
|
2200
|
+
- `deriveBuildingLots` (function): function deriveBuildingLots(options: BuildingLotOptions): PlacedBuildingLot[] — Derive street-aligned building lots from road frontage. Lots are stepped along each road at `footprint.w + spacing`, offset to each side by `width / 2 + setback + footprint.d / 2`, and turned to face the road. Cross-road overlaps are rejected by a bounded nearest-neighbour test, so two roads meeting at a corner don't stack buildings. Deterministic: identical inputs (including seed) always yield the identical list.
|
|
2201
|
+
|
|
2193
2202
|
## @jgengine/core/world/buildings
|
|
2194
2203
|
|
|
2195
2204
|
- `BUILDING_STYLE_PALETTES` (const): const BUILDING_STYLE_PALETTES: Record<BuildingStyle, BuildingPalette> — ⚠ undocumented
|
|
@@ -2275,6 +2284,12 @@
|
|
|
2275
2284
|
- `CityZoneBand` (type): type CityZoneBand = "core" | "mid" | "edge" — Zone band a lot falls in: dense core, middle ring, or the district edge.
|
|
2276
2285
|
- `CityZoneProfile` (type): type CityZoneProfile = "core-out" | "inverted" | "uniform" — How the radial zone metric maps to bands.
|
|
2277
2286
|
|
|
2287
|
+
## @jgengine/core/world/cityGenerator
|
|
2288
|
+
|
|
2289
|
+
- `CityGeneratorOptions` (interface): interface CityGeneratorOptions — Options for {@link generateCity}: a seed, street-dial overrides, and lot pass-through options.
|
|
2290
|
+
- `GeneratedCity` (interface): interface GeneratedCity — A generated city: the street network and the building lots lining its frontage.
|
|
2291
|
+
- `generateCity` (function): function generateCity(options: CityGeneratorOptions, hx: number, hz: number): GeneratedCity — Grow a street network inside the `hx`/`hz` half-extents and line its frontage with building lots. Deterministic: identical options ⇒ identical city.
|
|
2292
|
+
|
|
2278
2293
|
## @jgengine/core/world/cityGeometry
|
|
2279
2294
|
|
|
2280
2295
|
- `Vec2` (type): type Vec2 = readonly [number, number] — Deterministic 2D polygon math for the `city` studio's block/parcel pipeline: signed areas, point-in-polygon, half-plane clipping, per-edge inward insets (the curb → sidewalk → land and parcel → buildable transforms), simple-loop recovery after aggressive insets, and rotated-rect fitting inside arbitrary polygons. Pure functions over `[x, z]` tuples — no allocation-heavy classes, no rendering, no randomness — so every consumer from the resolver to the tests shares one geometric truth.
|
|
@@ -2377,7 +2392,9 @@
|
|
|
2377
2392
|
- `BiomeSky` (interface): interface BiomeSky — Per-band sky/light override cross-faded along z by `createBiomeSkySampler`; unset fields fall through to the base sky.
|
|
2378
2393
|
- `BiomesWorldConfig` (interface): interface BiomesWorldConfig extends WorldGridConfig — ⚠ undocumented
|
|
2379
2394
|
- `BuildingEnvironmentConfig` (interface): interface BuildingEnvironmentConfig — ⚠ undocumented
|
|
2380
|
-
- `BuildingEnvironmentDescriptor` (type): type BuildingEnvironmentDescriptor = { kind: "building" } & Required< Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style"> > & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette"> — ⚠ undocumented
|
|
2395
|
+
- `BuildingEnvironmentDescriptor` (type): type BuildingEnvironmentDescriptor = { kind: "building" } & Required< Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style"> > & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette" | "along"> — ⚠ undocumented
|
|
2396
|
+
- `BuildingFrontageConfig` (interface): interface BuildingFrontageConfig — Street-aware placement for `building()`: instead of a grid scattered around `position`, lots are stepped along each road's frontage, offset by a consistent setback (a curb + sidewalk strip), and turned so every building FRONT faces its road. `footprint` (`w` = frontage width, `d` = depth) and `spacing` (along-road gap) are shared with the grid mode. Deterministic and bounded by `maxLots`.
|
|
2397
|
+
- `BuildingFrontageRoad` (interface): interface BuildingFrontageRoad — One road a `building({ along })` frontage lines with buildings: centerline + full width.
|
|
2381
2398
|
- `EnvironmentArea` (interface): interface EnvironmentArea extends WorldBounds — ⚠ undocumented
|
|
2382
2399
|
- `EnvironmentDescriptorList` (type): type EnvironmentDescriptorList<T> = T | readonly T[] — ⚠ undocumented
|
|
2383
2400
|
- `EnvironmentVec2` (type): type EnvironmentVec2 = readonly [number, number] — ⚠ undocumented
|
|
@@ -2425,7 +2442,7 @@
|
|
|
2425
2442
|
- `WorldGridCell` (interface): interface WorldGridCell — ⚠ undocumented
|
|
2426
2443
|
- `WorldGridConfig` (interface): interface WorldGridConfig — Shared by `biomes()`/`voxel()`/`plots()`/`tilemap()` so the shell can render their declared content as instanced boxes without a hand-written renderer.
|
|
2427
2444
|
- `biomes` (function): function biomes(config: BiomesWorldConfig): WorldFeature — Declares a biome-painted world — the whole-world alternative to a single `environment()` terrain.
|
|
2428
|
-
- `building` (function): function building(config: BuildingEnvironmentConfig = {}): BuildingEnvironmentDescriptor — Declares a cluster of procedurally-massed buildings for `environment()` — count, footprint, stories, style.
|
|
2445
|
+
- `building` (function): function building(config: BuildingEnvironmentConfig = {}): BuildingEnvironmentDescriptor — Declares a cluster of procedurally-massed buildings for `environment()` — count, footprint, stories, style. Pass `along` to line road frontage instead of gridding around `position`.
|
|
2429
2446
|
- `environment` (function): function environment(config: EnvironmentWorldConfig = {}): EnvironmentWorldFeature — Composes an `environment()` feature from terrain, sky, weather, vegetation, water, structures, roads, and pads.
|
|
2430
2447
|
- `flat` (function): function flat(): WorldFeature — Declares an empty flat world — the minimal `WorldFeature` for games with no terrain of their own.
|
|
2431
2448
|
- `grass` (function): function grass(config: GrassEnvironmentConfig = {}): GrassEnvironmentDescriptor — Declares a grass vegetation patch for `environment()` — area, blade sizing, density, and colors.
|
|
@@ -2588,24 +2605,6 @@
|
|
|
2588
2605
|
- `PlaceAlongPathOptions` (interface): interface PlaceAlongPathOptions — Options for {@link placeAlongPath}.
|
|
2589
2606
|
- `placeAlongPath` (function): function placeAlongPath(points: readonly { x: number; z: number }[], options: PlaceAlongPathOptions): PathInstance[] — Evenly place transforms along `points` (XZ polyline). The run length is divided into the whole number of equal spans closest to `spacing`, so instances always land on both endpoints and stay evenly distributed. Returns `spans + 1` instances. Empty for fewer than 2 points.
|
|
2590
2607
|
|
|
2591
|
-
## @jgengine/core/world/pathNetwork
|
|
2592
|
-
|
|
2593
|
-
- `PathDeadEnd` (interface): interface PathDeadEnd — One dangling street end kept as a cul-de-sac: node position plus the heading pointing off the road.
|
|
2594
|
-
- `PathEdge` (interface): interface PathEdge — One atomic node-to-node edge — the fabric graph consumes these (welds at shared node coords).
|
|
2595
|
-
- `PathFeature` (interface): interface PathFeature — A resolved path feature in world-of-the-volume space: a bridge deck or tunnel bore centerline.
|
|
2596
|
-
- `PathFeatureKind` (type): type PathFeatureKind = "bridge" | "tunnel" — A path feature spanning part of an edge/street: a bridge deck over a gap or a tunnel bore under a ridge.
|
|
2597
|
-
- `PathFeatureSpan` (interface): interface PathFeatureSpan — A feature span carried by a street: a `[from, to]` index window into the street's `points`.
|
|
2598
|
-
- `PathJunction` (interface): interface PathJunction — One crossing of three or more streets: patch center/radius plus outgoing arm directions.
|
|
2599
|
-
- `PathLevel` (type): type PathLevel = "boulevard" | "avenue" | "street" | "lane" — Road hierarchy, widest to narrowest — shared by the city fabric and the renderer.
|
|
2600
|
-
- `PathNetwork` (interface): interface PathNetwork — The fully-resolved network in volume-local coords.
|
|
2601
|
-
- `PathNetworkContext` (interface): interface PathNetworkContext — Ground sampler + feature toggles enabling bridges/tunnels; omit for a flat, feature-free network.
|
|
2602
|
-
- `PathNetworkMode` (type): type PathNetworkMode = "net" | "circuit" — The generator's chosen topology family: an open street `net`, or a closed `circuit` loop.
|
|
2603
|
-
- `PathNetworkRules` (interface): interface PathNetworkRules — Fully-defaulted slider set the generator reads.
|
|
2604
|
-
- `PathNode` (interface): interface PathNode — One graph node: a junction, a dead end, or a mid-street bend, with its connection count.
|
|
2605
|
-
- `PathStreet` (interface): interface PathStreet — One chained through-street: a maximal run of edges through degree-2 nodes, for rendering + furniture.
|
|
2606
|
-
- `PathVec2` (type): type PathVec2 = readonly [number, number] — A path vertex in the volume-local XZ frame.
|
|
2607
|
-
- `buildPathNetwork` (function): function buildPathNetwork(rules: PathNetworkRules, hx: number, hz: number, context: PathNetworkContext = {}): PathNetwork — Resolve a full path network from its rules inside a volume of half-extents `hx`×`hz`. Deterministic per `(rules.seed, hx, hz, context)`. Pass a {@link PathNetworkContext} with a ground sampler to turn water gaps into bridges and ridges into tunnels. Coordinates are volume-local; the caller maps to world space.
|
|
2608
|
-
|
|
2609
2608
|
## @jgengine/core/world/pathTerrain
|
|
2610
2609
|
|
|
2611
2610
|
- `PathHeightPolicy` (type): type PathHeightPolicy = | { readonly kind: "sample" } | { readonly kind: "fixed"; readonly height: number } | { readonly kind: "grade"; readonly start: number; readonly end: number } — Where a path corridor drives its centerline height: - `sample` — follow the base terrain under the centerline, so the corridor drapes level across its width while still tracking the hills the path crosses (the natural policy for a road or trail). - `fixed` — hold one constant height for the whole corridor (a level causeway, runway, or dam crest). - `grade` — interpolate linearly from `start` to `end` height along the path's arc length, a constant-slope ramp between two anchors (a switchback, a graded rail bed, an accessibility ramp).
|
|
@@ -2731,12 +2730,14 @@
|
|
|
2731
2730
|
|
|
2732
2731
|
## @jgengine/core/world/roads
|
|
2733
2732
|
|
|
2733
|
+
- `DashExclusion` (interface): interface DashExclusion — A circular exclusion zone: dashes whose midpoint falls inside are dropped (e.g. junction patches).
|
|
2734
2734
|
- `RoadPoint` (type): type RoadPoint = readonly [number, number] — A road centerline vertex in world XZ.
|
|
2735
2735
|
- `RoadRibbon` (interface): interface RoadRibbon — Renderer-ready triangle ribbon: flat position triples plus triangle indices.
|
|
2736
2736
|
- `RoadRibbonOptions` (interface): interface RoadRibbonOptions — Options for {@link buildRoadRibbon}.
|
|
2737
2737
|
- `RoadSample` (interface): interface RoadSample — Result of {@link nearestOnPath}: closest point on the centerline plus distance and tangent.
|
|
2738
|
+
- `buildJunctionPatch` (function): function buildJunctionPatch(center: RoadPoint, radius: number, sampleHeight: (x: number, z: number) => number, options: { elevation?: number; segments?: number } = {}): RoadRibbon — Build a flat, ground-draped disc patch centered on `center` — the welded junction surface that covers crossing road ribbons so they read as one intersection instead of stacking/z-fighting. A triangle fan of `segments` sides (default 16), every vertex draped at `sampleHeight + elevation`. Pure geometry; the shell meshes the result with the asphalt material.
|
|
2738
2739
|
- `buildRoadRibbon` (function): function buildRoadRibbon(path: readonly RoadPoint[], width: number, sampleHeight: (x: number, z: number) => number, options: RoadRibbonOptions = {}): RoadRibbon — Triangulate a road centerline into a ground-draped ribbon mesh: the polyline is subdivided, each vertex is offset half a `width` along the local perpendicular, and every vertex sits at `sampleHeight(x, z) + elevation`. Pure geometry — the shell (or any renderer) turns the result into a mesh, and tests can assert on it directly.
|
|
2739
|
-
- `dashSegments` (function): function dashSegments(path: readonly RoadPoint[], dashLength = 3, gapLength = 3): readonly (readonly RoadPoint[])[] — Split a centerline into dash sub-polylines for lane markings: `dashLength` of painted line, `gapLength` of asphalt, repeated along the path's arc length. Feed each returned sub-path back through {@link buildRoadRibbon} with a thin width to mesh the dashes.
|
|
2740
|
+
- `dashSegments` (function): function dashSegments(path: readonly RoadPoint[], dashLength = 3, gapLength = 3, exclude: readonly DashExclusion[] = []): readonly (readonly RoadPoint[])[] — Split a centerline into dash sub-polylines for lane markings: `dashLength` of painted line, `gapLength` of asphalt, repeated along the path's arc length. Feed each returned sub-path back through {@link buildRoadRibbon} with a thin width to mesh the dashes. Pass `exclude` circles (junction patches) to interrupt the center line through intersections — any dash whose midpoint lands inside an exclusion is dropped.
|
|
2740
2741
|
- `isOnRoad` (function): function isOnRoad(path: readonly RoadPoint[], width: number, x: number, z: number): boolean — True when the query point lies within half the road `width` of the centerline.
|
|
2741
2742
|
- `nearestOnPath` (function): function nearestOnPath(path: readonly RoadPoint[], x: number, z: number): RoadSample | null — Closest-point query against a road centerline — the seam traffic AI, spawn placement, and "am I on the road" checks share. Returns null for a degenerate path.
|
|
2742
2743
|
- `pathLength` (function): function pathLength(path: readonly RoadPoint[]): number — Total arc length of a centerline in world units.
|
|
@@ -2837,16 +2838,43 @@
|
|
|
2837
2838
|
- `sphereRegion` (function): function sphereRegion(center: Point3, radius: number, options: { distribution?: VolumeDistribution } = {}): SampleRegion<Point3> — A filled ball. `"volume"` is volume-uniform (∛-corrected radius); `"radial"` is radius-uniform. Direction is drawn first (two draws), then radius.
|
|
2838
2839
|
- `weightedRegion` (function): function weightedRegion<P extends SamplePoint>(entries: readonly WeightedRegionEntry<P>[]): SampleRegion<P> — A composite that first picks one member by weight, then delegates to its sampler — the "weighted subregions" distribution policy. `contains` is true when any member contains the point.
|
|
2839
2840
|
|
|
2841
|
+
## @jgengine/core/world/streetGenerator
|
|
2842
|
+
|
|
2843
|
+
- `Street` (interface): interface Street — One chained through-street: a maximal run of edges through degree-2 nodes, for rendering + furniture.
|
|
2844
|
+
- `StreetDeadEnd` (interface): interface StreetDeadEnd — One dangling street end kept as a cul-de-sac: node position plus the heading pointing off the road.
|
|
2845
|
+
- `StreetEdge` (interface): interface StreetEdge — One atomic node-to-node edge — the fabric graph consumes these (welds at shared node coords).
|
|
2846
|
+
- `StreetFeature` (interface): interface StreetFeature — A resolved path feature in world-of-the-volume space: a bridge deck or tunnel bore centerline.
|
|
2847
|
+
- `StreetFeatureKind` (type): type StreetFeatureKind = "bridge" | "tunnel" — A path feature spanning part of an edge/street: a bridge deck over a gap or a tunnel bore under a ridge.
|
|
2848
|
+
- `StreetFeatureSpan` (interface): interface StreetFeatureSpan — A feature span carried by a street: a `[from, to]` index window into the street's `points`.
|
|
2849
|
+
- `StreetJunction` (interface): interface StreetJunction — One crossing of three or more streets: patch center/radius plus outgoing arm directions.
|
|
2850
|
+
- `StreetLevel` (type): type StreetLevel = "boulevard" | "avenue" | "street" | "lane" — Road hierarchy, widest to narrowest — shared by the city fabric and the renderer.
|
|
2851
|
+
- `StreetNetwork` (interface): interface StreetNetwork — The fully-resolved network in volume-local coords.
|
|
2852
|
+
- `StreetNetworkContext` (interface): interface StreetNetworkContext — Ground sampler + feature toggles enabling bridges/tunnels; omit for a flat, feature-free network.
|
|
2853
|
+
- `StreetNetworkMode` (type): type StreetNetworkMode = "net" | "circuit" — The generator's chosen topology family: an open street `net`, or a closed `circuit` loop.
|
|
2854
|
+
- `StreetNetworkRules` (interface): interface StreetNetworkRules — Fully-defaulted slider set the generator reads.
|
|
2855
|
+
- `StreetNode` (interface): interface StreetNode — One graph node: a junction, a dead end, or a mid-street bend, with its connection count.
|
|
2856
|
+
- `StreetVec2` (type): type StreetVec2 = readonly [number, number] — A path vertex in the volume-local XZ frame.
|
|
2857
|
+
- `generateStreets` (function): function generateStreets(rules: StreetNetworkRules, hx: number, hz: number, context: StreetNetworkContext = {}): StreetNetwork — Resolve a full path network from its rules inside a volume of half-extents `hx`×`hz`. Deterministic per `(rules.seed, hx, hz, context)`. Pass a {@link StreetNetworkContext} with a ground sampler to turn water gaps into bridges and ridges into tunnels. Coordinates are volume-local; the caller maps to world space.
|
|
2858
|
+
|
|
2840
2859
|
## @jgengine/core/world/streets
|
|
2841
2860
|
|
|
2842
2861
|
- `FurnitureSpot` (interface): interface FurnitureSpot — A placement anchor on the curb line: position, outward-facing heading, and which side it sits on.
|
|
2843
2862
|
- `FurnitureSpotOptions` (interface): interface FurnitureSpotOptions — Options for {@link furnitureSpots}.
|
|
2863
|
+
- `JunctionMarkingOptions` (interface): interface JunctionMarkingOptions — Options for {@link junctionMarkings}.
|
|
2864
|
+
- `JunctionMarkings` (interface): interface JunctionMarkings — Thin marking polylines for a junction: stop-line bars and crosswalk bars, ready for {@link buildRoadRibbon}.
|
|
2844
2865
|
- `ParkingSpot` (interface): interface ParkingSpot — A curbside parking anchor: position at the road edge and a heading parallel to the street.
|
|
2845
2866
|
- `ParkingSpotOptions` (interface): interface ParkingSpotOptions — Options for {@link parkingSpots}.
|
|
2867
|
+
- `RoadApproach` (interface): interface RoadApproach — A single arm of a junction: the road width plus the outward direction the road leaves the center.
|
|
2868
|
+
- `RoadJunction` (interface): interface RoadJunction — A welded road intersection: a patch center/radius plus the arms that feed it.
|
|
2869
|
+
- `RoadJunctionOptions` (interface): interface RoadJunctionOptions — Options for {@link findRoadJunctions}.
|
|
2846
2870
|
- `RoadSurfaceOptions` (interface): interface RoadSurfaceOptions — Grip levels and blend width for {@link roadSurfaceSampler}; each field is optional and defaulted.
|
|
2847
2871
|
- `StreetLane` (interface): interface StreetLane — A directed lane derived from a road: an offset centerline plus its direction of travel.
|
|
2872
|
+
- `curbPaths` (function): function curbPaths(road: RoadEnvironmentDescriptor, inset = 0): readonly [readonly RoadPoint[], readonly RoadPoint[]] — The two curb lines of a road — offset polylines running along the outer edge of the asphalt on each side. Mesh these with a thin darker ribbon to give the road a curb/edge strip instead of a hard asphalt-to-terrain seam. `inset` (default 0) nudges the line inward off the raw edge so the strip straddles the border.
|
|
2848
2873
|
- `distanceToRoadEdge` (function): function distanceToRoadEdge(roads: readonly RoadEnvironmentDescriptor[], x: number, z: number): number — Signed planar distance from `(x, z)` to the nearest road *surface edge* across every segment of every road (#1051): negative inside the asphalt (magnitude = how far in from the closest edge, so `-width/2` on the centerline), `~0` right at the curb, and positive off the road (the gap to the nearest edge). Point-to-segment distance minus the road's half width, minimised over all segments — the "am I on the road, and by how much" seam driving grip, tyre audio, and off-road penalties.
|
|
2874
|
+
- `findRoadJunctions` (function): function findRoadJunctions(roads: readonly RoadEnvironmentDescriptor[], options: RoadJunctionOptions = {}): readonly RoadJunction[] — Detect welded intersections across a set of roads: wherever two road centerlines cross (or a road terminates on another) within the same elevation band, emit a {@link RoadJunction} whose patch covers the crossing ribbons. Nearby crossings merge into one junction. Deterministic and bounded — O(roads² · segments²) with a `maxJunctions` cap — so a scene builds its intersections once.
|
|
2849
2875
|
- `furnitureSpots` (function): function furnitureSpots(road: RoadEnvironmentDescriptor, options: FurnitureSpotOptions = {}): readonly FurnitureSpot[] — Evenly spaced street-furniture anchors along a road's curb lines — streetlights, palms, signs, hydrants, benches. Each spot sits just outside the asphalt (plus `outset`), faces away from the street, and alternates sides by default so lights stagger like a real avenue. This is the answer to "where do I put it": furniture is an asset of the street, never a hand-typed coordinate.
|
|
2876
|
+
- `junctionExclusions` (function): function junctionExclusions(junctions: readonly RoadJunction[], pad = 0.5): readonly DashExclusion[] — Circular marking-exclusion zones for the junctions, sized to hide the center line under each patch.
|
|
2877
|
+
- `junctionMarkings` (function): function junctionMarkings(junction: RoadJunction, options: JunctionMarkingOptions = {}): JunctionMarkings — Data-driven junction entry markings: a stop-line bar across each approach and a band of crosswalk bars just outside the welded patch. Each returned polyline is a thin centerline meant to be meshed with {@link buildRoadRibbon} at a small width. Fully skippable per-feature via the options.
|
|
2850
2878
|
- `laneCenters` (function): function laneCenters(road: RoadEnvironmentDescriptor): readonly [StreetLane, StreetLane] — Two right-hand-traffic lane centerlines for a road — each offset a quarter of the drivable width from the center and ordered in its direction of travel. Feed a lane's `path` straight into `nav/pathFollow` for traffic AI, or use its endpoints as directed car spawn points.
|
|
2851
2879
|
- `offsetPath` (function): function offsetPath(path: readonly RoadPoint[], offset: number): readonly RoadPoint[] — Offset a centerline sideways by a signed distance along its local perpendicular — the building block for lanes, curb lines, and sidewalk paths. Positive offsets fall on the left of the direction of travel, negative on the right.
|
|
2852
2880
|
- `parkingSpots` (function): function parkingSpots(road: RoadEnvironmentDescriptor, options: ParkingSpotOptions = {}): readonly ParkingSpot[] — Curbside parking anchors along a road: hugging the edge of the asphalt, headed parallel to the street in that side's direction of travel. Spawn parked vehicles here instead of eyeballing coordinates in the middle of the carriageway.
|
|
@@ -109,6 +109,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
109
109
|
|
|
110
110
|
- `createBuildSocketCatalog` (function) · `import { createBuildSocketCatalog } from "@jgengine/core/world/buildSockets"`
|
|
111
111
|
|
|
112
|
+
## building-lots — derive street-facing building lots from road frontage
|
|
113
|
+
|
|
114
|
+
- `deriveBuildingLots` (function) · `import { deriveBuildingLots } from "@jgengine/core/world/buildingLots"`
|
|
115
|
+
|
|
112
116
|
## city-district — polygon math for road-derived blocks, parcels, and buildable footprints
|
|
113
117
|
|
|
114
118
|
- `Vec2` (type) · `import { Vec2 } from "@jgengine/core/world/cityGeometry"`
|
|
@@ -281,10 +285,6 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
281
285
|
|
|
282
286
|
- `pathLength` (function) · `import { pathLength } from "@jgengine/core/world"`
|
|
283
287
|
|
|
284
|
-
## path-network — build a deterministic road/track graph (nodes, edges, streets, junctions, bridges, tunnels) from sliders
|
|
285
|
-
|
|
286
|
-
- `buildPathNetwork` (function) · `import { buildPathNetwork } from "@jgengine/core/world/pathNetwork"`
|
|
287
|
-
|
|
288
288
|
## path-terrain — apply flatten/grade/carve/retaining path profiles to a terrain field
|
|
289
289
|
|
|
290
290
|
- `applyPathProfiles` (function) · `import { applyPathProfiles } from "@jgengine/core/world"`
|
|
@@ -415,6 +415,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
415
415
|
|
|
416
416
|
- `defineStopOrder` (function) · `import { defineStopOrder } from "@jgengine/core/world"`
|
|
417
417
|
|
|
418
|
+
## street-generator — build a deterministic street/track graph (nodes, edges, streets, junctions, bridges, tunnels) from sliders
|
|
419
|
+
|
|
420
|
+
- `generateStreets` (function) · `import { generateStreets } from "@jgengine/core/world/streetGenerator"`
|
|
421
|
+
|
|
418
422
|
## surface-drape — lift an XZ polyline onto sculpted terrain
|
|
419
423
|
|
|
420
424
|
- `drapePolyline` (function) · `import { drapePolyline } from "@jgengine/core/world"`
|
|
@@ -115,7 +115,7 @@ Turn data-only placement into the build tooling of Valheim/Enshrouded/The Sims/F
|
|
|
115
115
|
|
|
116
116
|
**Body shape: box or sphere.** `AddBodyOptions` is a discriminated union — `{ shape?: "box", halfExtents }` (box is the default, `shape` omittable) or `{ shape: "sphere", radius }` (the radius fills all three half-extent columns, so broadphase/bounds see the sphere's enclosing AABB). `world.shape[i]` (`SHAPE_BOX` / `SHAPE_SPHERE`) reports a live body's shape for a consumer walking the raw SoA arrays. Sphere-sphere and sphere-box pairs resolve with a proper radial normal (not the axis-aligned box/box path) — balls, projectile bodies, and rolling debris collide correctly against both boxes and each other.
|
|
117
117
|
|
|
118
|
-
**Collision shapes are box/sphere/voxel — plus opt-in triangle meshes for raycasts and mesh-accurate walk obstruction.** Every collider in the engine — `PhysicsWorld` bodies (box `halfExtents` or sphere `radius`), object/entity picking (`scene/objectQuery` raycasts against unit boxes), and `world/voxelField`/`world/carve` blocks — is an axis-aligned box, a sphere, or a voxel cell. The one exception is opted-in catalog assets: an asset listed in `@jgengine/assets` `COLLISION_MESH_ASSET_IDS` ships a compact quantized triangle mesh in the generated index (`scene/collisionMesh` `CollisionMeshData`, extracted from the GLB at reindex — never from the rendered scene, so a headless host resolves the same shape) **plus a deterministic compound-box decomposition** (`voxelizeToBoxes` → `CollisionMeshData.boxes`, a voxel-rasterize + column-fill + greedy-merge of the model's solid volume). Collider auto-fit then (a) raycasts the actual triangles through a precomputed BVH — shots pass through a torus hole or an archway instead of hitting the fitted box; and (b) exposes the compound boxes on the fitted `mesh` `ColliderShape` (`ColliderShape.mesh.boxes`, scaled/translated onto the placement like the mesh). The `collideObjects` walk controller consumes these in `stepPlayerMovement` (host and client alike, so parity is automatic): it gathers obstacles from resolved blocking physical colliders via a bounded `object.inBox` broadphase (reach ≥ radius + max half-extent, so a wide wall's full span blocks, not a phantom 1 m box), carries each collider's real `halfExtents`/`offset` — or, for a mesh collider, the compound sub-boxes — yaw-rotated onto the obstacle, and slides the capsule against **each** sub-box (`resolveObstacleStep`), so a capsule walks THROUGH an archway opening and is stopped by its pillars/lintel/sill. `PhysicsWorld` and overlap tests still see the conservative fitted AABB; objects
|
|
118
|
+
**Collision shapes are box/sphere/voxel — plus opt-in triangle meshes for raycasts and mesh-accurate walk obstruction.** Every collider in the engine — `PhysicsWorld` bodies (box `halfExtents` or sphere `radius`), object/entity picking (`scene/objectQuery` raycasts against unit boxes), and `world/voxelField`/`world/carve` blocks — is an axis-aligned box, a sphere, or a voxel cell. The one exception is opted-in catalog assets: an asset listed in `@jgengine/assets` `COLLISION_MESH_ASSET_IDS` ships a compact quantized triangle mesh in the generated index (`scene/collisionMesh` `CollisionMeshData`, extracted from the GLB at reindex — never from the rendered scene, so a headless host resolves the same shape) **plus a deterministic compound-box decomposition** (`voxelizeToBoxes` → `CollisionMeshData.boxes`, a voxel-rasterize + column-fill + greedy-merge of the model's solid volume). Collider auto-fit then (a) raycasts the actual triangles through a precomputed BVH — shots pass through a torus hole or an archway instead of hitting the fitted box; and (b) exposes the compound boxes on the fitted `mesh` `ColliderShape` (`ColliderShape.mesh.boxes`, scaled/translated onto the placement like the mesh). The `collideObjects` walk controller consumes these in `stepPlayerMovement` (host and client alike, so parity is automatic): it gathers obstacles from resolved blocking physical colliders via a bounded `object.inBox` broadphase (reach ≥ radius + max half-extent, so a wide wall's full span blocks, not a phantom 1 m box), carries each collider's real `halfExtents`/`offset` — or, for a mesh collider, the compound sub-boxes — yaw-rotated onto the obstacle, and slides the capsule against **each** sub-box (`resolveObstacleStep`), so a capsule walks THROUGH an archway opening and is stopped by its pillars/lintel/sill. `PhysicsWorld` and overlap tests still see the conservative fitted AABB; objects whose colliders don't block stop obstructing, and non-opted assets keep the fitted box everywhere. Objects with no index `dims` are not stuck with the default unit box either: the shell measures what `renderObject`/dims-less models actually mount and reports it through `ctx.scene.object.reportBounds`, so a custom-rendered prop obstructs (and raycasts) as its real rendered footprint; only objects with nothing measurable keep the default box. The seams for custom *walk* collision remain `movement.beforeCommit` and object raycasts.
|
|
119
119
|
|
|
120
120
|
**Ballistic collision sweep (`physics/ballisticSweep`).** `createBallisticSweep(world, { step?, radius? })` → `BallisticSweep`, a `(origin, velocity, gravity, maxTime) => BallisticSweepHit | null` function that marches the closed-form arc (constant gravity, straight lateral) through a `PhysicsWorld` and reports the first sample inside any live body's AABB (sleeping bodies included), refined by one bisection step; `null` means the whole arc is clear. `step` (default 1/60) is the march interval in seconds, `radius` (default 0) inflates every body's AABB before the point test — pass the projectile's own radius. Wire it into `combat/projectiles` via `ProjectileSystemDeps.sweepBallistic`: when set, a ballistic shot settles at the swept impact point instead of the closed-form landing; omitted or `null` falls back to that closed-form arc.
|
|
121
121
|
|