@jgengine/core 0.17.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +130 -2
- package/dist/ai/decisionGraph.d.ts +52 -0
- package/dist/ai/decisionGraph.js +82 -0
- package/dist/ai/mobBrainGraph.d.ts +3 -0
- package/dist/ai/mobBrainGraph.js +25 -0
- package/dist/ai/tacticalQueries.d.ts +30 -0
- package/dist/ai/tacticalQueries.js +45 -0
- package/dist/anim/animGraph.d.ts +144 -0
- package/dist/anim/animGraph.js +286 -0
- package/dist/anim/ikSolver.d.ts +51 -0
- package/dist/anim/ikSolver.js +124 -0
- package/dist/anim/locomotionGraph.d.ts +26 -0
- package/dist/anim/locomotionGraph.js +33 -0
- package/dist/assets/spriteAtlas.d.ts +21 -0
- package/dist/assets/spriteAtlas.js +103 -0
- package/dist/audio/audioFalloff.d.ts +10 -0
- package/dist/economy/currency.d.ts +16 -2
- package/dist/economy/currency.js +55 -14
- package/dist/economy/wallet.d.ts +5 -4
- package/dist/economy/wallet.js +8 -4
- package/dist/editor/commandApply.js +1 -0
- package/dist/editor/commands.d.ts +4 -1
- package/dist/editor/document.d.ts +2 -1
- package/dist/editor/document.js +89 -0
- package/dist/editor/environment.d.ts +3 -0
- package/dist/editor/environment.js +16 -0
- package/dist/editor/index.d.ts +4 -2
- package/dist/editor/index.js +1 -1
- package/dist/editor/types.d.ts +16 -0
- package/dist/game/cameraConfig.d.ts +8 -0
- package/dist/game/chat.d.ts +22 -1
- package/dist/game/chat.js +48 -19
- package/dist/game/controlGate.d.ts +6 -0
- package/dist/game/controlGate.js +30 -1
- package/dist/game/defineGame.d.ts +10 -1
- package/dist/game/events.d.ts +8 -1
- package/dist/game/pixelPerfect.d.ts +25 -0
- package/dist/game/pixelPerfect.js +29 -0
- package/dist/game/playableGame.d.ts +97 -3
- package/dist/game/systemRuntime.js +6 -1
- package/dist/input/actionBindings.d.ts +2 -1
- package/dist/input/actionBindings.js +10 -8
- package/dist/input/actionContexts.d.ts +21 -0
- package/dist/input/actionContexts.js +56 -0
- package/dist/input/gamepadModel.d.ts +45 -0
- package/dist/input/gamepadModel.js +69 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +103 -1
- package/dist/movement/characterController.d.ts +76 -0
- package/dist/movement/characterController.js +216 -0
- package/dist/movement/freeFlight.d.ts +119 -0
- package/dist/movement/freeFlight.js +294 -0
- package/dist/movement/playerMovement.d.ts +10 -2
- package/dist/movement/playerMovement.js +218 -4
- package/dist/movement/serverStep.d.ts +5 -0
- package/dist/movement/serverStep.js +6 -0
- package/dist/movement/solidObstacles.d.ts +30 -7
- package/dist/movement/solidObstacles.js +27 -4
- package/dist/multiplayer/poseSyncGate.d.ts +3 -1
- package/dist/multiplayer/poseSyncGate.js +9 -1
- package/dist/multiplayer/presenceContract.d.ts +6 -0
- package/dist/multiplayer/presenceModel.d.ts +2 -0
- package/dist/multiplayer/presenceModel.js +11 -0
- package/dist/nav/navMesh.d.ts +33 -0
- package/dist/nav/navMesh.js +164 -0
- package/dist/physics/flightDynamics.d.ts +8 -0
- package/dist/physics/kinematicVehicle.d.ts +6 -1
- package/dist/physics/physicsBackend.d.ts +191 -0
- package/dist/physics/physicsBackend.js +49 -0
- package/dist/physics/physicsBackendConformance.d.ts +27 -0
- package/dist/physics/physicsBackendConformance.js +117 -0
- package/dist/physics/physicsWorldBackend.d.ts +44 -0
- package/dist/physics/physicsWorldBackend.js +485 -0
- package/dist/physics/worldColliders.d.ts +13 -0
- package/dist/physics/worldColliders.js +96 -0
- package/dist/random/rng.d.ts +12 -1
- package/dist/random/rng.js +20 -3
- package/dist/render/environment.d.ts +23 -0
- package/dist/render/environment.js +1 -0
- package/dist/render/lookPreset.d.ts +11 -5
- package/dist/render/lookPreset.js +29 -4
- package/dist/render/postProcessing.d.ts +10 -1
- package/dist/render/sprite2d.d.ts +38 -0
- package/dist/render/sprite2d.js +40 -0
- package/dist/runtime/adapter.d.ts +20 -1
- package/dist/runtime/adapter.js +8 -1
- package/dist/runtime/commandInput.d.ts +15 -0
- package/dist/runtime/commandInput.js +13 -0
- package/dist/runtime/commandRunner.d.ts +44 -3
- package/dist/runtime/commandRunner.js +41 -3
- package/dist/runtime/gameContext.js +11 -1
- package/dist/runtime/gameContextTypes.d.ts +14 -4
- package/dist/runtime/gameRuntime.d.ts +33 -4
- package/dist/runtime/gameRuntime.js +26 -5
- package/dist/runtime/headlessRunner.d.ts +10 -0
- package/dist/runtime/headlessRunner.js +26 -8
- package/dist/runtime/hostPolicy.d.ts +3 -2
- package/dist/runtime/hostPolicy.js +8 -10
- package/dist/runtime/hostedGameRunner.js +34 -6
- package/dist/runtime/hostedWorldSession.d.ts +12 -16
- package/dist/runtime/hostedWorldSession.js +29 -6
- package/dist/runtime/hostedWorldStore.d.ts +20 -0
- package/dist/runtime/hostedWorldStore.js +1 -0
- package/dist/runtime/inputRecorder.d.ts +30 -0
- package/dist/runtime/inputRecorder.js +59 -0
- package/dist/runtime/inputSnapshot.d.ts +8 -0
- package/dist/runtime/inputSnapshot.js +1 -0
- package/dist/runtime/poseInterpolation.d.ts +36 -0
- package/dist/runtime/poseInterpolation.js +91 -0
- package/dist/runtime/prediction.d.ts +45 -0
- package/dist/runtime/prediction.js +62 -0
- package/dist/runtime/simContext.d.ts +45 -0
- package/dist/runtime/simContext.js +65 -0
- package/dist/runtime/simLoop.d.ts +52 -0
- package/dist/runtime/simLoop.js +72 -0
- package/dist/runtime/simSnapshot.d.ts +32 -0
- package/dist/runtime/simSnapshot.js +60 -0
- package/dist/runtime/snapshot.d.ts +8 -0
- package/dist/runtime/snapshotBuffer.d.ts +33 -0
- package/dist/runtime/snapshotBuffer.js +58 -0
- package/dist/runtime/transport.d.ts +38 -2
- package/dist/runtime/worldChunks.d.ts +2 -0
- package/dist/runtime/worldChunks.js +14 -0
- package/dist/runtime/worldProjection.d.ts +9 -0
- package/dist/runtime/worldProjection.js +17 -1
- package/dist/runtime/worldSnapshot.d.ts +6 -0
- package/dist/runtime/worldSnapshot.js +3 -0
- package/dist/scene/behaviorRuntime.d.ts +19 -1
- package/dist/scene/behaviorRuntime.js +55 -4
- package/dist/scene/behaviors.d.ts +8 -1
- package/dist/scene/builtinSceneKinds.js +10 -0
- package/dist/sensor/perception.d.ts +60 -0
- package/dist/sensor/perception.js +142 -0
- package/dist/settings/graphicsProfile.d.ts +19 -0
- package/dist/settings/graphicsProfile.js +36 -0
- package/dist/time/accrueSince.d.ts +10 -0
- package/dist/time/accrueSince.js +9 -0
- package/dist/time/rateWindow.d.ts +16 -0
- package/dist/time/rateWindow.js +14 -0
- package/dist/time/serverTick.d.ts +21 -3
- package/dist/time/serverTick.js +14 -13
- package/dist/vfx/particleDirector.d.ts +68 -0
- package/dist/vfx/particleDirector.js +81 -0
- package/dist/world/buildings.d.ts +12 -0
- package/dist/world/buildings.js +43 -3
- package/dist/world/cityContent.js +103 -25
- package/dist/world/cityKind.d.ts +132 -0
- package/dist/world/cityKind.js +315 -15
- package/dist/world/features.d.ts +27 -1
- package/dist/world/features.js +7 -2
- package/dist/world/placement.d.ts +7 -0
- package/dist/world/placement.js +2 -0
- package/dist/world/placementController.d.ts +1 -1
- package/dist/world/placementController.js +7 -0
- package/dist/world/raceCircuit.d.ts +168 -0
- package/dist/world/raceCircuit.js +797 -0
- package/dist/world/terrain.d.ts +5 -1
- package/dist/world/terrain.js +5 -0
- package/dist/world/territory.d.ts +97 -0
- package/dist/world/territory.js +182 -0
- package/dist/world.d.ts +5 -3
- package/dist/world.js +4 -2
- package/package.json +1 -1
- package/skills/ce-handoff/SKILL.md +1 -1
- package/skills/game-design/SKILL.md +11 -1
- package/skills/jgengine/SKILL.md +2 -0
- package/skills/jgengine/api.md +98 -22
- package/skills/jgengine/capabilities.md +28 -0
- package/skills/jgengine-gameplay/SKILL.md +10 -0
- package/skills/jgengine-gameplay/api.md +40 -18
- package/skills/jgengine-gameplay/capabilities.md +12 -0
- package/skills/jgengine-world/SKILL.md +6 -0
- package/skills/jgengine-world/api.md +312 -12
- package/skills/jgengine-world/capabilities.md +102 -0
package/dist/world/terrain.d.ts
CHANGED
|
@@ -203,9 +203,13 @@ export interface ResolvedTerrainDetailMaterial {
|
|
|
203
203
|
tint: number;
|
|
204
204
|
}
|
|
205
205
|
/** A {@link TerrainDetailConfig} with every field resolved to a concrete value — the shape the shell's detail material consumes. */
|
|
206
|
-
export type ResolvedTerrainDetail = Required<Omit<TerrainDetailConfig, "waterLevel" | "material">> & {
|
|
206
|
+
export type ResolvedTerrainDetail = Required<Omit<TerrainDetailConfig, "waterLevel" | "material" | "sweeps">> & {
|
|
207
207
|
waterLevel: number;
|
|
208
208
|
material?: ResolvedTerrainDetailMaterial;
|
|
209
|
+
sweeps: {
|
|
210
|
+
dry: readonly [number, number, number];
|
|
211
|
+
wet: readonly [number, number, number];
|
|
212
|
+
};
|
|
209
213
|
};
|
|
210
214
|
/** Fill a `TerrainDetailConfig` with defaults; `waterLevel` falls back to the terrain's own water level.
|
|
211
215
|
* @internal
|
package/dist/world/terrain.js
CHANGED
|
@@ -475,6 +475,7 @@ const DEFAULT_TERRAIN_DETAIL = {
|
|
|
475
475
|
macroScale: 45,
|
|
476
476
|
roughness: 0.9,
|
|
477
477
|
strength: 1,
|
|
478
|
+
sweeps: { dry: [1.14, 1.05, 0.76], wet: [0.78, 1.0, 0.86] },
|
|
478
479
|
};
|
|
479
480
|
const DEFAULT_TERRAIN_MATERIAL_REPEAT = 4;
|
|
480
481
|
const DEFAULT_TERRAIN_MATERIAL_STRENGTH = 1;
|
|
@@ -493,6 +494,10 @@ export function resolveTerrainDetail(config, terrainWaterLevel = 0) {
|
|
|
493
494
|
macroScale: config.macroScale ?? DEFAULT_TERRAIN_DETAIL.macroScale,
|
|
494
495
|
roughness: config.roughness ?? DEFAULT_TERRAIN_DETAIL.roughness,
|
|
495
496
|
strength: clamp01(config.strength ?? DEFAULT_TERRAIN_DETAIL.strength),
|
|
497
|
+
sweeps: {
|
|
498
|
+
dry: config.sweeps?.dry ?? DEFAULT_TERRAIN_DETAIL.sweeps.dry,
|
|
499
|
+
wet: config.sweeps?.wet ?? DEFAULT_TERRAIN_DETAIL.sweeps.wet,
|
|
500
|
+
},
|
|
496
501
|
waterLevel: config.waterLevel ?? terrainWaterLevel,
|
|
497
502
|
...(config.material === undefined
|
|
498
503
|
? {}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { GameRuntimeSnapshot } from "../runtime/snapshot.js";
|
|
2
|
+
import type { Aabb } from "./geometry.js";
|
|
3
|
+
/** Integer cell coordinates used for ownership on the ground plane. */
|
|
4
|
+
export type TerritoryCell = {
|
|
5
|
+
x: number;
|
|
6
|
+
z: number;
|
|
7
|
+
};
|
|
8
|
+
/** Cell size, foreign-owner gap, growth price, currency and starter-grant policy. */
|
|
9
|
+
export type TerritoryPolicy = {
|
|
10
|
+
gapCells?: number;
|
|
11
|
+
cellSize?: number;
|
|
12
|
+
chunkSize?: number;
|
|
13
|
+
currency?: string;
|
|
14
|
+
price?: (ownedCount: number) => number;
|
|
15
|
+
starterBlock?: number;
|
|
16
|
+
nowMs?: number;
|
|
17
|
+
};
|
|
18
|
+
/** Atomic claim outcome with the replacement snapshot and total charge. */
|
|
19
|
+
export type TerritoryResult = {
|
|
20
|
+
ok: true;
|
|
21
|
+
snapshot: GameRuntimeSnapshot;
|
|
22
|
+
cost: number;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
reason: "territory.blocked" | "territory.unaffordable";
|
|
26
|
+
};
|
|
27
|
+
/** Read-only footprint quote or a blocked/insufficient-balance rejection. */
|
|
28
|
+
export type TerritoryPlan = {
|
|
29
|
+
ok: true;
|
|
30
|
+
cells: TerritoryCell[];
|
|
31
|
+
cost: number;
|
|
32
|
+
} | {
|
|
33
|
+
ok: false;
|
|
34
|
+
reason: "territory.blocked" | "territory.unaffordable";
|
|
35
|
+
};
|
|
36
|
+
/** Host-owned snapshot access used by the territory facade. */
|
|
37
|
+
export interface TerritoryStorage {
|
|
38
|
+
get(): GameRuntimeSnapshot;
|
|
39
|
+
set(snapshot: GameRuntimeSnapshot): void;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Persisted chunk key containing one territory cell.
|
|
43
|
+
* @capability territory-cell-chunk Map ownership cells to persisted world chunks.
|
|
44
|
+
*/
|
|
45
|
+
export declare function territoryChunkKey(cell: TerritoryCell, policy?: TerritoryPolicy): string;
|
|
46
|
+
/**
|
|
47
|
+
* Exact chunks needed to validate a footprint and its foreign-owner gap.
|
|
48
|
+
* @capability territory-read-scope Load the footprint and foreign-owner gap without scanning the world.
|
|
49
|
+
*/
|
|
50
|
+
export declare function territoryChunkKeys(cells: readonly TerritoryCell[], policy?: TerritoryPolicy): string[];
|
|
51
|
+
/**
|
|
52
|
+
* Owner of a loaded cell; hosts load the target and gap neighborhood before evaluating claims.
|
|
53
|
+
* @capability territory-owner-query Read a loaded land cell owner for authoritative placement checks.
|
|
54
|
+
*/
|
|
55
|
+
export declare function territoryOwnerOf(snapshot: GameRuntimeSnapshot, cell: TerritoryCell, policy?: TerritoryPolicy): string | null;
|
|
56
|
+
/**
|
|
57
|
+
* Cells touched by a placement, excluding cells that only touch its outer edge.
|
|
58
|
+
* @capability territory-footprint Resolve exactly the ownership cells touched by a placement footprint.
|
|
59
|
+
*/
|
|
60
|
+
export declare function territoryFootprintCells(bounds: Aabb, cellSize?: number): TerritoryCell[];
|
|
61
|
+
/** Price and validate a footprint without mutating or charging; reuse for placement previews. */
|
|
62
|
+
export declare function planFootprintClaims(snapshot: GameRuntimeSnapshot, userId: string, footprint: readonly TerritoryCell[], policy?: TerritoryPolicy): TerritoryPlan;
|
|
63
|
+
/** Atomically buy every unowned footprint cell, leaving the input untouched on failure. */
|
|
64
|
+
export declare function claimTerritory(snapshot: GameRuntimeSnapshot, userId: string, cells: readonly TerritoryCell[], policy?: TerritoryPolicy): TerritoryResult;
|
|
65
|
+
/**
|
|
66
|
+
* Placement transaction: rejected object placement also rolls back territory and its charge.
|
|
67
|
+
* @capability territory-placement Purchase footprint cells atomically with object placement.
|
|
68
|
+
*/
|
|
69
|
+
export declare function placeWithTerritory(snapshot: GameRuntimeSnapshot, userId: string, bounds: Aabb, policy: TerritoryPolicy, place: (snapshot: GameRuntimeSnapshot) => {
|
|
70
|
+
ok: true;
|
|
71
|
+
snapshot: GameRuntimeSnapshot;
|
|
72
|
+
} | {
|
|
73
|
+
ok: false;
|
|
74
|
+
reason: string;
|
|
75
|
+
}): {
|
|
76
|
+
ok: true;
|
|
77
|
+
snapshot: GameRuntimeSnapshot;
|
|
78
|
+
cost: number;
|
|
79
|
+
} | {
|
|
80
|
+
ok: false;
|
|
81
|
+
reason: string;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Snapshot-backed territory facade with host-owned storage and live policy.
|
|
85
|
+
* @capability territory-ownership Claim, release and query land through host-owned snapshot storage.
|
|
86
|
+
*/
|
|
87
|
+
export declare function createTerritory(storage: TerritoryStorage, policy?: () => TerritoryPolicy): {
|
|
88
|
+
snapshot: () => GameRuntimeSnapshot;
|
|
89
|
+
restore: (snapshot: GameRuntimeSnapshot) => void;
|
|
90
|
+
ownerOf: (cell: TerritoryCell) => string | null;
|
|
91
|
+
canUse: (userId: string, cell: TerritoryCell) => boolean;
|
|
92
|
+
claim(userId: string, cells: readonly TerritoryCell[]): TerritoryResult;
|
|
93
|
+
release(userId: string, cell: TerritoryCell): boolean;
|
|
94
|
+
grantStarter(userId: string, center: TerritoryCell): TerritoryResult;
|
|
95
|
+
};
|
|
96
|
+
/** Snapshot-backed claim, release, owner and starter-grant operations. */
|
|
97
|
+
export type Territory = ReturnType<typeof createTerritory>;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { chunkKeyAt, updateChunk } from "../runtime/worldChunks.js";
|
|
2
|
+
function checked(policy) {
|
|
3
|
+
if (policy.nowMs !== undefined && !Number.isFinite(policy.nowMs))
|
|
4
|
+
throw new RangeError("Invalid territory timestamp");
|
|
5
|
+
const gapCells = policy.gapCells ?? 0, cellSize = policy.cellSize ?? 1, chunkSize = policy.chunkSize ?? 64;
|
|
6
|
+
if (!Number.isSafeInteger(gapCells) || gapCells < 0 || gapCells > 128 || !Number.isFinite(cellSize) || cellSize <= 0 || !Number.isFinite(chunkSize) || chunkSize <= 0)
|
|
7
|
+
throw new RangeError("Invalid territory policy");
|
|
8
|
+
return { gapCells, cellSize, chunkSize };
|
|
9
|
+
}
|
|
10
|
+
function key(cell) {
|
|
11
|
+
if (!Number.isSafeInteger(cell.x) || !Number.isSafeInteger(cell.z))
|
|
12
|
+
throw new RangeError("Invalid territory cell");
|
|
13
|
+
return `${cell.x},${cell.z}`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Persisted chunk key containing one territory cell.
|
|
17
|
+
* @capability territory-cell-chunk Map ownership cells to persisted world chunks.
|
|
18
|
+
*/
|
|
19
|
+
export function territoryChunkKey(cell, policy = {}) {
|
|
20
|
+
key(cell);
|
|
21
|
+
const { cellSize, chunkSize } = checked(policy);
|
|
22
|
+
return chunkKeyAt([cell.x * cellSize, 0, cell.z * cellSize], chunkSize);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Exact chunks needed to validate a footprint and its foreign-owner gap.
|
|
26
|
+
* @capability territory-read-scope Load the footprint and foreign-owner gap without scanning the world.
|
|
27
|
+
*/
|
|
28
|
+
export function territoryChunkKeys(cells, policy = {}) {
|
|
29
|
+
const { gapCells } = checked(policy);
|
|
30
|
+
const chunks = new Set();
|
|
31
|
+
if (cells.length * (2 * gapCells + 1) ** 2 > 1_000_000)
|
|
32
|
+
throw new RangeError("Territory neighborhood is too large");
|
|
33
|
+
for (const cell of cells) {
|
|
34
|
+
key(cell);
|
|
35
|
+
for (let x = cell.x - gapCells; x <= cell.x + gapCells; x++) {
|
|
36
|
+
for (let z = cell.z - gapCells; z <= cell.z + gapCells; z++)
|
|
37
|
+
chunks.add(territoryChunkKey({ x, z }, policy));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return [...chunks];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Owner of a loaded cell; hosts load the target and gap neighborhood before evaluating claims.
|
|
44
|
+
* @capability territory-owner-query Read a loaded land cell owner for authoritative placement checks.
|
|
45
|
+
*/
|
|
46
|
+
export function territoryOwnerOf(snapshot, cell, policy = {}) {
|
|
47
|
+
return snapshot.chunks[territoryChunkKey(cell, policy)]?.territory?.[key(cell)] ?? null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Cells touched by a placement, excluding cells that only touch its outer edge.
|
|
51
|
+
* @capability territory-footprint Resolve exactly the ownership cells touched by a placement footprint.
|
|
52
|
+
*/
|
|
53
|
+
export function territoryFootprintCells(bounds, cellSize = 1) {
|
|
54
|
+
if (!Number.isFinite(cellSize) || cellSize <= 0 || !Object.values(bounds).every(Number.isFinite) || bounds.maxX <= bounds.minX || bounds.maxZ <= bounds.minZ)
|
|
55
|
+
throw new RangeError("Invalid territory footprint");
|
|
56
|
+
const cells = [];
|
|
57
|
+
const minX = Math.floor(bounds.minX / cellSize), maxX = Math.ceil(bounds.maxX / cellSize) - 1;
|
|
58
|
+
const minZ = Math.floor(bounds.minZ / cellSize), maxZ = Math.ceil(bounds.maxZ / cellSize) - 1;
|
|
59
|
+
if ((maxX - minX + 1) * (maxZ - minZ + 1) > 65536)
|
|
60
|
+
throw new RangeError("Territory footprint is too large");
|
|
61
|
+
for (let x = minX; x <= maxX; x++)
|
|
62
|
+
for (let z = minZ; z <= maxZ; z++)
|
|
63
|
+
cells.push({ x, z });
|
|
64
|
+
return cells;
|
|
65
|
+
}
|
|
66
|
+
/** Price and validate a footprint without mutating or charging; reuse for placement previews. */
|
|
67
|
+
export function planFootprintClaims(snapshot, userId, footprint, policy = {}) {
|
|
68
|
+
const { gapCells } = checked(policy);
|
|
69
|
+
if (footprint.length * (2 * gapCells + 1) ** 2 > 1_000_000)
|
|
70
|
+
throw new RangeError("Territory neighborhood is too large");
|
|
71
|
+
const player = snapshot.players[userId];
|
|
72
|
+
if (!player)
|
|
73
|
+
return { ok: false, reason: "territory.blocked" };
|
|
74
|
+
const cells = [], seen = new Set();
|
|
75
|
+
let cost = 0;
|
|
76
|
+
const owned = Number(player.territoryOwnedCount ?? 0);
|
|
77
|
+
if (!Number.isSafeInteger(owned) || owned < 0)
|
|
78
|
+
throw new RangeError("Invalid territory owned count");
|
|
79
|
+
for (const cell of footprint) {
|
|
80
|
+
const cellKey = key(cell);
|
|
81
|
+
if (seen.has(cellKey))
|
|
82
|
+
continue;
|
|
83
|
+
seen.add(cellKey);
|
|
84
|
+
const owner = territoryOwnerOf(snapshot, cell, policy);
|
|
85
|
+
if (owner === userId)
|
|
86
|
+
continue;
|
|
87
|
+
if (owner !== null)
|
|
88
|
+
return { ok: false, reason: "territory.blocked" };
|
|
89
|
+
for (let x = cell.x - gapCells; x <= cell.x + gapCells; x++)
|
|
90
|
+
for (let z = cell.z - gapCells; z <= cell.z + gapCells; z++) {
|
|
91
|
+
const neighbor = territoryOwnerOf(snapshot, { x, z }, policy);
|
|
92
|
+
if (neighbor !== null && neighbor !== userId)
|
|
93
|
+
return { ok: false, reason: "territory.blocked" };
|
|
94
|
+
}
|
|
95
|
+
const price = policy.price?.(owned + cells.length) ?? 0;
|
|
96
|
+
if (!Number.isFinite(price) || price < 0)
|
|
97
|
+
throw new RangeError("Invalid territory price");
|
|
98
|
+
cost += price;
|
|
99
|
+
if (!Number.isFinite(cost))
|
|
100
|
+
throw new RangeError("Invalid territory cost");
|
|
101
|
+
cells.push(cell);
|
|
102
|
+
}
|
|
103
|
+
if (cost > 0 && (!policy.currency || (player.economy[policy.currency] ?? 0) < cost))
|
|
104
|
+
return { ok: false, reason: "territory.unaffordable" };
|
|
105
|
+
return { ok: true, cells, cost };
|
|
106
|
+
}
|
|
107
|
+
/** Atomically buy every unowned footprint cell, leaving the input untouched on failure. */
|
|
108
|
+
export function claimTerritory(snapshot, userId, cells, policy = {}) {
|
|
109
|
+
const plan = planFootprintClaims(snapshot, userId, cells, policy);
|
|
110
|
+
if (!plan.ok)
|
|
111
|
+
return plan;
|
|
112
|
+
let next = snapshot;
|
|
113
|
+
for (const [index, cell] of plan.cells.entries())
|
|
114
|
+
next = updateChunk(next, territoryChunkKey(cell, policy), chunk => ({
|
|
115
|
+
...chunk,
|
|
116
|
+
territory: { ...chunk.territory, [key(cell)]: userId },
|
|
117
|
+
territoryReceipts: { ...chunk.territoryReceipts, [key(cell)]: { claimedAt: policy.nowMs ?? 0, costPaid: policy.price?.(Number(snapshot.players[userId]?.territoryOwnedCount ?? 0) + index) ?? 0 } },
|
|
118
|
+
}));
|
|
119
|
+
if (plan.cells.length === 0)
|
|
120
|
+
return { ok: true, snapshot, cost: 0 };
|
|
121
|
+
const player = snapshot.players[userId];
|
|
122
|
+
next = { ...next, players: { ...next.players, [userId]: { ...player, territoryOwnedCount: Number(player.territoryOwnedCount ?? 0) + plan.cells.length, ownedTerritoryChunkKeys: [...new Set([...(player.ownedTerritoryChunkKeys ?? []), ...plan.cells.map(cell => territoryChunkKey(cell, policy))])], economy: policy.currency ? { ...player.economy, [policy.currency]: (player.economy[policy.currency] ?? 0) - plan.cost } : player.economy } }, dirty: { ...next.dirty, players: [...new Set([...next.dirty.players, userId])] } };
|
|
123
|
+
return { ok: true, snapshot: next, cost: plan.cost };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Placement transaction: rejected object placement also rolls back territory and its charge.
|
|
127
|
+
* @capability territory-placement Purchase footprint cells atomically with object placement.
|
|
128
|
+
*/
|
|
129
|
+
export function placeWithTerritory(snapshot, userId, bounds, policy, place) {
|
|
130
|
+
const claimed = claimTerritory(snapshot, userId, territoryFootprintCells(bounds, policy.cellSize), policy);
|
|
131
|
+
if (!claimed.ok)
|
|
132
|
+
return claimed;
|
|
133
|
+
const placed = place(claimed.snapshot);
|
|
134
|
+
return placed.ok ? { ...placed, cost: claimed.cost } : placed;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Snapshot-backed territory facade with host-owned storage and live policy.
|
|
138
|
+
* @capability territory-ownership Claim, release and query land through host-owned snapshot storage.
|
|
139
|
+
*/
|
|
140
|
+
export function createTerritory(storage, policy = () => ({})) {
|
|
141
|
+
return {
|
|
142
|
+
snapshot: () => storage.get(),
|
|
143
|
+
restore: (snapshot) => storage.set(snapshot),
|
|
144
|
+
ownerOf: (cell) => territoryOwnerOf(storage.get(), cell, policy()),
|
|
145
|
+
canUse: (userId, cell) => territoryOwnerOf(storage.get(), cell, policy()) === userId,
|
|
146
|
+
claim(userId, cells) {
|
|
147
|
+
const result = claimTerritory(storage.get(), userId, cells, policy());
|
|
148
|
+
if (result.ok)
|
|
149
|
+
storage.set(result.snapshot);
|
|
150
|
+
return result;
|
|
151
|
+
},
|
|
152
|
+
release(userId, cell) {
|
|
153
|
+
const snapshot = storage.get(), config = policy();
|
|
154
|
+
if (territoryOwnerOf(snapshot, cell, config) !== userId || !snapshot.players[userId])
|
|
155
|
+
return false;
|
|
156
|
+
let next = updateChunk(snapshot, territoryChunkKey(cell, config), chunk => {
|
|
157
|
+
const territory = { ...chunk.territory }, territoryReceipts = { ...chunk.territoryReceipts };
|
|
158
|
+
delete territory[key(cell)];
|
|
159
|
+
delete territoryReceipts[key(cell)];
|
|
160
|
+
return { ...chunk, territory, territoryReceipts };
|
|
161
|
+
});
|
|
162
|
+
const player = next.players[userId];
|
|
163
|
+
next = { ...next, players: { ...next.players, [userId]: { ...player, territoryOwnedCount: Math.max(0, Number(player.territoryOwnedCount ?? 0) - 1), ownedTerritoryChunkKeys: (player.ownedTerritoryChunkKeys ?? []).filter(chunkKey => chunkKey !== territoryChunkKey(cell, config) || Object.values(next.chunks[chunkKey]?.territory ?? {}).includes(userId)) } }, dirty: { ...next.dirty, players: [...new Set([...next.dirty.players, userId])] } };
|
|
164
|
+
storage.set(next);
|
|
165
|
+
return true;
|
|
166
|
+
},
|
|
167
|
+
grantStarter(userId, center) {
|
|
168
|
+
const config = policy(), width = config.starterBlock ?? 3;
|
|
169
|
+
if (!Number.isSafeInteger(width) || width < 1 || width > 128)
|
|
170
|
+
throw new RangeError("Invalid starter block");
|
|
171
|
+
const snapshot = storage.get();
|
|
172
|
+
if (Number(snapshot.players[userId]?.territoryOwnedCount ?? 0) > 0)
|
|
173
|
+
return { ok: true, snapshot, cost: 0 };
|
|
174
|
+
const startX = center.x - Math.floor(width / 2), startZ = center.z - Math.floor(width / 2);
|
|
175
|
+
const cells = territoryFootprintCells({ minX: startX, minZ: startZ, maxX: startX + width, maxZ: startZ + width });
|
|
176
|
+
const result = claimTerritory(snapshot, userId, cells, { ...config, price: () => 0 });
|
|
177
|
+
if (result.ok)
|
|
178
|
+
storage.set(result.snapshot);
|
|
179
|
+
return result;
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
package/dist/world.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export { evaluateQteSequence, pendingQteStep, type QteStep } from "./interaction
|
|
|
21
21
|
export { evaluateSkillCheck, skillCheckZoneAt, type SkillCheckConfig, type SkillCheckResult, } from "./interaction/skillCheck.js";
|
|
22
22
|
export { resolveLocalAvoidance, type AvoidanceAgent, type LocalAvoidanceOptions } from "./movement/avoidance.js";
|
|
23
23
|
export { assignFormationSlots, boxFormation, circleFormation, facingYaw, lineFormation, placeFormation, wedgeFormation, type BoxFormationOptions, type CircleFormationOptions, type FormationSlotGenerator, type LineFormationOptions, type SlotAssignmentOptions, type WedgeFormationOptions, } from "./movement/formation.js";
|
|
24
|
+
export { CREATIVE_FLIGHT_TUNING, HOVER_FLIGHT_TUNING, NOCLIP_FLIGHT_TUNING, SPECTATOR_FLIGHT_TUNING, advanceFreeFlight, createFreeFlightController, createFreeFlightState, resolveFlightStep, resolveFreeFlightIntent, resolveFreeFlightIntentFromInput, type FreeFlightBindings, type FreeFlightController, type FreeFlightIntent, type FreeFlightMode, type FreeFlightState, type FreeFlightStep, type FreeFlightTuning, } from "./movement/freeFlight.js";
|
|
24
25
|
export { createGlideModel } from "./movement/glideModel.js";
|
|
25
26
|
export { createGrappleSwing } from "./movement/grappleSwing.js";
|
|
26
27
|
export { createLeaderTrail } from "./movement/leaderTrail.js";
|
|
@@ -31,6 +32,7 @@ export { steerYaw } from "./movement/steering.js";
|
|
|
31
32
|
export { constrainToNavGrid } from "./nav/navConstrain.js";
|
|
32
33
|
export { populateNavGridFromEnvironment } from "./nav/navFromEnvironment.js";
|
|
33
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";
|
|
34
36
|
export { advancePathFollow, createPathFollow, pathFollowProgress, pathFollowSeek, pathFromNav, pathLength, type PathFollowConfig, type PathFollowProgress, type PathFollowState, type PathProgress, type Waypoint, } from "./nav/pathFollow.js";
|
|
35
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";
|
|
36
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";
|
|
@@ -39,7 +41,7 @@ export { createBuoyantBody } from "./physics/buoyancy.js";
|
|
|
39
41
|
export { Carryable, carrySpeedMultiplier } from "./physics/carryable.js";
|
|
40
42
|
export { createDamageModel } from "./physics/damageZones.js";
|
|
41
43
|
export { tickDrivableVehicle } from "./physics/drivableVehicle.js";
|
|
42
|
-
export { createAircraftDynamics, type AircraftDynamics, type AircraftKind, type AircraftOptions, type AircraftStep, type AircraftTuning, type FlightControlInput, type FlightControlRates, type FlightVector, } from "./physics/flightDynamics.js";
|
|
44
|
+
export { createAircraftDynamics, type AircraftAxis, type AircraftDynamics, type AircraftKind, type AircraftOptions, type AircraftStep, type AircraftTuning, type FlightControlInput, type FlightControlRates, type FlightVector, } from "./physics/flightDynamics.js";
|
|
43
45
|
export { ForceVolume, PlatformCarry } from "./physics/forceVolume.js";
|
|
44
46
|
export { combineGravity, pointGravity, uniformGravity, type GravityField, type GravityVector, type PointGravityOptions, } from "./physics/gravityField.js";
|
|
45
47
|
export { createKinematicVehicle, type KinematicChassisTuning, type KinematicDynamicsTuning, type KinematicPowertrainTuning, type KinematicSteeringTuning, type KinematicVehicle, type KinematicVehicleStep, type KinematicVehicleTuning, } from "./physics/kinematicVehicle.js";
|
|
@@ -56,8 +58,8 @@ export { partsBounds, registerAssetGenerator, type GeneratedAsset, type Generate
|
|
|
56
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";
|
|
57
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";
|
|
58
60
|
export { selectAutoTarget, type AutoTargetPolicy } from "./scene/autoTarget.js";
|
|
59
|
-
export { advanceBehaviors, behaviorControl, type BehaviorControl, type BehaviorInspection, type BehaviorResumePolicy, type BehaviorSnapshot, type BehaviorStatus, } from "./scene/behaviorRuntime.js";
|
|
60
|
-
export { patrol, player, talkable, wander, type BehaviorDescriptor } from "./scene/behaviors.js";
|
|
61
|
+
export { advanceBehaviors, behaviorControl, registerBehaviorActions, type BehaviorAction, type BehaviorActionContext, type BehaviorControl, type BehaviorInspection, type BehaviorResumePolicy, type BehaviorSnapshot, type BehaviorStatus, } from "./scene/behaviorRuntime.js";
|
|
62
|
+
export { patrol, player, talkable, wander, type BehaviorDescriptor, type DecisionGraphBehavior, } from "./scene/behaviors.js";
|
|
61
63
|
export { createBodyBind } from "./scene/bodyBind.js";
|
|
62
64
|
export { type ColliderPurpose, type EntityColliderSet, type ResolvedCollider } from "./scene/colliders.js";
|
|
63
65
|
export { type StatCatalog, type StatValue } from "./scene/entityStats.js";
|
package/dist/world.js
CHANGED
|
@@ -21,6 +21,7 @@ export { evaluateQteSequence, pendingQteStep } from "./interaction/qte.js";
|
|
|
21
21
|
export { evaluateSkillCheck, skillCheckZoneAt, } from "./interaction/skillCheck.js";
|
|
22
22
|
export { resolveLocalAvoidance } from "./movement/avoidance.js";
|
|
23
23
|
export { assignFormationSlots, boxFormation, circleFormation, facingYaw, lineFormation, placeFormation, wedgeFormation, } from "./movement/formation.js";
|
|
24
|
+
export { CREATIVE_FLIGHT_TUNING, HOVER_FLIGHT_TUNING, NOCLIP_FLIGHT_TUNING, SPECTATOR_FLIGHT_TUNING, advanceFreeFlight, createFreeFlightController, createFreeFlightState, resolveFlightStep, resolveFreeFlightIntent, resolveFreeFlightIntentFromInput, } from "./movement/freeFlight.js";
|
|
24
25
|
export { createGlideModel } from "./movement/glideModel.js";
|
|
25
26
|
export { createGrappleSwing } from "./movement/grappleSwing.js";
|
|
26
27
|
export { createLeaderTrail } from "./movement/leaderTrail.js";
|
|
@@ -31,6 +32,7 @@ export { steerYaw } from "./movement/steering.js";
|
|
|
31
32
|
export { constrainToNavGrid } from "./nav/navConstrain.js";
|
|
32
33
|
export { populateNavGridFromEnvironment } from "./nav/navFromEnvironment.js";
|
|
33
34
|
export { createNavGrid, findPath, slopeStepCost } from "./nav/navGrid.js";
|
|
35
|
+
export { buildNavAdjacency, closestPoint, raycastNav, } from "./nav/navMesh.js";
|
|
34
36
|
export { advancePathFollow, createPathFollow, pathFollowProgress, pathFollowSeek, pathFromNav, pathLength, } from "./nav/pathFollow.js";
|
|
35
37
|
export { defineAttackMoveOrder, defineHoldOrder, defineMoveOrder, definePatrolOrder, defineStopOrder, defineTargetedOrder, } from "./orders/orderKinds.js";
|
|
36
38
|
export { createOrderQueue, createOrderRegistry, } from "./orders/orderQueue.js";
|
|
@@ -56,8 +58,8 @@ export { partsBounds, registerAssetGenerator } from "./scene/assetGenerator.js";
|
|
|
56
58
|
export { applyRotationPolicy, headingToRotationY, normalizeDegrees, parseAssetSpace, parseRotationPolicy, resolveAnchorOffset, resolveFacingRotationY, rotatedFootprint, rotationYToHeading, snapHeading, toEngineUnits, validateAssetSpace, } from "./scene/assetSpace.js";
|
|
57
59
|
export { collectAuthoredTriggers, createAuthoredTriggerRuntime, getTriggerAction, listTriggerActions, pointInVolume, registerTriggerAction, } from "./scene/authoredTriggers.js";
|
|
58
60
|
export { selectAutoTarget } from "./scene/autoTarget.js";
|
|
59
|
-
export { advanceBehaviors, behaviorControl, } from "./scene/behaviorRuntime.js";
|
|
60
|
-
export { patrol, player, talkable, wander } from "./scene/behaviors.js";
|
|
61
|
+
export { advanceBehaviors, behaviorControl, registerBehaviorActions, } from "./scene/behaviorRuntime.js";
|
|
62
|
+
export { patrol, player, talkable, wander, } from "./scene/behaviors.js";
|
|
61
63
|
export { createBodyBind } from "./scene/bodyBind.js";
|
|
62
64
|
export {} from "./scene/colliders.js";
|
|
63
65
|
export {} from "./scene/entityStats.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
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",
|
|
@@ -146,4 +146,4 @@ When sufficient, return a concise orientation: objective, progress, decisions, c
|
|
|
146
146
|
- Multi-session engine work often spans `docs/plans/`, GitHub issues, and stacked 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.
|
|
@@ -11,7 +11,7 @@ Own the intended player experience and the rules that produce it: fantasy, pilla
|
|
|
11
11
|
|
|
12
12
|
Use `level-design` for spatial realization; route implementation to `jgengine-gameplay`, `jgengine-world`, `jgengine-combat`, `jgengine-ui`, `jgengine-multiplayer` once intent is coherent.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
For a greenfield build, read [references/application-playbook.md](references/application-playbook.md) and produce its greenfield contract (promise, pillars, anti-pillars, core decision, and falsifier) before writing code. Audits may read only the playbook's audit sections. Read [references/field-guide.md](references/field-guide.md) when the task needs deeper loop, economy, progression, genre, accessibility, or research guidance.
|
|
15
15
|
|
|
16
16
|
## Choose the mode
|
|
17
17
|
|
|
@@ -28,3 +28,13 @@ Every design pass yields an observed journey, a north star (promise, pillars, an
|
|
|
28
28
|
An audit is not completion for a build/improve request. Finish when the slice is playable, a fresh player can state goal/choice/consequence, failure/recovery behave as intended, and evidence supports the pillars. Use `jgengine-verify` and `workflow` to ship.
|
|
29
29
|
|
|
30
30
|
**Required: player death is a designed, visible moment.** If the game can kill or down the player, death is part of the experience contract, not an invisible state reset. A lethal hit must resolve into an authored beat the player perceives and understands — a death or downed moment, the stakes it carries, and a legible path back into play (respawn, revive, restart) — never a silent teleport to spawn. Design what death means for this pitch (permadeath, checkpoint, bleed-out-and-revive, run reset) and treat "player dies with no acknowledged consequence" as a failure/recovery defect. `jgengine-ui` owns building the screen and respawn feedback.
|
|
31
|
+
|
|
32
|
+
## Persistent-builder checks
|
|
33
|
+
|
|
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
|
+
|
|
36
|
+
Declare currency `decimals` for income paid per second. Anchor time-based decay to `game.createdAt` or a persisted per-instance creation time, never a fixed wall-clock epoch. Test fractional income over real tick cadences before tuning prices.
|
|
37
|
+
|
|
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
|
+
|
|
40
|
+
For a persistent shared-world builder, compose neighborhood presence, declared command read scopes, membership storage, online-player batched ticks, territory, chat, and join retry UI through their engine owners. Verify capacity and first-player onboarding before scaling the content catalog.
|
package/skills/jgengine/SKILL.md
CHANGED
|
@@ -9,6 +9,8 @@ Read the JGengine README for package truth, commands, layering, and license —
|
|
|
9
9
|
|
|
10
10
|
## 1. Establish the target
|
|
11
11
|
|
|
12
|
+
For greenfield builds, write `src/art-direction.md` before any code.
|
|
13
|
+
|
|
12
14
|
Treat the pitch as a unique composition of needs, not a genre to fill in — never reach for a genre kit, preset, archetype, or class template ("default sports car", "default RPG", "default FPS") in the SDK or skills (see [AGENTS.md](../../../AGENTS.md)). Use `game-design` to turn the pitch into a testable player promise, verbs, loops, progression, failure, and completion scenario. Use `level-design` when play depends on authored spaces, routes, encounters, exploration, or spatial pacing. Capture only decisions that change architecture:
|
|
13
15
|
|
|
14
16
|
- player point of view and controls
|