@jgengine/shell 0.12.0 → 0.14.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 +73 -1
- package/dist/CombatPresentation.js +2 -1
- package/dist/GameHost.d.ts +2 -0
- package/dist/GameHost.js +25 -2
- package/dist/GamePlayerShell.js +15 -1
- package/dist/Shell3dPresentation.js +2 -2
- package/dist/camera/GameOrbitCamera.js +7 -1
- package/dist/camera/cameraCollision.d.ts +28 -0
- package/dist/camera/cameraCollision.js +34 -0
- package/dist/camera/cameraRigs.js +8 -8
- package/dist/camera/index.d.ts +1 -0
- package/dist/camera/index.js +1 -0
- package/dist/camera/rigMath.d.ts +7 -0
- package/dist/camera/rigMath.js +16 -0
- package/dist/devtools/CollisionDebugWorld.js +15 -1
- package/dist/devtools/collisionDebugMath.d.ts +8 -0
- package/dist/devtools/collisionDebugMath.js +14 -5
- package/dist/environment/EnvironmentScene.js +6 -1
- package/dist/environment/RoadJunctions.d.ts +12 -0
- package/dist/environment/RoadJunctions.js +69 -0
- package/dist/environment/RoadRibbons.d.ts +8 -2
- package/dist/environment/RoadRibbons.js +26 -8
- package/dist/postfx/PostProcessing.js +27 -2
- package/dist/postfx/postfxOverlay.d.ts +24 -0
- package/dist/postfx/postfxOverlay.js +35 -0
- package/dist/presentationResolve.d.ts +5 -1
- package/dist/presentationResolve.js +3 -1
- package/dist/render/EntityPreview.d.ts +53 -0
- package/dist/render/EntityPreview.js +23 -0
- package/dist/render/PartMotion.d.ts +18 -0
- package/dist/render/PartMotion.js +91 -0
- package/dist/render/SceneModels.d.ts +9 -2
- package/dist/render/SceneModels.js +43 -179
- package/dist/render/measureBounds.d.ts +34 -0
- package/dist/render/measureBounds.js +131 -0
- package/dist/render/measureCollisionMesh.d.ts +26 -0
- package/dist/render/measureCollisionMesh.js +108 -0
- package/dist/render/resolveModel.js +16 -10
- package/dist/render/useModelAnimation.d.ts +10 -0
- package/dist/render/useModelAnimation.js +198 -0
- package/dist/structures/GeneratedBuilding.d.ts +4 -0
- package/dist/structures/GeneratedBuilding.js +17 -7
- package/dist/world/WorldHud.d.ts +3 -1
- package/dist/world/WorldHud.js +2 -2
- package/dist/world/WorldScene.js +3 -2
- package/package.json +4 -4
- package/skills/jgengine/SKILL.md +5 -2
- package/skills/jgengine/api.md +13 -5
- package/skills/jgengine-ui/SKILL.md +9 -4
- package/skills/jgengine-ui/api.md +138 -8
- package/skills/jgengine-ui/capabilities.md +72 -0
- package/skills/jgengine-ui/reference.md +40 -3
- package/skills/jgengine-world/api.md +82 -25
- package/skills/jgengine-world/capabilities.md +12 -4
- package/skills/jgengine-world/reference.md +1 -1
|
@@ -28,6 +28,22 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
28
28
|
|
|
29
29
|
- `ActionTooltip` (function) · `import { ActionTooltip } from "@jgengine/react"`
|
|
30
30
|
|
|
31
|
+
## camera-transparent-decor — let author decor pass through the orbit spring-arm; a child opts back in with jgCameraCollide
|
|
32
|
+
|
|
33
|
+
- `isCameraOccluderTransparent` (function) · `import { isCameraOccluderTransparent } from "@jgengine/shell/camera"`
|
|
34
|
+
|
|
35
|
+
## character-paperdoll — equip-slot paperdoll — flanking columns around a portrait, activatable slots
|
|
36
|
+
|
|
37
|
+
- `Paperdoll` (function) · `import { Paperdoll } from "@jgengine/react"`
|
|
38
|
+
|
|
39
|
+
## character-sheet — drop-in paperdoll + derived-stat sheet in a HudFrame — game supplies slots/stats
|
|
40
|
+
|
|
41
|
+
- `CharacterSheet` (function) · `import { CharacterSheet } from "@jgengine/react"`
|
|
42
|
+
|
|
43
|
+
## character-statlist — derived-stat readout — grouped small-caps label→value rows, token-themed
|
|
44
|
+
|
|
45
|
+
- `StatList` (function) · `import { StatList } from "@jgengine/react"`
|
|
46
|
+
|
|
31
47
|
## clock-format — format a signed time gap like a race split (+/- m:ss.ff)
|
|
32
48
|
|
|
33
49
|
- `formatDelta` (function) · `import { formatDelta } from "@jgengine/core/ui"`
|
|
@@ -49,6 +65,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
49
65
|
|
|
50
66
|
- `EntityPortrait` (function) · `import { EntityPortrait } from "@jgengine/react"`
|
|
51
67
|
|
|
68
|
+
## entity-preview — live 3D entity portrait — nested Canvas + context bridge + studio stage, driven by the game's own renderEntity
|
|
69
|
+
|
|
70
|
+
- `EntityPreview` (function) · `import { EntityPreview } from "@jgengine/shell/render/EntityPreview"`
|
|
71
|
+
|
|
52
72
|
## entity-render-cues — live velocity/bob/fire/reload/hit/death cues for a custom renderEntity or viewmodel component
|
|
53
73
|
|
|
54
74
|
- `useEntityRenderCues` (function) · `import { useEntityRenderCues } from "@jgengine/shell/render/useEntityRenderCues"`
|
|
@@ -61,6 +81,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
61
81
|
|
|
62
82
|
- `useEventMeter` (function) · `import { useEventMeter } from "@jgengine/react"`
|
|
63
83
|
|
|
84
|
+
## game-context-bridge — re-provide the GameContext across a nested Canvas/reconciler boundary
|
|
85
|
+
|
|
86
|
+
- `GameContextBridge` (function) · `import { GameContextBridge } from "@jgengine/react"`
|
|
87
|
+
|
|
64
88
|
## heightfield-mesh-update — in-place partial update of a displaced ground plane mesh
|
|
65
89
|
|
|
66
90
|
- `displaceHeightfieldGeometry` (function) · `import { displaceHeightfieldGeometry } from "@jgengine/shell/terrain"`
|
|
@@ -158,6 +182,46 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
158
182
|
|
|
159
183
|
- `formatOrdinal` (function) · `import { formatOrdinal } from "@jgengine/core/ui"`
|
|
160
184
|
|
|
185
|
+
## panel-close — close a window while remembering its last position
|
|
186
|
+
|
|
187
|
+
- `closePanel` (function) · `import { closePanel } from "@jgengine/core/ui"`
|
|
188
|
+
|
|
189
|
+
## panel-close-top — close the focused (topmost) closable window — the ESC behavior
|
|
190
|
+
|
|
191
|
+
- `closeTopPanel` (function) · `import { closeTopPanel } from "@jgengine/core/ui"`
|
|
192
|
+
|
|
193
|
+
## panel-focus — raise a window to the top of the focus/z stack
|
|
194
|
+
|
|
195
|
+
- `focusPanel` (function) · `import { focusPanel } from "@jgengine/core/ui"`
|
|
196
|
+
|
|
197
|
+
## panel-host — render a manager's open windows as draggable, closable, z-stacked dialogs above the HUD
|
|
198
|
+
|
|
199
|
+
- `PanelHost` (function) · `import { PanelHost } from "@jgengine/react"`
|
|
200
|
+
|
|
201
|
+
## panel-hotkey — resolve a keybind (code or key) to the panel id it toggles
|
|
202
|
+
|
|
203
|
+
- `panelByHotkey` (function) · `import { panelByHotkey } from "@jgengine/core/ui"`
|
|
204
|
+
|
|
205
|
+
## panel-move — override a window's on-surface position (the drag commit)
|
|
206
|
+
|
|
207
|
+
- `movePanel` (function) · `import { movePanel } from "@jgengine/core/ui"`
|
|
208
|
+
|
|
209
|
+
## panel-open — open a window, raise its z, and enforce exclusive-group membership
|
|
210
|
+
|
|
211
|
+
- `openPanel` (function) · `import { openPanel } from "@jgengine/core/ui"`
|
|
212
|
+
|
|
213
|
+
## panel-ordered-open — open panels' defs in z/render order for a window host
|
|
214
|
+
|
|
215
|
+
- `orderedOpen` (function) · `import { orderedOpen } from "@jgengine/core/ui"`
|
|
216
|
+
|
|
217
|
+
## panel-state — serializable open/z/position state for a set of toggleable game windows
|
|
218
|
+
|
|
219
|
+
- `createPanelState` (function) · `import { createPanelState } from "@jgengine/core/ui"`
|
|
220
|
+
|
|
221
|
+
## panel-toggle — toggle a window open/closed from a single keybind
|
|
222
|
+
|
|
223
|
+
- `togglePanel` (function) · `import { togglePanel } from "@jgengine/core/ui"`
|
|
224
|
+
|
|
161
225
|
## placement-ghost — render valid/invalid placement preview mesh
|
|
162
226
|
|
|
163
227
|
- `PlacementGhost` (function) · `import { PlacementGhost } from "@jgengine/shell/structures"`
|
|
@@ -202,10 +266,18 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
202
266
|
|
|
203
267
|
- `TransformGizmo` (const) · `import { TransformGizmo } from "@jgengine/shell/structures"`
|
|
204
268
|
|
|
269
|
+
## use-panels — headless toggleable-window manager with keybind + ESC handling over the core panel model
|
|
270
|
+
|
|
271
|
+
- `usePanels` (function) · `import { usePanels } from "@jgengine/react"`
|
|
272
|
+
|
|
205
273
|
## vital-bar — compact current/max vital meter for an entity summary
|
|
206
274
|
|
|
207
275
|
- `VitalBar` (function) · `import { VitalBar } from "@jgengine/react"`
|
|
208
276
|
|
|
277
|
+
## window — standalone draggable, closable window primitive over HudFrame — no manager required
|
|
278
|
+
|
|
279
|
+
- `Window` (function) · `import { Window } from "@jgengine/react"`
|
|
280
|
+
|
|
209
281
|
## world-object-highlights — ground-ring highlight over every selected placed object
|
|
210
282
|
|
|
211
283
|
- `WorldObjectHighlights` (function) · `import { WorldObjectHighlights } from "@jgengine/shell/world/WorldHud"`
|
|
@@ -6,7 +6,7 @@ This reference defines the required visual and interaction quality for JGengine
|
|
|
6
6
|
|
|
7
7
|
A game must visually own its viewport **and its entire interface**. Presentation is game content: each title designs a custom HUD, menus, feedback, and art direction. It must not resemble a dashboard, landing page, documentation page, ordinary responsive web app, **or another JGengine game's default widget stack**.
|
|
8
8
|
|
|
9
|
-
The engine
|
|
9
|
+
The engine ships both headless data/layout/accessibility seams *and* good drop-in building blocks — inventory grids, toggleable windows, character sheets, bars, action bars. `@jgengine/react` offers all of them. Composing and reskinning those building blocks into this game's arrangement is using the engine correctly. What's incomplete is shipping a whole *unarranged, unskinned generic face* — a page that looks like a dashboard or another game's untouched default stack, with no art direction, terminology, or placement of its own.
|
|
10
10
|
|
|
11
11
|
HTML and React are valid implementation tools. Website visual grammar is not the default. Engine default chrome is not the default either.
|
|
12
12
|
|
|
@@ -51,11 +51,14 @@ Identify the screens the game actually needs:
|
|
|
51
51
|
|
|
52
52
|
- boot/loading
|
|
53
53
|
- title or attract screen
|
|
54
|
+
- **main menu** (the game's front-end — see the Main menu section)
|
|
54
55
|
- mode selection
|
|
56
|
+
- character create / character select where the game has classes, characters, or profiles
|
|
55
57
|
- onboarding/tutorial
|
|
56
58
|
- gameplay HUD
|
|
57
59
|
- pause
|
|
58
60
|
- settings
|
|
61
|
+
- credits
|
|
59
62
|
- map/inventory/dialogue where relevant
|
|
60
63
|
- victory/results
|
|
61
64
|
- failure/retry
|
|
@@ -239,6 +242,12 @@ instead of mirroring external entities into `createMarkerSet`.
|
|
|
239
242
|
|
|
240
243
|
**Action cards / command bars / ability bars** are a headless data → renderer → chrome split, not a hand-rolled cooldown grid. The model is caller-owned data: build `ActionDef[]` (`@jgengine/core/ui/actionModel` — `id`, optional `label`/`icon`/`hotkey`/`group`/`active`/`disabled`, plus `cooldown` via `actionCooldown`/`actionCooldownFromFraction` and `costs` via `actionCost`); `resolveAction`/`resolveActionCollection` compute `enabled` + ordered blocking `reasons` (cooldown, then unmet cost, then caller reasons). React side (`@jgengine/react`): `useActionBar(defs, { columns, wrap, hotkeys, onActivate })` is the DATA/HOOK layer (`action-bar-model` — focus, hover, `moveGridFocus` keyboard/controller grid nav, `actionByHotkey` routing); `ActionBarChrome` is the composable RENDERER (`action-bar-chrome` — `role="toolbar"`, roving tabindex, `layout: "grid" | "list"`, swap per-item chrome with `renderItem`); `ActionButton` is the default item chrome (`action-button` — radial cooldown wipe, cost line, `Keycap` hotkey, full rest/hover/focus/pressed/active/disabled states, `aria-disabled`/`aria-pressed`/`aria-keyshortcuts`); `ActionBar` is the batteries-included skinned default. A radial UI ignores the chrome and lays out `ActionButton`s from the model. Bind a combat `AbilityKit` with `actionFromAbilitySlot(slot, { label, icon, hotkey, resourceId })` over `useAbilitySlots`. Tooltips use the shared seam (no more DOM `title=` hand-rolling): `actionTooltip(resolved)` → `TooltipContent`, rendered by `ActionTooltip` inside a `Popover` (`popover` — pure `placePopover` viewport flip/clamp math from `@jgengine/core/ui/tooltipModel`, SSR-safe). **Selection panels / party frames / squad summaries** are the same split for entities: feed caller-owned `EntitySummaryDef[]` (`@jgengine/core/ui/selectionModel` — `id`, `name`, `kind`, `icon`, `vitals`, `tags`) to `summarizeSelection` (primary focus, `kind` buckets, `grouped` past a threshold, `selectionWindow` virtualization) or the `useSelectionView` hook (`selection-model` — focus stepping + keyboard nav). Renderers (`@jgengine/react`): `EntityPortrait` (`entity-portrait` selectable tile + health strip), `EntitySummary` (`entity-summary` primary detail with `VitalBar` rows), `SelectionGroupChip` (`selection-group-chip` icon+count), `SelectionCollectionChrome` (`selection-collection` portrait strip that flips to group chips when large, roving tabindex), and `SelectionPanel` (`selection-panel` batteries-included primary-summary-beside-strip default). Keep layout, dimensions, hotkeys, catalog-id mapping, renderer, and chrome caller-owned — the defaults are examples, not fixed RTS rules; never fork the model to reskin.
|
|
241
244
|
|
|
245
|
+
**Inventory grid** is already shipped, not hand-rolled: `<InventoryGrid inventoryId columns>` (`@jgengine/react`, backed by the `useInventoryGrid` hook) is a drop-in NxN grid bound to a live inventory — it renders the bound inventory's slots and wires drag-to-move / swap / stack / split through the built-in `inventory.move` / `inventory.split` commands, so a bag is one element plus a reskin, never a re-derived slot matrix over raw divs. It is distinct from the atomic `EquipmentSlots` / `WeaponSlots`, which are single-slot targets for paperdoll and equip layouts — reach for `InventoryGrid` for the bag/backpack surface and the atomic slots for the worn/equipped layout. Reskin via `className`/`data-*` slots and HudTheme tokens; the mechanics stay caller-agnostic.
|
|
246
|
+
|
|
247
|
+
**Toggleable game windows** are already shipped, not hand-rolled open/close state: `usePanels(defs)` + `<PanelHost>` + `<Window>` (`@jgengine/react`) over the headless `@jgengine/core/ui/panelModel` are THE way to build openable windows — bag (`B`), character (`C`), spellbook, quest log — with keybind toggles, drag-to-move, close buttons, ESC-to-close, and z-stack ordering all handled. Declare each window in `defs` (id, keybind, title), mount one `<PanelHost>`, and render each panel's body inside a `<Window>`; the game owns the body content and skin, the model owns focus/stacking/keybind wiring. Never re-derive window toggling from local `useState` booleans and hand-rolled keydown listeners.
|
|
248
|
+
|
|
249
|
+
**Character sheet / paperdoll** is already shipped, not hand-rolled: `<CharacterSheet>` / `<Paperdoll>` / `<StatList>` (`@jgengine/react`) compose the paperdoll equip layout (worn `EquipmentSlots` arranged around a portrait) and the derived-stat readout into one window body. `Paperdoll` is the equip-slot arrangement, `StatList` the derived-stat rows, `CharacterSheet` the batteries-included default that pairs them — the game composes and reskins them (slot layout, stat labels/terminology, skin) rather than re-deriving a paperdoll from raw divs and manual stat math.
|
|
250
|
+
|
|
242
251
|
**Toast/announcer stacks**: two supported sources, pick the one that matches where the message comes from. An engine event already bound onto `ctx.game.feed` (kill feed, loot log, quest update) renders with `@jgengine/react`'s `ToastStack({ action })` directly. A game-raised one-off message with its own lifetime (boss-intro banner, objective callout) belongs in a `createToastQueue` (`@jgengine/core/game/toasts`, `toast-feed` capability — see `jgengine-gameplay`) rendered from `queue.list()`; don't reimplement the append-cap-plus-TTL-prune loop as a local `Toast`/`pushToast` pair.
|
|
243
252
|
|
|
244
253
|
Do not create a primitive whose only value is wrapping a `div` with border radius.
|
|
@@ -296,8 +305,28 @@ Three composable-chrome seams for FPS/TPS polish — a placement hook plus a goo
|
|
|
296
305
|
- **Render cues for any custom rig.** `useEntityRenderCues(instanceId, tuning?)` (`@jgengine/shell/render/useEntityRenderCues`) returns the same `cuesRef` the viewmodel gets, for a custom `renderEntity` component: velocity-driven `bobPhase`, `firing`/`recoil` from `ctx.game.playEntityAnimation(instanceId, "fire")`, `reloading` from `"reload"`/`"reloadEnd"`, `hit` from `combat.hitReaction`, `dead` from `entity.died` — no diffing the parent group's position, no game-side module map for attack timing. `RenderCueTuning` (`@jgengine/core/combat/renderCues`) overrides the bob rate / recoil decay / pulse durations; see `jgengine-combat`'s `combat/renderCues` for the underlying pure math.
|
|
297
306
|
`@jgengine/shell/terrain`'s `ProceduralGround`/`CarvedTerrain`/`GrassField` are the game-facing terrain surface; the height-field math underneath (`arenaField`, `flatField`, `fractalNoise`, `resolveGroundStep`, `valueNoise`, `withNormal`, re-exported from `@jgengine/core/world/terrain`) and `GamePlayerShell`'s physics helpers (`applyMotionImpulses`, `nearbyObstacles`, `resolvePhysicsTuning`, `hasEnvironmentTerrain`, sourced from `core/runtime/motionIntents` / `core/movement`) are internal building blocks the shell composes for you — reach for `defineGame({ movement, world })` and the ground primitives above instead of calling these directly.
|
|
298
307
|
|
|
308
|
+
## Main menu
|
|
309
|
+
|
|
310
|
+
**The main menu is the game's front-end, not a marketing splash.** The hosting website/runner loads straight into the game's own menu (see rejection criteria) — there is no engine-supplied main menu, and the page must not render a title card, tagline, or "enter game" button of its own. What the player first sees is a screen the game designs and skins in its own art direction: the same shape/material/type language as its HUD, not a landing page, hero banner, feature list, or store page. Homage/credit lines are fine as small print; the screen's job is to start play, not to sell it.
|
|
311
|
+
|
|
312
|
+
Compose it from the game's own markup over the headless `StartScreen` (`@jgengine/react`, `start-screen` capability) — a full-bleed `data-jg-menu` overlay that centers your content and offers an opt-in settings corner; it imposes no look. Menu rows are the copy-in `MenuList` / `MenuButton` / `TitleScreen` registry scaffolds (keyboard/controller focus + `KeybindBadge` that self-hides on touch already wired) restyled into the game's chrome — never shipped as raw scaffold.
|
|
313
|
+
|
|
314
|
+
**The front-end entries, when the game has them.** A menu is more than one Start button. Offer the real front-end verbs a player expects, gated to what the game actually supports:
|
|
315
|
+
|
|
316
|
+
- **New Game** — begin a fresh run. When a save exists, keep it distinct from Continue rather than silently overwriting.
|
|
317
|
+
- **Continue / Load** — resume the most recent save, and (when the game keeps more than one) a **Load** entry that lists save slots. Save slots are `createSaveStore` instances keyed by slot id; read them reactively with `useSaveStore` (`@jgengine/react`, offline `localSaveBackend` or cloud `remoteSaveBackend` — same hook, different backend). Disable Continue/Load with a real disabled state when there is nothing to resume; do not hide the verb entirely.
|
|
318
|
+
- **Create Character / Choose Class** — when the game has characters, classes, loadouts, or profiles, the create/select flow lives here as an authored screen, not a stray dropdown. Keep the character/roster model serializable and game-owned (persist it through the game's save store); the menu is its presentation. Do not reach for a genre "class picker" preset — compose the choices this game actually offers.
|
|
319
|
+
- **Load Character / Roster** — choose an existing profile or party when the game persists more than one.
|
|
320
|
+
- **Settings** — reachable from the main menu, always (see Settings menu below). This is one of the two canonical places settings live; pause is the other.
|
|
321
|
+
- **Credits** — a real, reachable screen. Player-facing games owe HUD and website credit (see governance) and record borrowed work in `CREDITS.md`; the menu's Credits entry surfaces that in-game. Read it from the game's own credits data — do not bury attribution only in a build artifact.
|
|
322
|
+
- **Quit / Back to site** — where it makes sense for the host.
|
|
323
|
+
|
|
324
|
+
Only show entries the game supports, and disable (don't delete) verbs that are temporarily unavailable so the menu shape stays legible. Character create/select and Credits are their own authored sub-screens reached from the menu, composed to the same art direction — not modal form dumps.
|
|
325
|
+
|
|
299
326
|
## Settings menu
|
|
300
327
|
|
|
328
|
+
**Settings is game-owned and reachable from a place that makes sense.** Every game ships settings — but as *its* settings, skinned in its own art direction and placed inline with its own UI, never the engine's stock face dropped into a corner. The two canonical homes are the **main menu** and the **pause** screen; put the entry where the player would look, not floating over live play. The engine gives you the menu's contents and behavior for free — you own its skin, layout choice, and placement. If the default four layouts do not match the game, drive `useSettings()` and render the rows entirely inside your own chrome.
|
|
329
|
+
|
|
301
330
|
**Settings menu (themed, four layouts, no forced chrome).** The engine builds the whole menu for free — Sound (master + per-bus volume), Graphics (quality/dpr + shadows), Gameplay (FOV slider, default 40–120), Controls (per-action key rebinding, inline click-to-rebind, persisted) — from the game's `audio.buses` and `input` map. What it does **not** do is bolt a fixed gear onto every game: **there is no auto trigger.** You place the entry yourself so it lives *inline with your game's own UI*, never a stray corner overlay. Drop `<SettingsTrigger className=…>` (from `@jgengine/react`) anywhere in your HUD or menu — headless button, `className` for skin/placement, optional `children` to replace the default gear glyph, renders nothing when there's nothing to show. Or call `useSettings().open()` from your own control. Tune the menu via `defineGame({ settings })` (`GameSettingsConfig` from `@jgengine/core/settings/settingsModel`):
|
|
302
331
|
|
|
303
332
|
- `variant: "panel" | "sheet" | "sidebar" | "fullscreen"` — the layout + skin (default `panel`; `sheet` is the mobile bottom-sheet). All four are fixed-size (no shrink-to-content jitter) and read the game's `--jg-*` theme tokens, falling back to a neutral dark skin.
|
|
@@ -472,7 +501,7 @@ Require revision when any of these are true:
|
|
|
472
501
|
- controls overlap each other
|
|
473
502
|
- keyboard instructions appear on touch devices
|
|
474
503
|
- large instruction panels remain visible during gameplay
|
|
475
|
-
- more than three
|
|
504
|
+
- more than three undifferentiated, equally-weighted card-like panels are persistently visible (this is about generic card soup — using the shipped windows/bars is fine; the fault is unstyled boxes with no hierarchy)
|
|
476
505
|
- the main action looks like a standard website button
|
|
477
506
|
- pause resembles a generic website modal
|
|
478
507
|
- victory/failure is only text plus restart
|
|
@@ -487,6 +516,11 @@ Require revision when any of these are true:
|
|
|
487
516
|
- ordinary document flow determines the HUD layout
|
|
488
517
|
- generic default styling is used because no art direction was written
|
|
489
518
|
- the hosting website page renders its own title, tagline, or "enter game" button instead of loading straight into the game's own menu
|
|
519
|
+
- the game's own main menu reads as a marketing/landing page (hero banner, feature list, store framing) rather than a game front-end
|
|
520
|
+
- the game supports saves, characters, or classes but the main menu offers no Continue/Load or character create/select entry
|
|
521
|
+
- Continue/Load is hidden rather than shown disabled when there is nothing to resume
|
|
522
|
+
- settings ship as the engine's unskinned stock menu instead of the game's own skin and placement, or sit floating over live play with no sensible home
|
|
523
|
+
- credits are absent from the game, or attribution lives only in a build artifact instead of a reachable in-game screen
|
|
490
524
|
|
|
491
525
|
## 15. Compact implementation API appendix
|
|
492
526
|
|
|
@@ -494,7 +528,7 @@ Use the main `jgengine` skill for the authoritative engine API routing. The Reac
|
|
|
494
528
|
|
|
495
529
|
Use these APIs to bind state; do not let API wiring dictate visual composition. Keybind labels should derive from the game’s binding table, and UI actions should dispatch through game commands rather than existing only as click handlers.
|
|
496
530
|
|
|
497
|
-
|
|
531
|
+
The shipped building blocks — inventory grids, toggleable windows, character sheets, bars — are legitimate to compose and are the right starting point; reach for them instead of re-deriving inventory, windowing, or a paperdoll. But neither they nor the headless seams are finished design on their own: the game must still arrange, place, name, and art-direct them into its own look. Compose the building blocks, then skin and lay them out — don't ship them unarranged.
|
|
498
532
|
|
|
499
533
|
## Definition of done
|
|
500
534
|
|
|
@@ -507,6 +541,9 @@ UI work is complete only when:
|
|
|
507
541
|
- mobile controls do not cover critical content
|
|
508
542
|
- touch controls match the genre and game identity
|
|
509
543
|
- title, pause, and results screens feel authored
|
|
544
|
+
- the main menu is a game-owned front-end (New Game / Continue / Load / character create+select / Settings / Credits as the game supports them), not a marketing splash
|
|
545
|
+
- settings are game-skinned and reachable from the main menu and pause, not a stock corner overlay
|
|
546
|
+
- credits are reachable in-game and record borrowed work
|
|
510
547
|
- interaction states and transitions are present
|
|
511
548
|
- screenshots have been reviewed and revised
|
|
512
549
|
- the implementation remains accessible and performant
|
|
@@ -925,7 +925,7 @@
|
|
|
925
925
|
|
|
926
926
|
## @jgengine/core/scene/builtinSceneKinds
|
|
927
927
|
|
|
928
|
-
- `SCATTER_SCHEMA` (const): const SCATTER_SCHEMA: ParamSchema — The scatter/foliage region schema
|
|
928
|
+
- `SCATTER_SCHEMA` (const): const SCATTER_SCHEMA: ParamSchema — The scatter/foliage region schema. `palette` (what fills the area) leads the schema body and `density` is the single coverage slider hoisted into the shared `CoverageSection` (see the kind's `coverage` descriptor), so a scatter region reads as Area → Assets → Density like every other scatterable kind; the rest is kind-specific tuning.
|
|
929
929
|
|
|
930
930
|
## @jgengine/core/scene/captureCheck
|
|
931
931
|
|
|
@@ -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
|
|
|
@@ -954,6 +955,7 @@
|
|
|
954
955
|
- `PreparedCollisionMesh` (interface): interface PreparedCollisionMesh — Decoded, BVH-indexed triangles ready for per-ray queries — build once per asset via {@link prepareCollisionMesh}. Treat the fields as opaque; they exist so the raycast hot path can run allocation-free over flat arrays.
|
|
955
956
|
- `encodeCollisionMesh` (function): function encodeCollisionMesh(source: CollisionMeshSource): CollisionMeshData | null — Quantize a triangle soup onto the 16-bit grid, weld coincident vertices, and drop triangles that collapse — the reindex-time compressor producing {@link CollisionMeshData}. Returns `null` when no finite, non-degenerate triangle survives.
|
|
956
957
|
- `prepareCollisionMesh` (function): function prepareCollisionMesh(data: CollisionMeshData): PreparedCollisionMesh | null — Decode a stored collision mesh and index it with a BVH. Memoized per data object, so the shell and a headless host preparing the same catalog entry share one build. Returns `null` (also memoized) when the payload is malformed.
|
|
958
|
+
- `prepareCollisionMeshSource` (function): function prepareCollisionMeshSource(source: CollisionMeshSource): PreparedCollisionMesh | null — Build a prepared BVH mesh straight from a runtime triangle soup (float positions + indices) — the renderer-fed counterpart to {@link prepareCollisionMesh}, feeding `ctx.scene.entity.reportCollisionMesh` / `ctx.scene.object.reportCollisionMesh` without a quantize/encode round-trip. Copies and sanitizes the input: triangles referencing out-of-range, non-finite, or repeated vertices are dropped. Returns `null` when no triangle survives.
|
|
957
959
|
- `raycastCollisionMesh` (function): function raycastCollisionMesh(mesh: PreparedCollisionMesh, origin: EntityPosition, direction: EntityPosition, maxDistance: number, position: EntityPosition, rotationY: number, scale: number, translate: EntityPosition): CollisionMeshHit | null — Nearest triangle hit for a world-space ray against a prepared mesh placed like its rendered model: model space scaled uniformly by `scale`, translated by entity-local `translate`, yaw-rotated by `rotationY`, and moved to `position` — the same composition {@link "scene/colliders".ModelBodySource} fitting applies to the AABB. Allocation-free until the returned hit. `distance` is world distance along `direction` (callers pass `direction` normalized); `normal` is the world-space geometric triangle normal facing the ray.
|
|
958
960
|
|
|
959
961
|
## @jgengine/core/scene/companion
|
|
@@ -1097,6 +1099,7 @@
|
|
|
1097
1099
|
- `ParamSchema` (interface): interface ParamSchema — A kind's full parameter surface: an ordered list of fields the inspector renders top-to-bottom.
|
|
1098
1100
|
- `ParsedParams` (type): type ParsedParams = Record<string, number | boolean | string | WeightedParamEntry[]> — Parsed params after `parseParams`: every schema field present with a validated, defaulted value.
|
|
1099
1101
|
- `RangeParamField` (interface): interface RangeParamField — A numeric slider row: bounded range with a live readout.
|
|
1102
|
+
- `SceneKindCoverage` (interface): interface SceneKindCoverage — Marks a kind as *scatterable* — it fills an authored area with instances at one density under a bounded budget — so the editor renders the shared `CoverageSection` (Area → Assets → Density slider + budget note) that gives grass/scatter/city one authoring gesture. `spec` selects the unit + budget from `@jgengine/core/world/scatterCoverage`; `densityKey` is the single density slider hoisted into the coverage header (and hidden from the schema body below); `assetsKey` is the optional weighted "what fills it" palette summarized in the header.
|
|
1100
1103
|
- `SceneKindDefinition` (interface): interface SceneKindDefinition<TResolved = unknown> — A registered scene kind — everything the editor/engine need to author and render a studio without bespoke code. `schema` drives the inspector + parse; `resolve` (optional) turns one document object into pure renderable data a matching `shell` renderer consumes; `add*` fields build the `+ Add` menu.
|
|
1101
1104
|
- `SceneKindObject` (interface): interface SceneKindObject — The raw document object a resolver receives — shape shared by markers, volumes, and paths.
|
|
1102
1105
|
- `SceneKindResolveContext` (interface): interface SceneKindResolveContext — Ground sampler + options a resolver may read (terrain height/normal snap).
|
|
@@ -1448,10 +1451,11 @@
|
|
|
1448
1451
|
- `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
1452
|
- `BoxFormationOptions` (interface): interface BoxFormationOptions — Options for {@link boxFormation}.
|
|
1450
1453
|
- `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
|
|
1454
|
+
- `BuildingEnvironmentDescriptor` (type): type BuildingEnvironmentDescriptor = { kind: "building" } & Required< Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style"> > & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette" | "along"> — ⚠ undocumented
|
|
1452
1455
|
- `BuildingIndex` (interface): interface BuildingIndex — ⚠ undocumented
|
|
1453
1456
|
- `BuildingPaletteOverrides` (type): type BuildingPaletteOverrides = Partial<BuildingPalette> — ⚠ undocumented
|
|
1454
1457
|
- `BuildingStyle` (type): type BuildingStyle = | "generic" | "capital" | "village" | "desert" | "industrial" | "coastal" | "neon" | "ruin" | "frontier" | "aerial" — ⚠ undocumented
|
|
1458
|
+
- `CITY_BUILDING_BUDGET` (const): const CITY_BUILDING_BUDGET: 2600 — Max buildings a single `city` district generates — the block/parcel pipeline's bounded lot cap.
|
|
1455
1459
|
- `CITY_DEFAULTS` (const): const CITY_DEFAULTS: CityRules — City defaults: a zoned mixed metropolis — towers downtown, slabs mid-ring, houses at the edge.
|
|
1456
1460
|
- `CITY_KIND` (const): const CITY_KIND: "city" — The editor volume kind marking a box as a procedural city district.
|
|
1457
1461
|
- `CITY_LOT_CLASSES` (const): const CITY_LOT_CLASSES: readonly CityLotClass[] — All classes, for schema hints and validation.
|
|
@@ -1696,6 +1700,8 @@
|
|
|
1696
1700
|
- `RosterEntry` (interface): interface RosterEntry — ⚠ undocumented
|
|
1697
1701
|
- `RoundGround` (interface): interface RoundGround — A planet/sphere you play on the outside of. Sized by `radius` only.
|
|
1698
1702
|
- `RoundGroundSize` (interface): interface RoundGroundSize — Size of a `round` ground: the planet/sphere radius in world units. Nothing else.
|
|
1703
|
+
- `SCATTER_COVERAGE_SPECS` (const): const SCATTER_COVERAGE_SPECS: Record<ScatterCoverageKind, ScatterCoverageSpec> — The coverage spec per scatterable kind — the single source for its unit + budget.
|
|
1704
|
+
- `SCATTER_INSTANCE_BUDGET` (const): const SCATTER_INSTANCE_BUDGET: 250000 — Max blade/placement instances a single `grass_field` or `scatter` patch places. Mirrors the grass renderer's instance-buffer ceiling; the scatter resolver truncates to it too, so a patch never renders more than this many placements regardless of density × area.
|
|
1699
1705
|
- `SCATTER_PATH_KIND` (const): const SCATTER_PATH_KIND: "scatter" — The editor path kind that marks a closed polyline as a foliage/scatter region.
|
|
1700
1706
|
- `SHAPE_BOX` (const): const SHAPE_BOX: 0 — ⚠ undocumented
|
|
1701
1707
|
- `SHAPE_SPHERE` (const): const SHAPE_SPHERE: 1 — ⚠ undocumented
|
|
@@ -1709,6 +1715,9 @@
|
|
|
1709
1715
|
- `SampleReason` (type): type SampleReason = "accepted" | "exhausted" | "empty-region" — Why a sample ended: a clean hit, budget exhausted, or the region was degenerate.
|
|
1710
1716
|
- `SampleRegion` (interface): interface SampleRegion<P extends SamplePoint = SamplePoint> — A geometric region a sampler can draw from. Built-ins cover point sets, rect/box, circle/sphere, annulus/shell, polygon, and weighted composites; {@link customRegion} wraps a caller-defined sampler + bounds. `sample` draws one raw candidate under the region's own distribution policy; `contains` is the membership test reused for include/exclude constraints and rejection.
|
|
1711
1717
|
- `SampleResult` (interface): interface SampleResult<P extends SamplePoint = SamplePoint> — Structured single-sample outcome — never a bare point, so failure and fallback are visible.
|
|
1718
|
+
- `ScatterCoverage` (interface): interface ScatterCoverage — A resolved coverage readout: how many instances an authored patch WANTS (`requested`, when it can be computed cheaply from geometry × density), how many it will actually place after the budget clamp (`count`), the `budget` ceiling, and whether the request hit it (`capped`). `requested` is `null` for kinds whose pre-cap ask is not knowable without running the generator (city, where the count comes from the resolved street fabric).
|
|
1719
|
+
- `ScatterCoverageKind` (type): type ScatterCoverageKind = "grass_field" | "scatter" | "city" — The shared "how much stuff grows here" contract for scatterable scene kinds (`grass_field` / `scatter` / `city`). Every scatterable kind fills an authored AREA with instances at a DENSITY, and every kind caps the placed count at a bounded BUDGET so a huge or dense request can never generate unbounded work. Before this module each kind hand-rolled its own readout and clamp story (grass silently capped in the renderer, scatter never capped, city had no budget note); this is the one place that
|
|
1720
|
+
- `ScatterCoverageSpec` (interface): interface ScatterCoverageSpec — Per-kind coverage descriptor: the unit noun, whether density is per-m², and the placement budget.
|
|
1712
1721
|
- `ScatterInstance` (interface): interface ScatterInstance — ⚠ undocumented
|
|
1713
1722
|
- `ScatterPoint` (interface): interface ScatterPoint — ⚠ undocumented
|
|
1714
1723
|
- `ScatterTerrain` (interface): interface ScatterTerrain — Ground sampler a scatter resolve reads height/normal from (the sculpt terrain or the game's ground).
|
|
@@ -1847,9 +1856,10 @@
|
|
|
1847
1856
|
- `boundaryNeighbors` (function): function boundaryNeighbors(grid: FootprintGrid, cells: readonly GridCell[]): AdjacentCell[] — Every occupied cell orthogonally touching `cells` but outside them — the connective-piece neighbor set.
|
|
1848
1857
|
- `boxFormation` (function): function boxFormation(options: BoxFormationOptions): FormationSlotGenerator — A rectangular grid centered on the destination, front row toward `+forward` — a marching block or a phalanx. Rows fill front-to-back, left-to-right.
|
|
1849
1858
|
- `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.
|
|
1859
|
+
- `budgetWarning` (function): function budgetWarning(coverage: ScatterCoverage): string — The shared clamp-and-warn clause every scatterable kind appends, worded identically: `""` when under budget, else ` · requested N, capped at M (budget)` when the pre-cap ask is known, or ` · capped at M (budget)` when only the ceiling is (city). This is the "surfaced, never silent" budget signal from #1112.
|
|
1850
1860
|
- `buildContextMenu` (function): function buildContextMenu(input: BuildContextMenuInput): ContextMenu | null — Assemble a menu from a target's catalog verbs; null when the target lists none.
|
|
1851
1861
|
- `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.
|
|
1862
|
+
- `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
1863
|
- `buildingIndex` (function): function buildingIndex(buildings: readonly GeneratedBuilding[]): BuildingIndex — ⚠ undocumented
|
|
1854
1864
|
- `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
1865
|
- `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,13 +1931,15 @@
|
|
|
1921
1931
|
- `createVisibilitySystem` (function): function createVisibilitySystem(options: VisibilitySystemOptions): VisibilitySystem — ⚠ undocumented
|
|
1922
1932
|
- `createVoxelField` (function): function createVoxelField<T extends string = string>(config?: VoxelFieldConfig): VoxelField<T> — ⚠ undocumented
|
|
1923
1933
|
- `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.
|
|
1934
|
+
- `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
1935
|
- `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
1936
|
- `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
1937
|
- `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`.
|
|
1928
1938
|
- `definePatrolOrder` (function): function definePatrolOrder<TCtx extends OrderMover>(config: OrderKindConfig & { arriveRadius?: number } = {}): OrderKind<TCtx, PatrolOrderPayload> — Patrol: walk a waypoint route, advancing the serialized `index` as each point is reached. Loops forever by default, or completes at the last waypoint when `loop` is `false`.
|
|
1929
1939
|
- `defineStopOrder` (function): function defineStopOrder<TCtx extends OrderMover>(config: OrderKindConfig = {}): OrderKind<TCtx, EmptyOrderPayload> — Stop: halt immediately and complete the same tick — the interrupt that clears a unit's intent to standstill.
|
|
1930
1940
|
- `defineTargetedOrder` (function): function defineTargetedOrder<TCtx extends OrderMover & OrderTargeting>(config: EngagementKindConfig = {}): OrderKind<TCtx, TargetedOrderPayload> — Targeted action: pursue one specific target until within `range`, writing the engagement intent into `order.state`. Completes when the target is gone (despawned/dead), so the game's effect loop runs while `inRange` is true.
|
|
1941
|
+
- `densityCoverage` (function): function densityCoverage(kind: "grass_field" | "scatter", area: number, density: number): ScatterCoverage — Coverage for a per-m² kind (`grass_field` / `scatter`): `requested = floor(density × area)`, clamped to the kind's budget. `capped` is true when the request exceeds the budget.
|
|
1942
|
+
- `describeScatterCoverage` (function): function describeScatterCoverage(coverage: ScatterCoverage): string — The full one-line coverage readout: `≈ N unit · A m²` plus the {@link budgetWarning} when capped.
|
|
1931
1943
|
- `discRegion` (function): function discRegion(center: Vec2, radius: number, options: { distribution?: RadialDistribution } = {}): SampleRegion<Vec2> — A filled circle. `"area"` is area-uniform (√-corrected radius, even density); `"radial"` is radius-uniform (clumps toward the center). Angle is drawn first, then radius — matching the common `angle = rng()·2π; r = rng()·R` idiom so migrations keep their stream.
|
|
1932
1944
|
- `distance` (function): function distance(a: Vec3, b: Vec3): number — ⚠ undocumented
|
|
1933
1945
|
- `distance3` (function): function distance3(a: { x: number; y: number; z: number }, b: { x: number; y: number; z: number }): number — ⚠ undocumented
|
|
@@ -2001,6 +2013,7 @@
|
|
|
2001
2013
|
- `placeAuthoredObjects` (function): function placeAuthoredObjects(store: AuthoredObjectPlaceTarget, objects: readonly AuthoredObject[], sampleHeight: (x: number, z: number) => number, options: PlaceAuthoredObjectsOptions = {}): string[] — Places resolved authored objects into an object store, grounding each on `sampleHeight(x,z)` plus per-object and options vertical offsets. Returns the instance ids that were placed (or kept).
|
|
2002
2014
|
- `placeAuthoredObjectsFromDocument` (function): function placeAuthoredObjectsFromDocument(store: AuthoredObjectPlaceTarget, document: AuthoredObjectsDocumentLike, sampleHeight: (x: number, z: number) => number, options: PlaceAuthoredObjectsOptions = {}): string[] — Convenience: resolve a document then place every authored catalog prop.
|
|
2003
2015
|
- `placeFormation` (function): function placeFormation(destination: Vec2, facing: number, count: number, generator: FormationSlotGenerator): Vec2[] — Transform a generator's local slot offsets into world XZ positions around a `destination`, rotated by `facing` (engine yaw). Slot `i` in the returned array is `generator(count)[i]` mapped through the group frame, so it stays aligned with {@link assignFormationSlots}' slot indices. Pure and allocation- light: one array of `count` points, no per-call closures retained.
|
|
2016
|
+
- `placedCoverage` (function): function placedCoverage(kind: ScatterCoverageKind, area: number, placed: number): ScatterCoverage — Coverage for a kind whose placed `count` is already known from its generator (city, from its resolved lots). No pre-cap `requested` is available — the generator stops at the budget — so `capped` is inferred from the placed count reaching the ceiling.
|
|
2004
2017
|
- `player` (function): function player(): PlayerBehavior — ⚠ undocumented
|
|
2005
2018
|
- `plots` (function): function plots(config: PlotsWorldConfig = {}): WorldFeature — Declares a subdivided-plots world — farming, base-building, and other parcel-based layouts.
|
|
2006
2019
|
- `pointGravity` (function): function pointGravity(options: PointGravityOptions): GravityField — Radial gravity toward one center, with optional inverse-square falloff for planetary worlds.
|
|
@@ -2056,6 +2069,8 @@
|
|
|
2056
2069
|
- `sampleStratified` (function): function sampleStratified(options: StratifiedOptions): Vec2[] — Stratified 2D sampling: one jittered point per grid cell over `area`, in deterministic row-major order. Unlike pure rejection sampling this guarantees even coverage (no clumps, no gaps) — the distinct "stratified" distribution policy, kept separate from area-uniform draws.
|
|
2057
2070
|
- `sanitizeGameTimeScale` (function): function sanitizeGameTimeScale(timeScale?: number | null): number — ⚠ undocumented
|
|
2058
2071
|
- `scatter` (function): function scatter(config: ScatterConfig): ScatterPoint[] — ⚠ undocumented
|
|
2072
|
+
- `scatterCoverageSpec` (function): function scatterCoverageSpec(kind: string): ScatterCoverageSpec | null — The coverage spec for a scatterable kind, or `null` when the kind is not scatterable.
|
|
2073
|
+
- `scatterFootprintArea` (function): function scatterFootprintArea(object: { points?: readonly { x: number; z: number }[]; halfExtents?: { x: number; z: number }; radius?: number; }): number — Footprint area (m²) of a scatterable object: a closed path polygon (shoelace), a volume box (`halfExtents`), or a disc (`radius`). Structural so it accepts a `SceneKindObject`, an editor volume, or a scatter path without a hard type dependency.
|
|
2059
2074
|
- `scatterItems` (function): function scatterItems<T>(field: RegionField<T>, area: Aabb, layersFor: (sample: RegionSample<T>) => readonly ScatterLayer[], options: { cell?: number; max?: number; saltKey?: number } = {}): ScatterInstance[] — Deterministically place opaque items across `area`, grounded on a region field. For each grid cell it asks `layersFor` which items may appear in that region and rolls one against their densities. The engine never interprets `item` — a game maps it to a mesh or entity. Content scatter (region-driven density) as opposed to `scatter` in `./scatter`, which is renderer-free geometric point distribution.
|
|
2060
2075
|
- `scatterRegionEstimate` (function): function scatterRegionEstimate(path: ScenePathLike): { area: number; count: number } — Estimated placement count for a scatter path — density × polygon area, for a live UI readout.
|
|
2061
2076
|
- `scatterRegionFromPath` (function): function scatterRegionFromPath(path: ScenePathLike): ScatterRegion | null — Builds a resolvable {@link ScatterRegion} from a scatter path (XZ polygon + rules), or null.
|
|
@@ -2190,6 +2205,14 @@
|
|
|
2190
2205
|
- `BuildingIndex` (interface): interface BuildingIndex — ⚠ undocumented
|
|
2191
2206
|
- `buildingIndex` (function): function buildingIndex(buildings: readonly GeneratedBuilding[]): BuildingIndex — ⚠ undocumented
|
|
2192
2207
|
|
|
2208
|
+
## @jgengine/core/world/buildingLots
|
|
2209
|
+
|
|
2210
|
+
- `BuildingLotOptions` (interface): interface BuildingLotOptions — Options for {@link deriveBuildingLots}.
|
|
2211
|
+
- `LotArea` (interface): interface LotArea — Rectangular clip region lots must fall within (world center + half-extents).
|
|
2212
|
+
- `PlacedBuildingLot` (interface): interface PlacedBuildingLot — One placed building lot: where a building stands and how it is turned to face its road.
|
|
2213
|
+
- `RoadFrontage` (interface): interface RoadFrontage — One road the frontage placer lines with buildings: a centerline polyline and its full width.
|
|
2214
|
+
- `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.
|
|
2215
|
+
|
|
2193
2216
|
## @jgengine/core/world/buildings
|
|
2194
2217
|
|
|
2195
2218
|
- `BUILDING_STYLE_PALETTES` (const): const BUILDING_STYLE_PALETTES: Record<BuildingStyle, BuildingPalette> — ⚠ undocumented
|
|
@@ -2275,6 +2298,12 @@
|
|
|
2275
2298
|
- `CityZoneBand` (type): type CityZoneBand = "core" | "mid" | "edge" — Zone band a lot falls in: dense core, middle ring, or the district edge.
|
|
2276
2299
|
- `CityZoneProfile` (type): type CityZoneProfile = "core-out" | "inverted" | "uniform" — How the radial zone metric maps to bands.
|
|
2277
2300
|
|
|
2301
|
+
## @jgengine/core/world/cityGenerator
|
|
2302
|
+
|
|
2303
|
+
- `CityGeneratorOptions` (interface): interface CityGeneratorOptions — Options for {@link generateCity}: a seed, street-dial overrides, and lot pass-through options.
|
|
2304
|
+
- `GeneratedCity` (interface): interface GeneratedCity — A generated city: the street network and the building lots lining its frontage.
|
|
2305
|
+
- `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.
|
|
2306
|
+
|
|
2278
2307
|
## @jgengine/core/world/cityGeometry
|
|
2279
2308
|
|
|
2280
2309
|
- `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 +2406,9 @@
|
|
|
2377
2406
|
- `BiomeSky` (interface): interface BiomeSky — Per-band sky/light override cross-faded along z by `createBiomeSkySampler`; unset fields fall through to the base sky.
|
|
2378
2407
|
- `BiomesWorldConfig` (interface): interface BiomesWorldConfig extends WorldGridConfig — ⚠ undocumented
|
|
2379
2408
|
- `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
|
|
2409
|
+
- `BuildingEnvironmentDescriptor` (type): type BuildingEnvironmentDescriptor = { kind: "building" } & Required< Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style"> > & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette" | "along"> — ⚠ undocumented
|
|
2410
|
+
- `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`.
|
|
2411
|
+
- `BuildingFrontageRoad` (interface): interface BuildingFrontageRoad — One road a `building({ along })` frontage lines with buildings: centerline + full width.
|
|
2381
2412
|
- `EnvironmentArea` (interface): interface EnvironmentArea extends WorldBounds — ⚠ undocumented
|
|
2382
2413
|
- `EnvironmentDescriptorList` (type): type EnvironmentDescriptorList<T> = T | readonly T[] — ⚠ undocumented
|
|
2383
2414
|
- `EnvironmentVec2` (type): type EnvironmentVec2 = readonly [number, number] — ⚠ undocumented
|
|
@@ -2425,7 +2456,7 @@
|
|
|
2425
2456
|
- `WorldGridCell` (interface): interface WorldGridCell — ⚠ undocumented
|
|
2426
2457
|
- `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
2458
|
- `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.
|
|
2459
|
+
- `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
2460
|
- `environment` (function): function environment(config: EnvironmentWorldConfig = {}): EnvironmentWorldFeature — Composes an `environment()` feature from terrain, sky, weather, vegetation, water, structures, roads, and pads.
|
|
2430
2461
|
- `flat` (function): function flat(): WorldFeature — Declares an empty flat world — the minimal `WorldFeature` for games with no terrain of their own.
|
|
2431
2462
|
- `grass` (function): function grass(config: GrassEnvironmentConfig = {}): GrassEnvironmentDescriptor — Declares a grass vegetation patch for `environment()` — area, blade sizing, density, and colors.
|
|
@@ -2588,24 +2619,6 @@
|
|
|
2588
2619
|
- `PlaceAlongPathOptions` (interface): interface PlaceAlongPathOptions — Options for {@link placeAlongPath}.
|
|
2589
2620
|
- `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
2621
|
|
|
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
2622
|
## @jgengine/core/world/pathTerrain
|
|
2610
2623
|
|
|
2611
2624
|
- `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 +2744,14 @@
|
|
|
2731
2744
|
|
|
2732
2745
|
## @jgengine/core/world/roads
|
|
2733
2746
|
|
|
2747
|
+
- `DashExclusion` (interface): interface DashExclusion — A circular exclusion zone: dashes whose midpoint falls inside are dropped (e.g. junction patches).
|
|
2734
2748
|
- `RoadPoint` (type): type RoadPoint = readonly [number, number] — A road centerline vertex in world XZ.
|
|
2735
2749
|
- `RoadRibbon` (interface): interface RoadRibbon — Renderer-ready triangle ribbon: flat position triples plus triangle indices.
|
|
2736
2750
|
- `RoadRibbonOptions` (interface): interface RoadRibbonOptions — Options for {@link buildRoadRibbon}.
|
|
2737
2751
|
- `RoadSample` (interface): interface RoadSample — Result of {@link nearestOnPath}: closest point on the centerline plus distance and tangent.
|
|
2752
|
+
- `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
2753
|
- `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.
|
|
2754
|
+
- `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
2755
|
- `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
2756
|
- `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
2757
|
- `pathLength` (function): function pathLength(path: readonly RoadPoint[]): number — Total arc length of a centerline in world units.
|
|
@@ -2749,6 +2764,21 @@
|
|
|
2749
2764
|
- `scatter` (function): function scatter(config: ScatterConfig): ScatterPoint[] — ⚠ undocumented
|
|
2750
2765
|
- `scatterAabb` (function): function scatterAabb(area: ScatterArea | Aabb): Aabb — ⚠ undocumented
|
|
2751
2766
|
|
|
2767
|
+
## @jgengine/core/world/scatterCoverage
|
|
2768
|
+
|
|
2769
|
+
- `CITY_BUILDING_BUDGET` (const): const CITY_BUILDING_BUDGET: 2600 — Max buildings a single `city` district generates — the block/parcel pipeline's bounded lot cap.
|
|
2770
|
+
- `SCATTER_COVERAGE_SPECS` (const): const SCATTER_COVERAGE_SPECS: Record<ScatterCoverageKind, ScatterCoverageSpec> — The coverage spec per scatterable kind — the single source for its unit + budget.
|
|
2771
|
+
- `SCATTER_INSTANCE_BUDGET` (const): const SCATTER_INSTANCE_BUDGET: 250000 — Max blade/placement instances a single `grass_field` or `scatter` patch places. Mirrors the grass renderer's instance-buffer ceiling; the scatter resolver truncates to it too, so a patch never renders more than this many placements regardless of density × area.
|
|
2772
|
+
- `ScatterCoverage` (interface): interface ScatterCoverage — A resolved coverage readout: how many instances an authored patch WANTS (`requested`, when it can be computed cheaply from geometry × density), how many it will actually place after the budget clamp (`count`), the `budget` ceiling, and whether the request hit it (`capped`). `requested` is `null` for kinds whose pre-cap ask is not knowable without running the generator (city, where the count comes from the resolved street fabric).
|
|
2773
|
+
- `ScatterCoverageKind` (type): type ScatterCoverageKind = "grass_field" | "scatter" | "city" — The shared "how much stuff grows here" contract for scatterable scene kinds (`grass_field` / `scatter` / `city`). Every scatterable kind fills an authored AREA with instances at a DENSITY, and every kind caps the placed count at a bounded BUDGET so a huge or dense request can never generate unbounded work. Before this module each kind hand-rolled its own readout and clamp story (grass silently capped in the renderer, scatter never capped, city had no budget note); this is the one place that
|
|
2774
|
+
- `ScatterCoverageSpec` (interface): interface ScatterCoverageSpec — Per-kind coverage descriptor: the unit noun, whether density is per-m², and the placement budget.
|
|
2775
|
+
- `budgetWarning` (function): function budgetWarning(coverage: ScatterCoverage): string — The shared clamp-and-warn clause every scatterable kind appends, worded identically: `""` when under budget, else ` · requested N, capped at M (budget)` when the pre-cap ask is known, or ` · capped at M (budget)` when only the ceiling is (city). This is the "surfaced, never silent" budget signal from #1112.
|
|
2776
|
+
- `densityCoverage` (function): function densityCoverage(kind: "grass_field" | "scatter", area: number, density: number): ScatterCoverage — Coverage for a per-m² kind (`grass_field` / `scatter`): `requested = floor(density × area)`, clamped to the kind's budget. `capped` is true when the request exceeds the budget.
|
|
2777
|
+
- `describeScatterCoverage` (function): function describeScatterCoverage(coverage: ScatterCoverage): string — The full one-line coverage readout: `≈ N unit · A m²` plus the {@link budgetWarning} when capped.
|
|
2778
|
+
- `placedCoverage` (function): function placedCoverage(kind: ScatterCoverageKind, area: number, placed: number): ScatterCoverage — Coverage for a kind whose placed `count` is already known from its generator (city, from its resolved lots). No pre-cap `requested` is available — the generator stops at the budget — so `capped` is inferred from the placed count reaching the ceiling.
|
|
2779
|
+
- `scatterCoverageSpec` (function): function scatterCoverageSpec(kind: string): ScatterCoverageSpec | null — The coverage spec for a scatterable kind, or `null` when the kind is not scatterable.
|
|
2780
|
+
- `scatterFootprintArea` (function): function scatterFootprintArea(object: { points?: readonly { x: number; z: number }[]; halfExtents?: { x: number; z: number }; radius?: number; }): number — Footprint area (m²) of a scatterable object: a closed path polygon (shoelace), a volume box (`halfExtents`), or a disc (`radius`). Structural so it accepts a `SceneKindObject`, an editor volume, or a scatter path without a hard type dependency.
|
|
2781
|
+
|
|
2752
2782
|
## @jgengine/core/world/scatterItems
|
|
2753
2783
|
|
|
2754
2784
|
- `ScatterInstance` (interface): interface ScatterInstance — ⚠ undocumented
|
|
@@ -2837,16 +2867,43 @@
|
|
|
2837
2867
|
- `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
2868
|
- `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
2869
|
|
|
2870
|
+
## @jgengine/core/world/streetGenerator
|
|
2871
|
+
|
|
2872
|
+
- `Street` (interface): interface Street — One chained through-street: a maximal run of edges through degree-2 nodes, for rendering + furniture.
|
|
2873
|
+
- `StreetDeadEnd` (interface): interface StreetDeadEnd — One dangling street end kept as a cul-de-sac: node position plus the heading pointing off the road.
|
|
2874
|
+
- `StreetEdge` (interface): interface StreetEdge — One atomic node-to-node edge — the fabric graph consumes these (welds at shared node coords).
|
|
2875
|
+
- `StreetFeature` (interface): interface StreetFeature — A resolved path feature in world-of-the-volume space: a bridge deck or tunnel bore centerline.
|
|
2876
|
+
- `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.
|
|
2877
|
+
- `StreetFeatureSpan` (interface): interface StreetFeatureSpan — A feature span carried by a street: a `[from, to]` index window into the street's `points`.
|
|
2878
|
+
- `StreetJunction` (interface): interface StreetJunction — One crossing of three or more streets: patch center/radius plus outgoing arm directions.
|
|
2879
|
+
- `StreetLevel` (type): type StreetLevel = "boulevard" | "avenue" | "street" | "lane" — Road hierarchy, widest to narrowest — shared by the city fabric and the renderer.
|
|
2880
|
+
- `StreetNetwork` (interface): interface StreetNetwork — The fully-resolved network in volume-local coords.
|
|
2881
|
+
- `StreetNetworkContext` (interface): interface StreetNetworkContext — Ground sampler + feature toggles enabling bridges/tunnels; omit for a flat, feature-free network.
|
|
2882
|
+
- `StreetNetworkMode` (type): type StreetNetworkMode = "net" | "circuit" — The generator's chosen topology family: an open street `net`, or a closed `circuit` loop.
|
|
2883
|
+
- `StreetNetworkRules` (interface): interface StreetNetworkRules — Fully-defaulted slider set the generator reads.
|
|
2884
|
+
- `StreetNode` (interface): interface StreetNode — One graph node: a junction, a dead end, or a mid-street bend, with its connection count.
|
|
2885
|
+
- `StreetVec2` (type): type StreetVec2 = readonly [number, number] — A path vertex in the volume-local XZ frame.
|
|
2886
|
+
- `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.
|
|
2887
|
+
|
|
2840
2888
|
## @jgengine/core/world/streets
|
|
2841
2889
|
|
|
2842
2890
|
- `FurnitureSpot` (interface): interface FurnitureSpot — A placement anchor on the curb line: position, outward-facing heading, and which side it sits on.
|
|
2843
2891
|
- `FurnitureSpotOptions` (interface): interface FurnitureSpotOptions — Options for {@link furnitureSpots}.
|
|
2892
|
+
- `JunctionMarkingOptions` (interface): interface JunctionMarkingOptions — Options for {@link junctionMarkings}.
|
|
2893
|
+
- `JunctionMarkings` (interface): interface JunctionMarkings — Thin marking polylines for a junction: stop-line bars and crosswalk bars, ready for {@link buildRoadRibbon}.
|
|
2844
2894
|
- `ParkingSpot` (interface): interface ParkingSpot — A curbside parking anchor: position at the road edge and a heading parallel to the street.
|
|
2845
2895
|
- `ParkingSpotOptions` (interface): interface ParkingSpotOptions — Options for {@link parkingSpots}.
|
|
2896
|
+
- `RoadApproach` (interface): interface RoadApproach — A single arm of a junction: the road width plus the outward direction the road leaves the center.
|
|
2897
|
+
- `RoadJunction` (interface): interface RoadJunction — A welded road intersection: a patch center/radius plus the arms that feed it.
|
|
2898
|
+
- `RoadJunctionOptions` (interface): interface RoadJunctionOptions — Options for {@link findRoadJunctions}.
|
|
2846
2899
|
- `RoadSurfaceOptions` (interface): interface RoadSurfaceOptions — Grip levels and blend width for {@link roadSurfaceSampler}; each field is optional and defaulted.
|
|
2847
2900
|
- `StreetLane` (interface): interface StreetLane — A directed lane derived from a road: an offset centerline plus its direction of travel.
|
|
2901
|
+
- `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
2902
|
- `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.
|
|
2903
|
+
- `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
2904
|
- `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.
|
|
2905
|
+
- `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.
|
|
2906
|
+
- `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
2907
|
- `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
2908
|
- `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
2909
|
- `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"`
|
|
@@ -355,6 +355,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
355
355
|
- `controlGroupKey` (function) · `import { controlGroupKey } from "@jgengine/core/world"`
|
|
356
356
|
- `resolveControlGroupIntent` (function) · `import { resolveControlGroupIntent } from "@jgengine/core/world"`
|
|
357
357
|
|
|
358
|
+
## scatter-coverage — shared density/budget semantics for scatterable kinds
|
|
359
|
+
|
|
360
|
+
- `ScatterCoverageKind` (type) · `import { ScatterCoverageKind } from "@jgengine/core/world"`
|
|
361
|
+
|
|
358
362
|
## selection-bookmark-recall — fold a saved set into the active selection with stale-ref pruning and a caller focus hook
|
|
359
363
|
|
|
360
364
|
- `recallSelectionBookmark` (function) · `import { recallSelectionBookmark } from "@jgengine/core/world"`
|
|
@@ -415,6 +419,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
415
419
|
|
|
416
420
|
- `defineStopOrder` (function) · `import { defineStopOrder } from "@jgengine/core/world"`
|
|
417
421
|
|
|
422
|
+
## street-generator — build a deterministic street/track graph (nodes, edges, streets, junctions, bridges, tunnels) from sliders
|
|
423
|
+
|
|
424
|
+
- `generateStreets` (function) · `import { generateStreets } from "@jgengine/core/world/streetGenerator"`
|
|
425
|
+
|
|
418
426
|
## surface-drape — lift an XZ polyline onto sculpted terrain
|
|
419
427
|
|
|
420
428
|
- `drapePolyline` (function) · `import { drapePolyline } from "@jgengine/core/world"`
|