@jgengine/assets 0.15.0 → 0.16.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 +32 -0
- package/package.json +2 -2
- package/skills/jgengine/api.md +15 -3
- package/skills/jgengine/capabilities.md +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -34,6 +34,38 @@ At publish, rename this heading to the new version and mirror the entries into
|
|
|
34
34
|
|
|
35
35
|
### Fixed
|
|
36
36
|
|
|
37
|
+
## 0.16.0
|
|
38
|
+
|
|
39
|
+
### Migrate
|
|
40
|
+
|
|
41
|
+
- **Bump lockstep SDK packages to `^0.16.0`:** `@jgengine/{core,react,ws,node,sql,convex,shell,editor,assets}`. CLI `jgengine` is `0.13.0`; `@jgengine/github` is `0.3.0`.
|
|
42
|
+
- **Placed objects now replicate.** `objects` joined the always-on baseline snapshot modules, so `ctx.hydrate` replaces the client's placed object set with the host's, the way `entities` already did. A game whose peers all run the same authored placement is unaffected; a game that places props on the client only, after hydrate, must move that placement to the host or keep it out of `ctx.scene.object`.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- **`GeneratedBuilding`'s `kit` prop is now the `BuildingKit`**; the per-part React escape hatch it used to name moved to `partRenderer`, so `kit` means the same thing on `GeneratedBuilding` and `InstancedBuildings`. Rename the prop if you passed a `BuildingKitRenderer`.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- **`modelLoadFallbacks()` from `@jgengine/shell/render/modelLoad`** lists every model the shared loader diagnosed as broken and replaced with a magenta placeholder. A placeholder was only ever a console warning, so a shipped game could render one in its starting area and nobody found out until someone looked at a screenshot; a capture host or smoke test can now require the list to be empty. `clearModelLoadFallbacks()` resets it between runs.
|
|
51
|
+
- **`worldHealthBars.occlude` / `WorldHealthBars`+`WorldNameplates` `occlude` prop.** World bars and nameplates are a screen-space overlay drawn outside the WebGL depth buffer, so nothing in the scene can hide them and an enemy's health bar reads straight through the building it stands behind. Set `occlude: true` to drop a bar when world geometry blocks the line from the player to the entity. Default `false` preserves the always-visible behaviour every shipped game currently relies on.
|
|
52
|
+
|
|
53
|
+
- **`DustField` + `WeatherLayer` mode `"dust"` (`@jgengine/shell/weather`) — wind-borne particulate.** `WeatherLayer` could only do `clear`/`rain`/`snow`/`mixed`, so an arid, volcanic, ashen, or blown-out world had no way to put anything in the air. Dust drifts on horizontal wind with a slow vertical bob rather than falling; `groundBias` packs density toward the surface, motes fade near the top of the volume so recycled ones do not pop against the sky, and faster motes ride higher. Set `dustAlways` to keep it in the air alongside rain or snow instead of replacing them. Defaults `DEFAULT_DUST_COUNT`/`DEFAULT_DUST_DENSITY` sit beside the rain and snow pair.
|
|
54
|
+
|
|
55
|
+
- **Authored `city` volumes render their near-LOD facades with a `BuildingKit`.** `CityRules.buildingKit` names a kit; a game registers the bound art with `registerBuildingKit(kit)` from `@jgengine/shell/structures/buildingKitRegistry` and the scene document carries only the name, so a city stays portable and free of model paths. Unset or unknown names keep the palette blocks. This puts the whole `city` primitive — not just the `building()` feature — on the kit path.
|
|
56
|
+
- **`BuildingKit` from `@jgengine/core/world/buildingKit`** binds a generated building's facade part slots to real models, so procedural buildings render as an instanced asset kit instead of untextured blocks. `defineBuildingKit({ parts: { wall: ["Wall_A.glb", …] } })` maps a part kind — or a `${facade}.${kind}` slot — to ordered variants that `kit.variant` indexes, with `fit`/`offset`/`rotation`/`scale`/`tint`/`autoOrient` per variant. Kinds a kit leaves unbound keep rendering the palette block, so a partial kit is a valid kit, and `omit` drops a kind entirely. Pass one through `building({ kit })` or straight to `InstancedBuildings`; `buildingKitVariantCounts(kit)` feeds `generateBuilding` so its seeded picks span exactly the models the kit binds.
|
|
57
|
+
- **`generateBuilding`, `generateBuildingDistrict`, and `createBuildingConfig` are now documented public API** (`@jgengine/core/world/buildings`) and appear in `capabilities.md` under `building-generator`.
|
|
58
|
+
- **`capture.views` on `defineGame`** — named framings (`GameCaptureView`: `look`, `lookFrom`, `spawn`, `state`, `run`, `settleMs`) that `shoot <game> --view <name>` and `drive --view <name>` replay exactly, so a before/after pair is the same view by construction. `shoot <game> --list-views` prints what a game declares; an unknown name fails listing them, and any explicit capture flag still wins over the view's value.
|
|
59
|
+
- **`modelLoadIdleMs()` from `@jgengine/shell/render/modelLoad`** reports how long the shared GLB loader has been idle (`0` while a model is in flight). Capture hosts wait on it instead of guessing a settle delay, so an establishing shot no longer races streaming models.
|
|
60
|
+
- **Per-instance state on placed objects** — `SceneObject.state` (opaque `Record`), set through `ctx.scene.object.setState`/`patchState`/`place({ state })`. It survives move/rotate/setVisual, replicates and saves with the object, and round-trips as `RuntimeObjectRow.flags` via `toRuntimeObjectRow`/`fromRuntimeObjectRow` (`@jgengine/core/multiplayer`). A chest's locked flag or a machine's fill level no longer needs a game-owned map keyed by `instanceId`.
|
|
61
|
+
- **`slotInventory` is a working feature** — `ctx.scene.object.slots` gives objects whose catalog entry declares it real per-instance container contents: `layout`/`state`/`count`, `put`/`take`, and `transferIn`/`transferOut` between a player inventory and the object, all validated against the declared slot count, `accepts` kind filter, and stack limits, with neither side mutated on a rejection. Contents live on `SceneObject.slots`, so they replicate and save with the placement.
|
|
62
|
+
- **`loadServerSnapshot` and `persistServerSnapshot` are public** (`@jgengine/convex/server`), and `createGameServerFunctions` also returns `helpers` (`loadSnapshot`, `applyCommand`, `persistSnapshot`, `runCommand`) bound to the same runtimes and auth mode. A host mutation can now pair snapshot work with its own table writes in one transaction; the registered `runCommand` mutation delegates to `helpers.runCommand`, so there is one implementation.
|
|
63
|
+
|
|
64
|
+
### Fixed
|
|
65
|
+
|
|
66
|
+
- **Open storefronts are no longer holes through the building.** `generateBuilding` emitted an open ground-floor store as a lone `storefront` panel with no wall behind it, while every upper-floor window was correctly backed by one. Storefront and window materials render at 0.56 opacity and a generated building is a hollow shell, so those bays let you see straight through the building to the world beyond — and open stores only land on the street-facing facade, so a row of shops along a road was the worst case. The bay now emits its wall first, exactly as an upper floor does.
|
|
67
|
+
- **`joinServer`, `flushSave`, `flushDirtyServers`, and the `auto` autosave can write player profiles again.** All four cleared the snapshot's dirty set before handing it to the persist plan, which gates profile writes on exactly that set — so the functions named "flush" flushed the server row and chunks only, and an `onNewPlayer` grant (starting money, starting items) never reached `jgPlayerProfiles`. `joinServer` now persists the snapshot it just marked; the flush paths mark every player dirty first, since hydrating from storage resets the dirty set. `planServerPersist` also clears `dirtyAt` once everything outstanding is written, so a successful flush stops autosave from re-firing forever, and a profile patch no longer clobbers a host-maintained `dirtyAt`.
|
|
68
|
+
|
|
37
69
|
## 0.15.0
|
|
38
70
|
|
|
39
71
|
### Migrate
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/assets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Self-generating, license-verified index of thousands of CC0 3D models for JGengine. Sources fetch from providers' own CDNs at pull time; the npm tarball ships only the typed index and CLI (no GLB bytes).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"test": "bun test src"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@jgengine/core": "^0.
|
|
38
|
+
"@jgengine/core": "^0.16.0",
|
|
39
39
|
"fflate": "^0.8.2"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
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.16.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
|
|
10
10
|
|
|
11
11
|
## @jgengine/core/authoring
|
|
12
12
|
|
|
@@ -228,7 +228,7 @@
|
|
|
228
228
|
|
|
229
229
|
- `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
|
|
230
230
|
- `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
|
|
231
|
-
- `VERSION` (const): const VERSION: "0.
|
|
231
|
+
- `VERSION` (const): const VERSION: "0.16.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
|
|
232
232
|
|
|
233
233
|
## @jgengine/core/runtime/adapter
|
|
234
234
|
|
|
@@ -264,6 +264,13 @@
|
|
|
264
264
|
- `CommandValidationError` (type): type CommandValidationError = { reason: string } — ⚠ undocumented
|
|
265
265
|
- `RunCommandResult` (type): type RunCommandResult = | { ok: true; snapshot: GameRuntimeSnapshot } | { ok: false; reason: string } — ⚠ undocumented
|
|
266
266
|
|
|
267
|
+
## @jgengine/core/runtime/context/objectSlots
|
|
268
|
+
|
|
269
|
+
- `ObjectSlotRejection` (type): type ObjectSlotRejection = | "unknown-object" | "no-slot-inventory" | "unknown-inventory" | "no-space" | "wrong-kind" | "slot-occupied" | "invalid-slot" | "insufficient" — Why an object slot mutation was refused — the instance, its catalog entry, the player inventory, or the declared `accepts`/capacity.
|
|
270
|
+
- `ObjectSlotResult` (type): type ObjectSlotResult = { status: "ok" } | { status: "rejected"; reason: ObjectSlotRejection } — Outcome of an object slot mutation: applied, or refused with a reason and no state change on either side.
|
|
271
|
+
- `ObjectSlotTransfer` (interface): interface ObjectSlotTransfer — Which player inventory a transfer moves between, and what it moves.
|
|
272
|
+
- `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.
|
|
273
|
+
|
|
267
274
|
## @jgengine/core/runtime/gameContext
|
|
268
275
|
|
|
269
276
|
- `CatalogEntityRole` (type): type CatalogEntityRole = "player" | "enemy" | "hostile" | "npc" | "vehicle" — ⚠ undocumented
|
|
@@ -402,6 +409,11 @@
|
|
|
402
409
|
- `MotionIntentBatch` (interface): interface MotionIntentBatch — ⚠ undocumented
|
|
403
410
|
- `MotionIntents` (interface): interface MotionIntents — Seam for game code to reach the motion the shell's FrameDriver otherwise owns privately (#162.4). Game code calls `impulse`, `pushHorizontal`, `setVerticalVelocity`, and/or `setY` from `onTick` or commands; the shell calls `takePending()` once per frame, before integrating gravity, to drain what accumulated. `setY` wins over physics for that frame; impulses add to the velocity the driver is about to integrate; a later `setVerticalVelocity` replaces that velocity outright. Horizontal pushes compose with the walk controller (#282.4): they add to its horizontal velocity and decay naturally as it re-blends toward input — knockback, dashes, explosion shoves without raw `setPose` offsets.
|
|
404
411
|
|
|
412
|
+
## @jgengine/core/runtime/objectRows
|
|
413
|
+
|
|
414
|
+
- `fromRuntimeObjectRow` (function): function fromRuntimeObjectRow(row: RuntimeObjectRow): SceneObject — Inverse of {@link toRuntimeObjectRow}: rebuild the live placed object a host persisted.
|
|
415
|
+
- `toRuntimeObjectRow` (function): function toRuntimeObjectRow(object: SceneObject): RuntimeObjectRow — Convert a live placed object into its persisted row — the bridge between `ctx.scene.object` and `GameRuntimeSnapshot.server.objects`/`chunks[].objects`. Per-instance `state` is carried as `flags`. Presentation overrides (`visual`, `animation`) are catalog- and authoring-resolved and deliberately stay off the persisted row — put anything a command must own in `state`.
|
|
416
|
+
|
|
405
417
|
## @jgengine/core/runtime/perContext
|
|
406
418
|
|
|
407
419
|
- `perContext` (function): function perContext<T>(init: (ctx: GameContext) => T): (ctx: GameContext) => T — Per-`GameContext` lazy state, keyed by context identity through a `WeakMap` so it is reclaimed when the context is — the seam a game uses instead of a module-global `Map`, which is process-global and bleeds across worlds when one host serves several `serverId`s (#632). `init` runs once per distinct context on first access; the same context always resolves to the same value. This mirrors the engine's built-in per-ctx pattern (`ctx.player.motion`, `stepPlayerMovement`).
|
|
@@ -441,7 +453,7 @@
|
|
|
441
453
|
- `RuntimeChunkRow` (type): type RuntimeChunkRow = { chunkKey: string; objects: RuntimeObjectRow[]; entities: RuntimeEntityRow[]; flags?: Record<string, unknown>; } — ⚠ undocumented
|
|
442
454
|
- `RuntimeEntityRow` (type): type RuntimeEntityRow = { instanceId: string; catalogId: string; position?: [number, number, number]; rotationY?: number; parentSpace?: string; group?: string; stats?: Record<string, { current: number; max: number; min?: number }>; targetInstanceId?: string | null; userId?: string; } — ⚠ undocumented
|
|
443
455
|
- `RuntimeInventorySlot` (type): type RuntimeInventorySlot = { item: string; count: number; slot?: number; } — ⚠ undocumented
|
|
444
|
-
- `RuntimeObjectRow` (type): type RuntimeObjectRow = { instanceId: string; catalogId: string; position: [number, number, number]; rotationY?: number; parentSpace?: string; flags?: Record<string, unknown>;
|
|
456
|
+
- `RuntimeObjectRow` (type): type RuntimeObjectRow = { instanceId: string; catalogId: string; position: [number, number, number]; rotationY?: number; parentSpace?: string; /** Opaque per-instance state; the persisted name for `SceneObject.state`. */ flags?: Record<string, unknown>; /** Container contents for a catalog `slotInve… — The persisted form of a placed `SceneObject`: identity, placement, per-instance state, and slot contents. Convert with `toRuntimeObjectRow`/`fromRuntimeObjectRow` (`runtime/objectRows`).
|
|
445
457
|
- `RuntimePlayerRow` (type): type RuntimePlayerRow = { userId: string; inventories: Record<string, RuntimeInventorySlot[]>; economy: Record<string, number>; unlocks: string[]; quests?: unknown; social?: unknown; leaderboard?: Record<string, number>; session?: Record<string, unknown>; } — ⚠ undocumented
|
|
446
458
|
- `RuntimeProfileRow` (type): type RuntimeProfileRow = { userId: string; gameId: string; player: RuntimePlayerRow; updatedAt: number; } — ⚠ undocumented
|
|
447
459
|
- `RuntimeServerRow` (type): type RuntimeServerRow = { entities: RuntimeEntityRow[]; objects: RuntimeObjectRow[]; session: Record<string, unknown>; feeds?: Record<string, unknown[]>; } — ⚠ undocumented
|
|
@@ -100,6 +100,18 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
100
100
|
|
|
101
101
|
- `ws` (function) · `import { ws } from "@jgengine/core/runtime/adapter"`
|
|
102
102
|
|
|
103
|
+
## object-slot-inventory — placed objects hold validated per-instance container contents that replicate and save with the placement
|
|
104
|
+
|
|
105
|
+
- `SceneObjectSlots` (interface) · `import { SceneObjectSlots } from "@jgengine/core/runtime/context/objectSlots"`
|
|
106
|
+
|
|
107
|
+
## placed-object-rehydrate — rebuild live placed objects, with their state and slot contents, from persisted snapshot rows
|
|
108
|
+
|
|
109
|
+
- `fromRuntimeObjectRow` (function) · `import { fromRuntimeObjectRow } from "@jgengine/core/runtime/objectRows"`
|
|
110
|
+
|
|
111
|
+
## placed-object-rows — a placed object's per-instance state and container contents convert to and from the persisted snapshot row a host stores
|
|
112
|
+
|
|
113
|
+
- `toRuntimeObjectRow` (function) · `import { toRuntimeObjectRow } from "@jgengine/core/runtime/objectRows"`
|
|
114
|
+
|
|
103
115
|
## render-fallback-diagnostics — Enable/disable the dev-only placeholder-fallback probe.
|
|
104
116
|
|
|
105
117
|
- `armFallbackSeams` (function) · `import { armFallbackSeams } from "@jgengine/core/devtools/fallbackSeams"`
|