@jgengine/shell 0.32.0 → 0.33.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 +48 -0
- package/dist/GamePlayerShell.js +7 -0
- package/dist/Shell3dPresentation.js +5 -3
- package/dist/audio/AudioComponents.js +61 -51
- package/dist/audio/FeedbackHost.js +8 -4
- package/dist/audio/audioEngine.js +101 -24
- package/dist/audio/emitterTracking.d.ts +40 -0
- package/dist/audio/emitterTracking.js +153 -0
- package/dist/audio/eventFeedback.d.ts +15 -5
- package/dist/audio/eventFeedback.js +12 -7
- package/dist/audio/hapticsRoute.d.ts +28 -0
- package/dist/audio/hapticsRoute.js +59 -0
- package/dist/audio/loopParams.js +20 -10
- package/dist/devtools/FrameTelemetry.d.ts +1 -1
- package/dist/devtools/FrameTelemetry.js +4 -2
- package/dist/devtools/agentBridge.js +13 -0
- package/dist/devtools/frameSampler.js +4 -0
- package/dist/drivers/CanvasCoverFrameloop.d.ts +13 -0
- package/dist/drivers/CanvasCoverFrameloop.js +22 -0
- package/dist/drivers/FrameRateLimiter.d.ts +2 -1
- package/dist/drivers/FrameRateLimiter.js +6 -3
- package/dist/postfx/PostProcessing.d.ts +78 -2
- package/dist/postfx/PostProcessing.js +149 -76
- package/dist/postfx/postfxOverlay.d.ts +14 -4
- package/dist/postfx/postfxOverlay.js +90 -10
- package/dist/postfx/shadowPrepass.d.ts +6 -2
- package/dist/postfx/shadowPrepass.js +7 -3
- package/dist/render/characterLod.js +6 -0
- package/dist/render/useFootIk.js +54 -15
- package/dist/render/useModelAnimation.js +22 -5
- package/dist/settings/settingsController.d.ts +10 -1
- package/dist/settings/settingsController.js +81 -69
- package/dist/vision/RevealVision.d.ts +3 -1
- package/dist/vision/RevealVision.js +34 -15
- package/dist/world/WorldItems.js +11 -3
- package/package.json +4 -4
- package/skills/jgengine/api.md +2 -2
- package/skills/jgengine-ui/api.md +45 -24
- package/skills/jgengine-ui/capabilities.md +8 -0
- package/skills/jgengine-ui/reference-primitives.md +2 -0
- package/skills/jgengine-world/api.md +4 -4
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { bindingLabel, type ActionCodesMap } from "@jgengine/core/input/actionBindings";
|
|
2
2
|
import type { BindingOverrides } from "@jgengine/core/input/bindingOverrides";
|
|
3
3
|
import type { AudioBusDef } from "@jgengine/core/audio/audioFalloff";
|
|
4
|
-
import { type GameSettingDef, type SettingCategory, type SettingCategoryDef } from "@jgengine/core/settings/settingsModel";
|
|
4
|
+
import { type GameSettingDef, type SettingCategory, type SettingCategoryDef, type SettingsStore } from "@jgengine/core/settings/settingsModel";
|
|
5
5
|
import { type GraphicsProfileOverrides } from "@jgengine/core/settings/graphicsSettings";
|
|
6
|
+
import type { PostProcessingConfig } from "@jgengine/core/render/postProcessing";
|
|
6
7
|
import { type SettingsActionView, type SettingsKeybindRow, type SettingsCategoryView, type SettingsController, type SettingsRow } from "@jgengine/react/settings";
|
|
7
8
|
export type { SettingsActionView, SettingsKeybindRow, SettingsCategoryView, SettingsController, SettingsRow };
|
|
8
9
|
export interface SettingsControllerInput {
|
|
@@ -14,6 +15,8 @@ export interface SettingsControllerInput {
|
|
|
14
15
|
fovEnabled: boolean;
|
|
15
16
|
/** The game's per-tier profile overrides (`defineGame({ graphics })`), so tier defaults match what renders. */
|
|
16
17
|
graphics?: GraphicsProfileOverrides;
|
|
18
|
+
/** The post chain this game actually runs, so a stage row never outlives its pass. */
|
|
19
|
+
postProcessing?: PostProcessingConfig;
|
|
17
20
|
hideBindings: readonly string[];
|
|
18
21
|
/** Show the touch-controls skin selector in Controls (touch device with a dock). */
|
|
19
22
|
touchStyle: boolean;
|
|
@@ -22,4 +25,10 @@ export interface SettingsControllerInput {
|
|
|
22
25
|
resetBinding: (action: string) => void;
|
|
23
26
|
}
|
|
24
27
|
export declare function useSettingsCategories(config: SettingsControllerInput): SettingsCategoryView[];
|
|
28
|
+
/**
|
|
29
|
+
* The built-in Graphics rows: tier, scale, filtering, shadows, one toggle per postfx stage this
|
|
30
|
+
* game's chain can build, then UI scale and FPS.
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export declare function graphicsSettingRows(store: SettingsStore, overrides: GraphicsProfileOverrides | undefined, postProcessing: PostProcessingConfig | undefined): SettingsRow[];
|
|
25
34
|
export { bindingLabel };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { actionLabel, bindingLabel } from "@jgengine/core/input/actionBindings";
|
|
2
2
|
import { BUILT_IN_SETTING_CATEGORIES, busVolumeSettingId, DEFAULT_GRAPHICS_SHADOWS, DEFAULT_MASTER_VOLUME, DEFAULT_UI_SCALE, GRAPHICS_QUALITY_OPTIONS, SETTING_IDS, UI_SCALE_MAX, UI_SCALE_MIN, } from "@jgengine/core/settings/settingsModel";
|
|
3
|
-
import { ANISOTROPY_OPTIONS, applyGraphicsQuality, GRAPHICS_POST_STAGE_LABELS,
|
|
3
|
+
import { ANISOTROPY_OPTIONS, applyGraphicsQuality, GRAPHICS_POST_STAGE_LABELS, graphicsPostStageSettingId, readGraphicsSettings, RENDER_SCALE_MAX, RENDER_SCALE_MIN, RENDER_SCALE_STEP, } from "@jgengine/core/settings/graphicsSettings";
|
|
4
4
|
import { FRAME_RATE_LIMIT_DISPLAY, FRAME_RATE_LIMIT_OPTIONS } from "@jgengine/core/settings/frameRateLimit";
|
|
5
5
|
import { accessibilitySettingRows } from "@jgengine/core/settings/accessibilitySettings";
|
|
6
6
|
import { busMuteSettingId, MASTER_MUTE_SETTING_ID } from "../audio/busMix.js";
|
|
7
|
+
import { availablePostfxStages } from "../postfx/PostProcessing.js";
|
|
7
8
|
import { DEFAULT_TOUCH_JOYSTICK_VARIANT, TOUCH_JOYSTICK_VARIANT_OPTIONS, TOUCH_STYLE_OPTIONS, } from "@jgengine/core/input/touchScheme";
|
|
8
9
|
import { TOUCH_STYLE_AUTO, useSettingsRevision } from "./appliedSettings.js";
|
|
9
10
|
import { useSettingsStore, } from "@jgengine/react/settings";
|
|
@@ -80,75 +81,8 @@ export function useSettingsCategories(config) {
|
|
|
80
81
|
},
|
|
81
82
|
...extrasFor("sound"),
|
|
82
83
|
];
|
|
83
|
-
const graphics = readGraphicsSettings(store, config.graphics);
|
|
84
84
|
const graphicsRows = [
|
|
85
|
-
|
|
86
|
-
id: SETTING_IDS.graphicsQuality,
|
|
87
|
-
label: "Quality",
|
|
88
|
-
kind: "select",
|
|
89
|
-
value: graphics.quality,
|
|
90
|
-
options: GRAPHICS_QUALITY_OPTIONS,
|
|
91
|
-
set: (value) => applyGraphicsQuality(store, String(value), config.graphics),
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
id: SETTING_IDS.graphicsRenderScale,
|
|
95
|
-
label: "Render scale",
|
|
96
|
-
kind: "slider",
|
|
97
|
-
value: graphics.profile.renderScale,
|
|
98
|
-
min: RENDER_SCALE_MIN,
|
|
99
|
-
max: RENDER_SCALE_MAX,
|
|
100
|
-
step: RENDER_SCALE_STEP,
|
|
101
|
-
format: percent,
|
|
102
|
-
set: (value) => store.set(SETTING_IDS.graphicsRenderScale, value),
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
id: SETTING_IDS.graphicsFrameRateLimit,
|
|
106
|
-
label: "Frame rate limit",
|
|
107
|
-
kind: "select",
|
|
108
|
-
value: store.get(SETTING_IDS.graphicsFrameRateLimit, FRAME_RATE_LIMIT_DISPLAY),
|
|
109
|
-
options: FRAME_RATE_LIMIT_OPTIONS,
|
|
110
|
-
set: (value) => store.set(SETTING_IDS.graphicsFrameRateLimit, String(value)),
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
id: SETTING_IDS.graphicsAnisotropy,
|
|
114
|
-
label: "Texture filtering",
|
|
115
|
-
kind: "select",
|
|
116
|
-
value: String(graphics.profile.anisotropy),
|
|
117
|
-
options: ANISOTROPY_OPTIONS,
|
|
118
|
-
set: (value) => store.set(SETTING_IDS.graphicsAnisotropy, String(value)),
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
id: SETTING_IDS.graphicsShadows,
|
|
122
|
-
label: "Shadows",
|
|
123
|
-
kind: "toggle",
|
|
124
|
-
value: store.get(SETTING_IDS.graphicsShadows, DEFAULT_GRAPHICS_SHADOWS),
|
|
125
|
-
set: (value) => store.set(SETTING_IDS.graphicsShadows, value),
|
|
126
|
-
},
|
|
127
|
-
...GRAPHICS_POST_STAGES.map((stage) => ({
|
|
128
|
-
id: graphicsPostStageSettingId(stage),
|
|
129
|
-
label: GRAPHICS_POST_STAGE_LABELS[stage],
|
|
130
|
-
kind: "toggle",
|
|
131
|
-
value: graphics.profile.postStages[stage],
|
|
132
|
-
set: (value) => store.set(graphicsPostStageSettingId(stage), value),
|
|
133
|
-
})),
|
|
134
|
-
{
|
|
135
|
-
id: SETTING_IDS.graphicsUiScale,
|
|
136
|
-
label: "UI scale",
|
|
137
|
-
kind: "slider",
|
|
138
|
-
value: store.get(SETTING_IDS.graphicsUiScale, DEFAULT_UI_SCALE),
|
|
139
|
-
min: UI_SCALE_MIN,
|
|
140
|
-
max: UI_SCALE_MAX,
|
|
141
|
-
step: 0.05,
|
|
142
|
-
format: percent,
|
|
143
|
-
set: (value) => store.set(SETTING_IDS.graphicsUiScale, value),
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
id: SETTING_IDS.graphicsShowFps,
|
|
147
|
-
label: "Show FPS",
|
|
148
|
-
kind: "toggle",
|
|
149
|
-
value: store.get(SETTING_IDS.graphicsShowFps, false),
|
|
150
|
-
set: (value) => store.set(SETTING_IDS.graphicsShowFps, value),
|
|
151
|
-
},
|
|
85
|
+
...graphicsSettingRows(store, config.graphics, config.postProcessing),
|
|
152
86
|
...extrasFor("graphics"),
|
|
153
87
|
];
|
|
154
88
|
const gameplayRows = [
|
|
@@ -240,6 +174,84 @@ export function useSettingsCategories(config) {
|
|
|
240
174
|
views.sort((a, b) => (declaredOrder.get(a.id) ?? indexOrder(order, a.id)) - (declaredOrder.get(b.id) ?? indexOrder(order, b.id)));
|
|
241
175
|
return views;
|
|
242
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* The built-in Graphics rows: tier, scale, filtering, shadows, one toggle per postfx stage this
|
|
179
|
+
* game's chain can build, then UI scale and FPS.
|
|
180
|
+
* @internal
|
|
181
|
+
*/
|
|
182
|
+
export function graphicsSettingRows(store, overrides, postProcessing) {
|
|
183
|
+
const graphics = readGraphicsSettings(store, overrides);
|
|
184
|
+
const stages = postProcessing === undefined || postProcessing.enabled === false ? [] : availablePostfxStages(postProcessing);
|
|
185
|
+
return [
|
|
186
|
+
{
|
|
187
|
+
id: SETTING_IDS.graphicsQuality,
|
|
188
|
+
label: "Quality",
|
|
189
|
+
kind: "select",
|
|
190
|
+
value: graphics.quality,
|
|
191
|
+
options: GRAPHICS_QUALITY_OPTIONS,
|
|
192
|
+
set: (value) => applyGraphicsQuality(store, String(value), overrides),
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: SETTING_IDS.graphicsRenderScale,
|
|
196
|
+
label: "Render scale",
|
|
197
|
+
kind: "slider",
|
|
198
|
+
value: graphics.profile.renderScale,
|
|
199
|
+
min: RENDER_SCALE_MIN,
|
|
200
|
+
max: RENDER_SCALE_MAX,
|
|
201
|
+
step: RENDER_SCALE_STEP,
|
|
202
|
+
format: percent,
|
|
203
|
+
set: (value) => store.set(SETTING_IDS.graphicsRenderScale, value),
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: SETTING_IDS.graphicsFrameRateLimit,
|
|
207
|
+
label: "Frame rate limit",
|
|
208
|
+
kind: "select",
|
|
209
|
+
value: store.get(SETTING_IDS.graphicsFrameRateLimit, FRAME_RATE_LIMIT_DISPLAY),
|
|
210
|
+
options: FRAME_RATE_LIMIT_OPTIONS,
|
|
211
|
+
set: (value) => store.set(SETTING_IDS.graphicsFrameRateLimit, String(value)),
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: SETTING_IDS.graphicsAnisotropy,
|
|
215
|
+
label: "Texture filtering",
|
|
216
|
+
kind: "select",
|
|
217
|
+
value: String(graphics.profile.anisotropy),
|
|
218
|
+
options: ANISOTROPY_OPTIONS,
|
|
219
|
+
set: (value) => store.set(SETTING_IDS.graphicsAnisotropy, String(value)),
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: SETTING_IDS.graphicsShadows,
|
|
223
|
+
label: "Shadows",
|
|
224
|
+
kind: "toggle",
|
|
225
|
+
value: store.get(SETTING_IDS.graphicsShadows, DEFAULT_GRAPHICS_SHADOWS),
|
|
226
|
+
set: (value) => store.set(SETTING_IDS.graphicsShadows, value),
|
|
227
|
+
},
|
|
228
|
+
...stages.map((stage) => ({
|
|
229
|
+
id: graphicsPostStageSettingId(stage),
|
|
230
|
+
label: GRAPHICS_POST_STAGE_LABELS[stage],
|
|
231
|
+
kind: "toggle",
|
|
232
|
+
value: graphics.profile.postStages[stage],
|
|
233
|
+
set: (value) => store.set(graphicsPostStageSettingId(stage), value),
|
|
234
|
+
})),
|
|
235
|
+
{
|
|
236
|
+
id: SETTING_IDS.graphicsUiScale,
|
|
237
|
+
label: "UI scale",
|
|
238
|
+
kind: "slider",
|
|
239
|
+
value: store.get(SETTING_IDS.graphicsUiScale, DEFAULT_UI_SCALE),
|
|
240
|
+
min: UI_SCALE_MIN,
|
|
241
|
+
max: UI_SCALE_MAX,
|
|
242
|
+
step: 0.05,
|
|
243
|
+
format: percent,
|
|
244
|
+
set: (value) => store.set(SETTING_IDS.graphicsUiScale, value),
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: SETTING_IDS.graphicsShowFps,
|
|
248
|
+
label: "Show FPS",
|
|
249
|
+
kind: "toggle",
|
|
250
|
+
value: store.get(SETTING_IDS.graphicsShowFps, false),
|
|
251
|
+
set: (value) => store.set(SETTING_IDS.graphicsShowFps, value),
|
|
252
|
+
},
|
|
253
|
+
];
|
|
254
|
+
}
|
|
243
255
|
function indexOrder(order, id) {
|
|
244
256
|
const index = order.indexOf(id);
|
|
245
257
|
return index === -1 ? 999 : index;
|
|
@@ -6,7 +6,7 @@ export interface RevealVisionOptions {
|
|
|
6
6
|
tags: readonly string[];
|
|
7
7
|
resolveTags: (entity: SceneEntity) => readonly string[];
|
|
8
8
|
}
|
|
9
|
-
/** Occlusion-ignoring tagged-entity radius query (#115), bound to the live scene. */
|
|
9
|
+
/** Occlusion-ignoring tagged-entity radius query (#115), bound to the live scene and refreshed on render. */
|
|
10
10
|
export declare function useRevealHits(options: RevealVisionOptions): readonly RevealHit[];
|
|
11
11
|
export interface RevealHighlightsProps extends RevealVisionOptions {
|
|
12
12
|
enabled: boolean;
|
|
@@ -17,6 +17,8 @@ export interface RevealHighlightsProps extends RevealVisionOptions {
|
|
|
17
17
|
* occluders (Dark Sight / detective-vision / wallhack style). Renders with
|
|
18
18
|
* `depthTest: false` so the highlight draws over any wall standing between
|
|
19
19
|
* the origin and the revealed entity, rather than the usual depth-sorted scene.
|
|
20
|
+
* Highlights track the origin and their targets every frame while enabled, so
|
|
21
|
+
* a disabled mode costs no renders.
|
|
20
22
|
*/
|
|
21
23
|
export declare function RevealHighlights(props: RevealHighlightsProps): import("react").JSX.Element | null;
|
|
22
24
|
export interface RevealScreenTintProps {
|
|
@@ -1,37 +1,56 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo } from "react";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { useFrame } from "@react-three/fiber";
|
|
3
4
|
import { DoubleSide } from "three";
|
|
4
5
|
import { createRevealQuery } from "@jgengine/core/sensor/revealQuery";
|
|
5
6
|
import { usePlayer, useSceneEntities } from "@jgengine/react/hooks";
|
|
6
|
-
|
|
7
|
+
function revealHits(entities, originId, options) {
|
|
8
|
+
const byId = new Map(entities.map((entity) => [entity.id, entity]));
|
|
9
|
+
const query = createRevealQuery({
|
|
10
|
+
resolvePosition: (id) => byId.get(id)?.position,
|
|
11
|
+
resolveTags: (id) => {
|
|
12
|
+
const entity = byId.get(id);
|
|
13
|
+
return entity === undefined ? [] : options.resolveTags(entity);
|
|
14
|
+
},
|
|
15
|
+
candidates: () => entities.map((entity) => entity.id),
|
|
16
|
+
});
|
|
17
|
+
return query.inRadius(originId, options.radius, options.tags);
|
|
18
|
+
}
|
|
19
|
+
/** Occlusion-ignoring tagged-entity radius query (#115), bound to the live scene and refreshed on render. */
|
|
7
20
|
export function useRevealHits(options) {
|
|
8
21
|
const entities = useSceneEntities();
|
|
9
22
|
const player = usePlayer();
|
|
10
23
|
const originId = options.originEntityId ?? player.userId;
|
|
11
24
|
const tagsKey = options.tags.join("|");
|
|
12
|
-
return useMemo(() =>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return query.inRadius(originId, options.radius, options.tags);
|
|
23
|
-
}, [entities, originId, options.radius, tagsKey, options.tags, options.resolveTags]);
|
|
25
|
+
return useMemo(() => revealHits(entities, originId, options), [entities, originId, options.radius, tagsKey, options.tags, options.resolveTags]);
|
|
26
|
+
}
|
|
27
|
+
/** Frames on a counter while `active`: the hook lists only change on membership, so a live overlay drives its own re-render. */
|
|
28
|
+
function useFrameRefresh(active) {
|
|
29
|
+
const [frame, setFrame] = useState(0);
|
|
30
|
+
useFrame(() => {
|
|
31
|
+
if (active)
|
|
32
|
+
setFrame((value) => (value + 1) % 1_000_000);
|
|
33
|
+
});
|
|
34
|
+
return frame;
|
|
24
35
|
}
|
|
25
36
|
/**
|
|
26
37
|
* Screen-space reveal effect (#115) — highlights tagged entities through
|
|
27
38
|
* occluders (Dark Sight / detective-vision / wallhack style). Renders with
|
|
28
39
|
* `depthTest: false` so the highlight draws over any wall standing between
|
|
29
40
|
* the origin and the revealed entity, rather than the usual depth-sorted scene.
|
|
41
|
+
* Highlights track the origin and their targets every frame while enabled, so
|
|
42
|
+
* a disabled mode costs no renders.
|
|
30
43
|
*/
|
|
31
44
|
export function RevealHighlights(props) {
|
|
32
45
|
const { enabled, color = "#7dd3fc" } = props;
|
|
33
|
-
const
|
|
46
|
+
const frame = useFrameRefresh(enabled);
|
|
34
47
|
const entities = useSceneEntities();
|
|
48
|
+
const player = usePlayer();
|
|
49
|
+
const originId = props.originEntityId ?? player.userId;
|
|
50
|
+
const tagsKey = props.tags.join("|");
|
|
51
|
+
const hits = useMemo(() => (enabled ? revealHits(entities, originId, props) : []),
|
|
52
|
+
// `frame` is the refresh: the list identity says nothing about where the origin or the targets moved.
|
|
53
|
+
[enabled, frame, entities, originId, props.radius, tagsKey, props.tags, props.resolveTags]);
|
|
35
54
|
if (!enabled)
|
|
36
55
|
return null;
|
|
37
56
|
return (_jsx(_Fragment, { children: hits.map((hit) => {
|
package/dist/world/WorldItems.js
CHANGED
|
@@ -5,12 +5,16 @@ import { useFrame } from "@react-three/fiber";
|
|
|
5
5
|
import { resolveWorldItemPresentation } from "@jgengine/core/game/worldItem";
|
|
6
6
|
import { useGameContext } from "@jgengine/react/provider";
|
|
7
7
|
import { useWorldItems } from "@jgengine/react/hooks";
|
|
8
|
+
import { emptyPoseMemo, writePoseIfChanged } from "./entityPose.js";
|
|
8
9
|
import { POINTER_ENTITY_KEY } from "../pointer/pointerService.js";
|
|
9
10
|
const DEFAULT_BEAM_HEIGHT = 2.5;
|
|
10
11
|
const FALLBACK_COLOR = "#e5e7eb";
|
|
11
12
|
const REST_HEIGHT = 0.32;
|
|
12
13
|
const SPAWN_DURATION = 0.4;
|
|
13
|
-
function WorldItemMarker({ instanceId,
|
|
14
|
+
function WorldItemMarker({ instanceId, color, beam, label, beamHeight, }) {
|
|
15
|
+
const ctx = useGameContext();
|
|
16
|
+
const groupRef = useRef(null);
|
|
17
|
+
const poseRef = useRef(emptyPoseMemo());
|
|
14
18
|
const beamPoints = useMemo(() => [
|
|
15
19
|
[0, 0, 0],
|
|
16
20
|
[0, beamHeight, 0],
|
|
@@ -21,6 +25,10 @@ function WorldItemMarker({ instanceId, position, color, beam, label, beamHeight,
|
|
|
21
25
|
const gemRef = useRef(null);
|
|
22
26
|
const ageRef = useRef(0);
|
|
23
27
|
useFrame((_state, delta) => {
|
|
28
|
+
const group = groupRef.current;
|
|
29
|
+
const live = ctx.scene.entity.get(instanceId);
|
|
30
|
+
if (group !== null && live !== null)
|
|
31
|
+
writePoseIfChanged(group, live, poseRef.current);
|
|
24
32
|
const gem = gemRef.current;
|
|
25
33
|
if (gem === null)
|
|
26
34
|
return;
|
|
@@ -31,7 +39,7 @@ function WorldItemMarker({ instanceId, position, color, beam, label, beamHeight,
|
|
|
31
39
|
gem.position.y = REST_HEIGHT + bob + spawn;
|
|
32
40
|
gem.rotation.y = age * 1.6;
|
|
33
41
|
});
|
|
34
|
-
return (_jsxs("group", {
|
|
42
|
+
return (_jsxs("group", { ref: groupRef, matrixAutoUpdate: false, userData: { [POINTER_ENTITY_KEY]: instanceId }, children: [_jsxs("mesh", { "rotation-x": -Math.PI / 2, "position-y": 0.03, children: [_jsx("ringGeometry", { args: [0.26, 0.4, 24] }), _jsx("meshBasicMaterial", { color: color, transparent: true, opacity: 0.85 })] }), _jsx("group", { ref: gemRef, "position-y": REST_HEIGHT, children: _jsxs("mesh", { children: [_jsx("octahedronGeometry", { args: [0.22, 0] }), _jsx("meshStandardMaterial", { color: color, emissive: color, emissiveIntensity: 0.6 })] }) }), beam ? _jsx(Line, { points: beamPoints, color: color, lineWidth: 2, transparent: true, opacity: 0.55 }) : null, label !== undefined && label.length > 0 ? (_jsx(Html, { position: [0, beamHeight + 0.2, 0], center: true, distanceFactor: 12, zIndexRange: [15, 0], children: _jsx("div", { className: "whitespace-nowrap rounded-sm border px-1.5 py-0.5 text-[11px] font-bold shadow", style: { borderColor: color, color, backgroundColor: "rgba(10,10,14,0.75)" }, children: label }) })) : null] }));
|
|
35
43
|
}
|
|
36
44
|
/** Rarity→beam/color/label render binding + loot-filter overlay (#32/#33) for every dropped `worldItem`. */
|
|
37
45
|
export function WorldItems({ config }) {
|
|
@@ -45,6 +53,6 @@ export function WorldItems({ config }) {
|
|
|
45
53
|
const presentation = resolveWorldItemPresentation(item, config?.rarityStyle, config?.filter);
|
|
46
54
|
if (presentation.hidden)
|
|
47
55
|
return null;
|
|
48
|
-
return (_jsx(WorldItemMarker, { instanceId: item.instanceId,
|
|
56
|
+
return (_jsx(WorldItemMarker, { instanceId: item.instanceId, color: presentation.color ?? FALLBACK_COLOR, beam: presentation.beam, label: presentation.label, beamHeight: beamHeight }, item.instanceId));
|
|
49
57
|
}) }));
|
|
50
58
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
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",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"test": "bun test src"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@jgengine/core": "^0.
|
|
39
|
-
"@jgengine/react": "^0.
|
|
40
|
-
"@jgengine/ws": "^0.
|
|
38
|
+
"@jgengine/core": "^0.33.0",
|
|
39
|
+
"@jgengine/react": "^0.33.0",
|
|
40
|
+
"@jgengine/ws": "^0.33.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@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.
|
|
9
|
+
- `VERSION` (const): const VERSION: "0.33.0" — 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.
|
|
288
|
+
- `VERSION` (const): const VERSION: "0.33.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
|
|
289
289
|
|
|
290
290
|
## @jgengine/core/runtime/adapter
|
|
291
291
|
|