@jgengine/core 0.7.0 → 0.9.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 +63 -0
- package/README.md +2 -0
- package/dist/ai/flock.d.ts +36 -0
- package/dist/ai/flock.js +182 -0
- package/dist/ai/groupAssist.d.ts +19 -0
- package/dist/ai/groupAssist.js +59 -0
- package/dist/ai/laneSelect.d.ts +21 -0
- package/dist/ai/laneSelect.js +37 -0
- package/dist/ai/mobBrain.d.ts +68 -0
- package/dist/ai/mobBrain.js +115 -0
- package/dist/ai/spawnDirector.d.ts +5 -0
- package/dist/ai/spawnDirector.js +14 -2
- package/dist/ai/threat.d.ts +2 -0
- package/dist/ai/threat.js +34 -0
- package/dist/anim/easing.d.ts +14 -0
- package/dist/anim/easing.js +55 -0
- package/dist/behaviour/behaviour.d.ts +136 -0
- package/dist/behaviour/behaviour.js +388 -0
- package/dist/cards/cardPile.d.ts +1 -0
- package/dist/cards/cardPile.js +12 -3
- package/dist/cartridge/runtime.d.ts +15 -0
- package/dist/cartridge/runtime.js +556 -0
- package/dist/cartridge/spec.d.ts +188 -0
- package/dist/cartridge/spec.js +19 -0
- package/dist/cartridge/validate.d.ts +2 -0
- package/dist/cartridge/validate.js +78 -0
- package/dist/combat/abilityKit.d.ts +22 -2
- package/dist/combat/abilityKit.js +72 -13
- package/dist/combat/castRunner.d.ts +40 -0
- package/dist/combat/castRunner.js +56 -0
- package/dist/combat/comboPoints.d.ts +14 -0
- package/dist/combat/comboPoints.js +52 -0
- package/dist/combat/defensiveWindow.d.ts +1 -1
- package/dist/combat/defensiveWindow.js +5 -3
- package/dist/combat/effects.d.ts +1 -1
- package/dist/combat/effects.js +9 -6
- package/dist/combat/projectiles.d.ts +64 -6
- package/dist/combat/projectiles.js +232 -74
- package/dist/combat/resistance.d.ts +16 -4
- package/dist/combat/resistance.js +43 -7
- package/dist/combat/resourcePool.d.ts +20 -0
- package/dist/combat/resourcePool.js +55 -0
- package/dist/combat/shotOrigin.d.ts +40 -0
- package/dist/combat/shotOrigin.js +57 -0
- package/dist/combat/telegraph.d.ts +30 -1
- package/dist/combat/telegraph.js +61 -0
- package/dist/commands/commandRegistry.d.ts +1 -1
- package/dist/commands/commandRegistry.js +2 -1
- package/dist/crafting/recipe.js +4 -1
- package/dist/data/dataSource.d.ts +26 -0
- package/dist/data/dataSource.js +93 -0
- package/dist/data/devProxy.d.ts +11 -0
- package/dist/data/devProxy.js +38 -0
- package/dist/data/fetchJson.d.ts +20 -0
- package/dist/data/fetchJson.js +43 -0
- package/dist/data/jsonDataSource.d.ts +4 -0
- package/dist/data/jsonDataSource.js +6 -0
- package/dist/devtools/devtools.d.ts +205 -0
- package/dist/devtools/devtools.js +832 -0
- package/dist/devtools/transformTunables.d.ts +14 -0
- package/dist/devtools/transformTunables.js +38 -0
- package/dist/devtools/tunableSchema.d.ts +140 -0
- package/dist/devtools/tunableSchema.js +476 -0
- package/dist/faction/factions.d.ts +33 -0
- package/dist/faction/factions.js +76 -0
- package/dist/faction/reputation.d.ts +32 -0
- package/dist/faction/reputation.js +82 -0
- package/dist/game/chat.d.ts +68 -0
- package/dist/game/chat.js +173 -0
- package/dist/game/chatFilter.d.ts +15 -0
- package/dist/game/chatFilter.js +55 -0
- package/dist/game/controlGate.d.ts +4 -0
- package/dist/game/controlGate.js +7 -0
- package/dist/game/defineGame.d.ts +10 -2
- package/dist/game/defineGame.js +2 -1
- package/dist/game/events.d.ts +35 -0
- package/dist/game/gamePhase.d.ts +20 -0
- package/dist/game/gamePhase.js +21 -0
- package/dist/game/levelSequence.d.ts +44 -0
- package/dist/game/levelSequence.js +78 -0
- package/dist/game/lootTable.d.ts +10 -1
- package/dist/game/lootTable.js +27 -7
- package/dist/game/playableGame.d.ts +286 -6
- package/dist/game/playableGame.js +6 -1
- package/dist/game/progression.d.ts +1 -0
- package/dist/game/progression.js +23 -2
- package/dist/game/quest.d.ts +7 -3
- package/dist/game/quest.js +17 -8
- package/dist/game/race.d.ts +44 -1
- package/dist/game/race.js +116 -11
- package/dist/game/recordBook.d.ts +29 -0
- package/dist/game/recordBook.js +86 -0
- package/dist/game/social.d.ts +42 -0
- package/dist/game/social.js +122 -0
- package/dist/game/spawnPoints.d.ts +19 -0
- package/dist/game/spawnPoints.js +23 -0
- package/dist/game/talents.d.ts +47 -0
- package/dist/game/talents.js +140 -0
- package/dist/game/worldItem.d.ts +3 -0
- package/dist/game/worldItem.js +12 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/input/actionBindings.d.ts +10 -0
- package/dist/input/actionBindings.js +14 -0
- package/dist/input/axisInput.d.ts +26 -3
- package/dist/input/axisInput.js +44 -4
- package/dist/input/bindingOverrides.d.ts +25 -0
- package/dist/input/bindingOverrides.js +95 -0
- package/dist/input/gestureSurface.d.ts +31 -0
- package/dist/input/gestureSurface.js +120 -0
- package/dist/input/pointer.d.ts +45 -0
- package/dist/input/pointer.js +48 -0
- package/dist/input/pointerAxis.d.ts +30 -0
- package/dist/input/pointerAxis.js +31 -0
- package/dist/input/touchScheme.d.ts +89 -0
- package/dist/input/touchScheme.js +153 -0
- package/dist/item/affix.d.ts +1 -1
- package/dist/item/affix.js +1 -19
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +59 -1
- package/dist/movement/dash.d.ts +3 -0
- package/dist/movement/dash.js +19 -5
- package/dist/movement/glideModel.d.ts +44 -0
- package/dist/movement/glideModel.js +54 -0
- package/dist/movement/grappleSwing.d.ts +34 -0
- package/dist/movement/grappleSwing.js +56 -0
- package/dist/movement/leaderTrail.d.ts +29 -0
- package/dist/movement/leaderTrail.js +67 -0
- package/dist/movement/movementModel.d.ts +36 -3
- package/dist/movement/movementModel.js +81 -5
- package/dist/movement/steering.d.ts +27 -0
- package/dist/movement/steering.js +40 -0
- package/dist/movement/voxelController.d.ts +61 -0
- package/dist/movement/voxelController.js +229 -0
- package/dist/multiplayer/chatContract.d.ts +42 -0
- package/dist/multiplayer/chatContract.js +57 -0
- package/dist/multiplayer/feedWriteGate.d.ts +10 -0
- package/dist/multiplayer/feedWriteGate.js +12 -0
- package/dist/multiplayer/identity.d.ts +13 -0
- package/dist/multiplayer/identity.js +20 -0
- package/dist/multiplayer/poseSyncGate.d.ts +1 -0
- package/dist/multiplayer/poseSyncGate.js +13 -1
- package/dist/multiplayer/presenceModel.d.ts +17 -2
- package/dist/multiplayer/presenceModel.js +30 -4
- package/dist/multiplayer/voiceContract.d.ts +44 -0
- package/dist/multiplayer/voiceContract.js +108 -0
- package/dist/nav/corridors.d.ts +30 -0
- package/dist/nav/corridors.js +69 -0
- package/dist/nav/navConstrain.d.ts +14 -0
- package/dist/nav/navConstrain.js +26 -0
- package/dist/nav/navFromEnvironment.d.ts +11 -0
- package/dist/nav/navFromEnvironment.js +16 -0
- package/dist/nav/navGrid.d.ts +10 -0
- package/dist/nav/navGrid.js +48 -5
- package/dist/nav/pathFollow.d.ts +4 -2
- package/dist/nav/pathFollow.js +5 -3
- package/dist/nav/railGraph.d.ts +66 -0
- package/dist/nav/railGraph.js +129 -0
- package/dist/nav/timetable.d.ts +41 -0
- package/dist/nav/timetable.js +94 -0
- package/dist/physics/ballisticSweep.d.ts +18 -0
- package/dist/physics/ballisticSweep.js +49 -0
- package/dist/physics/buoyancy.d.ts +6 -0
- package/dist/physics/buoyancy.js +14 -0
- package/dist/physics/flowTube.d.ts +32 -0
- package/dist/physics/flowTube.js +64 -0
- package/dist/physics/forceVolume.d.ts +27 -0
- package/dist/physics/forceVolume.js +43 -5
- package/dist/physics/kinematicVehicle.d.ts +52 -0
- package/dist/physics/kinematicVehicle.js +81 -0
- package/dist/physics/physicsWorld.d.ts +54 -8
- package/dist/physics/physicsWorld.js +400 -79
- package/dist/physics/ragdoll.js +1 -1
- package/dist/physics/spatialGrid.d.ts +1 -1
- package/dist/physics/spatialGrid.js +5 -0
- package/dist/physics/vehicleBody.d.ts +6 -0
- package/dist/physics/vehicleBody.js +18 -18
- package/dist/puzzle/cellGrid.d.ts +26 -0
- package/dist/puzzle/cellGrid.js +110 -0
- package/dist/puzzle/fallingPiece.d.ts +24 -0
- package/dist/puzzle/fallingPiece.js +51 -0
- package/dist/random/nameGen.d.ts +18 -0
- package/dist/random/nameGen.js +35 -0
- package/dist/random/rng.d.ts +2 -0
- package/dist/random/rng.js +23 -0
- package/dist/random/seedLink.d.ts +5 -0
- package/dist/random/seedLink.js +35 -0
- package/dist/runtime/adapter.d.ts +35 -0
- package/dist/runtime/adapter.js +37 -1
- package/dist/runtime/cameraDirector.d.ts +15 -0
- package/dist/runtime/cameraDirector.js +31 -0
- package/dist/runtime/commandRunner.d.ts +2 -2
- package/dist/runtime/commandRunner.js +2 -2
- package/dist/runtime/gameContext.d.ts +65 -2
- package/dist/runtime/gameContext.js +280 -9
- package/dist/runtime/gameRuntime.d.ts +5 -1
- package/dist/runtime/gameRuntime.js +13 -12
- package/dist/runtime/hostPersistence.d.ts +30 -1
- package/dist/runtime/hostPersistence.js +70 -5
- package/dist/runtime/inputSnapshot.d.ts +12 -0
- package/dist/runtime/inputSnapshot.js +17 -0
- package/dist/runtime/motionIntents.d.ts +33 -0
- package/dist/runtime/motionIntents.js +43 -0
- package/dist/runtime/snapshot.d.ts +1 -0
- package/dist/runtime/snapshot.js +1 -1
- package/dist/runtime/transport.d.ts +32 -0
- package/dist/runtime/visibility.d.ts +74 -0
- package/dist/runtime/visibility.js +127 -0
- package/dist/scene/colliders.d.ts +44 -0
- package/dist/scene/colliders.js +77 -0
- package/dist/scene/entityStore.d.ts +30 -5
- package/dist/scene/entityStore.js +83 -13
- package/dist/scene/movementSpeed.d.ts +35 -0
- package/dist/scene/movementSpeed.js +20 -0
- package/dist/scene/objectQuery.d.ts +29 -0
- package/dist/scene/objectQuery.js +140 -0
- package/dist/scene/objectStore.d.ts +13 -0
- package/dist/scene/objectStore.js +130 -6
- package/dist/scene/paintLayer.d.ts +15 -0
- package/dist/scene/paintLayer.js +53 -0
- package/dist/scene/sceneRaycast.d.ts +80 -0
- package/dist/scene/sceneRaycast.js +284 -0
- package/dist/scene/spatial.d.ts +17 -1
- package/dist/scene/spatial.js +140 -17
- package/dist/sensor/concealment.d.ts +21 -0
- package/dist/sensor/concealment.js +63 -0
- package/dist/sensor/freezeMonitor.d.ts +17 -0
- package/dist/sensor/freezeMonitor.js +35 -0
- package/dist/sensor/recordingBuffer.d.ts +6 -0
- package/dist/sensor/recordingBuffer.js +7 -0
- package/dist/sensor/replayLoop.d.ts +39 -0
- package/dist/sensor/replayLoop.js +64 -0
- package/dist/sensor/visionCone.d.ts +31 -0
- package/dist/sensor/visionCone.js +72 -0
- package/dist/session/extraction.d.ts +2 -2
- package/dist/session/extraction.js +13 -2
- package/dist/session/roles.d.ts +6 -0
- package/dist/session/roles.js +32 -0
- package/dist/session/roundState.d.ts +41 -17
- package/dist/session/roundState.js +67 -38
- package/dist/settings/settingsModel.d.ts +97 -0
- package/dist/settings/settingsModel.js +100 -0
- package/dist/stats/statModifiers.d.ts +5 -2
- package/dist/stats/statModifiers.js +32 -14
- package/dist/store/observableKeyedStore.js +8 -2
- package/dist/survival/decayMeter.d.ts +1 -1
- package/dist/survival/decayMeter.js +0 -1
- package/dist/tactics/fallingGrid.d.ts +62 -0
- package/dist/tactics/fallingGrid.js +158 -0
- package/dist/tactics/tacticalGrid.d.ts +11 -0
- package/dist/tactics/tacticalGrid.js +17 -0
- package/dist/time/beatClock.d.ts +18 -0
- package/dist/time/beatClock.js +24 -0
- package/dist/time/idleProgress.d.ts +29 -0
- package/dist/time/idleProgress.js +31 -0
- package/dist/time/serverTick.d.ts +7 -1
- package/dist/time/serverTick.js +27 -4
- package/dist/time/simClock.d.ts +25 -1
- package/dist/time/simClock.js +28 -3
- package/dist/time/stateSchedule.d.ts +57 -0
- package/dist/time/stateSchedule.js +121 -0
- package/dist/turn/commit.d.ts +2 -2
- package/dist/turn/commit.js +2 -2
- package/dist/turn/intent.d.ts +14 -0
- package/dist/turn/intent.js +29 -0
- package/dist/turn/turnLoop.d.ts +2 -0
- package/dist/turn/turnLoop.js +8 -1
- package/dist/ui/hudLayout.d.ts +58 -0
- package/dist/ui/hudLayout.js +207 -0
- package/dist/ui/hudScale.d.ts +44 -0
- package/dist/ui/hudScale.js +55 -0
- package/dist/visibility/assetStreaming.d.ts +66 -0
- package/dist/visibility/assetStreaming.js +201 -0
- package/dist/visibility/bounds.d.ts +66 -0
- package/dist/visibility/bounds.js +113 -0
- package/dist/visibility/camera.d.ts +21 -0
- package/dist/visibility/camera.js +3 -0
- package/dist/visibility/config.d.ts +21 -0
- package/dist/visibility/config.js +1 -0
- package/dist/visibility/diagnostics.d.ts +26 -0
- package/dist/visibility/diagnostics.js +36 -0
- package/dist/visibility/distance.d.ts +11 -0
- package/dist/visibility/distance.js +30 -0
- package/dist/visibility/frustum.d.ts +47 -0
- package/dist/visibility/frustum.js +186 -0
- package/dist/visibility/occlusion.d.ts +36 -0
- package/dist/visibility/occlusion.js +45 -0
- package/dist/visibility/settings.d.ts +75 -0
- package/dist/visibility/settings.js +49 -0
- package/dist/visibility/simulationCulling.d.ts +34 -0
- package/dist/visibility/simulationCulling.js +36 -0
- package/dist/visibility/spatialIndex.d.ts +45 -0
- package/dist/visibility/spatialIndex.js +198 -0
- package/dist/visibility/visibilitySystem.d.ts +90 -0
- package/dist/visibility/visibilitySystem.js +257 -0
- package/dist/world/buildings.d.ts +6 -0
- package/dist/world/buildings.js +152 -0
- package/dist/world/carve.d.ts +7 -2
- package/dist/world/carve.js +17 -7
- package/dist/world/cellStates.d.ts +45 -0
- package/dist/world/cellStates.js +103 -0
- package/dist/world/environmentSummary.d.ts +61 -0
- package/dist/world/environmentSummary.js +138 -0
- package/dist/world/features.d.ts +138 -9
- package/dist/world/features.js +73 -2
- package/dist/world/gridInstances.d.ts +12 -0
- package/dist/world/gridInstances.js +160 -0
- package/dist/world/lod.d.ts +17 -0
- package/dist/world/lod.js +66 -0
- package/dist/world/mapLayers.d.ts +60 -0
- package/dist/world/mapLayers.js +44 -0
- package/dist/world/massing.d.ts +45 -0
- package/dist/world/massing.js +581 -0
- package/dist/world/minimap.d.ts +13 -1
- package/dist/world/minimap.js +21 -2
- package/dist/world/placementController.d.ts +22 -1
- package/dist/world/placementController.js +56 -0
- package/dist/world/terrain.d.ts +62 -1
- package/dist/world/terrain.js +253 -9
- package/dist/world/voxelField.d.ts +54 -0
- package/dist/world/voxelField.js +241 -0
- package/dist/world/water.d.ts +7 -0
- package/dist/world/water.js +19 -4
- package/dist/world/weather.d.ts +3 -18
- package/dist/world/weather.js +4 -17
- package/dist/world/windZones.d.ts +47 -0
- package/dist/world/windZones.js +59 -0
- package/llms.txt +4983 -0
- package/package.json +14 -3
|
@@ -13,6 +13,7 @@ export function createGameRuntime(definition) {
|
|
|
13
13
|
server: input.serverRow,
|
|
14
14
|
players: input.playersByUserId,
|
|
15
15
|
chunks: input.chunksByKey,
|
|
16
|
+
revision: input.revision,
|
|
16
17
|
});
|
|
17
18
|
if (loop?.onInit && !initialized) {
|
|
18
19
|
initialized = true;
|
|
@@ -34,18 +35,18 @@ export function createGameRuntime(definition) {
|
|
|
34
35
|
if (!loop?.onTick)
|
|
35
36
|
return snapshot;
|
|
36
37
|
let current = snapshot;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
38
|
+
const ctx = {
|
|
39
|
+
get snapshot() {
|
|
40
|
+
return current;
|
|
41
|
+
},
|
|
42
|
+
get playerIds() {
|
|
43
|
+
return Object.keys(current.players);
|
|
44
|
+
},
|
|
45
|
+
setSnapshot(next) {
|
|
46
|
+
current = next;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
loop.onTick(ctx, dtSeconds);
|
|
49
50
|
return current;
|
|
50
51
|
},
|
|
51
52
|
joinPlayer(snapshot, userId, isNew) {
|
|
@@ -62,6 +62,27 @@ export declare function drainPendingLeaderboardIncrements(session: Record<string
|
|
|
62
62
|
increments: LeaderboardIncrement[];
|
|
63
63
|
session: Record<string, unknown>;
|
|
64
64
|
};
|
|
65
|
+
export declare const LEADERBOARD_TOP_LIMIT = 100;
|
|
66
|
+
export declare function clampLimit(value: number | undefined, fallback: number, max: number): number;
|
|
67
|
+
export type LeaderboardRow = {
|
|
68
|
+
gameId: string;
|
|
69
|
+
stat: string;
|
|
70
|
+
scope: LeaderboardScope;
|
|
71
|
+
serverId?: string;
|
|
72
|
+
userId: string;
|
|
73
|
+
value: number;
|
|
74
|
+
updatedAt: number;
|
|
75
|
+
};
|
|
76
|
+
export declare function leaderboardRowKey(row: Omit<LeaderboardRow, "value" | "updatedAt">): string;
|
|
77
|
+
export declare function applyLeaderboardRows(rows: Map<string, LeaderboardRow>, gameId: string, entries: LeaderboardIncrement[], now: number): void;
|
|
78
|
+
export declare function topLeaderboardRows(rows: Iterable<LeaderboardRow>, args: {
|
|
79
|
+
gameId: string;
|
|
80
|
+
stat: string;
|
|
81
|
+
scope: LeaderboardScope;
|
|
82
|
+
serverId?: string;
|
|
83
|
+
limit?: number;
|
|
84
|
+
}): LeaderboardEntry[];
|
|
85
|
+
export declare function profileLeaderboardStats(rows: Iterable<LeaderboardRow>, gameId: string, userId: string): Record<string, number>;
|
|
65
86
|
export declare const FEED_RING_LIMIT = 20;
|
|
66
87
|
export declare function trimFeedEntries<T>(entries: T[], limit?: number): T[];
|
|
67
88
|
export declare function shouldAutoSave(save: SaveConfig, dirtyAt: number | undefined, lastSavedAt: number | undefined, now: number): boolean;
|
|
@@ -77,7 +98,13 @@ export type ServerListing = {
|
|
|
77
98
|
tags?: string[];
|
|
78
99
|
updatedAt: number;
|
|
79
100
|
};
|
|
80
|
-
export
|
|
101
|
+
export type ToServerListingOptions = {
|
|
102
|
+
includeJoinCode?: boolean;
|
|
103
|
+
};
|
|
104
|
+
export declare function toServerListing(record: GameServerRecord, options?: ToServerListingOptions): ServerListing;
|
|
105
|
+
export declare const OPEN_SERVER_LISTING_LIMIT = 20;
|
|
106
|
+
export declare const OPEN_SERVER_LISTING_MAX = 100;
|
|
107
|
+
export declare function toOpenServerListings(listings: Iterable<ServerListing>, limit?: number): ServerListing[];
|
|
81
108
|
export type HostPersistence = {
|
|
82
109
|
savePlan?: (plan: ServerPersistPlan) => Promise<void>;
|
|
83
110
|
resetScenario?: (reset: NormalizedScenarioReset) => Promise<void>;
|
|
@@ -91,6 +118,7 @@ export type HostPersistence = {
|
|
|
91
118
|
saveProfile: (record: PlayerProfileRecord) => Promise<void>;
|
|
92
119
|
loadChunks: (serverId: string) => Promise<WorldChunkRecord[]>;
|
|
93
120
|
saveChunks: (serverId: string, chunks: WorldChunkRecord[]) => Promise<void>;
|
|
121
|
+
deleteChunks: (serverId: string, chunkKeys: string[]) => Promise<void>;
|
|
94
122
|
loadFeed: (args: {
|
|
95
123
|
serverId: string;
|
|
96
124
|
action: string;
|
|
@@ -118,6 +146,7 @@ export type ServerPersistPlan = {
|
|
|
118
146
|
server: GameServerRecord;
|
|
119
147
|
profiles: PlayerProfileRecord[];
|
|
120
148
|
chunks: WorldChunkRecord[];
|
|
149
|
+
deletedChunks: string[];
|
|
121
150
|
leaderboard: LeaderboardIncrement[];
|
|
122
151
|
};
|
|
123
152
|
export declare function planServerPersist(server: GameServerRecord, snapshot: GameRuntimeSnapshot, save: SaveConfig, now: number): ServerPersistPlan;
|
|
@@ -20,6 +20,56 @@ export function drainPendingLeaderboardIncrements(session) {
|
|
|
20
20
|
delete rest[LEADERBOARD_PENDING_KEY];
|
|
21
21
|
return { increments: raw.filter(isLeaderboardIncrement), session: rest };
|
|
22
22
|
}
|
|
23
|
+
export const LEADERBOARD_TOP_LIMIT = 100;
|
|
24
|
+
export function clampLimit(value, fallback, max) {
|
|
25
|
+
const candidate = value ?? fallback;
|
|
26
|
+
if (!Number.isFinite(candidate))
|
|
27
|
+
return fallback;
|
|
28
|
+
return Math.min(Math.max(Math.floor(candidate), 1), max);
|
|
29
|
+
}
|
|
30
|
+
export function leaderboardRowKey(row) {
|
|
31
|
+
return [row.gameId, row.scope, row.stat, row.serverId ?? "", row.userId].join("|");
|
|
32
|
+
}
|
|
33
|
+
export function applyLeaderboardRows(rows, gameId, entries, now) {
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
const key = leaderboardRowKey({ ...entry, gameId });
|
|
36
|
+
const existing = rows.get(key);
|
|
37
|
+
if (existing) {
|
|
38
|
+
rows.set(key, { ...existing, value: existing.value + entry.by, updatedAt: now });
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
rows.set(key, {
|
|
42
|
+
gameId,
|
|
43
|
+
stat: entry.stat,
|
|
44
|
+
scope: entry.scope,
|
|
45
|
+
serverId: entry.serverId,
|
|
46
|
+
userId: entry.userId,
|
|
47
|
+
value: entry.by,
|
|
48
|
+
updatedAt: now,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function topLeaderboardRows(rows, args) {
|
|
54
|
+
const limit = clampLimit(args.limit, LEADERBOARD_TOP_LIMIT, LEADERBOARD_TOP_LIMIT);
|
|
55
|
+
return [...rows]
|
|
56
|
+
.filter((row) => row.gameId === args.gameId &&
|
|
57
|
+
row.stat === args.stat &&
|
|
58
|
+
row.scope === args.scope &&
|
|
59
|
+
(args.serverId === undefined || row.serverId === args.serverId))
|
|
60
|
+
.sort((a, b) => b.value - a.value)
|
|
61
|
+
.slice(0, limit)
|
|
62
|
+
.map((row) => ({ userId: row.userId, value: row.value }));
|
|
63
|
+
}
|
|
64
|
+
export function profileLeaderboardStats(rows, gameId, userId) {
|
|
65
|
+
const profile = {};
|
|
66
|
+
for (const row of rows) {
|
|
67
|
+
if (row.gameId === gameId && row.userId === userId && row.scope === "profile") {
|
|
68
|
+
profile[row.stat] = row.value;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return profile;
|
|
72
|
+
}
|
|
23
73
|
export const FEED_RING_LIMIT = 20;
|
|
24
74
|
export function trimFeedEntries(entries, limit = FEED_RING_LIMIT) {
|
|
25
75
|
if (entries.length <= limit)
|
|
@@ -35,7 +85,7 @@ export function shouldAutoSave(save, dirtyAt, lastSavedAt, now) {
|
|
|
35
85
|
const anchor = lastSavedAt ?? 0;
|
|
36
86
|
return now - anchor >= intervalMs;
|
|
37
87
|
}
|
|
38
|
-
export function toServerListing(record) {
|
|
88
|
+
export function toServerListing(record, options = {}) {
|
|
39
89
|
return {
|
|
40
90
|
serverId: record.serverId,
|
|
41
91
|
status: record.status,
|
|
@@ -44,11 +94,21 @@ export function toServerListing(record) {
|
|
|
44
94
|
mode: record.mode,
|
|
45
95
|
label: record.label,
|
|
46
96
|
visibility: record.visibility,
|
|
47
|
-
|
|
97
|
+
...(options.includeJoinCode === true && record.joinCode !== undefined
|
|
98
|
+
? { joinCode: record.joinCode }
|
|
99
|
+
: {}),
|
|
48
100
|
tags: record.tags,
|
|
49
101
|
updatedAt: record.updatedAt,
|
|
50
102
|
};
|
|
51
103
|
}
|
|
104
|
+
export const OPEN_SERVER_LISTING_LIMIT = 20;
|
|
105
|
+
export const OPEN_SERVER_LISTING_MAX = 100;
|
|
106
|
+
export function toOpenServerListings(listings, limit = OPEN_SERVER_LISTING_LIMIT) {
|
|
107
|
+
return [...listings]
|
|
108
|
+
.filter((listing) => listing.status === "running")
|
|
109
|
+
.sort((a, b) => b.updatedAt - a.updatedAt)
|
|
110
|
+
.slice(0, clampLimit(limit, OPEN_SERVER_LISTING_LIMIT, OPEN_SERVER_LISTING_MAX));
|
|
111
|
+
}
|
|
52
112
|
export function buildHydratePlayers(server, profiles) {
|
|
53
113
|
const players = {};
|
|
54
114
|
for (const userId of server.memberUserIds) {
|
|
@@ -96,12 +156,16 @@ export function planServerPersist(server, snapshot, save, now) {
|
|
|
96
156
|
}
|
|
97
157
|
}
|
|
98
158
|
const chunks = [];
|
|
159
|
+
const deletedChunks = [];
|
|
99
160
|
if (isSaveEnabled(save) && saveScopeIncludesChunks(save.scope)) {
|
|
100
161
|
for (const chunkKey of snapshot.dirty.chunks) {
|
|
101
162
|
const chunk = snapshot.chunks[chunkKey];
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
163
|
+
if (chunk) {
|
|
164
|
+
chunks.push({ serverId: server.serverId, chunkKey, snapshot: chunk, updatedAt: now });
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
deletedChunks.push(chunkKey);
|
|
168
|
+
}
|
|
105
169
|
}
|
|
106
170
|
}
|
|
107
171
|
return {
|
|
@@ -116,6 +180,7 @@ export function planServerPersist(server, snapshot, save, now) {
|
|
|
116
180
|
},
|
|
117
181
|
profiles,
|
|
118
182
|
chunks,
|
|
183
|
+
deletedChunks,
|
|
119
184
|
leaderboard,
|
|
120
185
|
};
|
|
121
186
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PointerAxisState } from "../input/pointerAxis.js";
|
|
2
|
+
export interface InputSnapshot {
|
|
3
|
+
/** Replaces the held-action set for this frame. Called by the shell before `onTick` each frame; does not bump `ctx.version()` or notify `ctx.subscribe` listeners — per-frame publishes would storm subscribers. */
|
|
4
|
+
publish(held: readonly string[]): void;
|
|
5
|
+
/** Replaces the normalized pointer-position state for this frame (#293). Same no-notify contract as `publish`. */
|
|
6
|
+
publishPointer(state: PointerAxisState | null): void;
|
|
7
|
+
isDown(action: string): boolean;
|
|
8
|
+
held(): readonly string[];
|
|
9
|
+
/** Pointer position over the play surface, `[-1, 1]` per axis with `+y` down, published by the shell each frame; `null` until the first pointer move. */
|
|
10
|
+
pointer(): PointerAxisState | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function createInputSnapshot(): InputSnapshot;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function createInputSnapshot() {
|
|
2
|
+
let heldSet = new Set();
|
|
3
|
+
let heldList = [];
|
|
4
|
+
let pointerState = null;
|
|
5
|
+
return {
|
|
6
|
+
publish(held) {
|
|
7
|
+
heldList = held;
|
|
8
|
+
heldSet = new Set(held);
|
|
9
|
+
},
|
|
10
|
+
publishPointer(state) {
|
|
11
|
+
pointerState = state;
|
|
12
|
+
},
|
|
13
|
+
isDown: (action) => heldSet.has(action),
|
|
14
|
+
held: () => heldList,
|
|
15
|
+
pointer: () => pointerState,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seam for game code to reach the motion the shell's FrameDriver
|
|
3
|
+
* otherwise owns privately (#162.4). Game code calls `impulse`,
|
|
4
|
+
* `pushHorizontal`, `setVerticalVelocity`, and/or `setY` from `onTick` or
|
|
5
|
+
* commands; the shell calls `takePending()` once per frame, before
|
|
6
|
+
* integrating gravity, to drain what accumulated. `setY` wins over physics
|
|
7
|
+
* for that frame; impulses add to the velocity the driver is about to
|
|
8
|
+
* integrate; a later `setVerticalVelocity` replaces that velocity outright.
|
|
9
|
+
* Horizontal pushes compose with the walk controller (#282.4): they add to
|
|
10
|
+
* its horizontal velocity and decay naturally as it re-blends toward input —
|
|
11
|
+
* knockback, dashes, explosion shoves without raw `setPose` offsets.
|
|
12
|
+
*
|
|
13
|
+
* Intentionally not `ctx.version()`/`ctx.subscribe` wired: these fire at
|
|
14
|
+
* most once per frame and would otherwise storm subscribers with per-frame
|
|
15
|
+
* churn (same rationale as `InputSnapshot.publish`).
|
|
16
|
+
*/
|
|
17
|
+
export interface MotionIntents {
|
|
18
|
+
impulse(velocityY: number): void;
|
|
19
|
+
/** Adds a horizontal velocity kick (world units/s) that the walk controller integrates and decays. */
|
|
20
|
+
pushHorizontal(velocityX: number, velocityZ: number): void;
|
|
21
|
+
setVerticalVelocity(velocityY: number): void;
|
|
22
|
+
setY(y: number): void;
|
|
23
|
+
takePending(): MotionIntentBatch | null;
|
|
24
|
+
}
|
|
25
|
+
export interface MotionIntentBatch {
|
|
26
|
+
impulses: readonly number[];
|
|
27
|
+
horizontalImpulses: readonly (readonly [number, number])[];
|
|
28
|
+
verticalVelocity: number | null;
|
|
29
|
+
y: number | null;
|
|
30
|
+
}
|
|
31
|
+
export declare function createMotionIntents(): MotionIntents;
|
|
32
|
+
/** Fold a batch's horizontal pushes into a controller's velocity pair — shared by the walk and voxel drivers. */
|
|
33
|
+
export declare function applyHorizontalImpulses(velocityX: number, velocityZ: number, batch: MotionIntentBatch | null): readonly [number, number];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function createMotionIntents() {
|
|
2
|
+
let impulses = [];
|
|
3
|
+
let horizontalImpulses = [];
|
|
4
|
+
let verticalVelocity = null;
|
|
5
|
+
let y = null;
|
|
6
|
+
return {
|
|
7
|
+
impulse(velocityY) {
|
|
8
|
+
impulses.push(velocityY);
|
|
9
|
+
},
|
|
10
|
+
pushHorizontal(velocityX, velocityZ) {
|
|
11
|
+
horizontalImpulses.push([velocityX, velocityZ]);
|
|
12
|
+
},
|
|
13
|
+
setVerticalVelocity(velocityY) {
|
|
14
|
+
verticalVelocity = velocityY;
|
|
15
|
+
},
|
|
16
|
+
setY(nextY) {
|
|
17
|
+
y = nextY;
|
|
18
|
+
},
|
|
19
|
+
takePending() {
|
|
20
|
+
if (impulses.length === 0 && horizontalImpulses.length === 0 && verticalVelocity === null && y === null) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const batch = { impulses, horizontalImpulses, verticalVelocity, y };
|
|
24
|
+
impulses = [];
|
|
25
|
+
horizontalImpulses = [];
|
|
26
|
+
verticalVelocity = null;
|
|
27
|
+
y = null;
|
|
28
|
+
return batch;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Fold a batch's horizontal pushes into a controller's velocity pair — shared by the walk and voxel drivers. */
|
|
33
|
+
export function applyHorizontalImpulses(velocityX, velocityZ, batch) {
|
|
34
|
+
if (batch === null || batch.horizontalImpulses.length === 0)
|
|
35
|
+
return [velocityX, velocityZ];
|
|
36
|
+
let x = velocityX;
|
|
37
|
+
let z = velocityZ;
|
|
38
|
+
for (const [ix, iz] of batch.horizontalImpulses) {
|
|
39
|
+
x += ix;
|
|
40
|
+
z += iz;
|
|
41
|
+
}
|
|
42
|
+
return [x, z];
|
|
43
|
+
}
|
|
@@ -77,6 +77,7 @@ export declare function createRuntimeSnapshot(args: {
|
|
|
77
77
|
server?: RuntimeServerRow;
|
|
78
78
|
players?: Record<string, RuntimePlayerRow>;
|
|
79
79
|
chunks?: Record<string, RuntimeChunkRow>;
|
|
80
|
+
revision?: number;
|
|
80
81
|
}): GameRuntimeSnapshot;
|
|
81
82
|
export declare function markServerDirty(snapshot: GameRuntimeSnapshot): GameRuntimeSnapshot;
|
|
82
83
|
export declare function markPlayerDirty(snapshot: GameRuntimeSnapshot, userId: string): GameRuntimeSnapshot;
|
package/dist/runtime/snapshot.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ChatSync } from "../multiplayer/chatContract.js";
|
|
2
|
+
import type { PlayerPose } from "../multiplayer/poseSyncGate.js";
|
|
1
3
|
import type { PresenceTransport } from "../multiplayer/presenceContract.js";
|
|
2
4
|
export type JoinServerResult = {
|
|
3
5
|
serverId: string;
|
|
@@ -58,3 +60,33 @@ export type GameBackend<TPresenceRow = unknown, TPresenceLocation = unknown, TGa
|
|
|
58
60
|
feeds?: GameRuntimeFeeds;
|
|
59
61
|
presence?: PresenceTransport<TPresenceRow, TPresenceLocation, TGameId>;
|
|
60
62
|
};
|
|
63
|
+
export type PresencePoseRow = {
|
|
64
|
+
userId: string;
|
|
65
|
+
position: {
|
|
66
|
+
x: number;
|
|
67
|
+
y: number;
|
|
68
|
+
z: number;
|
|
69
|
+
};
|
|
70
|
+
rotationY: number;
|
|
71
|
+
rotationPitch: number;
|
|
72
|
+
lastSeenAt: number;
|
|
73
|
+
};
|
|
74
|
+
export type PresenceSync = {
|
|
75
|
+
subscribe: (serverId: string, onChange: (rows: PresencePoseRow[]) => void) => FeedUnsubscribe;
|
|
76
|
+
syncPose: (serverId: string, pose: PlayerPose) => void;
|
|
77
|
+
};
|
|
78
|
+
export type LiveGameBackend<TPresenceRow = unknown, TPresenceLocation = unknown, TGameId extends string = string> = GameBackend<TPresenceRow, TPresenceLocation, TGameId> & {
|
|
79
|
+
presenceSync: PresenceSync;
|
|
80
|
+
pushFeedEntry: (args: {
|
|
81
|
+
serverId: string;
|
|
82
|
+
action: string;
|
|
83
|
+
entry: unknown;
|
|
84
|
+
}) => Promise<void>;
|
|
85
|
+
chatSyncFor?: (serverId: string) => ChatSync;
|
|
86
|
+
};
|
|
87
|
+
export type MultiplayerSession = {
|
|
88
|
+
gameId: string;
|
|
89
|
+
userId: string;
|
|
90
|
+
backend: LiveGameBackend;
|
|
91
|
+
feedActions: string[];
|
|
92
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** Stable identifier for a visibility-tracked object or camera. */
|
|
2
|
+
export type VisibilityId = string | number;
|
|
3
|
+
/** World-space point; `z` is optional for 2D adapters. */
|
|
4
|
+
export interface VisibilityPoint {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
z?: number;
|
|
8
|
+
}
|
|
9
|
+
/** Sphere bounds used for visibility/streaming distance checks. */
|
|
10
|
+
export interface VisibilityBounds {
|
|
11
|
+
center: VisibilityPoint;
|
|
12
|
+
radius: number;
|
|
13
|
+
}
|
|
14
|
+
/** A camera's position and reach, as seen by the visibility system. */
|
|
15
|
+
export interface CameraVisibilityContext {
|
|
16
|
+
id: VisibilityId;
|
|
17
|
+
position: VisibilityPoint;
|
|
18
|
+
/** Approximate visible radius in world units. Adapters may derive this from a frustum. */
|
|
19
|
+
visibleRadius: number;
|
|
20
|
+
/** Extra distance used to request assets before an object becomes visible. */
|
|
21
|
+
preloadMargin?: number;
|
|
22
|
+
/** Cameras such as editor thumbnails can opt out of influencing streaming. */
|
|
23
|
+
influencesStreaming?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Per-object escape hatches that bypass or tune the default visibility policy. */
|
|
26
|
+
export interface VisibilityOverrides {
|
|
27
|
+
alwaysVisible?: boolean;
|
|
28
|
+
neverUnload?: boolean;
|
|
29
|
+
cullingDisabled?: boolean;
|
|
30
|
+
streamingDisabled?: boolean;
|
|
31
|
+
minRenderDistance?: number;
|
|
32
|
+
maxRenderDistance?: number;
|
|
33
|
+
preloadMargin?: number;
|
|
34
|
+
}
|
|
35
|
+
/** An entity/asset tracked by `VisibilitySystem.evaluate`. */
|
|
36
|
+
export interface VisibilityObject {
|
|
37
|
+
id: VisibilityId;
|
|
38
|
+
bounds: VisibilityBounds;
|
|
39
|
+
overrides?: VisibilityOverrides;
|
|
40
|
+
}
|
|
41
|
+
/** Baseline policy settings a `VisibilitySystem` is constructed with. */
|
|
42
|
+
export interface VisibilityDefaults {
|
|
43
|
+
enabled: boolean;
|
|
44
|
+
streamingEnabled: boolean;
|
|
45
|
+
minRenderDistance: number;
|
|
46
|
+
maxRenderDistance: number;
|
|
47
|
+
preloadMargin: number;
|
|
48
|
+
unloadGraceMs: number;
|
|
49
|
+
hysteresis: number;
|
|
50
|
+
}
|
|
51
|
+
/** Default `VisibilityDefaults` a `VisibilitySystem` falls back to when unspecified. */
|
|
52
|
+
export declare const DEFAULT_VISIBILITY_SETTINGS: Readonly<VisibilityDefaults>;
|
|
53
|
+
/** Result of evaluating one object against the active cameras this tick. */
|
|
54
|
+
export interface VisibilityDecision {
|
|
55
|
+
render: boolean;
|
|
56
|
+
load: boolean;
|
|
57
|
+
unload: boolean;
|
|
58
|
+
reason: "always-visible" | "culling-disabled" | "visible" | "preload" | "distance-culled" | "outside-view" | "grace-period" | "streaming-disabled";
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Engine-level visibility and asset-residency policy.
|
|
62
|
+
*
|
|
63
|
+
* Render culling and asset streaming are intentionally separate. This class never
|
|
64
|
+
* destroys entities or throttles simulation; adapters decide how to apply results.
|
|
65
|
+
*/
|
|
66
|
+
export declare class VisibilitySystem {
|
|
67
|
+
#private;
|
|
68
|
+
readonly settings: VisibilityDefaults;
|
|
69
|
+
constructor(settings?: Partial<VisibilityDefaults>);
|
|
70
|
+
evaluate(object: VisibilityObject, cameras: readonly CameraVisibilityContext[], now?: number): VisibilityDecision;
|
|
71
|
+
setLoaded(id: VisibilityId, loaded: boolean, now?: number): void;
|
|
72
|
+
forget(id: VisibilityId): void;
|
|
73
|
+
clear(): void;
|
|
74
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/** Default `VisibilityDefaults` a `VisibilitySystem` falls back to when unspecified. */
|
|
2
|
+
export const DEFAULT_VISIBILITY_SETTINGS = Object.freeze({
|
|
3
|
+
enabled: true,
|
|
4
|
+
streamingEnabled: true,
|
|
5
|
+
minRenderDistance: 0,
|
|
6
|
+
maxRenderDistance: Number.POSITIVE_INFINITY,
|
|
7
|
+
preloadMargin: 24,
|
|
8
|
+
unloadGraceMs: 5_000,
|
|
9
|
+
hysteresis: 8,
|
|
10
|
+
});
|
|
11
|
+
function squaredDistance(a, b) {
|
|
12
|
+
const dx = a.x - b.x;
|
|
13
|
+
const dy = a.y - b.y;
|
|
14
|
+
const dz = (a.z ?? 0) - (b.z ?? 0);
|
|
15
|
+
return dx * dx + dy * dy + dz * dz;
|
|
16
|
+
}
|
|
17
|
+
function intersectsRadius(camera, bounds, extraMargin) {
|
|
18
|
+
const radius = Math.max(0, camera.visibleRadius + extraMargin + bounds.radius);
|
|
19
|
+
return squaredDistance(camera.position, bounds.center) <= radius * radius;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Engine-level visibility and asset-residency policy.
|
|
23
|
+
*
|
|
24
|
+
* Render culling and asset streaming are intentionally separate. This class never
|
|
25
|
+
* destroys entities or throttles simulation; adapters decide how to apply results.
|
|
26
|
+
*/
|
|
27
|
+
export class VisibilitySystem {
|
|
28
|
+
settings;
|
|
29
|
+
#states = new Map();
|
|
30
|
+
constructor(settings = {}) {
|
|
31
|
+
this.settings = { ...DEFAULT_VISIBILITY_SETTINGS, ...settings };
|
|
32
|
+
}
|
|
33
|
+
evaluate(object, cameras, now = Date.now()) {
|
|
34
|
+
const overrides = object.overrides ?? {};
|
|
35
|
+
const state = this.#states.get(object.id) ?? { loaded: false, lastNeededAt: now };
|
|
36
|
+
if (overrides.alwaysVisible) {
|
|
37
|
+
this.#markNeeded(object.id, state, now);
|
|
38
|
+
return { render: true, load: true, unload: false, reason: "always-visible" };
|
|
39
|
+
}
|
|
40
|
+
if (!this.settings.enabled || overrides.cullingDisabled) {
|
|
41
|
+
this.#markNeeded(object.id, state, now);
|
|
42
|
+
return { render: true, load: true, unload: false, reason: "culling-disabled" };
|
|
43
|
+
}
|
|
44
|
+
const minDistance = Math.max(0, overrides.minRenderDistance ?? this.settings.minRenderDistance);
|
|
45
|
+
const maxDistance = Math.max(minDistance, overrides.maxRenderDistance ?? this.settings.maxRenderDistance);
|
|
46
|
+
const minDistanceSq = minDistance * minDistance;
|
|
47
|
+
const maxDistanceWithRadius = maxDistance + object.bounds.radius;
|
|
48
|
+
const maxDistanceSq = maxDistanceWithRadius * maxDistanceWithRadius;
|
|
49
|
+
let insideDistance = false;
|
|
50
|
+
let visible = false;
|
|
51
|
+
let preload = false;
|
|
52
|
+
for (const camera of cameras) {
|
|
53
|
+
const distanceSq = squaredDistance(camera.position, object.bounds.center);
|
|
54
|
+
if (distanceSq < minDistanceSq || distanceSq > maxDistanceSq)
|
|
55
|
+
continue;
|
|
56
|
+
insideDistance = true;
|
|
57
|
+
if (intersectsRadius(camera, object.bounds, this.settings.hysteresis)) {
|
|
58
|
+
visible = true;
|
|
59
|
+
}
|
|
60
|
+
if (camera.influencesStreaming !== false) {
|
|
61
|
+
const margin = overrides.preloadMargin ?? camera.preloadMargin ?? this.settings.preloadMargin;
|
|
62
|
+
if (intersectsRadius(camera, object.bounds, margin + this.settings.hysteresis)) {
|
|
63
|
+
preload = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (visible) {
|
|
68
|
+
this.#markNeeded(object.id, state, now);
|
|
69
|
+
return { render: true, load: true, unload: false, reason: "visible" };
|
|
70
|
+
}
|
|
71
|
+
if (overrides.streamingDisabled || !this.settings.streamingEnabled) {
|
|
72
|
+
state.loaded = true;
|
|
73
|
+
this.#states.set(object.id, state);
|
|
74
|
+
return {
|
|
75
|
+
render: false,
|
|
76
|
+
load: true,
|
|
77
|
+
unload: false,
|
|
78
|
+
reason: "streaming-disabled",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (preload) {
|
|
82
|
+
this.#markNeeded(object.id, state, now);
|
|
83
|
+
return { render: false, load: true, unload: false, reason: "preload" };
|
|
84
|
+
}
|
|
85
|
+
if (overrides.neverUnload) {
|
|
86
|
+
state.loaded = true;
|
|
87
|
+
this.#states.set(object.id, state);
|
|
88
|
+
return {
|
|
89
|
+
render: false,
|
|
90
|
+
load: true,
|
|
91
|
+
unload: false,
|
|
92
|
+
reason: insideDistance ? "outside-view" : "distance-culled",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const graceActive = state.loaded && now - state.lastNeededAt < this.settings.unloadGraceMs;
|
|
96
|
+
if (graceActive) {
|
|
97
|
+
this.#states.set(object.id, state);
|
|
98
|
+
return { render: false, load: true, unload: false, reason: "grace-period" };
|
|
99
|
+
}
|
|
100
|
+
state.loaded = false;
|
|
101
|
+
this.#states.set(object.id, state);
|
|
102
|
+
return {
|
|
103
|
+
render: false,
|
|
104
|
+
load: false,
|
|
105
|
+
unload: true,
|
|
106
|
+
reason: insideDistance ? "outside-view" : "distance-culled",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
setLoaded(id, loaded, now = Date.now()) {
|
|
110
|
+
const previous = this.#states.get(id);
|
|
111
|
+
this.#states.set(id, {
|
|
112
|
+
loaded,
|
|
113
|
+
lastNeededAt: loaded ? now : previous?.lastNeededAt ?? now,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
forget(id) {
|
|
117
|
+
this.#states.delete(id);
|
|
118
|
+
}
|
|
119
|
+
clear() {
|
|
120
|
+
this.#states.clear();
|
|
121
|
+
}
|
|
122
|
+
#markNeeded(id, state, now) {
|
|
123
|
+
state.loaded = true;
|
|
124
|
+
state.lastNeededAt = now;
|
|
125
|
+
this.#states.set(id, state);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { EntityPosition } from "./entityStore.js";
|
|
2
|
+
export type ColliderPurpose = "physical" | "damage";
|
|
3
|
+
export type ColliderShape = {
|
|
4
|
+
kind: "sphere";
|
|
5
|
+
radius: number;
|
|
6
|
+
offset?: EntityPosition;
|
|
7
|
+
} | {
|
|
8
|
+
kind: "aabb";
|
|
9
|
+
halfExtents: EntityPosition;
|
|
10
|
+
offset?: EntityPosition;
|
|
11
|
+
};
|
|
12
|
+
export interface ColliderDef {
|
|
13
|
+
name: string;
|
|
14
|
+
purpose: ColliderPurpose;
|
|
15
|
+
shape: ColliderShape;
|
|
16
|
+
/** Defaults true for `purpose: "damage"`, false for `purpose: "physical"`. */
|
|
17
|
+
damageEligible?: boolean;
|
|
18
|
+
/** Defaults true — a blocking collider stops a ray at first impact. */
|
|
19
|
+
blocks?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface EntityColliderSet {
|
|
22
|
+
/** Single physical collision body (push / obstruction). */
|
|
23
|
+
body?: ColliderDef;
|
|
24
|
+
/** Named damage hitboxes (zero or more). */
|
|
25
|
+
hitboxes?: readonly ColliderDef[];
|
|
26
|
+
}
|
|
27
|
+
export interface ResolvedCollider {
|
|
28
|
+
name: string;
|
|
29
|
+
purpose: ColliderPurpose;
|
|
30
|
+
shape: ColliderShape;
|
|
31
|
+
damageEligible: boolean;
|
|
32
|
+
blocks: boolean;
|
|
33
|
+
}
|
|
34
|
+
export declare const DEFAULT_ENTITY_HIT_RADIUS = 0.5;
|
|
35
|
+
export declare const DEFAULT_OBJECT_HALF_EXTENTS: EntityPosition;
|
|
36
|
+
export declare function defaultEntityColliders(): EntityColliderSet;
|
|
37
|
+
export declare function defaultObjectColliders(halfExtents?: EntityPosition): EntityColliderSet;
|
|
38
|
+
export declare function resolveColliders(set: EntityColliderSet | null | undefined): ResolvedCollider[];
|
|
39
|
+
export declare function worldOffset(local: EntityPosition | undefined, position: EntityPosition, rotationY: number): EntityPosition;
|
|
40
|
+
export declare function colliderWorldCenter(collider: ResolvedCollider, position: EntityPosition, rotationY: number): EntityPosition;
|
|
41
|
+
export declare function colliderBounds(collider: ResolvedCollider, position: EntityPosition, rotationY: number): {
|
|
42
|
+
min: EntityPosition;
|
|
43
|
+
max: EntityPosition;
|
|
44
|
+
};
|