@adatechnology/game-test-room 0.19.1 → 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 +86 -7
- package/dist/index.global.js +4970 -860
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +611 -142
- 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
|
}
|
|
@@ -2954,7 +3167,7 @@ function createRoomDoors(options) {
|
|
|
2954
3167
|
// src/room.ts
|
|
2955
3168
|
import { releaseObject3D as releaseObject3D2 } from "@adatechnology/game-shapes-three";
|
|
2956
3169
|
import { createTerrain, createTerrainImpl, DEFAULT_PROFILE } from "@adatechnology/game-terrain";
|
|
2957
|
-
import { createTerrainRenderer } from "@adatechnology/game-terrain-three";
|
|
3170
|
+
import { createTerrainRenderer, terrainProfileName } from "@adatechnology/game-terrain-three";
|
|
2958
3171
|
|
|
2959
3172
|
// src/avatar.ts
|
|
2960
3173
|
var DEFAULT_STRIKE_CLIPS = { hand: "punch_right", fists: "punch_right", shove: "shove_two_hands", kick: "front_kick" };
|
|
@@ -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",
|
|
@@ -3325,6 +3546,8 @@ var en = {
|
|
|
3325
3546
|
"ins.damage": "Marks",
|
|
3326
3547
|
"ins.damage_counts": "{dents}, {holes}, {cracks}",
|
|
3327
3548
|
"ins.mass": "Mass",
|
|
3549
|
+
"ins.dug": "Dug",
|
|
3550
|
+
"ins.dig_limit": "Ground left to dig",
|
|
3328
3551
|
"ins.load": "Load",
|
|
3329
3552
|
"ins.stress": "Stress",
|
|
3330
3553
|
"ins.joints": "Joints",
|
|
@@ -3469,6 +3692,11 @@ var ptBR = {
|
|
|
3469
3692
|
"metric.fragments": "Fragmentos",
|
|
3470
3693
|
"metric.particles": "Part\xEDculas",
|
|
3471
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).",
|
|
3472
3700
|
"act.spawn": "Criou {name} em x {x} \xB7 z {z}",
|
|
3473
3701
|
"act.clear": "Limpou a sala",
|
|
3474
3702
|
"act.reset": "Consertou todos os objetos",
|
|
@@ -3515,8 +3743,11 @@ var ptBR = {
|
|
|
3515
3743
|
"act.throw": "Arremessou {name} a {speed} m/s",
|
|
3516
3744
|
"ground.name": "Ch\xE3o",
|
|
3517
3745
|
"act.landing": "{name} caiu a {speed} m/s: {energy} J",
|
|
3746
|
+
"act.player_land": "Voc\xEA aterrissou a {speed} m/s",
|
|
3518
3747
|
"act.bench_failed": "{name} falhou: {error}",
|
|
3519
3748
|
"loading.working": "Trabalhando\u2026",
|
|
3749
|
+
"loading.graphics": "Calibrando os gr\xE1ficos\u2026",
|
|
3750
|
+
"ui.graphics": "Gr\xE1ficos",
|
|
3520
3751
|
"loading.open_area": "Abrindo {area}\u2026",
|
|
3521
3752
|
"loading.spawn": "Montando {name}\u2026",
|
|
3522
3753
|
"act.loosened": "{name} \xE9 pesado mas estava solto: a explos\xE3o o moveu",
|
|
@@ -3673,6 +3904,8 @@ var ptBR = {
|
|
|
3673
3904
|
"ins.damage": "Marcas",
|
|
3674
3905
|
"ins.damage_counts": "{dents}, {holes}, {cracks}",
|
|
3675
3906
|
"ins.mass": "Massa",
|
|
3907
|
+
"ins.dug": "Escavado",
|
|
3908
|
+
"ins.dig_limit": "Ch\xE3o at\xE9 o limite",
|
|
3676
3909
|
"ins.load": "Carga",
|
|
3677
3910
|
"ins.stress": "Tens\xE3o",
|
|
3678
3911
|
"ins.joints": "Juntas",
|
|
@@ -4106,6 +4339,7 @@ var CSS = `
|
|
|
4106
4339
|
.tr-top{position:absolute;top:8px;left:50%;transform:translateX(-50%);text-align:center;max-width:46vw}
|
|
4107
4340
|
.tr-tool{display:inline-block;background:rgba(10,12,16,.72);padding:4px 12px;border-radius:6px;font-size:14px;font-weight:600}
|
|
4108
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)}
|
|
4109
4343
|
.tr-status{margin-top:4px;min-height:16px;color:#ffd23f;text-shadow:0 1px 2px #000}
|
|
4110
4344
|
.tr-prog{margin:4px auto 0;width:260px;background:rgba(10,12,16,.72);border-radius:6px;padding:4px 8px;display:none}
|
|
4111
4345
|
.tr-prog b{display:block;font-weight:500;margin-bottom:3px}
|
|
@@ -4224,13 +4458,14 @@ function createUi(o) {
|
|
|
4224
4458
|
const top = mk3("div", "tr-top");
|
|
4225
4459
|
const toolBox = mk3("div", "tr-tool");
|
|
4226
4460
|
const status = mk3("div", "tr-status");
|
|
4461
|
+
const alertBox = mk3("div", "tr-alert");
|
|
4227
4462
|
const prog = mk3("div", "tr-prog");
|
|
4228
4463
|
const progLabel = mk3("b");
|
|
4229
4464
|
const progBar = mk3("div", "tr-bar");
|
|
4230
4465
|
const progFill = mk3("i");
|
|
4231
4466
|
progBar.appendChild(progFill);
|
|
4232
4467
|
prog.append(progLabel, progBar);
|
|
4233
|
-
top.append(toolBox, status, prog);
|
|
4468
|
+
top.append(toolBox, alertBox, status, prog);
|
|
4234
4469
|
const bottom = mk3("div", "tr-bottom");
|
|
4235
4470
|
const held = mk3("div", "tr-held");
|
|
4236
4471
|
const hotbar = mk3("div", "tr-hotbar");
|
|
@@ -4457,17 +4692,22 @@ function createUi(o) {
|
|
|
4457
4692
|
fullscreenBtn.title = t("ui.fullscreen.hint");
|
|
4458
4693
|
const blastBtns = ["shelter", "fragmentHits", "confinement"].map((key2) => ({ key: key2, b: ctl(t(`ui.blast.${key2}`), () => o.onBlast(key2)) }));
|
|
4459
4694
|
for (const { key: key2, b } of blastBtns) b.title = t(`ui.blast.${key2}.hint`);
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
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);
|
|
4465
4710
|
}
|
|
4466
|
-
qSel.value = o.quality;
|
|
4467
|
-
qSel.onchange = () => o.onQuality(qSel.value);
|
|
4468
|
-
const qLabel = mk3("label", void 0, t("ui.quality") + " ");
|
|
4469
|
-
qLabel.appendChild(qSel);
|
|
4470
|
-
controls.appendChild(qLabel);
|
|
4471
4711
|
const count = mk3("span");
|
|
4472
4712
|
controls.appendChild(count);
|
|
4473
4713
|
cat.appendChild(controls);
|
|
@@ -4560,6 +4800,15 @@ function createUi(o) {
|
|
|
4560
4800
|
insp.appendChild(mk3("div", void 0, t("ui.aim_at_something")));
|
|
4561
4801
|
return;
|
|
4562
4802
|
}
|
|
4803
|
+
if (i.ground) {
|
|
4804
|
+
const g = i.ground;
|
|
4805
|
+
insp.appendChild(mk3("div", "t", `${i.object} \u203A ${g.surface}`));
|
|
4806
|
+
insp.appendChild(row(t("ins.material"), g.material));
|
|
4807
|
+
insp.appendChild(row(t("ins.dug"), `${num(g.dug * 100, 0)} cm`));
|
|
4808
|
+
insp.appendChild(row(t("ins.dig_limit"), `${num(g.remaining, 2)} m`));
|
|
4809
|
+
if (g.scorch > 0.01) insp.appendChild(row(t("ins.char"), `${num(g.scorch * 100, 0)} %`));
|
|
4810
|
+
return;
|
|
4811
|
+
}
|
|
4563
4812
|
if (i.thumbnail) insp.appendChild(thumb(i.thumbnail, i.thumbnailCredit));
|
|
4564
4813
|
insp.appendChild(mk3("div", "t", `${i.object} \u203A ${i.part}`));
|
|
4565
4814
|
insp.appendChild(row(t("ins.material"), i.material));
|
|
@@ -4684,6 +4933,10 @@ function createUi(o) {
|
|
|
4684
4933
|
renderInspector(s.inspect);
|
|
4685
4934
|
}
|
|
4686
4935
|
},
|
|
4936
|
+
alert(text) {
|
|
4937
|
+
alertBox.textContent = text ?? "";
|
|
4938
|
+
alertBox.style.display = text ? "block" : "none";
|
|
4939
|
+
},
|
|
4687
4940
|
toast(text) {
|
|
4688
4941
|
status.textContent = text;
|
|
4689
4942
|
if (toastTimer) clearTimeout(toastTimer);
|
|
@@ -4720,7 +4973,14 @@ function createUi(o) {
|
|
|
4720
4973
|
|
|
4721
4974
|
// src/room.ts
|
|
4722
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 };
|
|
4723
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/;
|
|
4724
4984
|
var STEP_UP = 0.45;
|
|
4725
4985
|
var BODY_HEIGHT2 = 1.75;
|
|
4726
4986
|
var KICK_MASS = 6;
|
|
@@ -4788,6 +5048,17 @@ function createTestRoom(options) {
|
|
|
4788
5048
|
const arenaSize = options.arenaSize ?? 14;
|
|
4789
5049
|
const arena = createArena(THREE, arenaSize, scene, options.scenery, options.tap);
|
|
4790
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;
|
|
4791
5062
|
const ownWorld = !options.world;
|
|
4792
5063
|
const world = options.world ?? createDestruction({ seed: 1, ...options.worldOptions, materials: [...options.worldOptions?.materials ?? []] });
|
|
4793
5064
|
const pristineWorld = ownWorld ? world.serialize() : null;
|
|
@@ -4833,12 +5104,17 @@ function createTestRoom(options) {
|
|
|
4833
5104
|
const ownAudio = options.audio === void 0;
|
|
4834
5105
|
const audio = options.audio === false ? null : options.audio ?? createAudioEngine({ deferredStart: {} });
|
|
4835
5106
|
const destructionSound = audio ? createDestructionSound(audio) : null;
|
|
5107
|
+
const bodySounds = createBodySounds(audio);
|
|
4836
5108
|
const playSound = (key2, point, volume) => {
|
|
4837
5109
|
destructionSound?.onSound(key2, point, volume);
|
|
4838
5110
|
options.onSound?.(key2, point, volume);
|
|
4839
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 }) };
|
|
4840
5115
|
const vfx = createVfxRenderer(THREE, {
|
|
4841
5116
|
scene,
|
|
5117
|
+
lights,
|
|
4842
5118
|
camera,
|
|
4843
5119
|
overlay: container,
|
|
4844
5120
|
quality,
|
|
@@ -4992,7 +5268,7 @@ function createTestRoom(options) {
|
|
|
4992
5268
|
});
|
|
4993
5269
|
let pending = [];
|
|
4994
5270
|
const eventListeners = /* @__PURE__ */ new Set();
|
|
4995
|
-
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() };
|
|
4996
5272
|
const fire = (type, data) => {
|
|
4997
5273
|
for (const fn of listeners[type] ?? []) fn(data);
|
|
4998
5274
|
};
|
|
@@ -5129,6 +5405,14 @@ function createTestRoom(options) {
|
|
|
5129
5405
|
floorAt: terrain.heightAt,
|
|
5130
5406
|
start: arena.playerStart,
|
|
5131
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
|
+
},
|
|
5132
5416
|
hooks: {
|
|
5133
5417
|
primaryDown: () => {
|
|
5134
5418
|
useHeld = true;
|
|
@@ -5305,12 +5589,23 @@ function createTestRoom(options) {
|
|
|
5305
5589
|
for (const p of st?.parts ?? []) if (p.status !== "broken" && p.status !== "melted" && p.status !== "ashes") m += p.mass;
|
|
5306
5590
|
return m;
|
|
5307
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
|
+
};
|
|
5308
5602
|
const registerEntry = (objectId, itemId, root, spec, name, parts, item, arenaObject, movable) => {
|
|
5309
5603
|
specs.set(objectId, spec);
|
|
5310
5604
|
renderer.bind(objectId, root, parts ? { parts } : void 0);
|
|
5311
5605
|
const so = { objectId, itemId, root, movable };
|
|
5312
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] };
|
|
5313
5607
|
entries.set(objectId, e);
|
|
5608
|
+
batchObject(objectId, root);
|
|
5314
5609
|
if (movable) {
|
|
5315
5610
|
e.piece = renderer.makePiece(objectId, { mass: Math.max(0.2, massOfObject(objectId)) });
|
|
5316
5611
|
rendererPieces.set(objectId, e.piece);
|
|
@@ -5578,6 +5873,8 @@ function createTestRoom(options) {
|
|
|
5578
5873
|
dropTree(e.so.root);
|
|
5579
5874
|
dropTree(inner);
|
|
5580
5875
|
entries.delete(id);
|
|
5876
|
+
batchers.get(id)?.dispose();
|
|
5877
|
+
batchers.delete(id);
|
|
5581
5878
|
doors.forget(id);
|
|
5582
5879
|
rendererPieces.delete(id);
|
|
5583
5880
|
specs.delete(id);
|
|
@@ -5676,7 +5973,7 @@ function createTestRoom(options) {
|
|
|
5676
5973
|
lastImpact.clear();
|
|
5677
5974
|
addArena();
|
|
5678
5975
|
standFootprints();
|
|
5679
|
-
timeScale = 1;
|
|
5976
|
+
scheduler.timeScale = 1;
|
|
5680
5977
|
fastForward = false;
|
|
5681
5978
|
walkPlan = null;
|
|
5682
5979
|
};
|
|
@@ -5877,6 +6174,28 @@ function createTestRoom(options) {
|
|
|
5877
6174
|
}
|
|
5878
6175
|
};
|
|
5879
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
|
+
};
|
|
5880
6199
|
let rng = 12345;
|
|
5881
6200
|
const rnd = () => {
|
|
5882
6201
|
rng = rng * 1664525 + 1013904223 >>> 0;
|
|
@@ -5995,6 +6314,12 @@ function createTestRoom(options) {
|
|
|
5995
6314
|
ui?.toast(msg);
|
|
5996
6315
|
};
|
|
5997
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
|
+
};
|
|
5998
6323
|
const hitWhere = (r) => {
|
|
5999
6324
|
const p = world.part(r.object, r.part);
|
|
6000
6325
|
return `${nameOf(r.object)} \u203A ${r.part}${p ? ` (${dStrings.materialNames[p.material] ?? p.material})` : ""}`;
|
|
@@ -6004,6 +6329,7 @@ function createTestRoom(options) {
|
|
|
6004
6329
|
renderer.impulse(objectId, [dir[0] * momentum, dir[1] * momentum, dir[2] * momentum], point);
|
|
6005
6330
|
};
|
|
6006
6331
|
const shotRay = new THREE.Raycaster();
|
|
6332
|
+
shotRay.layers.enableAll();
|
|
6007
6333
|
const LIQUID_SLOWING = 0.25;
|
|
6008
6334
|
const SHOT_GRIP = 0.3;
|
|
6009
6335
|
const shotPush = (s, objectId, results, fallback) => {
|
|
@@ -6174,6 +6500,8 @@ function createTestRoom(options) {
|
|
|
6174
6500
|
return;
|
|
6175
6501
|
}
|
|
6176
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);
|
|
6177
6505
|
pushPiece(r.object, u.direction, 2 * u.energy / 6, r.point);
|
|
6178
6506
|
action("melee", t("act.melee.hit", { weapon: w, energy: num(u.energy, 0), where: hitWhere(r), dist: num(r.distance, 1) }), {
|
|
6179
6507
|
objectId: r.object,
|
|
@@ -6214,6 +6542,10 @@ function createTestRoom(options) {
|
|
|
6214
6542
|
comboNext = (stepIndex + 1) % combo.steps.length;
|
|
6215
6543
|
comboAt = simTime;
|
|
6216
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();
|
|
6217
6549
|
if (clip === void 0 || seconds === null) {
|
|
6218
6550
|
if (leg) {
|
|
6219
6551
|
legAim();
|
|
@@ -6228,6 +6560,7 @@ function createTestRoom(options) {
|
|
|
6228
6560
|
strikes.push({
|
|
6229
6561
|
key: id,
|
|
6230
6562
|
remaining: seconds,
|
|
6563
|
+
...swing && seconds > lead ? { swing, lead } : {},
|
|
6231
6564
|
land: () => {
|
|
6232
6565
|
if (leg) legAim();
|
|
6233
6566
|
else aim();
|
|
@@ -6237,7 +6570,13 @@ function createTestRoom(options) {
|
|
|
6237
6570
|
};
|
|
6238
6571
|
const stepStrikes = (dt) => {
|
|
6239
6572
|
if (strikes.length === 0) return;
|
|
6240
|
-
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
|
+
}
|
|
6241
6580
|
const landed = strikes.filter((strike) => strike.remaining <= 0);
|
|
6242
6581
|
strikes = strikes.filter((strike) => strike.remaining > 0);
|
|
6243
6582
|
for (const strike of landed) strike.land();
|
|
@@ -6351,26 +6690,27 @@ function createTestRoom(options) {
|
|
|
6351
6690
|
aim();
|
|
6352
6691
|
const cat = tool.def.category;
|
|
6353
6692
|
if (cat === "hand") {
|
|
6354
|
-
if (strikes.some((strike) => strike.key === "hand" || strike.key === "fists")) return;
|
|
6693
|
+
if (strikes.some((strike) => strike.key === "hand" || strike.key === "fists")) return "cooldown";
|
|
6355
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");
|
|
6356
|
-
return;
|
|
6695
|
+
return "ok";
|
|
6357
6696
|
}
|
|
6358
|
-
if (strikes.some((strike) => strike.key === toolId)) return;
|
|
6697
|
+
if (strikes.some((strike) => strike.key === toolId)) return "cooldown";
|
|
6359
6698
|
const res = tool.trigger([aimO[0], aimO[1], aimO[2]], [aimD[0], aimD[1], aimD[2]], dt, held, "player");
|
|
6360
6699
|
flushEvents(toolId, tool, res.recoil);
|
|
6361
6700
|
if (res.status === "empty") {
|
|
6362
6701
|
if (!tool.reloading && tool.reserve > 0) startReload();
|
|
6363
6702
|
say(t(tool.reloading ? "hud.reloading" : "hud.empty"));
|
|
6364
|
-
return;
|
|
6703
|
+
return res.status;
|
|
6365
6704
|
}
|
|
6366
6705
|
if (res.status !== "ok") {
|
|
6367
6706
|
if (res.status === "reloading") say(t("hud.reloading"));
|
|
6368
6707
|
else if (res.status === "no_fuel") say(t("hud.no_fuel"));
|
|
6369
6708
|
else if (res.status === "fault" && res.fault) say(t("hud.fault", { fault: t(`fault.${res.fault}`) }));
|
|
6370
6709
|
else if (res.status === "safe") say(t("hud.safe"));
|
|
6371
|
-
return;
|
|
6710
|
+
return res.status;
|
|
6372
6711
|
}
|
|
6373
6712
|
applyUses(res, dt);
|
|
6713
|
+
return res.status;
|
|
6374
6714
|
};
|
|
6375
6715
|
const applyUses = (res, dt) => {
|
|
6376
6716
|
if (res.shots.length) fireShots(res.shots, res.recoil);
|
|
@@ -6472,8 +6812,9 @@ function createTestRoom(options) {
|
|
|
6472
6812
|
heldTilt = 0;
|
|
6473
6813
|
};
|
|
6474
6814
|
const handUse = () => {
|
|
6475
|
-
if (heldPiece)
|
|
6476
|
-
|
|
6815
|
+
if (!heldPiece) return useTool(FIXED_DT, false);
|
|
6816
|
+
grab();
|
|
6817
|
+
return "ok";
|
|
6477
6818
|
};
|
|
6478
6819
|
const grab = () => {
|
|
6479
6820
|
if (push.active) return false;
|
|
@@ -6918,9 +7259,36 @@ function createTestRoom(options) {
|
|
|
6918
7259
|
if (!leaf) return void 0;
|
|
6919
7260
|
return t(leaf.locked ? "hud.door_hint_locked" : leaf.open ? "hud.door_hint_close" : "hud.door_hint_open");
|
|
6920
7261
|
};
|
|
7262
|
+
const groundInspect = (r) => {
|
|
7263
|
+
const onFloor = terrain.raycast(aimO, aimD, r.distance + 3);
|
|
7264
|
+
const [x, , z] = onFloor?.point ?? r.point;
|
|
7265
|
+
const column = terrain.columnAt(x, z);
|
|
7266
|
+
const materialName = S.text[`material.${column.material}`] ?? dStrings.materialNames[column.material] ?? column.material;
|
|
7267
|
+
return {
|
|
7268
|
+
object: t("ground.name"),
|
|
7269
|
+
objectId: GROUND_ID,
|
|
7270
|
+
part: column.profile,
|
|
7271
|
+
material: materialName,
|
|
7272
|
+
status: "",
|
|
7273
|
+
hp: 0,
|
|
7274
|
+
maxHp: 0,
|
|
7275
|
+
temperature: 0,
|
|
7276
|
+
burning: false,
|
|
7277
|
+
char: column.scorch,
|
|
7278
|
+
melt: 0,
|
|
7279
|
+
dents: 0,
|
|
7280
|
+
holes: 0,
|
|
7281
|
+
cracks: 0,
|
|
7282
|
+
mass: 0,
|
|
7283
|
+
joints: [],
|
|
7284
|
+
distance: onFloor?.distance ?? r.distance,
|
|
7285
|
+
ground: { surface: terrainProfileName(column.profile, locale), material: materialName, dug: column.depth, remaining: column.remaining, scorch: column.scorch }
|
|
7286
|
+
};
|
|
7287
|
+
};
|
|
6921
7288
|
const inspectInfo = () => {
|
|
6922
7289
|
const r = inspectTarget();
|
|
6923
7290
|
if (!r) return null;
|
|
7291
|
+
if (r.object === GROUND_ID) return groundInspect(r);
|
|
6924
7292
|
const st = world.state(r.object);
|
|
6925
7293
|
const p = world.part(r.object, r.part);
|
|
6926
7294
|
if (!st || !p) return null;
|
|
@@ -6959,13 +7327,64 @@ function createTestRoom(options) {
|
|
|
6959
7327
|
distance: r.distance
|
|
6960
7328
|
};
|
|
6961
7329
|
};
|
|
6962
|
-
let timeScale = 1;
|
|
6963
7330
|
let fastForward = false;
|
|
6964
7331
|
let hudAcc = 1;
|
|
6965
7332
|
let metricsAcc = 0;
|
|
6966
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]);
|
|
6967
7366
|
const roomMetricIds = options.metrics === false ? [] : options.metrics ?? ROOM_METRICS;
|
|
6968
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
|
+
});
|
|
6969
7388
|
let qualityName = quality;
|
|
6970
7389
|
const syncContainers = (sdt) => {
|
|
6971
7390
|
const drinkingHeld = useItemPlayer !== null && (useItemName === "drink" || useItemName === "sip" || useItemName === "chug");
|
|
@@ -6991,7 +7410,6 @@ function createTestRoom(options) {
|
|
|
6991
7410
|
if (part && part.liters !== void 0 && Math.abs(part.liters - c.liters) > 1e-4) world.setLiters(host2.objectId, e.fluidPart, c.liters);
|
|
6992
7411
|
}
|
|
6993
7412
|
};
|
|
6994
|
-
let paused = false;
|
|
6995
7413
|
let walkPlan = null;
|
|
6996
7414
|
const stepWalk = (dt) => {
|
|
6997
7415
|
if (!walkPlan) return;
|
|
@@ -7000,8 +7418,7 @@ function createTestRoom(options) {
|
|
|
7000
7418
|
walkPlan.remaining -= dt;
|
|
7001
7419
|
if (walkPlan.remaining <= 1e-9) walkPlan = null;
|
|
7002
7420
|
};
|
|
7003
|
-
const
|
|
7004
|
-
if (paused) return;
|
|
7421
|
+
const simulateBeforeModules = (dt) => {
|
|
7005
7422
|
simTime += dt;
|
|
7006
7423
|
stepWalk(dt);
|
|
7007
7424
|
stepStrikes(dt);
|
|
@@ -7033,7 +7450,8 @@ function createTestRoom(options) {
|
|
|
7033
7450
|
const look = camera.getWorldDirection(listenerDirection);
|
|
7034
7451
|
audio.setListener({ position: [camera.position.x, camera.position.y, camera.position.z], yaw: Math.atan2(-look.x, -look.z) });
|
|
7035
7452
|
}
|
|
7036
|
-
|
|
7453
|
+
};
|
|
7454
|
+
const simulateAfterModules = (dt) => {
|
|
7037
7455
|
for (const tr of tracers) {
|
|
7038
7456
|
if (tr.life > 0) {
|
|
7039
7457
|
tr.life -= dt;
|
|
@@ -7042,7 +7460,7 @@ function createTestRoom(options) {
|
|
|
7042
7460
|
}
|
|
7043
7461
|
flush();
|
|
7044
7462
|
};
|
|
7045
|
-
const
|
|
7463
|
+
const updateInput = (dt, isPaused) => {
|
|
7046
7464
|
if (!ownsLoop) roomMetrics.frame(dt);
|
|
7047
7465
|
frameStamp++;
|
|
7048
7466
|
seating.update();
|
|
@@ -7051,23 +7469,25 @@ function createTestRoom(options) {
|
|
|
7051
7469
|
pushByKey = false;
|
|
7052
7470
|
push.end();
|
|
7053
7471
|
}
|
|
7054
|
-
push.step(
|
|
7055
|
-
doors.step(
|
|
7056
|
-
if (!
|
|
7057
|
-
if (!
|
|
7472
|
+
push.step(isPaused ? 0 : dt);
|
|
7473
|
+
doors.step(isPaused ? 0 : dt);
|
|
7474
|
+
if (!isPaused) kickLight(dt);
|
|
7475
|
+
if (!isPaused) pushByPlayer(dt);
|
|
7058
7476
|
else player.takeWish();
|
|
7059
|
-
stepAvatar(
|
|
7477
|
+
stepAvatar(isPaused ? 0 : dt);
|
|
7478
|
+
stepFeet(isPaused ? 0 : dt);
|
|
7060
7479
|
if (tiltKey || heldTilt > 0) {
|
|
7061
7480
|
heldTilt = Math.max(0, Math.min(2.4, heldTilt + (tiltKey ? 2.2 : -3) * dt));
|
|
7062
7481
|
}
|
|
7063
|
-
if ((useHeld || useFirst) && !
|
|
7482
|
+
if ((useHeld || useFirst) && !isPaused && !push.active) {
|
|
7064
7483
|
if (tool.def.category === "hand") {
|
|
7065
7484
|
if (useFirst) handUse();
|
|
7066
7485
|
} else if (useHeld) useTool(dt, !useFirst);
|
|
7067
7486
|
useFirst = false;
|
|
7068
7487
|
}
|
|
7069
|
-
if (!
|
|
7070
|
-
|
|
7488
|
+
if (!isPaused) followHeld(dt);
|
|
7489
|
+
};
|
|
7490
|
+
const updatePresent = (dt) => {
|
|
7071
7491
|
reachAvatar();
|
|
7072
7492
|
if (progress && simTime - progress.at > 0.25) progress = null;
|
|
7073
7493
|
hudAcc += dt;
|
|
@@ -7100,7 +7520,7 @@ function createTestRoom(options) {
|
|
|
7100
7520
|
inspect: inspectInfo(),
|
|
7101
7521
|
animations: animationsOf(),
|
|
7102
7522
|
locked: player.locked,
|
|
7103
|
-
timeScale,
|
|
7523
|
+
timeScale: scheduler.timeScale,
|
|
7104
7524
|
fast: fastForward,
|
|
7105
7525
|
tap: isTapOn(),
|
|
7106
7526
|
combo: combo ? comboOn : null,
|
|
@@ -7252,6 +7672,8 @@ function createTestRoom(options) {
|
|
|
7252
7672
|
const value = sampled[id];
|
|
7253
7673
|
return value === void 0 ? [] : [{ id, name: t(`metric.${id}`), text: num(value, id === "frame_ms" ? 1 : 0) }];
|
|
7254
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 }) });
|
|
7255
7677
|
const values = moduleMetrics();
|
|
7256
7678
|
const declared = (activeBench ? benches.get(activeBench)?.metrics : void 0) ?? [];
|
|
7257
7679
|
const module = declared.map((metric) => {
|
|
@@ -7267,7 +7689,6 @@ function createTestRoom(options) {
|
|
|
7267
7689
|
ui.metrics(metricRows());
|
|
7268
7690
|
};
|
|
7269
7691
|
const hostListeners = /* @__PURE__ */ new Set();
|
|
7270
|
-
const frameListeners = /* @__PURE__ */ new Set();
|
|
7271
7692
|
const listenerDirection = new THREE.Vector3();
|
|
7272
7693
|
const environment = options.environment ?? createEnvironmentBus();
|
|
7273
7694
|
const audioSink = audio ? createAudioSink(audio) : void 0;
|
|
@@ -7320,10 +7741,16 @@ function createTestRoom(options) {
|
|
|
7320
7741
|
scene,
|
|
7321
7742
|
camera,
|
|
7322
7743
|
locale,
|
|
7744
|
+
...graphics ? { graphics: graphics.host } : {},
|
|
7323
7745
|
...audioSink ? { audio: audioSink } : {},
|
|
7324
7746
|
environment,
|
|
7325
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,
|
|
7326
7750
|
effects: vfx,
|
|
7751
|
+
particleLimit: () => options.debris?.particleLimit ?? DEFAULT_PARTICLE_LIMIT,
|
|
7752
|
+
lights,
|
|
7753
|
+
pools,
|
|
7327
7754
|
loading,
|
|
7328
7755
|
...options.blastShove ? { blastShove: options.blastShove } : {},
|
|
7329
7756
|
tool: () => ({
|
|
@@ -7375,9 +7802,9 @@ function createTestRoom(options) {
|
|
|
7375
7802
|
hostListeners.add(listener);
|
|
7376
7803
|
return () => hostListeners.delete(listener);
|
|
7377
7804
|
},
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
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++}` });
|
|
7381
7808
|
},
|
|
7382
7809
|
holdFrame: () => bodyHoldFrame(),
|
|
7383
7810
|
reachFor(reach) {
|
|
@@ -7435,46 +7862,47 @@ function createTestRoom(options) {
|
|
|
7435
7862
|
return r.ok ? store : null;
|
|
7436
7863
|
};
|
|
7437
7864
|
const LIGHTS_CHECK_MS = 250;
|
|
7438
|
-
const
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
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() };
|
|
7444
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
|
+
};
|
|
7445
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;
|
|
7446
7903
|
const warmUp = () => {
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
const spriteMap = new THREE.DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1);
|
|
7450
|
-
spriteMap.needsUpdate = true;
|
|
7451
|
-
const spriteProbes = [true, false].map((fog) => {
|
|
7452
|
-
const probe = new THREE.Sprite(new THREE.SpriteMaterial({ map: spriteMap, transparent: true, fog }));
|
|
7453
|
-
probe.frustumCulled = false;
|
|
7454
|
-
return probe;
|
|
7455
|
-
});
|
|
7456
|
-
groups.push(...spriteProbes);
|
|
7457
|
-
scene.add(...groups);
|
|
7458
|
-
gl.render(scene, camera);
|
|
7459
|
-
pinPrograms();
|
|
7460
|
-
scene.remove(...groups);
|
|
7461
|
-
for (const probe of probes) probe.dispose();
|
|
7462
|
-
for (const probe of spriteProbes) probe.material.dispose();
|
|
7463
|
-
spriteMap.dispose();
|
|
7464
|
-
};
|
|
7465
|
-
const warmUpEffects = () => {
|
|
7466
|
-
const ids = vfx.catalog?.() ?? [];
|
|
7467
|
-
const flames = ids.filter((id) => /fire|flame/i.test(id)).slice(0, 1);
|
|
7468
|
-
gl.compile(scene, camera);
|
|
7469
|
-
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] }));
|
|
7470
|
-
for (let i = 0; i < 3; i++) {
|
|
7471
|
-
vfx.update(0.02);
|
|
7472
|
-
gl.render(scene, camera);
|
|
7473
|
-
}
|
|
7474
|
-
for (const h of handles) if (h !== null) vfx.stop(h);
|
|
7475
|
-
vfx.world.clear();
|
|
7476
|
-
vfx.update(0);
|
|
7477
|
-
gl.render(scene, camera);
|
|
7904
|
+
warmup.warmUp(gl, scene, camera, { lights, fogStates: WARMED_FOG_STATES, materials: pools.materials.registeredMaterials?.() ?? [] });
|
|
7905
|
+
warmedUp = true;
|
|
7478
7906
|
};
|
|
7479
7907
|
const loadModule = (mod) => {
|
|
7480
7908
|
const report = { module: mod.id, items: [], scenarios: [], ok: true, problems: [] };
|
|
@@ -7586,24 +8014,13 @@ function createTestRoom(options) {
|
|
|
7586
8014
|
};
|
|
7587
8015
|
const allScenarios = () => [...extraScenarios];
|
|
7588
8016
|
let scenarioRunning = false;
|
|
7589
|
-
const step = (seconds) => {
|
|
7590
|
-
const dt = seconds > 6 ? 1 / 30 : FIXED_DT;
|
|
7591
|
-
const n = Math.max(1, Math.round(seconds / dt));
|
|
7592
|
-
for (let i = 0; i < n; i++) {
|
|
7593
|
-
seating.update();
|
|
7594
|
-
player.update(0);
|
|
7595
|
-
push.step(dt);
|
|
7596
|
-
doors.step(dt);
|
|
7597
|
-
followHeld(dt);
|
|
7598
|
-
stepSim(dt);
|
|
7599
|
-
}
|
|
7600
|
-
};
|
|
8017
|
+
const step = (seconds) => runHeadless(() => scheduler.advance(seconds, { dt: seconds > 6 ? 1 / 30 : FIXED_DT }));
|
|
7601
8018
|
const runScenarios = async (list) => {
|
|
7602
8019
|
const all = list ?? allScenarios();
|
|
7603
8020
|
const report = { passed: 0, failed: 0, results: [] };
|
|
7604
8021
|
scenarioRunning = true;
|
|
7605
|
-
const wasPaused = paused;
|
|
7606
|
-
|
|
8022
|
+
const wasPaused = scheduler.paused;
|
|
8023
|
+
scheduler.resume();
|
|
7607
8024
|
try {
|
|
7608
8025
|
for (const sc of all) {
|
|
7609
8026
|
hardReset();
|
|
@@ -7635,7 +8052,7 @@ function createTestRoom(options) {
|
|
|
7635
8052
|
}
|
|
7636
8053
|
} finally {
|
|
7637
8054
|
scenarioRunning = false;
|
|
7638
|
-
|
|
8055
|
+
if (wasPaused) scheduler.pause();
|
|
7639
8056
|
restoreScenery();
|
|
7640
8057
|
}
|
|
7641
8058
|
return report;
|
|
@@ -7767,19 +8184,25 @@ function createTestRoom(options) {
|
|
|
7767
8184
|
useItem,
|
|
7768
8185
|
use(hold = 0) {
|
|
7769
8186
|
if (tool.def.category === "hand") {
|
|
7770
|
-
handUse();
|
|
8187
|
+
const status2 = handUse();
|
|
7771
8188
|
flush();
|
|
7772
|
-
return;
|
|
8189
|
+
return status2;
|
|
7773
8190
|
}
|
|
7774
|
-
useTool(FIXED_DT, false);
|
|
8191
|
+
const status = useTool(FIXED_DT, false);
|
|
7775
8192
|
flush();
|
|
7776
8193
|
let t0 = 0;
|
|
7777
8194
|
while (t0 < hold) {
|
|
7778
8195
|
useTool(FIXED_DT, true);
|
|
7779
|
-
|
|
8196
|
+
directStep = true;
|
|
8197
|
+
try {
|
|
8198
|
+
runHeadless(() => scheduler.advance(FIXED_DT));
|
|
8199
|
+
} finally {
|
|
8200
|
+
directStep = false;
|
|
8201
|
+
}
|
|
7780
8202
|
t0 += FIXED_DT;
|
|
7781
8203
|
}
|
|
7782
8204
|
releaseTrigger();
|
|
8205
|
+
return status;
|
|
7783
8206
|
},
|
|
7784
8207
|
grab,
|
|
7785
8208
|
throwHeld,
|
|
@@ -7795,14 +8218,14 @@ function createTestRoom(options) {
|
|
|
7795
8218
|
spawned() {
|
|
7796
8219
|
return [...entries.values()].filter((e) => !e.arena).map((e) => e.so);
|
|
7797
8220
|
},
|
|
7798
|
-
update,
|
|
8221
|
+
update: (dt) => runHeadless(() => scheduler.frame(dt, { render: false, exact: true })),
|
|
7799
8222
|
waitUntilReady(maxSeconds = 5) {
|
|
7800
8223
|
let waited = 0;
|
|
7801
8224
|
while (tool.phase && waited < maxSeconds) {
|
|
7802
|
-
update(FIXED_DT);
|
|
8225
|
+
room.update(FIXED_DT);
|
|
7803
8226
|
waited += FIXED_DT;
|
|
7804
8227
|
}
|
|
7805
|
-
update(FIXED_DT);
|
|
8228
|
+
room.update(FIXED_DT);
|
|
7806
8229
|
return waited + FIXED_DT;
|
|
7807
8230
|
},
|
|
7808
8231
|
on(type, fn) {
|
|
@@ -7819,20 +8242,20 @@ function createTestRoom(options) {
|
|
|
7819
8242
|
blastOptions = { ...blastOptions, ...next };
|
|
7820
8243
|
},
|
|
7821
8244
|
pause() {
|
|
7822
|
-
|
|
8245
|
+
scheduler.pause();
|
|
7823
8246
|
},
|
|
7824
8247
|
resume() {
|
|
7825
|
-
|
|
8248
|
+
scheduler.resume();
|
|
7826
8249
|
},
|
|
7827
8250
|
get paused() {
|
|
7828
|
-
return paused;
|
|
8251
|
+
return scheduler.paused;
|
|
7829
8252
|
},
|
|
7830
8253
|
walk(to, seconds) {
|
|
7831
8254
|
walkPlan = seconds > 0 ? { x: to[0], z: to[2], remaining: seconds } : null;
|
|
7832
8255
|
if (!walkPlan) player.moveBy(to[0] - camera.position.x, to[2] - camera.position.z);
|
|
7833
8256
|
},
|
|
7834
8257
|
setSlowMotion(on) {
|
|
7835
|
-
timeScale = on ? 0.25 : 1;
|
|
8258
|
+
scheduler.timeScale = on ? 0.25 : 1;
|
|
7836
8259
|
},
|
|
7837
8260
|
setFastForward(on) {
|
|
7838
8261
|
fastForward = on;
|
|
@@ -7848,12 +8271,13 @@ function createTestRoom(options) {
|
|
|
7848
8271
|
modules: () => [...modules.values()].map((m) => ({ id: m.id, name: m.name, group: m.group, hasArea: typeof m.area === "function" })),
|
|
7849
8272
|
openAreaAsync: (area) => loading.busy(t("loading.open_area", { area: areaLabel(area) }), () => openArea(area)),
|
|
7850
8273
|
loading,
|
|
8274
|
+
graphics: graphics?.controller ?? null,
|
|
8275
|
+
graphicsScreen: graphics?.screen ?? null,
|
|
7851
8276
|
busy: (label, work, busyOptions) => loading.busy(label, work, busyOptions),
|
|
7852
8277
|
loadModule,
|
|
7853
8278
|
warmUp: () => {
|
|
7854
8279
|
audio?.warmUp();
|
|
7855
8280
|
warmUp();
|
|
7856
|
-
warmUpEffects();
|
|
7857
8281
|
},
|
|
7858
8282
|
animations: animationsOf,
|
|
7859
8283
|
playAnimation,
|
|
@@ -7925,6 +8349,9 @@ function createTestRoom(options) {
|
|
|
7925
8349
|
metrics() {
|
|
7926
8350
|
return { room: roomMetrics.sample(), module: moduleMetrics() };
|
|
7927
8351
|
},
|
|
8352
|
+
frameStats: () => scheduler.stats(),
|
|
8353
|
+
lights,
|
|
8354
|
+
webgl: gl,
|
|
7928
8355
|
dispose() {
|
|
7929
8356
|
destructionSound?.dispose();
|
|
7930
8357
|
if (ownAudio) audio?.dispose();
|
|
@@ -7932,13 +8359,19 @@ function createTestRoom(options) {
|
|
|
7932
8359
|
detachers.clear();
|
|
7933
8360
|
for (const store of benches.values()) store.dispose();
|
|
7934
8361
|
benches.clear();
|
|
7935
|
-
|
|
8362
|
+
stopLoop?.();
|
|
8363
|
+
disposePacer?.();
|
|
8364
|
+
scheduler.dispose();
|
|
7936
8365
|
window.removeEventListener("resize", onResize);
|
|
7937
8366
|
player.dispose();
|
|
7938
8367
|
ui?.dispose();
|
|
8368
|
+
graphics?.dispose();
|
|
7939
8369
|
loading.dispose();
|
|
7940
8370
|
renderer.dispose();
|
|
7941
8371
|
vfx.dispose();
|
|
8372
|
+
lights.dispose();
|
|
8373
|
+
pools.materials.dispose();
|
|
8374
|
+
pools.geometries.dispose();
|
|
7942
8375
|
fluidView.dispose();
|
|
7943
8376
|
filmBridge.dispose();
|
|
7944
8377
|
film.dispose();
|
|
@@ -8004,6 +8437,7 @@ function createTestRoom(options) {
|
|
|
8004
8437
|
}
|
|
8005
8438
|
},
|
|
8006
8439
|
quality: qualityName,
|
|
8440
|
+
...graphics?.screen ? { onGraphics: () => graphics.screen?.open() } : {},
|
|
8007
8441
|
scenarios: allScenarios,
|
|
8008
8442
|
onRun: async (sc) => {
|
|
8009
8443
|
await runScenarios([sc]);
|
|
@@ -8037,41 +8471,76 @@ function createTestRoom(options) {
|
|
|
8037
8471
|
ui.hud(hudState());
|
|
8038
8472
|
showBench();
|
|
8039
8473
|
}
|
|
8040
|
-
let
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
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) {
|
|
8044
8499
|
let nextLightsCheck = 0;
|
|
8045
|
-
let
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
return key2;
|
|
8052
|
-
};
|
|
8053
|
-
const loop = (now) => {
|
|
8054
|
-
raf = requestAnimationFrame(loop);
|
|
8055
|
-
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;
|
|
8056
8506
|
nextLightsCheck = now + LIGHTS_CHECK_MS;
|
|
8057
|
-
const
|
|
8058
|
-
if (
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
}
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
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
|
+
}
|
|
8075
8544
|
}
|
|
8076
8545
|
if (options.initial) {
|
|
8077
8546
|
for (const [i, id] of options.initial.entries()) spawn(id, [(i - (options.initial.length - 1) / 2) * 1.3, 0, 0.5]);
|