@jgengine/core 0.5.0 → 0.7.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 +191 -2
- package/dist/ai/crowd.d.ts +36 -0
- package/dist/ai/crowd.js +213 -0
- package/dist/ai/jobBoard.d.ts +49 -0
- package/dist/ai/jobBoard.js +155 -0
- package/dist/ai/spawnDirector.d.ts +56 -0
- package/dist/ai/spawnDirector.js +150 -0
- package/dist/ai/threat.d.ts +25 -0
- package/dist/ai/threat.js +67 -0
- package/dist/audio/audioFalloff.d.ts +38 -0
- package/dist/audio/audioFalloff.js +32 -0
- package/dist/board/laneBoard.d.ts +63 -0
- package/dist/board/laneBoard.js +114 -0
- package/dist/board/timelineBoard.d.ts +36 -0
- package/dist/board/timelineBoard.js +70 -0
- package/dist/cards/cardPile.d.ts +63 -0
- package/dist/cards/cardPile.js +166 -0
- package/dist/cards/modifierPipeline.d.ts +33 -0
- package/dist/cards/modifierPipeline.js +48 -0
- package/dist/combat/abilityKit.d.ts +42 -0
- package/dist/combat/abilityKit.js +129 -0
- package/dist/combat/animationState.d.ts +47 -0
- package/dist/combat/animationState.js +101 -0
- package/dist/combat/attackTags.d.ts +14 -0
- package/dist/combat/attackTags.js +26 -0
- package/dist/combat/breakMeters.d.ts +32 -0
- package/dist/combat/breakMeters.js +51 -0
- package/dist/combat/comboString.d.ts +35 -0
- package/dist/combat/comboString.js +59 -0
- package/dist/combat/death.d.ts +8 -0
- package/dist/combat/death.js +5 -2
- package/dist/combat/defensiveWindow.d.ts +34 -0
- package/dist/combat/defensiveWindow.js +66 -0
- package/dist/combat/downed.d.ts +35 -0
- package/dist/combat/downed.js +104 -0
- package/dist/combat/effects.d.ts +11 -0
- package/dist/combat/effects.js +22 -13
- package/dist/combat/hitReaction.d.ts +23 -0
- package/dist/combat/hitReaction.js +20 -0
- package/dist/combat/resistance.d.ts +13 -0
- package/dist/combat/resistance.js +46 -0
- package/dist/combat/telegraph.d.ts +27 -0
- package/dist/combat/telegraph.js +37 -0
- package/dist/crafting/crop.d.ts +59 -0
- package/dist/crafting/crop.js +181 -0
- package/dist/crafting/production.d.ts +64 -0
- package/dist/crafting/production.js +130 -0
- package/dist/crafting/recipe.d.ts +62 -0
- package/dist/crafting/recipe.js +101 -0
- package/dist/economy/sharedWallet.d.ts +28 -0
- package/dist/economy/sharedWallet.js +62 -0
- package/dist/economy/techTree.d.ts +40 -0
- package/dist/economy/techTree.js +66 -0
- package/dist/game/cosmetics.d.ts +25 -0
- package/dist/game/cosmetics.js +51 -0
- package/dist/game/defineGame.d.ts +5 -0
- package/dist/game/events.d.ts +62 -0
- package/dist/game/lootFilter.d.ts +33 -0
- package/dist/game/lootFilter.js +48 -0
- package/dist/game/ping.d.ts +67 -0
- package/dist/game/ping.js +86 -0
- package/dist/game/playableGame.d.ts +291 -5
- package/dist/game/race.d.ts +95 -0
- package/dist/game/race.js +216 -0
- package/dist/game/runDraft.d.ts +34 -0
- package/dist/game/runDraft.js +97 -0
- package/dist/game/social.d.ts +27 -0
- package/dist/game/social.js +16 -0
- package/dist/game/worldItem.d.ts +92 -0
- package/dist/game/worldItem.js +132 -0
- package/dist/input/axisInput.d.ts +43 -0
- package/dist/input/axisInput.js +70 -0
- package/dist/input/pointer.d.ts +24 -0
- package/dist/input/pointer.js +17 -0
- package/dist/interaction/contextMenu.d.ts +30 -0
- package/dist/interaction/contextMenu.js +22 -0
- package/dist/interaction/qte.d.ts +20 -0
- package/dist/interaction/qte.js +24 -0
- package/dist/interaction/skillCheck.d.ts +20 -0
- package/dist/interaction/skillCheck.js +26 -0
- package/dist/inventory/shapedGrid.d.ts +56 -0
- package/dist/inventory/shapedGrid.js +176 -0
- package/dist/inventory/storageTier.d.ts +52 -0
- package/dist/inventory/storageTier.js +77 -0
- package/dist/item/affix.d.ts +54 -0
- package/dist/item/affix.js +133 -0
- package/dist/item/durability.d.ts +51 -0
- package/dist/item/durability.js +78 -0
- package/dist/item/modularItem.d.ts +46 -0
- package/dist/item/modularItem.js +75 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +53 -1
- package/dist/movement/dash.d.ts +37 -0
- package/dist/movement/dash.js +65 -0
- package/dist/multiplayer/combatSnapshot.d.ts +45 -0
- package/dist/multiplayer/combatSnapshot.js +89 -0
- package/dist/multiplayer/lagCompensation.d.ts +43 -0
- package/dist/multiplayer/lagCompensation.js +154 -0
- package/dist/multiplayer/matchmaking.d.ts +33 -0
- package/dist/multiplayer/matchmaking.js +60 -0
- package/dist/multiplayer/simultaneousCommit.d.ts +34 -0
- package/dist/multiplayer/simultaneousCommit.js +53 -0
- package/dist/nav/navGrid.d.ts +46 -0
- package/dist/nav/navGrid.js +248 -0
- package/dist/nav/pathFollow.d.ts +29 -0
- package/dist/nav/pathFollow.js +67 -0
- package/dist/physics/buoyancy.d.ts +49 -0
- package/dist/physics/buoyancy.js +112 -0
- package/dist/physics/carryable.d.ts +46 -0
- package/dist/physics/carryable.js +110 -0
- package/dist/physics/damageZones.d.ts +56 -0
- package/dist/physics/damageZones.js +107 -0
- package/dist/physics/forceVolume.d.ts +48 -0
- package/dist/physics/forceVolume.js +119 -0
- package/dist/physics/physicsWorld.d.ts +219 -0
- package/dist/physics/physicsWorld.js +749 -0
- package/dist/physics/ragdoll.d.ts +49 -0
- package/dist/physics/ragdoll.js +103 -0
- package/dist/physics/spatialGrid.d.ts +41 -0
- package/dist/physics/spatialGrid.js +138 -0
- package/dist/physics/structure.d.ts +83 -0
- package/dist/physics/structure.js +0 -0
- package/dist/physics/traversal.d.ts +75 -0
- package/dist/physics/traversal.js +138 -0
- package/dist/physics/vehicleBody.d.ts +93 -0
- package/dist/physics/vehicleBody.js +234 -0
- package/dist/runtime/gameContext.d.ts +66 -2
- package/dist/runtime/gameContext.js +157 -4
- package/dist/runtime/hostPersistence.d.ts +18 -0
- package/dist/runtime/hostPersistence.js +4 -0
- package/dist/runtime/persistenceScope.d.ts +32 -0
- package/dist/runtime/persistenceScope.js +64 -0
- package/dist/scene/assetCatalog.d.ts +13 -0
- package/dist/scene/autoTarget.d.ts +15 -0
- package/dist/scene/autoTarget.js +54 -0
- package/dist/scene/behaviors.d.ts +13 -1
- package/dist/scene/behaviors.js +3 -0
- package/dist/scene/captureCheck.d.ts +7 -0
- package/dist/scene/captureCheck.js +11 -0
- package/dist/scene/entityStore.d.ts +7 -1
- package/dist/scene/entityStore.js +15 -1
- package/dist/scene/form.d.ts +40 -0
- package/dist/scene/form.js +56 -0
- package/dist/scene/mount.d.ts +62 -0
- package/dist/scene/mount.js +107 -0
- package/dist/scene/possession.d.ts +30 -0
- package/dist/scene/possession.js +51 -0
- package/dist/scene/roster.d.ts +25 -0
- package/dist/scene/roster.js +57 -0
- package/dist/scene/selection.d.ts +30 -0
- package/dist/scene/selection.js +38 -0
- package/dist/scene/stationClaim.d.ts +43 -0
- package/dist/scene/stationClaim.js +109 -0
- package/dist/sensor/frustumSensor.d.ts +60 -0
- package/dist/sensor/frustumSensor.js +104 -0
- package/dist/sensor/hiddenStateProbe.d.ts +31 -0
- package/dist/sensor/hiddenStateProbe.js +40 -0
- package/dist/sensor/recordingBuffer.d.ts +29 -0
- package/dist/sensor/recordingBuffer.js +61 -0
- package/dist/sensor/revealQuery.d.ts +22 -0
- package/dist/sensor/revealQuery.js +36 -0
- package/dist/session/contestedChannel.d.ts +36 -0
- package/dist/session/contestedChannel.js +100 -0
- package/dist/session/extraction.d.ts +54 -0
- package/dist/session/extraction.js +108 -0
- package/dist/session/ring.d.ts +36 -0
- package/dist/session/ring.js +88 -0
- package/dist/session/roundState.d.ts +55 -0
- package/dist/session/roundState.js +129 -0
- package/dist/stats/accumulatorMeter.d.ts +31 -0
- package/dist/stats/accumulatorMeter.js +81 -0
- package/dist/stats/eventMeter.d.ts +34 -0
- package/dist/stats/eventMeter.js +76 -0
- package/dist/stats/rollCheck.d.ts +15 -0
- package/dist/stats/rollCheck.js +15 -0
- package/dist/survival/decayMeter.d.ts +59 -0
- package/dist/survival/decayMeter.js +95 -0
- package/dist/survival/moodle.d.ts +48 -0
- package/dist/survival/moodle.js +91 -0
- package/dist/survival/regionHealth.d.ts +72 -0
- package/dist/survival/regionHealth.js +129 -0
- package/dist/tactics/predictiveQuery.d.ts +34 -0
- package/dist/tactics/predictiveQuery.js +44 -0
- package/dist/tactics/snapshot.d.ts +18 -0
- package/dist/tactics/snapshot.js +65 -0
- package/dist/tactics/surface.d.ts +52 -0
- package/dist/tactics/surface.js +135 -0
- package/dist/tactics/tacticalGrid.d.ts +57 -0
- package/dist/tactics/tacticalGrid.js +244 -0
- package/dist/time/beatClock.d.ts +40 -0
- package/dist/time/beatClock.js +73 -0
- package/dist/time/simClock.d.ts +62 -0
- package/dist/time/simClock.js +132 -0
- package/dist/turn/commit.d.ts +27 -0
- package/dist/turn/commit.js +72 -0
- package/dist/turn/turnLoop.d.ts +58 -0
- package/dist/turn/turnLoop.js +216 -0
- package/dist/world/buildPermissions.d.ts +47 -0
- package/dist/world/buildPermissions.js +134 -0
- package/dist/world/buildingIndex.d.ts +16 -0
- package/dist/world/buildingIndex.js +63 -0
- package/dist/world/buildings.d.ts +98 -0
- package/dist/world/buildings.js +320 -0
- package/dist/world/carve.d.ts +105 -0
- package/dist/world/carve.js +175 -0
- package/dist/world/connectors.d.ts +40 -0
- package/dist/world/connectors.js +67 -0
- package/dist/world/envField.d.ts +71 -0
- package/dist/world/envField.js +96 -0
- package/dist/world/features.d.ts +105 -1
- package/dist/world/features.js +95 -0
- package/dist/world/fog.d.ts +40 -0
- package/dist/world/fog.js +137 -0
- package/dist/world/markers.d.ts +52 -0
- package/dist/world/markers.js +96 -0
- package/dist/world/minimap.d.ts +42 -0
- package/dist/world/minimap.js +72 -0
- package/dist/world/placedStructureStore.d.ts +39 -0
- package/dist/world/placedStructureStore.js +95 -0
- package/dist/world/placementController.d.ts +48 -0
- package/dist/world/placementController.js +97 -0
- package/dist/world/realm.d.ts +61 -0
- package/dist/world/realm.js +66 -0
- package/dist/world/regions.d.ts +66 -0
- package/dist/world/regions.js +158 -0
- package/dist/world/scatter.d.ts +24 -0
- package/dist/world/scatter.js +126 -0
- package/dist/world/scatterItems.d.ts +38 -0
- package/dist/world/scatterItems.js +85 -0
- package/dist/world/support.d.ts +27 -0
- package/dist/world/support.js +75 -0
- package/dist/world/terraform.d.ts +54 -0
- package/dist/world/terraform.js +186 -0
- package/dist/world/terrain.d.ts +54 -0
- package/dist/world/terrain.js +181 -0
- package/dist/world/walls.d.ts +61 -0
- package/dist/world/walls.js +221 -0
- package/dist/world/water.d.ts +27 -0
- package/dist/world/water.js +125 -0
- package/dist/world/weather.d.ts +92 -0
- package/dist/world/weather.js +169 -0
- package/dist/world/wind.d.ts +17 -0
- package/dist/world/wind.js +40 -0
- package/package.json +1 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { CollisionEvent } from "./physicsWorld.js";
|
|
2
|
+
export interface DamageZoneDef {
|
|
3
|
+
id: string;
|
|
4
|
+
thresholds: readonly number[];
|
|
5
|
+
detachStage?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface DamageZoneState {
|
|
8
|
+
id: string;
|
|
9
|
+
accumulated: number;
|
|
10
|
+
stage: number;
|
|
11
|
+
detached: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface DamageTransition {
|
|
14
|
+
zone: string;
|
|
15
|
+
stage: number;
|
|
16
|
+
previousStage: number;
|
|
17
|
+
detached: boolean;
|
|
18
|
+
disabled: boolean;
|
|
19
|
+
accumulated: number;
|
|
20
|
+
total: number;
|
|
21
|
+
}
|
|
22
|
+
export interface DamageModelConfig {
|
|
23
|
+
zones: readonly DamageZoneDef[];
|
|
24
|
+
disableAt?: number;
|
|
25
|
+
onStage?: (transition: DamageTransition) => void;
|
|
26
|
+
onDetach?: (zoneId: string, accumulated: number) => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Coarse crash-damage model (issue #86 — stages, not soft-body). Each zone accumulates contact impulse
|
|
30
|
+
* fed from G3's `onCollision` `CollisionEvent`; crossing a threshold bumps the zone to the next discrete
|
|
31
|
+
* stage (the caller swaps the visual/collider), an optional `detachStage` ejects a part as debris once,
|
|
32
|
+
* and a total-impulse `disableAt` flips the whole body to a disabled state. `absorb` returns a transition
|
|
33
|
+
* only when something actually changed, so it doubles as the event source for HUD/feed.
|
|
34
|
+
*/
|
|
35
|
+
export declare class DamageModel {
|
|
36
|
+
private readonly zones;
|
|
37
|
+
private readonly order;
|
|
38
|
+
private readonly disableAt;
|
|
39
|
+
private readonly onStage?;
|
|
40
|
+
private readonly onDetach?;
|
|
41
|
+
private totalImpulse;
|
|
42
|
+
private disabledFlag;
|
|
43
|
+
constructor(config: DamageModelConfig);
|
|
44
|
+
get total(): number;
|
|
45
|
+
get disabled(): boolean;
|
|
46
|
+
stageOf(zoneId: string): number;
|
|
47
|
+
states(): readonly DamageZoneState[];
|
|
48
|
+
absorb(zoneId: string, impulse: number): DamageTransition | null;
|
|
49
|
+
/**
|
|
50
|
+
* Route a raw {@link CollisionEvent} into a zone by a caller-supplied selector (front/rear/side from
|
|
51
|
+
* `nx`/`nz`, or which body index took the hit). The event's `impulse` feeds `absorb`.
|
|
52
|
+
*/
|
|
53
|
+
routeCollision(event: CollisionEvent, resolveZone: (event: CollisionEvent) => string | null): DamageTransition | null;
|
|
54
|
+
reset(): void;
|
|
55
|
+
}
|
|
56
|
+
export declare function createDamageModel(config: DamageModelConfig): DamageModel;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
function stageFor(thresholds, accumulated) {
|
|
2
|
+
let stage = 0;
|
|
3
|
+
for (const t of thresholds) {
|
|
4
|
+
if (accumulated >= t)
|
|
5
|
+
stage += 1;
|
|
6
|
+
else
|
|
7
|
+
break;
|
|
8
|
+
}
|
|
9
|
+
return stage;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Coarse crash-damage model (issue #86 — stages, not soft-body). Each zone accumulates contact impulse
|
|
13
|
+
* fed from G3's `onCollision` `CollisionEvent`; crossing a threshold bumps the zone to the next discrete
|
|
14
|
+
* stage (the caller swaps the visual/collider), an optional `detachStage` ejects a part as debris once,
|
|
15
|
+
* and a total-impulse `disableAt` flips the whole body to a disabled state. `absorb` returns a transition
|
|
16
|
+
* only when something actually changed, so it doubles as the event source for HUD/feed.
|
|
17
|
+
*/
|
|
18
|
+
export class DamageModel {
|
|
19
|
+
zones = new Map();
|
|
20
|
+
order = [];
|
|
21
|
+
disableAt;
|
|
22
|
+
onStage;
|
|
23
|
+
onDetach;
|
|
24
|
+
totalImpulse = 0;
|
|
25
|
+
disabledFlag = false;
|
|
26
|
+
constructor(config) {
|
|
27
|
+
for (const def of config.zones) {
|
|
28
|
+
this.zones.set(def.id, { def, state: { id: def.id, accumulated: 0, stage: 0, detached: false } });
|
|
29
|
+
this.order.push(def.id);
|
|
30
|
+
}
|
|
31
|
+
this.disableAt = config.disableAt ?? null;
|
|
32
|
+
this.onStage = config.onStage;
|
|
33
|
+
this.onDetach = config.onDetach;
|
|
34
|
+
}
|
|
35
|
+
get total() {
|
|
36
|
+
return this.totalImpulse;
|
|
37
|
+
}
|
|
38
|
+
get disabled() {
|
|
39
|
+
return this.disabledFlag;
|
|
40
|
+
}
|
|
41
|
+
stageOf(zoneId) {
|
|
42
|
+
return this.zones.get(zoneId)?.state.stage ?? 0;
|
|
43
|
+
}
|
|
44
|
+
states() {
|
|
45
|
+
return this.order.map((id) => {
|
|
46
|
+
const s = this.zones.get(id).state;
|
|
47
|
+
return { id: s.id, accumulated: s.accumulated, stage: s.stage, detached: s.detached };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
absorb(zoneId, impulse) {
|
|
51
|
+
const entry = this.zones.get(zoneId);
|
|
52
|
+
if (entry === undefined || impulse <= 0)
|
|
53
|
+
return null;
|
|
54
|
+
const { def, state } = entry;
|
|
55
|
+
const previousStage = state.stage;
|
|
56
|
+
const wasDisabled = this.disabledFlag;
|
|
57
|
+
state.accumulated += impulse;
|
|
58
|
+
this.totalImpulse += impulse;
|
|
59
|
+
state.stage = stageFor(def.thresholds, state.accumulated);
|
|
60
|
+
let detachedNow = false;
|
|
61
|
+
if (def.detachStage !== undefined &&
|
|
62
|
+
!state.detached &&
|
|
63
|
+
state.stage >= def.detachStage) {
|
|
64
|
+
state.detached = true;
|
|
65
|
+
detachedNow = true;
|
|
66
|
+
this.onDetach?.(zoneId, state.accumulated);
|
|
67
|
+
}
|
|
68
|
+
if (this.disableAt !== null && this.totalImpulse >= this.disableAt)
|
|
69
|
+
this.disabledFlag = true;
|
|
70
|
+
const changed = state.stage !== previousStage || detachedNow || this.disabledFlag !== wasDisabled;
|
|
71
|
+
if (!changed)
|
|
72
|
+
return null;
|
|
73
|
+
const transition = {
|
|
74
|
+
zone: zoneId,
|
|
75
|
+
stage: state.stage,
|
|
76
|
+
previousStage,
|
|
77
|
+
detached: state.detached,
|
|
78
|
+
disabled: this.disabledFlag,
|
|
79
|
+
accumulated: state.accumulated,
|
|
80
|
+
total: this.totalImpulse,
|
|
81
|
+
};
|
|
82
|
+
this.onStage?.(transition);
|
|
83
|
+
return transition;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Route a raw {@link CollisionEvent} into a zone by a caller-supplied selector (front/rear/side from
|
|
87
|
+
* `nx`/`nz`, or which body index took the hit). The event's `impulse` feeds `absorb`.
|
|
88
|
+
*/
|
|
89
|
+
routeCollision(event, resolveZone) {
|
|
90
|
+
const zone = resolveZone(event);
|
|
91
|
+
if (zone === null)
|
|
92
|
+
return null;
|
|
93
|
+
return this.absorb(zone, Math.abs(event.impulse));
|
|
94
|
+
}
|
|
95
|
+
reset() {
|
|
96
|
+
for (const { state } of this.zones.values()) {
|
|
97
|
+
state.accumulated = 0;
|
|
98
|
+
state.stage = 0;
|
|
99
|
+
state.detached = false;
|
|
100
|
+
}
|
|
101
|
+
this.totalImpulse = 0;
|
|
102
|
+
this.disabledFlag = false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function createDamageModel(config) {
|
|
106
|
+
return new DamageModel(config);
|
|
107
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { PhysicsBounds, PhysicsWorld } from "./physicsWorld.js";
|
|
2
|
+
export type ForceMode = "impulse" | "velocity" | "accelerate";
|
|
3
|
+
export interface ForceVolumeConfig {
|
|
4
|
+
/** Region a body's center must be inside to be affected. */
|
|
5
|
+
bounds: PhysicsBounds;
|
|
6
|
+
/** `impulse` adds to velocity, `velocity` sets it, `accelerate` adds force·dt each tick. */
|
|
7
|
+
force: readonly [number, number, number];
|
|
8
|
+
/** Default `impulse`. */
|
|
9
|
+
mode?: ForceMode;
|
|
10
|
+
/** Fire only when a body first enters (boost pad) rather than every tick inside (fan/wind). Default false. */
|
|
11
|
+
once?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A trigger region that pushes bodies passing through it — boost pads (`impulse` + `once`),
|
|
15
|
+
* conveyors (`velocity`), fans/wind (`accelerate`). Call `apply` each tick; `once` mode fires only
|
|
16
|
+
* on entry by tracking membership between ticks.
|
|
17
|
+
*/
|
|
18
|
+
export declare class ForceVolume {
|
|
19
|
+
readonly bounds: PhysicsBounds;
|
|
20
|
+
readonly mode: ForceMode;
|
|
21
|
+
readonly once: boolean;
|
|
22
|
+
private readonly fx;
|
|
23
|
+
private readonly fy;
|
|
24
|
+
private readonly fz;
|
|
25
|
+
private members;
|
|
26
|
+
private nextMembers;
|
|
27
|
+
constructor(config: ForceVolumeConfig);
|
|
28
|
+
apply(world: PhysicsWorld, dt: number): void;
|
|
29
|
+
}
|
|
30
|
+
export interface PlatformCarryConfig {
|
|
31
|
+
/** Vertical gap between a rider's base and the platform top counted as "standing on". Default 0.12. */
|
|
32
|
+
contactTolerance?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Carries bodies standing on a moving platform by composing their transform with the platform's
|
|
36
|
+
* per-`step` delta — moving/rotating lifts and conveyor floors (Fall Guys, Gang Beasts). The
|
|
37
|
+
* platform is a body the game repositions each frame; riders are detected by overlap on its top face.
|
|
38
|
+
*/
|
|
39
|
+
export declare class PlatformCarry {
|
|
40
|
+
private readonly world;
|
|
41
|
+
private readonly platform;
|
|
42
|
+
private readonly tolerance;
|
|
43
|
+
private prevX;
|
|
44
|
+
private prevY;
|
|
45
|
+
private prevZ;
|
|
46
|
+
constructor(world: PhysicsWorld, platform: number, config?: PlatformCarryConfig);
|
|
47
|
+
step(): void;
|
|
48
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
function inside(bounds, x, y, z) {
|
|
2
|
+
return (x >= bounds.min[0] &&
|
|
3
|
+
x <= bounds.max[0] &&
|
|
4
|
+
y >= bounds.min[1] &&
|
|
5
|
+
y <= bounds.max[1] &&
|
|
6
|
+
z >= bounds.min[2] &&
|
|
7
|
+
z <= bounds.max[2]);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A trigger region that pushes bodies passing through it — boost pads (`impulse` + `once`),
|
|
11
|
+
* conveyors (`velocity`), fans/wind (`accelerate`). Call `apply` each tick; `once` mode fires only
|
|
12
|
+
* on entry by tracking membership between ticks.
|
|
13
|
+
*/
|
|
14
|
+
export class ForceVolume {
|
|
15
|
+
bounds;
|
|
16
|
+
mode;
|
|
17
|
+
once;
|
|
18
|
+
fx;
|
|
19
|
+
fy;
|
|
20
|
+
fz;
|
|
21
|
+
members = new Set();
|
|
22
|
+
nextMembers = new Set();
|
|
23
|
+
constructor(config) {
|
|
24
|
+
this.bounds = config.bounds;
|
|
25
|
+
this.mode = config.mode ?? "impulse";
|
|
26
|
+
this.once = config.once ?? false;
|
|
27
|
+
this.fx = config.force[0];
|
|
28
|
+
this.fy = config.force[1];
|
|
29
|
+
this.fz = config.force[2];
|
|
30
|
+
}
|
|
31
|
+
apply(world, dt) {
|
|
32
|
+
const next = this.nextMembers;
|
|
33
|
+
next.clear();
|
|
34
|
+
const count = world.count;
|
|
35
|
+
for (let i = 0; i < count; i += 1) {
|
|
36
|
+
if (world.invMass[i] === 0)
|
|
37
|
+
continue;
|
|
38
|
+
if (!inside(this.bounds, world.posX[i], world.posY[i], world.posZ[i]))
|
|
39
|
+
continue;
|
|
40
|
+
const wasInside = this.members.has(i);
|
|
41
|
+
next.add(i);
|
|
42
|
+
if (this.once && wasInside)
|
|
43
|
+
continue;
|
|
44
|
+
switch (this.mode) {
|
|
45
|
+
case "velocity":
|
|
46
|
+
world.velX[i] = this.fx;
|
|
47
|
+
world.velY[i] = this.fy;
|
|
48
|
+
world.velZ[i] = this.fz;
|
|
49
|
+
break;
|
|
50
|
+
case "accelerate":
|
|
51
|
+
world.velX[i] += this.fx * dt;
|
|
52
|
+
world.velY[i] += this.fy * dt;
|
|
53
|
+
world.velZ[i] += this.fz * dt;
|
|
54
|
+
break;
|
|
55
|
+
default:
|
|
56
|
+
world.velX[i] += this.fx;
|
|
57
|
+
world.velY[i] += this.fy;
|
|
58
|
+
world.velZ[i] += this.fz;
|
|
59
|
+
}
|
|
60
|
+
world.wake(i);
|
|
61
|
+
}
|
|
62
|
+
this.nextMembers = this.members;
|
|
63
|
+
this.members = next;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Carries bodies standing on a moving platform by composing their transform with the platform's
|
|
68
|
+
* per-`step` delta — moving/rotating lifts and conveyor floors (Fall Guys, Gang Beasts). The
|
|
69
|
+
* platform is a body the game repositions each frame; riders are detected by overlap on its top face.
|
|
70
|
+
*/
|
|
71
|
+
export class PlatformCarry {
|
|
72
|
+
world;
|
|
73
|
+
platform;
|
|
74
|
+
tolerance;
|
|
75
|
+
prevX;
|
|
76
|
+
prevY;
|
|
77
|
+
prevZ;
|
|
78
|
+
constructor(world, platform, config = {}) {
|
|
79
|
+
this.world = world;
|
|
80
|
+
this.platform = platform;
|
|
81
|
+
this.tolerance = config.contactTolerance ?? 0.12;
|
|
82
|
+
this.prevX = world.posX[platform];
|
|
83
|
+
this.prevY = world.posY[platform];
|
|
84
|
+
this.prevZ = world.posZ[platform];
|
|
85
|
+
}
|
|
86
|
+
step() {
|
|
87
|
+
const w = this.world;
|
|
88
|
+
const p = this.platform;
|
|
89
|
+
const dx = w.posX[p] - this.prevX;
|
|
90
|
+
const dy = w.posY[p] - this.prevY;
|
|
91
|
+
const dz = w.posZ[p] - this.prevZ;
|
|
92
|
+
const topY = this.prevY + w.halfY[p];
|
|
93
|
+
const px = this.prevX;
|
|
94
|
+
const pz = this.prevZ;
|
|
95
|
+
this.prevX = w.posX[p];
|
|
96
|
+
this.prevY = w.posY[p];
|
|
97
|
+
this.prevZ = w.posZ[p];
|
|
98
|
+
if (dx === 0 && dy === 0 && dz === 0)
|
|
99
|
+
return;
|
|
100
|
+
const hx = w.halfX[p];
|
|
101
|
+
const hz = w.halfZ[p];
|
|
102
|
+
const count = w.count;
|
|
103
|
+
for (let i = 0; i < count; i += 1) {
|
|
104
|
+
if (i === p)
|
|
105
|
+
continue;
|
|
106
|
+
const base = w.posY[i] - w.halfY[i];
|
|
107
|
+
if (base < topY - this.tolerance || base > topY + this.tolerance)
|
|
108
|
+
continue;
|
|
109
|
+
if (Math.abs(w.posX[i] - px) > hx + w.halfX[i])
|
|
110
|
+
continue;
|
|
111
|
+
if (Math.abs(w.posZ[i] - pz) > hz + w.halfZ[i])
|
|
112
|
+
continue;
|
|
113
|
+
w.posX[i] += dx;
|
|
114
|
+
w.posY[i] += dy;
|
|
115
|
+
w.posZ[i] += dz;
|
|
116
|
+
w.wake(i);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
export interface PhysicsBounds {
|
|
2
|
+
min: readonly [number, number, number];
|
|
3
|
+
max: readonly [number, number, number];
|
|
4
|
+
}
|
|
5
|
+
export interface PhysicsWorldConfig {
|
|
6
|
+
/** Maximum bodies the SoA buffers can hold. Fixed at construction; no reallocation later. */
|
|
7
|
+
capacity: number;
|
|
8
|
+
/** AABB container; every body is constrained inside it (floor included). */
|
|
9
|
+
bounds: PhysicsBounds;
|
|
10
|
+
/** Downward acceleration applied to awake bodies each substep. Default -20. */
|
|
11
|
+
gravity?: number;
|
|
12
|
+
/** Broadphase cell edge; tune to ≈ small-body diameter. Default 1. */
|
|
13
|
+
cellSize?: number;
|
|
14
|
+
/** Fixed substep length in seconds. Default 1/60. */
|
|
15
|
+
fixedDt?: number;
|
|
16
|
+
/** Cap on substeps per `step()` — bounds catch-up work, drops the rest. Default 4. */
|
|
17
|
+
maxSubsteps?: number;
|
|
18
|
+
/** Bounce factor on contacts and walls, 0..1. Default 0.2. */
|
|
19
|
+
restitution?: number;
|
|
20
|
+
/** Approach speed below which restitution is ignored (contacts rest instead of jittering). Default 1. */
|
|
21
|
+
restitutionThreshold?: number;
|
|
22
|
+
/** Coulomb friction coefficient on contacts and the floor, 0..1. Default 0.4. */
|
|
23
|
+
friction?: number;
|
|
24
|
+
/** Per-second linear velocity damping (physical energy bleed so piles settle and sleep). Default 0.2. */
|
|
25
|
+
linearDamping?: number;
|
|
26
|
+
/** Sequential-impulse velocity iterations per substep (higher = more stable stacks). Default 4. */
|
|
27
|
+
solverIterations?: number;
|
|
28
|
+
/** Baumgarte positional-correction fraction per substep, 0..1. Default 0.2. */
|
|
29
|
+
correctionFactor?: number;
|
|
30
|
+
/** Penetration tolerated before correction kicks in. Default 0.005. */
|
|
31
|
+
slop?: number;
|
|
32
|
+
/** Speed at or below which a body counts as still. Default 0.08. */
|
|
33
|
+
sleepLinearVelocity?: number;
|
|
34
|
+
/** Consecutive still substeps before a body sleeps. Default 30. */
|
|
35
|
+
sleepThresholdSteps?: number;
|
|
36
|
+
/** Approach speed a contact must exceed to wake a sleeping body (resting contacts don't). Default 0.5. */
|
|
37
|
+
wakeThreshold?: number;
|
|
38
|
+
/** Maximum simultaneous joints the constraint buffers can hold. Default 256. */
|
|
39
|
+
jointCapacity?: number;
|
|
40
|
+
/** Baumgarte fraction applied to hard-joint positional error per iteration, 0..1. Default 0.5. */
|
|
41
|
+
jointCorrection?: number;
|
|
42
|
+
}
|
|
43
|
+
export interface PhysicsStats {
|
|
44
|
+
count: number;
|
|
45
|
+
awake: number;
|
|
46
|
+
sleeping: number;
|
|
47
|
+
contacts: number;
|
|
48
|
+
pairs: number;
|
|
49
|
+
substeps: number;
|
|
50
|
+
stepMs: number;
|
|
51
|
+
}
|
|
52
|
+
export interface AddBodyOptions {
|
|
53
|
+
position: readonly [number, number, number];
|
|
54
|
+
halfExtents: readonly [number, number, number];
|
|
55
|
+
velocity?: readonly [number, number, number];
|
|
56
|
+
/** ≤ 0 or omitted with `static` → infinite mass (immovable collider). Default 1. */
|
|
57
|
+
mass?: number;
|
|
58
|
+
/** Immovable collider: never integrates, never sleeps-out, invMass 0. */
|
|
59
|
+
static?: boolean;
|
|
60
|
+
/** Seed the body asleep (a settled bed pays no integration cost until woken). */
|
|
61
|
+
asleep?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* `hinge`/`fixed`/`distance` are hard bilateral constraints; `spring` is a soft PD constraint.
|
|
65
|
+
* No angular state → `hinge`/`fixed` both pin the shared anchor (`axis` is metadata), `distance`
|
|
66
|
+
* holds a fixed separation, `spring` drives toward `restLength` (suspension, follow-point carry).
|
|
67
|
+
*/
|
|
68
|
+
export type JointKind = "hinge" | "distance" | "spring" | "fixed";
|
|
69
|
+
export interface JointOptions {
|
|
70
|
+
/** First body index. */
|
|
71
|
+
bodyA: number;
|
|
72
|
+
/** Second body index, or omit / < 0 to anchor A to a fixed world point (`anchorB`). */
|
|
73
|
+
bodyB?: number;
|
|
74
|
+
/** Local offset of the anchor on A, in world axes (no rotation). Default [0,0,0]. */
|
|
75
|
+
anchorA?: readonly [number, number, number];
|
|
76
|
+
/** Local offset on B, or — when B is a world anchor — the world point itself. Default [0,0,0]. */
|
|
77
|
+
anchorB?: readonly [number, number, number];
|
|
78
|
+
/** Target separation for `distance`/`spring`. Defaults to the anchors' current distance. */
|
|
79
|
+
restLength?: number;
|
|
80
|
+
/** Spring restoring rate. Default 40. */
|
|
81
|
+
stiffness?: number;
|
|
82
|
+
/** Spring damping (relative-velocity bleed). Default 6. */
|
|
83
|
+
damping?: number;
|
|
84
|
+
/** Clamp on the per-substep spring impulse (a follow-point that never yanks). Default unbounded. */
|
|
85
|
+
maxImpulse?: number;
|
|
86
|
+
/** Hinge axis, retained as metadata (this sim has no angular DOF). */
|
|
87
|
+
axis?: readonly [number, number, number];
|
|
88
|
+
}
|
|
89
|
+
/** A contact reported to `onCollision`. The object is reused each call — read/copy, never retain. */
|
|
90
|
+
export interface CollisionEvent {
|
|
91
|
+
a: number;
|
|
92
|
+
b: number;
|
|
93
|
+
nx: number;
|
|
94
|
+
ny: number;
|
|
95
|
+
nz: number;
|
|
96
|
+
approachSpeed: number;
|
|
97
|
+
impulse: number;
|
|
98
|
+
}
|
|
99
|
+
/** Grid cell containing a point, clamped into the grid. Pure — exported for tests. */
|
|
100
|
+
export declare function cellCoord(value: number, min: number, cellSize: number, cells: number): number;
|
|
101
|
+
/** Linear cell index from clamped 3D coords. Pure — exported for tests. */
|
|
102
|
+
export declare function cellIndex(cx: number, cy: number, cz: number, nx: number, ny: number): number;
|
|
103
|
+
export declare class PhysicsWorld {
|
|
104
|
+
readonly capacity: number;
|
|
105
|
+
readonly bounds: PhysicsBounds;
|
|
106
|
+
readonly gravity: number;
|
|
107
|
+
readonly cellSize: number;
|
|
108
|
+
readonly fixedDt: number;
|
|
109
|
+
readonly maxSubsteps: number;
|
|
110
|
+
readonly restitution: number;
|
|
111
|
+
readonly restitutionThreshold: number;
|
|
112
|
+
readonly friction: number;
|
|
113
|
+
readonly linearDamping: number;
|
|
114
|
+
readonly solverIterations: number;
|
|
115
|
+
readonly correctionFactor: number;
|
|
116
|
+
readonly slop: number;
|
|
117
|
+
readonly sleepMove2: number;
|
|
118
|
+
readonly sleepSteps: number;
|
|
119
|
+
readonly wakeThreshold: number;
|
|
120
|
+
readonly jointCapacity: number;
|
|
121
|
+
readonly jointCorrection: number;
|
|
122
|
+
readonly posX: Float32Array;
|
|
123
|
+
readonly posY: Float32Array;
|
|
124
|
+
readonly posZ: Float32Array;
|
|
125
|
+
readonly velX: Float32Array;
|
|
126
|
+
readonly velY: Float32Array;
|
|
127
|
+
readonly velZ: Float32Array;
|
|
128
|
+
readonly halfX: Float32Array;
|
|
129
|
+
readonly halfY: Float32Array;
|
|
130
|
+
readonly halfZ: Float32Array;
|
|
131
|
+
readonly invMass: Float32Array;
|
|
132
|
+
readonly flags: Uint8Array;
|
|
133
|
+
/** 1 for a body that took part in a contact this frame; reset each `step()`. Render tint reads this. */
|
|
134
|
+
readonly contact: Uint8Array;
|
|
135
|
+
private readonly sleepTimer;
|
|
136
|
+
private readonly prevX;
|
|
137
|
+
private readonly prevY;
|
|
138
|
+
private readonly prevZ;
|
|
139
|
+
private readonly nx;
|
|
140
|
+
private readonly ny;
|
|
141
|
+
private readonly nz;
|
|
142
|
+
private readonly numCells;
|
|
143
|
+
private readonly cellStart;
|
|
144
|
+
private readonly cursor;
|
|
145
|
+
private readonly sorted;
|
|
146
|
+
private readonly bodyCell;
|
|
147
|
+
private readonly awakeList;
|
|
148
|
+
private readonly awakeSlot;
|
|
149
|
+
private awakeCount;
|
|
150
|
+
private readonly jSpring;
|
|
151
|
+
private readonly jActive;
|
|
152
|
+
private readonly jBodyA;
|
|
153
|
+
private readonly jBodyB;
|
|
154
|
+
private readonly jAnchorAX;
|
|
155
|
+
private readonly jAnchorAY;
|
|
156
|
+
private readonly jAnchorAZ;
|
|
157
|
+
private readonly jAnchorBX;
|
|
158
|
+
private readonly jAnchorBY;
|
|
159
|
+
private readonly jAnchorBZ;
|
|
160
|
+
private readonly jRest;
|
|
161
|
+
private readonly jStiffness;
|
|
162
|
+
private readonly jDamping;
|
|
163
|
+
private readonly jMaxImpulse;
|
|
164
|
+
private readonly jFree;
|
|
165
|
+
private jFreeCount;
|
|
166
|
+
private jointHigh;
|
|
167
|
+
private jointActiveCount;
|
|
168
|
+
private collisionListener;
|
|
169
|
+
private collisionMinSpeed;
|
|
170
|
+
private readonly collisionEvent;
|
|
171
|
+
private bodyCount;
|
|
172
|
+
private accumulator;
|
|
173
|
+
private readonly stats;
|
|
174
|
+
constructor(config: PhysicsWorldConfig);
|
|
175
|
+
private addAwake;
|
|
176
|
+
private removeAwake;
|
|
177
|
+
get count(): number;
|
|
178
|
+
get cells(): {
|
|
179
|
+
nx: number;
|
|
180
|
+
ny: number;
|
|
181
|
+
nz: number;
|
|
182
|
+
total: number;
|
|
183
|
+
};
|
|
184
|
+
get jointCount(): number;
|
|
185
|
+
/**
|
|
186
|
+
* Deliver every impacting contact (approach speed ≥ `minApproachSpeed`) to `listener` during
|
|
187
|
+
* `step`. The event object is reused — read/copy it, never retain. Pass `null` to detach. This is
|
|
188
|
+
* the seam crash-damage and destruction read; contacts otherwise stay inside the sim.
|
|
189
|
+
*/
|
|
190
|
+
onCollision(listener: ((e: CollisionEvent) => void) | null, minApproachSpeed?: number): void;
|
|
191
|
+
hingeJoint(options: JointOptions): number;
|
|
192
|
+
fixedJoint(options: JointOptions): number;
|
|
193
|
+
distanceJoint(options: JointOptions): number;
|
|
194
|
+
springJoint(options: JointOptions): number;
|
|
195
|
+
private addJoint;
|
|
196
|
+
removeJoint(id: number): void;
|
|
197
|
+
/** Move a joint's world anchor point (only meaningful when `bodyB` is a world anchor). */
|
|
198
|
+
setJointAnchor(id: number, x: number, y: number, z: number): void;
|
|
199
|
+
setJointRest(id: number, restLength: number): void;
|
|
200
|
+
private jointDistance;
|
|
201
|
+
/** Write [ax,ay,az,bx,by,bz] world endpoints per active joint into `out`; returns the joint count. */
|
|
202
|
+
readJointSegments(out: Float32Array): number;
|
|
203
|
+
addBody(options: AddBodyOptions): number;
|
|
204
|
+
isSleeping(i: number): boolean;
|
|
205
|
+
wake(i: number): void;
|
|
206
|
+
wakeAll(): void;
|
|
207
|
+
clear(): void;
|
|
208
|
+
/** Advance by a real frame delta; runs whole fixed substeps and carries the remainder. */
|
|
209
|
+
step(frameDt: number): PhysicsStats;
|
|
210
|
+
getStats(): PhysicsStats;
|
|
211
|
+
private substep;
|
|
212
|
+
private constrainBounds;
|
|
213
|
+
private buildGrid;
|
|
214
|
+
private cellOf;
|
|
215
|
+
private solve;
|
|
216
|
+
private resolve;
|
|
217
|
+
private solveJoints;
|
|
218
|
+
private updateSleep;
|
|
219
|
+
}
|