@jgengine/core 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/ai/decisionGraph.d.ts +48 -4
- package/dist/ai/decisionGraph.js +153 -23
- package/dist/ai/groupAssist.d.ts +10 -0
- package/dist/ai/groupAssist.js +11 -0
- package/dist/ai/pursuit.d.ts +1 -1
- package/dist/ai/pursuit.js +1 -1
- package/dist/ai/threat.d.ts +8 -0
- package/dist/ai/threat.js +10 -0
- package/dist/anim/animGraph.d.ts +11 -0
- package/dist/anim/animGraph.js +128 -0
- package/dist/anim/boneTexture.d.ts +55 -0
- package/dist/anim/boneTexture.js +43 -0
- package/dist/anim/footPlacement.d.ts +57 -0
- package/dist/anim/footPlacement.js +67 -0
- package/dist/anim/locomotionGraph.d.ts +9 -0
- package/dist/anim/locomotionGraph.js +29 -0
- package/dist/audio/audioFalloff.d.ts +2 -0
- package/dist/audio/doppler.d.ts +21 -0
- package/dist/audio/doppler.js +32 -0
- package/dist/audio/engineLayers.d.ts +83 -0
- package/dist/audio/engineLayers.js +115 -0
- package/dist/combat/abilityKit.d.ts +1 -0
- package/dist/combat/abilityKit.js +1 -0
- package/dist/combat/death.d.ts +7 -0
- package/dist/combat/shotOrigin.d.ts +6 -0
- package/dist/combat/shotOrigin.js +6 -0
- package/dist/combat/weaponFeedback.d.ts +36 -0
- package/dist/combat/weaponFeedback.js +42 -0
- package/dist/combat/weaponHandling.d.ts +161 -0
- package/dist/combat/weaponHandling.js +202 -0
- package/dist/combat/weaponPresentation.d.ts +115 -0
- package/dist/combat/weaponPresentation.js +96 -0
- package/dist/devtools/warnOnce.d.ts +8 -0
- package/dist/devtools/warnOnce.js +18 -0
- package/dist/editor/document.js +3 -1
- package/dist/editor/environment.js +8 -0
- package/dist/editor/types.d.ts +4 -0
- package/dist/faction/reputation.d.ts +6 -0
- package/dist/faction/reputation.js +11 -0
- package/dist/game/cameraConfig.d.ts +51 -1
- package/dist/game/events.d.ts +6 -0
- package/dist/game/lootTable.d.ts +4 -0
- package/dist/game/lootTable.js +16 -0
- package/dist/game/playableGame.d.ts +84 -17
- package/dist/game/toasts.d.ts +8 -0
- package/dist/game/toasts.js +7 -0
- package/dist/game/vfxInstance.d.ts +9 -0
- package/dist/game/vfxInstance.js +12 -1
- package/dist/game/viewports.d.ts +43 -0
- package/dist/game/viewports.js +46 -0
- package/dist/game/worldItem.d.ts +5 -1
- package/dist/game/worldItem.js +0 -1
- package/dist/input/actionBindings.d.ts +15 -1
- package/dist/input/actionBindings.js +14 -3
- package/dist/input/actionContexts.d.ts +25 -1
- package/dist/input/actionContexts.js +82 -25
- package/dist/input/axisInput.d.ts +4 -0
- package/dist/input/axisInput.js +4 -0
- package/dist/input/axisShaper.d.ts +73 -0
- package/dist/input/axisShaper.js +105 -0
- package/dist/input/gamepadModel.d.ts +36 -2
- package/dist/input/gamepadModel.js +69 -30
- package/dist/input/haptics.d.ts +50 -0
- package/dist/input/haptics.js +74 -0
- package/dist/input/inputBuffer.d.ts +40 -0
- package/dist/input/inputBuffer.js +59 -0
- package/dist/input/lookChannel.d.ts +10 -0
- package/dist/input/lookChannel.js +6 -0
- package/dist/input/pointer.d.ts +2 -0
- package/dist/input/pointer.js +4 -0
- package/dist/item/affix.d.ts +6 -0
- package/dist/item/affix.js +6 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +112 -1
- package/dist/movement/glideModel.d.ts +3 -0
- package/dist/movement/glideModel.js +6 -0
- package/dist/movement/grappleSwing.d.ts +2 -0
- package/dist/movement/grappleSwing.js +5 -1
- package/dist/movement/movementModel.d.ts +39 -0
- package/dist/movement/movementModel.js +62 -19
- package/dist/movement/movementProbe.d.ts +51 -0
- package/dist/movement/movementProbe.js +119 -0
- package/dist/movement/playerMovement.d.ts +21 -2
- package/dist/movement/playerMovement.js +89 -6
- package/dist/movement/solidObstacles.d.ts +9 -1
- package/dist/movement/solidObstacles.js +44 -2
- package/dist/movement/voxelController.js +7 -7
- package/dist/nav/navFromEnvironment.d.ts +9 -3
- package/dist/nav/navFromEnvironment.js +18 -12
- package/dist/nav/navMesh.d.ts +43 -2
- package/dist/nav/navMesh.js +534 -128
- package/dist/physics/aircraftDynamics.d.ts +390 -0
- package/dist/physics/aircraftDynamics.js +704 -0
- package/dist/physics/boatDynamics.d.ts +116 -0
- package/dist/physics/boatDynamics.js +160 -0
- package/dist/physics/drivableVehicle.d.ts +22 -8
- package/dist/physics/drivableVehicle.js +4 -2
- package/dist/physics/flightDynamics.d.ts +21 -4
- package/dist/physics/flightDynamics.js +28 -5
- package/dist/physics/handlingProbe.d.ts +249 -0
- package/dist/physics/handlingProbe.js +606 -0
- package/dist/physics/kinematicVehicle.d.ts +32 -2
- package/dist/physics/kinematicVehicle.js +15 -0
- package/dist/physics/physicsBackend.d.ts +12 -0
- package/dist/physics/physicsBackendConformance.js +34 -0
- package/dist/physics/physicsWorldBackend.d.ts +1 -0
- package/dist/physics/physicsWorldBackend.js +33 -0
- package/dist/physics/vehicleBackendLink.d.ts +49 -0
- package/dist/physics/vehicleBackendLink.js +102 -0
- package/dist/physics/vehicleDynamics.d.ts +374 -0
- package/dist/physics/vehicleDynamics.js +737 -0
- package/dist/physics/worldColliders.d.ts +1 -1
- package/dist/physics/worldColliders.js +35 -3
- package/dist/puzzle/cellGrid.d.ts +15 -9
- package/dist/puzzle/cellGrid.js +13 -9
- package/dist/render/lookPreset.d.ts +9 -2
- package/dist/render/lookPreset.js +29 -2
- package/dist/render/postProcessing.d.ts +30 -0
- package/dist/render/postProcessing.js +1 -1
- package/dist/runtime/cameraDirector.d.ts +17 -3
- package/dist/runtime/cameraDirector.js +25 -0
- package/dist/runtime/commandInput.d.ts +37 -0
- package/dist/runtime/commandInput.js +58 -0
- package/dist/runtime/commandRunner.d.ts +27 -0
- package/dist/runtime/commandRunner.js +31 -3
- package/dist/runtime/context/combat.d.ts +1 -0
- package/dist/runtime/context/combat.js +2 -1
- package/dist/runtime/context/deathLoot.d.ts +2 -0
- package/dist/runtime/context/deathLoot.js +1 -0
- package/dist/runtime/gameContext.js +7 -0
- package/dist/runtime/gameContextTypes.d.ts +19 -3
- package/dist/runtime/gameRuntime.d.ts +3 -1
- package/dist/runtime/gameRuntime.js +4 -0
- package/dist/runtime/inputSnapshot.d.ts +6 -0
- package/dist/runtime/inputSnapshot.js +10 -0
- package/dist/runtime/localPlayers.d.ts +73 -0
- package/dist/runtime/localPlayers.js +119 -0
- package/dist/runtime/transport.d.ts +9 -0
- package/dist/runtime/transport.js +7 -1
- package/dist/scene/behaviorRuntime.d.ts +12 -2
- package/dist/scene/behaviorRuntime.js +30 -14
- package/dist/scene/behaviors.d.ts +5 -1
- package/dist/scene/sceneKinds.d.ts +9 -0
- package/dist/scene/vehicleSeat.d.ts +5 -1
- package/dist/scene/vehicleSeat.js +5 -1
- package/dist/sensor/concealment.d.ts +6 -0
- package/dist/sensor/concealment.js +8 -0
- package/dist/sensor/freezeMonitor.d.ts +6 -0
- package/dist/sensor/freezeMonitor.js +8 -0
- package/dist/sensor/perception.d.ts +11 -1
- package/dist/sensor/perception.js +57 -10
- package/dist/sensor/recordingBuffer.d.ts +3 -0
- package/dist/sensor/recordingBuffer.js +8 -0
- package/dist/session/roundState.d.ts +4 -0
- package/dist/session/roundState.js +11 -0
- package/dist/settings/frameRateLimit.d.ts +16 -0
- package/dist/settings/frameRateLimit.js +37 -0
- package/dist/settings/graphicsProfile.d.ts +2 -0
- package/dist/settings/graphicsProfile.js +12 -0
- package/dist/settings/graphicsSettings.d.ts +40 -0
- package/dist/settings/graphicsSettings.js +81 -0
- package/dist/settings/settingsModel.d.ts +9 -1
- package/dist/settings/settingsModel.js +10 -0
- package/dist/stats/eventMeter.d.ts +1 -1
- package/dist/stats/eventMeter.js +1 -1
- package/dist/vfx/feedbackMixer.d.ts +61 -0
- package/dist/vfx/feedbackMixer.js +106 -0
- package/dist/visibility/simulationCulling.d.ts +8 -1
- package/dist/visibility/simulationCulling.js +7 -0
- package/dist/visibility/spatialIndex.d.ts +21 -0
- package/dist/visibility/spatialIndex.js +16 -0
- package/dist/world/authoredSolids.d.ts +46 -0
- package/dist/world/authoredSolids.js +75 -0
- package/dist/world/buildingGenerator.js +2 -2
- package/dist/world/buildingKit.d.ts +3 -1
- package/dist/world/buildings.d.ts +34 -2
- package/dist/world/buildings.js +11 -1
- package/dist/world/cityKind.d.ts +7 -0
- package/dist/world/cityKind.js +52 -1
- package/dist/world/features.d.ts +29 -2
- package/dist/world/features.js +6 -0
- package/dist/world/footprintGrid.d.ts +7 -0
- package/dist/world/footprintGrid.js +16 -0
- package/dist/world/interiors.d.ts +1 -0
- package/dist/world/interiors.js +5 -3
- package/dist/world/lod.d.ts +6 -0
- package/dist/world/lod.js +8 -0
- package/dist/world/terraform.d.ts +8 -0
- package/dist/world/terraform.js +7 -0
- package/dist/world/walls.d.ts +7 -0
- package/dist/world/walls.js +7 -0
- package/dist/world/worldSolids.d.ts +84 -0
- package/dist/world/worldSolids.js +240 -0
- package/dist/world.d.ts +2 -2
- package/dist/world.js +1 -1
- package/package.json +1 -1
- 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-combat/SKILL.md +5 -0
- package/skills/jgengine-combat/api.md +138 -103
- package/skills/jgengine-combat/capabilities.md +29 -1
- package/skills/jgengine-combat/recipes/weapon-feel.md +50 -0
- package/skills/jgengine-gameplay/api.md +462 -436
- package/skills/jgengine-gameplay/capabilities.md +24 -0
- package/skills/jgengine-gameplay/reference-systems.md +4 -0
- 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
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
function cellKey(cell) {
|
|
2
2
|
return `${cell.col}:${cell.row}`;
|
|
3
3
|
}
|
|
4
|
+
function copyReservation(reservation) {
|
|
5
|
+
return { id: reservation.id, kind: reservation.kind, cells: reservation.cells.map((cell) => ({ col: cell.col, row: cell.row })) };
|
|
6
|
+
}
|
|
4
7
|
/**
|
|
5
8
|
* Multi-cell footprint occupancy/reservation on a shared build grid — `world/placementController`
|
|
6
9
|
* only owns the ghost preview; this is the persistent claim a committed placement holds so the next
|
|
@@ -70,6 +73,19 @@ export function createFootprintGrid(options = {}) {
|
|
|
70
73
|
occupied.clear();
|
|
71
74
|
reservations.clear();
|
|
72
75
|
},
|
|
76
|
+
snapshot() {
|
|
77
|
+
return { reservations: Array.from(reservations.values(), copyReservation) };
|
|
78
|
+
},
|
|
79
|
+
restore(next) {
|
|
80
|
+
occupied.clear();
|
|
81
|
+
reservations.clear();
|
|
82
|
+
for (const reservation of next.reservations) {
|
|
83
|
+
const copy = copyReservation(reservation);
|
|
84
|
+
reservations.set(copy.id, copy);
|
|
85
|
+
for (const cell of copy.cells)
|
|
86
|
+
occupied.set(cellKey(cell), copy.id);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
73
89
|
};
|
|
74
90
|
}
|
|
75
91
|
const CARDINAL_OFFSETS = [
|
|
@@ -32,4 +32,5 @@ export interface Interiors {
|
|
|
32
32
|
toInterior(id: string, exterior: Vec2): Vec2 | null;
|
|
33
33
|
toExterior(id: string, interior: Vec2): Vec2 | null;
|
|
34
34
|
}
|
|
35
|
+
/** Frame conversion and bounded movement across an exterior and its interiors; holds config only, no play state. */
|
|
35
36
|
export declare function createInteriors(config?: InteriorsConfig): Interiors;
|
package/dist/world/interiors.js
CHANGED
|
@@ -14,12 +14,14 @@ function toExteriorFrame(interior, local) {
|
|
|
14
14
|
const rotated = rotate(local, interior.rotation ?? 0);
|
|
15
15
|
return [rotated[0] + interior.origin[0], rotated[1] + interior.origin[1]];
|
|
16
16
|
}
|
|
17
|
+
function indexInteriors(interiors) {
|
|
18
|
+
return new Map(interiors.map((interior) => [interior.id, interior]));
|
|
19
|
+
}
|
|
20
|
+
/** Frame conversion and bounded movement across an exterior and its interiors; holds config only, no play state. */
|
|
17
21
|
export function createInteriors(config = {}) {
|
|
18
22
|
const radius = config.radius ?? 0;
|
|
19
23
|
const exterior = config.exterior ?? {};
|
|
20
|
-
const byId =
|
|
21
|
-
for (const interior of config.interiors ?? [])
|
|
22
|
-
byId.set(interior.id, interior);
|
|
24
|
+
const byId = indexInteriors(config.interiors ?? []);
|
|
23
25
|
return {
|
|
24
26
|
move(location, delta) {
|
|
25
27
|
if (location.space.kind === "exterior") {
|
package/dist/world/lod.d.ts
CHANGED
|
@@ -7,11 +7,17 @@ export interface LodSchedulerConfig {
|
|
|
7
7
|
beyondInterval?: number | null;
|
|
8
8
|
stagger?: boolean;
|
|
9
9
|
}
|
|
10
|
+
/** Plain JSON state of a {@link LodScheduler}: each id's accumulated seconds. */
|
|
11
|
+
export interface LodSchedulerState {
|
|
12
|
+
buckets: Record<string, number>;
|
|
13
|
+
}
|
|
10
14
|
export interface LodScheduler {
|
|
11
15
|
bandIndex(distance: number): number;
|
|
12
16
|
step(id: string, distance: number, dtSeconds: number): number;
|
|
13
17
|
remove(id: string): void;
|
|
14
18
|
clear(): void;
|
|
15
19
|
size(): number;
|
|
20
|
+
snapshot(): LodSchedulerState;
|
|
21
|
+
restore(next: LodSchedulerState): void;
|
|
16
22
|
}
|
|
17
23
|
export declare function createLodScheduler(config: LodSchedulerConfig): LodScheduler;
|
package/dist/world/lod.js
CHANGED
|
@@ -62,5 +62,13 @@ export function createLodScheduler(config) {
|
|
|
62
62
|
size() {
|
|
63
63
|
return buckets.size;
|
|
64
64
|
},
|
|
65
|
+
snapshot() {
|
|
66
|
+
return { buckets: Object.fromEntries(buckets) };
|
|
67
|
+
},
|
|
68
|
+
restore(next) {
|
|
69
|
+
buckets.clear();
|
|
70
|
+
for (const [id, seconds] of Object.entries(next.buckets))
|
|
71
|
+
buckets.set(id, seconds);
|
|
72
|
+
},
|
|
65
73
|
};
|
|
66
74
|
}
|
|
@@ -262,5 +262,13 @@ export interface TerraformBrush {
|
|
|
262
262
|
config(): Required<Omit<TerraformBrushConfig, "surface">> & {
|
|
263
263
|
surface: string | undefined;
|
|
264
264
|
};
|
|
265
|
+
/** The retunable brush settings; the terrain keeps its own `snapshot()`. */
|
|
266
|
+
snapshot(): TerraformBrushState;
|
|
267
|
+
restore(next: TerraformBrushState): void;
|
|
268
|
+
}
|
|
269
|
+
/** Plain JSON state of a {@link TerraformBrush}. */
|
|
270
|
+
export interface TerraformBrushState {
|
|
271
|
+
radius: number;
|
|
272
|
+
strength: number;
|
|
265
273
|
}
|
|
266
274
|
export declare function createTerraformBrush(terrain: Pick<EditableTerrain, "apply">, config?: TerraformBrushConfig): TerraformBrush;
|
package/dist/world/terraform.js
CHANGED
|
@@ -787,5 +787,12 @@ export function createTerraformBrush(terrain, config = {}) {
|
|
|
787
787
|
config() {
|
|
788
788
|
return { radius, strength, falloff, surface };
|
|
789
789
|
},
|
|
790
|
+
snapshot() {
|
|
791
|
+
return { radius, strength };
|
|
792
|
+
},
|
|
793
|
+
restore(next) {
|
|
794
|
+
radius = next.radius;
|
|
795
|
+
strength = next.strength;
|
|
796
|
+
},
|
|
790
797
|
};
|
|
791
798
|
}
|
package/dist/world/walls.d.ts
CHANGED
|
@@ -50,6 +50,11 @@ export interface SurfacePaintStore {
|
|
|
50
50
|
}
|
|
51
51
|
/** @internal */
|
|
52
52
|
export declare function createSurfacePaint(): SurfacePaintStore;
|
|
53
|
+
/** Plain JSON state of a {@link WallDrawTool}: the drawn points and whether the loop is closed. */
|
|
54
|
+
export interface WallDrawToolState {
|
|
55
|
+
points: Vec2[];
|
|
56
|
+
closed: boolean;
|
|
57
|
+
}
|
|
53
58
|
export interface WallDrawTool {
|
|
54
59
|
addPoint(point: Vec2, snap?: number): Vec2;
|
|
55
60
|
undo(): void;
|
|
@@ -60,6 +65,8 @@ export interface WallDrawTool {
|
|
|
60
65
|
isClosed(): boolean;
|
|
61
66
|
footprint(): EnclosedFootprint | null;
|
|
62
67
|
roof(config?: RoofConfig): RoofPlan | null;
|
|
68
|
+
snapshot(): WallDrawToolState;
|
|
69
|
+
restore(next: WallDrawToolState): void;
|
|
63
70
|
}
|
|
64
71
|
/** @internal */
|
|
65
72
|
export declare function createWallDrawTool(config?: {
|
package/dist/world/walls.js
CHANGED
|
@@ -220,6 +220,13 @@ export function createWallDrawTool(config = {}) {
|
|
|
220
220
|
const fp = this.footprint();
|
|
221
221
|
return fp === null ? null : autoRoof(fp, roofConfig);
|
|
222
222
|
},
|
|
223
|
+
snapshot() {
|
|
224
|
+
return { points: pts.map((point) => [point[0], point[1]]), closed };
|
|
225
|
+
},
|
|
226
|
+
restore(next) {
|
|
227
|
+
pts = next.points.map((point) => [point[0], point[1]]);
|
|
228
|
+
closed = next.closed;
|
|
229
|
+
},
|
|
223
230
|
};
|
|
224
231
|
}
|
|
225
232
|
/** @internal */
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { GeneratedBuilding } from "./buildings.js";
|
|
2
|
+
import type { EnvironmentWorldFeature } from "./features.js";
|
|
3
|
+
import type { Aabb } from "./geometry.js";
|
|
4
|
+
import type { WallSegment } from "./walls.js";
|
|
5
|
+
/** One solid box in world space: the collision half of generated or authored world geometry. */
|
|
6
|
+
export interface WorldSolid {
|
|
7
|
+
center: readonly [number, number, number];
|
|
8
|
+
halfExtents: readonly [number, number, number];
|
|
9
|
+
/** Yaw about `center`, radians, in the engine's `rotationY` convention. Default `0`. */
|
|
10
|
+
rotationY?: number;
|
|
11
|
+
}
|
|
12
|
+
/** Serializable contents of a {@link WorldSolids}: every layer's solids by layer id. */
|
|
13
|
+
export interface WorldSolidsState {
|
|
14
|
+
layers: Record<string, readonly WorldSolid[]>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Static collision for world geometry that is not a scene object: generated buildings, studio
|
|
18
|
+
* volumes like `city`, wall runs. Solids live in named layers so a feature can replace or drop its
|
|
19
|
+
* own set without touching the rest. `solidObstaclesNear` (player and NPC movement),
|
|
20
|
+
* `syncWorldColliders` (physics backends) and `populateNavGridFromSolids` all read this one store.
|
|
21
|
+
*/
|
|
22
|
+
export interface WorldSolids {
|
|
23
|
+
/** Replace a layer's solids; an empty list removes the layer. */
|
|
24
|
+
set(layer: string, solids: readonly WorldSolid[]): void;
|
|
25
|
+
remove(layer: string): void;
|
|
26
|
+
/** Run several `set`/`remove` calls as one change: one index rebuild, one notification. */
|
|
27
|
+
batch(apply: () => void): void;
|
|
28
|
+
/** Solids in one layer, empty when absent. */
|
|
29
|
+
layer(layer: string): readonly WorldSolid[];
|
|
30
|
+
layers(): readonly string[];
|
|
31
|
+
/** Every solid whose world AABB overlaps `min`..`max`. Bounded by a uniform XZ hash grid. */
|
|
32
|
+
inBox(min: readonly [number, number, number], max: readonly [number, number, number]): WorldSolid[];
|
|
33
|
+
/** Every solid in every layer. */
|
|
34
|
+
all(): WorldSolid[];
|
|
35
|
+
count(): number;
|
|
36
|
+
/** Bumps on every change; cheap cache key for consumers. */
|
|
37
|
+
version(): number;
|
|
38
|
+
subscribe(listener: () => void): () => void;
|
|
39
|
+
snapshot(): WorldSolidsState;
|
|
40
|
+
restore(state: WorldSolidsState): void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Yaw-expanded world AABB of an oriented solid box.
|
|
44
|
+
* @capability world-solid-bounds world AABB and nav footprint of a world solid
|
|
45
|
+
*/
|
|
46
|
+
export declare function worldSolidBounds(solid: WorldSolid): {
|
|
47
|
+
min: [number, number, number];
|
|
48
|
+
max: [number, number, number];
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* XZ footprint of a solid's world AABB, for 2D consumers such as nav grids.
|
|
52
|
+
* @capability world-solid-bounds world AABB and nav footprint of a world solid
|
|
53
|
+
*/
|
|
54
|
+
export declare function worldSolidFootprint(solid: WorldSolid): Aabb;
|
|
55
|
+
/**
|
|
56
|
+
* A store of static world solids in named layers.
|
|
57
|
+
* @capability world-solids collision for generated and authored world geometry (buildings, city lots, walls)
|
|
58
|
+
*/
|
|
59
|
+
export declare function createWorldSolids(options?: {
|
|
60
|
+
cellSize?: number;
|
|
61
|
+
}): WorldSolids;
|
|
62
|
+
/**
|
|
63
|
+
* One solid per generated building: its `bounds` footprint turned by `rotationY` about `center`,
|
|
64
|
+
* from the ground under its center up through its floors. `groundHeight` defaults to flat `0`.
|
|
65
|
+
* @capability world-solids-from-data derive collision boxes from generated buildings and wall runs
|
|
66
|
+
*/
|
|
67
|
+
export declare function buildingSolids(buildings: readonly GeneratedBuilding[], groundHeight?: (x: number, z: number) => number): WorldSolid[];
|
|
68
|
+
/**
|
|
69
|
+
* Solids for every `structures` descriptor on an environment feature, skipping any marked `solid: false`.
|
|
70
|
+
* @capability world-solids-from-data derive collision boxes from generated buildings and wall runs
|
|
71
|
+
*/
|
|
72
|
+
export declare function structureSolids(world: EnvironmentWorldFeature, groundHeight?: (x: number, z: number) => number): WorldSolid[];
|
|
73
|
+
/** Wall height, thickness and base for {@link wallSolids}. */
|
|
74
|
+
export interface WallSolidOptions {
|
|
75
|
+
height: number;
|
|
76
|
+
thickness: number;
|
|
77
|
+
/** Ground height under a point; the wall stands on it at each segment's midpoint. Default flat `0`. */
|
|
78
|
+
groundHeight?: (x: number, z: number) => number;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* One oriented solid per wall segment (see `wallSegments` in `world/walls`).
|
|
82
|
+
* @capability world-solids-from-data derive collision boxes from generated buildings and wall runs
|
|
83
|
+
*/
|
|
84
|
+
export declare function wallSolids(segments: readonly WallSegment[], options: WallSolidOptions): WorldSolid[];
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { resolveStructureBuildings } from "./environmentSummary.js";
|
|
2
|
+
const DEFAULT_CELL_SIZE = 16;
|
|
3
|
+
/** A solid covering more cells than this goes on an always-checked list instead of flooding the grid. */
|
|
4
|
+
const MAX_CELLS_PER_SOLID = 256;
|
|
5
|
+
const CELL_OFFSET = 32768;
|
|
6
|
+
/**
|
|
7
|
+
* Yaw-expanded world AABB of an oriented solid box.
|
|
8
|
+
* @capability world-solid-bounds world AABB and nav footprint of a world solid
|
|
9
|
+
*/
|
|
10
|
+
export function worldSolidBounds(solid) {
|
|
11
|
+
const yaw = solid.rotationY ?? 0;
|
|
12
|
+
const cos = Math.abs(Math.cos(yaw));
|
|
13
|
+
const sin = Math.abs(Math.sin(yaw));
|
|
14
|
+
const [hx, hy, hz] = solid.halfExtents;
|
|
15
|
+
const ex = hx * cos + hz * sin;
|
|
16
|
+
const ez = hx * sin + hz * cos;
|
|
17
|
+
const [cx, cy, cz] = solid.center;
|
|
18
|
+
return { min: [cx - ex, cy - hy, cz - ez], max: [cx + ex, cy + hy, cz + ez] };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* XZ footprint of a solid's world AABB, for 2D consumers such as nav grids.
|
|
22
|
+
* @capability world-solid-bounds world AABB and nav footprint of a world solid
|
|
23
|
+
*/
|
|
24
|
+
export function worldSolidFootprint(solid) {
|
|
25
|
+
const { min, max } = worldSolidBounds(solid);
|
|
26
|
+
return { minX: min[0], minZ: min[2], maxX: max[0], maxZ: max[2] };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A store of static world solids in named layers.
|
|
30
|
+
* @capability world-solids collision for generated and authored world geometry (buildings, city lots, walls)
|
|
31
|
+
*/
|
|
32
|
+
export function createWorldSolids(options = {}) {
|
|
33
|
+
const cellSize = options.cellSize ?? DEFAULT_CELL_SIZE;
|
|
34
|
+
const layerMap = new Map();
|
|
35
|
+
const grid = new Map();
|
|
36
|
+
const oversize = [];
|
|
37
|
+
const listeners = new Set();
|
|
38
|
+
let stamp = 0;
|
|
39
|
+
let total = 0;
|
|
40
|
+
let revision = 0;
|
|
41
|
+
let batchDepth = 0;
|
|
42
|
+
let pending = false;
|
|
43
|
+
const cellOf = (value) => Math.floor(value / cellSize);
|
|
44
|
+
const keyOf = (ix, iz) => (ix + CELL_OFFSET) * 65536 + (iz + CELL_OFFSET);
|
|
45
|
+
function index(entry) {
|
|
46
|
+
const x0 = cellOf(entry.minX);
|
|
47
|
+
const x1 = cellOf(entry.maxX);
|
|
48
|
+
const z0 = cellOf(entry.minZ);
|
|
49
|
+
const z1 = cellOf(entry.maxZ);
|
|
50
|
+
if ((x1 - x0 + 1) * (z1 - z0 + 1) > MAX_CELLS_PER_SOLID) {
|
|
51
|
+
oversize.push(entry);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for (let ix = x0; ix <= x1; ix += 1) {
|
|
55
|
+
for (let iz = z0; iz <= z1; iz += 1) {
|
|
56
|
+
const key = keyOf(ix, iz);
|
|
57
|
+
const bucket = grid.get(key);
|
|
58
|
+
if (bucket === undefined)
|
|
59
|
+
grid.set(key, [entry]);
|
|
60
|
+
else
|
|
61
|
+
bucket.push(entry);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function rebuildIndex() {
|
|
66
|
+
grid.clear();
|
|
67
|
+
oversize.length = 0;
|
|
68
|
+
total = 0;
|
|
69
|
+
for (const entries of layerMap.values()) {
|
|
70
|
+
for (const entry of entries)
|
|
71
|
+
index(entry);
|
|
72
|
+
total += entries.length;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function changed() {
|
|
76
|
+
if (batchDepth > 0) {
|
|
77
|
+
pending = true;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
pending = false;
|
|
81
|
+
rebuildIndex();
|
|
82
|
+
revision += 1;
|
|
83
|
+
for (const listener of listeners)
|
|
84
|
+
listener();
|
|
85
|
+
}
|
|
86
|
+
function indexed(solid) {
|
|
87
|
+
const { min, max } = worldSolidBounds(solid);
|
|
88
|
+
return { solid, minX: min[0], minY: min[1], minZ: min[2], maxX: max[0], maxY: max[1], maxZ: max[2], stamp: 0 };
|
|
89
|
+
}
|
|
90
|
+
function overlaps(entry, min, max) {
|
|
91
|
+
return (entry.maxX >= min[0] &&
|
|
92
|
+
entry.minX <= max[0] &&
|
|
93
|
+
entry.maxY >= min[1] &&
|
|
94
|
+
entry.minY <= max[1] &&
|
|
95
|
+
entry.maxZ >= min[2] &&
|
|
96
|
+
entry.minZ <= max[2]);
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
set(layer, solids) {
|
|
100
|
+
if (solids.length === 0) {
|
|
101
|
+
if (!layerMap.delete(layer))
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
layerMap.set(layer, solids.map(indexed));
|
|
106
|
+
}
|
|
107
|
+
changed();
|
|
108
|
+
},
|
|
109
|
+
remove(layer) {
|
|
110
|
+
if (layerMap.delete(layer))
|
|
111
|
+
changed();
|
|
112
|
+
},
|
|
113
|
+
batch(apply) {
|
|
114
|
+
batchDepth += 1;
|
|
115
|
+
try {
|
|
116
|
+
apply();
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
batchDepth -= 1;
|
|
120
|
+
}
|
|
121
|
+
if (batchDepth === 0 && pending)
|
|
122
|
+
changed();
|
|
123
|
+
},
|
|
124
|
+
layer: (layer) => layerMap.get(layer)?.map((entry) => entry.solid) ?? [],
|
|
125
|
+
layers: () => [...layerMap.keys()],
|
|
126
|
+
inBox(min, max) {
|
|
127
|
+
stamp += 1;
|
|
128
|
+
const out = [];
|
|
129
|
+
if (total === 0)
|
|
130
|
+
return out;
|
|
131
|
+
const x0 = cellOf(min[0]);
|
|
132
|
+
const x1 = cellOf(max[0]);
|
|
133
|
+
const z0 = cellOf(min[2]);
|
|
134
|
+
const z1 = cellOf(max[2]);
|
|
135
|
+
if ((x1 - x0 + 1) * (z1 - z0 + 1) > grid.size) {
|
|
136
|
+
for (const entries of layerMap.values()) {
|
|
137
|
+
for (const entry of entries)
|
|
138
|
+
if (overlaps(entry, min, max))
|
|
139
|
+
out.push(entry.solid);
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
for (let ix = x0; ix <= x1; ix += 1) {
|
|
144
|
+
for (let iz = z0; iz <= z1; iz += 1) {
|
|
145
|
+
const bucket = grid.get(keyOf(ix, iz));
|
|
146
|
+
if (bucket === undefined)
|
|
147
|
+
continue;
|
|
148
|
+
for (const entry of bucket) {
|
|
149
|
+
if (entry.stamp === stamp)
|
|
150
|
+
continue;
|
|
151
|
+
entry.stamp = stamp;
|
|
152
|
+
if (overlaps(entry, min, max))
|
|
153
|
+
out.push(entry.solid);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const entry of oversize)
|
|
158
|
+
if (overlaps(entry, min, max))
|
|
159
|
+
out.push(entry.solid);
|
|
160
|
+
return out;
|
|
161
|
+
},
|
|
162
|
+
all: () => [...layerMap.values()].flatMap((entries) => entries.map((entry) => entry.solid)),
|
|
163
|
+
count: () => total,
|
|
164
|
+
version: () => revision,
|
|
165
|
+
subscribe(listener) {
|
|
166
|
+
listeners.add(listener);
|
|
167
|
+
return () => {
|
|
168
|
+
listeners.delete(listener);
|
|
169
|
+
};
|
|
170
|
+
},
|
|
171
|
+
snapshot() {
|
|
172
|
+
const layers = {};
|
|
173
|
+
for (const [layer, entries] of layerMap)
|
|
174
|
+
layers[layer] = entries.map((entry) => entry.solid);
|
|
175
|
+
return { layers };
|
|
176
|
+
},
|
|
177
|
+
restore(state) {
|
|
178
|
+
layerMap.clear();
|
|
179
|
+
for (const [layer, solids] of Object.entries(state.layers)) {
|
|
180
|
+
if (solids.length > 0)
|
|
181
|
+
layerMap.set(layer, solids.map(indexed));
|
|
182
|
+
}
|
|
183
|
+
changed();
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* One solid per generated building: its `bounds` footprint turned by `rotationY` about `center`,
|
|
189
|
+
* from the ground under its center up through its floors. `groundHeight` defaults to flat `0`.
|
|
190
|
+
* @capability world-solids-from-data derive collision boxes from generated buildings and wall runs
|
|
191
|
+
*/
|
|
192
|
+
export function buildingSolids(buildings, groundHeight = () => 0) {
|
|
193
|
+
return buildings.map((building) => {
|
|
194
|
+
const { bounds, center } = building;
|
|
195
|
+
const yaw = building.rotationY ?? 0;
|
|
196
|
+
const localX = (bounds.minX + bounds.maxX) / 2 - center[0];
|
|
197
|
+
const localZ = (bounds.minZ + bounds.maxZ) / 2 - center[1];
|
|
198
|
+
const cos = Math.cos(yaw);
|
|
199
|
+
const sin = Math.sin(yaw);
|
|
200
|
+
const base = groundHeight(center[0], center[1]);
|
|
201
|
+
const halfHeight = (building.floors * building.floorHeight) / 2;
|
|
202
|
+
return {
|
|
203
|
+
center: [center[0] + localX * cos + localZ * sin, base + halfHeight, center[1] - localX * sin + localZ * cos],
|
|
204
|
+
halfExtents: [(bounds.maxX - bounds.minX) / 2, halfHeight, (bounds.maxZ - bounds.minZ) / 2],
|
|
205
|
+
...(yaw === 0 ? {} : { rotationY: yaw }),
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Solids for every `structures` descriptor on an environment feature, skipping any marked `solid: false`.
|
|
211
|
+
* @capability world-solids-from-data derive collision boxes from generated buildings and wall runs
|
|
212
|
+
*/
|
|
213
|
+
export function structureSolids(world, groundHeight) {
|
|
214
|
+
const solids = [];
|
|
215
|
+
for (const descriptor of world.structures ?? []) {
|
|
216
|
+
if (descriptor.solid === false)
|
|
217
|
+
continue;
|
|
218
|
+
solids.push(...buildingSolids(resolveStructureBuildings(descriptor), groundHeight));
|
|
219
|
+
}
|
|
220
|
+
return solids;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* One oriented solid per wall segment (see `wallSegments` in `world/walls`).
|
|
224
|
+
* @capability world-solids-from-data derive collision boxes from generated buildings and wall runs
|
|
225
|
+
*/
|
|
226
|
+
export function wallSolids(segments, options) {
|
|
227
|
+
return segments
|
|
228
|
+
.filter((segment) => segment.length > 0)
|
|
229
|
+
.map((segment) => {
|
|
230
|
+
const mid = [(segment.from[0] + segment.to[0]) / 2, (segment.from[1] + segment.to[1]) / 2];
|
|
231
|
+
const base = options.groundHeight?.(mid[0], mid[1]) ?? 0;
|
|
232
|
+
// Local x runs along the segment: rotationY maps local +x to (cos, -sin) in XZ.
|
|
233
|
+
const yaw = -Math.atan2(segment.to[1] - segment.from[1], segment.to[0] - segment.from[0]);
|
|
234
|
+
return {
|
|
235
|
+
center: [mid[0], base + options.height / 2, mid[1]],
|
|
236
|
+
halfExtents: [segment.length / 2, options.height / 2, options.thickness / 2],
|
|
237
|
+
rotationY: yaw,
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
}
|
package/dist/world.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export { steerYaw } from "./movement/steering.js";
|
|
|
32
32
|
export { constrainToNavGrid } from "./nav/navConstrain.js";
|
|
33
33
|
export { populateNavGridFromEnvironment } from "./nav/navFromEnvironment.js";
|
|
34
34
|
export { createNavGrid, findPath, slopeStepCost, type NavGrid, type NavPoint } from "./nav/navGrid.js";
|
|
35
|
-
export { buildNavAdjacency, closestPoint, raycastNav, type NavMeshAdjacency, type NavMeshData, type NavMeshLink, type NavMeshPath, } from "./nav/navMesh.js";
|
|
35
|
+
export { buildNavAdjacency, closestPoint, createNavMeshQuery, raycastNav, type NavMeshAdjacency, type NavMeshData, type NavMeshLink, type NavMeshPath, type NavMeshQuery, type NavMeshQueryOptions, type NavMeshQuerySnapshot, } from "./nav/navMesh.js";
|
|
36
36
|
export { advancePathFollow, createPathFollow, pathFollowProgress, pathFollowSeek, pathFromNav, pathLength, type PathFollowConfig, type PathFollowProgress, type PathFollowState, type PathProgress, type Waypoint, } from "./nav/pathFollow.js";
|
|
37
37
|
export { defineAttackMoveOrder, defineHoldOrder, defineMoveOrder, definePatrolOrder, defineStopOrder, defineTargetedOrder, type AttackMoveOrderPayload, type EmptyOrderPayload, type EngagementKindConfig, type EngagementOrderState, type MoveOrderPayload, type OrderKindConfig, type OrderMoveResult, type OrderMover, type OrderTargeting, type PatrolOrderPayload, type PatrolOrderState, type TargetedOrderPayload, } from "./orders/orderKinds.js";
|
|
38
38
|
export { createOrderQueue, createOrderRegistry, type Order, type OrderCancelReason, type OrderEvent, type OrderIssueResult, type OrderKind, type OrderOutcome, type OrderPhase, type OrderProgress, type OrderQueue, type OrderQueueOptions, type OrderQueuePolicy, type OrderQueueState, type OrderRegistry, type OrderRejection, type OrderRequest, type OrderStartResult, type OrderTickReport, type OrderVec3, } from "./orders/orderQueue.js";
|
|
@@ -58,7 +58,7 @@ export { partsBounds, registerAssetGenerator, type GeneratedAsset, type Generate
|
|
|
58
58
|
export { applyRotationPolicy, headingToRotationY, normalizeDegrees, parseAssetSpace, parseRotationPolicy, resolveAnchorOffset, resolveFacingRotationY, rotatedFootprint, rotationYToHeading, snapHeading, toEngineUnits, validateAssetSpace, type AssetAnchor, type AssetSpace, type AssetSpaceIssue, type PlacementRotationPolicy, } from "./scene/assetSpace.js";
|
|
59
59
|
export { collectAuthoredTriggers, createAuthoredTriggerRuntime, getTriggerAction, listTriggerActions, pointInVolume, registerTriggerAction, type AuthoredTrigger, type AuthoredTriggerRuntime, type TriggerActionDefinition, type TriggerDispatchEvent, type TriggerEvent, type TriggerHandlers, type TriggerSourceKind, } from "./scene/authoredTriggers.js";
|
|
60
60
|
export { selectAutoTarget, type AutoTargetPolicy } from "./scene/autoTarget.js";
|
|
61
|
-
export { advanceBehaviors, behaviorControl, registerBehaviorActions, type BehaviorAction, type BehaviorActionContext, type BehaviorControl, type BehaviorInspection, type BehaviorResumePolicy, type BehaviorSnapshot, type BehaviorStatus, } from "./scene/behaviorRuntime.js";
|
|
61
|
+
export { advanceBehaviors, behaviorControl, registerBehaviorActions, type BehaviorAction, type BehaviorActionContext, type BehaviorActionOptions, type BehaviorControl, type BehaviorInspection, type BehaviorResumePolicy, type BehaviorSnapshot, type BehaviorStatus, } from "./scene/behaviorRuntime.js";
|
|
62
62
|
export { patrol, player, talkable, wander, type BehaviorDescriptor, type DecisionGraphBehavior, } from "./scene/behaviors.js";
|
|
63
63
|
export { createBodyBind } from "./scene/bodyBind.js";
|
|
64
64
|
export { type ColliderPurpose, type EntityColliderSet, type ResolvedCollider } from "./scene/colliders.js";
|
package/dist/world.js
CHANGED
|
@@ -32,7 +32,7 @@ export { steerYaw } from "./movement/steering.js";
|
|
|
32
32
|
export { constrainToNavGrid } from "./nav/navConstrain.js";
|
|
33
33
|
export { populateNavGridFromEnvironment } from "./nav/navFromEnvironment.js";
|
|
34
34
|
export { createNavGrid, findPath, slopeStepCost } from "./nav/navGrid.js";
|
|
35
|
-
export { buildNavAdjacency, closestPoint, raycastNav, } from "./nav/navMesh.js";
|
|
35
|
+
export { buildNavAdjacency, closestPoint, createNavMeshQuery, raycastNav, } from "./nav/navMesh.js";
|
|
36
36
|
export { advancePathFollow, createPathFollow, pathFollowProgress, pathFollowSeek, pathFromNav, pathLength, } from "./nav/pathFollow.js";
|
|
37
37
|
export { defineAttackMoveOrder, defineHoldOrder, defineMoveOrder, definePatrolOrder, defineStopOrder, defineTargetedOrder, } from "./orders/orderKinds.js";
|
|
38
38
|
export { createOrderQueue, createOrderRegistry, } from "./orders/orderQueue.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "jgengine core — pure-TypeScript game framework SDK (npm @jgengine/core). Entity stores, commands, combat, inventory, multiplayer seams. Zero dependencies. Not an ECS. Agent skills ship in the tarball. https://jgengine.com",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jgengine",
|
|
@@ -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
|
|