incanto 0.65.0 → 0.67.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/bin/_behaviors-loader.mjs +87 -0
- package/bin/incanto-editor.mjs +39 -2
- package/bin/incanto-feel.mjs +4 -1
- package/bin/incanto-frame.mjs +52 -3
- package/bin/incanto-logs.mjs +33 -3
- package/bin/incanto-multiplay.mjs +50 -1
- package/bin/incanto-play.mjs +3 -1
- package/bin/incanto-playtest.mjs +3 -25
- package/bin/incanto-verify.mjs +15 -2
- package/dist/2d.d.ts +76 -4
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +213 -14
- package/dist/3d.js +6 -6
- package/dist/{audio-player-BrEHwbK1.d.ts → audio-player-BNUHo0JF.d.ts} +1 -1
- package/dist/{behavior-CmMuQ6CL.d.ts → behavior-ZnfzKDo3.d.ts} +58 -1
- package/dist/{create-game-DIyh5Zy9.js → create-game-DMetd3vR.js} +14 -8
- package/dist/{create-game-W2xzTJhs.js → create-game-FAXdHta3.js} +138 -11
- package/dist/debug.d.ts +1 -1
- package/dist/debug.js +3 -2
- package/dist/{duplicate-CqSAtdrh.js → duplicate-B8QnX2-r.js} +1 -1
- package/dist/editor.js +7 -2
- package/dist/{environment-presets-BvHs6HEk.js → environment-presets-C-5g2pMh.js} +113 -9
- package/dist/{gameplay-D-H986A4.js → gameplay-CGSv-9XR.js} +295 -12
- package/dist/gameplay.d.ts +102 -6
- package/dist/gameplay.js +2 -2
- package/dist/index.d.ts +50 -6
- package/dist/index.js +7 -6
- package/dist/{loader-CTB441z4.d.ts → loader-DyT30ArB.d.ts} +1 -1
- package/dist/{loader-lQDCwNag.js → loader-KZESOZmE.js} +148 -1
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/{physics-2d-CmnunaR2.js → physics-2d-_B6zj3zU.js} +57 -4
- package/dist/{physics-3d-DULB1lD1.js → physics-3d-i1IeAY0F.js} +74 -5
- package/dist/{quiet-rapier-BAJ4K94N.js → quiet-rapier-C6fW4zcW.js} +14 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-CgeR4ckF.js → register-Cs53CY7s.js} +110 -16
- package/dist/{register-DO5_Qp0Q.js → register-vIxU3Xbv.js} +38 -4
- package/dist/rendering-options-ByAdi8PF.js +184 -0
- package/dist/{replay-CEw2xAhG.js → replay-BkpDV51O.js} +4 -3
- package/dist/{replay-CThvwkcQ.d.ts → replay-D0GcEHYU.d.ts} +1 -1
- package/dist/{split-screen-DxZzC1nm.d.ts → split-screen-DD5eYIgW.d.ts} +2 -2
- package/dist/{split-screen-gb0c3xdb.js → split-screen-DS8RaUV_.js} +2 -2
- package/dist/{src-DYffzHbd.js → src-BLlsGJW9.js} +1 -1
- package/dist/{teardown-BordHL9o.js → teardown-LB9cTnXJ.js} +2 -2
- package/dist/{test-CXZuXs8k.js → test-81uZ967a.js} +127 -24
- package/dist/test.d.ts +7 -5
- package/dist/test.js +2 -2
- package/dist/{touch-DESwnpOc.js → touch-DEAmqGdf.js} +30 -186
- package/dist/vite.d.ts +0 -7
- package/dist/vite.js +13 -3
- package/editor/assets/{agent8-DO5qzIOD.js → agent8-C2jtNKbx.js} +1 -1
- package/editor/assets/debug-DoU3KeqS.js +3 -0
- package/editor/assets/{index-CqEUVaPN.js → index-CNqHojcC.js} +92 -92
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +20 -1
- package/skills/incanto-building-3d-games.md +19 -0
- package/skills/incanto-editor.md +5 -1
- package/skills/incanto-environment.md +2 -2
- package/skills/incanto-gameplay-behaviors.md +86 -7
- package/skills/incanto-hud.md +14 -1
- package/skills/incanto-multiplayer.md +14 -0
- package/skills/incanto-node-reference.md +60 -2
- package/skills/incanto-performance.md +35 -0
- package/skills/incanto-physics-and-input.md +69 -0
- package/skills/incanto-playtesting.md +15 -1
- package/skills/incanto-save-slots.md +21 -1
- package/skills/incanto-verifying-your-game.md +19 -0
- package/skills/incanto-web-integration.md +24 -3
- package/skills/incanto-your-first-game.md +4 -4
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/game.scene.json +38 -19
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/platformer-2d/src/game.scene.json +99 -53
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/star-survivor/src/game.scene.json +31 -17
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/game.scene.json +14 -8
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/grove.scene.json +20 -11
- package/templates-app/village-quest-3d/src/village.scene.json +10 -7
- package/editor/assets/debug-DntG9CVD.js +0 -3
- package/templates-app/beacon-isle-3d/coverage.json +0 -9
- package/templates-app/platformer-2d/coverage.json +0 -5
- package/templates-app/star-survivor/coverage.json +0 -5
- package/templates-app/tps-3d/coverage.json +0 -5
- package/templates-app/village-quest-3d/coverage.json +0 -9
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { f as resolveViewport, n as loadScene, z as registerBehavior } from "./loader-
|
|
2
|
-
import { v as Engine } from "./register-
|
|
1
|
+
import { f as resolveViewport, n as loadScene, z as registerBehavior } from "./loader-KZESOZmE.js";
|
|
2
|
+
import { v as Engine } from "./register-Cs53CY7s.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { n as jsonEquals, t as jsonClone } from "./json-CwwhxQgb.js";
|
|
5
5
|
import { i as getNodeSchema, s as mergeStaticProps } from "./registry-CF70EArN.js";
|
|
6
|
-
import { n as startRecording } from "./replay-
|
|
7
|
-
import { n as registerGameplayBehaviors } from "./gameplay-
|
|
8
|
-
import { t as registerNodes2D } from "./register-
|
|
9
|
-
import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-
|
|
10
|
-
import { a as NetworkManager, c as readSyncKey, i as sanitizeName, n as registerNodesNet, r as NetworkSpawner, s as findOwnerNode, t as createSplitScreen } from "./split-screen-
|
|
6
|
+
import { n as startRecording } from "./replay-BkpDV51O.js";
|
|
7
|
+
import { n as registerGameplayBehaviors } from "./gameplay-CGSv-9XR.js";
|
|
8
|
+
import { t as registerNodes2D } from "./register-vIxU3Xbv.js";
|
|
9
|
+
import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-C-5g2pMh.js";
|
|
10
|
+
import { a as NetworkManager, c as readSyncKey, i as sanitizeName, n as registerNodesNet, r as NetworkSpawner, s as findOwnerNode, t as createSplitScreen } from "./split-screen-DS8RaUV_.js";
|
|
11
11
|
import { Box3, Euler, Matrix4, PerspectiveCamera, Quaternion, Vector3 } from "three";
|
|
12
12
|
//#region src/test/framing.ts
|
|
13
13
|
/**
|
|
@@ -632,12 +632,49 @@ var Driver = class {
|
|
|
632
632
|
buttons;
|
|
633
633
|
moveAction;
|
|
634
634
|
held = /* @__PURE__ */ new Map();
|
|
635
|
+
/**
|
|
636
|
+
* Everything in the scene a click can reach.
|
|
637
|
+
*
|
|
638
|
+
* The bot drove declared ACTIONS and nothing else, so on a mouse game it could
|
|
639
|
+
* only ever watch. Measured across one round: a turn-based tactics game, a
|
|
640
|
+
* tower defense and a duel all reported the identical
|
|
641
|
+
* `? plays — 8 runs played without reaching a win`, and nobody filed it
|
|
642
|
+
* because the message is gracious about it. A whole class — tactics, RTS,
|
|
643
|
+
* tower defense, point-and-click, board games — was structurally unmeasurable
|
|
644
|
+
* by the rung that exists to answer "does this game work".
|
|
645
|
+
*
|
|
646
|
+
* Headless there is no raycast, so a picker is installed that answers from the
|
|
647
|
+
* TREE — the exact recipe `incanto-physics-and-input.md` gives for testing a
|
|
648
|
+
* mouse game — and the click goes through the real `Clickable`, the real
|
|
649
|
+
* `mouseJustPressed` edge, and the real connection. Nothing is faked but the
|
|
650
|
+
* cursor's arrival.
|
|
651
|
+
*/
|
|
652
|
+
clickables;
|
|
653
|
+
clickCooldown = 0;
|
|
654
|
+
clicking = null;
|
|
655
|
+
/** press -> release -> let go. `clicked` fires on RELEASE, so the picker must
|
|
656
|
+
* still be answering with the same node on the frame the release is read. */
|
|
657
|
+
clickPhase = "idle";
|
|
635
658
|
constructor(engine, rand) {
|
|
636
659
|
this.engine = engine;
|
|
637
660
|
this.rand = rand;
|
|
638
661
|
const declared = engine.input.declaredActions();
|
|
639
662
|
this.moveAction = declared.find((a) => a.type === "vector2")?.name ?? null;
|
|
640
663
|
this.buttons = declared.filter((a) => a.type === "button").map((a) => a.name);
|
|
664
|
+
this.clickables = [];
|
|
665
|
+
const root = engine.scene?.root;
|
|
666
|
+
if (root) {
|
|
667
|
+
const walk = (node) => {
|
|
668
|
+
if (node.behavior?.constructor.name === "Clickable") this.clickables.push(node);
|
|
669
|
+
for (const child of node.children) walk(child);
|
|
670
|
+
};
|
|
671
|
+
walk(root);
|
|
672
|
+
if (this.clickables.length > 0 && !engine.picker) engine.picker = () => this.clicking;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
/** How many nodes this bot can click. Reported, so a ✓ says what it drove. */
|
|
676
|
+
get clickableCount() {
|
|
677
|
+
return this.clickables.length;
|
|
641
678
|
}
|
|
642
679
|
/** Grid cell key — coarse on purpose, this is novelty not navigation. */
|
|
643
680
|
cell(p) {
|
|
@@ -668,6 +705,36 @@ var Driver = class {
|
|
|
668
705
|
this.held.set(button, .08 + this.rand() * .12);
|
|
669
706
|
}
|
|
670
707
|
}
|
|
708
|
+
this.maybeClick(dt);
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Press and release over one Clickable, occasionally.
|
|
712
|
+
*
|
|
713
|
+
* `clicked` fires on RELEASE over the same node the press started on, so this
|
|
714
|
+
* has to hold the target across two frames — which is also what makes it a
|
|
715
|
+
* real click rather than a synthetic signal.
|
|
716
|
+
*/
|
|
717
|
+
maybeClick(dt) {
|
|
718
|
+
if (this.clickables.length === 0) return;
|
|
719
|
+
this.engine.input.setPointerPosition(1, 1);
|
|
720
|
+
if (this.clickPhase === "pressed") {
|
|
721
|
+
this.engine.input.handleMouseButton(0, false);
|
|
722
|
+
this.clickPhase = "released";
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (this.clickPhase === "released") {
|
|
726
|
+
this.clicking = null;
|
|
727
|
+
this.clickPhase = "idle";
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
this.clickCooldown -= dt;
|
|
731
|
+
if (this.clickCooldown > 0) return;
|
|
732
|
+
this.clickCooldown = .25 + this.rand() * .6;
|
|
733
|
+
const target = this.clickables[Math.floor(this.rand() * this.clickables.length)];
|
|
734
|
+
if (!target || target.tree === null) return;
|
|
735
|
+
this.clicking = target;
|
|
736
|
+
this.clickPhase = "pressed";
|
|
737
|
+
this.engine.input.handleMouseButton(0, true);
|
|
671
738
|
}
|
|
672
739
|
/** Prefer the least-visited neighbouring cell; ties break randomly. */
|
|
673
740
|
pickHeading(at) {
|
|
@@ -724,6 +791,7 @@ async function runOnce(json, seed, opts) {
|
|
|
724
791
|
for (const child of node.children) walkAll(child);
|
|
725
792
|
};
|
|
726
793
|
walkAll(scene.root);
|
|
794
|
+
const hazards = countHazardsIn(json);
|
|
727
795
|
const targetNodes = [];
|
|
728
796
|
const collect = (node) => {
|
|
729
797
|
if (node !== player && isTarget(node)) targetNodes.push([node.getPath(), node]);
|
|
@@ -746,6 +814,10 @@ async function runOnce(json, seed, opts) {
|
|
|
746
814
|
const stepMs = 1e3 / 60;
|
|
747
815
|
const deadline = (opts.seconds ?? 60) * 1e3;
|
|
748
816
|
let outcome = null;
|
|
817
|
+
let swappedAtMs = null;
|
|
818
|
+
const offSwap = engine.sceneChanged.connect(() => {
|
|
819
|
+
swappedAtMs ??= session.timeMs;
|
|
820
|
+
});
|
|
749
821
|
let at = start;
|
|
750
822
|
let wentAsFarAs = 0;
|
|
751
823
|
while (session.timeMs < deadline) {
|
|
@@ -771,8 +843,13 @@ async function runOnce(json, seed, opts) {
|
|
|
771
843
|
outcome = "fell";
|
|
772
844
|
break;
|
|
773
845
|
}
|
|
846
|
+
if (swappedAtMs !== null) {
|
|
847
|
+
outcome = "swapped";
|
|
848
|
+
break;
|
|
849
|
+
}
|
|
774
850
|
}
|
|
775
851
|
driver.release();
|
|
852
|
+
offSwap();
|
|
776
853
|
const WENT_NOWHERE = scene.dimension === "2d" ? STUCK_2D_PX : STUCK_3D_M;
|
|
777
854
|
outcome ??= wentAsFarAs > WENT_NOWHERE ? "unfinished" : "stuck";
|
|
778
855
|
const heard = engine.audio.recent().map((e) => e.from);
|
|
@@ -800,9 +877,27 @@ async function runOnce(json, seed, opts) {
|
|
|
800
877
|
targets,
|
|
801
878
|
signals,
|
|
802
879
|
declaresEnd: oracle.declaresEnd(),
|
|
880
|
+
hazards,
|
|
803
881
|
hasPlayer: player !== null
|
|
804
882
|
};
|
|
805
883
|
}
|
|
884
|
+
/**
|
|
885
|
+
* How many nodes the scene FILE wires to hurt the player.
|
|
886
|
+
*
|
|
887
|
+
* Read from the JSON, not the tree: a `Spawner` detaches its prefab at ready, so
|
|
888
|
+
* a wave game's hazards exist only in the file until the first wave lands.
|
|
889
|
+
*/
|
|
890
|
+
function countHazardsIn(json) {
|
|
891
|
+
let n = 0;
|
|
892
|
+
const walk = (node) => {
|
|
893
|
+
if (typeof node !== "object" || node === null) return;
|
|
894
|
+
const nd = node;
|
|
895
|
+
if (nd.script?.name === "DamageOnContact") n += 1;
|
|
896
|
+
if (Array.isArray(nd.children)) for (const child of nd.children) walk(child);
|
|
897
|
+
};
|
|
898
|
+
walk(json?.root);
|
|
899
|
+
return n;
|
|
900
|
+
}
|
|
806
901
|
/** Play the scene `runs` times and report what happened. */
|
|
807
902
|
async function playtest(json, opts = {}) {
|
|
808
903
|
const count = Math.max(1, opts.runs ?? 20);
|
|
@@ -812,6 +907,7 @@ async function playtest(json, opts = {}) {
|
|
|
812
907
|
let signals = [];
|
|
813
908
|
let declaresWin = false;
|
|
814
909
|
let hasPlayer = false;
|
|
910
|
+
let hazards = 0;
|
|
815
911
|
for (let i = 0; i < count; i++) {
|
|
816
912
|
const one = await runOnce(json, base + i, opts);
|
|
817
913
|
runs.push(one.run);
|
|
@@ -819,6 +915,7 @@ async function playtest(json, opts = {}) {
|
|
|
819
915
|
signals = one.signals;
|
|
820
916
|
declaresWin = declaresWin || one.declaresEnd;
|
|
821
917
|
hasPlayer = hasPlayer || one.hasPlayer;
|
|
918
|
+
hazards = one.hazards;
|
|
822
919
|
}
|
|
823
920
|
const probe = await createPlaySession(json, {
|
|
824
921
|
seed: base,
|
|
@@ -848,6 +945,7 @@ async function playtest(json, opts = {}) {
|
|
|
848
945
|
inertActions: [],
|
|
849
946
|
declaresWin,
|
|
850
947
|
hasPlayer,
|
|
948
|
+
hazards,
|
|
851
949
|
seconds: opts.seconds ?? 60,
|
|
852
950
|
feedback: {
|
|
853
951
|
declaredAudio: feedbackNodes.audio,
|
|
@@ -872,7 +970,7 @@ function median(values) {
|
|
|
872
970
|
* of green is a wall nobody reads.
|
|
873
971
|
*/
|
|
874
972
|
function playtestText(report) {
|
|
875
|
-
const { runs, targets, signals } = report;
|
|
973
|
+
const { runs, targets, signals, hazards } = report;
|
|
876
974
|
const total = runs.length;
|
|
877
975
|
const lines = [];
|
|
878
976
|
lines.push(`${total} runs × up to ${report.seconds}s`);
|
|
@@ -889,18 +987,21 @@ function playtestText(report) {
|
|
|
889
987
|
"fell",
|
|
890
988
|
"lost",
|
|
891
989
|
"stuck",
|
|
892
|
-
"unfinished"
|
|
990
|
+
"unfinished",
|
|
991
|
+
"swapped"
|
|
893
992
|
]) {
|
|
894
993
|
const hits = runs.filter((r) => r.outcome === bad);
|
|
895
994
|
if (hits.length === 0) continue;
|
|
896
995
|
const where = bad === "fell" ? ` (last at y=${hits[0].endedAt[1].toFixed(1)})` : "";
|
|
897
|
-
const mark = bad === "unfinished" ? "·" : "✗";
|
|
898
|
-
const what = bad === "unfinished" ? "ran out of time still playing" : `${bad} in ${pct(hits.length, total)}`;
|
|
899
|
-
lines.push(bad === "unfinished" ? ` ${mark} ${pct(hits.length, total)} ${what}` : ` ${mark} ${what}${where}`);
|
|
996
|
+
const mark = bad === "unfinished" || bad === "swapped" ? "·" : "✗";
|
|
997
|
+
const what = bad === "unfinished" ? "ran out of time still playing" : bad === "swapped" ? "the game changed scene — this bot is bound to the tree it started in, so nothing after the swap was measured (not a defect)" : `${bad} in ${pct(hits.length, total)}`;
|
|
998
|
+
lines.push(bad === "unfinished" || bad === "swapped" ? ` ${mark} ${pct(hits.length, total)} ${what}` : ` ${mark} ${what}${where}`);
|
|
900
999
|
}
|
|
901
1000
|
const everReached = new Set(runs.flatMap((r) => r.reached));
|
|
902
1001
|
const missed = targets.filter((path) => !everReached.has(path));
|
|
903
|
-
|
|
1002
|
+
const anySwapped = runs.some((r) => r.outcome === "swapped");
|
|
1003
|
+
if (missed.length > 0 && anySwapped) lines.push(` · ${missed.length} destination(s) unmeasured — a run changed scene and the bot stopped there. Judge these with a scripted run.`);
|
|
1004
|
+
else if (missed.length > 0) {
|
|
904
1005
|
lines.push("");
|
|
905
1006
|
lines.push(` never reached (${missed.length} of ${targets.length}):`);
|
|
906
1007
|
for (const path of missed.slice(0, 12)) lines.push(` ${path}`);
|
|
@@ -916,7 +1017,9 @@ function playtestText(report) {
|
|
|
916
1017
|
}
|
|
917
1018
|
const damage = runs.reduce((n, r) => n + r.damageTaken, 0);
|
|
918
1019
|
lines.push("");
|
|
919
|
-
|
|
1020
|
+
if (damage > 0) lines.push(` danger: ${damage} hits across ${total} runs (${(damage / total).toFixed(1)} per run)`);
|
|
1021
|
+
else if (hazards === 0) lines.push(" danger: nothing in this scene can hurt the player — no DamageOnContact, no hazard group. A game with no way to lose is a walkabout.");
|
|
1022
|
+
else lines.push(` danger: ${hazards} hazard(s) are wired and none of them landed a hit in ${total} runs. They may be unreachable — or the bot may simply have outrun them, which a real player would not. Judge it with a scripted run.`);
|
|
920
1023
|
return lines.join("\n");
|
|
921
1024
|
}
|
|
922
1025
|
/** The failing runs worth saving, newest problem first. */
|
|
@@ -1709,8 +1812,8 @@ async function playMultiplayer(opts) {
|
|
|
1709
1812
|
*/
|
|
1710
1813
|
async function joinLate(server, first, opts) {
|
|
1711
1814
|
try {
|
|
1712
|
-
const { Engine } = await import("./register-
|
|
1713
|
-
const { loadScene } = await import("./loader-
|
|
1815
|
+
const { Engine } = await import("./register-Cs53CY7s.js").then((n) => n.y);
|
|
1816
|
+
const { loadScene } = await import("./loader-KZESOZmE.js").then((n) => n.r);
|
|
1714
1817
|
const { jsonClone } = await import("./json-CwwhxQgb.js").then((n) => n.i);
|
|
1715
1818
|
const engine = new Engine(opts.seed !== void 0 ? { seed: opts.seed + 999 } : {});
|
|
1716
1819
|
engine.setScene(loadScene(jsonClone(opts.scene), opts.load));
|
|
@@ -1891,9 +1994,9 @@ function findFloatingProps(json, opts = {}) {
|
|
|
1891
1994
|
const types = new Set(opts.types ?? GROUNDED_TYPES);
|
|
1892
1995
|
let scene;
|
|
1893
1996
|
try {
|
|
1894
|
-
scene = loadScene(json);
|
|
1895
|
-
} catch {
|
|
1896
|
-
|
|
1997
|
+
scene = loadScene(json, { stubMissingBehaviors: true });
|
|
1998
|
+
} catch (error) {
|
|
1999
|
+
throw new IncantoError("BAD_FORMAT", `findFloatingProps: the scene does not load, so nothing could be measured. ${error?.message ?? String(error)}`);
|
|
1897
2000
|
}
|
|
1898
2001
|
let ground = null;
|
|
1899
2002
|
const findGround = (node) => {
|
|
@@ -1901,7 +2004,7 @@ function findFloatingProps(json, opts = {}) {
|
|
|
1901
2004
|
for (const child of node.children) findGround(child);
|
|
1902
2005
|
};
|
|
1903
2006
|
findGround(scene.root);
|
|
1904
|
-
if (!ground)
|
|
2007
|
+
if (!ground) throw new IncantoError("NODE_NOT_FOUND", "findFloatingProps: no Terrain3D in this scene, so there is no ground to measure against. This check reads terrain height; a level built from mesh floors needs a physics raycast instead.");
|
|
1905
2008
|
const out = [];
|
|
1906
2009
|
const walk = (node, path, ox, oy, oz, yaw, bodyOwned) => {
|
|
1907
2010
|
const n = node;
|
|
@@ -2072,10 +2175,10 @@ async function runScript(json, opts) {
|
|
|
2072
2175
|
if (opts.locale) engine.locale.locale = opts.locale;
|
|
2073
2176
|
const physics = opts.physics ?? "auto";
|
|
2074
2177
|
if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
|
|
2075
|
-
const { enablePhysics2D } = await import("./physics-2d-
|
|
2178
|
+
const { enablePhysics2D } = await import("./physics-2d-_B6zj3zU.js").then((n) => n.r);
|
|
2076
2179
|
await enablePhysics2D(engine);
|
|
2077
2180
|
} else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
|
|
2078
|
-
const { enablePhysics3D } = await import("./physics-3d-
|
|
2181
|
+
const { enablePhysics3D } = await import("./physics-3d-i1IeAY0F.js").then((n) => n.r);
|
|
2079
2182
|
await enablePhysics3D(engine);
|
|
2080
2183
|
}
|
|
2081
2184
|
const failures = [];
|
|
@@ -2188,10 +2291,10 @@ async function createPlaySession(json, opts = {}) {
|
|
|
2188
2291
|
engine.setScene(scene);
|
|
2189
2292
|
const physics = opts.physics ?? "auto";
|
|
2190
2293
|
if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
|
|
2191
|
-
const { enablePhysics2D } = await import("./physics-2d-
|
|
2294
|
+
const { enablePhysics2D } = await import("./physics-2d-_B6zj3zU.js").then((n) => n.r);
|
|
2192
2295
|
await enablePhysics2D(engine);
|
|
2193
2296
|
} else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
|
|
2194
|
-
const { enablePhysics3D } = await import("./physics-3d-
|
|
2297
|
+
const { enablePhysics3D } = await import("./physics-3d-i1IeAY0F.js").then((n) => n.r);
|
|
2195
2298
|
await enablePhysics3D(engine);
|
|
2196
2299
|
}
|
|
2197
2300
|
const stepMs = 1e3 / (opts.fixedHz ?? 60);
|
package/dist/test.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { P as Scene, Pt as LogEntry, b as Engine, jt as Node, n as BehaviorCtor } from "./behavior-
|
|
1
|
+
import { P as Scene, Pt as LogEntry, b as Engine, jt as Node, n as BehaviorCtor } from "./behavior-ZnfzKDo3.js";
|
|
2
2
|
import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
|
|
3
|
-
import { t as LoadSceneOptions } from "./loader-
|
|
4
|
-
import { l as auditScene, o as IncantoError, r as ReplayJson } from "./replay-
|
|
5
|
-
import { l as LocalGameServerOptions, n as SplitScreenPlayer } from "./split-screen-
|
|
3
|
+
import { t as LoadSceneOptions } from "./loader-DyT30ArB.js";
|
|
4
|
+
import { l as auditScene, o as IncantoError, r as ReplayJson } from "./replay-D0GcEHYU.js";
|
|
5
|
+
import { l as LocalGameServerOptions, n as SplitScreenPlayer } from "./split-screen-DD5eYIgW.js";
|
|
6
6
|
|
|
7
7
|
//#region src/test/facing.d.ts
|
|
8
8
|
interface FacingOptions {
|
|
@@ -295,7 +295,7 @@ declare function multiplayProblems(detail: MultiplayPlayer[], rooms: string[]):
|
|
|
295
295
|
declare function multiplayText(report: MultiplayReport): string;
|
|
296
296
|
//#endregion
|
|
297
297
|
//#region src/test/playtest.d.ts
|
|
298
|
-
type Outcome = "won" | "lost" | "fell" | "error" | "stuck" | "unfinished";
|
|
298
|
+
type Outcome = "won" | "lost" | "fell" | "error" | "stuck" | "unfinished" | "swapped";
|
|
299
299
|
interface PlaytestOptions {
|
|
300
300
|
/** How many independent runs. Each gets its own seed. Default 20. */
|
|
301
301
|
runs?: number;
|
|
@@ -358,6 +358,8 @@ interface PlaytestReport {
|
|
|
358
358
|
* author looking for a bug that was never there.
|
|
359
359
|
*/
|
|
360
360
|
declaresWin: boolean;
|
|
361
|
+
/** How many nodes are wired to hurt the player — see the `danger:` line. */
|
|
362
|
+
hazards: number;
|
|
361
363
|
/**
|
|
362
364
|
* Did `findPlayer` find anyone to drive?
|
|
363
365
|
*
|
package/dist/test.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { r as auditScene } from "./replay-
|
|
2
|
-
import { C as describeFraming, S as playtestText, _ as facingReport, a as registerAllNodes, b as findPlayer, c as ladderText, d as multiplayText, f as playMultiplayer, g as agreementProblems, h as takeAgreementSnapshots, i as findFloatingProps, l as ladderVerdict, m as feelText, n as createPlaySession, o as runScript, p as feelReport, r as describeCapture, s as validateScene, t as captureScene, u as multiplayProblems, v as facingText, w as framingText, x as playtest, y as failingReplays } from "./test-
|
|
1
|
+
import { r as auditScene } from "./replay-BkpDV51O.js";
|
|
2
|
+
import { C as describeFraming, S as playtestText, _ as facingReport, a as registerAllNodes, b as findPlayer, c as ladderText, d as multiplayText, f as playMultiplayer, g as agreementProblems, h as takeAgreementSnapshots, i as findFloatingProps, l as ladderVerdict, m as feelText, n as createPlaySession, o as runScript, p as feelReport, r as describeCapture, s as validateScene, t as captureScene, u as multiplayProblems, v as facingText, w as framingText, x as playtest, y as failingReplays } from "./test-81uZ967a.js";
|
|
3
3
|
export { agreementProblems, auditScene, captureScene, createPlaySession, describeCapture, describeFraming, facingReport, facingText, failingReplays, feelReport, feelText, findFloatingProps, findPlayer, framingText, ladderText, ladderVerdict, multiplayProblems, multiplayText, playMultiplayer, playtest, playtestText, registerAllNodes, runScript, takeAgreementSnapshots, validateScene };
|
|
@@ -1,186 +1,3 @@
|
|
|
1
|
-
//#region src/core/drive-script.ts
|
|
2
|
-
const num = (raw) => {
|
|
3
|
-
if (raw === void 0) return null;
|
|
4
|
-
const n = Number(raw);
|
|
5
|
-
return Number.isFinite(n) ? n : null;
|
|
6
|
-
};
|
|
7
|
-
/**
|
|
8
|
-
* `vector move 0 1; step 2000` → steps, or an error naming the bad command.
|
|
9
|
-
*
|
|
10
|
-
* Semicolons and newlines both separate, so a whole plan fits in one shell
|
|
11
|
-
* argument and a longer one fits in a file. `#` starts a comment.
|
|
12
|
-
*
|
|
13
|
-
* A command it does not know REFUSES THE WHOLE SCRIPT. Skipping it would leave
|
|
14
|
-
* an agent looking at a frame that never moved, believing it had walked
|
|
15
|
-
* somewhere — the failure mode this exists to prevent.
|
|
16
|
-
*/
|
|
17
|
-
function parseDrive(text) {
|
|
18
|
-
const steps = [];
|
|
19
|
-
let totalMs = 0;
|
|
20
|
-
const lines = text.split(/[;\n]/).map((l) => l.replace(/#.*$/, "").trim()).filter(Boolean);
|
|
21
|
-
for (const line of lines) {
|
|
22
|
-
const [word, ...rest] = line.split(/\s+/);
|
|
23
|
-
const bad = (why) => ({
|
|
24
|
-
steps: [],
|
|
25
|
-
error: `${why}: "${line}"`,
|
|
26
|
-
totalMs: 0
|
|
27
|
-
});
|
|
28
|
-
switch (word) {
|
|
29
|
-
case "press":
|
|
30
|
-
case "release": {
|
|
31
|
-
const action = rest[0];
|
|
32
|
-
if (!action) return bad(`${word} needs an action name`);
|
|
33
|
-
steps.push(word === "press" ? {
|
|
34
|
-
kind: "press",
|
|
35
|
-
action
|
|
36
|
-
} : {
|
|
37
|
-
kind: "release",
|
|
38
|
-
action
|
|
39
|
-
});
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
case "vector": {
|
|
43
|
-
const action = rest[0];
|
|
44
|
-
const x = num(rest[1]);
|
|
45
|
-
const y = num(rest[2]);
|
|
46
|
-
if (!action || x === null || y === null) return bad("vector needs an action and two numbers");
|
|
47
|
-
steps.push({
|
|
48
|
-
kind: "vector",
|
|
49
|
-
action,
|
|
50
|
-
x,
|
|
51
|
-
y
|
|
52
|
-
});
|
|
53
|
-
break;
|
|
54
|
-
}
|
|
55
|
-
case "key": {
|
|
56
|
-
const code = rest[0];
|
|
57
|
-
const dir = rest[1];
|
|
58
|
-
if (!code || dir !== "down" && dir !== "up") return bad("key needs a code and down|up");
|
|
59
|
-
steps.push({
|
|
60
|
-
kind: "key",
|
|
61
|
-
code,
|
|
62
|
-
down: dir === "down"
|
|
63
|
-
});
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
case "pointer": {
|
|
67
|
-
const dx = num(rest[0]);
|
|
68
|
-
const dy = num(rest[1]);
|
|
69
|
-
if (dx === null || dy === null) return bad("pointer needs two numbers");
|
|
70
|
-
steps.push({
|
|
71
|
-
kind: "pointer",
|
|
72
|
-
dx,
|
|
73
|
-
dy
|
|
74
|
-
});
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
case "step": {
|
|
78
|
-
const ms = num(rest[0]);
|
|
79
|
-
if (ms === null || ms < 0) return bad("step needs a duration in ms");
|
|
80
|
-
steps.push({
|
|
81
|
-
kind: "step",
|
|
82
|
-
ms
|
|
83
|
-
});
|
|
84
|
-
totalMs += ms;
|
|
85
|
-
break;
|
|
86
|
-
}
|
|
87
|
-
default: return bad("not a command");
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return {
|
|
91
|
-
steps,
|
|
92
|
-
error: null,
|
|
93
|
-
totalMs
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
//#endregion
|
|
97
|
-
//#region src/core/log-report.ts
|
|
98
|
-
const RANK = {
|
|
99
|
-
debug: 0,
|
|
100
|
-
info: 1,
|
|
101
|
-
warn: 2,
|
|
102
|
-
error: 3
|
|
103
|
-
};
|
|
104
|
-
/** Under this, with the loop running, is a frame rate somebody can feel. */
|
|
105
|
-
const SLOW_FPS = 50;
|
|
106
|
-
/** How many distinct messages to carry. Beyond this it is a log file, not a report. */
|
|
107
|
-
const MAX_GROUPS = 20;
|
|
108
|
-
/** The engine logs values, not strings — format them the way a console would. */
|
|
109
|
-
function format(parts) {
|
|
110
|
-
return parts.map((p) => {
|
|
111
|
-
if (typeof p === "string") return p;
|
|
112
|
-
if (p instanceof Error) return p.message;
|
|
113
|
-
try {
|
|
114
|
-
return JSON.stringify(p);
|
|
115
|
-
} catch {
|
|
116
|
-
return String(p);
|
|
117
|
-
}
|
|
118
|
-
}).join(" ");
|
|
119
|
-
}
|
|
120
|
-
function logReport(input) {
|
|
121
|
-
const counts = {
|
|
122
|
-
debug: 0,
|
|
123
|
-
info: 0,
|
|
124
|
-
warn: 0,
|
|
125
|
-
error: 0
|
|
126
|
-
};
|
|
127
|
-
const byMessage = /* @__PURE__ */ new Map();
|
|
128
|
-
for (const e of input.entries) {
|
|
129
|
-
counts[e.level] += 1;
|
|
130
|
-
const message = format(e.parts);
|
|
131
|
-
const key = `${e.level}\0${message}`;
|
|
132
|
-
const seen = byMessage.get(key);
|
|
133
|
-
if (seen) {
|
|
134
|
-
seen.count += 1;
|
|
135
|
-
seen.lastMs = Math.max(seen.lastMs, e.timeMs);
|
|
136
|
-
} else byMessage.set(key, {
|
|
137
|
-
level: e.level,
|
|
138
|
-
message,
|
|
139
|
-
count: 1,
|
|
140
|
-
lastMs: e.timeMs
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
const grouped = [...byMessage.values()].sort((a, b) => RANK[b.level] - RANK[a.level] || b.lastMs - a.lastMs).slice(0, MAX_GROUPS);
|
|
144
|
-
const hidden = input.hidden === true;
|
|
145
|
-
const slow = !hidden && input.stats.running && input.stats.fps > 0 && input.stats.fps < SLOW_FPS;
|
|
146
|
-
return {
|
|
147
|
-
hidden,
|
|
148
|
-
ok: counts.error === 0 && input.stats.errors === 0 && input.assetErrors.length === 0,
|
|
149
|
-
slow,
|
|
150
|
-
counts,
|
|
151
|
-
grouped,
|
|
152
|
-
worst: grouped.find((g) => g.level === "error") ?? grouped[0] ?? null,
|
|
153
|
-
stats: input.stats,
|
|
154
|
-
assetErrors: input.assetErrors
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
/** The report as a person or an agent reads it: what is wrong, then the numbers. */
|
|
158
|
-
function logText(r) {
|
|
159
|
-
const lines = [];
|
|
160
|
-
if (r.stats.errors > 0) lines.push(`${r.stats.errors} error${r.stats.errors === 1 ? "" : "s"} were swallowed to keep the game alive — it is running, and something in it is broken`);
|
|
161
|
-
for (const a of r.assetErrors) lines.push(`asset ${a.ref} never loaded: ${a.error} (${a.url})`);
|
|
162
|
-
for (const g of r.grouped) {
|
|
163
|
-
if (g.level === "debug") continue;
|
|
164
|
-
lines.push(`[${g.level}] ${g.message}${g.count > 1 ? ` ×${g.count}` : ""}`);
|
|
165
|
-
}
|
|
166
|
-
if (lines.length === 0) lines.push("nothing wrong — no warnings, no errors, no failed assets");
|
|
167
|
-
if (!r.stats.running) lines.push(`not running — the loop is stopped (${r.stats.nodes} nodes in the scene)`);
|
|
168
|
-
else if (r.hidden) lines.push(`${r.stats.nodes} nodes · frame rate not measurable — the page is hidden (a window covered by another counts), and the browser throttles it. Bring it to the front`);
|
|
169
|
-
else lines.push(`${Math.round(r.stats.fps)} fps · ${r.stats.frameMs.toFixed(1)}ms per frame · ${r.stats.nodes} nodes${r.slow ? " — slow enough to feel" : ""}`);
|
|
170
|
-
return lines.join("\n");
|
|
171
|
-
}
|
|
172
|
-
//#endregion
|
|
173
|
-
//#region src/core/rendering-options.ts
|
|
174
|
-
function resolveRendering(environment, fallback, devicePixelRatio, explicit) {
|
|
175
|
-
const rendering = environment?.rendering ?? {};
|
|
176
|
-
const scenePixelRatio = rendering.pixelRatio === "device" ? devicePixelRatio : rendering.pixelRatio;
|
|
177
|
-
return {
|
|
178
|
-
antialias: explicit?.antialias ?? rendering.antialias ?? fallback.antialias,
|
|
179
|
-
pixelRatio: explicit?.pixelRatio ?? scenePixelRatio ?? fallback.pixelRatio,
|
|
180
|
-
preserveDrawingBuffer: explicit?.preserveDrawingBuffer ?? rendering.preserveDrawingBuffer ?? fallback.preserveDrawingBuffer
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
//#endregion
|
|
184
1
|
//#region src/core/touch.ts
|
|
185
2
|
/** Drags inside this fraction of the radius read as neutral. */
|
|
186
3
|
const DEADZONE = .08;
|
|
@@ -228,6 +45,33 @@ function attachTouchControls(engine, container, opts = {}) {
|
|
|
228
45
|
function isCoarsePointer() {
|
|
229
46
|
return typeof matchMedia !== "undefined" && matchMedia("(pointer: coarse)").matches;
|
|
230
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Retarget moves and ups back to this element — and never at the cost of the press.
|
|
50
|
+
*
|
|
51
|
+
* `setPointerCapture` THROWS `NotFoundError` for a pointerId that is not
|
|
52
|
+
* currently active, and `?.` guards a missing method rather than a throwing
|
|
53
|
+
* one. It ran BEFORE `feed()` — the try/catch that would have absorbed it was
|
|
54
|
+
* the very next line — so a synthetic `pointerdown` lost its press entirely.
|
|
55
|
+
*
|
|
56
|
+
* Real fingers are unaffected: a real touch is an active pointer. What it broke
|
|
57
|
+
* was every `dispatchEvent`-based driver, which is how a touch game is tested
|
|
58
|
+
* at all. And the instrument agreed with the bug: happy-dom's
|
|
59
|
+
* `setPointerCapture` throws nothing, so a harness printed `ALL CHECKS PASSED`
|
|
60
|
+
* while Chrome lost the same game. Controlled A/B, one page, one autopilot, one
|
|
61
|
+
* variable:
|
|
62
|
+
*
|
|
63
|
+
* capture throws gameover @6.48s, 7/9 shards, 19 uncaught errors
|
|
64
|
+
* capture guarded WON @6.30s, 9/9 shards, 0 errors
|
|
65
|
+
*
|
|
66
|
+
* Worse than losing the press: `activePointer` is assigned first, so the
|
|
67
|
+
* matching `pointerup` still fired `releaseAction` — a release the state
|
|
68
|
+
* machine never earned.
|
|
69
|
+
*/
|
|
70
|
+
function capturePointer(el, pointerId) {
|
|
71
|
+
try {
|
|
72
|
+
el.setPointerCapture?.(pointerId);
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
231
75
|
var TouchControls = class {
|
|
232
76
|
elements = [];
|
|
233
77
|
detachers = [];
|
|
@@ -294,7 +138,7 @@ var TouchControls = class {
|
|
|
294
138
|
cx = rect.left + rect.width / 2;
|
|
295
139
|
cy = rect.top + rect.height / 2;
|
|
296
140
|
radius = rect.width / 2;
|
|
297
|
-
base
|
|
141
|
+
capturePointer(base, e.pointerId);
|
|
298
142
|
apply(e.clientX, e.clientY);
|
|
299
143
|
};
|
|
300
144
|
const onMove = (e) => {
|
|
@@ -344,8 +188,8 @@ var TouchControls = class {
|
|
|
344
188
|
let activePointer = null;
|
|
345
189
|
const onDown = (e) => {
|
|
346
190
|
activePointer = e.pointerId;
|
|
347
|
-
button.setPointerCapture?.(e.pointerId);
|
|
348
191
|
feed(() => engine.input.pressAction(action));
|
|
192
|
+
capturePointer(button, e.pointerId);
|
|
349
193
|
button.style.background = "rgba(255,255,255,0.3)";
|
|
350
194
|
};
|
|
351
195
|
const release = (e) => {
|
|
@@ -378,4 +222,4 @@ function style(el, rules) {
|
|
|
378
222
|
for (const [key, value] of Object.entries(rules)) el.style[key] = value;
|
|
379
223
|
}
|
|
380
224
|
//#endregion
|
|
381
|
-
export {
|
|
225
|
+
export { joystickVector as i, attachTouchControls as n, capturePointer as r, TouchControls as t };
|
package/dist/vite.d.ts
CHANGED
|
@@ -74,13 +74,6 @@ interface FrameEndpointOptions {
|
|
|
74
74
|
*/
|
|
75
75
|
store?: Map<string, FrameSignature>;
|
|
76
76
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Wire `/__incanto/ping` and `/__incanto/frame` onto a dev server.
|
|
79
|
-
*
|
|
80
|
-
* `ping` is how the CLI tells our server apart from anything else listening in
|
|
81
|
-
* the container: it enumerates the ports that are actually open and asks each
|
|
82
|
-
* one. That answer has to be cheap and unmistakable.
|
|
83
|
-
*/
|
|
84
77
|
declare function serveFrameEndpoints(host: FrameHost, version: string, opts?: FrameEndpointOptions): void;
|
|
85
78
|
//#endregion
|
|
86
79
|
//#region src/vite/index.d.ts
|
package/dist/vite.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as VERSION } from "./src-
|
|
1
|
+
import { t as VERSION } from "./src-BLlsGJW9.js";
|
|
2
2
|
import { n as diffSignatures } from "./frame-report-BSMny7oe.js";
|
|
3
|
-
import { s as validateScene } from "./test-
|
|
3
|
+
import { s as validateScene } from "./test-81uZ967a.js";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { basename, dirname, join, normalize, relative, resolve, sep } from "node:path";
|
|
6
6
|
//#region src/vite/frame-endpoint.ts
|
|
@@ -28,6 +28,14 @@ const DEFAULT_LABEL = "last";
|
|
|
28
28
|
* the container: it enumerates the ports that are actually open and asks each
|
|
29
29
|
* one. That answer has to be cheap and unmistakable.
|
|
30
30
|
*/
|
|
31
|
+
/** The consuming project's package name, for the ping's identity. */
|
|
32
|
+
function projectName(root) {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(readFileSync(join(root, "package.json"), "utf8")).name ?? "";
|
|
35
|
+
} catch {
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
31
39
|
function serveFrameEndpoints(host, version, opts = {}) {
|
|
32
40
|
const channels = [
|
|
33
41
|
host.environments?.client?.hot,
|
|
@@ -65,7 +73,9 @@ function serveFrameEndpoints(host, version, opts = {}) {
|
|
|
65
73
|
host.middlewares.use("/__incanto/ping", (_req, res) => {
|
|
66
74
|
json$1(res, 200, {
|
|
67
75
|
incanto: version,
|
|
68
|
-
frame: Boolean(channel)
|
|
76
|
+
frame: Boolean(channel),
|
|
77
|
+
root: host.config?.root ?? process.cwd(),
|
|
78
|
+
project: projectName(host.config?.root ?? process.cwd())
|
|
69
79
|
});
|
|
70
80
|
});
|
|
71
81
|
serveLogs(host, channels, waiting, () => nextId++, timeoutMs);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./index-
|
|
1
|
+
import{n as e}from"./index-CNqHojcC.js";async function t(t){return new n((await e(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,t)}var n=class{raw;active=new Map;reconnecting=!1;disposed=!1;constructor(e,t){this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{await this.rawConnect();for(let e of this.active.values())e.off(),e.off=e.make()}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{t as createAgent8Server};
|