@jgengine/core 0.11.0 → 0.12.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 +26 -0
- package/dist/ai/populationDirector.d.ts +103 -0
- package/dist/ai/populationDirector.js +190 -0
- package/dist/ai/pursuit.d.ts +56 -0
- package/dist/ai/pursuit.js +53 -0
- package/dist/authoring.d.ts +1 -1
- package/dist/authoring.js +1 -1
- package/dist/combat.d.ts +1 -0
- package/dist/combat.js +1 -0
- package/dist/devtools/controls.d.ts +38 -0
- package/dist/devtools/controls.js +181 -0
- package/dist/devtools/devtools.d.ts +5 -197
- package/dist/devtools/devtools.js +38 -774
- package/dist/devtools/discover.d.ts +32 -0
- package/dist/devtools/discover.js +240 -0
- package/dist/devtools/fallbackSeams.d.ts +50 -0
- package/dist/devtools/fallbackSeams.js +111 -0
- package/dist/devtools/frame.d.ts +15 -0
- package/dist/devtools/frame.js +247 -0
- package/dist/devtools/logs.d.ts +15 -0
- package/dist/devtools/logs.js +86 -0
- package/dist/devtools/overrides.d.ts +16 -0
- package/dist/devtools/overrides.js +66 -0
- package/dist/devtools/types.d.ts +212 -0
- package/dist/devtools/types.js +1 -0
- package/dist/economy/auctionBook.d.ts +122 -0
- package/dist/economy/auctionBook.js +213 -0
- package/dist/economy/priceHistory.d.ts +44 -0
- package/dist/economy/priceHistory.js +89 -0
- package/dist/economy/unlockPoints.d.ts +92 -0
- package/dist/economy/unlockPoints.js +103 -0
- package/dist/editor/commandApply.d.ts +5 -0
- package/dist/editor/commandApply.js +755 -0
- package/dist/editor/commands.d.ts +48 -3
- package/dist/editor/commands.js +40 -754
- package/dist/editor/directives.d.ts +94 -0
- package/dist/editor/directives.js +206 -0
- package/dist/editor/document.d.ts +37 -2
- package/dist/editor/document.js +422 -6
- package/dist/editor/entityCatalog.d.ts +42 -0
- package/dist/editor/entityCatalog.js +101 -0
- package/dist/editor/environment.d.ts +74 -0
- package/dist/editor/environment.js +169 -0
- package/dist/editor/index.d.ts +26 -1
- package/dist/editor/index.js +8 -1
- package/dist/editor/minimap.d.ts +31 -0
- package/dist/editor/minimap.js +73 -0
- package/dist/editor/streamer.d.ts +66 -0
- package/dist/editor/streamer.js +68 -0
- package/dist/editor/types.d.ts +143 -0
- package/dist/editor/world.d.ts +136 -0
- package/dist/editor/world.js +303 -0
- package/dist/game/breeding.d.ts +161 -0
- package/dist/game/breeding.js +181 -0
- package/dist/game/cameraConfig.d.ts +451 -0
- package/dist/game/cameraConfig.js +1 -0
- package/dist/game/defineGame.d.ts +16 -9
- package/dist/game/defineGame.js +14 -6
- package/dist/game/defineSystem.d.ts +2 -0
- package/dist/game/defineSystem.js +2 -0
- package/dist/game/events.d.ts +28 -0
- package/dist/game/playableGame.d.ts +34 -435
- package/dist/game/playableGame.js +1 -1
- package/dist/game/progression.d.ts +35 -0
- package/dist/game/progression.js +19 -0
- package/dist/game/quest.js +1 -3
- package/dist/game/tribe.d.ts +184 -0
- package/dist/game/tribe.js +432 -0
- package/dist/gameplay.d.ts +3 -1
- package/dist/gameplay.js +3 -1
- package/dist/inventory/encumbrance.d.ts +69 -0
- package/dist/inventory/encumbrance.js +68 -0
- package/dist/item/durability.js +1 -3
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +24 -1
- package/dist/movement/movementModel.d.ts +15 -1
- package/dist/movement/movementModel.js +33 -15
- package/dist/movement/playerMovement.js +102 -2
- package/dist/multiplayer/combatSnapshot.js +13 -8
- package/dist/multiplayer/presenceModel.d.ts +2 -0
- package/dist/multiplayer/presenceModel.js +8 -0
- package/dist/physics/drivableVehicle.d.ts +2 -0
- package/dist/physics/drivableVehicle.js +1 -1
- package/dist/physics/flightDynamics.d.ts +82 -0
- package/dist/physics/flightDynamics.js +165 -0
- package/dist/physics/gravityField.d.ts +25 -0
- package/dist/physics/gravityField.js +41 -0
- package/dist/physics/kinematicVehicle.d.ts +87 -0
- package/dist/physics/kinematicVehicle.js +193 -11
- package/dist/physics/physicsWorld.d.ts +26 -0
- package/dist/physics/physicsWorld.js +109 -117
- package/dist/physics/vehicleObstacles.d.ts +44 -0
- package/dist/physics/vehicleObstacles.js +115 -0
- package/dist/random/rng.d.ts +4 -1
- package/dist/random/rng.js +4 -1
- package/dist/runtime/adapter.d.ts +13 -0
- package/dist/runtime/adapter.js +13 -0
- package/dist/runtime/context/combat.d.ts +51 -0
- package/dist/runtime/context/combat.js +130 -0
- package/dist/runtime/context/deathLoot.d.ts +30 -0
- package/dist/runtime/context/deathLoot.js +39 -0
- package/dist/runtime/context/player.d.ts +48 -0
- package/dist/runtime/context/player.js +125 -0
- package/dist/runtime/context/scene.d.ts +59 -0
- package/dist/runtime/context/scene.js +291 -0
- package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
- package/dist/runtime/context/snapshotCodecs.js +26 -0
- package/dist/runtime/descriptors/baseline.js +26 -3
- package/dist/runtime/descriptors/features.d.ts +5 -0
- package/dist/runtime/descriptors/features.js +20 -2
- package/dist/runtime/gameContext.d.ts +2 -428
- package/dist/runtime/gameContext.js +88 -501
- package/dist/runtime/gameContextTypes.d.ts +467 -0
- package/dist/runtime/gameContextTypes.js +1 -0
- package/dist/runtime/headlessRunner.d.ts +4 -2
- package/dist/runtime/headlessRunner.js +1 -0
- package/dist/runtime/hostPolicy.d.ts +51 -0
- package/dist/runtime/hostPolicy.js +68 -0
- package/dist/runtime/hostedGameRunner.d.ts +3 -1
- package/dist/runtime/hostedGameRunner.js +1 -0
- package/dist/runtime/hostedWorldSession.d.ts +3 -1
- package/dist/runtime/hostedWorldSession.js +1 -0
- package/dist/runtime/worldSnapshot.d.ts +13 -1
- package/dist/runtime/worldSnapshot.js +15 -3
- package/dist/scene/assetCatalog.d.ts +5 -0
- package/dist/scene/assetSpace.js +1 -3
- package/dist/scene/authoredTriggers.d.ts +50 -0
- package/dist/scene/authoredTriggers.js +114 -0
- package/dist/scene/builtinSceneKinds.js +8 -3
- package/dist/scene/colliders.d.ts +58 -0
- package/dist/scene/colliders.js +108 -0
- package/dist/scene/collisionMesh.d.ts +94 -0
- package/dist/scene/collisionMesh.js +699 -0
- package/dist/scene/companion.d.ts +147 -0
- package/dist/scene/companion.js +193 -0
- package/dist/scene/entityStats.d.ts +7 -9
- package/dist/scene/entityStats.js +10 -14
- package/dist/scene/sceneKinds.d.ts +16 -0
- package/dist/scene/sceneKinds.js +4 -0
- package/dist/scene/sceneRaycast.js +15 -0
- package/dist/stats/spawnLevelStats.d.ts +110 -0
- package/dist/stats/spawnLevelStats.js +140 -0
- package/dist/stats/statPool.d.ts +70 -0
- package/dist/stats/statPool.js +59 -0
- package/dist/store/changeSignal.d.ts +3 -0
- package/dist/store/changeSignal.js +2 -0
- package/dist/store/observableKeyedStore.d.ts +2 -0
- package/dist/store/observableKeyedStore.js +1 -0
- package/dist/survival/decayMeter.js +1 -3
- package/dist/survival/regionHealth.js +1 -3
- package/dist/ui/hudLayout.d.ts +5 -0
- package/dist/ui/hudLayout.js +17 -1
- package/dist/world/authoredEntities.d.ts +29 -0
- package/dist/world/authoredEntities.js +29 -0
- package/dist/world/authoredObjects.d.ts +15 -1
- package/dist/world/authoredObjects.js +13 -1
- package/dist/world/authoredSpawn.d.ts +39 -0
- package/dist/world/authoredSpawn.js +37 -0
- package/dist/world/buildSockets.d.ts +152 -0
- package/dist/world/buildSockets.js +188 -0
- package/dist/world/cityBlocks.d.ts +152 -0
- package/dist/world/cityBlocks.js +670 -0
- package/dist/world/cityContent.d.ts +77 -0
- package/dist/world/cityContent.js +261 -0
- package/dist/world/cityGeometry.d.ts +114 -0
- package/dist/world/cityGeometry.js +573 -0
- package/dist/world/cityKind.d.ts +314 -0
- package/dist/world/cityKind.js +1578 -0
- package/dist/world/features.d.ts +18 -3
- package/dist/world/features.js +10 -1
- package/dist/world/grassKind.js +2 -2
- package/dist/world/markers.d.ts +50 -4
- package/dist/world/markers.js +49 -0
- package/dist/world/massing.js +1 -1
- package/dist/world/minimapBake.d.ts +78 -0
- package/dist/world/minimapBake.js +208 -0
- package/dist/world/minimapTrack.d.ts +17 -0
- package/dist/world/minimapTrack.js +22 -0
- package/dist/world/pathNetwork.d.ts +159 -0
- package/dist/world/pathNetwork.js +684 -0
- package/dist/world/place.d.ts +166 -0
- package/dist/world/place.js +96 -0
- package/dist/world/placeAsset.js +3 -1
- package/dist/world/poleLineKind.d.ts +40 -0
- package/dist/world/poleLineKind.js +97 -0
- package/dist/world/polyline.js +9 -2
- package/dist/world/resourceNode.d.ts +109 -0
- package/dist/world/resourceNode.js +140 -0
- package/dist/world/scatter.js +2 -20
- package/dist/world/streets.d.ts +29 -0
- package/dist/world/streets.js +55 -0
- package/dist/world/terraform.js +58 -88
- package/dist/world/terrain.d.ts +45 -0
- package/dist/world/terrain.js +78 -0
- package/dist/world.d.ts +14 -5
- package/dist/world.js +12 -3
- package/package.json +1 -1
- package/skills/game-design/SKILL.md +28 -0
- package/skills/game-design/agents/openai.yaml +4 -0
- package/skills/game-design/references/application-playbook.md +170 -0
- package/skills/game-design/references/field-guide.md +288 -0
- package/skills/jgengine/SKILL.md +7 -4
- package/skills/jgengine/api.md +172 -79
- package/skills/jgengine/capabilities.md +104 -2
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-combat/SKILL.md +5 -1
- package/skills/jgengine-combat/api.md +36 -0
- package/skills/jgengine-combat/capabilities.md +44 -0
- package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
- package/skills/jgengine-combat/reference.md +12 -2
- package/skills/jgengine-gameplay/api.md +152 -26
- package/skills/jgengine-gameplay/capabilities.md +78 -2
- package/skills/jgengine-gameplay/reference-systems.md +1 -5
- package/skills/jgengine-world/SKILL.md +13 -0
- package/skills/jgengine-world/api.md +419 -15
- package/skills/jgengine-world/capabilities.md +108 -2
- package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
- package/skills/jgengine-world/reference.md +43 -4
- package/skills/jgengine/reference-cartridge.md +0 -72
|
@@ -95,6 +95,9 @@ export class PhysicsWorld {
|
|
|
95
95
|
jFreeCount = 0;
|
|
96
96
|
jointHigh = 0;
|
|
97
97
|
jointActiveCount = 0;
|
|
98
|
+
// Reused scratch for a joint's two world anchor points [ax,ay,az,bx,by,bz]; filled by anchorWorld
|
|
99
|
+
// so the gather is defined once and no per-call array is allocated on the joint hot path.
|
|
100
|
+
anchorScratch = new Float64Array(6);
|
|
98
101
|
// Removed bodies tombstone (FLAG_DEAD) rather than compact: `id` is the SoA slot itself, handed
|
|
99
102
|
// out to callers and stored raw in joints/game state, so a slot can never move once allocated.
|
|
100
103
|
// `bodyHigh` is the high-water mark of ever-used slots; `bodyFree` recycles dead ones for addBody.
|
|
@@ -313,18 +316,29 @@ export class PhysicsWorld {
|
|
|
313
316
|
setJointRest(id, restLength) {
|
|
314
317
|
this.jRest[id] = restLength;
|
|
315
318
|
}
|
|
316
|
-
|
|
319
|
+
/**
|
|
320
|
+
* Gather joint `id`'s two world anchor points into {@link anchorScratch} as [ax,ay,az,bx,by,bz]
|
|
321
|
+
* (A's local offset added to body A's centre, or world-origin when A is unset; B likewise, treating
|
|
322
|
+
* `anchorB` as a world point when B is unset). The single definition shared by {@link jointDistance},
|
|
323
|
+
* {@link readJointSegments}, and {@link solveJoints}.
|
|
324
|
+
*/
|
|
325
|
+
anchorWorld(id) {
|
|
326
|
+
const s = this.anchorScratch;
|
|
317
327
|
const bA = this.jBodyA[id];
|
|
318
328
|
const bB = this.jBodyB[id];
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
329
|
+
s[0] = (bA >= 0 ? this.posX[bA] : 0) + this.jAnchorAX[id];
|
|
330
|
+
s[1] = (bA >= 0 ? this.posY[bA] : 0) + this.jAnchorAY[id];
|
|
331
|
+
s[2] = (bA >= 0 ? this.posZ[bA] : 0) + this.jAnchorAZ[id];
|
|
332
|
+
s[3] = bB >= 0 ? this.posX[bB] + this.jAnchorBX[id] : this.jAnchorBX[id];
|
|
333
|
+
s[4] = bB >= 0 ? this.posY[bB] + this.jAnchorBY[id] : this.jAnchorBY[id];
|
|
334
|
+
s[5] = bB >= 0 ? this.posZ[bB] + this.jAnchorBZ[id] : this.jAnchorBZ[id];
|
|
335
|
+
}
|
|
336
|
+
jointDistance(id) {
|
|
337
|
+
this.anchorWorld(id);
|
|
338
|
+
const s = this.anchorScratch;
|
|
339
|
+
const dx = s[3] - s[0];
|
|
340
|
+
const dy = s[4] - s[1];
|
|
341
|
+
const dz = s[5] - s[2];
|
|
328
342
|
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
329
343
|
}
|
|
330
344
|
/** Write [ax,ay,az,bx,by,bz] world endpoints per active joint into `out`; returns the joint count. */
|
|
@@ -333,15 +347,15 @@ export class PhysicsWorld {
|
|
|
333
347
|
for (let id = 0; id < this.jointHigh; id += 1) {
|
|
334
348
|
if (this.jActive[id] !== 1)
|
|
335
349
|
continue;
|
|
336
|
-
|
|
337
|
-
const
|
|
350
|
+
this.anchorWorld(id);
|
|
351
|
+
const s = this.anchorScratch;
|
|
338
352
|
const o = n * 6;
|
|
339
|
-
out[o] =
|
|
340
|
-
out[o + 1] =
|
|
341
|
-
out[o + 2] =
|
|
342
|
-
out[o + 3] =
|
|
343
|
-
out[o + 4] =
|
|
344
|
-
out[o + 5] =
|
|
353
|
+
out[o] = s[0];
|
|
354
|
+
out[o + 1] = s[1];
|
|
355
|
+
out[o + 2] = s[2];
|
|
356
|
+
out[o + 3] = s[3];
|
|
357
|
+
out[o + 4] = s[4];
|
|
358
|
+
out[o + 5] = s[5];
|
|
345
359
|
n += 1;
|
|
346
360
|
}
|
|
347
361
|
return n;
|
|
@@ -609,6 +623,29 @@ export class PhysicsWorld {
|
|
|
609
623
|
this.velY[i] = this.velY[i] < rt ? 0 : -this.velY[i] * e;
|
|
610
624
|
}
|
|
611
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* Walk every broadphase cell body `i`'s AABB overlaps, invoking `visit(cell)` once per cell. The
|
|
628
|
+
* cell-range math (six `cellCoord` calls plus the row-major triple loop) lives here so the count
|
|
629
|
+
* and scatter passes of {@link buildGrid} share one definition. `visit` is a caller-hoisted closure
|
|
630
|
+
* (created once per `buildGrid`, not per body) so the hot loop stays allocation-free.
|
|
631
|
+
*/
|
|
632
|
+
forEachBodyCell(i, visit) {
|
|
633
|
+
const nx = this.nx;
|
|
634
|
+
const ny = this.ny;
|
|
635
|
+
const x0 = cellCoord(this.posX[i] - this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
|
|
636
|
+
const x1 = cellCoord(this.posX[i] + this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
|
|
637
|
+
const y0 = cellCoord(this.posY[i] - this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
|
|
638
|
+
const y1 = cellCoord(this.posY[i] + this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
|
|
639
|
+
const z0 = cellCoord(this.posZ[i] - this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
|
|
640
|
+
const z1 = cellCoord(this.posZ[i] + this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
|
|
641
|
+
for (let z = z0; z <= z1; z += 1) {
|
|
642
|
+
for (let y = y0; y <= y1; y += 1) {
|
|
643
|
+
const rowBase = (z * ny + y) * nx;
|
|
644
|
+
for (let x = x0; x <= x1; x += 1)
|
|
645
|
+
visit(rowBase + x);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
612
649
|
buildGrid() {
|
|
613
650
|
const n = this.bodyHigh;
|
|
614
651
|
const start = this.cellStart;
|
|
@@ -618,24 +655,13 @@ export class PhysicsWorld {
|
|
|
618
655
|
const i = this.awakeList[a];
|
|
619
656
|
this.bodyCell[i] = this.cellOf(i);
|
|
620
657
|
}
|
|
621
|
-
const
|
|
622
|
-
|
|
658
|
+
const countCell = (cell) => {
|
|
659
|
+
start[cell + 1] += 1;
|
|
660
|
+
};
|
|
623
661
|
for (let i = 0; i < n; i += 1) {
|
|
624
662
|
if ((this.flags[i] & FLAG_DEAD) !== 0)
|
|
625
663
|
continue;
|
|
626
|
-
|
|
627
|
-
const x1 = cellCoord(this.posX[i] + this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
|
|
628
|
-
const y0 = cellCoord(this.posY[i] - this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
|
|
629
|
-
const y1 = cellCoord(this.posY[i] + this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
|
|
630
|
-
const z0 = cellCoord(this.posZ[i] - this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
|
|
631
|
-
const z1 = cellCoord(this.posZ[i] + this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
|
|
632
|
-
for (let z = z0; z <= z1; z += 1) {
|
|
633
|
-
for (let y = y0; y <= y1; y += 1) {
|
|
634
|
-
const rowBase = (z * ny + y) * nx;
|
|
635
|
-
for (let x = x0; x <= x1; x += 1)
|
|
636
|
-
start[rowBase + x + 1] += 1;
|
|
637
|
-
}
|
|
638
|
-
}
|
|
664
|
+
this.forEachBodyCell(i, countCell);
|
|
639
665
|
}
|
|
640
666
|
for (let c = 0; c < this.numCells; c += 1) {
|
|
641
667
|
start[c + 1] += start[c];
|
|
@@ -645,24 +671,15 @@ export class PhysicsWorld {
|
|
|
645
671
|
if (total > this.sorted.length) {
|
|
646
672
|
this.sorted = new Int32Array(Math.max(total, this.sorted.length * 2));
|
|
647
673
|
}
|
|
674
|
+
let scatterBody = 0;
|
|
675
|
+
const scatterCell = (cell) => {
|
|
676
|
+
this.sorted[this.cursor[cell]++] = scatterBody;
|
|
677
|
+
};
|
|
648
678
|
for (let i = 0; i < n; i += 1) {
|
|
649
679
|
if ((this.flags[i] & FLAG_DEAD) !== 0)
|
|
650
680
|
continue;
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
const y0 = cellCoord(this.posY[i] - this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
|
|
654
|
-
const y1 = cellCoord(this.posY[i] + this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
|
|
655
|
-
const z0 = cellCoord(this.posZ[i] - this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
|
|
656
|
-
const z1 = cellCoord(this.posZ[i] + this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
|
|
657
|
-
for (let z = z0; z <= z1; z += 1) {
|
|
658
|
-
for (let y = y0; y <= y1; y += 1) {
|
|
659
|
-
const rowBase = (z * ny + y) * nx;
|
|
660
|
-
for (let x = x0; x <= x1; x += 1) {
|
|
661
|
-
const c = rowBase + x;
|
|
662
|
-
this.sorted[this.cursor[c]++] = i;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
}
|
|
681
|
+
scatterBody = i;
|
|
682
|
+
this.forEachBodyCell(i, scatterCell);
|
|
666
683
|
}
|
|
667
684
|
}
|
|
668
685
|
cellOf(i) {
|
|
@@ -867,6 +884,39 @@ export class PhysicsWorld {
|
|
|
867
884
|
this.posY[j] += corr * imJ * nY;
|
|
868
885
|
this.posZ[j] += corr * imJ * nZ;
|
|
869
886
|
}
|
|
887
|
+
/**
|
|
888
|
+
* Apply an axial velocity impulse `jn` along unit axis (nX,nY,nZ) to a joint's two bodies: body A
|
|
889
|
+
* recoils (−) and body B advances (+), each scaled by its inverse mass. A world anchor (body index
|
|
890
|
+
* < 0) is skipped. Scalar args only — no allocation on the joint hot path.
|
|
891
|
+
*/
|
|
892
|
+
applyAxialImpulse(bA, bB, imA, imB, jn, nX, nY, nZ) {
|
|
893
|
+
if (bA >= 0) {
|
|
894
|
+
this.velX[bA] -= jn * imA * nX;
|
|
895
|
+
this.velY[bA] -= jn * imA * nY;
|
|
896
|
+
this.velZ[bA] -= jn * imA * nZ;
|
|
897
|
+
}
|
|
898
|
+
if (bB >= 0) {
|
|
899
|
+
this.velX[bB] += jn * imB * nX;
|
|
900
|
+
this.velY[bB] += jn * imB * nY;
|
|
901
|
+
this.velZ[bB] += jn * imB * nZ;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Positional (Baumgarte) twin of {@link applyAxialImpulse}: nudge the joint's two bodies along
|
|
906
|
+
* (nX,nY,nZ) by `corr` — body A advances (+), body B recoils (−), each scaled by its inverse mass.
|
|
907
|
+
*/
|
|
908
|
+
applyAxialCorrection(bA, bB, imA, imB, corr, nX, nY, nZ) {
|
|
909
|
+
if (bA >= 0) {
|
|
910
|
+
this.posX[bA] += corr * imA * nX;
|
|
911
|
+
this.posY[bA] += corr * imA * nY;
|
|
912
|
+
this.posZ[bA] += corr * imA * nZ;
|
|
913
|
+
}
|
|
914
|
+
if (bB >= 0) {
|
|
915
|
+
this.posX[bB] -= corr * imB * nX;
|
|
916
|
+
this.posY[bB] -= corr * imB * nY;
|
|
917
|
+
this.posZ[bB] -= corr * imB * nZ;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
870
920
|
solveJoints(dt) {
|
|
871
921
|
const beta = this.jointCorrection;
|
|
872
922
|
const slop = this.slop;
|
|
@@ -876,15 +926,11 @@ export class PhysicsWorld {
|
|
|
876
926
|
const kind = this.jKind[id];
|
|
877
927
|
const bA = this.jBodyA[id];
|
|
878
928
|
const bB = this.jBodyB[id];
|
|
879
|
-
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
const bz = bB >= 0 ? this.posZ[bB] + this.jAnchorBZ[id] : this.jAnchorBZ[id];
|
|
885
|
-
let dx = bx - ax;
|
|
886
|
-
let dy = by - ay;
|
|
887
|
-
let dz = bz - az;
|
|
929
|
+
this.anchorWorld(id);
|
|
930
|
+
const s = this.anchorScratch;
|
|
931
|
+
let dx = s[3] - s[0];
|
|
932
|
+
let dy = s[4] - s[1];
|
|
933
|
+
let dz = s[5] - s[2];
|
|
888
934
|
const imA = bA >= 0 ? this.invMass[bA] : 0;
|
|
889
935
|
const imB = bB >= 0 ? this.invMass[bB] : 0;
|
|
890
936
|
const imSum = imA + imB;
|
|
@@ -924,27 +970,9 @@ export class PhysicsWorld {
|
|
|
924
970
|
}
|
|
925
971
|
const rvn = rvx * nX + rvy * nY + rvz * nZ;
|
|
926
972
|
const jn = -rvn / imSum;
|
|
927
|
-
|
|
928
|
-
this.velX[bA] -= jn * imA * nX;
|
|
929
|
-
this.velY[bA] -= jn * imA * nY;
|
|
930
|
-
this.velZ[bA] -= jn * imA * nZ;
|
|
931
|
-
}
|
|
932
|
-
if (bB >= 0) {
|
|
933
|
-
this.velX[bB] += jn * imB * nX;
|
|
934
|
-
this.velY[bB] += jn * imB * nY;
|
|
935
|
-
this.velZ[bB] += jn * imB * nZ;
|
|
936
|
-
}
|
|
973
|
+
this.applyAxialImpulse(bA, bB, imA, imB, jn, nX, nY, nZ);
|
|
937
974
|
const corr = (dist * beta) / imSum;
|
|
938
|
-
|
|
939
|
-
this.posX[bA] += corr * imA * nX;
|
|
940
|
-
this.posY[bA] += corr * imA * nY;
|
|
941
|
-
this.posZ[bA] += corr * imA * nZ;
|
|
942
|
-
}
|
|
943
|
-
if (bB >= 0) {
|
|
944
|
-
this.posX[bB] -= corr * imB * nX;
|
|
945
|
-
this.posY[bB] -= corr * imB * nY;
|
|
946
|
-
this.posZ[bB] -= corr * imB * nZ;
|
|
947
|
-
}
|
|
975
|
+
this.applyAxialCorrection(bA, bB, imA, imB, corr, nX, nY, nZ);
|
|
948
976
|
}
|
|
949
977
|
else {
|
|
950
978
|
const rvl = Math.sqrt(rvx * rvx + rvy * rvy + rvz * rvz);
|
|
@@ -953,16 +981,7 @@ export class PhysicsWorld {
|
|
|
953
981
|
const nY = rvy / rvl;
|
|
954
982
|
const nZ = rvz / rvl;
|
|
955
983
|
const jn = -rvl / imSum;
|
|
956
|
-
|
|
957
|
-
this.velX[bA] -= jn * imA * nX;
|
|
958
|
-
this.velY[bA] -= jn * imA * nY;
|
|
959
|
-
this.velZ[bA] -= jn * imA * nZ;
|
|
960
|
-
}
|
|
961
|
-
if (bB >= 0) {
|
|
962
|
-
this.velX[bB] += jn * imB * nX;
|
|
963
|
-
this.velY[bB] += jn * imB * nY;
|
|
964
|
-
this.velZ[bB] += jn * imB * nZ;
|
|
965
|
-
}
|
|
984
|
+
this.applyAxialImpulse(bA, bB, imA, imB, jn, nX, nY, nZ);
|
|
966
985
|
}
|
|
967
986
|
}
|
|
968
987
|
continue;
|
|
@@ -994,16 +1013,7 @@ export class PhysicsWorld {
|
|
|
994
1013
|
jn = maxJ;
|
|
995
1014
|
else if (jn < -maxJ)
|
|
996
1015
|
jn = -maxJ;
|
|
997
|
-
|
|
998
|
-
this.velX[bA] -= jn * imA * nX;
|
|
999
|
-
this.velY[bA] -= jn * imA * nY;
|
|
1000
|
-
this.velZ[bA] -= jn * imA * nZ;
|
|
1001
|
-
}
|
|
1002
|
-
if (bB >= 0) {
|
|
1003
|
-
this.velX[bB] += jn * imB * nX;
|
|
1004
|
-
this.velY[bB] += jn * imB * nY;
|
|
1005
|
-
this.velZ[bB] += jn * imB * nZ;
|
|
1006
|
-
}
|
|
1016
|
+
this.applyAxialImpulse(bA, bB, imA, imB, jn, nX, nY, nZ);
|
|
1007
1017
|
continue;
|
|
1008
1018
|
}
|
|
1009
1019
|
if (kind !== JOINT_FIXED && kind !== JOINT_DISTANCE) {
|
|
@@ -1016,27 +1026,9 @@ export class PhysicsWorld {
|
|
|
1016
1026
|
this.wake(bB);
|
|
1017
1027
|
}
|
|
1018
1028
|
const jn = -rvn / imSum;
|
|
1019
|
-
|
|
1020
|
-
this.velX[bA] -= jn * imA * nX;
|
|
1021
|
-
this.velY[bA] -= jn * imA * nY;
|
|
1022
|
-
this.velZ[bA] -= jn * imA * nZ;
|
|
1023
|
-
}
|
|
1024
|
-
if (bB >= 0) {
|
|
1025
|
-
this.velX[bB] += jn * imB * nX;
|
|
1026
|
-
this.velY[bB] += jn * imB * nY;
|
|
1027
|
-
this.velZ[bB] += jn * imB * nZ;
|
|
1028
|
-
}
|
|
1029
|
+
this.applyAxialImpulse(bA, bB, imA, imB, jn, nX, nY, nZ);
|
|
1029
1030
|
const corr = (c * beta) / imSum;
|
|
1030
|
-
|
|
1031
|
-
this.posX[bA] += corr * imA * nX;
|
|
1032
|
-
this.posY[bA] += corr * imA * nY;
|
|
1033
|
-
this.posZ[bA] += corr * imA * nZ;
|
|
1034
|
-
}
|
|
1035
|
-
if (bB >= 0) {
|
|
1036
|
-
this.posX[bB] -= corr * imB * nX;
|
|
1037
|
-
this.posY[bB] -= corr * imB * nY;
|
|
1038
|
-
this.posZ[bB] -= corr * imB * nZ;
|
|
1039
|
-
}
|
|
1031
|
+
this.applyAxialCorrection(bA, bB, imA, imB, corr, nX, nY, nZ);
|
|
1040
1032
|
}
|
|
1041
1033
|
}
|
|
1042
1034
|
updateSleep() {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type CollisionObstacle } from "../movement/movementModel.js";
|
|
2
|
+
/**
|
|
3
|
+
* A single blocked-move event a car can react to (#1051): crash damage, screen shake, a metal-crunch
|
|
4
|
+
* cue. Reported once, when a move is first vetoed, then consumed by {@link VehicleObstacleClamp.takeImpact}.
|
|
5
|
+
*/
|
|
6
|
+
export interface VehicleImpact {
|
|
7
|
+
/** Speed (world units/s) the car was carrying *into* the obstacle at the moment the move was blocked. */
|
|
8
|
+
closingSpeed: number;
|
|
9
|
+
/** Unit push-back direction — points from the struck surface back toward the car. */
|
|
10
|
+
normal: readonly [number, number];
|
|
11
|
+
/** Index into the obstacle set passed for the blocking tick. */
|
|
12
|
+
obstacleIndex: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A planar move clamp for a kinematic car: feed {@link clampMove} to a vehicle's `clampMove` hook so
|
|
16
|
+
* an attempted XZ displacement slides along world solids instead of driving through them, and read
|
|
17
|
+
* {@link takeImpact} once per tick for the crash it produced.
|
|
18
|
+
*/
|
|
19
|
+
export interface VehicleObstacleClamp {
|
|
20
|
+
/** Resolve an attempted `from`→`to` XZ move to the destination actually allowed (slide-along). */
|
|
21
|
+
clampMove(from: readonly [number, number], to: readonly [number, number]): readonly [number, number];
|
|
22
|
+
/** Impact recorded since last take, consumed on read (null when none). Closing speed is the velocity component into the obstacle at block time, units/s. */
|
|
23
|
+
takeImpact(): VehicleImpact | null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build a slide-along move clamp for a kinematic car (#1051). `obstacles` is sampled fresh each tick —
|
|
27
|
+
* the caller hands back the already-filtered set of solids near the car — and `dt` supplies the current
|
|
28
|
+
* tick length so a blocked move's lost displacement converts to a closing speed. `radius` inflates each
|
|
29
|
+
* obstacle footprint by the car's body radius (default {@link DEFAULT_VEHICLE_RADIUS}).
|
|
30
|
+
*
|
|
31
|
+
* The slide itself is delegated to {@link resolveObstacleStep} so cars and pedestrians share one proven
|
|
32
|
+
* axis-separated resolver: a shallow graze keeps its tangential run and only the into-wall component is
|
|
33
|
+
* shed, a head-on zeroes forward progress. When any component is shed the largest such block this tick is
|
|
34
|
+
* kept as a {@link VehicleImpact}; {@link VehicleObstacleClamp.takeImpact} returns and clears it so a hit
|
|
35
|
+
* fires exactly once. The happy path (no solids near) is a zero-allocation passthrough returning `to`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createVehicleObstacleClamp(options: {
|
|
38
|
+
/** Solids near the car this tick (already filtered to the relevant, solid set). */
|
|
39
|
+
obstacles: () => readonly CollisionObstacle[];
|
|
40
|
+
/** Vehicle body radius (units). Default {@link DEFAULT_VEHICLE_RADIUS}. */
|
|
41
|
+
radius?: number;
|
|
42
|
+
/** Current tick dt (seconds) — the divisor turning shed displacement into closing speed. */
|
|
43
|
+
dt: () => number;
|
|
44
|
+
}): VehicleObstacleClamp;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { resolveObstacleStep } from "../movement/movementModel.js";
|
|
2
|
+
/** Matches the unit-box default {@link resolveObstacleStep} applies to obstacles without explicit extents. */
|
|
3
|
+
const DEFAULT_OBSTACLE_HALF_EXTENTS = [0.5, 0.5, 0.5];
|
|
4
|
+
/** Default vehicle body radius (units) inflating obstacle footprints; a compact hatchback-ish disc. */
|
|
5
|
+
const DEFAULT_VEHICLE_RADIUS = 1.4;
|
|
6
|
+
/**
|
|
7
|
+
* Y the car body is pinned to when handing solids to {@link resolveObstacleStep}. That stepper gates
|
|
8
|
+
* each obstacle by a 1.8-tall pedestrian span around `current[1]`; centring every pre-filtered solid
|
|
9
|
+
* on this value makes the gate always admit them, so collision is purely horizontal and the car hits
|
|
10
|
+
* world solids at any ground elevation (bridges, hills) even though the clamp only knows XZ.
|
|
11
|
+
*/
|
|
12
|
+
const CAR_BODY_MID_Y = 0.9;
|
|
13
|
+
/** Below this the returned step is treated as unblocked (float-noise slack), so no phantom impacts fire. */
|
|
14
|
+
const BLOCK_EPSILON = 1e-6;
|
|
15
|
+
/**
|
|
16
|
+
* Build a slide-along move clamp for a kinematic car (#1051). `obstacles` is sampled fresh each tick —
|
|
17
|
+
* the caller hands back the already-filtered set of solids near the car — and `dt` supplies the current
|
|
18
|
+
* tick length so a blocked move's lost displacement converts to a closing speed. `radius` inflates each
|
|
19
|
+
* obstacle footprint by the car's body radius (default {@link DEFAULT_VEHICLE_RADIUS}).
|
|
20
|
+
*
|
|
21
|
+
* The slide itself is delegated to {@link resolveObstacleStep} so cars and pedestrians share one proven
|
|
22
|
+
* axis-separated resolver: a shallow graze keeps its tangential run and only the into-wall component is
|
|
23
|
+
* shed, a head-on zeroes forward progress. When any component is shed the largest such block this tick is
|
|
24
|
+
* kept as a {@link VehicleImpact}; {@link VehicleObstacleClamp.takeImpact} returns and clears it so a hit
|
|
25
|
+
* fires exactly once. The happy path (no solids near) is a zero-allocation passthrough returning `to`.
|
|
26
|
+
*/
|
|
27
|
+
export function createVehicleObstacleClamp(options) {
|
|
28
|
+
const radius = options.radius ?? DEFAULT_VEHICLE_RADIUS;
|
|
29
|
+
// Grow-only scratch of Y-normalised solids, reused across ticks so a sustained crash allocates nothing
|
|
30
|
+
// after warm-up. Per-clamp (closure-local) so concurrent cars never share it.
|
|
31
|
+
const scratch = [];
|
|
32
|
+
let pending = null;
|
|
33
|
+
function vehicleView(source) {
|
|
34
|
+
const n = source.length;
|
|
35
|
+
for (let i = 0; i < n; i += 1) {
|
|
36
|
+
let entry = scratch[i];
|
|
37
|
+
if (entry === undefined) {
|
|
38
|
+
entry = { position: [0, 0, 0] };
|
|
39
|
+
scratch[i] = entry;
|
|
40
|
+
}
|
|
41
|
+
const from = source[i];
|
|
42
|
+
entry.position[0] = from.position[0];
|
|
43
|
+
entry.position[1] = CAR_BODY_MID_Y;
|
|
44
|
+
entry.position[2] = from.position[2];
|
|
45
|
+
entry.halfExtents = from.halfExtents;
|
|
46
|
+
}
|
|
47
|
+
if (scratch.length > n)
|
|
48
|
+
scratch.length = n;
|
|
49
|
+
return scratch;
|
|
50
|
+
}
|
|
51
|
+
/** Obstacle the attempted endpoint penetrates deepest (its inflated footprint), else nearest centre. */
|
|
52
|
+
function blockingIndex(source, tx, tz) {
|
|
53
|
+
let bestIndex = -1;
|
|
54
|
+
let bestDepth = -Number.POSITIVE_INFINITY;
|
|
55
|
+
let fallbackIndex = 0;
|
|
56
|
+
let fallbackDistSq = Number.POSITIVE_INFINITY;
|
|
57
|
+
for (let i = 0; i < source.length; i += 1) {
|
|
58
|
+
const o = source[i];
|
|
59
|
+
const half = o.halfExtents ?? DEFAULT_OBSTACLE_HALF_EXTENTS;
|
|
60
|
+
const ex = half[0] + radius;
|
|
61
|
+
const ez = half[2] + radius;
|
|
62
|
+
const minX = o.position[0] - ex;
|
|
63
|
+
const maxX = o.position[0] + ex;
|
|
64
|
+
const minZ = o.position[2] - ez;
|
|
65
|
+
const maxZ = o.position[2] + ez;
|
|
66
|
+
if (tx > minX && tx < maxX && tz > minZ && tz < maxZ) {
|
|
67
|
+
const depth = Math.min(tx - minX, maxX - tx, tz - minZ, maxZ - tz);
|
|
68
|
+
if (depth > bestDepth) {
|
|
69
|
+
bestDepth = depth;
|
|
70
|
+
bestIndex = i;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const dx = o.position[0] - tx;
|
|
74
|
+
const dz = o.position[2] - tz;
|
|
75
|
+
const distSq = dx * dx + dz * dz;
|
|
76
|
+
if (distSq < fallbackDistSq) {
|
|
77
|
+
fallbackDistSq = distSq;
|
|
78
|
+
fallbackIndex = i;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return bestIndex >= 0 ? bestIndex : fallbackIndex;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
clampMove(from, to) {
|
|
85
|
+
const obstacles = options.obstacles();
|
|
86
|
+
if (obstacles.length === 0)
|
|
87
|
+
return to;
|
|
88
|
+
const attemptedX = to[0] - from[0];
|
|
89
|
+
const attemptedZ = to[1] - from[1];
|
|
90
|
+
const step = resolveObstacleStep([from[0], CAR_BODY_MID_Y, from[1]], attemptedX, attemptedZ, vehicleView(obstacles), radius);
|
|
91
|
+
const blockedX = attemptedX - step.stepX;
|
|
92
|
+
const blockedZ = attemptedZ - step.stepZ;
|
|
93
|
+
if (blockedX === 0 && blockedZ === 0)
|
|
94
|
+
return to;
|
|
95
|
+
const blockedMag = Math.hypot(blockedX, blockedZ);
|
|
96
|
+
if (blockedMag > BLOCK_EPSILON) {
|
|
97
|
+
const dt = options.dt();
|
|
98
|
+
const closingSpeed = dt > 0 ? blockedMag / dt : 0;
|
|
99
|
+
if (pending === null || closingSpeed > pending.closingSpeed) {
|
|
100
|
+
pending = {
|
|
101
|
+
closingSpeed,
|
|
102
|
+
normal: [-blockedX / blockedMag, -blockedZ / blockedMag],
|
|
103
|
+
obstacleIndex: blockingIndex(obstacles, to[0], to[1]),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return [from[0] + step.stepX, from[1] + step.stepZ];
|
|
108
|
+
},
|
|
109
|
+
takeImpact() {
|
|
110
|
+
const impact = pending;
|
|
111
|
+
pending = null;
|
|
112
|
+
return impact;
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
package/dist/random/rng.d.ts
CHANGED
|
@@ -20,7 +20,10 @@ export declare function randomSeedFrom(seed: number): RandomSeed;
|
|
|
20
20
|
* instead of closing over a generator.
|
|
21
21
|
*/
|
|
22
22
|
export declare function stepRandomSeed(seed: RandomSeed): readonly [value: number, next: RandomSeed];
|
|
23
|
-
/** Deterministic pseudo-random generator seeded from a string or number — same seed, same sequence.
|
|
23
|
+
/** Deterministic pseudo-random generator seeded from a string or number — same seed, same sequence.
|
|
24
|
+
*
|
|
25
|
+
* @capability seeded-random injected deterministic randomness for game logic — never `Math.random` in a simulation
|
|
26
|
+
*/
|
|
24
27
|
export declare function seededRng(seed: string | number): () => number;
|
|
25
28
|
/** Derives independent, deterministic {@link seededRng} streams from one base seed, keyed by stream name. */
|
|
26
29
|
export declare function seededStreams(seed: string | number): (stream: string) => () => number;
|
package/dist/random/rng.js
CHANGED
|
@@ -29,7 +29,10 @@ export function stepRandomSeed(seed) {
|
|
|
29
29
|
const value = ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
30
30
|
return [value, next];
|
|
31
31
|
}
|
|
32
|
-
/** Deterministic pseudo-random generator seeded from a string or number — same seed, same sequence.
|
|
32
|
+
/** Deterministic pseudo-random generator seeded from a string or number — same seed, same sequence.
|
|
33
|
+
*
|
|
34
|
+
* @capability seeded-random injected deterministic randomness for game logic — never `Math.random` in a simulation
|
|
35
|
+
*/
|
|
33
36
|
export function seededRng(seed) {
|
|
34
37
|
let cursor = randomSeedFrom(hashSeed(seed));
|
|
35
38
|
return () => {
|
|
@@ -56,6 +56,8 @@ export declare function convexPresence(config?: {
|
|
|
56
56
|
* WebSocket transport. Omitting `authority` (or passing `"client"`) is **presence-only** — prefer
|
|
57
57
|
* `wsPresence()` to name that intent explicitly. Pass `{ authority: "server" }` for a shared,
|
|
58
58
|
* host-authoritative world — see `examples/HOSTED.md`.
|
|
59
|
+
*
|
|
60
|
+
* @capability multiplayer-ws server-hosted shared world over WebSocket — `multiplayer: ws({ authority: "server" })`
|
|
59
61
|
*/
|
|
60
62
|
export declare function ws(config?: {
|
|
61
63
|
topology?: MultiplayerTopology;
|
|
@@ -78,6 +80,11 @@ export declare function socketIo(config?: {
|
|
|
78
80
|
url?: string;
|
|
79
81
|
authority?: MultiplayerAuthority;
|
|
80
82
|
}): MultiplayerAdapterConfig;
|
|
83
|
+
/**
|
|
84
|
+
* Serverless peer-to-peer (WebRTC) session — one peer hosts, friends join by room code.
|
|
85
|
+
*
|
|
86
|
+
* @capability multiplayer-p2p serverless co-op over WebRTC — `multiplayer: p2p({ room })`
|
|
87
|
+
*/
|
|
81
88
|
export declare function p2p(config?: {
|
|
82
89
|
topology?: MultiplayerTopology;
|
|
83
90
|
room?: string;
|
|
@@ -105,6 +112,12 @@ export declare function resolveAuthority(multiplayer: unknown): MultiplayerAutho
|
|
|
105
112
|
export declare function isPresenceOnly(multiplayer: unknown): boolean;
|
|
106
113
|
/** True for a single-player world — no adapter, or an explicit `offline()` one. Gates offline-only wiring like local whole-world save. */
|
|
107
114
|
export declare function isOffline(multiplayer: unknown): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Explicit single-player adapter. Solo games never need this — omitting `multiplayer` in the shell
|
|
117
|
+
* `defineGame` already defaults to offline; pass it only where an adapter value is structurally required.
|
|
118
|
+
*
|
|
119
|
+
* @capability multiplayer-offline explicit solo adapter — omit `multiplayer` instead; offline is the shell default
|
|
120
|
+
*/
|
|
108
121
|
export declare function offline(): MultiplayerAdapterConfig;
|
|
109
122
|
export declare function adapterOf(multiplayer: unknown): MultiplayerAdapterConfig | null;
|
|
110
123
|
export declare function multiplayerAdapterKind(multiplayer: unknown): string | null;
|
package/dist/runtime/adapter.js
CHANGED
|
@@ -14,6 +14,8 @@ export function convexPresence(config) {
|
|
|
14
14
|
* WebSocket transport. Omitting `authority` (or passing `"client"`) is **presence-only** — prefer
|
|
15
15
|
* `wsPresence()` to name that intent explicitly. Pass `{ authority: "server" }` for a shared,
|
|
16
16
|
* host-authoritative world — see `examples/HOSTED.md`.
|
|
17
|
+
*
|
|
18
|
+
* @capability multiplayer-ws server-hosted shared world over WebSocket — `multiplayer: ws({ authority: "server" })`
|
|
17
19
|
*/
|
|
18
20
|
export function ws(config) {
|
|
19
21
|
return { kind: "ws", topology: config?.topology ?? "shared", url: config?.url, authority: config?.authority };
|
|
@@ -33,6 +35,11 @@ export function fly(config) {
|
|
|
33
35
|
export function socketIo(config) {
|
|
34
36
|
return { kind: "socketio", topology: config?.topology ?? "shared", url: config?.url, authority: config?.authority };
|
|
35
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Serverless peer-to-peer (WebRTC) session — one peer hosts, friends join by room code.
|
|
40
|
+
*
|
|
41
|
+
* @capability multiplayer-p2p serverless co-op over WebRTC — `multiplayer: p2p({ room })`
|
|
42
|
+
*/
|
|
36
43
|
export function p2p(config) {
|
|
37
44
|
return { kind: "p2p", topology: config?.topology ?? "private", room: config?.room, authority: config?.authority };
|
|
38
45
|
}
|
|
@@ -69,6 +76,12 @@ export function isOffline(multiplayer) {
|
|
|
69
76
|
const adapter = adapterOf(multiplayer);
|
|
70
77
|
return adapter === null || adapter.kind === "offline";
|
|
71
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Explicit single-player adapter. Solo games never need this — omitting `multiplayer` in the shell
|
|
81
|
+
* `defineGame` already defaults to offline; pass it only where an adapter value is structurally required.
|
|
82
|
+
*
|
|
83
|
+
* @capability multiplayer-offline explicit solo adapter — omit `multiplayer` instead; offline is the shell default
|
|
84
|
+
*/
|
|
72
85
|
export function offline() {
|
|
73
86
|
return { kind: "offline" };
|
|
74
87
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type DeathSystem } from "../../combat/death.js";
|
|
2
|
+
import { type CombatSpatialDeps, type EffectSystem } from "../../combat/effects.js";
|
|
3
|
+
import { type ProjectileSystem } from "../../combat/projectiles.js";
|
|
4
|
+
import type { PhysicsConfig } from "../../game/defineGame.js";
|
|
5
|
+
import type { GameEvents } from "../../game/events.js";
|
|
6
|
+
import { type VfxInstanceStore } from "../../game/vfxInstance.js";
|
|
7
|
+
import type { LootRegistry } from "../../game/lootTable.js";
|
|
8
|
+
import type { EntityColliderSet } from "../../scene/colliders.js";
|
|
9
|
+
import type { EntityStore } from "../../scene/entityStore.js";
|
|
10
|
+
import type { ObjectStore } from "../../scene/objectStore.js";
|
|
11
|
+
import type { SceneRaycastApi } from "../../scene/sceneRaycast.js";
|
|
12
|
+
import type { WeaponStats } from "../../item/weapon.js";
|
|
13
|
+
import type { SimClock } from "../../time/simClock.js";
|
|
14
|
+
import type { WorldItemRecord, WorldItemSpawnInput } from "../../game/worldItem.js";
|
|
15
|
+
import type { GameContextContent, GameContextEntityEntry, GameContextLoot } from "../gameContext.js";
|
|
16
|
+
import { type CombatFx } from "./combatFx.js";
|
|
17
|
+
/** @internal Wiring combat needs from the live scene, loot, and command seams. */
|
|
18
|
+
export interface CombatSubsystemDeps {
|
|
19
|
+
content: GameContextContent;
|
|
20
|
+
signalNotify: () => void;
|
|
21
|
+
now: () => number;
|
|
22
|
+
events: GameEvents;
|
|
23
|
+
time: SimClock;
|
|
24
|
+
entities: EntityStore;
|
|
25
|
+
objects: ObjectStore;
|
|
26
|
+
combatSpatial: CombatSpatialDeps;
|
|
27
|
+
sceneRaycast: SceneRaycastApi;
|
|
28
|
+
entityCollidersOf: (instanceId: string) => EntityColliderSet | null;
|
|
29
|
+
objectCollidersOf: (instanceId: string) => EntityColliderSet | null;
|
|
30
|
+
catalogEntry: (instanceId: string) => GameContextEntityEntry | null | undefined;
|
|
31
|
+
statsByInstance: Map<string, import("../../scene/entityStats.js").StatValueMap>;
|
|
32
|
+
weapon: WeaponStats;
|
|
33
|
+
loot: GameContextLoot;
|
|
34
|
+
lootRegistry: LootRegistry;
|
|
35
|
+
spawnWorldItem: (input: WorldItemSpawnInput) => WorldItemRecord;
|
|
36
|
+
despawnEntity: (instanceId: string) => boolean;
|
|
37
|
+
runCommand: (name: string, args: unknown) => void;
|
|
38
|
+
localUserId: string;
|
|
39
|
+
physics?: PhysicsConfig;
|
|
40
|
+
}
|
|
41
|
+
/** @internal Effects, projectiles, death, and combat presentation surface. */
|
|
42
|
+
export interface CombatSubsystem {
|
|
43
|
+
death: DeathSystem;
|
|
44
|
+
effects: EffectSystem;
|
|
45
|
+
floatingEffects: EffectSystem;
|
|
46
|
+
projectiles: ProjectileSystem;
|
|
47
|
+
combatFx: CombatFx;
|
|
48
|
+
vfxInstances: VfxInstanceStore;
|
|
49
|
+
}
|
|
50
|
+
/** @internal */
|
|
51
|
+
export declare function createCombatSubsystem(d: CombatSubsystemDeps): CombatSubsystem;
|