@jgengine/convex 0.52.0 → 0.54.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
CHANGED
|
@@ -26,6 +26,383 @@ At publish, rename this heading to the new version and mirror the entries into
|
|
|
26
26
|
`packages/core/src/meta/changelog.ts` (the typed `CHANGELOG` export).
|
|
27
27
|
-->
|
|
28
28
|
|
|
29
|
+
## 0.54.0
|
|
30
|
+
|
|
31
|
+
### Migrate
|
|
32
|
+
|
|
33
|
+
- **Bump lockstep SDK packages to `^0.54.0`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.51.0`; `@jgengine/github` is `0.41.0`.
|
|
34
|
+
- the grass meta group species (second species) is now mix; GrassCover's stripeAt prop is now shadeAt.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- `createDoseTable` (`@jgengine/core/stats/doseTable`): What each source does to each kind of receiver, as {@link VitalDose}s: a raptor's foods and how much taming, food and ineffectiveness each gives, a sim's meals and the needs they fill, a shooter's potions and the shield each pours in up to its cap, a dart's torpor by the body it lands in. Also `DoseTable`, `DoseTableOptions`, `DoseTableRows`, `hasRoomFor`.
|
|
39
|
+
- `createVitals` (`@jgengine/core/stats/vitals`): Survival vitals for one body — health, stamina, food, water, oxygen, torpor or anything the game names. Also `VitalDef`, `VitalDose`, `VitalFlag`, `VitalFlagChange`, `VitalLink`, `VitalRateModifier`, `Vitals`, `VitalsState` and 1 more.
|
|
40
|
+
- `importedPackNames` (`@jgengine/assets`): The original export names imported from `generated/packs`, including imports renamed locally.
|
|
41
|
+
- `importedPackNames` (`@jgengine/assets/manifest`): The original export names imported from `generated/packs`, including imports renamed locally.
|
|
42
|
+
- `LINGER_MS` (`@jgengine/core/run/lingering`): How long a resource whose last user left waits before it is freed. Also `Lingering`.
|
|
43
|
+
- `nextFrame` (`@jgengine/core/run/nextFrame.draw`): Resolves on the next animation frame, or a zero-delay timer in tests and SSR.
|
|
44
|
+
- `DECAL_CELL` (`@jgengine/core/scene/DecalRenderer.draw`): Edge in metres of the world cell one merged decal draw covers, so an unseen cell still culls whole.
|
|
45
|
+
- bun run perf <game> --profile also prints what allocates per second of play, the source of garbage collection pauses
|
|
46
|
+
- `SCENE_PROBE_REFRESH_MS` (`@jgengine/core/devtools/discover`): How often a probe that walks the whole scene walks it again; in between it answers its last reading.
|
|
47
|
+
- generator-grown scatter species bake the generator's coarser levels and switch to them by size on screen through visibility.meshLods (Fernwild 28M to 12.5M triangles a frame)
|
|
48
|
+
- `RoadPiece` (`@jgengine/core/roads/RoadRibbons.draw`): One road's share of a merged draw: its trimmed centreline sub-paths and the base height they drape at.
|
|
49
|
+
- Grass reference boards: `bun run shoot --board` shoots six credited photos beside our matching views, our games' stylised art beside our grass, the 18 grass game views and every species, with `--diff` to compare two boards and `--resume`/`--timeout` to finish a long board
|
|
50
|
+
- `--look-from` takes a fourth number, the ground clearance, so a capture can sit centimetres above the turf
|
|
51
|
+
- 128 named grass species (GRASS_SPECIES in @jgengine/core/grass/grassSpecies) across eleven habitats, from putting greens and prairie to reed beds, cereal crops, ornamentals, moss cushions and stylised fantasy grass. A grass_field or grass() names one with species and any knob it also sets wins.
|
|
52
|
+
- grass knobs for blade twist, tip droop, V-fold, tuft splay, moss cushion domes, variegated margins and cross bands, spike seed heads (headShape) and a resting wind lean.
|
|
53
|
+
- a param schema can name a presetKey; the meta id there backs every unset field (presetBackedMeta), and the editor inspector groups presets under headings and shows the current one.
|
|
54
|
+
- `presetBackedMeta` (`@jgengine/core/generator/paramSchema`): A `meta` bag with its {@link ParamSchema.presetKey} preset filled in under it: the preset's values, then the meta's own keys on top.
|
|
55
|
+
- `GrassSpeciesKnobs` (`@jgengine/core/grass/grassKind`): A knob set a species writes: any knobs, never the seed.
|
|
56
|
+
- `GRASS_SPECIES` (`@jgengine/core/grass/grassSpecies`): Every shipped grass species, grouped by habitat. Also `GrassSpecies`, `GrassSpeciesId`.
|
|
57
|
+
- Grass style: a species says what grows, a style says how it is drawn. `realism` (0 cartoon, 0.5 animated, 1 photoreal) moves every style slider along its own curve; the first slider is `normalLift`. A world sets one style (`environment.grassStyle`, the Lighting panel, or `environment({ grassStyle })`) and a patch may override it (`style` in a `grass_field` meta or a `grass()` config)
|
|
58
|
+
- The inspector shows a scene kind's nested settings with the values in effect and a Clear button; grass and ground cover use it for the patch style
|
|
59
|
+
- Grass look fingerprints: a test pins every look the repo draws (128 species, every game volume and band, demo and reference looks) to the values from before the split
|
|
60
|
+
- `MENU_PANE_STORE_KEY` (`@jgengine/core/game/gameFlow`): ⚠ undocumented Also `MenuPane`, `menuPane`, `showMenuPane`.
|
|
61
|
+
- swimming can dive: movement.swim.dive makes crouch swim down to the bed and jump swim up, with telemetry diveDepth
|
|
62
|
+
- Fernwild breath: oxygen drains while the head is under water, refills at the surface, and drowning takes health (survival.oxygen-swim)
|
|
63
|
+
- `SwimDiveConfig` (`@jgengine/core/movement/playerMovement`): Metres a second a diver swims down, swims up, and drifts up with no input.
|
|
64
|
+
- bun run perf fps-gap measures every game at the frame gate's conditions and prints each against 360 fps (2.78 ms) with its costs grouped into ranked engine work items; a report, not a gate
|
|
65
|
+
- a desktop build runs without the display's vsync with <Game>.exe --uncapped on Windows (WebView2); without the flag it keeps vsync
|
|
66
|
+
- bun run perf desktop --launch <app.exe> [--uncapped] measures a production desktop build in the game's scene at the gate's conditions
|
|
67
|
+
- the capture_run agent-bridge verb runs a game's capture.play or a capture state in a shipped build, skipping dev-only helpers on request
|
|
68
|
+
- `CAPTURE_COMMAND_INPUT` (`@jgengine/core/devtools/agentBridge.draw`): The input a capture command named by a bare string runs with: a neutral look, so a command that reads an aim or heading gets one.
|
|
69
|
+
- `bun run perf crowd` times one crowd tick (neighbours within 1.5 m, separation, speed clamp, move, bounds; 0.5 agents/m²) written against createEntityStore + createSpatialIndex the way game code is, beside the same step over Float32Array columns and a counting-sort grid. Engine and flat ticks alternate so machine load hits both alike.
|
|
70
|
+
- useGameStoreLive re-renders a HUD only when what it draws changes and writes per-frame values through refs
|
|
71
|
+
- `MinimapChromeHandle` (`@jgengine/core/map/map.view`): Moves an already-drawn `MinimapChrome`'s markers in place.
|
|
72
|
+
- `useGameStoreLive` (`@jgengine/react`): Select from the game store at two speeds. Also `MinimapChromeHandle`.
|
|
73
|
+
- `useGameStoreLive` (`@jgengine/react/hooks`): Select from the game store at two speeds.
|
|
74
|
+
- bun run perf server, a repeatable load benchmark for the multiplayer host: N players in one room on the real createGameServerHost and createHostRouter, each subscribed to presence and posing every 100 ms of a fake clock that also drives the router's timers; one JSON line per room size (wall and CPU ms per game second, MB and messages out per game second, average message bytes), and a stderr note when a size cannot keep up in real time
|
|
75
|
+
- a run measures up to 100 steps and stops early after 5 s of measuring once it has 5, sizes stop at 256 players, and --max-seconds (default 60, warmup included) is the hard cap on one run; documented in README, the verify drive reference and the multiplayer skill; not a CI gate
|
|
76
|
+
- `store.columns`, the entity store's fast path for engine systems: resolve an id to an integer handle once (`columns.handleOf`), read pose from Float64Array position, velocity and rotation columns, and write by handle (`columns.setPosition`, `columns.setVelocity`) with the same constraint, derived velocity and notifications as `setPose`/`setVelocity`. Every write, by id or by handle, keeps the columns equal to the entity objects; despawned handles are reused with a bumped generation. Games keep using ids and never need it.
|
|
77
|
+
- `createPointGrid` (`@jgengine/core/spatial/pointGrid`), a per-tick counting-sort XZ neighbour grid over a typed-array position column that allocates nothing once warm and returns candidates in a fixed z-row, x, input order.
|
|
78
|
+
- `EntityColumns` (`@jgengine/core/scene/entityStore`): The store's fast path for engine systems that touch many entities per tick (crowds, flocks, pose interpolation, a server kernel): resolve an id to an integer handle once, then read pose straight from typed-array columns and write it by handle.
|
|
79
|
+
- `createPointGrid` (`@jgengine/core/spatial/pointGrid`): Build a {@link PointGrid}. Also `PointGrid`, `PointGridOptions`.
|
|
80
|
+
- `bindingOverrides` (`@jgengine/core/game/controlGate`): The player's rebinds in this game context: action to the codes that now drive it. Also `rebindAction`, `reboundActionCodes`, `setBindingOverrides`.
|
|
81
|
+
- `saveBindingOverrides` (`@jgengine/core/gameplay`): Store a game's whole rebind map (an empty one clears it), as the shell does whenever the game's rebinds change.
|
|
82
|
+
- `saveBindingOverrides` (`@jgengine/core/input/bindingOverrides`): Store a game's whole rebind map (an empty one clears it), as the shell does whenever the game's rebinds change.
|
|
83
|
+
- `solidEnclosure` (`@jgengine/core/world/overhead`): How shut in a point is: the share of the ways round it that meet a wall within reach, and 0 under open sky. Also `EnclosureOptions`.
|
|
84
|
+
- jgengine desktop --steam lays out a SteamPipe build beside the app (steam/content, steam/scripts with app_build_<appid>.vdf and depot_build_<depotid>.vdf, steam/output); the app id, depot id and live branch come from package.json "jgengine": { "steam": { ... } } or --steam-app, --steam-depot and --steam-branch, and a game without an app id still builds its content and is told the id is missing
|
|
85
|
+
- jgengine desktop --steamworks links the Steamworks API and a transparent surface Steam's overlay can draw into, ships steam_api64.dll in the depot, and forwards Shift+Tab from the page to the overlay
|
|
86
|
+
- jgengine desktop --no-installer builds only the game executable
|
|
87
|
+
- a desktop build's Frame rate limit setting offers Match display (V-Sync) and Uncapped; the choice is saved per player and applies on the next launch, and --uncapped still forces uncapped
|
|
88
|
+
- a desktop build's graphics settings gain Display mode (Windowed or Fullscreen): native borderless fullscreen that survives alt-tab, applied at once and remembered for the next launch
|
|
89
|
+
- `readDesktopShell`, `invokeDesktop`, `saveDesktopDisplayMode`, `saveDesktopFrameRate`, `desktopPresentMode`, `syncDesktopSettings`, `DISPLAY_MODE_OPTIONS` (`@jgengine/core/settings/desktopShell`): a desktop build's launch state and its commands; the settings store follows what the shell launched with
|
|
90
|
+
- `FRAME_RATE_LIMIT_UNCAPPED`, `frameRateLimitOptions` (`@jgengine/core/settings/frameRateLimit`): the frame-rate choices, with the desktop-only Uncapped option
|
|
91
|
+
- `SETTING_IDS.graphicsDisplayMode` (`@jgengine/core/settings/settingsModel`)
|
|
92
|
+
- bun run perf desktop --all [--uncapped] builds every game's production desktop app, measures each in turn and prints the 360 fps table; a report, not a gate
|
|
93
|
+
- every tame has a food meter; it eats what its diet takes from its own inventory and starves to death without (creatures.feed-starve)
|
|
94
|
+
- eatWhileRoom and DoseSupply in stats/doseTable, the shared 'eat from what you hold while there is room' step
|
|
95
|
+
- `eatWhileRoom` (`@jgengine/core/stats/doseTable`): Eat from `supply` while there is room: each time, the receiver's most preferred source it holds whose gain to `vital` fits under the max is taken and its doses go into `body`. Also `DoseSupply`.
|
|
96
|
+
- `CompanionStance` (`@jgengine/core/scene/companion`): What a tamed companion fights, wherever its {@link CompanionCommand} keeps it: - `passive` — never engages, even when struck (a pacifist escort).
|
|
97
|
+
- the creature panel says when a downed creature will wake
|
|
98
|
+
- `SaveStore.switchSlot(slot, { flush: false })` opens a slot without writing the current state first.
|
|
99
|
+
- Beacon's HUD says when a defense is not being saved, and its pause menu offers Withdraw without saving for it.
|
|
100
|
+
- The `reload-mid-run` Beacon flow raises a keep from the title, builds, starts wave 1, reloads the page and continues into the same defense.
|
|
101
|
+
- `HeadlessKeys` (`@jgengine/core/play/headlessKeys.draw`): Keys held against a headless {@link PlaySession}, mapped through the game's own bindings.
|
|
102
|
+
- `ContextMap` (`@jgengine/core/runtime/contextIdentity`): A `WeakMap` keyed by context identity that treats a wrapper of a context as the context itself ({@link aliasContext}). Also `contextOf`.
|
|
103
|
+
- `auditSuiteContent` (`@jgengine/core/suite/contentAudit`): The generic content check behind the suite's data entries: every item has a source and a use, every source is usable, every placeable item places, every firing weapon has obtainable ammo, and unlocks run steadily to the progression cap. Also `SuiteContent`, `SuiteContentItem`, `SuiteContentUse`, `SuiteUnlock`, `placedFact`, `recipeContent`.
|
|
104
|
+
- `expandSuiteGenres` (`@jgengine/core/suite/gameSuite`): The genres a declaration reaches, its includes followed. Also `GameSuiteConfig`, `GameSuiteScore`, `SuiteDataResult`, `SuiteProofResult`, `SuiteReport`, `SuiteScoreRow`, `SuiteStatus`, `parseSuiteReport` and 9 more.
|
|
105
|
+
- `runGameSuite` (`@jgengine/core/suite/runSuiteProof.draw`): Run a game's whole suite: every proof, then the content check. Also `LoadedSuiteProof`, `SuiteProofRun`, `SuiteProofRunOptions`, `runSuiteProof`.
|
|
106
|
+
- `SetupClosedError` (`@jgengine/core/suite/setupWindow`): Thrown when code touches a {@link SetupWindow} view after the window closed. Also `SetupWindow`, `openSetupWindow`.
|
|
107
|
+
- `SUITE_ENTRIES` (`@jgengine/core/suite/suiteCatalog`): Every entry, universal first, then by genre. Also `SUITE_GENRES`, `SUITE_UNIVERSAL`, `SuiteDepth`, `SuiteEntry`, `SuiteGenre`, `SuiteProofBy`.
|
|
108
|
+
- `CHEAT_COMMAND_NAME` (`@jgengine/core/suite/suiteProof`): Command names a proof treats as cheats even when the game forgot `cheat: true`. Also `ProofPlay`, `ProofSetup`, `SuiteProof`, `SuiteProofSpec`, `defineProof`.
|
|
109
|
+
- `STAND_ENVELOPE` (`@jgengine/core/plants/species/speciesReference`): How far a stand-grown tree may sit from its tables and still pass, at 20, 50 and 100 years in {@link REFERENCE_STAND}: height within 15% of its Carmean or FIA curve, DBH within 25% of its FIA curve, crown width inside its Tallo band.
|
|
110
|
+
- `FRAXINUS_NIGRA` (`@jgengine/core/plants/species/data/fraxinusNigra`): Black ash.
|
|
111
|
+
- `PRUNUS_SEROTINA` (`@jgengine/core/plants/species/data/prunusSerotina`): Black cherry.
|
|
112
|
+
- `ROBINIA_PSEUDOACACIA` (`@jgengine/core/plants/species/data/robiniaPseudoacacia`): Black locust.
|
|
113
|
+
|
|
114
|
+
### Changed
|
|
115
|
+
|
|
116
|
+
- A ridden flyer lifts a smaller creature in its talons
|
|
117
|
+
- The creature-survival suite asks for depth the research says matters: favourite and crafted taming food, waking loses the tame, gather roles at twice the best tool, lighter loads, airlift, feeding and starving, behavior modes, flight over terrain, encumbrance, farming, refining, fuel, preserving, tool wear, boss unlocks
|
|
118
|
+
- Fernwild: the slingshot throws stones and a new bow fires stone and tranquilizer arrows, so a charging raptor is knocked out from 25 m on two arrows; Fernwild scores 20/67
|
|
119
|
+
- Fernwild proof: the beach and the snow fill with their own animals
|
|
120
|
+
- The grass board notices every code edit
|
|
121
|
+
- Boat out of starts no longer runs the Linux suite on this PC: a start window that frees within the hour is waited for, one that frees later stops verify:fast with exit 75 and the time it frees, and lane:close lands nothing and says to close again then
|
|
122
|
+
- Boat runs hold a shared fairshare lease so every repo on the PC keeps to two boat machines; machines are named to fairshare so ones left by killed runs still count. Root cause: check-then-start let three machines run at once on 2026-11-07, since a machine is missing from boat list while it is created.
|
|
123
|
+
- Bolas, and every weapon hit is a dose list
|
|
124
|
+
- Summon the Broodmother, beat her and make brood greaves round her trophy
|
|
125
|
+
- A bred creature's screen shows which parent each stat came from and which a mutation raised
|
|
126
|
+
- Fernwild proof: the Broodmother calls her brood when wounded past two thirds
|
|
127
|
+
- Browser parity tests wait for their fixture before calling it
|
|
128
|
+
- Fernwild proof: fists dent a thatch wall but not a wooden pillar
|
|
129
|
+
- Cave air: swamp caves choke and sicken, lava caves burn hot, snow caves freeze
|
|
130
|
+
- Hostile caves: swamp gas burns breath and health, the lava cave is searing, the snow cave freezing
|
|
131
|
+
- Taming is built from general parts now: a body's meters (vitals, moved to @jgengine/core/stats/vitals) take doses that land now or over seconds up to a cap, and a dose table says what each source gives each kind of receiver, in preference order. The same two pieces describe a dino's foods, a sim's meals and shield potions that fill only to 50
|
|
132
|
+
- Fernwild's game-local subdual engine is deleted: torpor, belly, affinity and ineffectiveness are vitals, the diet is a dose table, knockout and the tame are flags. New proofs show waking loses the tame and a clean tame gives more levels; Fernwild scores 24/67
|
|
133
|
+
- Migration: import createVitals and its types from @jgengine/core/stats/vitals (was survival/vitals); VitalsState gains doses, and tick() also reports flags a change raised between ticks
|
|
134
|
+
- Fernwild proof: beach animals roll low levels and the far snow's roll high
|
|
135
|
+
- Fernwild proof: carrying slows the survivor and past the limit stops them
|
|
136
|
+
- Validation: the complete nine-game 1600x900 high moving-player gate measured zero shader links and framebuffers during play. Eight games failed the unchanged 16.7 ms frame limit while Wardogs used 26-31% GPU and whole-machine CPU reached 97%. The empty exception file and handling remain pending a clean all-game pass. An earlier quiet matched pair measured Harbor Heat 19.2 to 98.2 fps (102.5 to 12.5 ms p95) and Drift Foundry 62.8 to 113.4 fps (21.7 to 11.1 ms p95). Baseline Ember Command failed readiness twice and is not a matched comparison.
|
|
137
|
+
- 
|
|
138
|
+
- 
|
|
139
|
+
- 
|
|
140
|
+
- [Drift corridor movement proof](https://raw.githubusercontent.com/Noisemaker111/jgengine/pr-shots/pr-shots/agent/every-game-60fps-13/drift-foundry-sol-corridor.mp4): all arrival and movement checks passed; representative clip frames were inspected.
|
|
141
|
+
- Fernwild: a Small Crop Plot grows mejoberries from a seed, with water and fertilizer
|
|
142
|
+
- Following the Survivor's path through the beach uses every core feature first
|
|
143
|
+
- Fernwild proof: a tame follows when whistled and stays when told
|
|
144
|
+
- `bun run shoot --board --views` shoots six game views, one per grass game, the way a player sees them: the strategy games from their start camera, the others with the player placed in a grass patch (`--mode play --spawn`). They replace 18 close-up views from a detached camera 1 to 4 m off the ground, an angle the top-down games never show, three of which showed no grass
|
|
145
|
+
- beacon-bastion, brightway-park, ember-command, fernwild, harbor-heat and scrap-signal grass fields now name a species picked for their setting, and the grass-looks demo lays out every species.
|
|
146
|
+
- the engine bundle budget rises to 1880 KiB for the grass species table every grass field reads.
|
|
147
|
+
- A scene that sets no style draws exactly as before: every curve passes through today's look at realism 0.5
|
|
148
|
+
- Fernwild: a tame hauls what the survivor cannot, and an overloaded survivor can turn to it
|
|
149
|
+
- Fernwild: care raises a baby imprint, and the aim picks a baby standing in its parent
|
|
150
|
+
- A lane's landing wait runs in the harness background: lane:close, the Stop hook, the Monitor ban and AGENTS.md stop teaching a foreground background await --timeout 540 in slices, and a background await marks itself as the task's awaiter so the Stop hook lets a correctly waiting turn end
|
|
151
|
+
- Late gear from level 62 to the cap gives every Fernwild item a use
|
|
152
|
+
- The title and pause menus show their Controls, Settings and Credits panes through the menu.pane command
|
|
153
|
+
- Native Rust experiment for 360 fps: three.js frame export, wgpu replay with a pixel diff against three, Rune HUD, Deepward's tick in embedded V8 and in Rust, a wasm/WebGPU build, and bun run perf native for the four-way table. Verdict in native/README.md and #3041: stay on TS/three.js; the speedup Harbor Heat shows comes from WebGPU, in the browser too.
|
|
154
|
+
- Fernwild proof: a picked-clean berry bush grows back
|
|
155
|
+
- Diving and breath: crouch swims down, breath drains under water, drowning hurts
|
|
156
|
+
- Passive taming: an Ovis, Equus or Castoroides tames awake, hand-fed while hungry
|
|
157
|
+
- Cells live in an open-addressing table with their coordinates inline and their members in one pooled Int32Array; a query skips ids it already returned with a typed-array stamp instead of a Map. Cells keep creation order and each cell keeps its ids' order, so snapshot/restore still replay query results exactly. Public API unchanged.
|
|
158
|
+
- Measured in one process, old and new interleaved tick by tick (Bun 1.4.4, shared 24-core PC). Index alone, one querySphere + one update per id: 1k 9.8 → 1.4 ms (7.1x), 10k 207 → 24 ms (8.6x), 100k 4,199 → 881 ms (4.8x). Whole crowd tick through today's store: 1k 13.5 → 4.7 ms (2.9x), 10k 403 → 186 ms (2.2x), 100k 8,477 → 4,442 ms (1.9x); flat-array reference 0.19 / 2.0 / 38 ms.
|
|
159
|
+
- harbor-heat is unchanged, and was expected to be: the spatial index was ~1% of its frame and rendering dominates. Paired runs in both orders: old index 20.3 / 25.3 ms average frame, new index 26.6 ms (and one 53.7 ms run under machine load whose engine JS was not slower); the index's own profile share fell from 0.8-0.85% to 0.17-0.5%. This layout is the foundation for large crowds, big multiplayer worlds and the Rust multi-core kernel in #2999; the remaining crowd gap is string-keyed store lookups, addressed by the store fast path in a follow-up PR.
|
|
160
|
+
- Verified on a boat.dev Linux machine from a clean clone of head f602128fd: preflight (install, gen, type-checks, gates) and verify:fast (1001 test files, 31 type-checks, 6 gates, harbor-heat boot) passed.
|
|
161
|
+
- Regenerated the jgengine-world api.md for the spatial index's updated docs
|
|
162
|
+
- 128 players in one room, every player posing every 100 ms: 15 ms of host time per game second instead of 12 to 30 s on JSON (8.6 ms instead of 30 s on binary), and 25 MB/s out instead of 3.2 GB/s. The wire format is unchanged; #2990 covers binary delta rosters and interest-limited rows
|
|
163
|
+
- Measured: `bun run perf crowd` gains a by-handle row (store.columns plus the point grid), the three sides interleaved on the shared 24-core PC, median ms per tick: 1k by id 1.37, by handle 0.18, flat 0.12 (1.5x of flat); 10k by id 33.4, by handle 2.40, flat 1.41 (1.7x); 100k by id 1,629, by handle 41.3, flat 22.9 (1.8x). Under heavier machine load earlier the 100k ratio was 2.2x. The 3x target is met at both sizes on the handle path; the string-id path stays bounded by string-keyed lookups. This column layout is the one the Rust multi-core kernel in #2999 would read.
|
|
164
|
+
- harbor-heat is unchanged; this PR is the foundation for big crowds and multiplayer worlds, not a frame-time change for a game with tens of entities. Six alternating runs (branch, base, branch, base, then profiled) swung 17-28 ms average frame on both sides with other heavy jobs on the PC, and every system moved together, including GPU time and render passes this PR does not touch. Inside harbor-heat's own CPU profiles the changed files took 92 ms on the branch against 117 and 141 ms on base: pose history 14 ms against 28-34, the keyed store 3 against 9-10, scene spatial 11 against 21-25.
|
|
165
|
+
- Fernwild: a Preserving Bin keeps food four times as long
|
|
166
|
+
- Tests that read this PC's models pass in a fresh worktree
|
|
167
|
+
- A key moved in Settings takes effect in play: the game itself keeps the player's rebinds
|
|
168
|
+
- A refining forge smelts metal ore into ingots, and metal tools break stone
|
|
169
|
+
- Fernwild proof: a cleared beach herd comes back after the refill
|
|
170
|
+
- Flying: ride a Pteranodon or Argentavis, climb, cross and land; stamina out drops it
|
|
171
|
+
- A ridden Sarco swims through the sea at its own speed
|
|
172
|
+
- A save made in play survives quitting: Continue on the title brings it back
|
|
173
|
+
- Rebase conflicts inside a game's suite score resolve themselves
|
|
174
|
+
- Stop the served-game port test failing once in 483 runs
|
|
175
|
+
- A closed hut holds off the cold, and a survivor can walk up into one
|
|
176
|
+
- Fernwild: a hide sleeping bag places, and a dead survivor wakes on it once
|
|
177
|
+
- Fuelled stations: a campfire or pot burns wood, thatch or sparkpowder and cooks and warms only while lit
|
|
178
|
+
- Fernwild proof: sparkpowder crafts only beside a placed mortar
|
|
179
|
+
- Fernwild: the Storage Box places and keeps fifteen stacks
|
|
180
|
+
- a baby's hunger is the same meter at a faster pace; the breeding card no longer carries food
|
|
181
|
+
- Tames take a stance apart from follow or stay: passive, neutral or aggressive
|
|
182
|
+
- Tames at work: specialists gather their resource with a ridden bite and carry it lighter
|
|
183
|
+
- Tames gather what they're good at and carry it lighter
|
|
184
|
+
- Fernwild: kibble. Wild egg-layers drop eggs sized to their bodies, a cooking pot cooks five tiers of kibble from them, and each species tames about five times faster and cleaner on its preferred tier; proven side by side against raw meat and prime meat, Fernwild scores 22/67
|
|
185
|
+
- Fernwild proof: the snow at night takes health, and a lit campfire slows it
|
|
186
|
+
- Fernwild: tools wear with each landed swing or shot, break at zero, and repair with materials
|
|
187
|
+
- lane:close and verify:fast run every Linux-capable step on one boat.dev machine; game boots, model-reading and Windows-only tests stay on this PC
|
|
188
|
+
- The suite score generator writes into hand-edited READMEs and plans, so it names no outputs and reads only each game's suite files
|
|
189
|
+
- runStages test knows game-suite writes into hand-edited READMEs and PLANs
|
|
190
|
+
- jgengine suite scores the run it just made, so a regression reads red instead of the committed green report
|
|
191
|
+
- Game READMEs and plans lead with the suite score
|
|
192
|
+
- Brightway Park: headless suite proofs and the content check; Credits is not simulatable (#2967)
|
|
193
|
+
- Per-context state resolves a wrapped context to the real one, so a proof's setup seeds the game's own model
|
|
194
|
+
- perContext and the new ContextMap key by contextOf(ctx); the suite runner aliases its setup view to the
|
|
195
|
+
- session's context. A game helper handed the setup view used to build a second model for the wrapper.
|
|
196
|
+
- Scrap Signal: headless suite proofs; dev capture commands are marked cheat
|
|
197
|
+
- Fernwild: 18 headless suite proofs and the content check
|
|
198
|
+
- Games declare their suite genres; jgengine suite prints the score and gen writes it to README and PLAN
|
|
199
|
+
- Suite proofs: game code may keep the setup context, a command setup defines is a cheat, and the score renders by area
|
|
200
|
+
- The setup window lets the game's own ticks, commands and probe through after it closes, so a store
|
|
201
|
+
- that registered through the setup context keeps working; proof code that kept it is still refused.
|
|
202
|
+
- Game suite: a catalog of what players must be able to do, headless proofs that refuse cheats, and a content check
|
|
203
|
+
- Landing always runs beta's current landing code: lane:close fetches beta, unpacks its scripts/ once per version into .git/jg-landing/, and runs lane-close, land, the queue and the batch from there against the lane, so a landing fix reaches lanes made before it
|
|
204
|
+
- Breaking: the exports FRAXINUS_NIGRA, PRUNUS_SEROTINA and ROBINIA_PSEUDOACACIA and their plants/species/data subpaths are removed, because those species cannot yet grow a decurrent crown inside the forestry envelope; use another species from PLANT_LIBRARY
|
|
205
|
+
|
|
206
|
+
### Fixed
|
|
207
|
+
|
|
208
|
+
- `bun run shoot --board` no longer runs for hours under one 90-minute guard that killed full boards part way (292 captures at 40 s to 2 min each). One run shoots for `--budget` seconds (default 6 min), records each shot in the board folder's `board.json` as it lands and exits 3 while shots remain; the same command carries on, and the run that takes the last shot lays out the sheets. A killed run loses only the capture in flight, a failed capture is named and keeps the board unfinished (no sheets with gaps) until it lands, and shots taken with other code are retaken (`--resume` keeps them)
|
|
209
|
+
- A board run killed by its guard names the shot that hung instead of "hung before it reported a step"
|
|
210
|
+
- a lane's game suite reports come back from the boat machine and their scores are written before the commit, so new proofs count in each game's score
|
|
211
|
+
- grass and terrain clearing math stops calling Math.hypot per zone and per vertex, so streaming grass in ember-command allocates about a third less
|
|
212
|
+
- Scattered plants and rocks draw through the shared static batches, so every chunk, variant and detail level with the same material is one draw per area instead of one draw each (Fernwild: 738 draws down to about 370, frame p95 24.8 ms to 15.6 ms).
|
|
213
|
+
- Unique near plants and rocks (scatter heroes) join the same shared batches instead of drawing every part of every hero on its own in the main pass and each shadow cascade.
|
|
214
|
+
- World-projected (triplanar) materials now follow each placement's own rotation, so instanced and batched rocks blend their texture by their real facing, and they can share batched draws.
|
|
215
|
+
- A shared batch picks up textures its source material swaps in after the batch was made, such as generated rock surfaces whose bake lands after the first rock is placed.
|
|
216
|
+
- Clicking a building piece or other batched scenery that names no object no longer reports an empty object id.
|
|
217
|
+
- Harbor Heat no longer relinks its car shaders every few seconds or stalls when traffic comes and goes; its p95 frame went from 22.6 ms to 18.2 ms.
|
|
218
|
+
- the landing queue retries a failed GitHub read instead of giving up, and says what GitHub answered.
|
|
219
|
+
- landing retries a GitHub server error on a read or a push instead of giving up, and says what GitHub answered.
|
|
220
|
+
- landing retries a GitHub server error on a read, a push or a merge instead of giving up, and says what GitHub answered.
|
|
221
|
+
- flocks of birds draw as one instanced mesh per species instead of one skinned draw per bird
|
|
222
|
+
- placed props cost nothing per frame while nothing moves and the view shows the same set
|
|
223
|
+
- Harbor Heat holds 60 fps on the reference GPU (p95 16.2 ms, was 22.6 ms) and leaves the known-failing frame list
|
|
224
|
+
- shared scenery draws and impostor bakes survive temporary membership changes without rebuilding shaders in play.
|
|
225
|
+
- static props, buildings, character uploads, particles and the Drift Foundry HUD avoid repeated work while keeping their authored materials and detail.
|
|
226
|
+
- shadow and particle programs warm before play, and scatter loading holds readiness until the complete models mount.
|
|
227
|
+
- provisioning includes named asset-pack imports, and every game is measured moving at high quality with automatic quality changes disabled.
|
|
228
|
+
- the shader-ready check after load rides on the renderer's own draws instead of walking the whole visible scene before every render, so its cost follows what the camera draws, not the size of the world (Fernwild: 285 ms to 54 ms per 6 s)
|
|
229
|
+
- dev probes that walk the scene (triangles, shadow casters, static batch draws) walk it at most every 2 s; every slow frame and every telemetry report used to walk it again, which made slow games slower in dev and in perf runs
|
|
230
|
+
- the HUD overflow check measures panels at most twice a second instead of on every style change, which forced a page layout each frame in a HUD that moves bars or markers
|
|
231
|
+
- perf and capture wait for a slow load to finish instead of giving up at 90 s while the shader warmup still had 30 s left (Ember Command on a GPU short of memory); a perf batch's deadline grows with its games
|
|
232
|
+
- ambient fish, insects and motes reuse the ground height they last read until they move half a metre or near the edge of their height band, instead of every agent reading the sculpted ground every frame; they land exactly where they did on ground up to about 65°, and read it 5 to 15 times less often (Fernwild spent about 2.5 ms a frame there)
|
|
233
|
+
- small scatter species (shrubs, ferns, rocks) pick a detail level per plant by its own distance instead of per 200 m chunk, and stop drawing past their draw distance one plant at a time
|
|
234
|
+
- a scatter field keeps a mesh in the scene only while it has plants to draw; Fernwild kept about 11,900 empty meshes that three walked on every render (scene 13,315 to 4,817 nodes)
|
|
235
|
+
- the small-shadow cull stops watching a node once it leaves the scene, so meshes added and dropped as the camera moves are not held for the whole session
|
|
236
|
+
- verify:fast type-checks every package, app and game whose program imports a changed engine file, as CI's types job does; it checked only the games, so an engine change the shell's stricter settings reject passed locally and failed in CI
|
|
237
|
+
- the frame gate (bun run perf gate), CI's perf matrix and perf network --all measure every game under games/ with a web entry, read from disk, instead of a hand-kept list of 8 that left Fernwild unjudged; a new game is measured the day it lands
|
|
238
|
+
- the capture test that checks --reuse-storage refuses without a warm browser allows 30 s per start instead of 5; the script loads much of the engine first, which ran past 5 s on a cold CI runner and failed an unrelated PR
|
|
239
|
+
- props with a see-through part (street-lamp glass, foliage cards) now batch like other scenery instead of drawing one by one; each batch draws its copies farthest first, so blending looks as before. Harbor Heat drew its 28 street lamps and 28 bushes one by one; its scene draws drop from 440 to 336 a frame.
|
|
240
|
+
- Roads that look alike draw as one draw per layer (asphalt, gutter, lane paint, sidewalk, kerb), and every road junction of one colour is one draw: a street grid no longer costs five draws per street.
|
|
241
|
+
- Crowd characters that differ only in paint colour (traffic resprayed four ways) share one instanced draw per material, each drawing its own colour.
|
|
242
|
+
- Shared static-prop batches are culled whole by their cell instead of re-culling, re-sorting and re-uploading every instance in every render pass and shadow cascade.
|
|
243
|
+
- Foliage sway ramps over the model's own measured height, so one plant placed at several sizes sways alike and shares one batched draw.
|
|
244
|
+
- Auto exposure reads its light meter back at the start of a frame instead of after the scene, so it no longer stalls the frame for 7-118 ms while the browser finishes drawing.
|
|
245
|
+
- When the camera pans, at most one far shadow cascade redraws per frame (the longest waiting first) instead of all of them on the same frame.
|
|
246
|
+
- City ground layers with an edge fade draw in one pass instead of re-flagging their material twice every frame.
|
|
247
|
+
- Ember Command's HUD no longer re-renders every frame for a mission clock that changes once a second, and its minimap fog is built once per fog change as one rectangle per run of cells.
|
|
248
|
+
- The perf breakdown's own per-draw bookkeeping no longer allocates, so the garbage it reports is the game's.
|
|
249
|
+
- Characters out of view are no longer animated: they draw nothing and cast no shadow, so their skeletons are not posed until they come back into view (Scrap Signal: 3,508 bone writes a frame down to 32).
|
|
250
|
+
- Building kit pieces join the shared static batches, so walls and roofs of every building that share a material draw together per area instead of one draw per building chunk.
|
|
251
|
+
- Props with texture-map overrides (panel and scrap-metal crates, rocks) now batch like any other scenery instead of drawing one at a time; Scrap Signal's frame went from 25.7 ms to 16.5 ms at p95.
|
|
252
|
+
- grass no longer reads as a spotted patch. Tuft roots are laid on an even low-discrepancy pattern instead of random scatter, which left piles and bare gaps, and they stay even when distance thins them.
|
|
253
|
+
- each tuft's blades now spread far enough to meet the next tuft (at least three quarters of the tuft spacing), so a field is one sward instead of fountain islands on the turf mat.
|
|
254
|
+
- grass blades seen from behind are no longer near black. three.js flipped the whole normal on back faces and pointed the blade's sky lift into the ground. Both sides of a blade now light from above, and the lower blade sits in an even shade so a dense field still reads deep.
|
|
255
|
+
- clumping only pulls tufts into clumps when a look asks for strong clumping (or cushions), so ordinary lawns and meadows stop forming bald patches.
|
|
256
|
+
- the stray-browser sweep test no longer opens a Chrome window that takes the foreground; its planted browser now runs headless
|
|
257
|
+
- the home and /why pages' 2D canvas demos (car, keys, saves and the rest) stop drawing while offscreen or in a background tab, like the three.js demos already did
|
|
258
|
+
- traced code panels stop re-reading their lit lines every 90 ms while offscreen or in a background tab
|
|
259
|
+
- a production build's frame breakdown names its post passes instead of their minified class names
|
|
260
|
+
- perf play, perf gate and jgengine desktop link the game's asset packs first, so a fresh checkout no longer measures or ships placeholder boxes
|
|
261
|
+
- jgengine desktop --diagnostics passes --sourcemap through a game's own build script, and names the app after defineGame's own name
|
|
262
|
+
- createSpatialIndex keys cells by number and ids by integer handle, so moving ids and querying allocate nothing.
|
|
263
|
+
- Harbor Heat's chart stops re-rendering every frame; it pans, turns and places its markers through MinimapChrome's new place handle
|
|
264
|
+
- static prop batches with the same material share one material copy across cells, so the main pass stops re-uploading the same uniforms at each cell's batch
|
|
265
|
+
- a ws room sends at most one presence roster per pose interval (poseRules.minIntervalMs, 100 ms by default) instead of one per accepted pose; the first move in a quiet room, a player's first pose and a leave still go out at once, and later moves and appearance changes ride the roster that closes the window
|
|
266
|
+
- presence, chat and voice broadcasts encode each message once per codec and send the same frame to every subscriber
|
|
267
|
+
- `stepFlock` finds flockmates through the point grid instead of a per-call Map of arrays. Results are bit-identical over 120 steps at 50 and 500 agents; a step at 1k agents went 1.13 to 1.00 ms and at 10k 18.5 to 13.9 ms (8 agents: 5.8 µs either way).
|
|
268
|
+
- pose history captures each step from the store's columns into one flat array by handle. `beginStep` at 10k entities went 1.10 to 0.11 ms and at 100k 27.2 to 0.58 ms; sampling every entity at 100k went 19.5 to 14.6 ms. A handle reused by a new entity, or a body respawned under the same id, now renders live until the next step instead of gliding from the old body's pose.
|
|
269
|
+
- pose writes by id are cheaper, with no API change. setPose, setVelocity, update and resetToSpawn find the entity through the handle table (one id lookup where store.get then store.set took three) and notify through a new `store.touch`; the behaviour-change check skips its lookup when nothing is pending; a move that stays inside its scene-spatial cell skips the index bookkeeping. setPose plus setVelocity on a bare store, columns mirrored, beta and branch interleaved in one process: 10k 106 to 80 ns, 100k 210 to 195 ns. Through the game context's wiring (store onChange to the scene spatial index, one batch per frame), setPose with dt, median of three runs: 10k 271 to 259 ns, 100k 667 to 574 ns. A position given as {x, y, z} and a dt-derived velocity are still new tuples, each with the reason in code. Entity objects carry no handle: a hidden property made spreading them 8x slower in Bun.
|
|
270
|
+
- the land queue rebases a PR the base moved into conflict instead of giving up
|
|
271
|
+
- the demo nudge ring and arrow pause while their demo is off screen or the tab is hidden, so a page scrolled past a demo stops restyling it every frame
|
|
272
|
+
- a knocked-out creature now provably stays out until its torpor, plus any narcotic still landing, wears off; the wake proof fails if it wakes any sooner
|
|
273
|
+
- editor capture waits for its viewport and finished loading
|
|
274
|
+
- editor and asset provisioning help reject invalid options before changing files
|
|
275
|
+
- Keep weathering finite under deep cover
|
|
276
|
+
- Sort river sediment using its concentration near the bed
|
|
277
|
+
- Include hillslope creep in outlet volume
|
|
278
|
+
- Attribute test failures to the right file
|
|
279
|
+
- grass streaming bounds its work per frame and resumes it on the next, growing the nearest leaf first so the first frame still shows grass
|
|
280
|
+
- grass rebuilds when the terrain under it changes
|
|
281
|
+
- every grass patch on a canvas shares one 3 ms frame budget, the 8 ms fill budget ends once the first view has filled, and the first fill counts as loading so play never starts on half-grown grass
|
|
282
|
+
- captures wait out slow model loads for the whole settle budget instead of failing after one 15 s wait
|
|
283
|
+
- bun run shoot --load runs headless by default, so no jgengine capture opens a Chrome window unless --headed is passed; headless Chrome on Windows still renders on the real GPU.
|
|
284
|
+
- A Beacon defense saves itself once a second while playing, so a page reload mid-run continues into the same towers, purse, wave and raiders (about 0.2 ms and 12 KB per write on a busy defense).
|
|
285
|
+
- Opening or raising another Beacon keep never overwrites the keep left behind, and a keep changed in another window is left alone instead of overwritten.
|
|
286
|
+
- A HUD gated by `showDuring` shows its panels when play starts from a title menu.
|
|
287
|
+
- A failed capture is retried from its evidence instead of stopping after two attempts.
|
|
288
|
+
- black ash, black cherry and black locust are back in PLANT_LIBRARY, and FRAXINUS_NIGRA, PRUNUS_SEROTINA and ROBINIA_PSEUDOACACIA with their plants/species/data subpaths are exported again (#2959 removed them); each meets the tree envelope: stand height within 15% and DBH within 25% of its tables and inside the Tallo band at 20, 50 and 100 years, and a decurrent crown on two of three open-grown seeds. Their refits had stalled in the basins they started in; plants fit now starts a new tree search from the shipped tree of the same crown form that misses least of the tree checks (or one named with --from), and a missed check costs the fit 100 instead of 10
|
|
289
|
+
|
|
290
|
+
### Removed
|
|
291
|
+
|
|
292
|
+
- `@jgengine/core/survival/vitals` no longer exports `VitalDef`, `VitalFlag`, `VitalFlagChange`, `VitalLink`, `VitalRateModifier`, `Vitals`, `VitalsState`, `VitalsTickOptions` and 1 more.
|
|
293
|
+
- PERF_GATE_GAMES and perf-network's ALL_GAMES; FRAME_GAMES in scripts/ci-scope.ts is the one list
|
|
294
|
+
- `@jgengine/core/plants/species/data/fraxinusNigra` no longer exports `FRAXINUS_NIGRA`.
|
|
295
|
+
- `@jgengine/core/plants/species/data/prunusSerotina` no longer exports `PRUNUS_SEROTINA`.
|
|
296
|
+
- `@jgengine/core/plants/species/data/robiniaPseudoacacia` no longer exports `ROBINIA_PSEUDOACACIA`.
|
|
297
|
+
|
|
298
|
+
## 0.53.0
|
|
299
|
+
|
|
300
|
+
### Migrate
|
|
301
|
+
|
|
302
|
+
- **Bump lockstep SDK packages to `^0.53.0`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.50.0`; `@jgengine/github` is `0.40.0`.
|
|
303
|
+
- buildCatalog takes packs: [kaykitDungeon, ...] imported from @jgengine/assets/generated/packs instead of sources: ["kaykit-dungeon", ...].
|
|
304
|
+
- defineGame's uiKit takes the kit object (import { inkKit } from "@jgengine/core/ui/kit/uiKitInk.view") instead of a name; resolveUiKit is removed.
|
|
305
|
+
- an editor or tool that needs every scene kind without a game scene calls registerAllSceneKinds() and registerAllSceneKindRenderers().
|
|
306
|
+
|
|
307
|
+
### Added
|
|
308
|
+
|
|
309
|
+
- apps/web build:checked, the checked build deploy:cloudflare uploads and the release checks
|
|
310
|
+
- `STARTER_MODEL_PACKS` (`@jgengine/assets`): The model packs behind the curated starter assets: the same packs, in order, as `STARTER_SOURCE_PACKS`. Also `AssetPack`, `generatedPacks`, `packExportName`.
|
|
311
|
+
- `STARTER_MODEL_PACKS` (`@jgengine/assets/catalogs/starter`): The model packs behind the curated starter assets: the same packs, in order, as `STARTER_SOURCE_PACKS`.
|
|
312
|
+
- `generatedPacks` (`@jgengine/assets/generated`): Every pulled pack.
|
|
313
|
+
- `kaykitAdventurers` (`@jgengine/assets/generated/pack/kaykit-adventurers`): ⚠ undocumented
|
|
314
|
+
- `kaykitCityBuilder` (`@jgengine/assets/generated/pack/kaykit-city-builder`): ⚠ undocumented
|
|
315
|
+
- `kaykitDungeon` (`@jgengine/assets/generated/pack/kaykit-dungeon`): ⚠ undocumented
|
|
316
|
+
- `kaykitForest` (`@jgengine/assets/generated/pack/kaykit-forest`): ⚠ undocumented
|
|
317
|
+
- `kaykitFurniture` (`@jgengine/assets/generated/pack/kaykit-furniture`): ⚠ undocumented
|
|
318
|
+
- `kaykitMedievalHexagon` (`@jgengine/assets/generated/pack/kaykit-medieval-hexagon`): ⚠ undocumented
|
|
319
|
+
- `kaykitSkeletons` (`@jgengine/assets/generated/pack/kaykit-skeletons`): ⚠ undocumented
|
|
320
|
+
- `kaykitSpaceBase` (`@jgengine/assets/generated/pack/kaykit-space-base`): ⚠ undocumented
|
|
321
|
+
- `quaternius5LowPolyAnimals` (`@jgengine/assets/generated/pack/quaternius-5-low-poly-animals`): ⚠ undocumented
|
|
322
|
+
- `quaterniusAnimatedAnimalesLowPoly` (`@jgengine/assets/generated/pack/quaternius-animated-animales-low-poly`): ⚠ undocumented
|
|
323
|
+
- `quaterniusAnimatedAnimals` (`@jgengine/assets/generated/pack/quaternius-animated-animals`): ⚠ undocumented
|
|
324
|
+
- `quaterniusAnimatedCharactersPack` (`@jgengine/assets/generated/pack/quaternius-animated-characters-pack`): ⚠ undocumented
|
|
325
|
+
- `quaterniusAnimatedDinosaurs` (`@jgengine/assets/generated/pack/quaternius-animated-dinosaurs`): ⚠ undocumented
|
|
326
|
+
- `quaterniusAnimatedFish` (`@jgengine/assets/generated/pack/quaternius-animated-fish`): ⚠ undocumented
|
|
327
|
+
- `quaterniusAnimatedHumanLowPoly` (`@jgengine/assets/generated/pack/quaternius-animated-human-low-poly`): ⚠ undocumented
|
|
328
|
+
- `quaterniusAnimatedLowpolyRobot` (`@jgengine/assets/generated/pack/quaternius-animated-lowpoly-robot`): ⚠ undocumented
|
|
329
|
+
- `quaterniusAnimatedMechPack` (`@jgengine/assets/generated/pack/quaternius-animated-mech-pack`): ⚠ undocumented
|
|
330
|
+
- `quaterniusFantasyLowPolyTreesAndBushes` (`@jgengine/assets/generated/pack/quaternius-fantasy-low-poly-trees-and-bushes`): ⚠ undocumented
|
|
331
|
+
- `quaterniusFantasyProps` (`@jgengine/assets/generated/pack/quaternius-fantasy-props`): ⚠ undocumented
|
|
332
|
+
- `quaterniusFurnitureLowPoly` (`@jgengine/assets/generated/pack/quaternius-furniture-low-poly`): ⚠ undocumented
|
|
333
|
+
- `quaterniusLowPolyAnimatedGuns` (`@jgengine/assets/generated/pack/quaternius-low-poly-animated-guns`): ⚠ undocumented
|
|
334
|
+
- `quaterniusLowPolyCars0` (`@jgengine/assets/generated/pack/quaternius-low-poly-cars-0`): ⚠ undocumented
|
|
335
|
+
- `quaterniusLowPolyDesertAssets` (`@jgengine/assets/generated/pack/quaternius-low-poly-desert-assets`): ⚠ undocumented
|
|
336
|
+
- `quaterniusLowPolyFoodPack` (`@jgengine/assets/generated/pack/quaternius-low-poly-food-pack`): ⚠ undocumented
|
|
337
|
+
- `quaterniusLowPolyFurniture1` (`@jgengine/assets/generated/pack/quaternius-low-poly-furniture-1`): ⚠ undocumented
|
|
338
|
+
- `quaterniusLowPolyGuns` (`@jgengine/assets/generated/pack/quaternius-low-poly-guns`): ⚠ undocumented
|
|
339
|
+
- `quaterniusLowPolyGunsPack` (`@jgengine/assets/generated/pack/quaternius-low-poly-guns-pack`): ⚠ undocumented
|
|
340
|
+
- `quaterniusLowPolyMedievalWeapons` (`@jgengine/assets/generated/pack/quaternius-low-poly-medieval-weapons`): ⚠ undocumented
|
|
341
|
+
- `quaterniusLowPolyNaturePack1` (`@jgengine/assets/generated/pack/quaternius-low-poly-nature-pack-1`): ⚠ undocumented
|
|
342
|
+
- `quaterniusLowPolyPiratePack` (`@jgengine/assets/generated/pack/quaternius-low-poly-pirate-pack`): ⚠ undocumented
|
|
343
|
+
- `quaterniusLowPolyRpgPack` (`@jgengine/assets/generated/pack/quaternius-low-poly-rpg-pack`): ⚠ undocumented
|
|
344
|
+
- `quaterniusLowPolySpaceAssets` (`@jgengine/assets/generated/pack/quaternius-low-poly-space-assets`): ⚠ undocumented
|
|
345
|
+
- `quaterniusLowpolyAnimatedFarmAnimalPack` (`@jgengine/assets/generated/pack/quaternius-lowpoly-animated-farm-animal-pack`): ⚠ undocumented
|
|
346
|
+
- `quaterniusLowpolyAnimatedKnight` (`@jgengine/assets/generated/pack/quaternius-lowpoly-animated-knight`): ⚠ undocumented
|
|
347
|
+
- `quaterniusLowpolyAnimatedMonsters` (`@jgengine/assets/generated/pack/quaternius-lowpoly-animated-monsters`): ⚠ undocumented
|
|
348
|
+
- `quaterniusLowpolyBuildings` (`@jgengine/assets/generated/pack/quaternius-lowpoly-buildings`): ⚠ undocumented
|
|
349
|
+
- `quaterniusLowpolyBuildingsPack` (`@jgengine/assets/generated/pack/quaternius-lowpoly-buildings-pack`): ⚠ undocumented
|
|
350
|
+
- `quaterniusLowpolyCars` (`@jgengine/assets/generated/pack/quaternius-lowpoly-cars`): ⚠ undocumented
|
|
351
|
+
- `quaterniusLowpolyCropsPack` (`@jgengine/assets/generated/pack/quaternius-lowpoly-crops-pack`): ⚠ undocumented
|
|
352
|
+
- `quaterniusLowpolyHouseInteriorPack` (`@jgengine/assets/generated/pack/quaternius-lowpoly-house-interior-pack`): ⚠ undocumented
|
|
353
|
+
- `quaterniusLowpolyMedievalBuildings` (`@jgengine/assets/generated/pack/quaternius-lowpoly-medieval-buildings`): ⚠ undocumented
|
|
354
|
+
- `quaterniusLowpolyMedievalVillagePack` (`@jgengine/assets/generated/pack/quaternius-lowpoly-medieval-village-pack`): ⚠ undocumented
|
|
355
|
+
- `quaterniusLowpolyMedievalWeapons` (`@jgengine/assets/generated/pack/quaternius-lowpoly-medieval-weapons`): ⚠ undocumented
|
|
356
|
+
- `quaterniusLowpolyModularDungeonPack` (`@jgengine/assets/generated/pack/quaternius-lowpoly-modular-dungeon-pack`): ⚠ undocumented
|
|
357
|
+
- `quaterniusLowpolyModularSciFiEnvironments` (`@jgengine/assets/generated/pack/quaternius-lowpoly-modular-sci-fi-environments`): ⚠ undocumented
|
|
358
|
+
- `quaterniusLowpolyModularStreetPack` (`@jgengine/assets/generated/pack/quaternius-lowpoly-modular-street-pack`): ⚠ undocumented
|
|
359
|
+
- `quaterniusLowpolyNaturePack` (`@jgengine/assets/generated/pack/quaternius-lowpoly-nature-pack`): ⚠ undocumented
|
|
360
|
+
- `quaterniusLowpolyPosedHumans` (`@jgengine/assets/generated/pack/quaternius-lowpoly-posed-humans`): ⚠ undocumented
|
|
361
|
+
- `quaterniusLowpolyPublicTransport` (`@jgengine/assets/generated/pack/quaternius-lowpoly-public-transport`): ⚠ undocumented
|
|
362
|
+
- `quaterniusLowpolyRpg` (`@jgengine/assets/generated/pack/quaternius-lowpoly-rpg`): ⚠ undocumented
|
|
363
|
+
- `quaterniusLowpolyRpgCharacters` (`@jgengine/assets/generated/pack/quaternius-lowpoly-rpg-characters`): ⚠ undocumented
|
|
364
|
+
- `quaterniusLowpolyShipPack` (`@jgengine/assets/generated/pack/quaternius-lowpoly-ship-pack`): ⚠ undocumented
|
|
365
|
+
- `quaterniusLowpolySpaceshipsPack` (`@jgengine/assets/generated/pack/quaternius-lowpoly-spaceships-pack`): ⚠ undocumented
|
|
366
|
+
- `quaterniusLowpolySteampunkTurrets` (`@jgengine/assets/generated/pack/quaternius-lowpoly-steampunk-turrets`): ⚠ undocumented
|
|
367
|
+
- `quaterniusLowpolySurvivalPack` (`@jgengine/assets/generated/pack/quaternius-lowpoly-survival-pack`): ⚠ undocumented
|
|
368
|
+
- `quaterniusLowpolyTexturedTrees` (`@jgengine/assets/generated/pack/quaternius-lowpoly-textured-trees`): ⚠ undocumented
|
|
369
|
+
- `quaterniusMedievalVillage` (`@jgengine/assets/generated/pack/quaternius-medieval-village`): ⚠ undocumented
|
|
370
|
+
- `quaterniusModularMen` (`@jgengine/assets/generated/pack/quaternius-modular-men`): ⚠ undocumented
|
|
371
|
+
- `quaterniusModularScifi` (`@jgengine/assets/generated/pack/quaternius-modular-scifi`): ⚠ undocumented
|
|
372
|
+
- `quaterniusModularWomen` (`@jgengine/assets/generated/pack/quaternius-modular-women`): ⚠ undocumented
|
|
373
|
+
- `quaterniusStylizedNature` (`@jgengine/assets/generated/pack/quaternius-stylized-nature`): ⚠ undocumented
|
|
374
|
+
- `quaterniusTexturedCuteMonsterPack` (`@jgengine/assets/generated/pack/quaternius-textured-cute-monster-pack`): ⚠ undocumented
|
|
375
|
+
- `quaterniusToonShooter` (`@jgengine/assets/generated/pack/quaternius-toon-shooter`): ⚠ undocumented
|
|
376
|
+
- `kaykitAdventurers` (`@jgengine/assets/generated/packs`): ⚠ undocumented Also `kaykitCityBuilder`, `kaykitDungeon`, `kaykitForest`, `kaykitFurniture`, `kaykitMedievalHexagon`, `kaykitSkeletons`, `kaykitSpaceBase`, `quaternius5LowPolyAnimals` and 54 more.
|
|
377
|
+
- `writeIndexBarrels` (`@jgengine/assets/indexGen`): Writes the generated pack modules and the barrel over every `<source>.json` already in `outDir`.
|
|
378
|
+
- `packExportName` (`@jgengine/assets/manifest`): The name a pack is exported under in `generated/packs`: its source id in camelCase (`kaykit-dungeon` → `kaykitDungeon`). Also `AssetPack`.
|
|
379
|
+
- `OnDemandSceneKindId` (`@jgengine/core/scene/onDemandSceneKinds`): A heavy built-in system a scene loads only when it uses one of its kinds or generators. Also `SceneKindNeeds`.
|
|
380
|
+
|
|
381
|
+
### Changed
|
|
382
|
+
|
|
383
|
+
- every library species is checked for a valid record that grows finite, non-negative sizes and builds a mesh, open and in a stand
|
|
384
|
+
|
|
385
|
+
### Fixed
|
|
386
|
+
|
|
387
|
+
- the shrub fit reads its 3 to 30 stem limits from SHRUB_STEMS instead of its own copy
|
|
388
|
+
- a test edit no longer reruns the skill-api, capabilities, barrels, export-manifest, games-ledger, part-stats and skill-routing generators or the doc-symbols gate, none of which read test files (a run-stages row says so with skipsTests)
|
|
389
|
+
- when one test fails, the test batches already running finish instead of being killed, so the next run reuses their passes
|
|
390
|
+
- a failed test batch still records every file in it whose own tests passed, read from its junit report, so the retry reruns only the failing file
|
|
391
|
+
- tarballInstall.test runs only when every package's dist is fresh; it used to pack a stale dist on every engine lane in a worktree that had built once, 35 to 230 s that proved nothing about the change (CI builds right before it, so it still runs there)
|
|
392
|
+
- a release builds jgengine.com and runs its asset check before cutting, and stops with nothing published if that fails; 0.52.0 went to npm while the site deploy failed its bundle check, leaving npm and jgengine.com on different versions
|
|
393
|
+
- the UI kit tests no longer fail when they share a CI shard with the pointer probe test: tests that stub document now put back exactly what was there instead of leaving a document key set to undefined, which made the probe keep its stub
|
|
394
|
+
- every game downloaded about 3.7 MB of shared engine code, so the site build failed its size budgets. Each game now downloads 2.3 to 2.8 MB, and the code shared by every game fell from about 3.7 MB to 1.75 MB.
|
|
395
|
+
- a game bundles only the 3D model packs it names, only its own UI kit, and procedural cities and grown plants only when its scene uses them.
|
|
396
|
+
- download size is checked before merge (bun run check site-bundles in CI's web-build job): the shared engine once, against apps/dev/bundle-budget.json, and each game's own part against bundleKb, with the reason in bundleBaseline. The release's site check now checks only assets.
|
|
397
|
+
|
|
398
|
+
### Removed
|
|
399
|
+
|
|
400
|
+
- plant and tree tests that compared species to published forestry tables (Carmean height, FIA diameter, Tallo, Ek and Bechtold crown width, BAAD leaf per sapwood, Jenkins foliage mass, Reineke's thinning slope, USDA PLANTS heights, per-species fork and crown-shape bands); the species records from #2919 and #2921 are unchanged
|
|
401
|
+
- forestGrownCrownWidthM (@jgengine/core/plants/validation/crownAllometry), whose only user was a removed test
|
|
402
|
+
- `@jgengine/core/plants/validation/crownAllometry` no longer exports `forestGrownCrownWidthM`.
|
|
403
|
+
- `@jgengine/core/ui/kit/uiKits.view` no longer exports `resolveUiKit`.
|
|
404
|
+
- `@jgengine/react` no longer exports `resolveUiKit`.
|
|
405
|
+
|
|
29
406
|
## 0.52.0
|
|
30
407
|
|
|
31
408
|
### Migrate
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/convex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.54.0",
|
|
4
4
|
"description": "Convex adapters for JGengine: game transport, presence transport, and backend wiring over @jgengine/core.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"check-types": "tsgo --noEmit -p tsconfig.json"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@jgengine/core": "^0.
|
|
33
|
+
"@jgengine/core": "^0.54.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"convex": "^1.31.2",
|
package/skills/jgengine/SKILL.md
CHANGED
|
@@ -61,4 +61,4 @@ If nothing fits, name the shared owner first (AGENTS.md, "Reusable capability go
|
|
|
61
61
|
|
|
62
62
|
## Finish
|
|
63
63
|
|
|
64
|
-
Keep scene coordinates in the editor document, state serializable, randomness injected. A game is not done while its entities and props are box markers: run the asset pass in `jgengine-assets`. Prove it with `jgengine-verify`.
|
|
64
|
+
Declare the game's genres in `jgengine.json` (`suite`) and prove each required player verb with a headless proof; `jgengine suite <game>` is the score ([games/AGENTS.md](../../../games/AGENTS.md#game-suite)). Keep scene coordinates in the editor document, state serializable, randomness injected. A game is not done while its entities and props are box markers: run the asset pass in `jgengine-assets`. Prove it with `jgengine-verify`.
|
package/skills/jgengine/api.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
- `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
|
|
8
8
|
- `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
|
|
9
|
-
- `VERSION` (const): const VERSION: "0.
|
|
9
|
+
- `VERSION` (const): const VERSION: "0.54.0" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
|
|
10
10
|
|
|
11
11
|
## @jgengine/core/authoring
|
|
12
12
|
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
|
|
84
84
|
- `CommandDecodeResult` (type): type CommandDecodeResult<TInput> = | { ok: true; value: TInput } | { ok: false; reason: string } — ⚠ undocumented
|
|
85
85
|
- `CommandDecoder` (type): type CommandDecoder<TInput> = (input: unknown) => CommandDecodeResult<TInput> — Parses raw `unknown` transport input into `TInput`, rejecting anything that doesn't match the command's declared shape. Runs before `validate`/`apply`, so a malformed payload never reaches game logic.
|
|
86
|
-
- `CommandDefinition` (interface): interface CommandDefinition<TState, TInput> { decode?: CommandDecoder<TInput>; validate?(state: TState, input: TInput): CommandRejection | null; apply(state: TState, input: TInput): TState | void } — ⚠ undocumented
|
|
87
|
-
- `CommandRegistry` (interface): interface CommandRegistry<TState> { define<TInput>(name: string, definition: CommandDefinition<TState, TInput>): void; has(name: string): boolean; names(): string[]; run(state: TState, name: string, input: unknown): CommandResult<TState> } — ⚠ undocumented
|
|
86
|
+
- `CommandDefinition` (interface): interface CommandDefinition<TState, TInput> { decode?: CommandDecoder<TInput>; validate?(state: TState, input: TInput): CommandRejection | null; apply(state: TState, input: TInput): TState | void; cheat?: boolean } — ⚠ undocumented
|
|
87
|
+
- `CommandRegistry` (interface): interface CommandRegistry<TState> { define<TInput>(name: string, definition: CommandDefinition<TState, TInput>): void; has(name: string): boolean; names(): string[]; isCheat(name: string): boolean; run(state: TState, name: string, input: unknown): CommandResult<TState> } — ⚠ undocumented
|
|
88
88
|
- `CommandRejection` (interface): interface CommandRejection — Why a command refused: `reason` is a stable machine code; `message`, when set, is shown to the player.
|
|
89
89
|
- `CommandResult` (type): type CommandResult<TState> = | { status: "applied"; state: TState } | { status: "rejected"; reason: string; message?: string } | { status: "unknown-command" } — ⚠ undocumented
|
|
90
90
|
- `createCommandRegistry` (function): function createCommandRegistry<TState>(): CommandRegistry<TState> — ⚠ undocumented
|
|
@@ -142,6 +142,7 @@
|
|
|
142
142
|
|
|
143
143
|
- `AgentBridgeRequest` (type): type AgentBridgeRequest = { method: string } & Record<string, unknown> — One RPC call into the agent bridge: a verb name plus its verb-specific fields.
|
|
144
144
|
- `AgentBridgeResponse` (type): type AgentBridgeResponse = | { ok: true; [key: string]: unknown } | { ok: false; error: string } — Result of an agent bridge call: `ok: true` with verb payload, or `ok: false` with a reason.
|
|
145
|
+
- `CAPTURE_COMMAND_INPUT` (const): const CAPTURE_COMMAND_INPUT: { readonly yaw: 0; readonly pitch: 0; readonly aim: { readonly yaw: 0; readonly pitch: 0; }; } — The input a capture command named by a bare string runs with: a neutral look, so a command that reads an aim or heading gets one. Shared by the dev runner's capture mode and `capture_run`.
|
|
145
146
|
|
|
146
147
|
## @jgengine/core/devtools/collisionDebug
|
|
147
148
|
|
|
@@ -256,6 +257,7 @@
|
|
|
256
257
|
|
|
257
258
|
- `DiscoverModule` (interface): interface DiscoverModule — Discovery subsystem: the public discover and probes facades plus internal record map and helpers.
|
|
258
259
|
- `DiscoveredRecord` (interface): interface DiscoveredRecord — Internal record for an auto-discovered field: identity, kind, initial value, source object, and get/set accessors.
|
|
260
|
+
- `SCENE_PROBE_REFRESH_MS` (const): const SCENE_PROBE_REFRESH_MS: 2000 — How often a probe that walks the whole scene walks it again; in between it answers its last reading.
|
|
259
261
|
- `createDiscoverModule` (const): const createDiscoverModule: (deps: { signal: ChangeSignal; register: <T>(name: string, initial: T, options?: TunableOptions<T> | undefined) => Tunable<T>; controlRecords: Map<string, ControlRecord>; }) => DiscoverModule — Create the discovery subsystem that scans objects/tables for tunable fields and binds them as controls and probes.
|
|
260
262
|
|
|
261
263
|
## @jgengine/core/devtools/fallbackSeams
|
|
@@ -445,7 +447,7 @@
|
|
|
445
447
|
|
|
446
448
|
- `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
|
|
447
449
|
- `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
|
|
448
|
-
- `VERSION` (const): const VERSION: "0.
|
|
450
|
+
- `VERSION` (const): const VERSION: "0.54.0" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
|
|
449
451
|
|
|
450
452
|
## @jgengine/core/runtime/adapter
|
|
451
453
|
|
|
@@ -497,6 +499,11 @@
|
|
|
497
499
|
- `ObjectSlotTransfer` (interface): interface ObjectSlotTransfer — Which player inventory a transfer moves between, and what it moves.
|
|
498
500
|
- `SceneObjectSlots` (interface): interface SceneObjectSlots — Per-instance container contents for placed objects whose catalog entry declares `slotInventory` — the engine side of "install a GPU into a rack", "put an item in a chest". Contents live on `SceneObject.slots`, so they replicate and save with the object rather than needing a game-owned table keyed by `instanceId`, and every mutation is validated against the declared `accepts` and stack limits.
|
|
499
501
|
|
|
502
|
+
## @jgengine/core/runtime/contextIdentity
|
|
503
|
+
|
|
504
|
+
- `ContextMap` (class): class ContextMap<T> — A `WeakMap` keyed by context identity that treats a wrapper of a context as the context itself ({@link aliasContext}). Use it for per-context state instead of `new WeakMap<GameContext, T>()`.
|
|
505
|
+
- `contextOf` (function): function contextOf(ctx: GameContext): GameContext — The context `ctx` stands for: itself, or the context a wrapper of it was aliased to.
|
|
506
|
+
|
|
500
507
|
## @jgengine/core/runtime/gameContext
|
|
501
508
|
|
|
502
509
|
- `CatalogEntityRole` (type): type CatalogEntityRole = "player" | "enemy" | "hostile" | "npc" | "vehicle" — ⚠ undocumented
|
|
@@ -505,7 +512,7 @@
|
|
|
505
512
|
- `GameAudio` (interface): interface GameAudio — Reachable audio seam on `ctx.game`: `play`, `music`, and `resume` route through the `audio.play`/`audio.music`/`audio.resume` events the shell's audio engine listens on, so game code triggers sound without importing the shell. Retained `loop`/`setLoop`/`stopLoop` add id-keyed loops with live pitch/gain control over `audio.loopStart`/`audio.loopSet`/`audio.loopStop` (#1051).
|
|
506
513
|
- `GameContext` (interface): interface GameContext — The live engine handle a game's loop, systems, commands, and UI read and mutate — entities, objects, the reactive store, opted-in `game.*` subsystems, world queries, and the sim clock. One context = one running world.
|
|
507
514
|
- `GameContextCards` (interface): interface GameContextCards { pile(id: string, config?: CardPileConfig): CardPile } — ⚠ undocumented
|
|
508
|
-
- `GameContextCommands` (interface): interface GameContextCommands { define<TInput>(name: string, definition: CommandDefinition<GameContext, TInput>): void; has(name: string): boolean; names(): string[]; run(name: string, input: unknown): CommandResult<GameContext>; runAs(
|
|
515
|
+
- `GameContextCommands` (interface): interface GameContextCommands { define<TInput>(name: string, definition: CommandDefinition<GameContext, TInput>): void; has(name: string): boolean; names(): string[]; isCheat(name: string): boolean; run(name: string, input: unknown): CommandResult<GameContext>; runAs(ac… — ⚠ undocumented
|
|
509
516
|
- `GameContextContent` (interface): interface GameContextContent { defaultRarity?: string; itemById?(itemId: string): GameContextItemEntry | null | undefined; entityById?(catalogId: string): GameContextEntityEntry | null | undefined; objectById?(catalogId: string): GameContextObjectEntry | null | undefin… — ⚠ undocumented
|
|
510
517
|
- `GameContextEconomy` (interface): interface GameContextEconomy { balance(userId: string, currencyId: string | CurrencyDefinition): number; grant(userId: string, currencyId: string | CurrencyDefinition, amount: number): void; charge(userId: string, currencyId: string | CurrencyDefinition, amount: number… — ⚠ undocumented
|
|
511
518
|
- `GameContextEntityEntry` (interface): interface GameContextEntityEntry { stats?: StatCatalog; receive?: ReceiveMap; onDeath?: OnDeathSpec; movement?: PoseAllowedStates & {walkSpeed?: number}; role?: CatalogEntityRole; verbs?: readonly ContextVerb[]; colliders?: EntityColliderSet; scale?: number } — ⚠ undocumented · used by `entityEntryFromCatalog` (@jgengine/core/editor): Turns an authored entity row into the runtime {@link GameContextEntityEntry} a spawn consumes: looks up `entityId` in the document's {@link …
|
|
@@ -536,7 +543,7 @@
|
|
|
536
543
|
- `GameAudio` (interface): interface GameAudio — Reachable audio seam on `ctx.game`: `play`, `music`, and `resume` route through the `audio.play`/`audio.music`/`audio.resume` events the shell's audio engine listens on, so game code triggers sound without importing the shell. Retained `loop`/`setLoop`/`stopLoop` add id-keyed loops with live pitch/gain control over `audio.loopStart`/`audio.loopSet`/`audio.loopStop` (#1051).
|
|
537
544
|
- `GameContext` (interface): interface GameContext — The live engine handle a game's loop, systems, commands, and UI read and mutate — entities, objects, the reactive store, opted-in `game.*` subsystems, world queries, and the sim clock. One context = one running world.
|
|
538
545
|
- `GameContextCards` (interface): interface GameContextCards { pile(id: string, config?: CardPileConfig): CardPile } — ⚠ undocumented
|
|
539
|
-
- `GameContextCommands` (interface): interface GameContextCommands { define<TInput>(name: string, definition: CommandDefinition<GameContext, TInput>): void; has(name: string): boolean; names(): string[]; run(name: string, input: unknown): CommandResult<GameContext>; runAs(
|
|
546
|
+
- `GameContextCommands` (interface): interface GameContextCommands { define<TInput>(name: string, definition: CommandDefinition<GameContext, TInput>): void; has(name: string): boolean; names(): string[]; isCheat(name: string): boolean; run(name: string, input: unknown): CommandResult<GameContext>; runAs(ac… — ⚠ undocumented
|
|
540
547
|
- `GameContextContent` (interface): interface GameContextContent { defaultRarity?: string; itemById?(itemId: string): GameContextItemEntry | null | undefined; entityById?(catalogId: string): GameContextEntityEntry | null | undefined; objectById?(catalogId: string): GameContextObjectEntry | null | undefin… — ⚠ undocumented
|
|
541
548
|
- `GameContextEconomy` (interface): interface GameContextEconomy { balance(userId: string, currencyId: string | CurrencyDefinition): number; grant(userId: string, currencyId: string | CurrencyDefinition, amount: number): void; charge(userId: string, currencyId: string | CurrencyDefinition, amount: number… — ⚠ undocumented
|
|
542
549
|
- `GameContextEntityEntry` (interface): interface GameContextEntityEntry { stats?: StatCatalog; receive?: ReceiveMap; onDeath?: OnDeathSpec; movement?: PoseAllowedStates & {walkSpeed?: number}; role?: CatalogEntityRole; verbs?: readonly ContextVerb[]; colliders?: EntityColliderSet; scale?: number } — ⚠ undocumented · used by `entityEntryFromCatalog` (@jgengine/core/editor): Turns an authored entity row into the runtime {@link GameContextEntityEntry} a spawn consumes: looks up `entityId` in the document's {@link …
|
|
@@ -699,7 +706,7 @@
|
|
|
699
706
|
- `PoseHistory` (interface): interface PoseHistory — Previous-step pose buffer: capture at each fixed step, sample between steps for rendering.
|
|
700
707
|
- `PoseHistoryState` (interface): interface PoseHistoryState — Serializable previous-step poses keyed by entity id.
|
|
701
708
|
- `RenderPose` (type): type RenderPose = [x: number, y: number, z: number, rotationY: number] — A render pose sampled between two simulation steps: `[x, y, z, rotationY]`.
|
|
702
|
-
- `createPoseHistory` (function): function createPoseHistory(options?: { snapDistance?: number }): PoseHistory — Previous-step pose buffer for render interpolation under a fixed-step loop.
|
|
709
|
+
- `createPoseHistory` (function): function createPoseHistory(options?: { snapDistance?: number }): PoseHistory — Previous-step pose buffer for render interpolation under a fixed-step loop. Reads poses straight from the store's {@link EntityStore.columns} into one flat array indexed by handle, so a step over N entities allocates nothing and does no id lookups. Each captured pose remembers its handle's generation: a handle reused by a new entity, or a body respawned under the same id, renders live until the next step.
|
|
703
710
|
- `lerpAngle` (function): function lerpAngle(from: number, to: number, t: number): number — Shortest-arc interpolation between two yaw angles.
|
|
704
711
|
|
|
705
712
|
## @jgengine/core/runtime/prediction
|
|
@@ -48,6 +48,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
48
48
|
|
|
49
49
|
- `readQuantity` (function) · `import { readQuantity } from "@jgengine/core/commands/commandInput"`
|
|
50
50
|
|
|
51
|
+
## context-map — per-context state keyed by the context a wrapper stands for, so a wrapped context shares its model
|
|
52
|
+
|
|
53
|
+
- `ContextMap` (class) · `import { ContextMap } from "@jgengine/core/runtime/contextIdentity"`
|
|
54
|
+
|
|
51
55
|
## default-drive-codes — stock WASD/arrow throttle, brake, steer and handbrake key codes
|
|
52
56
|
|
|
53
57
|
- `DEFAULT_DRIVE_CODES` (const) · `import { DEFAULT_DRIVE_CODES } from "@jgengine/shell/gameKit"`
|
|
@@ -46,7 +46,7 @@ Use [capabilities.md](capabilities.md) for intent-to-import discovery, [api.md](
|
|
|
46
46
|
- A viewer's interest is the game's `replication: { aoiRadius, aoiAlways }`. The host asks the scene spatial index for entities within `aoiRadius` of the viewer's own entity, adds `aoiAlways` ids (a list or a function), and sends each connection a diff of only what entered, changed or left its view. A viewer with no entity yet holds every entity.
|
|
47
47
|
- A hosted world auto-saves a changed world at most once per `saveIntervalMs` (the node server and the browser host default to `DEFAULT_HOST_SAVE_INTERVAL_MS`, 1000); `0` saves after every changed tick, which copies the whole world 20 times a second. `bun scripts/multiplayer-load.ts` reports host ms per tick, and bytes allocated per tick under `BUN_JSC_useGC=false`.
|
|
48
48
|
- A module that throttles itself with `priority` still gets a projected baseline each tick instead of diffs.
|
|
49
|
-
- Measure with `bun scripts/multiplayer-load.ts` (bytes and host ms per tick for N clients and E entities) and `bun scripts/multiplayer-two-clients.ts` (a real game, two clients).
|
|
49
|
+
- Measure with `bun run perf server` (N players posing in one room: host wall and CPU ms, MB and messages out per game second, from presence fan-out), `bun scripts/multiplayer-load.ts` (world replication: bytes and host ms per tick for N clients and E entities) and `bun scripts/multiplayer-two-clients.ts` (a real game, two clients).
|
|
50
50
|
|
|
51
51
|
## Traps
|
|
52
52
|
|
|
@@ -52,6 +52,8 @@ Once a session resolves, the shell (`GameServerHost`'s internal player) wires it
|
|
|
52
52
|
|
|
53
53
|
A synced pose (`@jgengine/ws/protocol`'s `WsPose`/`WsPresenceRow`) carries an optional `appearance: Record<string, string | number | boolean>` alongside position/rotation — primitive-valued client-set tags (skin id, mount id, active emote, team color) riding the same pose-sync frames as position, so a remote player's cosmetic state updates on the existing presence channel instead of a second round-trip.
|
|
54
54
|
|
|
55
|
+
The ws host router (`createHostRouter`) sends each room's full presence roster at most once per pose interval (`poseRules.minIntervalMs`, 100 ms by default), however many players move: the first move in a quiet room goes out at once, later moves and appearance changes ride the roster that closes the window, and a player's first pose or a leave goes out immediately. Every broadcast (presence, chat, voice) is encoded once per codec and the same frame goes to every subscriber, so a room's cost grows with players × subscribers, not with its cube.
|
|
56
|
+
|
|
55
57
|
**Server side** — `@jgengine/convex/server` ships an entire authoritative Convex backend as factories, not a template to copy: `jgengineTables()` (schema spread), `createGameServerFunctions({ runtimes?, auth? })`, `createLeaderboardFunctions({ auth? })`, `createPresenceFunctions({ auth?, freshWindowMs?, idleCutoffMs?, poseRules?, resolveSpawn? })`, `createChatFunctions({ auth?, historyLimit?, maxBodyLength?, minIntervalMs? })`, and `jgengineCronSpecs()` (tick/flush interval metadata for a `crons.ts`). A consumer's `convex/` directory is ~25 lines: `schema.ts` (`defineSchema({ ...jgengineTables() })`), four one-line re-export files (`runtime.ts`, `leaderboard.ts`, `presence.ts`, `chat.ts`, each just calling its factory), and `crons.ts` registering the tick (1s) + flush (60s) internal mutations — see `examples/convex-host` for the reference shape. No game-specific code lives there; any JGengine game can point at the same deployment. Games without a registered `GameRuntime` fall back to a no-save runtime that only understands `engine.ping`; pass `createGameServerFunctions({ runtimes: [createGameRuntime({ gameId, commands, loop, save })] })` to make `runCommand` / tick / save actually do something. When a host mutation must pair snapshot work with its own table writes in one transaction, use the factory's `helpers` (`loadSnapshot`, `applyCommand`, `persistSnapshot`, `runCommand` — same registry and auth mode) or the standalone `loadServerSnapshot` / `persistServerSnapshot` exports.
|
|
56
58
|
|
|
57
59
|
**How big a hosted world gets.** One server is one Convex document holding the world row, the roster, and every member's session state, so member count is a per-transaction cost. `slotsPerServer` is capped at `JG_MAX_MEMBERS_PER_SERVER` (256) and `createGameServerFunctions` throws at wiring time on anything larger — a bigger number was never a bigger world, just a document that overflows later. World geometry that outgrows one row goes in `jgWorldChunks`: hydrate by key (`loadServerSnapshot(ctx, server, runtime, { players, chunkKeys })`), read from clients with the generated `getChunks`, and key cells with `chunkKeyAt` / `chunkKeysInRadius` from `@jgengine/core/runtime/worldChunks`. Mutate chunks through `setChunk` / `updateChunk` / `deleteChunk` — a raw `snapshot.chunks[key] = …` is never marked dirty and never persists.
|