@jgengine/shell 0.7.0 → 0.8.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +26 -1
  3. package/dist/GameHost.d.ts +12 -0
  4. package/dist/GameHost.js +33 -0
  5. package/dist/GamePlayer.d.ts +11 -0
  6. package/dist/GamePlayer.js +24 -0
  7. package/dist/GamePlayerShell.d.ts +33 -1
  8. package/dist/GamePlayerShell.js +439 -28
  9. package/dist/behaviour.d.ts +31 -0
  10. package/dist/behaviour.js +63 -0
  11. package/dist/camera/GameCameraRig.d.ts +8 -3
  12. package/dist/camera/GameCameraRig.js +28 -14
  13. package/dist/camera/GameFirstPersonCamera.js +2 -0
  14. package/dist/camera/GameInspectionCamera.d.ts +6 -0
  15. package/dist/camera/GameInspectionCamera.js +24 -0
  16. package/dist/camera/cameraRigs.d.ts +5 -1
  17. package/dist/camera/cameraRigs.js +43 -6
  18. package/dist/camera/index.d.ts +4 -2
  19. package/dist/camera/index.js +4 -2
  20. package/dist/camera/inspectionCameraMath.d.ts +25 -0
  21. package/dist/camera/inspectionCameraMath.js +44 -0
  22. package/dist/camera/rigMath.d.ts +40 -1
  23. package/dist/camera/rigMath.js +55 -0
  24. package/dist/camera/rigResolve.d.ts +15 -0
  25. package/dist/camera/rigResolve.js +52 -0
  26. package/dist/defineGame.d.ts +16 -0
  27. package/dist/defineGame.js +51 -0
  28. package/dist/devtools/DevtoolsOverlay.d.ts +14 -0
  29. package/dist/devtools/DevtoolsOverlay.js +334 -0
  30. package/dist/environment/Daylight.d.ts +50 -0
  31. package/dist/environment/Daylight.js +100 -0
  32. package/dist/environment/EnvironmentScene.js +18 -26
  33. package/dist/environment/GroundPad.d.ts +7 -0
  34. package/dist/environment/GroundPad.js +12 -0
  35. package/dist/environment/daylightCycle.d.ts +26 -0
  36. package/dist/environment/daylightCycle.js +116 -0
  37. package/dist/environment/groundPadMath.d.ts +13 -0
  38. package/dist/environment/groundPadMath.js +10 -0
  39. package/dist/environment/index.d.ts +2 -0
  40. package/dist/environment/index.js +2 -0
  41. package/dist/materialOverride.d.ts +8 -0
  42. package/dist/materialOverride.js +32 -0
  43. package/dist/multiplayer.d.ts +16 -9
  44. package/dist/multiplayer.js +62 -12
  45. package/dist/pointer/PointerProbe.js +14 -1
  46. package/dist/pointer/pointerService.d.ts +2 -0
  47. package/dist/pointer/pointerService.js +45 -24
  48. package/dist/registry.d.ts +4 -1
  49. package/dist/registry.js +3 -1
  50. package/dist/render/modelRender.d.ts +14 -0
  51. package/dist/render/modelRender.js +79 -0
  52. package/dist/terrain/index.d.ts +1 -1
  53. package/dist/terrain/index.js +1 -1
  54. package/dist/terrain/terrainMath.d.ts +1 -0
  55. package/dist/terrain/terrainMath.js +8 -2
  56. package/dist/touch/TouchControlsOverlay.d.ts +18 -0
  57. package/dist/touch/TouchControlsOverlay.js +173 -0
  58. package/dist/water/OceanShader.d.ts +1 -1
  59. package/dist/water/OceanShader.js +3 -3
  60. package/dist/world/DataObjects.d.ts +44 -0
  61. package/dist/world/DataObjects.js +75 -0
  62. package/dist/world/GridWorldScene.d.ts +10 -0
  63. package/dist/world/GridWorldScene.js +54 -0
  64. package/dist/world/WorldHud.d.ts +5 -1
  65. package/dist/world/WorldHud.js +6 -2
  66. package/dist/world/WorldItems.js +21 -2
  67. package/llms.txt +2382 -0
  68. package/package.json +6 -5
  69. package/dist/demo/builderDemo.d.ts +0 -2
  70. package/dist/demo/builderDemo.js +0 -189
  71. package/dist/demo/demoGame.d.ts +0 -2
  72. package/dist/demo/demoGame.js +0 -208
  73. package/dist/demo/environmentShowcase.d.ts +0 -2
  74. package/dist/demo/environmentShowcase.js +0 -15
  75. package/dist/demo/mapDemo.d.ts +0 -2
  76. package/dist/demo/mapDemo.js +0 -206
  77. package/dist/demo/pointerDemo.d.ts +0 -2
  78. package/dist/demo/pointerDemo.js +0 -151
  79. package/dist/demo/sensorShowcase.d.ts +0 -2
  80. package/dist/demo/sensorShowcase.js +0 -131
  81. package/dist/demo/survivalDemo.d.ts +0 -2
  82. package/dist/demo/survivalDemo.js +0 -291
@@ -1,206 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useMemo, useSyncExternalStore } from "react";
3
- import { defineGame } from "@jgengine/core/game/defineGame";
4
- import { createPingSystem, DEFAULT_PING_CATEGORIES, PING_FEED_ACTION, } from "@jgengine/core/game/ping";
5
- import { createAssetCatalog } from "@jgengine/core/scene/assetCatalog";
6
- import { environment, grass, terrain } from "@jgengine/core/world/features";
7
- import { createFogField } from "@jgengine/core/world/fog";
8
- import { createMarkerSet } from "@jgengine/core/world/markers";
9
- import { headingToBearing } from "@jgengine/core/world/minimap";
10
- import { resolveTerrainField } from "@jgengine/core/world/terrain";
11
- import { Compass, Minimap, WorldMap } from "@jgengine/react/map";
12
- import { usePlayer, useSceneEntities, useFeed } from "@jgengine/react/hooks";
13
- import { EnvironmentScene } from "../environment/EnvironmentScene.js";
14
- import { bakeTerrainMap } from "../map/terrainMap.js";
15
- import { MapMarkerBeacons } from "../map/MapMarkerBeacons.js";
16
- const SCOUT = "scout";
17
- const RAIDER = "raider";
18
- const CACHE = "cache";
19
- const HAZARD = "hazard";
20
- const RAIDER_ID = "raider-1";
21
- const RAIDER_MARKER = "raider-marker";
22
- const BOUNDS = { minX: -60, minZ: -60, maxX: 60, maxZ: 60 };
23
- const FOG_CELL = 6;
24
- const terrainFeature = environment({
25
- terrain: terrain({ bounds: { w: 120, d: 120 }, height: 5, frequency: 0.03, seed: "extraction", waterLevel: -2 }),
26
- vegetation: grass({ area: { w: 100, d: 100 }, density: 4, colors: ["#2f4f1e", "#8fbf4a"], seed: "extraction" }),
27
- });
28
- const objectPingCategory = {
29
- [CACHE]: "loot",
30
- [HAZARD]: "danger",
31
- };
32
- const entityCatalog = {
33
- [SCOUT]: { movement: { walkSpeed: 6 }, role: "player" },
34
- [RAIDER]: { movement: { walkSpeed: 4 }, role: "enemy" },
35
- };
36
- const objectCatalog = {
37
- [CACHE]: {},
38
- [HAZARD]: {},
39
- };
40
- const OBJECTIVES = [
41
- { id: "obj-extract", label: "Extraction", position: [44, 0, -46] },
42
- { id: "obj-relay", label: "Relay", position: [-40, 0, 30] },
43
- ];
44
- const CACHES = [
45
- [18, 0, 12],
46
- [-26, 0, -18],
47
- [8, 0, -34],
48
- ];
49
- const HAZARDS = [[-12, 0, -6]];
50
- const terrainField = resolveTerrainField(terrainFeature.terrain);
51
- const markerSet = createMarkerSet();
52
- const fogField = createFogField({ bounds: BOUNDS, cellSize: FOG_CELL });
53
- let pingSystem = null;
54
- let raiderAngle = 0;
55
- let lastPlayerXZ = null;
56
- let worldMapOpen = true;
57
- const mapListeners = new Set();
58
- function setWorldMapOpen(open) {
59
- worldMapOpen = open;
60
- for (const listener of mapListeners)
61
- listener();
62
- }
63
- const game = defineGame({
64
- name: "extraction-map",
65
- assets: createAssetCatalog(),
66
- multiplayer: null,
67
- inventories: {},
68
- input: {
69
- moveForward: ["KeyW"],
70
- moveBack: ["KeyS"],
71
- moveLeft: ["KeyA"],
72
- moveRight: ["KeyD"],
73
- ping: ["KeyQ"],
74
- toggleMap: ["KeyM"],
75
- },
76
- });
77
- function seedMarkers() {
78
- markerSet.clear();
79
- for (const objective of OBJECTIVES) {
80
- markerSet.add({ id: objective.id, kind: "objective", position: objective.position, label: objective.label });
81
- }
82
- CACHES.forEach((position, index) => {
83
- markerSet.add({ id: `cache-${index}`, kind: "loot", position, label: "Supply cache" });
84
- });
85
- HAZARDS.forEach((position, index) => {
86
- markerSet.add({ id: `hazard-${index}`, kind: "danger", position, label: "Minefield" });
87
- });
88
- }
89
- function onInit(ctx) {
90
- raiderAngle = 0;
91
- lastPlayerXZ = null;
92
- worldMapOpen = true;
93
- fogField.reset();
94
- seedMarkers();
95
- fogField.reveal(0, 0, 22);
96
- fogField.revealAlong([0, 0], [18, 12], 10);
97
- fogField.revealAlong([0, 0], [-26, -18], 8);
98
- pingSystem = createPingSystem({
99
- markers: markerSet,
100
- feed: { push: (action, entry) => ctx.game.feed.push(action, entry) },
101
- party: ctx.game.social.party,
102
- ttlMs: 45_000,
103
- categories: DEFAULT_PING_CATEGORIES,
104
- classify: {
105
- roleOf: (id) => {
106
- const entity = ctx.scene.entity.get(id);
107
- return entity === null ? null : entityCatalog[entity.name]?.role ?? null;
108
- },
109
- categoryOf: (id) => {
110
- const object = ctx.scene.object.get(id);
111
- return object === null ? null : objectPingCategory[object.catalogId] ?? null;
112
- },
113
- },
114
- });
115
- ctx.game.commands.define("map.ping", {
116
- apply(state, input) {
117
- if (pingSystem === null)
118
- return state;
119
- pingSystem.ping(state.player.userId, input);
120
- return state;
121
- },
122
- });
123
- ctx.game.commands.define("ui.toggleMap", {
124
- apply(state) {
125
- setWorldMapOpen(!worldMapOpen);
126
- return state;
127
- },
128
- });
129
- for (const position of CACHES)
130
- ctx.scene.object.place(CACHE, position[0], 0, position[2]);
131
- for (const position of HAZARDS)
132
- ctx.scene.object.place(HAZARD, position[0], 0, position[2]);
133
- }
134
- function onNewPlayer(ctx) {
135
- ctx.scene.entity.spawn(SCOUT, { id: ctx.player.userId, position: [0, 0, 0], role: "player" });
136
- ctx.scene.entity.spawn(RAIDER, { id: RAIDER_ID, position: [24, 0, -24], role: "npc" });
137
- markerSet.add({ id: RAIDER_MARKER, kind: "enemy", position: [24, 0, -24], label: "Raider" });
138
- if (pingSystem !== null) {
139
- pingSystem.ping(ctx.player.userId, {
140
- point: [30, 0, 8],
141
- normal: [0, 1, 0],
142
- entity: null,
143
- object: null,
144
- }, "location");
145
- }
146
- }
147
- function onTick(ctx, dt) {
148
- raiderAngle += dt * 0.4;
149
- const raiderX = 24 + Math.cos(raiderAngle) * 14;
150
- const raiderZ = -24 + Math.sin(raiderAngle) * 14;
151
- ctx.scene.entity.setPose(RAIDER_ID, { position: [raiderX, 0, raiderZ], rotationY: raiderAngle, dt });
152
- markerSet.add({ id: RAIDER_MARKER, kind: "enemy", position: [raiderX, 0, raiderZ], label: "Raider" });
153
- const player = ctx.scene.entity.get(ctx.player.userId);
154
- if (player !== null) {
155
- const here = [player.position[0], player.position[2]];
156
- if (lastPlayerXZ === null)
157
- fogField.reveal(here[0], here[1], 14);
158
- else
159
- fogField.revealAlong(lastPlayerXZ, here, 14);
160
- lastPlayerXZ = here;
161
- }
162
- markerSet.prune(Date.now());
163
- }
164
- function useWorldMapOpen() {
165
- return useSyncExternalStore((listener) => {
166
- mapListeners.add(listener);
167
- return () => mapListeners.delete(listener);
168
- }, () => worldMapOpen, () => worldMapOpen);
169
- }
170
- function MapUI() {
171
- const player = usePlayer();
172
- const entities = useSceneEntities();
173
- const pings = useFeed({ action: PING_FEED_ACTION, limit: 3 });
174
- const open = useWorldMapOpen();
175
- const self = entities.find((entity) => entity.id === player.userId) ??
176
- entities.find((entity) => entity.role === "player") ??
177
- null;
178
- const center = self === null ? [0, 0] : [self.position[0], self.position[2]];
179
- const heading = self === null ? 0 : headingToBearing(self.rotationY);
180
- const baked = useMemo(() => bakeTerrainMap(terrainField, BOUNDS, { resolution: 160 }), []);
181
- const background = baked?.url;
182
- return (_jsxs("div", { className: "pointer-events-none absolute inset-0 font-sans text-white", children: [_jsx("div", { className: "absolute left-1/2 top-4 -translate-x-1/2", children: _jsx(Compass, { heading: heading, center: center, markers: markerSet, width: 360 }) }), open ? (_jsx("div", { className: "pointer-events-auto absolute left-4 top-4", children: _jsx(WorldMap, { markers: markerSet, bounds: BOUNDS, fog: fogField, player: center, heading: heading, background: background, width: 340, onClose: () => setWorldMapOpen(false) }) })) : (_jsxs("div", { className: "absolute left-4 top-16 rounded-md border border-white/15 bg-neutral-900/80 px-3 py-2 text-[11px] text-white/60", children: ["Press ", _jsx("span", { className: "text-emerald-300", children: "M" }), " for the world map"] })), _jsx("div", { className: "absolute bottom-4 right-4", children: _jsx(Minimap, { markers: markerSet, fog: fogField, center: center, heading: heading, worldRadius: 46, size: 188, background: background, mapBounds: BOUNDS, title: "Sector" }) }), _jsxs("div", { className: "absolute bottom-4 left-4 w-64 rounded-lg border border-white/15 bg-neutral-900/80 p-3 text-[12px] leading-6 shadow-xl backdrop-blur-sm", children: [_jsx("div", { className: "mb-1 border-b border-white/10 pb-1 text-[10px] font-semibold uppercase tracking-widest text-white/45", children: "Comms" }), pings.length === 0 ? (_jsx("p", { className: "text-white/50", children: "Press Q to ping what you aim at." })) : (_jsx("ul", { className: "text-white/85", children: [...pings].reverse().map((entry, index) => {
183
- const payload = entry.data;
184
- return (_jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-cyan-300", children: "\u25CE" }), payload.callout] }, `${entry.at}-${index}`));
185
- }) }))] })] }));
186
- }
187
- export const mapDemoGame = {
188
- game,
189
- content: {
190
- entityById: (catalogId) => entityCatalog[catalogId] ?? null,
191
- objectById: (catalogId) => objectCatalog[catalogId] ?? null,
192
- },
193
- loop: { onInit, onNewPlayer, onTick },
194
- GameUI: MapUI,
195
- environment: () => _jsx(EnvironmentScene, { feature: terrainFeature }),
196
- WorldOverlay: () => _jsx(MapMarkerBeacons, { markers: markerSet }),
197
- pointer: { pingCommand: "map.ping" },
198
- camera: {
199
- initialDistance: 40,
200
- initialHeight: 34,
201
- minDistance: 16,
202
- maxDistance: 80,
203
- targetHeight: 0,
204
- maxPolarAngle: 1.35,
205
- },
206
- };
@@ -1,2 +0,0 @@
1
- import type { PlayableGame } from "../registry.js";
2
- export declare const pointerDemoGame: PlayableGame;
@@ -1,151 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { defineGame } from "@jgengine/core/game/defineGame";
3
- import { contextVerb } from "@jgengine/core/interaction/contextMenu";
4
- import { createNavGrid, findPath, } from "@jgengine/core/nav/navGrid";
5
- import { advancePathFollow, createPathFollow, pathFromNav, } from "@jgengine/core/nav/pathFollow";
6
- import { createAssetCatalog } from "@jgengine/core/scene/assetCatalog";
7
- const VILLAGER = "villager";
8
- const CREEP = "creep";
9
- const BOULDER = "boulder";
10
- const CREEP_ID = "creep-1";
11
- const BOUNDS = { minX: -24, minZ: -24, maxX: 24, maxZ: 24 };
12
- const CELL = 1.5;
13
- const OBSTACLES = [
14
- [-6, -4],
15
- [5, 6],
16
- [8, -9],
17
- [-11, 8],
18
- ];
19
- const SQUAD = [
20
- [3, 0, 2],
21
- [-3, 0, 2],
22
- [0, 0, 4],
23
- ];
24
- const CREEP_PATH = [
25
- [-22, 0, -16],
26
- [-8, 0, -16],
27
- [-8, 0, 10],
28
- [10, 0, 10],
29
- [10, 0, -14],
30
- [22, 0, -14],
31
- ];
32
- const entityCatalog = {
33
- [VILLAGER]: {
34
- movement: { walkSpeed: 5 },
35
- role: "npc",
36
- verbs: [contextVerb("Stop", "unit.stop"), contextVerb("Cheer", "unit.cheer")],
37
- },
38
- [CREEP]: { role: "enemy" },
39
- };
40
- const objectCatalog = {
41
- [BOULDER]: { verbs: [contextVerb("Inspect", "world.inspect")] },
42
- };
43
- let nav = null;
44
- const movers = new Map();
45
- let creep = null;
46
- function orderUnit(ctx, id, point) {
47
- if (nav === null)
48
- return;
49
- const unit = ctx.scene.entity.get(id);
50
- if (unit === null)
51
- return;
52
- const route = findPath(nav, [unit.position[0], unit.position[2]], [point[0], point[2]], { clearance: 1.4 });
53
- if (route === null)
54
- return;
55
- const config = { waypoints: pathFromNav(route, 0), speed: unit.movement.walkSpeed ?? 5 };
56
- movers.set(id, { config, state: createPathFollow(config) });
57
- }
58
- const game = defineGame({
59
- name: "pointer-commander",
60
- assets: createAssetCatalog(),
61
- multiplayer: null,
62
- inventories: {},
63
- input: {
64
- moveForward: ["KeyW"],
65
- moveBack: ["KeyS"],
66
- moveLeft: ["KeyA"],
67
- moveRight: ["KeyD"],
68
- },
69
- });
70
- function onInit(ctx) {
71
- movers.clear();
72
- creep = null;
73
- const grid = createNavGrid({ bounds: BOUNDS, cellSize: CELL });
74
- for (const [x, z] of OBSTACLES) {
75
- ctx.scene.object.place(BOULDER, x, 0, z);
76
- grid.blockAabb({ minX: x - 1.6, minZ: z - 1.6, maxX: x + 1.6, maxZ: z + 1.6 });
77
- }
78
- nav = grid;
79
- ctx.game.commands.define("unit.order", {
80
- apply(state, input) {
81
- for (const id of input.selection)
82
- orderUnit(state, id, input.point);
83
- return state;
84
- },
85
- });
86
- ctx.game.commands.define("unit.stop", {
87
- apply(state, input) {
88
- movers.delete(input.target);
89
- return state;
90
- },
91
- });
92
- ctx.game.commands.define("unit.cheer", {
93
- apply(state, input) {
94
- state.scene.entity.floatText({ instanceId: input.target, text: "For the realm!", kind: "info" });
95
- return state;
96
- },
97
- });
98
- ctx.game.commands.define("world.inspect", {
99
- apply(state, input) {
100
- if (input.point !== undefined)
101
- state.scene.entity.floatText({ position: input.point, text: "Granite", kind: "info" });
102
- return state;
103
- },
104
- });
105
- }
106
- function onNewPlayer(ctx) {
107
- ctx.scene.entity.spawn(VILLAGER, { id: ctx.player.userId, position: [0, 0, 0], role: "player" });
108
- for (const position of SQUAD)
109
- ctx.scene.entity.spawn(VILLAGER, { position, role: "npc" });
110
- const config = { waypoints: CREEP_PATH, speed: 3.5, loop: true };
111
- creep = { config, state: createPathFollow(config) };
112
- ctx.scene.entity.spawn(CREEP, { id: CREEP_ID, position: CREEP_PATH[0], role: "npc" });
113
- }
114
- function onTick(ctx, dt) {
115
- for (const [id, mover] of movers) {
116
- mover.state = advancePathFollow(mover.config, mover.state, dt);
117
- ctx.scene.entity.setPose(id, { position: mover.state.position, rotationY: mover.state.heading, dt });
118
- if (mover.state.done)
119
- movers.delete(id);
120
- }
121
- if (creep !== null) {
122
- creep.state = advancePathFollow(creep.config, creep.state, dt);
123
- ctx.scene.entity.setPose(CREEP_ID, { position: creep.state.position, rotationY: creep.state.heading, dt });
124
- }
125
- }
126
- function CommanderUI() {
127
- return (_jsxs("div", { className: "pointer-events-none absolute inset-0 font-sans text-white", children: [_jsxs("div", { className: "absolute left-4 top-4 rounded-lg border border-emerald-300/25 bg-neutral-900/80 px-4 py-3 shadow-xl backdrop-blur-sm", children: [_jsx("h1", { className: "text-sm font-semibold tracking-wide text-emerald-200", children: "RTS Commander" }), _jsx("p", { className: "text-xs text-white/60", children: "Pointer-driven unit command" })] }), _jsxs("div", { className: "absolute bottom-4 left-4 w-72 rounded-lg border border-white/15 bg-neutral-900/80 p-3 text-[13px] leading-6 shadow-xl backdrop-blur-sm", children: [_jsx("div", { className: "mb-1 border-b border-white/10 pb-1 text-[10px] font-semibold uppercase tracking-widest text-white/45", children: "Controls" }), _jsxs("ul", { className: "text-white/80", children: [_jsxs("li", { children: [_jsx("span", { className: "text-emerald-300", children: "Left-drag" }), " \u2014 box-select villagers"] }), _jsxs("li", { children: [_jsx("span", { className: "text-emerald-300", children: "Left-click" }), " \u2014 select one"] }), _jsxs("li", { children: [_jsx("span", { className: "text-emerald-300", children: "Right-click ground" }), " \u2014 move here (A*)"] }), _jsxs("li", { children: [_jsx("span", { className: "text-emerald-300", children: "Right-click unit / rock" }), " \u2014 verb menu"] }), _jsxs("li", { children: [_jsx("span", { className: "text-emerald-300", children: "Middle-drag" }), " \u2014 orbit \u00B7 wheel \u2014 zoom"] })] })] }), _jsx("div", { className: "absolute bottom-4 right-4 max-w-[220px] rounded-lg border border-white/10 bg-neutral-900/70 px-3 py-2 text-right text-[11px] text-white/50", children: "Green ring = selected. The looping creep walks an authored path (tower-defense pathFollow)." })] }));
128
- }
129
- export const pointerDemoGame = {
130
- game,
131
- content: {
132
- entityById: (catalogId) => entityCatalog[catalogId] ?? null,
133
- objectById: (catalogId) => objectCatalog[catalogId] ?? null,
134
- },
135
- loop: { onInit, onNewPlayer, onTick },
136
- GameUI: CommanderUI,
137
- pointer: {
138
- select: true,
139
- selectFilter: (id) => id !== CREEP_ID,
140
- orderCommand: "unit.order",
141
- contextMenu: true,
142
- },
143
- camera: {
144
- initialDistance: 34,
145
- initialHeight: 28,
146
- minDistance: 12,
147
- maxDistance: 64,
148
- targetHeight: 0,
149
- maxPolarAngle: 1.25,
150
- },
151
- };
@@ -1,2 +0,0 @@
1
- import type { PlayableGame } from "../registry.js";
2
- export declare const sensorShowcaseGame: PlayableGame;
@@ -1,131 +0,0 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from "react";
3
- import { defineGame } from "@jgengine/core/game/defineGame";
4
- import { createAssetCatalog } from "@jgengine/core/scene/assetCatalog";
5
- import { localPlayerEntity, useGameStore } from "@jgengine/react/hooks";
6
- import { useGameContext } from "@jgengine/react/provider";
7
- import { useSessionRecorder } from "../replay/useSessionRecorder.js";
8
- import { FrustumSensorReadout } from "../vision/FrustumSensorHud.js";
9
- import { SensorReadoutMeter, useHiddenStateProbe } from "../vision/HiddenStateProbeHud.js";
10
- import { RevealHighlights, RevealScreenTint } from "../vision/RevealVision.js";
11
- const HERO = "hero";
12
- const CULPRIT = "culprit";
13
- const CLUE = "clue-mote";
14
- const WALL = "sensor-wall";
15
- const CULPRIT_ID = "sensor-showcase-culprit";
16
- const TAGS_BY_CATALOG_ID = {
17
- [CULPRIT]: ["culprit"],
18
- [CLUE]: ["clue"],
19
- };
20
- const HIDDEN_SOURCES = [
21
- { id: "cold-spot", position: [0, 0, -9], variables: { ghostType: "poltergeist", activity: 0.85 } },
22
- ];
23
- const entityCatalog = {
24
- [HERO]: { movement: { walkSpeed: 2.6 } },
25
- [CULPRIT]: {},
26
- [CLUE]: {},
27
- };
28
- const game = defineGame({
29
- name: "sensor-showcase",
30
- assets: createAssetCatalog(),
31
- multiplayer: null,
32
- input: {
33
- moveForward: ["KeyW"],
34
- moveBack: ["KeyS"],
35
- moveLeft: ["KeyA"],
36
- moveRight: ["KeyD"],
37
- jump: ["Space"],
38
- sprint: ["ShiftLeft"],
39
- turnLeft: ["KeyQ"],
40
- turnRight: ["KeyE"],
41
- darkSight: ["KeyV"],
42
- },
43
- });
44
- let elapsed = 0;
45
- function onInit(ctx) {
46
- elapsed = 0;
47
- ctx.game.commands.define("darkSight", {
48
- apply(state) {
49
- const toggle = darkSightStateFor(state);
50
- toggle.on = !toggle.on;
51
- for (const listener of toggle.listeners)
52
- listener();
53
- return state;
54
- },
55
- });
56
- }
57
- function onNewPlayer(ctx) {
58
- ctx.scene.entity.spawn(HERO, { id: ctx.player.userId, position: [0, 0, 4], role: "player" });
59
- ctx.scene.entity.spawn(CULPRIT, { id: CULPRIT_ID, position: [0, 0, -9], role: "npc" });
60
- ctx.scene.entity.spawn(CLUE, { position: [4, 0, -6], role: "prop" });
61
- for (let x = -3; x <= 3; x += 1) {
62
- for (let y = 0; y <= 1; y += 1) {
63
- ctx.scene.object.place(WALL, x, y, -2);
64
- }
65
- }
66
- }
67
- function onTick(ctx, dt) {
68
- elapsed += dt;
69
- const culprit = ctx.scene.entity.get(CULPRIT_ID);
70
- if (culprit === null)
71
- return;
72
- const x = Math.sin(elapsed * 0.35) * 5;
73
- ctx.scene.entity.setPose(CULPRIT_ID, { position: [x, 0, -9], dt });
74
- }
75
- function tagsForEntity(entity) {
76
- return TAGS_BY_CATALOG_ID[entity.name] ?? [];
77
- }
78
- const darkSightByContext = new WeakMap();
79
- function darkSightStateFor(ctx) {
80
- let state = darkSightByContext.get(ctx);
81
- if (state === undefined) {
82
- state = { on: false, listeners: new Set() };
83
- darkSightByContext.set(ctx, state);
84
- }
85
- return state;
86
- }
87
- /** Dark Sight toggle shared between the WorldOverlay (3D reveal) and GameUI (screen tint + button) — both mount as separate React trees over the same GameContext. */
88
- function useDarkSight() {
89
- const ctx = useGameContext();
90
- const state = darkSightStateFor(ctx);
91
- const [, forceRender] = useState(0);
92
- useEffect(() => {
93
- const listener = () => forceRender((count) => count + 1);
94
- state.listeners.add(listener);
95
- return () => {
96
- state.listeners.delete(listener);
97
- };
98
- }, [state]);
99
- return [state.on, () => ctx.game.commands.run("darkSight", {})];
100
- }
101
- function GhostTrail() {
102
- const buffer = useSessionRecorder(CULPRIT_ID, { maxDurationSeconds: 6 });
103
- const frames = buffer.frames().filter((_, index) => index % 6 === 0);
104
- return (_jsx(_Fragment, { children: frames.map((frame, index) => (_jsxs("mesh", { position: [frame.data.position[0], frame.data.position[1] + 0.2, frame.data.position[2]], children: [_jsx("sphereGeometry", { args: [0.08, 8, 8] }), _jsx("meshBasicMaterial", { color: "#a78bfa", transparent: true, opacity: 0.45, depthTest: false })] }, index))) }));
105
- }
106
- function SensorWorldOverlay() {
107
- const [darkSight] = useDarkSight();
108
- return (_jsxs(_Fragment, { children: [_jsx(RevealHighlights, { enabled: darkSight, radius: 20, tags: ["culprit", "clue"], resolveTags: tagsForEntity }), _jsx(FrustumSensorReadout, { subjectIds: [CULPRIT_ID], subjectRadius: 0.6, idealDistance: 7 }), _jsx(GhostTrail, {})] }));
109
- }
110
- function SensorHud() {
111
- const [darkSight, toggleDarkSight] = useDarkSight();
112
- const playerPosition = useGameStore((ctx) => localPlayerEntity(ctx)?.position ?? [0, 0, 0]);
113
- const reading = useHiddenStateProbe(playerPosition, HIDDEN_SOURCES, { range: 18, variableId: "activity" });
114
- return (_jsxs("div", { className: "pointer-events-none absolute inset-0 font-mono text-white", children: [_jsx(RevealScreenTint, { enabled: darkSight }), _jsx("div", { className: "absolute left-4 top-4 flex flex-col gap-2", children: _jsx(SensorReadoutMeter, { label: "EMF reader", reading: reading }) }), _jsx("div", { className: "pointer-events-auto absolute right-4 top-4", children: _jsx("button", { onClick: toggleDarkSight, className: `rounded border px-3 py-1.5 text-xs font-semibold uppercase tracking-wide ${darkSight
115
- ? "border-sky-300 bg-sky-400/20 text-sky-100"
116
- : "border-white/25 bg-black/60 text-white/70 hover:bg-white/10"}`, children: "Dark Sight (V)" }) }), _jsx("div", { className: "absolute bottom-4 left-4 max-w-xs text-[11px] leading-4 text-white/50", children: "WASD move \u00B7 Q/E turn \u00B7 V toggles Dark Sight (reveals tagged entities through the wall) \u00B7 EMF reader tracks the culprit's cold spot \u00B7 bottom-right shows framing/dwell for the culprit in your camera view." })] }));
117
- }
118
- export const sensorShowcaseGame = {
119
- game,
120
- content: {
121
- entityById: (catalogId) => entityCatalog[catalogId] ?? null,
122
- },
123
- loop: { onInit, onNewPlayer, onTick },
124
- GameUI: SensorHud,
125
- WorldOverlay: SensorWorldOverlay,
126
- camera: {
127
- cinematic: {
128
- keyframes: [{ position: { x: 0, y: 1.6, z: 3 }, lookAt: { x: 0, y: 1, z: -9 }, fov: 55 }],
129
- },
130
- },
131
- };
@@ -1,2 +0,0 @@
1
- import type { PlayableGame } from "../registry.js";
2
- export declare const survivalDemoGame: PlayableGame;