@jgengine/shell 0.38.0 → 0.38.1
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 +19 -0
- package/dist/render/SceneModels.js +2 -2
- package/dist/render/singleVariantLod.d.ts +11 -4
- package/dist/render/singleVariantLod.js +29 -8
- package/dist/scatter/InstancedScatter.js +3 -3
- package/dist/world/StaticObjectModels.js +2 -1
- package/package.json +4 -4
- package/skills/jgengine/api.md +2 -2
- package/skills/jgengine-world/api.md +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -26,6 +26,25 @@ 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.38.1
|
|
30
|
+
|
|
31
|
+
### Migrate
|
|
32
|
+
|
|
33
|
+
- **Bump lockstep SDK packages to `^0.38.1`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.35.1`; `@jgengine/github` is `0.25.1`.
|
|
34
|
+
- Nothing to migrate. `variantLevels()` was renamed to `ModelVariantConfig.shadows` before any release shipped it, so no published version ever exported it and no consumer can be holding one. A level that wants to stop paying the near level's shadow cost writes `{ url, shadows: "none" }`; leaving `shadows` out keeps the model's own answer.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- `ModelVariantConfig.shadows` (`@jgengine/core/world/modelLod`): a model variant level declares its own shadow participation as a plain field, `"inherit" | "cast" | "receive" | "both" | "none"`. `"inherit"`, the default, keeps the model's own `shadows`, so every existing model draws as before; a far level set to `"none"` leaves the shadow pass while still drawing its mesh.
|
|
39
|
+
- `ModelShadowMode` (`@jgengine/core/world/modelLod`) names the four-value shadow vocabulary, and `ModelConfig.shadows` uses it.
|
|
40
|
+
- `variantLevelModel` (`@jgengine/shell/render/singleVariantLod`) is the one place that resolves what a level draws and how it shadows; the single prop, batched prop and scatter paths all ask it, so a level's asset and its shadow answer cannot disagree.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- `SaveSlotsPane` cards are one box each: equal tracks, `grid-auto-rows: 1fr`, a card edge that reads over a bright scene, and a name that ellipsises instead of widening the card. A save with a long name and one with none produce the same card box.
|
|
45
|
+
- `GameMenu` puts the primary verbs on their own scrim, so their labels stay legible over whatever the live scene is showing instead of relying on a text shadow.
|
|
46
|
+
- The theme emits three menu tokens the slot card and the verb surface read: `--jg-menu-card-height` (`clamp(244px, 27vh, 300px)`), `--jg-menu-card-thumb` (`clamp(88px, 14vh, 160px)`) and `--jg-menu-surface-radius` (`8px`). A game overrides them in its own `HudTheme` instead of patching the menu components.
|
|
47
|
+
|
|
29
48
|
## 0.38.0
|
|
30
49
|
|
|
31
50
|
### Migrate
|
|
@@ -17,7 +17,7 @@ import { useFootIk } from "./useFootIk.js";
|
|
|
17
17
|
import { resolveRigNode } from "./rigNode.js";
|
|
18
18
|
import { PartMotionRig } from "./PartMotion.js";
|
|
19
19
|
import { syncSpriteFrame } from "./spriteRender.js";
|
|
20
|
-
import { singleVariantLevel,
|
|
20
|
+
import { singleVariantLevel, variantLevelModel } from "./singleVariantLod.js";
|
|
21
21
|
import { applyMaterialOverride } from "../materialOverride.js";
|
|
22
22
|
import { applyPaintTextureToMaterials, cacheStandardMaterials, cloneModelScene, createPaintCanvas, disposeModelScene, ownModelMaterial, disposePaintCanvas, syncPaintCanvas, } from "./modelRender.js";
|
|
23
23
|
export function EntitySprite({ sprite }) {
|
|
@@ -123,7 +123,7 @@ export function SingleVariantModel({ model, instanceId, measure, }) {
|
|
|
123
123
|
if (next !== level)
|
|
124
124
|
setLevel(next);
|
|
125
125
|
});
|
|
126
|
-
const drawn = useMemo(() =>
|
|
126
|
+
const drawn = useMemo(() => variantLevelModel(model, level), [model, level]);
|
|
127
127
|
// The near level owns the measured bounds and collision mesh, like the batched path: a decimated
|
|
128
128
|
// asset must not redefine what the catalog entry collides as.
|
|
129
129
|
return _jsx(EntityModel, { model: drawn, instanceId: instanceId, measure: level === 0 ? measure : undefined });
|
|
@@ -2,12 +2,19 @@ import type * as THREE from "three";
|
|
|
2
2
|
import type { ModelConfig } from "@jgengine/core/game/playableGame";
|
|
3
3
|
import type { ModelVariantSelector } from "@jgengine/core/world/modelLod";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* to the nearest one, and a model without variants is returned untouched.
|
|
5
|
+
* Shadow participation the model draws with at one level: the level's own choice when it made one,
|
|
6
|
+
* otherwise the model's `shadows` untouched.
|
|
8
7
|
* @internal
|
|
9
8
|
*/
|
|
10
|
-
export declare function
|
|
9
|
+
export declare function variantLevelShadows(model: ModelConfig, level: number): ModelConfig["shadows"];
|
|
10
|
+
/**
|
|
11
|
+
* The model any path draws at one variant level: that level's asset, its shadow participation, and no
|
|
12
|
+
* nested variants, so a prop rendered on its own, a batched instance and a scatter cell all switch
|
|
13
|
+
* the same way. Out-of-range levels clamp to the nearest one, and a model without variants is
|
|
14
|
+
* returned untouched.
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export declare function variantLevelModel(model: ModelConfig, level: number): ModelConfig;
|
|
11
18
|
/**
|
|
12
19
|
* Metres from the camera's world position to a placement, written through the caller's scratch vectors
|
|
13
20
|
* so a frame allocates nothing.
|
|
@@ -1,15 +1,36 @@
|
|
|
1
|
+
function levelOf(model, level) {
|
|
2
|
+
const levels = model.variants?.levels;
|
|
3
|
+
if (levels === undefined)
|
|
4
|
+
return undefined;
|
|
5
|
+
return levels[Math.min(levels.length - 1, Math.max(0, level))];
|
|
6
|
+
}
|
|
1
7
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* to the nearest one, and a model without variants is returned untouched.
|
|
8
|
+
* Shadow participation the model draws with at one level: the level's own choice when it made one,
|
|
9
|
+
* otherwise the model's `shadows` untouched.
|
|
5
10
|
* @internal
|
|
6
11
|
*/
|
|
7
|
-
export function
|
|
8
|
-
const
|
|
9
|
-
|
|
12
|
+
export function variantLevelShadows(model, level) {
|
|
13
|
+
const chosen = levelOf(model, level)?.shadows;
|
|
14
|
+
return chosen === undefined || chosen === "inherit" ? model.shadows : chosen;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The model any path draws at one variant level: that level's asset, its shadow participation, and no
|
|
18
|
+
* nested variants, so a prop rendered on its own, a batched instance and a scatter cell all switch
|
|
19
|
+
* the same way. Out-of-range levels clamp to the nearest one, and a model without variants is
|
|
20
|
+
* returned untouched.
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export function variantLevelModel(model, level) {
|
|
24
|
+
const chosen = levelOf(model, level);
|
|
25
|
+
if (chosen === undefined)
|
|
10
26
|
return model;
|
|
11
|
-
const
|
|
12
|
-
return {
|
|
27
|
+
const shadows = variantLevelShadows(model, level);
|
|
28
|
+
return {
|
|
29
|
+
...model,
|
|
30
|
+
url: chosen.url,
|
|
31
|
+
variants: undefined,
|
|
32
|
+
...(shadows === undefined ? {} : { shadows }),
|
|
33
|
+
};
|
|
13
34
|
}
|
|
14
35
|
/**
|
|
15
36
|
* Metres from the camera's world position to a placement, written through the caller's scratch vectors
|
|
@@ -8,6 +8,7 @@ import { chunkScatterInstances } from "@jgengine/core/world/scatterRegion";
|
|
|
8
8
|
import { beginFallbackSeam, reportFallbackSeam } from "@jgengine/core/devtools/fallbackSeams";
|
|
9
9
|
import { createModelVariantSelector } from "@jgengine/core/world/modelLod";
|
|
10
10
|
import { CAMERA_POST_FRAME_PRIORITY } from "../camera/cameraRigs.js";
|
|
11
|
+
import { variantLevelModel } from "../render/singleVariantLod.js";
|
|
11
12
|
import { buildScatterProxy } from "./scatterProxies.js";
|
|
12
13
|
import { buildScatterModelSources, disposeScatterModelSources } from "./scatterModels.js";
|
|
13
14
|
import { writeScatterVariantLevel } from "./scatterVariants.js";
|
|
@@ -120,9 +121,8 @@ function ScatterVariantLevel({ model, url, level, selector, batches }) {
|
|
|
120
121
|
loader.setMeshoptDecoder(MeshoptDecoder);
|
|
121
122
|
});
|
|
122
123
|
const { sources, root } = useMemo(() => {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}, [gltf, model, url]);
|
|
124
|
+
return buildScatterModelSources(gltf.scene, variantLevelModel(model, level));
|
|
125
|
+
}, [gltf, model, level]);
|
|
126
126
|
useEffect(() => () => disposeScatterModelSources(root), [root]);
|
|
127
127
|
return (_jsx(_Fragment, { children: sources.map((source, sourceIndex) => batches.map((batch) => (_jsx(ScatterVariantSourceInstances, { source: source, batch: batch, level: level, selector: selector }, `${batch.key}:${sourceIndex}`)))) }));
|
|
128
128
|
}
|
|
@@ -7,6 +7,7 @@ import { devtools } from "@jgengine/core/devtools/devtools";
|
|
|
7
7
|
import { useGameContext } from "@jgengine/react/provider";
|
|
8
8
|
import { sharedGltfLoader } from "../render/modelLoad.js";
|
|
9
9
|
import { IsolatedModelPart } from "../render/SceneModels.js";
|
|
10
|
+
import { variantLevelModel } from "../render/singleVariantLod.js";
|
|
10
11
|
import { freezeStaticTransforms } from "../render/staticTransforms.js";
|
|
11
12
|
import { measureLocalBounds, reportMeasuredBounds } from "../render/measureBounds.js";
|
|
12
13
|
import { measureLocalCollisionTriangles, reportMeasuredCollisionMesh } from "../render/measureCollisionMesh.js";
|
|
@@ -174,7 +175,7 @@ export function VariantStaticObjects({ batch, single }) {
|
|
|
174
175
|
const { objects, excluded } = useBatchMembers(batch);
|
|
175
176
|
const levels = batch.model.variants.levels;
|
|
176
177
|
const selector = useMemo(() => createModelVariantSelector(batch.model.variants), [batch.model]);
|
|
177
|
-
return _jsxs(_Fragment, { children: [levels.map((level, index) => (_jsx(VariantLevel, { url: level.url, model:
|
|
178
|
+
return _jsxs(_Fragment, { children: [levels.map((level, index) => (_jsx(VariantLevel, { url: level.url, model: variantLevelModel(batch.model, index), batch: batch, objects: objects, level: index, selector: selector, measure: index === 0, single: single }, `${level.url}:${index}`))), excluded.map((id) => { const live = ctx.scene.object.get(id); return live === null ? null : single(live); })] });
|
|
178
179
|
}
|
|
179
180
|
/** Render compatible authored or `scenery` props as shared model draws, preserving unsupported models. @internal */
|
|
180
181
|
export function StaticObjectModels({ batch, single }) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/shell",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.1",
|
|
4
4
|
"description": "Game player shell for JGengine: React Three Fiber canvas, orbit camera, input tracking, HUD mounting, GameUiPreview, and a demo game. Consumers supply a GameRegistry.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"test": "bun test src"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@jgengine/core": "^0.38.
|
|
43
|
-
"@jgengine/react": "^0.38.
|
|
44
|
-
"@jgengine/ws": "^0.38.
|
|
42
|
+
"@jgengine/core": "^0.38.1",
|
|
43
|
+
"@jgengine/react": "^0.38.1",
|
|
44
|
+
"@jgengine/ws": "^0.38.1"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@react-three/drei": "^10.0.0",
|
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.38.
|
|
9
|
+
- `VERSION` (const): const VERSION: "0.38.1" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
|
|
10
10
|
|
|
11
11
|
## @jgengine/core/authoring
|
|
12
12
|
|
|
@@ -285,7 +285,7 @@
|
|
|
285
285
|
|
|
286
286
|
- `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
|
|
287
287
|
- `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
|
|
288
|
-
- `VERSION` (const): const VERSION: "0.38.
|
|
288
|
+
- `VERSION` (const): const VERSION: "0.38.1" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
|
|
289
289
|
|
|
290
290
|
## @jgengine/core/runtime/adapter
|
|
291
291
|
|
|
@@ -2655,7 +2655,7 @@
|
|
|
2655
2655
|
- `ModelAssetRef` (interface): interface ModelAssetRef { url: string; anchor?: "center" | "origin"; dims?: ModelDims; collisionMesh?: CollisionMeshData; space?: AssetSpace; clips?: readonly string[] } — ⚠ undocumented · used by `createHostedWorldSessionAsync` (@jgengine/core/runtime/hostedWorldSession): Build a hosted session from an asynchronous persistence backend.
|
|
2656
2656
|
- `ModelDims` (interface): interface ModelDims — Measured horizontal footprint, footprint center, and lowest Y of a model in model space.
|
|
2657
2657
|
- `ModelNode` (interface): interface ModelNode — Generic named-socket reader for loaded 3D models. Walks a node tree (any object with `.name`, `.position`, and `.children` — structurally satisfied by `THREE.Object3D`) and collects the local offsets of nodes whose name marks an attachment point. Genre-agnostic: wire anchors on a pylon, muzzle/hand mounts on a character, hardpoints on a ship, seat/decal slots on furniture — anything an artist tags with an empty in the GLB. Pure data (no three.js import), so it lives in core.
|
|
2658
|
-
- `ModelVariantConfig` (interface): interface ModelVariantConfig — One mesh level of a {@link ModelVariantLodConfig}: a full or decimated GLB
|
|
2658
|
+
- `ModelVariantConfig` (interface): interface ModelVariantConfig — One mesh level of a {@link ModelVariantLodConfig}: a full or decimated GLB, how far it draws and how it shadows.
|
|
2659
2659
|
- `ModelVariantLodConfig` (interface): interface ModelVariantLodConfig — Ordered mesh detail levels for one model, switched per instance by camera distance. The first level is the full mesh the model's own `url` names; every later level is a cheaper asset. A tree line uses this so near trees keep their full mesh while far ones draw a decimated copy of the same species.
|
|
2660
2660
|
- `ModelVariantSelector` (interface): interface ModelVariantSelector — Per-instance distance switch over a model's detail levels: one answer per id, held across a boundary until the instance clears it.
|
|
2661
2661
|
- `ModelVariantSelectorState` (interface): interface ModelVariantSelectorState — Plain JSON state of a {@link ModelVariantSelector}: each id's current level index.
|
|
@@ -4041,7 +4041,8 @@
|
|
|
4041
4041
|
## @jgengine/core/world/modelLod
|
|
4042
4042
|
|
|
4043
4043
|
- `DEFAULT_VARIANT_HYSTERESIS` (const): const DEFAULT_VARIANT_HYSTERESIS: 6 — Metres an instance must travel past a switch boundary before it changes level unless the model declares its own.
|
|
4044
|
-
- `
|
|
4044
|
+
- `ModelShadowMode` (type): type ModelShadowMode = "cast" | "receive" | "both" | "none" — How a model, or one of its variant levels, takes part in the shadow pass.
|
|
4045
|
+
- `ModelVariantConfig` (interface): interface ModelVariantConfig — One mesh level of a {@link ModelVariantLodConfig}: a full or decimated GLB, how far it draws and how it shadows.
|
|
4045
4046
|
- `ModelVariantLodConfig` (interface): interface ModelVariantLodConfig — Ordered mesh detail levels for one model, switched per instance by camera distance. The first level is the full mesh the model's own `url` names; every later level is a cheaper asset. A tree line uses this so near trees keep their full mesh while far ones draw a decimated copy of the same species.
|
|
4046
4047
|
- `ModelVariantSelector` (interface): interface ModelVariantSelector — Per-instance distance switch over a model's detail levels: one answer per id, held across a boundary until the instance clears it.
|
|
4047
4048
|
- `ModelVariantSelectorState` (interface): interface ModelVariantSelectorState — Plain JSON state of a {@link ModelVariantSelector}: each id's current level index.
|