@adatechnology/game-test-room 0.20.0 → 0.21.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/dist/index.d.ts +71 -7
- package/dist/index.global.js +4374 -754
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +570 -141
- package/dist/index.js.map +1 -1
- package/package.json +18 -14
package/dist/index.js
CHANGED
|
@@ -1,13 +1,87 @@
|
|
|
1
1
|
// src/room.ts
|
|
2
2
|
import { createDestruction, JOINT_DEFAULTS, resolveStrings as resolveDestructionStrings } from "@adatechnology/game-destruction";
|
|
3
3
|
import { createAudioEngine, createAudioSink } from "@adatechnology/game-audio";
|
|
4
|
+
import { surfaceOfMaterial } from "@adatechnology/game-contracts";
|
|
5
|
+
|
|
6
|
+
// src/body-sounds.ts
|
|
7
|
+
import { BODY_FOLEY_BANK } from "@adatechnology/game-audio";
|
|
8
|
+
import { SWING_LEAD_SECONDS, hitSoundId, landSoundId, swingSoundId } from "@adatechnology/game-contracts";
|
|
9
|
+
var DEFAULT_TOOL_BLOWS = {
|
|
10
|
+
hand: "punch",
|
|
11
|
+
fists: "punch",
|
|
12
|
+
kick: "kick",
|
|
13
|
+
shove: "shove",
|
|
14
|
+
knife: "blade",
|
|
15
|
+
machete: "blade",
|
|
16
|
+
utility_knife: "blade",
|
|
17
|
+
axe: "chop",
|
|
18
|
+
hammer: "blunt",
|
|
19
|
+
claw_hammer: "blunt",
|
|
20
|
+
sledgehammer: "blunt",
|
|
21
|
+
baseball_bat: "blunt",
|
|
22
|
+
crowbar: "blunt",
|
|
23
|
+
wrench: "blunt"
|
|
24
|
+
};
|
|
25
|
+
function blowOfTool(id, category, table = DEFAULT_TOOL_BLOWS) {
|
|
26
|
+
const listed = table[id];
|
|
27
|
+
if (listed) return listed;
|
|
28
|
+
if (category === "melee_blade") return "blade";
|
|
29
|
+
if (category === "melee_blunt" || category === "hand") return id === "kick" ? "kick" : "blunt";
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
var levelOf = (energyJ) => Math.max(0.5, Math.min(1, 0.5 + energyJ / 800));
|
|
33
|
+
var STRIDE_METRES = { walk: 0.72, run: 1.05 };
|
|
34
|
+
function createBodySounds(audio) {
|
|
35
|
+
audio?.register(BODY_FOLEY_BANK);
|
|
36
|
+
const played = [];
|
|
37
|
+
const play = (id, position, volume, delay = 0) => {
|
|
38
|
+
played.push(id);
|
|
39
|
+
if (played.length > 64) played.shift();
|
|
40
|
+
audio?.play(id, { position, volume, ...delay > 0 ? { delay } : {} });
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
swing(blow, position, delay = 0) {
|
|
44
|
+
const id = swingSoundId(blow);
|
|
45
|
+
if (id) play(id, position, 0.8, delay);
|
|
46
|
+
},
|
|
47
|
+
hit(blow, target, position, energyJ) {
|
|
48
|
+
play(hitSoundId(blow, target), position, levelOf(energyJ));
|
|
49
|
+
},
|
|
50
|
+
jump: (position) => play("jump", position, 0.6),
|
|
51
|
+
land: (surface, speed, position) => play(landSoundId(surface, speed), position, Math.min(1, 0.45 + speed / 8)),
|
|
52
|
+
played
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
var swingLead = (blow) => SWING_LEAD_SECONDS[blow];
|
|
56
|
+
function createStrideCounter(stride = STRIDE_METRES) {
|
|
57
|
+
let left = stride.walk / 2;
|
|
58
|
+
let next = "right";
|
|
59
|
+
return {
|
|
60
|
+
advance(metres, gait) {
|
|
61
|
+
left -= metres;
|
|
62
|
+
if (left > 0) return null;
|
|
63
|
+
left += stride[gait];
|
|
64
|
+
const foot = next;
|
|
65
|
+
next = next === "right" ? "left" : "right";
|
|
66
|
+
return foot;
|
|
67
|
+
},
|
|
68
|
+
reset() {
|
|
69
|
+
left = stride.walk / 2;
|
|
70
|
+
next = "right";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/room.ts
|
|
4
76
|
import { createDestructionRenderer, createDestructionSound, dragSoundKey, impactKey, specFromObject3D } from "@adatechnology/game-destruction-three";
|
|
5
77
|
import { createArsenal, DEFAULT_LOADOUT } from "@adatechnology/game-weapons";
|
|
6
78
|
import { createFilmLcg, createFluids, createItemUse, ITEM_USES, resolveStrings as resolveFluidStrings } from "@adatechnology/game-fluids";
|
|
7
79
|
import { createFilmBridge, createFilmManager, createFluidRenderer } from "@adatechnology/game-fluids-three";
|
|
8
80
|
import { SPRITE_KINDS, createVfx, patchSpriteKinds } from "@adatechnology/game-vfx";
|
|
9
81
|
import { createVfxRenderer } from "@adatechnology/game-vfx-three";
|
|
10
|
-
import {
|
|
82
|
+
import { createFramePacer, createGeometryPool, createLightBudget, createMaterialPool, createProgramWarmup, createStaticBatcher, spriteProbes } from "@adatechnology/game-render-three";
|
|
83
|
+
import { DEFERRED_PRIORITY, DEFERRED_QUEUE_ID, checkAnimation, checkBench, checkDestructible, checkSpec, createEnvironmentBus, sealBlock, sealOpener, shotExtras, twistStroke, TWIST_LIFT, useToHit } from "@adatechnology/game-contracts";
|
|
84
|
+
import { createScheduler, startLoop } from "@adatechnology/game-runtime";
|
|
11
85
|
|
|
12
86
|
// src/arena.ts
|
|
13
87
|
import { markKitOrigin } from "@adatechnology/game-contracts";
|
|
@@ -1187,6 +1261,91 @@ function createLoading(options) {
|
|
|
1187
1261
|
};
|
|
1188
1262
|
}
|
|
1189
1263
|
|
|
1264
|
+
// src/graphics.ts
|
|
1265
|
+
import { createGraphicsController, frameLoad, targetFrameMs } from "@adatechnology/game-graphics";
|
|
1266
|
+
import { createFpsOverlay, createGraphicsScreen } from "@adatechnology/game-graphics/screen";
|
|
1267
|
+
import { createGpuFrameTimer, createGraphicsView, gpuName } from "@adatechnology/game-graphics-three";
|
|
1268
|
+
var QUIET_FRAMES = 10;
|
|
1269
|
+
function createRoomGraphics(o) {
|
|
1270
|
+
const view = createGraphicsView({ renderer: o.gl, camera: o.camera, sun: o.sun });
|
|
1271
|
+
const seconds = o.options.calibrationSeconds ?? 3;
|
|
1272
|
+
const controller = createGraphicsController({
|
|
1273
|
+
store: { ...o.options.storage !== void 0 ? { storage: o.options.storage } : {}, ...o.options.key ? { key: o.options.key } : {} },
|
|
1274
|
+
gpu: gpuName(o.gl),
|
|
1275
|
+
benchmark: seconds > 0 ? { durationS: seconds } : false,
|
|
1276
|
+
// the room measures its own frames once it is up, not the loading of its content
|
|
1277
|
+
calibrate: "manual",
|
|
1278
|
+
apply: (e) => view.apply(e)
|
|
1279
|
+
});
|
|
1280
|
+
const timer = createGpuFrameTimer(o.gl);
|
|
1281
|
+
const screen = o.ui ? createGraphicsScreen({ container: o.container, controller, locale: o.locale }) : null;
|
|
1282
|
+
const overlay = o.ui ? createFpsOverlay({ container: o.container, controller, locale: o.locale }) : null;
|
|
1283
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1284
|
+
let lastHorde = controller.settings.hordeDetail;
|
|
1285
|
+
const unsubscribe = controller.subscribe(() => {
|
|
1286
|
+
if (controller.settings.hordeDetail === lastHorde) return;
|
|
1287
|
+
lastHorde = controller.settings.hordeDetail;
|
|
1288
|
+
for (const l of listeners) l();
|
|
1289
|
+
});
|
|
1290
|
+
let task = null;
|
|
1291
|
+
const syncTask = () => {
|
|
1292
|
+
if (controller.phase === "calibrating") {
|
|
1293
|
+
task ?? (task = o.loading.begin(o.label, { total: 100 }));
|
|
1294
|
+
task.step(`${Math.round(controller.calibrationProgress * 100)}%`, Math.round(controller.calibrationProgress * 100));
|
|
1295
|
+
} else if (task) {
|
|
1296
|
+
task.end();
|
|
1297
|
+
task = null;
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
syncTask();
|
|
1301
|
+
let started = 0;
|
|
1302
|
+
let quiet = 0;
|
|
1303
|
+
let lastEnd = 0;
|
|
1304
|
+
return {
|
|
1305
|
+
controller,
|
|
1306
|
+
view,
|
|
1307
|
+
screen,
|
|
1308
|
+
host: {
|
|
1309
|
+
hordeDetail: () => controller.settings.hordeDetail,
|
|
1310
|
+
onChange(listener) {
|
|
1311
|
+
listeners.add(listener);
|
|
1312
|
+
return () => listeners.delete(listener);
|
|
1313
|
+
},
|
|
1314
|
+
report: (key2, value) => controller.report(key2, value)
|
|
1315
|
+
},
|
|
1316
|
+
frameStart() {
|
|
1317
|
+
started = performance.now();
|
|
1318
|
+
},
|
|
1319
|
+
beforeRender(camera) {
|
|
1320
|
+
view.hold();
|
|
1321
|
+
view.follow(camera.position.x, camera.position.z);
|
|
1322
|
+
timer.begin();
|
|
1323
|
+
},
|
|
1324
|
+
afterRender() {
|
|
1325
|
+
timer.end();
|
|
1326
|
+
},
|
|
1327
|
+
frameEnd() {
|
|
1328
|
+
const now = performance.now();
|
|
1329
|
+
const realSeconds = lastEnd > 0 ? (now - lastEnd) / 1e3 : 0;
|
|
1330
|
+
lastEnd = now;
|
|
1331
|
+
if (!(realSeconds > 0)) return;
|
|
1332
|
+
const cpuMs = started > 0 ? now - started : realSeconds * 1e3;
|
|
1333
|
+
controller.frame(realSeconds, frameLoad({ cpuMs, gpuMs: timer.ms, intervalMs: realSeconds * 1e3, targetMs: targetFrameMs(controller.settings.fpsCap) }));
|
|
1334
|
+
quiet = o.loading.active && !task ? 0 : quiet + 1;
|
|
1335
|
+
if (o.options.calibrate !== "manual" && quiet >= QUIET_FRAMES) controller.calibrateIfNeeded();
|
|
1336
|
+
syncTask();
|
|
1337
|
+
},
|
|
1338
|
+
dispose() {
|
|
1339
|
+
task?.end();
|
|
1340
|
+
unsubscribe();
|
|
1341
|
+
listeners.clear();
|
|
1342
|
+
timer.dispose();
|
|
1343
|
+
screen?.dispose();
|
|
1344
|
+
overlay?.dispose();
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1190
1349
|
// src/log.ts
|
|
1191
1350
|
var MAX_ENTRIES = 5e3;
|
|
1192
1351
|
var pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
@@ -1316,8 +1475,12 @@ function createLog(options = {}) {
|
|
|
1316
1475
|
}
|
|
1317
1476
|
|
|
1318
1477
|
// src/metrics.ts
|
|
1319
|
-
var ROOM_METRICS = ["fps", "frame_ms", "draw_calls", "triangles", "objects", "fragments", "particles", "decals", "blast_shelter", "blast_confinement", "blast_fragments"];
|
|
1478
|
+
var ROOM_METRICS = ["fps", "frame_ms", "draw_calls", "triangles", "objects", "fragments", "particles", "decals", "blast_shelter", "blast_confinement", "blast_fragments", "geometries", "textures", "heap_mb"];
|
|
1320
1479
|
var WINDOW_S = 0.5;
|
|
1480
|
+
function heapMB() {
|
|
1481
|
+
const memory = globalThis.performance?.memory;
|
|
1482
|
+
return typeof memory?.usedJSHeapSize === "number" ? memory.usedJSHeapSize / 1048576 : void 0;
|
|
1483
|
+
}
|
|
1321
1484
|
function createRoomMetrics(sources) {
|
|
1322
1485
|
const wanted = new Set(sources.ids);
|
|
1323
1486
|
const gl = sources.gl;
|
|
@@ -1353,6 +1516,9 @@ function createRoomMetrics(sources) {
|
|
|
1353
1516
|
const render = ownsRender ? { calls, triangles } : gl.info?.render;
|
|
1354
1517
|
put("draw_calls", render?.calls);
|
|
1355
1518
|
put("triangles", render?.triangles);
|
|
1519
|
+
put("geometries", gl.info?.memory?.geometries);
|
|
1520
|
+
put("textures", gl.info?.memory?.textures);
|
|
1521
|
+
put("heap_mb", heapMB());
|
|
1356
1522
|
const blast = sources.blast?.();
|
|
1357
1523
|
if (blast) {
|
|
1358
1524
|
put("blast_shelter", blast.shelter * 100);
|
|
@@ -1372,6 +1538,43 @@ function createRoomMetrics(sources) {
|
|
|
1372
1538
|
};
|
|
1373
1539
|
}
|
|
1374
1540
|
|
|
1541
|
+
// src/memory-watch.ts
|
|
1542
|
+
function createMemoryWatch(options) {
|
|
1543
|
+
const clears = Math.max(2, options.clears ?? 4);
|
|
1544
|
+
const budget = { geometries: options.budget?.geometries ?? 40, textures: options.budget?.textures ?? 4 };
|
|
1545
|
+
const histories = /* @__PURE__ */ new Map();
|
|
1546
|
+
let last;
|
|
1547
|
+
return {
|
|
1548
|
+
cleared(where) {
|
|
1549
|
+
const now = options.read();
|
|
1550
|
+
if (!now) return void 0;
|
|
1551
|
+
const key2 = where ?? "";
|
|
1552
|
+
const history = histories.get(key2) ?? [];
|
|
1553
|
+
histories.set(key2, history);
|
|
1554
|
+
history.push(now);
|
|
1555
|
+
if (history.length > clears + 1) history.shift();
|
|
1556
|
+
const first = history[0];
|
|
1557
|
+
const grew = { geometries: now.geometries - first.geometries, textures: now.textures - first.textures };
|
|
1558
|
+
let growing = history.length === clears + 1;
|
|
1559
|
+
for (let i = 1; growing && i < history.length; i++) {
|
|
1560
|
+
const before = history[i - 1];
|
|
1561
|
+
const after = history[i];
|
|
1562
|
+
growing = after.geometries > before.geometries || after.textures > before.textures;
|
|
1563
|
+
}
|
|
1564
|
+
const leaking = growing && (grew.geometries > budget.geometries || grew.textures > budget.textures);
|
|
1565
|
+
last = { after: now, grew, leaking };
|
|
1566
|
+
return last;
|
|
1567
|
+
},
|
|
1568
|
+
get last() {
|
|
1569
|
+
return last;
|
|
1570
|
+
},
|
|
1571
|
+
reset() {
|
|
1572
|
+
histories.clear();
|
|
1573
|
+
last = void 0;
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1375
1578
|
// src/narrate.ts
|
|
1376
1579
|
var SKIP_DAMAGE = /* @__PURE__ */ new Set(["fire", "heat"]);
|
|
1377
1580
|
function createNarrator(c) {
|
|
@@ -1885,6 +2088,7 @@ function createNarrator(c) {
|
|
|
1885
2088
|
}
|
|
1886
2089
|
|
|
1887
2090
|
// src/player.ts
|
|
2091
|
+
var LANDING_MIN_SPEED = 1;
|
|
1888
2092
|
var EYE = 1.7;
|
|
1889
2093
|
var PLAYER_RADIUS = 0.3;
|
|
1890
2094
|
var FOOT = 0.5;
|
|
@@ -1911,6 +2115,7 @@ function createPlayer(THREE, o) {
|
|
|
1911
2115
|
let bend = 0;
|
|
1912
2116
|
let bendTarget = 0;
|
|
1913
2117
|
let vy = 0;
|
|
2118
|
+
let floorUnder = 0;
|
|
1914
2119
|
let walked = 0;
|
|
1915
2120
|
let running = false;
|
|
1916
2121
|
const px = { x: o.start[0], z: o.start[2] };
|
|
@@ -2163,6 +2368,9 @@ function createPlayer(THREE, o) {
|
|
|
2163
2368
|
get running() {
|
|
2164
2369
|
return running;
|
|
2165
2370
|
},
|
|
2371
|
+
get grounded() {
|
|
2372
|
+
return y <= floorUnder + 1e-3 && vy <= 0;
|
|
2373
|
+
},
|
|
2166
2374
|
get crouching() {
|
|
2167
2375
|
return isCrouched();
|
|
2168
2376
|
},
|
|
@@ -2188,13 +2396,18 @@ function createPlayer(THREE, o) {
|
|
|
2188
2396
|
return;
|
|
2189
2397
|
}
|
|
2190
2398
|
const floor = Math.max(floorHere(), o.footing?.(px.x, px.z, PLAYER_RADIUS * FOOT, y).floor ?? -Infinity);
|
|
2399
|
+
floorUnder = floor;
|
|
2191
2400
|
if (y < floor && vy <= 0) y = floor;
|
|
2192
|
-
if (y <= floor && keys.has("Space") && !walkHeld)
|
|
2401
|
+
if (y <= floor && keys.has("Space") && !walkHeld) {
|
|
2402
|
+
vy = JUMP;
|
|
2403
|
+
o.onFeet?.({ type: "jump" });
|
|
2404
|
+
}
|
|
2193
2405
|
if (y > floor || vy > 0) {
|
|
2194
2406
|
vy -= GRAVITY * dt;
|
|
2195
2407
|
y += vy * dt;
|
|
2196
2408
|
if (y <= floor) {
|
|
2197
2409
|
y = floor;
|
|
2410
|
+
if (-vy >= LANDING_MIN_SPEED) o.onFeet?.({ type: "land", speed: -vy });
|
|
2198
2411
|
vy = 0;
|
|
2199
2412
|
}
|
|
2200
2413
|
}
|
|
@@ -3121,6 +3334,11 @@ var en = {
|
|
|
3121
3334
|
"metric.fragments": "Fragments",
|
|
3122
3335
|
"metric.particles": "Particles",
|
|
3123
3336
|
"metric.decals": "Decals",
|
|
3337
|
+
"metric.geometries": "Geometries",
|
|
3338
|
+
"metric.textures": "Textures",
|
|
3339
|
+
"metric.heap_mb": "Heap MB",
|
|
3340
|
+
"memory.leak_short": "memory leak: +{geometries} geometries, +{textures} textures per {clears} clears",
|
|
3341
|
+
"memory.leak": "Memory keeps growing after each clear: +{geometries} geometries and +{textures} textures over the last {clears} clears. Something removed from the scene was not released (discardObject3D).",
|
|
3124
3342
|
"act.spawn": "Spawned {name} at x {x} \xB7 z {z}",
|
|
3125
3343
|
"act.clear": "Cleared the room",
|
|
3126
3344
|
"act.reset": "Repaired every object",
|
|
@@ -3167,8 +3385,11 @@ var en = {
|
|
|
3167
3385
|
"act.throw": "Threw {name} at {speed} m/s",
|
|
3168
3386
|
"ground.name": "Ground",
|
|
3169
3387
|
"act.landing": "{name} landed at {speed} m/s: {energy} J",
|
|
3388
|
+
"act.player_land": "You landed at {speed} m/s",
|
|
3170
3389
|
"act.bench_failed": "{name} failed: {error}",
|
|
3171
3390
|
"loading.working": "Working\u2026",
|
|
3391
|
+
"loading.graphics": "Calibrating graphics\u2026",
|
|
3392
|
+
"ui.graphics": "Graphics",
|
|
3172
3393
|
"loading.open_area": "Opening {area}\u2026",
|
|
3173
3394
|
"loading.spawn": "Building {name}\u2026",
|
|
3174
3395
|
"act.loosened": "{name} was heavy but free: the blast moved it",
|
|
@@ -3471,6 +3692,11 @@ var ptBR = {
|
|
|
3471
3692
|
"metric.fragments": "Fragmentos",
|
|
3472
3693
|
"metric.particles": "Part\xEDculas",
|
|
3473
3694
|
"metric.decals": "Decalques",
|
|
3695
|
+
"metric.geometries": "Geometrias",
|
|
3696
|
+
"metric.textures": "Texturas",
|
|
3697
|
+
"metric.heap_mb": "Heap MB",
|
|
3698
|
+
"memory.leak_short": "vazamento de mem\xF3ria: +{geometries} geometrias, +{textures} texturas em {clears} limpezas",
|
|
3699
|
+
"memory.leak": "A mem\xF3ria continua subindo a cada limpeza: +{geometries} geometrias e +{textures} texturas nas \xFAltimas {clears} limpezas. Algo tirado da cena n\xE3o foi liberado (discardObject3D).",
|
|
3474
3700
|
"act.spawn": "Criou {name} em x {x} \xB7 z {z}",
|
|
3475
3701
|
"act.clear": "Limpou a sala",
|
|
3476
3702
|
"act.reset": "Consertou todos os objetos",
|
|
@@ -3517,8 +3743,11 @@ var ptBR = {
|
|
|
3517
3743
|
"act.throw": "Arremessou {name} a {speed} m/s",
|
|
3518
3744
|
"ground.name": "Ch\xE3o",
|
|
3519
3745
|
"act.landing": "{name} caiu a {speed} m/s: {energy} J",
|
|
3746
|
+
"act.player_land": "Voc\xEA aterrissou a {speed} m/s",
|
|
3520
3747
|
"act.bench_failed": "{name} falhou: {error}",
|
|
3521
3748
|
"loading.working": "Trabalhando\u2026",
|
|
3749
|
+
"loading.graphics": "Calibrando os gr\xE1ficos\u2026",
|
|
3750
|
+
"ui.graphics": "Gr\xE1ficos",
|
|
3522
3751
|
"loading.open_area": "Abrindo {area}\u2026",
|
|
3523
3752
|
"loading.spawn": "Montando {name}\u2026",
|
|
3524
3753
|
"act.loosened": "{name} \xE9 pesado mas estava solto: a explos\xE3o o moveu",
|
|
@@ -4110,6 +4339,7 @@ var CSS = `
|
|
|
4110
4339
|
.tr-top{position:absolute;top:8px;left:50%;transform:translateX(-50%);text-align:center;max-width:46vw}
|
|
4111
4340
|
.tr-tool{display:inline-block;background:rgba(10,12,16,.72);padding:4px 12px;border-radius:6px;font-size:14px;font-weight:600}
|
|
4112
4341
|
.tr-tool small{font-weight:400;opacity:.8;margin-left:8px}
|
|
4342
|
+
.tr-alert{display:none;position:relative;z-index:20;margin-top:6px;background:rgba(183,28,28,.92);color:#fff;padding:6px 12px;border-radius:6px;font-size:13px;font-weight:600;box-shadow:0 2px 8px rgba(0,0,0,.4)}
|
|
4113
4343
|
.tr-status{margin-top:4px;min-height:16px;color:#ffd23f;text-shadow:0 1px 2px #000}
|
|
4114
4344
|
.tr-prog{margin:4px auto 0;width:260px;background:rgba(10,12,16,.72);border-radius:6px;padding:4px 8px;display:none}
|
|
4115
4345
|
.tr-prog b{display:block;font-weight:500;margin-bottom:3px}
|
|
@@ -4228,13 +4458,14 @@ function createUi(o) {
|
|
|
4228
4458
|
const top = mk3("div", "tr-top");
|
|
4229
4459
|
const toolBox = mk3("div", "tr-tool");
|
|
4230
4460
|
const status = mk3("div", "tr-status");
|
|
4461
|
+
const alertBox = mk3("div", "tr-alert");
|
|
4231
4462
|
const prog = mk3("div", "tr-prog");
|
|
4232
4463
|
const progLabel = mk3("b");
|
|
4233
4464
|
const progBar = mk3("div", "tr-bar");
|
|
4234
4465
|
const progFill = mk3("i");
|
|
4235
4466
|
progBar.appendChild(progFill);
|
|
4236
4467
|
prog.append(progLabel, progBar);
|
|
4237
|
-
top.append(toolBox, status, prog);
|
|
4468
|
+
top.append(toolBox, alertBox, status, prog);
|
|
4238
4469
|
const bottom = mk3("div", "tr-bottom");
|
|
4239
4470
|
const held = mk3("div", "tr-held");
|
|
4240
4471
|
const hotbar = mk3("div", "tr-hotbar");
|
|
@@ -4461,17 +4692,22 @@ function createUi(o) {
|
|
|
4461
4692
|
fullscreenBtn.title = t("ui.fullscreen.hint");
|
|
4462
4693
|
const blastBtns = ["shelter", "fragmentHits", "confinement"].map((key2) => ({ key: key2, b: ctl(t(`ui.blast.${key2}`), () => o.onBlast(key2)) }));
|
|
4463
4694
|
for (const { key: key2, b } of blastBtns) b.title = t(`ui.blast.${key2}.hint`);
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
qSel
|
|
4695
|
+
if (o.onGraphics) {
|
|
4696
|
+
const g = ctl(t("ui.graphics"), () => o.onGraphics());
|
|
4697
|
+
g.dataset.testid = "graphics-button";
|
|
4698
|
+
} else {
|
|
4699
|
+
const qSel = mk3("select");
|
|
4700
|
+
for (const q of ["low", "medium", "high"]) {
|
|
4701
|
+
const op = mk3("option", void 0, t(`ui.quality.${q}`));
|
|
4702
|
+
op.value = q;
|
|
4703
|
+
qSel.appendChild(op);
|
|
4704
|
+
}
|
|
4705
|
+
qSel.value = o.quality;
|
|
4706
|
+
qSel.onchange = () => o.onQuality(qSel.value);
|
|
4707
|
+
const qLabel = mk3("label", void 0, t("ui.quality") + " ");
|
|
4708
|
+
qLabel.appendChild(qSel);
|
|
4709
|
+
controls.appendChild(qLabel);
|
|
4469
4710
|
}
|
|
4470
|
-
qSel.value = o.quality;
|
|
4471
|
-
qSel.onchange = () => o.onQuality(qSel.value);
|
|
4472
|
-
const qLabel = mk3("label", void 0, t("ui.quality") + " ");
|
|
4473
|
-
qLabel.appendChild(qSel);
|
|
4474
|
-
controls.appendChild(qLabel);
|
|
4475
4711
|
const count = mk3("span");
|
|
4476
4712
|
controls.appendChild(count);
|
|
4477
4713
|
cat.appendChild(controls);
|
|
@@ -4697,6 +4933,10 @@ function createUi(o) {
|
|
|
4697
4933
|
renderInspector(s.inspect);
|
|
4698
4934
|
}
|
|
4699
4935
|
},
|
|
4936
|
+
alert(text) {
|
|
4937
|
+
alertBox.textContent = text ?? "";
|
|
4938
|
+
alertBox.style.display = text ? "block" : "none";
|
|
4939
|
+
},
|
|
4700
4940
|
toast(text) {
|
|
4701
4941
|
status.textContent = text;
|
|
4702
4942
|
if (toastTimer) clearTimeout(toastTimer);
|
|
@@ -4733,7 +4973,14 @@ function createUi(o) {
|
|
|
4733
4973
|
|
|
4734
4974
|
// src/room.ts
|
|
4735
4975
|
var FIXED_DT = 1 / 60;
|
|
4976
|
+
var EXPLOSIVE_EFFECT_IDS = ["m67", "molotov", "pipe_bomb", "m79_40mm", "m72_66mm", "at4_84mm", "dynamite", "c4", "claymore"];
|
|
4977
|
+
var DEFAULT_PARTICLE_LIMIT = 900;
|
|
4978
|
+
var WARMED_FOG_STATES = ["none", "linear", "exp2"];
|
|
4979
|
+
var ROOM_LIGHT_SIZES = { point: 16, spot: 4, pointShadows: 0, hemisphere: 1 };
|
|
4736
4980
|
var SOLID_SPAN = 3;
|
|
4981
|
+
var BATCH_MIN_MESHES = 8;
|
|
4982
|
+
var BATCH_BUDGET_MS = 1;
|
|
4983
|
+
var UNBATCHED_OWNERS = /vehicles|characters|animals|crowd/;
|
|
4737
4984
|
var STEP_UP = 0.45;
|
|
4738
4985
|
var BODY_HEIGHT2 = 1.75;
|
|
4739
4986
|
var KICK_MASS = 6;
|
|
@@ -4801,6 +5048,17 @@ function createTestRoom(options) {
|
|
|
4801
5048
|
const arenaSize = options.arenaSize ?? 14;
|
|
4802
5049
|
const arena = createArena(THREE, arenaSize, scene, options.scenery, options.tap);
|
|
4803
5050
|
scene.add(arena.group);
|
|
5051
|
+
const graphics = ownRenderer && options.graphics ? createRoomGraphics({
|
|
5052
|
+
gl,
|
|
5053
|
+
camera,
|
|
5054
|
+
sun: arena.sun,
|
|
5055
|
+
container,
|
|
5056
|
+
locale,
|
|
5057
|
+
loading,
|
|
5058
|
+
label: t("loading.graphics"),
|
|
5059
|
+
ui: options.ui !== false,
|
|
5060
|
+
options: options.graphics === true ? {} : options.graphics
|
|
5061
|
+
}) : null;
|
|
4804
5062
|
const ownWorld = !options.world;
|
|
4805
5063
|
const world = options.world ?? createDestruction({ seed: 1, ...options.worldOptions, materials: [...options.worldOptions?.materials ?? []] });
|
|
4806
5064
|
const pristineWorld = ownWorld ? world.serialize() : null;
|
|
@@ -4846,12 +5104,17 @@ function createTestRoom(options) {
|
|
|
4846
5104
|
const ownAudio = options.audio === void 0;
|
|
4847
5105
|
const audio = options.audio === false ? null : options.audio ?? createAudioEngine({ deferredStart: {} });
|
|
4848
5106
|
const destructionSound = audio ? createDestructionSound(audio) : null;
|
|
5107
|
+
const bodySounds = createBodySounds(audio);
|
|
4849
5108
|
const playSound = (key2, point, volume) => {
|
|
4850
5109
|
destructionSound?.onSound(key2, point, volume);
|
|
4851
5110
|
options.onSound?.(key2, point, volume);
|
|
4852
5111
|
};
|
|
5112
|
+
const lights = createLightBudget(THREE, scene, { ...ROOM_LIGHT_SIZES, ...options.lightSizes });
|
|
5113
|
+
const warmup = createProgramWarmup(THREE);
|
|
5114
|
+
const pools = { geometries: createGeometryPool(), materials: createMaterialPool({ anchors: warmup.anchors, renderer: gl }) };
|
|
4853
5115
|
const vfx = createVfxRenderer(THREE, {
|
|
4854
5116
|
scene,
|
|
5117
|
+
lights,
|
|
4855
5118
|
camera,
|
|
4856
5119
|
overlay: container,
|
|
4857
5120
|
quality,
|
|
@@ -5005,7 +5268,7 @@ function createTestRoom(options) {
|
|
|
5005
5268
|
});
|
|
5006
5269
|
let pending = [];
|
|
5007
5270
|
const eventListeners = /* @__PURE__ */ new Set();
|
|
5008
|
-
const listeners = { spawn: /* @__PURE__ */ new Set(), tool: /* @__PURE__ */ new Set(), grab: /* @__PURE__ */ new Set(), throw: /* @__PURE__ */ new Set(), event: eventListeners, camera: /* @__PURE__ */ new Set() };
|
|
5271
|
+
const listeners = { spawn: /* @__PURE__ */ new Set(), tool: /* @__PURE__ */ new Set(), grab: /* @__PURE__ */ new Set(), throw: /* @__PURE__ */ new Set(), event: eventListeners, camera: /* @__PURE__ */ new Set(), footfall: /* @__PURE__ */ new Set() };
|
|
5009
5272
|
const fire = (type, data) => {
|
|
5010
5273
|
for (const fn of listeners[type] ?? []) fn(data);
|
|
5011
5274
|
};
|
|
@@ -5142,6 +5405,14 @@ function createTestRoom(options) {
|
|
|
5142
5405
|
floorAt: terrain.heightAt,
|
|
5143
5406
|
start: arena.playerStart,
|
|
5144
5407
|
crouchKeys: options.crouch?.keys,
|
|
5408
|
+
onFeet: (event) => {
|
|
5409
|
+
const at = player.position;
|
|
5410
|
+
if (event.type === "jump") bodySounds.jump(at);
|
|
5411
|
+
else {
|
|
5412
|
+
bodySounds.land(surfaceUnderFeet(), event.speed, at);
|
|
5413
|
+
action("landing_player", t("act.player_land", { speed: num(event.speed, 1) }), { data: { speedMs: Math.round(event.speed * 100) / 100, surface: surfaceUnderFeet() } });
|
|
5414
|
+
}
|
|
5415
|
+
},
|
|
5145
5416
|
hooks: {
|
|
5146
5417
|
primaryDown: () => {
|
|
5147
5418
|
useHeld = true;
|
|
@@ -5318,12 +5589,23 @@ function createTestRoom(options) {
|
|
|
5318
5589
|
for (const p of st?.parts ?? []) if (p.status !== "broken" && p.status !== "melted" && p.status !== "ashes") m += p.mass;
|
|
5319
5590
|
return m;
|
|
5320
5591
|
};
|
|
5592
|
+
const batchers = /* @__PURE__ */ new Map();
|
|
5593
|
+
const batchObject = (objectId, root) => {
|
|
5594
|
+
let meshes = 0;
|
|
5595
|
+
let unbatched = false;
|
|
5596
|
+
root.traverse((object) => {
|
|
5597
|
+
if (object.isMesh) meshes++;
|
|
5598
|
+
if (UNBATCHED_OWNERS.test(object.userData.kitOrigin?.owner ?? "")) unbatched = true;
|
|
5599
|
+
});
|
|
5600
|
+
if (!unbatched && meshes >= BATCH_MIN_MESHES) batchers.set(objectId, createStaticBatcher(THREE, root, { budgetMs: BATCH_BUDGET_MS }));
|
|
5601
|
+
};
|
|
5321
5602
|
const registerEntry = (objectId, itemId, root, spec, name, parts, item, arenaObject, movable) => {
|
|
5322
5603
|
specs.set(objectId, spec);
|
|
5323
5604
|
renderer.bind(objectId, root, parts ? { parts } : void 0);
|
|
5324
5605
|
const so = { objectId, itemId, root, movable };
|
|
5325
5606
|
const e = { so, item, name, arena: arenaObject, fluidOffset: [0, 0, 0], uses: [], piece: null, last: [root.position.x, root.position.y, root.position.z], velocity: [0, 0, 0] };
|
|
5326
5607
|
entries.set(objectId, e);
|
|
5608
|
+
batchObject(objectId, root);
|
|
5327
5609
|
if (movable) {
|
|
5328
5610
|
e.piece = renderer.makePiece(objectId, { mass: Math.max(0.2, massOfObject(objectId)) });
|
|
5329
5611
|
rendererPieces.set(objectId, e.piece);
|
|
@@ -5591,6 +5873,8 @@ function createTestRoom(options) {
|
|
|
5591
5873
|
dropTree(e.so.root);
|
|
5592
5874
|
dropTree(inner);
|
|
5593
5875
|
entries.delete(id);
|
|
5876
|
+
batchers.get(id)?.dispose();
|
|
5877
|
+
batchers.delete(id);
|
|
5594
5878
|
doors.forget(id);
|
|
5595
5879
|
rendererPieces.delete(id);
|
|
5596
5880
|
specs.delete(id);
|
|
@@ -5689,7 +5973,7 @@ function createTestRoom(options) {
|
|
|
5689
5973
|
lastImpact.clear();
|
|
5690
5974
|
addArena();
|
|
5691
5975
|
standFootprints();
|
|
5692
|
-
timeScale = 1;
|
|
5976
|
+
scheduler.timeScale = 1;
|
|
5693
5977
|
fastForward = false;
|
|
5694
5978
|
walkPlan = null;
|
|
5695
5979
|
};
|
|
@@ -5890,6 +6174,28 @@ function createTestRoom(options) {
|
|
|
5890
6174
|
}
|
|
5891
6175
|
};
|
|
5892
6176
|
const rayWorld = (o, d, max) => withoutHeld(() => world.raycast(o, d, max));
|
|
6177
|
+
const surfaceUnderFeet = () => {
|
|
6178
|
+
const [x, feet, z] = player.position;
|
|
6179
|
+
const under = rayWorld([x, feet + 0.15, z], [0, -1, 0], 0.5);
|
|
6180
|
+
if (under && under.object !== GROUND_ID) {
|
|
6181
|
+
const material = world.state(under.object)?.parts.find((p) => p.id === under.part)?.material;
|
|
6182
|
+
if (material) return surfaceOfMaterial(material);
|
|
6183
|
+
}
|
|
6184
|
+
return surfaceOfMaterial(terrain.columnAt(x, z).material);
|
|
6185
|
+
};
|
|
6186
|
+
const strides = createStrideCounter();
|
|
6187
|
+
let feetAt = null;
|
|
6188
|
+
const stepFeet = (dt) => {
|
|
6189
|
+
const [x, , z] = player.position;
|
|
6190
|
+
const moved = feetAt && dt > 0 ? Math.hypot(x - feetAt[0], z - feetAt[1]) : 0;
|
|
6191
|
+
feetAt = [x, z];
|
|
6192
|
+
if (dt <= 0 || seating.seat) return;
|
|
6193
|
+
const gait = player.running ? "run" : "walk";
|
|
6194
|
+
const foot = avatar?.footfall ? avatar.footfall() : moved / dt > MAX_STRIDE_SPEED ? null : strides.advance(moved, gait);
|
|
6195
|
+
if (!foot || !player.grounded) return;
|
|
6196
|
+
emitHost({ type: "footfall", foot, surface: surfaceUnderFeet(), gait, position: player.position });
|
|
6197
|
+
fire("footfall", { foot, gait, time: simTime });
|
|
6198
|
+
};
|
|
5893
6199
|
let rng = 12345;
|
|
5894
6200
|
const rnd = () => {
|
|
5895
6201
|
rng = rng * 1664525 + 1013904223 >>> 0;
|
|
@@ -6008,6 +6314,12 @@ function createTestRoom(options) {
|
|
|
6008
6314
|
ui?.toast(msg);
|
|
6009
6315
|
};
|
|
6010
6316
|
const toolName = (id = toolId) => arsenal.name(id);
|
|
6317
|
+
const toolDef = (id) => arsenal.weapons.get(id);
|
|
6318
|
+
const blowTarget = (r) => {
|
|
6319
|
+
const part = world.state(r.object)?.parts.find((p) => p.id === r.part);
|
|
6320
|
+
if (!part || !(world.materials.get(part.material)?.tags ?? []).includes("body")) return "object";
|
|
6321
|
+
return /head|face|jaw|skull|neck/.test(r.part) ? "head" : "body";
|
|
6322
|
+
};
|
|
6011
6323
|
const hitWhere = (r) => {
|
|
6012
6324
|
const p = world.part(r.object, r.part);
|
|
6013
6325
|
return `${nameOf(r.object)} \u203A ${r.part}${p ? ` (${dStrings.materialNames[p.material] ?? p.material})` : ""}`;
|
|
@@ -6017,6 +6329,7 @@ function createTestRoom(options) {
|
|
|
6017
6329
|
renderer.impulse(objectId, [dir[0] * momentum, dir[1] * momentum, dir[2] * momentum], point);
|
|
6018
6330
|
};
|
|
6019
6331
|
const shotRay = new THREE.Raycaster();
|
|
6332
|
+
shotRay.layers.enableAll();
|
|
6020
6333
|
const LIQUID_SLOWING = 0.25;
|
|
6021
6334
|
const SHOT_GRIP = 0.3;
|
|
6022
6335
|
const shotPush = (s, objectId, results, fallback) => {
|
|
@@ -6187,6 +6500,8 @@ function createTestRoom(options) {
|
|
|
6187
6500
|
return;
|
|
6188
6501
|
}
|
|
6189
6502
|
world.hitPart(r.object, r.part, { ...useToHit(u, r.point, r.normal), source: "player" });
|
|
6503
|
+
const blow = blowOfTool(id, toolDef(id)?.category ?? "");
|
|
6504
|
+
if (blow) bodySounds.hit(blow, blowTarget(r), r.point, u.energy);
|
|
6190
6505
|
pushPiece(r.object, u.direction, 2 * u.energy / 6, r.point);
|
|
6191
6506
|
action("melee", t("act.melee.hit", { weapon: w, energy: num(u.energy, 0), where: hitWhere(r), dist: num(r.distance, 1) }), {
|
|
6192
6507
|
objectId: r.object,
|
|
@@ -6227,6 +6542,10 @@ function createTestRoom(options) {
|
|
|
6227
6542
|
comboNext = (stepIndex + 1) % combo.steps.length;
|
|
6228
6543
|
comboAt = simTime;
|
|
6229
6544
|
}
|
|
6545
|
+
const blow = blowOfTool(id, toolDef(id)?.category ?? "");
|
|
6546
|
+
const lead = blow ? swingLead(blow) : 0;
|
|
6547
|
+
const swing = blow ? () => bodySounds.swing(blow, player.position) : void 0;
|
|
6548
|
+
if (swing && (seconds === null || seconds <= lead)) swing();
|
|
6230
6549
|
if (clip === void 0 || seconds === null) {
|
|
6231
6550
|
if (leg) {
|
|
6232
6551
|
legAim();
|
|
@@ -6241,6 +6560,7 @@ function createTestRoom(options) {
|
|
|
6241
6560
|
strikes.push({
|
|
6242
6561
|
key: id,
|
|
6243
6562
|
remaining: seconds,
|
|
6563
|
+
...swing && seconds > lead ? { swing, lead } : {},
|
|
6244
6564
|
land: () => {
|
|
6245
6565
|
if (leg) legAim();
|
|
6246
6566
|
else aim();
|
|
@@ -6250,7 +6570,13 @@ function createTestRoom(options) {
|
|
|
6250
6570
|
};
|
|
6251
6571
|
const stepStrikes = (dt) => {
|
|
6252
6572
|
if (strikes.length === 0) return;
|
|
6253
|
-
for (const strike of strikes)
|
|
6573
|
+
for (const strike of strikes) {
|
|
6574
|
+
strike.remaining -= dt;
|
|
6575
|
+
if (strike.swing && strike.remaining <= (strike.lead ?? 0)) {
|
|
6576
|
+
strike.swing();
|
|
6577
|
+
delete strike.swing;
|
|
6578
|
+
}
|
|
6579
|
+
}
|
|
6254
6580
|
const landed = strikes.filter((strike) => strike.remaining <= 0);
|
|
6255
6581
|
strikes = strikes.filter((strike) => strike.remaining > 0);
|
|
6256
6582
|
for (const strike of landed) strike.land();
|
|
@@ -6364,26 +6690,27 @@ function createTestRoom(options) {
|
|
|
6364
6690
|
aim();
|
|
6365
6691
|
const cat = tool.def.category;
|
|
6366
6692
|
if (cat === "hand") {
|
|
6367
|
-
if (strikes.some((strike) => strike.key === "hand" || strike.key === "fists")) return;
|
|
6693
|
+
if (strikes.some((strike) => strike.key === "hand" || strike.key === "fists")) return "cooldown";
|
|
6368
6694
|
strikeUse({ kind: "melee", damage: "blunt", energy: 25, radius: 0.1, reach: 0.8, origin: [aimO[0], aimO[1], aimO[2]], direction: [aimD[0], aimD[1], aimD[2]] }, "hand");
|
|
6369
|
-
return;
|
|
6695
|
+
return "ok";
|
|
6370
6696
|
}
|
|
6371
|
-
if (strikes.some((strike) => strike.key === toolId)) return;
|
|
6697
|
+
if (strikes.some((strike) => strike.key === toolId)) return "cooldown";
|
|
6372
6698
|
const res = tool.trigger([aimO[0], aimO[1], aimO[2]], [aimD[0], aimD[1], aimD[2]], dt, held, "player");
|
|
6373
6699
|
flushEvents(toolId, tool, res.recoil);
|
|
6374
6700
|
if (res.status === "empty") {
|
|
6375
6701
|
if (!tool.reloading && tool.reserve > 0) startReload();
|
|
6376
6702
|
say(t(tool.reloading ? "hud.reloading" : "hud.empty"));
|
|
6377
|
-
return;
|
|
6703
|
+
return res.status;
|
|
6378
6704
|
}
|
|
6379
6705
|
if (res.status !== "ok") {
|
|
6380
6706
|
if (res.status === "reloading") say(t("hud.reloading"));
|
|
6381
6707
|
else if (res.status === "no_fuel") say(t("hud.no_fuel"));
|
|
6382
6708
|
else if (res.status === "fault" && res.fault) say(t("hud.fault", { fault: t(`fault.${res.fault}`) }));
|
|
6383
6709
|
else if (res.status === "safe") say(t("hud.safe"));
|
|
6384
|
-
return;
|
|
6710
|
+
return res.status;
|
|
6385
6711
|
}
|
|
6386
6712
|
applyUses(res, dt);
|
|
6713
|
+
return res.status;
|
|
6387
6714
|
};
|
|
6388
6715
|
const applyUses = (res, dt) => {
|
|
6389
6716
|
if (res.shots.length) fireShots(res.shots, res.recoil);
|
|
@@ -6485,8 +6812,9 @@ function createTestRoom(options) {
|
|
|
6485
6812
|
heldTilt = 0;
|
|
6486
6813
|
};
|
|
6487
6814
|
const handUse = () => {
|
|
6488
|
-
if (heldPiece)
|
|
6489
|
-
|
|
6815
|
+
if (!heldPiece) return useTool(FIXED_DT, false);
|
|
6816
|
+
grab();
|
|
6817
|
+
return "ok";
|
|
6490
6818
|
};
|
|
6491
6819
|
const grab = () => {
|
|
6492
6820
|
if (push.active) return false;
|
|
@@ -6999,13 +7327,64 @@ function createTestRoom(options) {
|
|
|
6999
7327
|
distance: r.distance
|
|
7000
7328
|
};
|
|
7001
7329
|
};
|
|
7002
|
-
let timeScale = 1;
|
|
7003
7330
|
let fastForward = false;
|
|
7004
7331
|
let hudAcc = 1;
|
|
7005
7332
|
let metricsAcc = 0;
|
|
7006
7333
|
let ownsLoop = false;
|
|
7334
|
+
let moduleFrameCount = 0;
|
|
7335
|
+
const headlessErrors = /* @__PURE__ */ new Map();
|
|
7336
|
+
const loggedErrors = /* @__PURE__ */ new Set();
|
|
7337
|
+
let headlessDepth = 0;
|
|
7338
|
+
const reportFrameError = (error, system) => {
|
|
7339
|
+
if (headlessDepth > 0) {
|
|
7340
|
+
if (!headlessErrors.has(system.id)) headlessErrors.set(system.id, error);
|
|
7341
|
+
return;
|
|
7342
|
+
}
|
|
7343
|
+
if (loggedErrors.has(system.id)) return;
|
|
7344
|
+
loggedErrors.add(system.id);
|
|
7345
|
+
console.error(`test-room: ${system.id} (${system.phase}) threw`, error);
|
|
7346
|
+
};
|
|
7347
|
+
const runHeadless = (run) => {
|
|
7348
|
+
headlessDepth++;
|
|
7349
|
+
try {
|
|
7350
|
+
run();
|
|
7351
|
+
} catch (error) {
|
|
7352
|
+
headlessErrors.clear();
|
|
7353
|
+
throw error;
|
|
7354
|
+
} finally {
|
|
7355
|
+
headlessDepth--;
|
|
7356
|
+
}
|
|
7357
|
+
if (headlessDepth > 0 || headlessErrors.size === 0) return;
|
|
7358
|
+
const errors = [...headlessErrors.values()];
|
|
7359
|
+
headlessErrors.clear();
|
|
7360
|
+
throw errors.length === 1 ? errors[0] : new AggregateError(errors, `${errors.length} systems threw in one frame`);
|
|
7361
|
+
};
|
|
7362
|
+
const scheduler = createScheduler({ maxFrameSeconds: 0.05, budgetMs: options.frameBudgetMs ?? 8, onError: reportFrameError });
|
|
7363
|
+
const audioQueue = audio?.deferredQueue?.();
|
|
7364
|
+
if (audioQueue) scheduler.budget.register(audioQueue, DEFERRED_PRIORITY[DEFERRED_QUEUE_ID.AUDIO_VOICES]);
|
|
7365
|
+
scheduler.budget.register(renderer.deferredQueue(), DEFERRED_PRIORITY[DEFERRED_QUEUE_ID.DESTRUCTION_EVENTS]);
|
|
7007
7366
|
const roomMetricIds = options.metrics === false ? [] : options.metrics ?? ROOM_METRICS;
|
|
7008
7367
|
const roomMetrics = createRoomMetrics({ gl, destruction: renderer, fluids: fluidView, effects: vfx.world, ids: roomMetricIds, blast: () => lastBlast });
|
|
7368
|
+
const MEMORY_CLEARS = 4;
|
|
7369
|
+
const memoryWatch = createMemoryWatch({
|
|
7370
|
+
clears: MEMORY_CLEARS,
|
|
7371
|
+
read: () => {
|
|
7372
|
+
const memory = gl.info?.memory;
|
|
7373
|
+
const heap = heapMB();
|
|
7374
|
+
return memory ? { geometries: memory.geometries, textures: memory.textures, ...heap === void 0 ? {} : { heapMB: heap } } : void 0;
|
|
7375
|
+
}
|
|
7376
|
+
});
|
|
7377
|
+
clearListeners.add(() => {
|
|
7378
|
+
const verdict = memoryWatch.cleared(activeBench ?? void 0);
|
|
7379
|
+
if (!verdict) return;
|
|
7380
|
+
if (!verdict.leaking) {
|
|
7381
|
+
ui?.alert(null);
|
|
7382
|
+
return;
|
|
7383
|
+
}
|
|
7384
|
+
const text = t("memory.leak", { geometries: verdict.grew.geometries, textures: verdict.grew.textures, clears: MEMORY_CLEARS });
|
|
7385
|
+
log.add({ channel: "event", type: "memory_leak", text, data: { ...verdict.after, grew: verdict.grew } });
|
|
7386
|
+
ui?.alert(`\u26A0 ${text}`);
|
|
7387
|
+
});
|
|
7009
7388
|
let qualityName = quality;
|
|
7010
7389
|
const syncContainers = (sdt) => {
|
|
7011
7390
|
const drinkingHeld = useItemPlayer !== null && (useItemName === "drink" || useItemName === "sip" || useItemName === "chug");
|
|
@@ -7031,7 +7410,6 @@ function createTestRoom(options) {
|
|
|
7031
7410
|
if (part && part.liters !== void 0 && Math.abs(part.liters - c.liters) > 1e-4) world.setLiters(host2.objectId, e.fluidPart, c.liters);
|
|
7032
7411
|
}
|
|
7033
7412
|
};
|
|
7034
|
-
let paused = false;
|
|
7035
7413
|
let walkPlan = null;
|
|
7036
7414
|
const stepWalk = (dt) => {
|
|
7037
7415
|
if (!walkPlan) return;
|
|
@@ -7040,8 +7418,7 @@ function createTestRoom(options) {
|
|
|
7040
7418
|
walkPlan.remaining -= dt;
|
|
7041
7419
|
if (walkPlan.remaining <= 1e-9) walkPlan = null;
|
|
7042
7420
|
};
|
|
7043
|
-
const
|
|
7044
|
-
if (paused) return;
|
|
7421
|
+
const simulateBeforeModules = (dt) => {
|
|
7045
7422
|
simTime += dt;
|
|
7046
7423
|
stepWalk(dt);
|
|
7047
7424
|
stepStrikes(dt);
|
|
@@ -7073,7 +7450,8 @@ function createTestRoom(options) {
|
|
|
7073
7450
|
const look = camera.getWorldDirection(listenerDirection);
|
|
7074
7451
|
audio.setListener({ position: [camera.position.x, camera.position.y, camera.position.z], yaw: Math.atan2(-look.x, -look.z) });
|
|
7075
7452
|
}
|
|
7076
|
-
|
|
7453
|
+
};
|
|
7454
|
+
const simulateAfterModules = (dt) => {
|
|
7077
7455
|
for (const tr of tracers) {
|
|
7078
7456
|
if (tr.life > 0) {
|
|
7079
7457
|
tr.life -= dt;
|
|
@@ -7082,7 +7460,7 @@ function createTestRoom(options) {
|
|
|
7082
7460
|
}
|
|
7083
7461
|
flush();
|
|
7084
7462
|
};
|
|
7085
|
-
const
|
|
7463
|
+
const updateInput = (dt, isPaused) => {
|
|
7086
7464
|
if (!ownsLoop) roomMetrics.frame(dt);
|
|
7087
7465
|
frameStamp++;
|
|
7088
7466
|
seating.update();
|
|
@@ -7091,23 +7469,25 @@ function createTestRoom(options) {
|
|
|
7091
7469
|
pushByKey = false;
|
|
7092
7470
|
push.end();
|
|
7093
7471
|
}
|
|
7094
|
-
push.step(
|
|
7095
|
-
doors.step(
|
|
7096
|
-
if (!
|
|
7097
|
-
if (!
|
|
7472
|
+
push.step(isPaused ? 0 : dt);
|
|
7473
|
+
doors.step(isPaused ? 0 : dt);
|
|
7474
|
+
if (!isPaused) kickLight(dt);
|
|
7475
|
+
if (!isPaused) pushByPlayer(dt);
|
|
7098
7476
|
else player.takeWish();
|
|
7099
|
-
stepAvatar(
|
|
7477
|
+
stepAvatar(isPaused ? 0 : dt);
|
|
7478
|
+
stepFeet(isPaused ? 0 : dt);
|
|
7100
7479
|
if (tiltKey || heldTilt > 0) {
|
|
7101
7480
|
heldTilt = Math.max(0, Math.min(2.4, heldTilt + (tiltKey ? 2.2 : -3) * dt));
|
|
7102
7481
|
}
|
|
7103
|
-
if ((useHeld || useFirst) && !
|
|
7482
|
+
if ((useHeld || useFirst) && !isPaused && !push.active) {
|
|
7104
7483
|
if (tool.def.category === "hand") {
|
|
7105
7484
|
if (useFirst) handUse();
|
|
7106
7485
|
} else if (useHeld) useTool(dt, !useFirst);
|
|
7107
7486
|
useFirst = false;
|
|
7108
7487
|
}
|
|
7109
|
-
if (!
|
|
7110
|
-
|
|
7488
|
+
if (!isPaused) followHeld(dt);
|
|
7489
|
+
};
|
|
7490
|
+
const updatePresent = (dt) => {
|
|
7111
7491
|
reachAvatar();
|
|
7112
7492
|
if (progress && simTime - progress.at > 0.25) progress = null;
|
|
7113
7493
|
hudAcc += dt;
|
|
@@ -7140,7 +7520,7 @@ function createTestRoom(options) {
|
|
|
7140
7520
|
inspect: inspectInfo(),
|
|
7141
7521
|
animations: animationsOf(),
|
|
7142
7522
|
locked: player.locked,
|
|
7143
|
-
timeScale,
|
|
7523
|
+
timeScale: scheduler.timeScale,
|
|
7144
7524
|
fast: fastForward,
|
|
7145
7525
|
tap: isTapOn(),
|
|
7146
7526
|
combo: combo ? comboOn : null,
|
|
@@ -7292,6 +7672,8 @@ function createTestRoom(options) {
|
|
|
7292
7672
|
const value = sampled[id];
|
|
7293
7673
|
return value === void 0 ? [] : [{ id, name: t(`metric.${id}`), text: num(value, id === "frame_ms" ? 1 : 0) }];
|
|
7294
7674
|
});
|
|
7675
|
+
const leak = memoryWatch.last;
|
|
7676
|
+
if (leak?.leaking) room2.push({ id: "memory_leak", name: "\u26A0", text: t("memory.leak_short", { geometries: leak.grew.geometries, textures: leak.grew.textures, clears: MEMORY_CLEARS }) });
|
|
7295
7677
|
const values = moduleMetrics();
|
|
7296
7678
|
const declared = (activeBench ? benches.get(activeBench)?.metrics : void 0) ?? [];
|
|
7297
7679
|
const module = declared.map((metric) => {
|
|
@@ -7307,7 +7689,6 @@ function createTestRoom(options) {
|
|
|
7307
7689
|
ui.metrics(metricRows());
|
|
7308
7690
|
};
|
|
7309
7691
|
const hostListeners = /* @__PURE__ */ new Set();
|
|
7310
|
-
const frameListeners = /* @__PURE__ */ new Set();
|
|
7311
7692
|
const listenerDirection = new THREE.Vector3();
|
|
7312
7693
|
const environment = options.environment ?? createEnvironmentBus();
|
|
7313
7694
|
const audioSink = audio ? createAudioSink(audio) : void 0;
|
|
@@ -7360,10 +7741,16 @@ function createTestRoom(options) {
|
|
|
7360
7741
|
scene,
|
|
7361
7742
|
camera,
|
|
7362
7743
|
locale,
|
|
7744
|
+
...graphics ? { graphics: graphics.host } : {},
|
|
7363
7745
|
...audioSink ? { audio: audioSink } : {},
|
|
7364
7746
|
environment,
|
|
7365
7747
|
groundAt,
|
|
7748
|
+
// the terrain's material where the floor is the top; a table or a counter answers undefined (the module's default)
|
|
7749
|
+
surfaceAt: (x, z) => Math.max(sceneryTopAt(x, z), restingTopAt(x, z)) > terrain.heightAt(x, z) ? void 0 : terrain.columnAt(x, z).material,
|
|
7366
7750
|
effects: vfx,
|
|
7751
|
+
particleLimit: () => options.debris?.particleLimit ?? DEFAULT_PARTICLE_LIMIT,
|
|
7752
|
+
lights,
|
|
7753
|
+
pools,
|
|
7367
7754
|
loading,
|
|
7368
7755
|
...options.blastShove ? { blastShove: options.blastShove } : {},
|
|
7369
7756
|
tool: () => ({
|
|
@@ -7415,9 +7802,9 @@ function createTestRoom(options) {
|
|
|
7415
7802
|
hostListeners.add(listener);
|
|
7416
7803
|
return () => hostListeners.delete(listener);
|
|
7417
7804
|
},
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
return () =>
|
|
7805
|
+
defer: (queue, queueOptions) => scheduler.budget.register(queue, queueOptions),
|
|
7806
|
+
onFrame(update, frameOptions) {
|
|
7807
|
+
return scheduler.add((ctx) => update(ctx.dt), { phase: frameOptions?.phase ?? "lateSimulate", order: frameOptions?.order ?? 0, id: `module.frame#${moduleFrameCount++}` });
|
|
7421
7808
|
},
|
|
7422
7809
|
holdFrame: () => bodyHoldFrame(),
|
|
7423
7810
|
reachFor(reach) {
|
|
@@ -7475,46 +7862,47 @@ function createTestRoom(options) {
|
|
|
7475
7862
|
return r.ok ? store : null;
|
|
7476
7863
|
};
|
|
7477
7864
|
const LIGHTS_CHECK_MS = 250;
|
|
7478
|
-
const
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7865
|
+
const materialsOf = (group) => {
|
|
7866
|
+
const found = /* @__PURE__ */ new Set();
|
|
7867
|
+
group.traverse((object) => {
|
|
7868
|
+
const material = object.material;
|
|
7869
|
+
for (const each of Array.isArray(material) ? material : material ? [material] : []) found.add(each);
|
|
7870
|
+
});
|
|
7871
|
+
return [...found];
|
|
7872
|
+
};
|
|
7873
|
+
const probeOf = (id, adapter) => ({
|
|
7874
|
+
id,
|
|
7875
|
+
build() {
|
|
7876
|
+
const probe = adapter.warmUpProbes();
|
|
7877
|
+
const group = probe.group;
|
|
7878
|
+
return { group, materials: materialsOf(group), dispose: () => probe.dispose() };
|
|
7484
7879
|
}
|
|
7880
|
+
});
|
|
7881
|
+
warmup.register(probeOf("destruction", renderer));
|
|
7882
|
+
warmup.register(probeOf("vfx", vfx));
|
|
7883
|
+
warmup.register(probeOf("fluids", fluidView));
|
|
7884
|
+
warmup.register(spriteProbes(THREE));
|
|
7885
|
+
const warmedEffect = (id) => {
|
|
7886
|
+
let handle = null;
|
|
7887
|
+
return {
|
|
7888
|
+
id: `vfx:${id}`,
|
|
7889
|
+
play() {
|
|
7890
|
+
handle = vfx.play({ effect: id, point: [0, 0.1, -4], direction: [0, 1, 0], normal: [0, 1, 0] });
|
|
7891
|
+
},
|
|
7892
|
+
step: (dt) => vfx.update(dt),
|
|
7893
|
+
stop() {
|
|
7894
|
+
if (handle !== null) vfx.stop(handle);
|
|
7895
|
+
vfx.world.clear();
|
|
7896
|
+
vfx.update(0);
|
|
7897
|
+
}
|
|
7898
|
+
};
|
|
7485
7899
|
};
|
|
7900
|
+
const catalogIds = vfx.catalog?.() ?? [];
|
|
7901
|
+
for (const id of [...EXPLOSIVE_EFFECT_IDS, ...catalogIds.filter((id2) => /fire|flame/i.test(id2)).slice(0, 1)]) if (catalogIds.includes(id)) warmup.registerEffect(warmedEffect(id));
|
|
7902
|
+
let warmedUp = false;
|
|
7486
7903
|
const warmUp = () => {
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
const spriteMap = new THREE.DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1);
|
|
7490
|
-
spriteMap.needsUpdate = true;
|
|
7491
|
-
const spriteProbes = [true, false].map((fog) => {
|
|
7492
|
-
const probe = new THREE.Sprite(new THREE.SpriteMaterial({ map: spriteMap, transparent: true, fog }));
|
|
7493
|
-
probe.frustumCulled = false;
|
|
7494
|
-
return probe;
|
|
7495
|
-
});
|
|
7496
|
-
groups.push(...spriteProbes);
|
|
7497
|
-
scene.add(...groups);
|
|
7498
|
-
gl.render(scene, camera);
|
|
7499
|
-
pinPrograms();
|
|
7500
|
-
scene.remove(...groups);
|
|
7501
|
-
for (const probe of probes) probe.dispose();
|
|
7502
|
-
for (const probe of spriteProbes) probe.material.dispose();
|
|
7503
|
-
spriteMap.dispose();
|
|
7504
|
-
};
|
|
7505
|
-
const warmUpEffects = () => {
|
|
7506
|
-
const ids = vfx.catalog?.() ?? [];
|
|
7507
|
-
const flames = ids.filter((id) => /fire|flame/i.test(id)).slice(0, 1);
|
|
7508
|
-
gl.compile(scene, camera);
|
|
7509
|
-
const handles = ["m67", ...flames].filter((id) => ids.includes(id)).map((effect) => vfx.play({ effect, point: [0, 0.1, -4], direction: [0, 1, 0], normal: [0, 1, 0] }));
|
|
7510
|
-
for (let i = 0; i < 3; i++) {
|
|
7511
|
-
vfx.update(0.02);
|
|
7512
|
-
gl.render(scene, camera);
|
|
7513
|
-
}
|
|
7514
|
-
for (const h of handles) if (h !== null) vfx.stop(h);
|
|
7515
|
-
vfx.world.clear();
|
|
7516
|
-
vfx.update(0);
|
|
7517
|
-
gl.render(scene, camera);
|
|
7904
|
+
warmup.warmUp(gl, scene, camera, { lights, fogStates: WARMED_FOG_STATES, materials: pools.materials.registeredMaterials?.() ?? [] });
|
|
7905
|
+
warmedUp = true;
|
|
7518
7906
|
};
|
|
7519
7907
|
const loadModule = (mod) => {
|
|
7520
7908
|
const report = { module: mod.id, items: [], scenarios: [], ok: true, problems: [] };
|
|
@@ -7626,24 +8014,13 @@ function createTestRoom(options) {
|
|
|
7626
8014
|
};
|
|
7627
8015
|
const allScenarios = () => [...extraScenarios];
|
|
7628
8016
|
let scenarioRunning = false;
|
|
7629
|
-
const step = (seconds) => {
|
|
7630
|
-
const dt = seconds > 6 ? 1 / 30 : FIXED_DT;
|
|
7631
|
-
const n = Math.max(1, Math.round(seconds / dt));
|
|
7632
|
-
for (let i = 0; i < n; i++) {
|
|
7633
|
-
seating.update();
|
|
7634
|
-
player.update(0);
|
|
7635
|
-
push.step(dt);
|
|
7636
|
-
doors.step(dt);
|
|
7637
|
-
followHeld(dt);
|
|
7638
|
-
stepSim(dt);
|
|
7639
|
-
}
|
|
7640
|
-
};
|
|
8017
|
+
const step = (seconds) => runHeadless(() => scheduler.advance(seconds, { dt: seconds > 6 ? 1 / 30 : FIXED_DT }));
|
|
7641
8018
|
const runScenarios = async (list) => {
|
|
7642
8019
|
const all = list ?? allScenarios();
|
|
7643
8020
|
const report = { passed: 0, failed: 0, results: [] };
|
|
7644
8021
|
scenarioRunning = true;
|
|
7645
|
-
const wasPaused = paused;
|
|
7646
|
-
|
|
8022
|
+
const wasPaused = scheduler.paused;
|
|
8023
|
+
scheduler.resume();
|
|
7647
8024
|
try {
|
|
7648
8025
|
for (const sc of all) {
|
|
7649
8026
|
hardReset();
|
|
@@ -7675,7 +8052,7 @@ function createTestRoom(options) {
|
|
|
7675
8052
|
}
|
|
7676
8053
|
} finally {
|
|
7677
8054
|
scenarioRunning = false;
|
|
7678
|
-
|
|
8055
|
+
if (wasPaused) scheduler.pause();
|
|
7679
8056
|
restoreScenery();
|
|
7680
8057
|
}
|
|
7681
8058
|
return report;
|
|
@@ -7807,19 +8184,25 @@ function createTestRoom(options) {
|
|
|
7807
8184
|
useItem,
|
|
7808
8185
|
use(hold = 0) {
|
|
7809
8186
|
if (tool.def.category === "hand") {
|
|
7810
|
-
handUse();
|
|
8187
|
+
const status2 = handUse();
|
|
7811
8188
|
flush();
|
|
7812
|
-
return;
|
|
8189
|
+
return status2;
|
|
7813
8190
|
}
|
|
7814
|
-
useTool(FIXED_DT, false);
|
|
8191
|
+
const status = useTool(FIXED_DT, false);
|
|
7815
8192
|
flush();
|
|
7816
8193
|
let t0 = 0;
|
|
7817
8194
|
while (t0 < hold) {
|
|
7818
8195
|
useTool(FIXED_DT, true);
|
|
7819
|
-
|
|
8196
|
+
directStep = true;
|
|
8197
|
+
try {
|
|
8198
|
+
runHeadless(() => scheduler.advance(FIXED_DT));
|
|
8199
|
+
} finally {
|
|
8200
|
+
directStep = false;
|
|
8201
|
+
}
|
|
7820
8202
|
t0 += FIXED_DT;
|
|
7821
8203
|
}
|
|
7822
8204
|
releaseTrigger();
|
|
8205
|
+
return status;
|
|
7823
8206
|
},
|
|
7824
8207
|
grab,
|
|
7825
8208
|
throwHeld,
|
|
@@ -7835,14 +8218,14 @@ function createTestRoom(options) {
|
|
|
7835
8218
|
spawned() {
|
|
7836
8219
|
return [...entries.values()].filter((e) => !e.arena).map((e) => e.so);
|
|
7837
8220
|
},
|
|
7838
|
-
update,
|
|
8221
|
+
update: (dt) => runHeadless(() => scheduler.frame(dt, { render: false, exact: true })),
|
|
7839
8222
|
waitUntilReady(maxSeconds = 5) {
|
|
7840
8223
|
let waited = 0;
|
|
7841
8224
|
while (tool.phase && waited < maxSeconds) {
|
|
7842
|
-
update(FIXED_DT);
|
|
8225
|
+
room.update(FIXED_DT);
|
|
7843
8226
|
waited += FIXED_DT;
|
|
7844
8227
|
}
|
|
7845
|
-
update(FIXED_DT);
|
|
8228
|
+
room.update(FIXED_DT);
|
|
7846
8229
|
return waited + FIXED_DT;
|
|
7847
8230
|
},
|
|
7848
8231
|
on(type, fn) {
|
|
@@ -7859,20 +8242,20 @@ function createTestRoom(options) {
|
|
|
7859
8242
|
blastOptions = { ...blastOptions, ...next };
|
|
7860
8243
|
},
|
|
7861
8244
|
pause() {
|
|
7862
|
-
|
|
8245
|
+
scheduler.pause();
|
|
7863
8246
|
},
|
|
7864
8247
|
resume() {
|
|
7865
|
-
|
|
8248
|
+
scheduler.resume();
|
|
7866
8249
|
},
|
|
7867
8250
|
get paused() {
|
|
7868
|
-
return paused;
|
|
8251
|
+
return scheduler.paused;
|
|
7869
8252
|
},
|
|
7870
8253
|
walk(to, seconds) {
|
|
7871
8254
|
walkPlan = seconds > 0 ? { x: to[0], z: to[2], remaining: seconds } : null;
|
|
7872
8255
|
if (!walkPlan) player.moveBy(to[0] - camera.position.x, to[2] - camera.position.z);
|
|
7873
8256
|
},
|
|
7874
8257
|
setSlowMotion(on) {
|
|
7875
|
-
timeScale = on ? 0.25 : 1;
|
|
8258
|
+
scheduler.timeScale = on ? 0.25 : 1;
|
|
7876
8259
|
},
|
|
7877
8260
|
setFastForward(on) {
|
|
7878
8261
|
fastForward = on;
|
|
@@ -7888,12 +8271,13 @@ function createTestRoom(options) {
|
|
|
7888
8271
|
modules: () => [...modules.values()].map((m) => ({ id: m.id, name: m.name, group: m.group, hasArea: typeof m.area === "function" })),
|
|
7889
8272
|
openAreaAsync: (area) => loading.busy(t("loading.open_area", { area: areaLabel(area) }), () => openArea(area)),
|
|
7890
8273
|
loading,
|
|
8274
|
+
graphics: graphics?.controller ?? null,
|
|
8275
|
+
graphicsScreen: graphics?.screen ?? null,
|
|
7891
8276
|
busy: (label, work, busyOptions) => loading.busy(label, work, busyOptions),
|
|
7892
8277
|
loadModule,
|
|
7893
8278
|
warmUp: () => {
|
|
7894
8279
|
audio?.warmUp();
|
|
7895
8280
|
warmUp();
|
|
7896
|
-
warmUpEffects();
|
|
7897
8281
|
},
|
|
7898
8282
|
animations: animationsOf,
|
|
7899
8283
|
playAnimation,
|
|
@@ -7965,6 +8349,9 @@ function createTestRoom(options) {
|
|
|
7965
8349
|
metrics() {
|
|
7966
8350
|
return { room: roomMetrics.sample(), module: moduleMetrics() };
|
|
7967
8351
|
},
|
|
8352
|
+
frameStats: () => scheduler.stats(),
|
|
8353
|
+
lights,
|
|
8354
|
+
webgl: gl,
|
|
7968
8355
|
dispose() {
|
|
7969
8356
|
destructionSound?.dispose();
|
|
7970
8357
|
if (ownAudio) audio?.dispose();
|
|
@@ -7972,13 +8359,19 @@ function createTestRoom(options) {
|
|
|
7972
8359
|
detachers.clear();
|
|
7973
8360
|
for (const store of benches.values()) store.dispose();
|
|
7974
8361
|
benches.clear();
|
|
7975
|
-
|
|
8362
|
+
stopLoop?.();
|
|
8363
|
+
disposePacer?.();
|
|
8364
|
+
scheduler.dispose();
|
|
7976
8365
|
window.removeEventListener("resize", onResize);
|
|
7977
8366
|
player.dispose();
|
|
7978
8367
|
ui?.dispose();
|
|
8368
|
+
graphics?.dispose();
|
|
7979
8369
|
loading.dispose();
|
|
7980
8370
|
renderer.dispose();
|
|
7981
8371
|
vfx.dispose();
|
|
8372
|
+
lights.dispose();
|
|
8373
|
+
pools.materials.dispose();
|
|
8374
|
+
pools.geometries.dispose();
|
|
7982
8375
|
fluidView.dispose();
|
|
7983
8376
|
filmBridge.dispose();
|
|
7984
8377
|
film.dispose();
|
|
@@ -8044,6 +8437,7 @@ function createTestRoom(options) {
|
|
|
8044
8437
|
}
|
|
8045
8438
|
},
|
|
8046
8439
|
quality: qualityName,
|
|
8440
|
+
...graphics?.screen ? { onGraphics: () => graphics.screen?.open() } : {},
|
|
8047
8441
|
scenarios: allScenarios,
|
|
8048
8442
|
onRun: async (sc) => {
|
|
8049
8443
|
await runScenarios([sc]);
|
|
@@ -8077,41 +8471,76 @@ function createTestRoom(options) {
|
|
|
8077
8471
|
ui.hud(hudState());
|
|
8078
8472
|
showBench();
|
|
8079
8473
|
}
|
|
8080
|
-
let
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8474
|
+
let directStep = false;
|
|
8475
|
+
scheduler.add(
|
|
8476
|
+
(ctx) => {
|
|
8477
|
+
if (directStep) return;
|
|
8478
|
+
if (!ctx.scripted) return updateInput(ctx.dt, ctx.paused);
|
|
8479
|
+
seating.update();
|
|
8480
|
+
player.update(0);
|
|
8481
|
+
push.step(ctx.dt);
|
|
8482
|
+
doors.step(ctx.dt);
|
|
8483
|
+
followHeld(ctx.dt);
|
|
8484
|
+
},
|
|
8485
|
+
{ phase: "input", id: "room.input" }
|
|
8486
|
+
);
|
|
8487
|
+
scheduler.add((ctx) => simulateBeforeModules(ctx.dt), { phase: "lateSimulate", order: -1e3, id: "room.simulate" });
|
|
8488
|
+
scheduler.add((ctx) => simulateAfterModules(ctx.dt), { phase: "lateSimulate", order: 1e3, id: "room.simulate-end" });
|
|
8489
|
+
scheduler.add(
|
|
8490
|
+
(ctx) => {
|
|
8491
|
+
if (ctx.simulated && !ctx.scripted) updatePresent(ctx.realDt);
|
|
8492
|
+
},
|
|
8493
|
+
{ phase: "present", id: "room.present" }
|
|
8494
|
+
);
|
|
8495
|
+
scheduler.add((ctx) => lights.present([camera.position.x, camera.position.y, camera.position.z], ctx.realDt), { phase: "present", order: 10, id: "room.lights" });
|
|
8496
|
+
let stopLoop;
|
|
8497
|
+
let disposePacer;
|
|
8498
|
+
if (ownRenderer) {
|
|
8084
8499
|
let nextLightsCheck = 0;
|
|
8085
|
-
let
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
return key2;
|
|
8092
|
-
};
|
|
8093
|
-
const loop = (now) => {
|
|
8094
|
-
raf = requestAnimationFrame(loop);
|
|
8095
|
-
if (now >= nextLightsCheck) {
|
|
8500
|
+
let warnedState = "";
|
|
8501
|
+
scheduler.add(
|
|
8502
|
+
(ctx) => {
|
|
8503
|
+
if (ctx.scripted) return;
|
|
8504
|
+
const now = performance.now();
|
|
8505
|
+
if (now < nextLightsCheck) return;
|
|
8096
8506
|
nextLightsCheck = now + LIGHTS_CHECK_MS;
|
|
8097
|
-
const
|
|
8098
|
-
if (
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
}
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8507
|
+
const state = lights.check(gl, scene);
|
|
8508
|
+
if (warmedUp && (state.ok || state.actual === warnedState)) return;
|
|
8509
|
+
warnedState = state.actual;
|
|
8510
|
+
warmUp();
|
|
8511
|
+
},
|
|
8512
|
+
{ phase: "render", order: -1e3, id: "room.lights-check" }
|
|
8513
|
+
);
|
|
8514
|
+
const pacer = createFramePacer(typeof gl.getContext === "function" ? gl.getContext() : null, options.framesInFlight === void 0 ? {} : { framesInFlight: options.framesInFlight });
|
|
8515
|
+
disposePacer = () => pacer.dispose();
|
|
8516
|
+
scheduler.add(
|
|
8517
|
+
(ctx) => {
|
|
8518
|
+
if (!ctx.scripted && !pacer.ready()) return;
|
|
8519
|
+
for (const batcher of batchers.values()) batcher.update();
|
|
8520
|
+
const restoreCamera = room.applyCamera();
|
|
8521
|
+
graphics?.beforeRender(camera);
|
|
8522
|
+
gl.render(scene, debugView ?? camera);
|
|
8523
|
+
graphics?.afterRender();
|
|
8524
|
+
restoreCamera();
|
|
8525
|
+
pacer.submitted();
|
|
8526
|
+
roomMetrics.rendered();
|
|
8527
|
+
if (!ctx.scripted) graphics?.frameEnd();
|
|
8528
|
+
},
|
|
8529
|
+
{ phase: "render", id: "room.render" }
|
|
8530
|
+
);
|
|
8531
|
+
try {
|
|
8532
|
+
stopLoop = startLoop(scheduler, {
|
|
8533
|
+
onFrame: (realSeconds) => {
|
|
8534
|
+
graphics?.frameStart();
|
|
8535
|
+
roomMetrics.frame(realSeconds);
|
|
8536
|
+
},
|
|
8537
|
+
skipSimulation: () => scenarioRunning,
|
|
8538
|
+
// the player's frame cap (Gráficos → Limite de FPS)
|
|
8539
|
+
...graphics ? { minFrameMs: () => graphics.controller.minFrameMs() } : {}
|
|
8540
|
+
});
|
|
8541
|
+
ownsLoop = true;
|
|
8542
|
+
} catch {
|
|
8543
|
+
}
|
|
8115
8544
|
}
|
|
8116
8545
|
if (options.initial) {
|
|
8117
8546
|
for (const [i, id] of options.initial.entries()) spawn(id, [(i - (options.initial.length - 1) / 2) * 1.3, 0, 0.5]);
|