@jgengine/shell 0.18.1 → 0.19.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 +158 -7
- package/dist/GamePlayerShell.js +20 -3
- package/dist/Shell3dPresentation.d.ts +3 -9
- package/dist/Shell3dPresentation.js +26 -6
- package/dist/ShellHudPresentation.d.ts +9 -1
- package/dist/ShellHudPresentation.js +21 -0
- package/dist/audio/AudioComponents.js +6 -2
- package/dist/audio/audioEngine.d.ts +8 -0
- package/dist/audio/audioEngine.js +65 -6
- package/dist/audio/audioWire.d.ts +3 -0
- package/dist/audio/audioWire.js +7 -1
- package/dist/audio/loopParams.d.ts +39 -0
- package/dist/audio/loopParams.js +43 -0
- package/dist/camera/GameCameraRig.js +1 -1
- package/dist/camera/GameFirstPersonCamera.d.ts +4 -2
- package/dist/camera/GameFirstPersonCamera.js +52 -15
- package/dist/camera/Viewports.d.ts +34 -0
- package/dist/camera/Viewports.js +123 -0
- package/dist/camera/cameraRigs.js +70 -62
- package/dist/camera/orbitCameraMath.js +2 -2
- package/dist/camera/rigMath.d.ts +82 -4
- package/dist/camera/rigMath.js +133 -15
- package/dist/defineGame.d.ts +9 -0
- package/dist/defineGame.js +20 -3
- package/dist/devtools/DevtoolsOverlay.js +2 -0
- package/dist/devtools/agentBridge.d.ts +6 -0
- package/dist/devtools/agentBridge.js +15 -1
- package/dist/diagnostics/FpsCounter.d.ts +17 -0
- package/dist/diagnostics/FpsCounter.js +32 -0
- package/dist/drivers/FrameDriver.js +13 -2
- package/dist/drivers/FrameRateLimiter.d.ts +9 -0
- package/dist/drivers/FrameRateLimiter.js +39 -0
- package/dist/drivers/HudOnlyDriver.js +1 -1
- package/dist/environment/DayNightSky.js +5 -5
- package/dist/environment/Daylight.d.ts +35 -1
- package/dist/environment/Daylight.js +98 -28
- package/dist/environment/VolumetricClouds.js +20 -2
- package/dist/environment/daylightCycle.d.ts +20 -0
- package/dist/environment/daylightCycle.js +59 -7
- package/dist/environment/sunShadowMath.d.ts +45 -0
- package/dist/environment/sunShadowMath.js +50 -0
- package/dist/input/gamepadPoll.d.ts +47 -0
- package/dist/input/gamepadPoll.js +142 -0
- package/dist/input/gamepadSource.d.ts +11 -1
- package/dist/input/gamepadSource.js +117 -71
- package/dist/input/mouseLook.js +2 -1
- package/dist/input/padHaptics.d.ts +17 -0
- package/dist/input/padHaptics.js +32 -0
- package/dist/input/pointerLock.d.ts +11 -0
- package/dist/input/pointerLock.js +19 -0
- package/dist/postfx/PostProcessing.d.ts +1 -1
- package/dist/postfx/PostProcessing.js +5 -1
- package/dist/postfx/stylizePass.d.ts +23 -0
- package/dist/postfx/stylizePass.js +216 -0
- package/dist/render/SceneModels.js +1 -1
- package/dist/render/SkinnedInstances.d.ts +91 -0
- package/dist/render/SkinnedInstances.js +249 -0
- package/dist/render/TextureFiltering.d.ts +16 -0
- package/dist/render/TextureFiltering.js +64 -0
- package/dist/render/displayColor.d.ts +33 -0
- package/dist/render/displayColor.js +138 -0
- package/dist/render/modelLoad.d.ts +29 -0
- package/dist/render/modelLoad.js +61 -1
- package/dist/render/resolveModel.js +4 -0
- package/dist/render/useFootIk.d.ts +53 -4
- package/dist/render/useFootIk.js +197 -70
- package/dist/render/useModelAnimation.d.ts +24 -0
- package/dist/render/useModelAnimation.js +61 -40
- package/dist/scene/AuthoredScene.d.ts +9 -0
- package/dist/scene/AuthoredScene.js +17 -1
- package/dist/scene/CityRenderer.js +6 -6
- package/dist/settings/QuickControls.js +1 -1
- package/dist/settings/SettingsMenu.js +1 -1
- package/dist/settings/appliedSettings.d.ts +9 -3
- package/dist/settings/appliedSettings.js +6 -5
- package/dist/settings/settingsController.d.ts +3 -0
- package/dist/settings/settingsController.js +47 -3
- package/dist/structures/BuildingKitBatch.js +26 -22
- package/dist/structures/GeneratedBuilding.d.ts +3 -15
- package/dist/structures/GeneratedBuilding.js +21 -15
- package/dist/structures/buildingSurface.d.ts +21 -0
- package/dist/structures/buildingSurface.js +134 -0
- package/dist/useShellMultiplayerSync.d.ts +1 -1
- package/dist/useShellMultiplayerSync.js +5 -3
- package/dist/world/WorldParticles.js +1 -1
- package/package.json +4 -4
- package/skills/ce-handoff/SKILL.md +1 -1
- package/skills/game-design/SKILL.md +2 -2
- package/skills/jgengine/SKILL.md +7 -5
- package/skills/jgengine/api.md +118 -89
- package/skills/jgengine/capabilities.md +28 -0
- package/skills/jgengine/recipes/minimal-game.md +3 -0
- package/skills/jgengine-ui/SKILL.md +13 -16
- package/skills/jgengine-ui/api.md +418 -317
- package/skills/jgengine-ui/capabilities.md +16 -0
- package/skills/jgengine-ui/reference-primitives.md +89 -0
- package/skills/jgengine-ui/reference.md +2 -81
- package/skills/jgengine-world/SKILL.md +15 -3
- package/skills/jgengine-world/api.md +873 -626
- package/skills/jgengine-world/capabilities.md +130 -6
- package/skills/jgengine-world/recipes/character-animation.md +39 -0
- package/skills/jgengine-world/recipes/controls.md +44 -0
- package/skills/jgengine-world/recipes/flight-feel.md +76 -0
- package/skills/jgengine-world/recipes/npc-senses.md +43 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +85 -0
- package/skills/jgengine-world/reference.md +31 -2
- package/skills/jgengine-world/visibility.md +112 -0
- package/skills/level-design/SKILL.md +1 -1
- package/dist/render/testFixtures/proceduralSpriteAtlas.d.ts +0 -15
- package/dist/render/testFixtures/proceduralSpriteAtlas.js +0 -30
- package/dist/render/testFixtures/proceduralTileLayer.d.ts +0 -13
- package/dist/render/testFixtures/proceduralTileLayer.js +0 -30
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import * as THREE from "three";
|
|
3
|
+
const SURFACE_ROLES = ["color", "normal", "roughness", "ao", "metalness"];
|
|
4
|
+
/** True when the surface names at least one map URL, so the batch needs the textured path. */
|
|
5
|
+
export function surfaceHasMaps(surface) {
|
|
6
|
+
if (surface?.maps === undefined)
|
|
7
|
+
return false;
|
|
8
|
+
return SURFACE_ROLES.some((role) => surface.maps?.[role] !== undefined);
|
|
9
|
+
}
|
|
10
|
+
/** A stable key for bucketing instances that share one surface (same colour, maps, tiling, and response). */
|
|
11
|
+
export function surfaceKey(surface) {
|
|
12
|
+
if (surface === undefined)
|
|
13
|
+
return "";
|
|
14
|
+
const repeat = surface.repeat === undefined ? 1 : surface.repeat;
|
|
15
|
+
const maps = surface.maps === undefined ? "" : SURFACE_ROLES.map((role) => surface.maps?.[role] ?? "").join(",");
|
|
16
|
+
const tiling = typeof repeat === "number" ? String(repeat) : repeat.join("x");
|
|
17
|
+
return `${surface.color ?? ""}|${maps}|${tiling}|${surface.roughness ?? ""}|${surface.metalness ?? ""}`;
|
|
18
|
+
}
|
|
19
|
+
function repeatOf(surface) {
|
|
20
|
+
const repeat = surface.repeat ?? 1;
|
|
21
|
+
return typeof repeat === "number" ? [repeat, repeat] : [repeat[0], repeat[1]];
|
|
22
|
+
}
|
|
23
|
+
const textureLoader = new THREE.TextureLoader();
|
|
24
|
+
const loads = new Map();
|
|
25
|
+
/** One shared decode per URL; a missing file (a pack without `ao.jpg`) resolves to null instead of failing the batch. */
|
|
26
|
+
function loadSurfaceTexture(url) {
|
|
27
|
+
let pending = loads.get(url);
|
|
28
|
+
if (pending === undefined) {
|
|
29
|
+
pending = new Promise((resolve) => {
|
|
30
|
+
textureLoader.load(url, (texture) => resolve(texture), undefined, () => resolve(null));
|
|
31
|
+
});
|
|
32
|
+
loads.set(url, pending);
|
|
33
|
+
}
|
|
34
|
+
return pending;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Loads a surface's PBR maps and returns per-caller clones configured to tile `repeat` times per
|
|
38
|
+
* slot, or `undefined` until the first map arrives so the batch draws its flat colour meanwhile.
|
|
39
|
+
* Clones share the decoded image, so two kinds using one map at different repeats cost one
|
|
40
|
+
* download. Maps whose file is missing are skipped rather than failing the whole surface.
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
export function useBuildingSurfaceTextures(surface) {
|
|
44
|
+
const maps = surface?.maps;
|
|
45
|
+
const color = maps?.color;
|
|
46
|
+
const normal = maps?.normal;
|
|
47
|
+
const roughness = maps?.roughness;
|
|
48
|
+
const ao = maps?.ao;
|
|
49
|
+
const metalness = maps?.metalness;
|
|
50
|
+
const urls = useMemo(() => {
|
|
51
|
+
const record = {};
|
|
52
|
+
if (color !== undefined)
|
|
53
|
+
record.color = color;
|
|
54
|
+
if (normal !== undefined)
|
|
55
|
+
record.normal = normal;
|
|
56
|
+
if (roughness !== undefined)
|
|
57
|
+
record.roughness = roughness;
|
|
58
|
+
if (ao !== undefined)
|
|
59
|
+
record.ao = ao;
|
|
60
|
+
if (metalness !== undefined)
|
|
61
|
+
record.metalness = metalness;
|
|
62
|
+
return record;
|
|
63
|
+
}, [color, normal, roughness, ao, metalness]);
|
|
64
|
+
const [loaded, setLoaded] = useState(undefined);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const entries = Object.entries(urls);
|
|
67
|
+
if (entries.length === 0) {
|
|
68
|
+
setLoaded(undefined);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
let cancelled = false;
|
|
72
|
+
Promise.all(entries.map(([role, url]) => loadSurfaceTexture(url).then((texture) => [role, texture]))).then((results) => {
|
|
73
|
+
if (cancelled)
|
|
74
|
+
return;
|
|
75
|
+
const record = {};
|
|
76
|
+
for (const [role, texture] of results)
|
|
77
|
+
if (texture !== null)
|
|
78
|
+
record[role] = texture;
|
|
79
|
+
setLoaded(Object.keys(record).length === 0 ? undefined : record);
|
|
80
|
+
});
|
|
81
|
+
return () => {
|
|
82
|
+
cancelled = true;
|
|
83
|
+
};
|
|
84
|
+
}, [urls]);
|
|
85
|
+
const [repeatX, repeatY] = surface === undefined ? [1, 1] : repeatOf(surface);
|
|
86
|
+
const textures = useMemo(() => {
|
|
87
|
+
if (loaded === undefined)
|
|
88
|
+
return undefined;
|
|
89
|
+
const out = {};
|
|
90
|
+
for (const role of SURFACE_ROLES) {
|
|
91
|
+
const source = loaded[role];
|
|
92
|
+
if (source === undefined)
|
|
93
|
+
continue;
|
|
94
|
+
const texture = source.clone();
|
|
95
|
+
texture.wrapS = THREE.RepeatWrapping;
|
|
96
|
+
texture.wrapT = THREE.RepeatWrapping;
|
|
97
|
+
texture.repeat.set(repeatX, repeatY);
|
|
98
|
+
texture.colorSpace = role === "color" ? THREE.SRGBColorSpace : THREE.NoColorSpace;
|
|
99
|
+
texture.needsUpdate = true;
|
|
100
|
+
out[role] = texture;
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}, [loaded, repeatX, repeatY]);
|
|
104
|
+
useEffect(() => () => {
|
|
105
|
+
if (textures === undefined)
|
|
106
|
+
return;
|
|
107
|
+
for (const role of SURFACE_ROLES)
|
|
108
|
+
textures[role]?.dispose();
|
|
109
|
+
}, [textures]);
|
|
110
|
+
return textures;
|
|
111
|
+
}
|
|
112
|
+
/** Applies a surface's colour, response, and loaded maps onto a standard or physical material in place. @internal */
|
|
113
|
+
export function applyBuildingSurface(material, surface, textures) {
|
|
114
|
+
if (surface.color !== undefined)
|
|
115
|
+
material.color.set(surface.color);
|
|
116
|
+
if (surface.roughness !== undefined)
|
|
117
|
+
material.roughness = surface.roughness;
|
|
118
|
+
if (surface.metalness !== undefined)
|
|
119
|
+
material.metalness = surface.metalness;
|
|
120
|
+
if (textures !== undefined) {
|
|
121
|
+
if (textures.color !== undefined)
|
|
122
|
+
material.map = textures.color;
|
|
123
|
+
if (textures.normal !== undefined)
|
|
124
|
+
material.normalMap = textures.normal;
|
|
125
|
+
if (textures.roughness !== undefined)
|
|
126
|
+
material.roughnessMap = textures.roughness;
|
|
127
|
+
if (textures.ao !== undefined)
|
|
128
|
+
material.aoMap = textures.ao;
|
|
129
|
+
if (textures.metalness !== undefined)
|
|
130
|
+
material.metalnessMap = textures.metalness;
|
|
131
|
+
material.needsUpdate = true;
|
|
132
|
+
}
|
|
133
|
+
return material;
|
|
134
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Dispatch, type SetStateAction } from "react";
|
|
2
2
|
import type { GameContext } from "@jgengine/core/runtime/gameContext";
|
|
3
|
-
import type
|
|
3
|
+
import { type PresencePoseRow } from "@jgengine/core/runtime/transport";
|
|
4
4
|
import { type AuthoritativeFrameHandler } from "./worldSync.js";
|
|
5
5
|
import type { ShellMultiplayer } from "./multiplayer.js";
|
|
6
6
|
import type { PlayableGame } from "./registry.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useState, useCallback } from "react";
|
|
2
2
|
import { isServerAuthoritative } from "@jgengine/core/runtime/adapter";
|
|
3
|
+
import { createTransportSessionId } from "@jgengine/core/runtime/transport";
|
|
3
4
|
import { attachWorldSync } from "./worldSync.js";
|
|
4
5
|
/** Joins the multiplayer server for the live context and wires presence, feed relay, and chat sync until teardown. */
|
|
5
6
|
export function useShellMultiplayerSync(ctx, multiplayer, playable, serverIdRef, setRemotePlayers, authoritativeFrameRef) {
|
|
@@ -14,8 +15,9 @@ export function useShellMultiplayerSync(ctx, multiplayer, playable, serverIdRef,
|
|
|
14
15
|
setFailureReason(null);
|
|
15
16
|
let disposed = false;
|
|
16
17
|
const cleanups = [];
|
|
18
|
+
const sessionId = createTransportSessionId();
|
|
17
19
|
void multiplayer.backend.transport
|
|
18
|
-
.joinServer({ gameId: multiplayer.gameId })
|
|
20
|
+
.joinServer({ gameId: multiplayer.gameId, sessionId })
|
|
19
21
|
.then((joined) => {
|
|
20
22
|
if (!joined.ok) {
|
|
21
23
|
if (!disposed) {
|
|
@@ -25,7 +27,7 @@ export function useShellMultiplayerSync(ctx, multiplayer, playable, serverIdRef,
|
|
|
25
27
|
return;
|
|
26
28
|
}
|
|
27
29
|
if (disposed) {
|
|
28
|
-
void multiplayer.backend.transport.leaveServer({ serverId: joined.serverId });
|
|
30
|
+
void multiplayer.backend.transport.leaveServer({ serverId: joined.serverId, sessionId }).catch(() => undefined);
|
|
29
31
|
return;
|
|
30
32
|
}
|
|
31
33
|
serverIdRef.current = joined.serverId;
|
|
@@ -110,7 +112,7 @@ export function useShellMultiplayerSync(ctx, multiplayer, playable, serverIdRef,
|
|
|
110
112
|
serverIdRef.current = null;
|
|
111
113
|
setRemotePlayers([]);
|
|
112
114
|
if (serverId !== null) {
|
|
113
|
-
void multiplayer.backend.transport.leaveServer({ serverId }).catch(() => undefined);
|
|
115
|
+
void multiplayer.backend.transport.leaveServer({ serverId, sessionId }).catch(() => undefined);
|
|
114
116
|
}
|
|
115
117
|
};
|
|
116
118
|
}, [ctx, multiplayer, playable, attempt]);
|
|
@@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from "react";
|
|
|
4
4
|
import { createParticleSystem } from "@jgengine/core/vfx/particles";
|
|
5
5
|
import { useGameContext } from "@jgengine/react/provider";
|
|
6
6
|
import { ParticleField } from "../vfx/ParticleField.js";
|
|
7
|
-
const QUALITY_POOL_CAP = { low: 128, medium: 256, high: 512 };
|
|
7
|
+
const QUALITY_POOL_CAP = { low: 128, medium: 256, high: 512, ultra: 1024 };
|
|
8
8
|
/**
|
|
9
9
|
* The per-effect particle pool a graphics tier allows: the requested `max`
|
|
10
10
|
* clamped to the tier cap, so lower tiers degrade density instead of dropping
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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.19.0",
|
|
39
|
+
"@jgengine/react": "^0.19.0",
|
|
40
|
+
"@jgengine/ws": "^0.19.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@react-three/drei": "^10.0.0",
|
|
@@ -143,7 +143,7 @@ When sufficient, return a concise orientation: objective, progress, decisions, c
|
|
|
143
143
|
|
|
144
144
|
## JGengine notes
|
|
145
145
|
|
|
146
|
-
- Multi-session engine work
|
|
146
|
+
- Multi-session engine work spans GitHub issues and PRs — point at those; do not paste full plan bodies.
|
|
147
147
|
- Prefer `bun run agent:bootstrap --check` status, branch names, and PR links as state anchors.
|
|
148
148
|
- Local parallelism uses `.claude/worktrees/`; never invent `C:\tmp\…` Codex-shared trees.
|
|
149
149
|
- Durable process changes belong in skills, not only the handoff.
|
|
@@ -33,8 +33,8 @@ An audit is not completion for a build/improve request. Finish when the slice is
|
|
|
33
33
|
|
|
34
34
|
Before content, write a first-hour balance sheet: starting cash, time to first income, net income per hour at starting hardware and at ten times that hardware, bill cadence versus income, and the insolvency recovery path. Insolvency resets the affected player or changes their options; it never deletes the world. Express clicks and event rewards as a fraction of the designed income rate, never an unrelated flat payout.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Currency precision, elapsed-time anchoring, and reset mechanics are implemented per `jgengine-gameplay` "Persistent economies and input"; test fractional income over real tick cadences before tuning prices.
|
|
37
37
|
|
|
38
38
|
The first-60-seconds gate requires the first tutorial verb to be executable from spawn with starting inventory. Grant dependencies such as land, power, and slots or let players buy them in-context; the tutorial cannot lock the surface needed to complete itself. Placement previews show cost and affordability inline.
|
|
39
39
|
|
|
40
|
-
For a persistent shared-world builder,
|
|
40
|
+
For a persistent shared-world builder, verify capacity and first-player onboarding before scaling the content catalog; presence, command read scopes, territory, chat, and join retry UI come from their owners in `jgengine-world`, `jgengine-ui`, and `jgengine-multiplayer`.
|
package/skills/jgengine/SKILL.md
CHANGED
|
@@ -5,7 +5,9 @@ description: Route game work to the smallest useful JGengine guidance set.
|
|
|
5
5
|
|
|
6
6
|
# JGengine intake and router
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
This skill decides what to load; it is not an engine manual. Package map, commands, layering, and license live in the JGengine README (repo-root [`README.md`](../../../README.md) in the monorepo, [jgengine.com](https://jgengine.com) from a created project); open it when you need one of those facts.
|
|
9
|
+
|
|
10
|
+
A created project installs only `jgengine`, `jgengine-editor`, `jgengine-verify`, `game-design`, and `jgengine-ui`. Run `npx jgengine skills -p --all` before loading any other domain named below. Those projects do not ship `api.md`; read exact signatures from the `.d.ts` files under `node_modules/@jgengine/<pkg>/dist/`.
|
|
9
11
|
|
|
10
12
|
## 1. Establish the target
|
|
11
13
|
|
|
@@ -20,7 +22,7 @@ Treat the pitch as a unique composition of needs, not a genre to fill in — nev
|
|
|
20
22
|
- HUD/menu states and **custom** UI art direction (compose shipped building blocks + HudTheme; no unskinned stock face)
|
|
21
23
|
- one observable completion scenario
|
|
22
24
|
|
|
23
|
-
For a new in-repo game, scaffold with `bun run new:game "<id-or-title>"` (thin script alias for `jgengine create`, auto-detects the in-repo variant). Outside this monorepo, start with `npx jgengine create`; never copy `Games/*`.
|
|
25
|
+
Pick the dimension first: `create` defaults to a 3D world (terrain, sky, player model, editor scene); a grid, card, tile, or other board game with no 3D scene takes `create --2d` (a `board` world drawn by GameUI over `@jgengine/core/puzzle/cellGrid`). For a new in-repo game, scaffold with `bun run new:game "<id-or-title>"` (thin script alias for `jgengine create`, auto-detects the in-repo variant). Outside this monorepo, start with `npx jgengine create`; never copy `Games/*`.
|
|
24
26
|
|
|
25
27
|
For an existing game asked to pick up a new engine release ("jgengine published a new version, grab it"), run `npx jgengine upgrade` in the project first: it diffs installed `@jgengine/*` versions against the latest release and prints every Migrate step and Adopt-worthy addition in between (`--json` for structured output). Bump, migrate oldest-first, then work the Adopt lists instead of reconstructing the changelog by hand.
|
|
26
28
|
|
|
@@ -46,9 +48,9 @@ World content routes editor-first: any request that adds, moves, restyles, or re
|
|
|
46
48
|
|
|
47
49
|
Within each selected domain:
|
|
48
50
|
|
|
49
|
-
0. Fastest start for a common composition: `npx jgengine recipe <name>` prints a vetted, SDK-typechecked snippet (imports + wired code)
|
|
50
|
-
1. Search
|
|
51
|
-
2. Open `api.md` only when exact signatures
|
|
51
|
+
0. Fastest start for a common composition: `npx jgengine recipe <name>` prints a vetted, SDK-typechecked snippet (imports + wired code) (`combat-loop`, `loot`, `world-drops`, `click-target`, `ability-bar`, `hitscan-weapon`, `rolled-gear`, `enter-vehicle` and more); `npx jgengine recipe` lists them. Copy that instead of reconning per-file.
|
|
52
|
+
1. Search capabilities by intent; do not read `capabilities.md` top to bottom. `npx jgengine find <intent>` searches every domain's index and every recipe at once, installed or not, and prints the primitive and its import (or the recipe to open); in the monorepo, `grep -i "<intent>" .claude/skills/*/capabilities.md` does the same. Search the moment you are tempted to hand-roll a HUD window, inventory grid, paperdoll, hotkey listener, minimap, stat bar, or character motion. A `z-index`, a `keydown` for a panel, a `<div>` inventory, or a limb bob almost certainly has a shipped drop-in (`usePanels`/`PanelHost`, `InventoryGrid`, `CharacterSheet`/`Paperdoll`, `PartMotionRig`, `EntityPreview`); games own the *skin*, not the re-derivation.
|
|
53
|
+
2. Open `api.md` (monorepo) or the package `.d.ts` only when exact signatures are needed; grep it, never read it whole.
|
|
52
54
|
3. Open the linked reference only for a deeper recipe or trap.
|
|
53
55
|
4. For how several primitives wire into a running loop, read the domain's `recipes/` — connected, genre-free walkthroughs organized by composition seam; this skill's own [recipes/minimal-game.md](recipes/minimal-game.md) is the default whole-game walkthrough. Prefer a recipe over reading a game's source; the games under `Games/*` are not templates or references, and a hybrid game is just a different composition of the same primitives.
|
|
54
56
|
|