incanto 0.70.0 → 0.72.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/incanto-editor.mjs +15 -9
- package/bin/incanto-feel.mjs +11 -1
- package/bin/incanto-play.mjs +11 -1
- package/bin/incanto-playtest.mjs +11 -1
- package/bin/incanto-verify.mjs +36 -98
- package/dist/2d.d.ts +18 -3
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +3 -3
- package/dist/3d.js +6 -6
- package/dist/{agent8-CmNF01gA.js → agent8-BqZ0_O1u.js} +1 -1
- package/dist/{audio-player-DaMxqfNE.d.ts → audio-player-ioZ7VKC6.d.ts} +1 -1
- package/dist/{behavior-DZExDn9o.d.ts → behavior-Bf9P9oB2.d.ts} +12 -1
- package/dist/{create-game-Bwvh6q8A.js → create-game-C9rWqqAC.js} +37 -13
- package/dist/{create-game-C7ffQWW7.js → create-game-CmFAtLZp.js} +8 -7
- package/dist/debug.d.ts +1 -1
- package/dist/editor.js +1083 -1045
- package/dist/{environment-presets-D6Q5BxeE.js → environment-presets-BeHoaGhy.js} +3 -3
- package/dist/{frame-report-Dlq13Gyj.js → frame-report-D-_7YF2G.js} +1 -1
- package/dist/{gameplay-BfHkuzVb.js → gameplay-BlQe-M07.js} +108 -6
- package/dist/gameplay.d.ts +68 -4
- package/dist/gameplay.js +2 -2
- package/dist/index.d.ts +27 -4
- package/dist/index.js +4 -4
- package/dist/{loader-Cff09LMm.d.ts → loader-CBfQzB6S.d.ts} +10 -3
- package/dist/net.d.ts +2 -2
- package/dist/net.js +2 -2
- package/dist/{physics-2d-CE0Qvy3V.js → physics-2d-DrIgTlLk.js} +2 -2
- package/dist/{physics-3d-CpH-2gn5.js → physics-3d-BzOCw1VS.js} +3 -3
- package/dist/{picking-CQJ_PJKh.js → picking-C9McVJrO.js} +3 -3
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-6DYnKZcy.js → register-5IYfooAN.js} +49 -15
- package/dist/{register-Bkk0wSDB.js → register-C8HuRkOf.js} +100 -8
- package/dist/{replay-Dmw-PKQu.d.ts → replay-CBEChqXq.d.ts} +2 -2
- package/dist/{replay-DjAkAzMq.js → replay-datiAE-b.js} +15 -8
- package/dist/{save-slots-BXVg148r.js → save-slots-CEuJPUle.js} +102 -4
- package/dist/{split-screen-PL78oVXP.js → split-screen-Bm5DTElG.js} +3 -3
- package/dist/{split-screen--k-XpBjr.d.ts → split-screen-DkqLng6q.d.ts} +2 -2
- package/dist/{src-Cxfiv1Hg.js → src-DRSsRleD.js} +1 -1
- package/dist/{test-iHYVUcDK.js → test-Djuq3Vhq.js} +252 -24
- package/dist/test.d.ts +113 -5
- package/dist/test.js +3 -3
- package/dist/vite.d.ts +8 -1
- package/dist/vite.js +23 -6
- package/editor/assets/{agent8-D0MS174y.js → agent8-CMKz2cax.js} +1 -1
- package/editor/assets/{debug-BnXkKuYu.js → debug-DLrQm-az.js} +1 -1
- package/editor/assets/{index-CIu3uc3l.js → index-BEKuxYcE.js} +94 -94
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +4 -0
- package/skills/incanto-behaviors-and-scripts.md +30 -9
- package/skills/incanto-building-2d-games.md +26 -2
- package/skills/incanto-editor.md +4 -0
- package/skills/incanto-gameplay-behaviors.md +63 -1
- package/skills/incanto-hud.md +14 -0
- package/skills/incanto-node-reference.md +18 -2
- package/skills/incanto-playtesting.md +34 -0
- package/skills/incanto-save-slots.md +56 -1
- package/skills/incanto-scene-json-authoring.md +30 -0
- package/skills/incanto-verifying-your-game.md +11 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/game.scene.json +396 -386
- package/templates-app/molehill-2d/package.json +1 -1
- package/templates-app/molehill-2d/src/game.scene.json +10 -60
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/platformer-2d/src/game.scene.json +268 -504
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/star-survivor/src/game.scene.json +23 -33
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/game.scene.json +90 -105
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/behaviors.ts +21 -4
- package/templates-app/village-quest-3d/src/grove.scene.json +99 -118
- package/templates-app/village-quest-3d/src/village.scene.json +449 -245
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { n as jsonEquals, t as jsonClone } from "./json-CfTjpvW8.js";
|
|
2
|
-
import {
|
|
3
|
-
import { v as Engine } from "./register-
|
|
2
|
+
import { B as HudLayer, M as resetMemorySaves, O as resolveViewport, V as HudWidgetBase, c as isCloned, h as loadScene, ot as registerBehavior, tt as StubBehavior } from "./save-slots-CEuJPUle.js";
|
|
3
|
+
import { v as Engine } from "./register-5IYfooAN.js";
|
|
4
4
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
5
5
|
import { t as Rng } from "./rng-CDOMybym.js";
|
|
6
6
|
import { c as getNodeSchema, d as mergeStaticProps, u as getNodeType } from "./touch-BnCyPA0G.js";
|
|
7
|
-
import { n as startRecording } from "./replay-
|
|
8
|
-
import {
|
|
9
|
-
import { t as registerNodes2D } from "./register-
|
|
10
|
-
import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-
|
|
11
|
-
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-
|
|
7
|
+
import { n as startRecording } from "./replay-datiAE-b.js";
|
|
8
|
+
import { Q as worldPosition, n as registerGameplayBehaviors } from "./gameplay-BlQe-M07.js";
|
|
9
|
+
import { t as registerNodes2D } from "./register-C8HuRkOf.js";
|
|
10
|
+
import { n as registerNodes3D, t as resolveEnvironmentHdri } from "./environment-presets-BeHoaGhy.js";
|
|
11
|
+
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-Bm5DTElG.js";
|
|
12
12
|
import { Box3, Euler, Frustum, Matrix4, PerspectiveCamera, Quaternion, Ray, Vector3 } from "three";
|
|
13
13
|
//#region src/core/asset-urls.ts
|
|
14
14
|
/** Keys inside a `material` object that hold a texture URL. */
|
|
@@ -1102,6 +1102,18 @@ const REACH_2D_PX = 32;
|
|
|
1102
1102
|
* a platformer that never wrote a physics header still falls, and reading
|
|
1103
1103
|
* absent as zero would have stopped reporting the pit it fell into.
|
|
1104
1104
|
*/
|
|
1105
|
+
/** Is anything in this tree driving the player without a down? */
|
|
1106
|
+
function hasTopDownController(root) {
|
|
1107
|
+
const stack = [root];
|
|
1108
|
+
while (stack.length > 0) {
|
|
1109
|
+
const node = stack.pop();
|
|
1110
|
+
if (node.constructor.typeName === "CharacterController2D") {
|
|
1111
|
+
if (node.mode === "topDown") return true;
|
|
1112
|
+
}
|
|
1113
|
+
for (const child of node.children) stack.push(child);
|
|
1114
|
+
}
|
|
1115
|
+
return false;
|
|
1116
|
+
}
|
|
1105
1117
|
function gravityMagnitude(physics) {
|
|
1106
1118
|
const g = physics?.gravity;
|
|
1107
1119
|
if (g === void 0) return 1;
|
|
@@ -1268,9 +1280,19 @@ var Driver = class {
|
|
|
1268
1280
|
* way a player would: the world stopped moving.
|
|
1269
1281
|
*/
|
|
1270
1282
|
frozeTheGame = /* @__PURE__ */ new Set();
|
|
1283
|
+
/**
|
|
1284
|
+
* Buttons an EARLIER run proved reload the scene, so this one never tries.
|
|
1285
|
+
*
|
|
1286
|
+
* Unlike a pause, a restart cannot be recovered from mid-run — the tree the
|
|
1287
|
+
* driver holds is gone — so the blame has to outlive the run that paid for
|
|
1288
|
+
* it. One run of the ten is spent learning; the other nine play the game.
|
|
1289
|
+
*/
|
|
1290
|
+
avoid = /* @__PURE__ */ new Set();
|
|
1271
1291
|
/** What was pressed most recently, so a freeze can be attributed to it. */
|
|
1272
1292
|
lastPressed = null;
|
|
1273
1293
|
moveAction;
|
|
1294
|
+
/** The button the scene's controller calls its jump — `jump` unless it says otherwise. */
|
|
1295
|
+
jumpButton = "jump";
|
|
1274
1296
|
/**
|
|
1275
1297
|
* The action vocabulary this driver was bound to, so it can notice a swap.
|
|
1276
1298
|
*
|
|
@@ -1384,6 +1406,18 @@ var Driver = class {
|
|
|
1384
1406
|
engine.toScreen ??= view.toScreen;
|
|
1385
1407
|
}
|
|
1386
1408
|
}
|
|
1409
|
+
if (root) {
|
|
1410
|
+
const stack = [root];
|
|
1411
|
+
while (stack.length > 0) {
|
|
1412
|
+
const node = stack.pop();
|
|
1413
|
+
if (CONTROLLERS.has(node.constructor.typeName ?? "")) {
|
|
1414
|
+
const want = node.jumpAction;
|
|
1415
|
+
if (typeof want === "string" && want !== "") this.jumpButton = want;
|
|
1416
|
+
break;
|
|
1417
|
+
}
|
|
1418
|
+
for (const child of node.children) stack.push(child);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1387
1421
|
this.readsMouse = asks;
|
|
1388
1422
|
const design = engine.scene?.viewport?.design;
|
|
1389
1423
|
this.view = [design?.[0] ?? 960, design?.[1] ?? 540];
|
|
@@ -1434,6 +1468,14 @@ var Driver = class {
|
|
|
1434
1468
|
paused() {
|
|
1435
1469
|
return [...this.frozeTheGame];
|
|
1436
1470
|
}
|
|
1471
|
+
/** Never press these — an earlier run watched them reload the scene. */
|
|
1472
|
+
avoidPressing(buttons) {
|
|
1473
|
+
this.avoid = buttons;
|
|
1474
|
+
}
|
|
1475
|
+
/** Whatever was pressed most recently, so a reload can be attributed to it. */
|
|
1476
|
+
blame() {
|
|
1477
|
+
return this.lastPressed;
|
|
1478
|
+
}
|
|
1437
1479
|
step(dt, at) {
|
|
1438
1480
|
this.rebind();
|
|
1439
1481
|
if (this.engine.timeScale === 0) {
|
|
@@ -1459,7 +1501,7 @@ var Driver = class {
|
|
|
1459
1501
|
}
|
|
1460
1502
|
if (this.moveAction) this.engine.input.setActionVector(this.moveAction, Math.cos(this.heading), Math.sin(this.heading));
|
|
1461
1503
|
for (const button of this.buttons) {
|
|
1462
|
-
if (this.frozeTheGame.has(button)) continue;
|
|
1504
|
+
if (this.frozeTheGame.has(button) || this.avoid.has(button)) continue;
|
|
1463
1505
|
const left = (this.held.get(button) ?? 0) - dt;
|
|
1464
1506
|
if (left > 0) {
|
|
1465
1507
|
this.held.set(button, left);
|
|
@@ -1470,7 +1512,7 @@ var Driver = class {
|
|
|
1470
1512
|
this.held.set(button, -1);
|
|
1471
1513
|
continue;
|
|
1472
1514
|
}
|
|
1473
|
-
const chance = button ===
|
|
1515
|
+
const chance = button === this.jumpButton ? .06 : .02;
|
|
1474
1516
|
if (this.rand() < chance) {
|
|
1475
1517
|
this.engine.input.pressAction(button);
|
|
1476
1518
|
this.actionPresses += 1;
|
|
@@ -1514,7 +1556,7 @@ var Driver = class {
|
|
|
1514
1556
|
if (widgets.length === 0) return;
|
|
1515
1557
|
const target = widgets[Math.floor(this.rand() * widgets.length)];
|
|
1516
1558
|
if (!target) return;
|
|
1517
|
-
target._activate(0);
|
|
1559
|
+
target._activate(this.rand() < .5 ? 0 : this.rand() < .5 ? -1 : 1);
|
|
1518
1560
|
this.presses += 1;
|
|
1519
1561
|
}
|
|
1520
1562
|
/** Every widget in the tree that can be picked up, or dropped onto. */
|
|
@@ -1678,7 +1720,7 @@ var Driver = class {
|
|
|
1678
1720
|
if (this.moveAction) this.engine.input.setActionVector(this.moveAction, 0, 0);
|
|
1679
1721
|
}
|
|
1680
1722
|
};
|
|
1681
|
-
async function runOnce(json, seed, opts) {
|
|
1723
|
+
async function runOnce(json, seed, opts, avoid = /* @__PURE__ */ new Set()) {
|
|
1682
1724
|
const session = await createPlaySession(json, {
|
|
1683
1725
|
seed,
|
|
1684
1726
|
...opts.behaviors ? { behaviors: opts.behaviors } : {},
|
|
@@ -1745,26 +1787,47 @@ async function runOnce(json, seed, opts) {
|
|
|
1745
1787
|
];
|
|
1746
1788
|
const down = scene.dimension === "2d" ? 1 : -1;
|
|
1747
1789
|
const pull = gravityMagnitude(scene.physics);
|
|
1748
|
-
const
|
|
1790
|
+
const topDown = hasTopDownController(scene.root);
|
|
1791
|
+
const floor = opts.fallBelow ?? (pull > 0 && !topDown ? start[1] + down * (down > 0 ? FALL_2D_PX : 50) : null);
|
|
1749
1792
|
const radius = opts.reachRadius ?? (down > 0 ? REACH_2D_PX : 2);
|
|
1750
1793
|
const recorder = startRecording(engine);
|
|
1751
1794
|
const botRng = new Rng((seed ^ 2654435769) >>> 0);
|
|
1752
1795
|
const rand = () => botRng.next();
|
|
1753
1796
|
const driver = new Driver(engine, rand);
|
|
1797
|
+
driver.avoidPressing(avoid);
|
|
1754
1798
|
const reached = /* @__PURE__ */ new Set();
|
|
1755
1799
|
const stepMs = 1e3 / 60;
|
|
1756
1800
|
const deadline = (opts.seconds ?? 60) * 1e3;
|
|
1757
1801
|
let outcome = null;
|
|
1758
1802
|
let swappedAtMs = null;
|
|
1803
|
+
let restartedBy = null;
|
|
1759
1804
|
const heardSoFar = /* @__PURE__ */ new Set();
|
|
1760
1805
|
const shownSoFar = /* @__PURE__ */ new Set();
|
|
1761
|
-
const
|
|
1806
|
+
const playing = scene.source.name;
|
|
1807
|
+
const offSwap = engine.sceneChanged.connect((next) => {
|
|
1762
1808
|
swappedAtMs ??= session.timeMs;
|
|
1809
|
+
if (next && next.source.name === playing) restartedBy ??= driver.blame();
|
|
1763
1810
|
});
|
|
1764
1811
|
let at = start;
|
|
1765
1812
|
let wentAsFarAs = 0;
|
|
1766
1813
|
const travelled = (a, b) => scene.dimension === "2d" ? Math.hypot((a[0] ?? 0) - (b[0] ?? 0), (a[1] ?? 0) - (b[1] ?? 0)) : Math.hypot((a[0] ?? 0) - (b[0] ?? 0), (a[2] ?? 0) - (b[2] ?? 0));
|
|
1814
|
+
const cloneCensus = () => {
|
|
1815
|
+
const out = /* @__PURE__ */ new Map();
|
|
1816
|
+
const walk = (node) => {
|
|
1817
|
+
if (isCloned(node)) {
|
|
1818
|
+
const at2 = node.wiringPath();
|
|
1819
|
+
out.set(at2, (out.get(at2) ?? 0) + 1);
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
for (const child of node.children) walk(child);
|
|
1823
|
+
};
|
|
1824
|
+
const root = engine.scene?.root;
|
|
1825
|
+
if (root) walk(root);
|
|
1826
|
+
return out;
|
|
1827
|
+
};
|
|
1828
|
+
let halfway = null;
|
|
1767
1829
|
while (session.timeMs < deadline) {
|
|
1830
|
+
if (halfway === null && session.timeMs >= deadline / 2) halfway = cloneCensus();
|
|
1768
1831
|
at = player ? worldOf(player) : start;
|
|
1769
1832
|
wentAsFarAs = Math.max(wentAsFarAs, travelled(at, start));
|
|
1770
1833
|
driver.step(stepMs / 1e3, at);
|
|
@@ -1796,6 +1859,11 @@ async function runOnce(json, seed, opts) {
|
|
|
1796
1859
|
}
|
|
1797
1860
|
driver.release();
|
|
1798
1861
|
offSwap();
|
|
1862
|
+
const aliveClones = [...cloneCensus()].map(([path, count]) => ({
|
|
1863
|
+
path,
|
|
1864
|
+
count,
|
|
1865
|
+
half: halfway?.get(path) ?? 0
|
|
1866
|
+
})).sort((a, b) => b.count - a.count);
|
|
1799
1867
|
const WENT_NOWHERE = scene.dimension === "2d" ? STUCK_2D_PX : STUCK_3D_M;
|
|
1800
1868
|
outcome ??= wentAsFarAs > WENT_NOWHERE || driver.clicksLanded > 0 || driver.dragsMade > 0 || driver.widgetsPressed > 0 || driver.slotsDragged > 0 ? "unfinished" : "stuck";
|
|
1801
1869
|
for (const path of engine.audio.sources()) heardSoFar.add(path);
|
|
@@ -1812,6 +1880,7 @@ async function runOnce(json, seed, opts) {
|
|
|
1812
1880
|
damageTaken: oracle.damage(),
|
|
1813
1881
|
errors: engine.stats().errors,
|
|
1814
1882
|
errorMessages: logLines(engine, "error"),
|
|
1883
|
+
aliveClones,
|
|
1815
1884
|
warnings: logLines(engine, "warn"),
|
|
1816
1885
|
replay: bad ? recorder.stop() : null,
|
|
1817
1886
|
endedAt: at,
|
|
@@ -1836,6 +1905,7 @@ async function runOnce(json, seed, opts) {
|
|
|
1836
1905
|
hazards,
|
|
1837
1906
|
hasPlayer: player !== null,
|
|
1838
1907
|
froze: driver.paused(),
|
|
1908
|
+
restartedBy,
|
|
1839
1909
|
unread: unreadNow
|
|
1840
1910
|
};
|
|
1841
1911
|
}
|
|
@@ -1867,6 +1937,7 @@ async function playtest(json, opts = {}) {
|
|
|
1867
1937
|
let hasPlayer = false;
|
|
1868
1938
|
let hazards = 0;
|
|
1869
1939
|
const froze = /* @__PURE__ */ new Set();
|
|
1940
|
+
const restarted = /* @__PURE__ */ new Set();
|
|
1870
1941
|
/**
|
|
1871
1942
|
* An action is inert only if NO run read it — one run that took a branch the
|
|
1872
1943
|
* others missed is enough to clear it.
|
|
@@ -1874,7 +1945,7 @@ async function playtest(json, opts = {}) {
|
|
|
1874
1945
|
let unread = null;
|
|
1875
1946
|
for (let i = 0; i < count; i++) {
|
|
1876
1947
|
resetMemorySaves();
|
|
1877
|
-
const one = await runOnce(json, base + i, opts);
|
|
1948
|
+
const one = await runOnce(json, base + i, opts, restarted);
|
|
1878
1949
|
runs.push(one.run);
|
|
1879
1950
|
targets = one.targets;
|
|
1880
1951
|
signals = one.signals;
|
|
@@ -1882,6 +1953,7 @@ async function playtest(json, opts = {}) {
|
|
|
1882
1953
|
hasPlayer = hasPlayer || one.hasPlayer;
|
|
1883
1954
|
hazards = one.hazards;
|
|
1884
1955
|
for (const action of one.froze) froze.add(action);
|
|
1956
|
+
if (one.restartedBy) restarted.add(one.restartedBy);
|
|
1885
1957
|
unread = unread === null ? one.unread : unread.filter((a) => one.unread.includes(a));
|
|
1886
1958
|
}
|
|
1887
1959
|
const probe = await createPlaySession(json, {
|
|
@@ -1930,6 +2002,7 @@ async function playtest(json, opts = {}) {
|
|
|
1930
2002
|
hasPlayer,
|
|
1931
2003
|
hazards,
|
|
1932
2004
|
froze: [...froze],
|
|
2005
|
+
restarted: [...restarted],
|
|
1933
2006
|
seconds: opts.seconds ?? 60,
|
|
1934
2007
|
feedback: {
|
|
1935
2008
|
declaredAudio: feedbackNodes.audio,
|
|
@@ -1997,6 +2070,33 @@ function playtestText(report) {
|
|
|
1997
2070
|
for (const line of said.slice(1, 3)) lines.push(` ! ${line}`);
|
|
1998
2071
|
if (said.length > 3) lines.push(` ! … and ${said.length - 3} more`);
|
|
1999
2072
|
};
|
|
2073
|
+
/**
|
|
2074
|
+
* What the runs were still holding when they ended, by template.
|
|
2075
|
+
*
|
|
2076
|
+
* The MEDIAN across runs, because one run that happened to end mid-wave says
|
|
2077
|
+
* less than what every run ends with. Reported, never judged: `4 × Tower` is
|
|
2078
|
+
* furniture and `5 × Bolt` is a leak, and the engine cannot tell which of an
|
|
2079
|
+
* author's clones are meant to outlive the run — but the author can, in one
|
|
2080
|
+
* glance, which is exactly what was missing when a shipped game kept every
|
|
2081
|
+
* projectile that missed alive forever.
|
|
2082
|
+
*/
|
|
2083
|
+
const pushAlive = () => {
|
|
2084
|
+
const worst = /* @__PURE__ */ new Map();
|
|
2085
|
+
for (const run of runs) for (const { path, count, half } of run.aliveClones ?? []) {
|
|
2086
|
+
const at = worst.get(path);
|
|
2087
|
+
if (!at || count > at.end) worst.set(path, {
|
|
2088
|
+
end: count,
|
|
2089
|
+
half
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
const named = [...worst].filter(([, x]) => x.end >= 4).map(([path, x]) => ({
|
|
2093
|
+
path,
|
|
2094
|
+
...x
|
|
2095
|
+
})).sort((a, b) => b.end - a.end);
|
|
2096
|
+
if (named.length === 0) return;
|
|
2097
|
+
const shown = named.slice(0, 3).map((x) => `${x.end} × ${x.path} (${x.half} at halfway)`);
|
|
2098
|
+
lines.push(` · made and still held: ${shown.join(", ")}` + (named.length > 3 ? ` (+${named.length - 3} more)` : "") + " — clones alive when the run ended, and how many there were at the halfway mark. Furniture holds steady; a leak climbs.");
|
|
2099
|
+
};
|
|
2000
2100
|
const pushOutcomes = () => {
|
|
2001
2101
|
for (const bad of [
|
|
2002
2102
|
"error",
|
|
@@ -2041,6 +2141,8 @@ function playtestText(report) {
|
|
|
2041
2141
|
pushOutcomes();
|
|
2042
2142
|
pushWarnings();
|
|
2043
2143
|
if (report.froze.length > 0) lines.push(` · the bot stopped pressing ${report.froze.map((x) => `\`${x}\``).join(", ")} — pressing it stopped the clock, and a run spent inside a pause menu measures nothing. Your game is fine; this is the bot getting out of its own way.`);
|
|
2144
|
+
if (report.restarted.length > 0) lines.push(` · the bot stopped pressing ${report.restarted.map((x) => `\`${x}\``).join(", ")} — pressing it reloaded the scene, which ends the run: the bot is bound to the tree it started in. One run paid for that; the rest played the game.`);
|
|
2145
|
+
pushAlive();
|
|
2044
2146
|
return lines.join("\n");
|
|
2045
2147
|
}
|
|
2046
2148
|
pushOutcomes();
|
|
@@ -2069,6 +2171,11 @@ function playtestText(report) {
|
|
|
2069
2171
|
lines.push("");
|
|
2070
2172
|
lines.push(` · the bot stopped pressing ${report.froze.map((a) => `\`${a}\``).join(", ")} — pressing it stopped the clock, and a run spent inside a pause menu measures nothing. Your game is fine; this is the bot getting out of its own way.`);
|
|
2071
2173
|
}
|
|
2174
|
+
if (report.restarted.length > 0) {
|
|
2175
|
+
lines.push("");
|
|
2176
|
+
lines.push(` · the bot stopped pressing ${report.restarted.map((a) => `\`${a}\``).join(", ")} — pressing it reloaded the scene, which ends the run: the bot is bound to the tree it started in. One run paid for that; the rest played the game.`);
|
|
2177
|
+
}
|
|
2178
|
+
pushAlive();
|
|
2072
2179
|
if (report.inertActions.length > 0) {
|
|
2073
2180
|
lines.push("");
|
|
2074
2181
|
lines.push(` · declared and never read: ${report.inertActions.map((a) => `\`${a}\``).join(", ")} — the scene binds a key for it and no code asks whether it is pressed, so the control list promises something the game does not do. Read it, or drop it from \`input\`. (An action only polled on a screen no run reached is a false alarm.)`);
|
|
@@ -2646,8 +2753,12 @@ async function makeProbe(json, opts) {
|
|
|
2646
2753
|
const player = findPlayer(session.scene.root);
|
|
2647
2754
|
const declared = session.engine.input.declaredActions();
|
|
2648
2755
|
const moveOptions = declared.filter((a) => a.type === "vector2").map((a) => a.name);
|
|
2649
|
-
const
|
|
2650
|
-
|
|
2756
|
+
const named = (prop, type) => {
|
|
2757
|
+
const want = controllerProp(session.scene.root, prop);
|
|
2758
|
+
return want && declared.some((a) => a.name === want && a.type === type) ? want : null;
|
|
2759
|
+
};
|
|
2760
|
+
const move = opts.moveAction ?? named("moveAction", "vector2") ?? moveOptions[0] ?? null;
|
|
2761
|
+
const jump = opts.jumpAction ?? named("jumpAction", "button") ?? (declared.some((a) => a.name === "jump" && a.type === "button") ? "jump" : null);
|
|
2651
2762
|
if (!player) {
|
|
2652
2763
|
session.dispose();
|
|
2653
2764
|
return {
|
|
@@ -3181,6 +3292,20 @@ function feelText(report) {
|
|
|
3181
3292
|
}
|
|
3182
3293
|
return lines.join("\n");
|
|
3183
3294
|
}
|
|
3295
|
+
/** The action name a `CharacterController2D/3D` in this tree says it reads. */
|
|
3296
|
+
function controllerProp(root, prop) {
|
|
3297
|
+
const stack = [root];
|
|
3298
|
+
while (stack.length > 0) {
|
|
3299
|
+
const node = stack.pop();
|
|
3300
|
+
const type = node.constructor.typeName ?? "";
|
|
3301
|
+
if (type === "CharacterController2D" || type === "CharacterController3D") {
|
|
3302
|
+
const value = node[prop];
|
|
3303
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
3304
|
+
}
|
|
3305
|
+
for (const child of node.children) stack.push(child);
|
|
3306
|
+
}
|
|
3307
|
+
return null;
|
|
3308
|
+
}
|
|
3184
3309
|
//#endregion
|
|
3185
3310
|
//#region src/test/multiplay.ts
|
|
3186
3311
|
/** Run a whole match headlessly and report what the clients ended up sharing. */
|
|
@@ -3264,8 +3389,8 @@ async function playMultiplayer(opts) {
|
|
|
3264
3389
|
*/
|
|
3265
3390
|
async function joinLate(server, first, opts) {
|
|
3266
3391
|
try {
|
|
3267
|
-
const { Engine } = await import("./register-
|
|
3268
|
-
const { loadScene } = await import("./save-slots-
|
|
3392
|
+
const { Engine } = await import("./register-5IYfooAN.js").then((n) => n.y);
|
|
3393
|
+
const { loadScene } = await import("./save-slots-CEuJPUle.js").then((n) => n.g);
|
|
3269
3394
|
const { jsonClone } = await import("./json-CfTjpvW8.js").then((n) => n.i);
|
|
3270
3395
|
const engine = new Engine(opts.seed !== void 0 ? { seed: opts.seed + 999 } : {});
|
|
3271
3396
|
engine.setScene(loadScene(jsonClone(opts.scene), opts.load));
|
|
@@ -3466,6 +3591,106 @@ function saysRung(report) {
|
|
|
3466
3591
|
...warnings.length > 3 ? { fix: "the rest are in `incanto-logs`" } : {}
|
|
3467
3592
|
};
|
|
3468
3593
|
}
|
|
3594
|
+
/**
|
|
3595
|
+
* What to say when the bot could not WALK — which is not the same as could not
|
|
3596
|
+
* play.
|
|
3597
|
+
*
|
|
3598
|
+
* This branch was "nobody to move", said about games the bot had played for
|
|
3599
|
+
* eight runs. The MOUSE half was fixed when a flip puzzle whose every tile the
|
|
3600
|
+
* bot clicked was told to add a character controller; the KEYBOARD half was
|
|
3601
|
+
* found the same way, one genre along: a sokoban declares `move`, `undo` and
|
|
3602
|
+
* `restart`, a behaviour reads them every frame and moves plain nodes, and the
|
|
3603
|
+
* bot pressed them 989 times across eight runs — while the rung said
|
|
3604
|
+
*
|
|
3605
|
+
* ? plays — nothing here is drivable — 8 runs had nobody to move and
|
|
3606
|
+
* nothing to click
|
|
3607
|
+
*
|
|
3608
|
+
* Both halves of that sentence were false. A whole class — sokoban, snake,
|
|
3609
|
+
* tetris, roguelikes, typing games, anything on a grid — has no character to
|
|
3610
|
+
* add, and the advice was to add one anyway.
|
|
3611
|
+
*
|
|
3612
|
+
* PRESSED and READ, not merely declared: `inertActions` is the report's own
|
|
3613
|
+
* list of actions nothing asks about, and a scene where every one of them is
|
|
3614
|
+
* inert really does have nothing driving it.
|
|
3615
|
+
*
|
|
3616
|
+
* Pure, so the wording can be tested without running a game.
|
|
3617
|
+
*/
|
|
3618
|
+
function unmeasuredPlays(report) {
|
|
3619
|
+
const runs = report.runs ?? [];
|
|
3620
|
+
const total = runs.length;
|
|
3621
|
+
const clicks = runs.reduce((n, r) => n + (r.clicks ?? 0), 0);
|
|
3622
|
+
const keyed = runs.reduce((n, r) => n + (r.actionPresses ?? 0), 0);
|
|
3623
|
+
const live = (report.actions ?? []).filter((a) => !(report.inertActions ?? []).includes(a));
|
|
3624
|
+
const ending = report.declaresWin ? "and no run reached the win" : "and no win to reach";
|
|
3625
|
+
if (clicks > 0) return {
|
|
3626
|
+
summary: `played with the MOUSE — ${clicks} click(s) across ${total} runs, ${ending}`,
|
|
3627
|
+
fix: "a mouse game is played, not walked — emit `won`/`lost` and this rung can pass"
|
|
3628
|
+
};
|
|
3629
|
+
if (keyed > 0 && live.length > 0) return {
|
|
3630
|
+
summary: `played with the KEYBOARD — ${keyed} press(es) of ${live.length} declared action(s) (${live.slice(0, 4).join(", ")}) across ${total} runs, ${ending}`,
|
|
3631
|
+
fix: "a game played by KEYS is played, not walked — emit `won`/`lost` and this rung can pass. (A random presser will not solve a puzzle: drive the solution in a scripted `runScript` harness, and let this rung tell you about crashes.)"
|
|
3632
|
+
};
|
|
3633
|
+
if (report.hasPlayer === false) return {
|
|
3634
|
+
summary: `nothing here is drivable — ${total} runs had nobody to move and nothing to click`,
|
|
3635
|
+
fix: "give the player a character controller, the `player` group, or the name Player — or `Clickable` on what the cursor drives"
|
|
3636
|
+
};
|
|
3637
|
+
return {
|
|
3638
|
+
summary: `${total} runs went nowhere, and your behaviours were not loaded — that is most of what moves a character`,
|
|
3639
|
+
fix: `name them: \`incanto-verify ${report.scene ?? "src/game.scene.json"} --behaviors src/behaviors.ts\``
|
|
3640
|
+
};
|
|
3641
|
+
}
|
|
3642
|
+
/**
|
|
3643
|
+
* What the run HEARD and SHOWED, as one rung.
|
|
3644
|
+
*
|
|
3645
|
+
* Pure, like `saysRung` and `unmeasuredPlays`: the bin owns running the
|
|
3646
|
+
* playtest, this owns what its feedback log means. Extracted when the
|
|
3647
|
+
* all-silent branch was found printing a COUNT with nothing beside it —
|
|
3648
|
+
* `racing-3d` reports 20 emitters and its scene declares none of them (they are
|
|
3649
|
+
* a pool of `new Particles3D()` built in `onReady`), so an author counted
|
|
3650
|
+
* twenty in a message and found zero in their JSON.
|
|
3651
|
+
*/
|
|
3652
|
+
function feelsRung(fb, opts = {}) {
|
|
3653
|
+
if (!fb) return {
|
|
3654
|
+
name: "feels",
|
|
3655
|
+
status: "skipped",
|
|
3656
|
+
summary: "not run — the playtest did not report"
|
|
3657
|
+
};
|
|
3658
|
+
const declaredPaths = [...fb.declaredAudio ?? [], ...fb.declaredEffects ?? []];
|
|
3659
|
+
const declared = declaredPaths.length;
|
|
3660
|
+
const fired = new Set([...fb.heard ?? [], ...fb.shown ?? []]);
|
|
3661
|
+
if (declared === 0) return {
|
|
3662
|
+
name: "feels",
|
|
3663
|
+
status: "skipped",
|
|
3664
|
+
summary: "this game declares no sound and no effects"
|
|
3665
|
+
};
|
|
3666
|
+
const some = (list) => `${list.slice(0, 3).join(", ")}${list.length > 3 ? ` +${list.length - 3}` : ""}`;
|
|
3667
|
+
if (fired.size === 0) {
|
|
3668
|
+
if (opts.played === false) return {
|
|
3669
|
+
name: "feels",
|
|
3670
|
+
status: "unmeasured",
|
|
3671
|
+
summary: `${declared} sound/effect node(s) in the tree and none fired — but no run got the game moving, so nothing here was measured — ${some(declaredPaths)}`,
|
|
3672
|
+
fix: "drive it in a scripted run (`runScript` from `incanto/test`): a sustained throttle, a real fight, the actual verb. A random bot that never gets going triggers nothing, and that is a fact about the bot."
|
|
3673
|
+
};
|
|
3674
|
+
return {
|
|
3675
|
+
name: "feels",
|
|
3676
|
+
status: "fail",
|
|
3677
|
+
summary: `${declared} sound/effect node(s) in the tree, and not one fired in any run — ${some(declaredPaths)}`,
|
|
3678
|
+
fix: "connect them — a signal to `play` on an AudioPlayer, or `replay()` on a one-shot; `engine.audio.recent()` and `engine.effects.recent()` say what did fire"
|
|
3679
|
+
};
|
|
3680
|
+
}
|
|
3681
|
+
const silent = declaredPaths.filter((path) => !fired.has(path));
|
|
3682
|
+
if (silent.length === 0) return {
|
|
3683
|
+
name: "feels",
|
|
3684
|
+
status: "pass",
|
|
3685
|
+
summary: `every one of the ${declared} sound/effect nodes fired`
|
|
3686
|
+
};
|
|
3687
|
+
return {
|
|
3688
|
+
name: "feels",
|
|
3689
|
+
status: "unmeasured",
|
|
3690
|
+
summary: `${declared - silent.length} of ${declared} fired — silent: ${some(silent)}`,
|
|
3691
|
+
fix: "a silent emitter is either a broken wire or a moment random play never reached — the two look identical from here. Reach it in a scripted run (`runScript` from `incanto/test`), and this rung can tell you which."
|
|
3692
|
+
};
|
|
3693
|
+
}
|
|
3469
3694
|
function ladderVerdict(rungs, ctx = {}) {
|
|
3470
3695
|
const failed = rungs.find((r) => r.status === "fail");
|
|
3471
3696
|
const unmeasured = rungs.filter((r) => r.status === "unmeasured");
|
|
@@ -3556,7 +3781,10 @@ function findFloatingProps(json, opts = {}) {
|
|
|
3556
3781
|
const types = new Set(opts.types ?? GROUNDED_TYPES);
|
|
3557
3782
|
let scene;
|
|
3558
3783
|
try {
|
|
3559
|
-
scene = loadScene(json, {
|
|
3784
|
+
scene = loadScene(json, {
|
|
3785
|
+
stubMissingBehaviors: true,
|
|
3786
|
+
engine: new Engine({ scheduler: () => () => {} })
|
|
3787
|
+
});
|
|
3560
3788
|
} catch (error) {
|
|
3561
3789
|
throw new IncantoError("BAD_FORMAT", `findFloatingProps: the scene does not load, so nothing could be measured. ${error?.message ?? String(error)}`);
|
|
3562
3790
|
}
|
|
@@ -3760,10 +3988,10 @@ async function runScript(json, opts) {
|
|
|
3760
3988
|
if (opts.locale) engine.locale.locale = opts.locale;
|
|
3761
3989
|
const physics = opts.physics ?? "auto";
|
|
3762
3990
|
if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
|
|
3763
|
-
const { enablePhysics2D } = await import("./physics-2d-
|
|
3991
|
+
const { enablePhysics2D } = await import("./physics-2d-DrIgTlLk.js").then((n) => n.r);
|
|
3764
3992
|
await enablePhysics2D(engine);
|
|
3765
3993
|
} else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
|
|
3766
|
-
const { enablePhysics3D } = await import("./physics-3d-
|
|
3994
|
+
const { enablePhysics3D } = await import("./physics-3d-BzOCw1VS.js").then((n) => n.r);
|
|
3767
3995
|
await enablePhysics3D(engine);
|
|
3768
3996
|
}
|
|
3769
3997
|
const failures = [];
|
|
@@ -3990,10 +4218,10 @@ async function createPlaySession(json, opts = {}) {
|
|
|
3990
4218
|
engine.setScene(scene);
|
|
3991
4219
|
const physics = opts.physics ?? "auto";
|
|
3992
4220
|
if (physics === "2d" || physics === "auto" && scene.dimension === "2d") {
|
|
3993
|
-
const { enablePhysics2D } = await import("./physics-2d-
|
|
4221
|
+
const { enablePhysics2D } = await import("./physics-2d-DrIgTlLk.js").then((n) => n.r);
|
|
3994
4222
|
await enablePhysics2D(engine);
|
|
3995
4223
|
} else if (physics === "3d" || physics === "auto" && scene.dimension === "3d") {
|
|
3996
|
-
const { enablePhysics3D } = await import("./physics-3d-
|
|
4224
|
+
const { enablePhysics3D } = await import("./physics-3d-BzOCw1VS.js").then((n) => n.r);
|
|
3997
4225
|
await enablePhysics3D(engine);
|
|
3998
4226
|
}
|
|
3999
4227
|
const stepMs = 1e3 / (opts.fixedHz ?? 60);
|
|
@@ -4033,4 +4261,4 @@ async function createPlaySession(json, opts = {}) {
|
|
|
4033
4261
|
};
|
|
4034
4262
|
}
|
|
4035
4263
|
//#endregion
|
|
4036
|
-
export {
|
|
4264
|
+
export { geometricPicker as A, facingReport as C, playtest as D, findPlayer as E, sceneAssetUrls as F, pointAtNode as M, describeFraming as N, playtestFailed as O, framingText as P, collectionProblems as S, failingReplays as T, feelReport as _, registerAllNodes as a, takeCollectionSnapshots as b, feelsRung as c, ladderVerdict as d, saysRung as f, playMultiplayer as g, multiplayText as h, findFloatingProps as i, geometricView as j, playtestText as k, firstLine as l, multiplayProblems as m, createPlaySession as n, runScript as o, unmeasuredPlays as p, describeCapture as r, validateScene as s, captureScene as t, ladderText as u, feelText as v, facingText as w, agreementProblems as x, takeAgreementSnapshots as y };
|
package/dist/test.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { P as Scene$1, Pt as LogEntry, b as Engine, jt as Node, n as BehaviorCtor } from "./behavior-
|
|
1
|
+
import { P as Scene$1, Pt as LogEntry, b as Engine, jt as Node, n as BehaviorCtor } from "./behavior-Bf9P9oB2.js";
|
|
2
2
|
import { a as SceneJson, c as JsonValue, s as JsonObject } from "./rng-Bb-IutXB.js";
|
|
3
|
-
import { t as LoadSceneOptions } from "./loader-
|
|
4
|
-
import { l as auditScene, o as IncantoError, r as ReplayJson, u as instancedScenes } from "./replay-
|
|
5
|
-
import { l as LocalGameServerOptions, n as SplitScreenPlayer } from "./split-screen
|
|
3
|
+
import { t as LoadSceneOptions } from "./loader-CBfQzB6S.js";
|
|
4
|
+
import { l as auditScene, o as IncantoError, r as ReplayJson, u as instancedScenes } from "./replay-CBEChqXq.js";
|
|
5
|
+
import { l as LocalGameServerOptions, n as SplitScreenPlayer } from "./split-screen-DkqLng6q.js";
|
|
6
6
|
import { Matrix4, Vector3 } from "three";
|
|
7
7
|
|
|
8
8
|
//#region src/core/asset-urls.d.ts
|
|
@@ -639,6 +639,24 @@ interface PlaytestRun {
|
|
|
639
639
|
* said `✗ error in 2/2` and nothing an author could act on.
|
|
640
640
|
*/
|
|
641
641
|
errorMessages: string[];
|
|
642
|
+
/**
|
|
643
|
+
* What this run MADE and was still holding when it ended, by template.
|
|
644
|
+
*
|
|
645
|
+
* A shipped tower defense kept every projectile that MISSED alive forever —
|
|
646
|
+
* its `Lifetime` had been left asleep on the prefab shelf — and no instrument
|
|
647
|
+
* said so: the playtest reported the game as fine, the audit passed, the
|
|
648
|
+
* harness passed, every rung was green. Total node count is too blunt to be
|
|
649
|
+
* the answer, because a tower defense legitimately ENDS with four towers
|
|
650
|
+
* standing and a survivor with a swarm. Grouped by the AUTHORED template
|
|
651
|
+
* (`Node.wiringPath`), the two read differently at a glance: `4 × Tower` is
|
|
652
|
+
* furniture and `5 × Bolt` is a leak, and only the author can tell them
|
|
653
|
+
* apart — so this is reported and never judged.
|
|
654
|
+
*/
|
|
655
|
+
aliveClones: {
|
|
656
|
+
path: string;
|
|
657
|
+
count: number;
|
|
658
|
+
half: number;
|
|
659
|
+
}[];
|
|
642
660
|
/** Only for a run worth watching — replay it with `incanto-play`. */
|
|
643
661
|
replay: ReplayJson | null;
|
|
644
662
|
/** Where the player ended up, for the fall report. */
|
|
@@ -753,6 +771,23 @@ interface PlaytestReport {
|
|
|
753
771
|
*/
|
|
754
772
|
froze: string[];
|
|
755
773
|
/**
|
|
774
|
+
* Actions the bot stopped pressing because they STARTED THE LEVEL OVER.
|
|
775
|
+
*
|
|
776
|
+
* The sibling of `froze`, and it hid behind it: a run inside a pause menu
|
|
777
|
+
* measures nothing, and a run that reloads the scene measures nothing EITHER
|
|
778
|
+
* — except that one is over, because the bot is bound to the tree it began
|
|
779
|
+
* in. Measured on one losable scene, ten runs: `lost 10/10` without a
|
|
780
|
+
* mid-play restart, `lost 0/10 · swapped 10/10` with one, and the rung read
|
|
781
|
+
* `? 10 runs played without reaching a win (10 changed scene)`.
|
|
782
|
+
*
|
|
783
|
+
* Mid-run retry is ordinary — puzzle, racing, tower defense — and the
|
|
784
|
+
* engine's own `GameFlow` restarts only from a terminal state, so the games
|
|
785
|
+
* this ruined are the ones that wired `restartScene` themselves, exactly as
|
|
786
|
+
* the skills tell them to. Observed, never read off the word "restart": the
|
|
787
|
+
* scene being played was replaced by that same scene.
|
|
788
|
+
*/
|
|
789
|
+
restarted: string[];
|
|
790
|
+
/**
|
|
756
791
|
* Did `findPlayer` find anyone to drive?
|
|
757
792
|
*
|
|
758
793
|
* `findPlayer`'s own comment says a scene it cannot drive "says so rather
|
|
@@ -941,6 +976,79 @@ declare function saysRung(report: {
|
|
|
941
976
|
errors?: number;
|
|
942
977
|
};
|
|
943
978
|
} | null): RungResult;
|
|
979
|
+
/**
|
|
980
|
+
* What to say when the bot could not WALK — which is not the same as could not
|
|
981
|
+
* play.
|
|
982
|
+
*
|
|
983
|
+
* This branch was "nobody to move", said about games the bot had played for
|
|
984
|
+
* eight runs. The MOUSE half was fixed when a flip puzzle whose every tile the
|
|
985
|
+
* bot clicked was told to add a character controller; the KEYBOARD half was
|
|
986
|
+
* found the same way, one genre along: a sokoban declares `move`, `undo` and
|
|
987
|
+
* `restart`, a behaviour reads them every frame and moves plain nodes, and the
|
|
988
|
+
* bot pressed them 989 times across eight runs — while the rung said
|
|
989
|
+
*
|
|
990
|
+
* ? plays — nothing here is drivable — 8 runs had nobody to move and
|
|
991
|
+
* nothing to click
|
|
992
|
+
*
|
|
993
|
+
* Both halves of that sentence were false. A whole class — sokoban, snake,
|
|
994
|
+
* tetris, roguelikes, typing games, anything on a grid — has no character to
|
|
995
|
+
* add, and the advice was to add one anyway.
|
|
996
|
+
*
|
|
997
|
+
* PRESSED and READ, not merely declared: `inertActions` is the report's own
|
|
998
|
+
* list of actions nothing asks about, and a scene where every one of them is
|
|
999
|
+
* inert really does have nothing driving it.
|
|
1000
|
+
*
|
|
1001
|
+
* Pure, so the wording can be tested without running a game.
|
|
1002
|
+
*/
|
|
1003
|
+
declare function unmeasuredPlays(report: {
|
|
1004
|
+
runs?: readonly {
|
|
1005
|
+
clicks?: number;
|
|
1006
|
+
actionPresses?: number;
|
|
1007
|
+
}[];
|
|
1008
|
+
actions?: readonly string[];
|
|
1009
|
+
inertActions?: readonly string[];
|
|
1010
|
+
hasPlayer?: boolean;
|
|
1011
|
+
declaresWin?: boolean;
|
|
1012
|
+
behaviorsLoaded?: boolean;
|
|
1013
|
+
scene?: string;
|
|
1014
|
+
}): {
|
|
1015
|
+
summary: string;
|
|
1016
|
+
fix: string;
|
|
1017
|
+
};
|
|
1018
|
+
/**
|
|
1019
|
+
* What the run HEARD and SHOWED, as one rung.
|
|
1020
|
+
*
|
|
1021
|
+
* Pure, like `saysRung` and `unmeasuredPlays`: the bin owns running the
|
|
1022
|
+
* playtest, this owns what its feedback log means. Extracted when the
|
|
1023
|
+
* all-silent branch was found printing a COUNT with nothing beside it —
|
|
1024
|
+
* `racing-3d` reports 20 emitters and its scene declares none of them (they are
|
|
1025
|
+
* a pool of `new Particles3D()` built in `onReady`), so an author counted
|
|
1026
|
+
* twenty in a message and found zero in their JSON.
|
|
1027
|
+
*/
|
|
1028
|
+
declare function feelsRung(fb: {
|
|
1029
|
+
declaredAudio?: readonly string[];
|
|
1030
|
+
declaredEffects?: readonly string[];
|
|
1031
|
+
heard?: readonly string[];
|
|
1032
|
+
shown?: readonly string[];
|
|
1033
|
+
} | null | undefined, opts?: {
|
|
1034
|
+
/**
|
|
1035
|
+
* Did the bot get the game MOVING at all?
|
|
1036
|
+
*
|
|
1037
|
+
* Zero emitters firing is a broken wire only if something was played. On
|
|
1038
|
+
* `racing-3d` — 20 pooled `Particles3D`, a car that needs a SUSTAINED
|
|
1039
|
+
* throttle — a random presser never gets it rolling, every run comes back
|
|
1040
|
+
* `stuck`, and this rung printed `✗ 20 … and not one fired in any run`
|
|
1041
|
+
* about a game whose emitters all fire the moment anyone drives it
|
|
1042
|
+
* (measured: hold throttle + steer for 12s, all 20 fire).
|
|
1043
|
+
*
|
|
1044
|
+
* The partial case beside it has always said the two are indistinguishable
|
|
1045
|
+
* — "either a broken wire or a moment random play never reached" — and the
|
|
1046
|
+
* zero case has exactly the same ambiguity when nothing was driven. Same
|
|
1047
|
+
* flag the `plays` rung uses, so the two never disagree about whether the
|
|
1048
|
+
* bot played.
|
|
1049
|
+
*/
|
|
1050
|
+
played?: boolean;
|
|
1051
|
+
}): RungResult;
|
|
944
1052
|
declare function ladderVerdict(rungs: RungResult[], ctx?: LadderContext): LadderVerdict;
|
|
945
1053
|
/** The ladder as a person or an agent reads it: every rung, then the one action. */
|
|
946
1054
|
declare function ladderText(v: LadderVerdict): string;
|
|
@@ -1201,4 +1309,4 @@ interface PlaySession {
|
|
|
1201
1309
|
*/
|
|
1202
1310
|
declare function createPlaySession(json: unknown, opts?: PlaySessionOptions): Promise<PlaySession>;
|
|
1203
1311
|
//#endregion
|
|
1204
|
-
export { type AgreementSnapshot, type CollectionSnapshot, type Disagreement, type DisagreementKind, type FacingOptions, type FacingReport, type FeelOptions, type FeelReport, type FramingEntry, type FramingOptions, type FramingReport, GroundingIssue, GroundingOptions, type LadderVerdict, type MultiplayOptions, type MultiplayPlayer, type MultiplayReport, NodeCapture, type Outcome, type PickerOptions, PlaySession, PlaySessionOptions, type PlaytestOptions, type PlaytestReport, type PlaytestRun, RunContext, RunFailure, RunResult, RunScriptOptions, type RungResult, type RungStatus, type SceneAssetUrl, SceneCapture, ScriptStep, ValidateSceneOptions, ValidationResult, type Where, agreementProblems, auditScene, captureScene, collectionProblems, createPlaySession, describeCapture, describeFraming, facingReport, facingText, failingReplays, feelReport, feelText, findFloatingProps, findPlayer, firstLine, framingText, geometricPicker, geometricView, instancedScenes, ladderText, ladderVerdict, multiplayProblems, multiplayText, playMultiplayer, playtest, playtestFailed, playtestText, pointAtNode, registerAllNodes, runScript, saysRung, sceneAssetUrls, takeAgreementSnapshots, takeCollectionSnapshots, validateScene };
|
|
1312
|
+
export { type AgreementSnapshot, type CollectionSnapshot, type Disagreement, type DisagreementKind, type FacingOptions, type FacingReport, type FeelOptions, type FeelReport, type FramingEntry, type FramingOptions, type FramingReport, GroundingIssue, GroundingOptions, type LadderVerdict, type MultiplayOptions, type MultiplayPlayer, type MultiplayReport, NodeCapture, type Outcome, type PickerOptions, PlaySession, PlaySessionOptions, type PlaytestOptions, type PlaytestReport, type PlaytestRun, RunContext, RunFailure, RunResult, RunScriptOptions, type RungResult, type RungStatus, type SceneAssetUrl, SceneCapture, ScriptStep, ValidateSceneOptions, ValidationResult, type Where, agreementProblems, auditScene, captureScene, collectionProblems, createPlaySession, describeCapture, describeFraming, facingReport, facingText, failingReplays, feelReport, feelText, feelsRung, findFloatingProps, findPlayer, firstLine, framingText, geometricPicker, geometricView, instancedScenes, ladderText, ladderVerdict, multiplayProblems, multiplayText, playMultiplayer, playtest, playtestFailed, playtestText, pointAtNode, registerAllNodes, runScript, saysRung, sceneAssetUrls, takeAgreementSnapshots, takeCollectionSnapshots, unmeasuredPlays, validateScene };
|
package/dist/test.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { i as instancedScenes, r as auditScene } from "./replay-
|
|
2
|
-
import { A as
|
|
3
|
-
export { agreementProblems, auditScene, captureScene, collectionProblems, createPlaySession, describeCapture, describeFraming, facingReport, facingText, failingReplays, feelReport, feelText, findFloatingProps, findPlayer, firstLine, framingText, geometricPicker, geometricView, instancedScenes, ladderText, ladderVerdict, multiplayProblems, multiplayText, playMultiplayer, playtest, playtestFailed, playtestText, pointAtNode, registerAllNodes, runScript, saysRung, sceneAssetUrls, takeAgreementSnapshots, takeCollectionSnapshots, validateScene };
|
|
1
|
+
import { i as instancedScenes, r as auditScene } from "./replay-datiAE-b.js";
|
|
2
|
+
import { A as geometricPicker, C as facingReport, D as playtest, E as findPlayer, F as sceneAssetUrls, M as pointAtNode, N as describeFraming, O as playtestFailed, P as framingText, S as collectionProblems, T as failingReplays, _ as feelReport, a as registerAllNodes, b as takeCollectionSnapshots, c as feelsRung, d as ladderVerdict, f as saysRung, g as playMultiplayer, h as multiplayText, i as findFloatingProps, j as geometricView, k as playtestText, l as firstLine, m as multiplayProblems, n as createPlaySession, o as runScript, p as unmeasuredPlays, r as describeCapture, s as validateScene, t as captureScene, u as ladderText, v as feelText, w as facingText, x as agreementProblems, y as takeAgreementSnapshots } from "./test-Djuq3Vhq.js";
|
|
3
|
+
export { agreementProblems, auditScene, captureScene, collectionProblems, createPlaySession, describeCapture, describeFraming, facingReport, facingText, failingReplays, feelReport, feelText, feelsRung, findFloatingProps, findPlayer, firstLine, framingText, geometricPicker, geometricView, instancedScenes, ladderText, ladderVerdict, multiplayProblems, multiplayText, playMultiplayer, playtest, playtestFailed, playtestText, pointAtNode, registerAllNodes, runScript, saysRung, sceneAssetUrls, takeAgreementSnapshots, takeCollectionSnapshots, unmeasuredPlays, validateScene };
|