@adatechnology/game-test-room 0.2.0 → 0.3.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/README.md +24 -0
- package/dist/index.d.ts +48 -2
- package/dist/index.global.js +734 -113
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +662 -87
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.global.js
CHANGED
|
@@ -29,7 +29,9 @@ var GameTestRoom = (() => {
|
|
|
29
29
|
buildFromSpec: () => buildFromSpec,
|
|
30
30
|
createLog: () => createLog,
|
|
31
31
|
createTestRoom: () => createTestRoom,
|
|
32
|
+
decodeBenchState: () => decodeBenchState,
|
|
32
33
|
demoItems: () => demoItems,
|
|
34
|
+
encodeBenchState: () => encodeBenchState,
|
|
33
35
|
locales: () => locales4,
|
|
34
36
|
makeScenarios: () => makeScenarios,
|
|
35
37
|
resolveStrings: () => resolveStrings4,
|
|
@@ -240,6 +242,47 @@ var GameTestRoom = (() => {
|
|
|
240
242
|
});
|
|
241
243
|
return { ok: problems.length === 0, problems };
|
|
242
244
|
};
|
|
245
|
+
var SNAKE_CASE = /^[a-z][a-z0-9_]*$/;
|
|
246
|
+
var finite2 = (n) => typeof n === "number" && Number.isFinite(n);
|
|
247
|
+
function checkControl(control, problems) {
|
|
248
|
+
const label = `control "${control.id}"`;
|
|
249
|
+
if (control.kind === "number") {
|
|
250
|
+
if (!finite2(control.min) || !finite2(control.max) || control.min >= control.max) problems.push(`${label} needs min < max`);
|
|
251
|
+
if (control.step !== void 0 && !(finite2(control.step) && control.step > 0)) problems.push(`${label} needs a step > 0`);
|
|
252
|
+
if (!finite2(control.default) || control.default < control.min || control.default > control.max) problems.push(`${label} default ${String(control.default)} is outside ${control.min}..${control.max}`);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (control.kind !== "choice") return;
|
|
256
|
+
if (control.options.length === 0) {
|
|
257
|
+
problems.push(`${label} needs at least one option`);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const seen = /* @__PURE__ */ new Set();
|
|
261
|
+
for (const option of control.options) {
|
|
262
|
+
if (seen.has(option.id)) problems.push(`${label} repeats option "${option.id}"`);
|
|
263
|
+
seen.add(option.id);
|
|
264
|
+
}
|
|
265
|
+
if (!seen.has(control.default)) problems.push(`${label} default "${control.default}" is not among its options`);
|
|
266
|
+
}
|
|
267
|
+
function checkBench(declared) {
|
|
268
|
+
const problems = [];
|
|
269
|
+
const ids = /* @__PURE__ */ new Set();
|
|
270
|
+
const claim = (kind, id) => {
|
|
271
|
+
if (!SNAKE_CASE.test(id)) problems.push(`${kind} id "${id}" must be snake_case`);
|
|
272
|
+
if (ids.has(id)) problems.push(`duplicate id "${id}" across controls, layers and metrics`);
|
|
273
|
+
ids.add(id);
|
|
274
|
+
};
|
|
275
|
+
for (const control of declared.controls ?? []) {
|
|
276
|
+
claim("control", control.id);
|
|
277
|
+
checkControl(control, problems);
|
|
278
|
+
}
|
|
279
|
+
for (const layer of declared.layers ?? []) claim("layer", layer.id);
|
|
280
|
+
for (const metric of declared.metrics ?? []) {
|
|
281
|
+
claim("metric", metric.id);
|
|
282
|
+
if (metric.max !== void 0 && !(finite2(metric.max) && metric.max > 0)) problems.push(`metric "${metric.id}" needs a max > 0`);
|
|
283
|
+
}
|
|
284
|
+
return { ok: problems.length === 0, problems };
|
|
285
|
+
}
|
|
243
286
|
|
|
244
287
|
// ../game-destruction/dist/index.js
|
|
245
288
|
var EPS = 1e-9;
|
|
@@ -2940,13 +2983,13 @@ var GameTestRoom = (() => {
|
|
|
2940
2983
|
...impls.size ? { impls: [...impls.values()].map((im) => ({ id: im.impl.id, position: [...im.position], rotation: [...im.rotation], data: im.impl.serialize?.() })) } : {}
|
|
2941
2984
|
};
|
|
2942
2985
|
},
|
|
2943
|
-
load(
|
|
2944
|
-
if (
|
|
2986
|
+
load(snap2) {
|
|
2987
|
+
if (snap2.version !== 1) throw new Error(`unsupported snapshot version ${String(snap2.version)}`);
|
|
2945
2988
|
objects.clear();
|
|
2946
|
-
time =
|
|
2947
|
-
rng.set(
|
|
2948
|
-
counter =
|
|
2949
|
-
for (const e of
|
|
2989
|
+
time = snap2.time;
|
|
2990
|
+
rng.set(snap2.rng);
|
|
2991
|
+
counter = snap2.counter ?? 0;
|
|
2992
|
+
for (const e of snap2.objects) {
|
|
2950
2993
|
const state = clone(e.state);
|
|
2951
2994
|
const carry = {
|
|
2952
2995
|
parts: new Map(
|
|
@@ -2957,7 +3000,7 @@ var GameTestRoom = (() => {
|
|
|
2957
3000
|
};
|
|
2958
3001
|
objects.set(e.id, build(clone(e.spec), e.id, state.position, state.rotation, carry));
|
|
2959
3002
|
}
|
|
2960
|
-
for (const e of
|
|
3003
|
+
for (const e of snap2.impls ?? []) {
|
|
2961
3004
|
const im = impls.get(e.id);
|
|
2962
3005
|
if (!im) continue;
|
|
2963
3006
|
im.position = [e.position[0], e.position[1], e.position[2]];
|
|
@@ -3644,6 +3687,8 @@ void main() {
|
|
|
3644
3687
|
__publicField(this, "alphaMul");
|
|
3645
3688
|
__publicField(this, "tag");
|
|
3646
3689
|
__publicField(this, "colorHex");
|
|
3690
|
+
__publicField(this, "rgbStart");
|
|
3691
|
+
__publicField(this, "rgbEnd");
|
|
3647
3692
|
__publicField(this, "bounced");
|
|
3648
3693
|
const n = Math.max(1, capacity);
|
|
3649
3694
|
this.pos = new Float32Array(n * 3);
|
|
@@ -3660,6 +3705,8 @@ void main() {
|
|
|
3660
3705
|
this.alphaMul = new Float32Array(n);
|
|
3661
3706
|
this.tag = new Int32Array(n);
|
|
3662
3707
|
this.colorHex = new Int32Array(n);
|
|
3708
|
+
this.rgbStart = new Float32Array(n * 3);
|
|
3709
|
+
this.rgbEnd = new Float32Array(n * 3);
|
|
3663
3710
|
this.bounced = new Uint8Array(n);
|
|
3664
3711
|
this.geometry = new THREE.BufferGeometry();
|
|
3665
3712
|
const attr = (a, size) => {
|
|
@@ -3707,6 +3754,8 @@ void main() {
|
|
|
3707
3754
|
this.alphaMul[i] = o?.alpha ?? 1;
|
|
3708
3755
|
this.tag[i] = o?.tag ?? 0;
|
|
3709
3756
|
this.colorHex[i] = o?.color ?? -1;
|
|
3757
|
+
this.rgbStart.set(hexToRgb(o?.color ?? def.c0), i * 3);
|
|
3758
|
+
this.rgbEnd.set(hexToRgb(o?.color ?? def.c1), i * 3);
|
|
3710
3759
|
this.bounced[i] = 0;
|
|
3711
3760
|
this.siz[i] = this.size0[i];
|
|
3712
3761
|
this.alp[i] = 0;
|
|
@@ -3800,13 +3849,9 @@ void main() {
|
|
|
3800
3849
|
this.pos[i3 + 1] = y;
|
|
3801
3850
|
this.pos[i3 + 2] = z;
|
|
3802
3851
|
this.siz[i] = this.size0[i] * lerp(1, def.grow, t);
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
const b = hexToRgb(c1);
|
|
3807
|
-
this.col[i3] = lerp(a[0], b[0], t);
|
|
3808
|
-
this.col[i3 + 1] = lerp(a[1], b[1], t);
|
|
3809
|
-
this.col[i3 + 2] = lerp(a[2], b[2], t);
|
|
3852
|
+
this.col[i3] = lerp(this.rgbStart[i3], this.rgbEnd[i3], t);
|
|
3853
|
+
this.col[i3 + 1] = lerp(this.rgbStart[i3 + 1], this.rgbEnd[i3 + 1], t);
|
|
3854
|
+
this.col[i3 + 2] = lerp(this.rgbStart[i3 + 2], this.rgbEnd[i3 + 2], t);
|
|
3810
3855
|
let env = t < def.fadeIn ? t / def.fadeIn : 1 - (t - def.fadeIn) / (1 - def.fadeIn);
|
|
3811
3856
|
if (def.flicker) env *= 1 - def.flicker * (0.5 + 0.5 * Math.sin(age * 38 + this.seed[i] * 50));
|
|
3812
3857
|
this.alp[i] = clamp3(env, 0, 1) * def.alpha * this.alphaMul[i];
|
|
@@ -6078,6 +6123,9 @@ void main() {
|
|
|
6078
6123
|
emit(kind, point, normal, count, speed) {
|
|
6079
6124
|
this.particles.burst(kind, point, normal, count, speed ?? DEFAULT_SPEED[kind] ?? 1, kind === "smoke" ? { color: SMOKE_TONES.grey } : void 0);
|
|
6080
6125
|
}
|
|
6126
|
+
explode(point, energy, radius) {
|
|
6127
|
+
this.onExplode({ type: "explode", object: "", part: "", time: this.time, point, energy, radius });
|
|
6128
|
+
}
|
|
6081
6129
|
restore(objectId) {
|
|
6082
6130
|
const homes = /* @__PURE__ */ new Set([objectId]);
|
|
6083
6131
|
const self = this.bounds.get(objectId);
|
|
@@ -9275,22 +9323,22 @@ void main() {
|
|
|
9275
9323
|
return snapshot;
|
|
9276
9324
|
},
|
|
9277
9325
|
load(input) {
|
|
9278
|
-
const
|
|
9279
|
-
if (!
|
|
9280
|
-
time =
|
|
9326
|
+
const snap2 = clone2(input);
|
|
9327
|
+
if (!snap2 || snap2.version !== 1) throw new Error("game-fluids: unsupported snapshot");
|
|
9328
|
+
time = snap2.time;
|
|
9281
9329
|
rng = new Rng(0);
|
|
9282
|
-
rng.state =
|
|
9283
|
-
counters.container =
|
|
9284
|
-
counters.stream =
|
|
9285
|
-
counters.stain =
|
|
9330
|
+
rng.state = snap2.rng;
|
|
9331
|
+
counters.container = snap2.counters.container;
|
|
9332
|
+
counters.stream = snap2.counters.stream;
|
|
9333
|
+
counters.stain = snap2.counters.stain;
|
|
9286
9334
|
containers = /* @__PURE__ */ new Map();
|
|
9287
|
-
for (const raw of
|
|
9335
|
+
for (const raw of snap2.containers) containers.set(raw.id, { ...raw, cols: makeColumns(raw.spec.shape) });
|
|
9288
9336
|
surfaces = /* @__PURE__ */ new Map();
|
|
9289
|
-
for (const s of
|
|
9290
|
-
counters.tap =
|
|
9291
|
-
streams = new Map(
|
|
9292
|
-
taps = new Map((
|
|
9293
|
-
stains = new Map(
|
|
9337
|
+
for (const s of snap2.surfaces) world.addSurface(s);
|
|
9338
|
+
counters.tap = snap2.counters.tap ?? 0;
|
|
9339
|
+
streams = new Map(snap2.streams.map((s) => [s.id, s]));
|
|
9340
|
+
taps = new Map((snap2.taps ?? []).map((t) => [t.id, t]));
|
|
9341
|
+
stains = new Map(snap2.stains.map((s) => [s.id, s]));
|
|
9294
9342
|
queue = [];
|
|
9295
9343
|
}
|
|
9296
9344
|
};
|
|
@@ -11170,6 +11218,21 @@ void main() {
|
|
|
11170
11218
|
"ui.animations": "Animations",
|
|
11171
11219
|
"ui.animations_aim": "Aim at an item or hold it to see its animations.",
|
|
11172
11220
|
"ui.animations_none": "This item brings no animations.",
|
|
11221
|
+
"mod.bench_ok": "{name}: bench ok ({controls} controls, {layers} layers, {metrics} metrics)",
|
|
11222
|
+
"mod.bench_fail": "{name}: bench broken: {problems}",
|
|
11223
|
+
"mod.metric_failed": '{name}: metric "{metric}" threw and shows n/a: {error}',
|
|
11224
|
+
"mod.listener_failed": "{name}: a bench listener threw: {error}",
|
|
11225
|
+
"ui.bench": "Bench",
|
|
11226
|
+
"ui.layers": "Layers",
|
|
11227
|
+
"ui.metrics": "Counters",
|
|
11228
|
+
"metric.fps": "FPS",
|
|
11229
|
+
"metric.frame_ms": "ms/frame",
|
|
11230
|
+
"metric.draw_calls": "Draw calls",
|
|
11231
|
+
"metric.triangles": "Triangles",
|
|
11232
|
+
"metric.objects": "Objects",
|
|
11233
|
+
"metric.fragments": "Fragments",
|
|
11234
|
+
"metric.particles": "Particles",
|
|
11235
|
+
"metric.decals": "Decals",
|
|
11173
11236
|
"act.spawn": "Spawned {name} at x {x} \xB7 z {z}",
|
|
11174
11237
|
"act.clear": "Cleared the room",
|
|
11175
11238
|
"act.reset": "Repaired every object",
|
|
@@ -11462,6 +11525,21 @@ void main() {
|
|
|
11462
11525
|
"ui.animations": "Anima\xE7\xF5es",
|
|
11463
11526
|
"ui.animations_aim": "Mire num item ou segure-o para ver as anima\xE7\xF5es.",
|
|
11464
11527
|
"ui.animations_none": "Este item n\xE3o traz anima\xE7\xF5es.",
|
|
11528
|
+
"mod.bench_ok": "{name}: bancada ok ({controls} controles, {layers} camadas, {metrics} contadores)",
|
|
11529
|
+
"mod.bench_fail": "{name}: bancada quebrada: {problems}",
|
|
11530
|
+
"mod.metric_failed": '{name}: o contador "{metric}" lan\xE7ou erro e mostra n/a: {error}',
|
|
11531
|
+
"mod.listener_failed": "{name}: um ouvinte da bancada lan\xE7ou erro: {error}",
|
|
11532
|
+
"ui.bench": "Bancada",
|
|
11533
|
+
"ui.layers": "Camadas",
|
|
11534
|
+
"ui.metrics": "Contadores",
|
|
11535
|
+
"metric.fps": "FPS",
|
|
11536
|
+
"metric.frame_ms": "ms/quadro",
|
|
11537
|
+
"metric.draw_calls": "Chamadas de desenho",
|
|
11538
|
+
"metric.triangles": "Tri\xE2ngulos",
|
|
11539
|
+
"metric.objects": "Objetos",
|
|
11540
|
+
"metric.fragments": "Fragmentos",
|
|
11541
|
+
"metric.particles": "Part\xEDculas",
|
|
11542
|
+
"metric.decals": "Decalques",
|
|
11465
11543
|
"act.spawn": "Criou {name} em x {x} \xB7 z {z}",
|
|
11466
11544
|
"act.clear": "Limpou a sala",
|
|
11467
11545
|
"act.reset": "Consertou todos os objetos",
|
|
@@ -12073,6 +12151,154 @@ void main() {
|
|
|
12073
12151
|
};
|
|
12074
12152
|
}
|
|
12075
12153
|
|
|
12154
|
+
// src/bench.ts
|
|
12155
|
+
var snap = (control, value) => {
|
|
12156
|
+
const clamped = Math.min(control.max, Math.max(control.min, value));
|
|
12157
|
+
if (!control.step) return clamped;
|
|
12158
|
+
const stepped = control.min + Math.round((clamped - control.min) / control.step) * control.step;
|
|
12159
|
+
return Number(Math.min(control.max, stepped).toFixed(10));
|
|
12160
|
+
};
|
|
12161
|
+
function fitValue(control, value) {
|
|
12162
|
+
if (control.kind === "number") return typeof value === "number" && Number.isFinite(value) ? snap(control, value) : void 0;
|
|
12163
|
+
if (control.kind === "boolean") return typeof value === "boolean" ? value : void 0;
|
|
12164
|
+
if (control.kind === "choice") return typeof value === "string" && control.options.some((option) => option.id === value) ? value : void 0;
|
|
12165
|
+
return void 0;
|
|
12166
|
+
}
|
|
12167
|
+
var fromText = (control, text) => {
|
|
12168
|
+
if (control.kind === "number") return text.trim() === "" ? Number.NaN : Number(text);
|
|
12169
|
+
if (control.kind === "boolean") return text === "1" ? true : text === "0" ? false : void 0;
|
|
12170
|
+
return text;
|
|
12171
|
+
};
|
|
12172
|
+
var toText = (value) => typeof value === "boolean" ? value ? "1" : "0" : String(value);
|
|
12173
|
+
function encodeBenchState(controls, layers) {
|
|
12174
|
+
const pairs = [];
|
|
12175
|
+
for (const control of controls) if (control.value !== void 0) pairs.push(`${control.id}=${encodeURIComponent(toText(control.value))}`);
|
|
12176
|
+
for (const layer of layers) pairs.push(`${layer.id}=${layer.on ? "1" : "0"}`);
|
|
12177
|
+
return pairs.join(";");
|
|
12178
|
+
}
|
|
12179
|
+
function decodeBenchState(encoded, controls, layers) {
|
|
12180
|
+
const out = /* @__PURE__ */ new Map();
|
|
12181
|
+
for (const pair of encoded.split(";")) {
|
|
12182
|
+
const at = pair.indexOf("=");
|
|
12183
|
+
if (at <= 0) continue;
|
|
12184
|
+
const id = pair.slice(0, at);
|
|
12185
|
+
let text;
|
|
12186
|
+
try {
|
|
12187
|
+
text = decodeURIComponent(pair.slice(at + 1));
|
|
12188
|
+
} catch {
|
|
12189
|
+
continue;
|
|
12190
|
+
}
|
|
12191
|
+
const control = controls.find((c) => c.id === id);
|
|
12192
|
+
if (control) {
|
|
12193
|
+
const value = fitValue(control, fromText(control, text));
|
|
12194
|
+
if (value !== void 0) out.set(id, value);
|
|
12195
|
+
continue;
|
|
12196
|
+
}
|
|
12197
|
+
if (layers.some((layer) => layer.id === id) && (text === "1" || text === "0")) out.set(id, text === "1");
|
|
12198
|
+
}
|
|
12199
|
+
return out;
|
|
12200
|
+
}
|
|
12201
|
+
function createBenchStore(module, locale, onListenerError) {
|
|
12202
|
+
const declared = { controls: module.controls?.(locale) ?? [], layers: module.layers?.(locale) ?? [], metrics: module.metrics?.(locale) ?? [] };
|
|
12203
|
+
const controls = declared.controls.map((control) => ({ ...control, disabled: false }));
|
|
12204
|
+
const values = /* @__PURE__ */ new Map();
|
|
12205
|
+
for (const control of controls) if (control.kind !== "action") values.set(control.id, control.default);
|
|
12206
|
+
const layers = new Map(declared.layers.map((layer) => [layer.id, layer.default !== false]));
|
|
12207
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
12208
|
+
const watchers = /* @__PURE__ */ new Set();
|
|
12209
|
+
const emit = (change) => {
|
|
12210
|
+
for (const listener of listeners) {
|
|
12211
|
+
try {
|
|
12212
|
+
listener(change);
|
|
12213
|
+
} catch (error) {
|
|
12214
|
+
onListenerError?.(error);
|
|
12215
|
+
}
|
|
12216
|
+
}
|
|
12217
|
+
};
|
|
12218
|
+
const changed = () => {
|
|
12219
|
+
for (const watcher of watchers) watcher();
|
|
12220
|
+
};
|
|
12221
|
+
const controlOf = (id) => controls.find((control) => control.id === id);
|
|
12222
|
+
const store = {
|
|
12223
|
+
moduleId: module.id,
|
|
12224
|
+
metrics: declared.metrics,
|
|
12225
|
+
declared,
|
|
12226
|
+
controls: () => controls.map((control) => ({ ...control, value: values.get(control.id) })),
|
|
12227
|
+
layers: () => declared.layers.map((layer) => ({ ...layer, on: layers.get(layer.id) === true })),
|
|
12228
|
+
value: (id) => values.get(id),
|
|
12229
|
+
layer: (id) => layers.get(id) === true,
|
|
12230
|
+
set(id, value) {
|
|
12231
|
+
const control = controlOf(id);
|
|
12232
|
+
if (!control || control.disabled) return false;
|
|
12233
|
+
const fitted = fitValue(control, value);
|
|
12234
|
+
if (fitted === void 0) return false;
|
|
12235
|
+
values.set(id, fitted);
|
|
12236
|
+
emit({ kind: "control", id, value: fitted });
|
|
12237
|
+
changed();
|
|
12238
|
+
return true;
|
|
12239
|
+
},
|
|
12240
|
+
setLayer(id, on) {
|
|
12241
|
+
if (!layers.has(id)) return false;
|
|
12242
|
+
layers.set(id, on);
|
|
12243
|
+
emit({ kind: "layer", id, on });
|
|
12244
|
+
changed();
|
|
12245
|
+
return true;
|
|
12246
|
+
},
|
|
12247
|
+
act(id) {
|
|
12248
|
+
const control = controlOf(id);
|
|
12249
|
+
if (!control || control.kind !== "action" || control.disabled) return false;
|
|
12250
|
+
emit({ kind: "action", id });
|
|
12251
|
+
return true;
|
|
12252
|
+
},
|
|
12253
|
+
encode: () => encodeBenchState(store.controls(), store.layers()),
|
|
12254
|
+
onChange(listener) {
|
|
12255
|
+
listeners.add(listener);
|
|
12256
|
+
return () => listeners.delete(listener);
|
|
12257
|
+
},
|
|
12258
|
+
watch(listener) {
|
|
12259
|
+
watchers.add(listener);
|
|
12260
|
+
return () => watchers.delete(listener);
|
|
12261
|
+
},
|
|
12262
|
+
patch(id, patch) {
|
|
12263
|
+
const index = controls.findIndex((control2) => control2.id === id);
|
|
12264
|
+
if (index < 0) return;
|
|
12265
|
+
let control = controls[index];
|
|
12266
|
+
if (patch.hidden !== void 0) control = { ...control, hidden: patch.hidden };
|
|
12267
|
+
if (patch.disabled !== void 0) control = { ...control, disabled: patch.disabled };
|
|
12268
|
+
if (patch.options && control.kind === "choice" && patch.options.length > 0) {
|
|
12269
|
+
control = { ...control, options: [...patch.options] };
|
|
12270
|
+
const offered = control.options;
|
|
12271
|
+
if (!offered.some((option) => option.id === values.get(id))) values.set(id, offered[0].id);
|
|
12272
|
+
}
|
|
12273
|
+
controls[index] = control;
|
|
12274
|
+
if (patch.value !== void 0) {
|
|
12275
|
+
const fitted = fitValue(control, patch.value);
|
|
12276
|
+
if (fitted !== void 0) values.set(id, fitted);
|
|
12277
|
+
}
|
|
12278
|
+
changed();
|
|
12279
|
+
},
|
|
12280
|
+
sendInitial() {
|
|
12281
|
+
for (const control of controls) {
|
|
12282
|
+
const value = values.get(control.id);
|
|
12283
|
+
if (value !== void 0) emit({ kind: "control", id: control.id, value });
|
|
12284
|
+
}
|
|
12285
|
+
for (const layer of declared.layers) emit({ kind: "layer", id: layer.id, on: layers.get(layer.id) === true });
|
|
12286
|
+
changed();
|
|
12287
|
+
},
|
|
12288
|
+
restore(encoded) {
|
|
12289
|
+
for (const [id, value] of decodeBenchState(encoded, controls, declared.layers)) {
|
|
12290
|
+
if (typeof value === "boolean" && layers.has(id)) layers.set(id, value);
|
|
12291
|
+
else values.set(id, value);
|
|
12292
|
+
}
|
|
12293
|
+
},
|
|
12294
|
+
dispose() {
|
|
12295
|
+
listeners.clear();
|
|
12296
|
+
watchers.clear();
|
|
12297
|
+
}
|
|
12298
|
+
};
|
|
12299
|
+
return store;
|
|
12300
|
+
}
|
|
12301
|
+
|
|
12076
12302
|
// src/log.ts
|
|
12077
12303
|
var MAX_ENTRIES = 5e3;
|
|
12078
12304
|
var pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
@@ -12201,6 +12427,56 @@ void main() {
|
|
|
12201
12427
|
return log;
|
|
12202
12428
|
}
|
|
12203
12429
|
|
|
12430
|
+
// src/metrics.ts
|
|
12431
|
+
var ROOM_METRICS = ["fps", "frame_ms", "draw_calls", "triangles", "objects", "fragments", "particles", "decals"];
|
|
12432
|
+
var WINDOW_S = 0.5;
|
|
12433
|
+
function createRoomMetrics(sources) {
|
|
12434
|
+
const wanted = new Set(sources.ids);
|
|
12435
|
+
const gl = sources.gl;
|
|
12436
|
+
let windowS = 0;
|
|
12437
|
+
let frames = 0;
|
|
12438
|
+
let fps;
|
|
12439
|
+
let frameMs;
|
|
12440
|
+
let calls;
|
|
12441
|
+
let triangles;
|
|
12442
|
+
let ownsRender = false;
|
|
12443
|
+
return {
|
|
12444
|
+
frame(dt) {
|
|
12445
|
+
frames++;
|
|
12446
|
+
windowS += dt;
|
|
12447
|
+
if (windowS < WINDOW_S) return;
|
|
12448
|
+
fps = frames / windowS;
|
|
12449
|
+
frameMs = windowS / frames * 1e3;
|
|
12450
|
+
frames = 0;
|
|
12451
|
+
windowS = 0;
|
|
12452
|
+
},
|
|
12453
|
+
rendered() {
|
|
12454
|
+
ownsRender = true;
|
|
12455
|
+
calls = gl.info?.render?.calls;
|
|
12456
|
+
triangles = gl.info?.render?.triangles;
|
|
12457
|
+
},
|
|
12458
|
+
sample() {
|
|
12459
|
+
const out = {};
|
|
12460
|
+
const put = (id, value) => {
|
|
12461
|
+
if (wanted.has(id) && value !== void 0) out[id] = value;
|
|
12462
|
+
};
|
|
12463
|
+
put("fps", fps);
|
|
12464
|
+
put("frame_ms", frameMs);
|
|
12465
|
+
const render = ownsRender ? { calls, triangles } : gl.info?.render;
|
|
12466
|
+
put("draw_calls", render?.calls);
|
|
12467
|
+
put("triangles", render?.triangles);
|
|
12468
|
+
if (!["objects", "fragments", "particles", "decals"].some((id) => wanted.has(id))) return out;
|
|
12469
|
+
const destruction = sources.destruction.stats();
|
|
12470
|
+
const fluids = sources.fluids.stats();
|
|
12471
|
+
put("objects", destruction.pieces);
|
|
12472
|
+
put("fragments", destruction.fragments);
|
|
12473
|
+
put("particles", destruction.particles + fluids.particles);
|
|
12474
|
+
put("decals", destruction.decals + fluids.stains);
|
|
12475
|
+
return out;
|
|
12476
|
+
}
|
|
12477
|
+
};
|
|
12478
|
+
}
|
|
12479
|
+
|
|
12204
12480
|
// src/narrate.ts
|
|
12205
12481
|
var SKIP_DAMAGE = /* @__PURE__ */ new Set(["fire", "heat"]);
|
|
12206
12482
|
function createNarrator(c) {
|
|
@@ -13270,6 +13546,209 @@ void main() {
|
|
|
13270
13546
|
}
|
|
13271
13547
|
var SCENARIOS = scenariosFor("en");
|
|
13272
13548
|
|
|
13549
|
+
// src/bench-panel.ts
|
|
13550
|
+
var SEGMENTED_MAX = 4;
|
|
13551
|
+
var mk = (tag, cls, text) => {
|
|
13552
|
+
const e = document.createElement(tag);
|
|
13553
|
+
if (cls) e.className = cls;
|
|
13554
|
+
if (text !== void 0) e.textContent = text;
|
|
13555
|
+
return e;
|
|
13556
|
+
};
|
|
13557
|
+
var shapeOf = (view) => JSON.stringify([view.title, view.controls.map((c) => ({ ...c, value: void 0 })), view.layers.map((l) => [l.id, l.name])]);
|
|
13558
|
+
function createBenchPanel(o) {
|
|
13559
|
+
const { t } = o;
|
|
13560
|
+
const element = mk("div", "tr-panel tr-bench");
|
|
13561
|
+
element.style.display = "none";
|
|
13562
|
+
element.addEventListener("keydown", (e) => e.stopPropagation());
|
|
13563
|
+
const roomLine = mk("div", "sub tr-bench-room");
|
|
13564
|
+
const body = mk("div");
|
|
13565
|
+
const metricTitle = mk("div", "sub", t("ui.metrics"));
|
|
13566
|
+
metricTitle.style.display = "none";
|
|
13567
|
+
const metricList = mk("dl", "tr-metrics");
|
|
13568
|
+
element.append(mk("h3", void 0, t("ui.bench")), roomLine, body, metricTitle, metricList);
|
|
13569
|
+
let shape = "";
|
|
13570
|
+
let hasRoom = false;
|
|
13571
|
+
let hasBench = false;
|
|
13572
|
+
const refreshers = [];
|
|
13573
|
+
const metricNodes = /* @__PURE__ */ new Map();
|
|
13574
|
+
let metricKey = "";
|
|
13575
|
+
const syncDisplay = () => {
|
|
13576
|
+
element.style.display = hasRoom || hasBench ? "" : "none";
|
|
13577
|
+
};
|
|
13578
|
+
const numberControl = (control) => {
|
|
13579
|
+
const wrap = mk("label", "tr-ctl");
|
|
13580
|
+
const input = mk("input");
|
|
13581
|
+
input.type = "range";
|
|
13582
|
+
input.min = String(control.min);
|
|
13583
|
+
input.max = String(control.max);
|
|
13584
|
+
input.step = control.step ? String(control.step) : "any";
|
|
13585
|
+
input.disabled = control.disabled;
|
|
13586
|
+
const reading = mk("output");
|
|
13587
|
+
input.oninput = () => o.onControl(control.id, Number(input.value));
|
|
13588
|
+
wrap.append(mk("span", void 0, control.name), input, reading);
|
|
13589
|
+
refreshers.push((view) => {
|
|
13590
|
+
const value = view.controls.find((c) => c.id === control.id)?.value;
|
|
13591
|
+
if (typeof value !== "number") return;
|
|
13592
|
+
if (Number(input.value) !== value) input.value = String(value);
|
|
13593
|
+
reading.textContent = `${o.num(value, control.step && control.step >= 1 ? 0 : 2)}${control.unit ? ` ${control.unit}` : ""}`;
|
|
13594
|
+
});
|
|
13595
|
+
return wrap;
|
|
13596
|
+
};
|
|
13597
|
+
const booleanControl = (control) => {
|
|
13598
|
+
const wrap = mk("label", "tr-ctl");
|
|
13599
|
+
const box2 = mk("input");
|
|
13600
|
+
box2.type = "checkbox";
|
|
13601
|
+
box2.disabled = control.disabled;
|
|
13602
|
+
box2.onchange = () => o.onControl(control.id, box2.checked);
|
|
13603
|
+
wrap.append(box2, document.createTextNode(` ${control.name}`));
|
|
13604
|
+
refreshers.push((view) => {
|
|
13605
|
+
box2.checked = view.controls.find((c) => c.id === control.id)?.value === true;
|
|
13606
|
+
});
|
|
13607
|
+
return wrap;
|
|
13608
|
+
};
|
|
13609
|
+
const choiceControl = (control) => {
|
|
13610
|
+
const wrap = mk("div", "tr-ctl");
|
|
13611
|
+
wrap.appendChild(mk("span", void 0, control.name));
|
|
13612
|
+
if (control.options.length <= SEGMENTED_MAX) {
|
|
13613
|
+
const group = mk("div", "tr-seg");
|
|
13614
|
+
group.setAttribute("role", "group");
|
|
13615
|
+
const buttons = control.options.map((option) => {
|
|
13616
|
+
const b = mk("button", "tr-btn", option.name);
|
|
13617
|
+
b.type = "button";
|
|
13618
|
+
b.disabled = control.disabled;
|
|
13619
|
+
b.onclick = () => o.onControl(control.id, option.id);
|
|
13620
|
+
group.appendChild(b);
|
|
13621
|
+
return { b, id: option.id };
|
|
13622
|
+
});
|
|
13623
|
+
wrap.appendChild(group);
|
|
13624
|
+
refreshers.push((view) => {
|
|
13625
|
+
const value = view.controls.find((c) => c.id === control.id)?.value;
|
|
13626
|
+
for (const { b, id } of buttons) {
|
|
13627
|
+
b.setAttribute("aria-pressed", String(id === value));
|
|
13628
|
+
b.classList.toggle("on", id === value);
|
|
13629
|
+
}
|
|
13630
|
+
});
|
|
13631
|
+
return wrap;
|
|
13632
|
+
}
|
|
13633
|
+
const select = mk("select");
|
|
13634
|
+
select.disabled = control.disabled;
|
|
13635
|
+
for (const option of control.options) {
|
|
13636
|
+
const op = mk("option", void 0, option.name);
|
|
13637
|
+
op.value = option.id;
|
|
13638
|
+
select.appendChild(op);
|
|
13639
|
+
}
|
|
13640
|
+
select.onchange = () => o.onControl(control.id, select.value);
|
|
13641
|
+
wrap.appendChild(select);
|
|
13642
|
+
refreshers.push((view) => {
|
|
13643
|
+
const value = view.controls.find((c) => c.id === control.id)?.value;
|
|
13644
|
+
if (typeof value === "string") select.value = value;
|
|
13645
|
+
});
|
|
13646
|
+
return wrap;
|
|
13647
|
+
};
|
|
13648
|
+
const actionControl = (control) => {
|
|
13649
|
+
const b = mk("button", "tr-btn", control.name);
|
|
13650
|
+
b.type = "button";
|
|
13651
|
+
b.disabled = control.disabled;
|
|
13652
|
+
b.onclick = () => o.onAction(control.id);
|
|
13653
|
+
return b;
|
|
13654
|
+
};
|
|
13655
|
+
const controlElement = (control) => {
|
|
13656
|
+
const el = control.kind === "number" ? numberControl(control) : control.kind === "boolean" ? booleanControl(control) : control.kind === "choice" ? choiceControl(control) : actionControl(control);
|
|
13657
|
+
if (control.hint) el.title = control.hint;
|
|
13658
|
+
return el;
|
|
13659
|
+
};
|
|
13660
|
+
const layerList = (layers) => {
|
|
13661
|
+
const wrap = mk("div", "tr-layers");
|
|
13662
|
+
wrap.appendChild(mk("div", "sub", t("ui.layers")));
|
|
13663
|
+
for (const layer of layers) {
|
|
13664
|
+
const label = mk("label", "tr-ctl");
|
|
13665
|
+
const box2 = mk("input");
|
|
13666
|
+
box2.type = "checkbox";
|
|
13667
|
+
box2.onchange = () => o.onLayer(layer.id, box2.checked);
|
|
13668
|
+
label.append(box2, document.createTextNode(` ${layer.name}`));
|
|
13669
|
+
wrap.appendChild(label);
|
|
13670
|
+
refreshers.push((view) => {
|
|
13671
|
+
box2.checked = view.layers.find((l) => l.id === layer.id)?.on === true;
|
|
13672
|
+
});
|
|
13673
|
+
}
|
|
13674
|
+
return wrap;
|
|
13675
|
+
};
|
|
13676
|
+
const rebuild = (view) => {
|
|
13677
|
+
body.textContent = "";
|
|
13678
|
+
refreshers.length = 0;
|
|
13679
|
+
body.appendChild(mk("div", "sub", view.title));
|
|
13680
|
+
const list = mk("div", "tr-ctls");
|
|
13681
|
+
for (const control of view.controls) if (!control.hidden) list.appendChild(controlElement(control));
|
|
13682
|
+
body.appendChild(list);
|
|
13683
|
+
if (view.layers.length) body.appendChild(layerList(view.layers));
|
|
13684
|
+
};
|
|
13685
|
+
const renderMetrics = (rows) => {
|
|
13686
|
+
metricList.textContent = "";
|
|
13687
|
+
metricNodes.clear();
|
|
13688
|
+
metricTitle.style.display = rows.length ? "" : "none";
|
|
13689
|
+
for (const row of rows) {
|
|
13690
|
+
const dd = mk("dd");
|
|
13691
|
+
const text = mk("span", void 0, row.text);
|
|
13692
|
+
dd.appendChild(text);
|
|
13693
|
+
let bar = null;
|
|
13694
|
+
if (row.ratio !== void 0) {
|
|
13695
|
+
bar = mk("div", "tr-bar");
|
|
13696
|
+
bar.appendChild(mk("i"));
|
|
13697
|
+
dd.appendChild(bar);
|
|
13698
|
+
}
|
|
13699
|
+
metricList.append(mk("dt", void 0, row.name), dd);
|
|
13700
|
+
metricNodes.set(row.id, { text, bar });
|
|
13701
|
+
}
|
|
13702
|
+
};
|
|
13703
|
+
const fillBar = (bar, ratio) => {
|
|
13704
|
+
bar.className = `tr-bar${ratio > 1 ? " bad" : ratio >= 0.8 ? " warn" : ""}`;
|
|
13705
|
+
const fill = bar.firstElementChild;
|
|
13706
|
+
fill.style.width = `${Math.round(Math.max(0, Math.min(1, ratio)) * 100)}%`;
|
|
13707
|
+
};
|
|
13708
|
+
return {
|
|
13709
|
+
element,
|
|
13710
|
+
show(view) {
|
|
13711
|
+
hasBench = view !== null;
|
|
13712
|
+
syncDisplay();
|
|
13713
|
+
if (!view) {
|
|
13714
|
+
shape = "";
|
|
13715
|
+
body.textContent = "";
|
|
13716
|
+
refreshers.length = 0;
|
|
13717
|
+
renderMetrics([]);
|
|
13718
|
+
metricKey = "";
|
|
13719
|
+
return;
|
|
13720
|
+
}
|
|
13721
|
+
const next = shapeOf(view);
|
|
13722
|
+
if (next !== shape) {
|
|
13723
|
+
shape = next;
|
|
13724
|
+
rebuild(view);
|
|
13725
|
+
}
|
|
13726
|
+
for (const refresh of refreshers) refresh(view);
|
|
13727
|
+
},
|
|
13728
|
+
metrics(rows) {
|
|
13729
|
+
hasRoom = rows.room.length > 0;
|
|
13730
|
+
syncDisplay();
|
|
13731
|
+
roomLine.textContent = rows.room.map((row) => `${row.name} ${row.text}`).join(" \xB7 ");
|
|
13732
|
+
const key = rows.module.map((row) => `${row.id}:${row.ratio !== void 0}`).join("|");
|
|
13733
|
+
if (key !== metricKey) {
|
|
13734
|
+
metricKey = key;
|
|
13735
|
+
renderMetrics(rows.module);
|
|
13736
|
+
}
|
|
13737
|
+
for (const row of rows.module) {
|
|
13738
|
+
const node = metricNodes.get(row.id);
|
|
13739
|
+
if (!node) continue;
|
|
13740
|
+
node.text.textContent = row.text;
|
|
13741
|
+
if (node.bar && row.ratio !== void 0) fillBar(node.bar, row.ratio);
|
|
13742
|
+
}
|
|
13743
|
+
},
|
|
13744
|
+
get visible() {
|
|
13745
|
+
if (element.style.display === "none") return false;
|
|
13746
|
+
const parent = element.parentElement;
|
|
13747
|
+
return !parent || getComputedStyle(parent).display !== "none";
|
|
13748
|
+
}
|
|
13749
|
+
};
|
|
13750
|
+
}
|
|
13751
|
+
|
|
13273
13752
|
// src/ui.ts
|
|
13274
13753
|
var CSS = `
|
|
13275
13754
|
.tr-root{position:absolute;inset:0;pointer-events:none;font:12px/1.35 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;color:#e8eaee;overflow:hidden}
|
|
@@ -13322,6 +13801,13 @@ void main() {
|
|
|
13322
13801
|
.tr-cat{flex:0 1 auto;max-height:40%}
|
|
13323
13802
|
.tr-anim{flex:0 0 auto;max-height:18%}
|
|
13324
13803
|
.tr-anim .list{display:flex;flex-wrap:wrap;gap:4px}.tr-anim .sub{color:#9aa3b2;margin-bottom:4px}
|
|
13804
|
+
.tr-bench{flex:0 1 auto;max-height:34%}
|
|
13805
|
+
.tr-bench .sub{color:#9aa3b2;margin-bottom:4px}
|
|
13806
|
+
.tr-ctls{display:flex;flex-direction:column;gap:5px}
|
|
13807
|
+
.tr-ctl{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.tr-ctl input[type=range]{flex:1;min-width:90px}.tr-ctl output{min-width:48px;text-align:right;color:#cfd3da}
|
|
13808
|
+
.tr-seg{display:inline-flex;flex-wrap:wrap;gap:2px}
|
|
13809
|
+
.tr-layers{margin-top:6px;display:flex;flex-direction:column;gap:2px}
|
|
13810
|
+
.tr-metrics{display:grid;grid-template-columns:auto 1fr;gap:2px 8px;margin:6px 0 0}.tr-metrics dt{color:#9aa3b2}.tr-metrics dd{margin:0}
|
|
13325
13811
|
.tr-tests{flex:1 1 auto;min-height:100px}
|
|
13326
13812
|
.tr-tests .list{display:flex;flex-direction:column;gap:3px;margin-top:6px}.tr-tests .list .tr-btn{text-align:left}
|
|
13327
13813
|
.tr-row{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;align-items:center}
|
|
@@ -13330,7 +13816,7 @@ void main() {
|
|
|
13330
13816
|
@media (max-width:980px){.tr-left,.tr-right{display:none}.tr-root.open .tr-left,.tr-root.open .tr-right{display:flex}.tr-toggle{display:block;pointer-events:auto}.tr-root.open .tr-toggle{right:min(310px,34vw)}.tr-left{width:min(400px,46vw)}.tr-help{display:none}}
|
|
13331
13817
|
`;
|
|
13332
13818
|
var fold = (text) => text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
13333
|
-
var
|
|
13819
|
+
var mk2 = (tag, cls, text) => {
|
|
13334
13820
|
const e = document.createElement(tag);
|
|
13335
13821
|
if (cls) e.className = cls;
|
|
13336
13822
|
if (text !== void 0) e.textContent = text;
|
|
@@ -13338,42 +13824,42 @@ void main() {
|
|
|
13338
13824
|
};
|
|
13339
13825
|
function createUi(o) {
|
|
13340
13826
|
const { t } = o;
|
|
13341
|
-
const style =
|
|
13827
|
+
const style = mk2("style");
|
|
13342
13828
|
style.textContent = CSS;
|
|
13343
|
-
const root =
|
|
13829
|
+
const root = mk2("div", "tr-root");
|
|
13344
13830
|
const pos = getComputedStyle?.(o.container)?.position;
|
|
13345
13831
|
if (!pos || pos === "static") o.container.style.position = "relative";
|
|
13346
13832
|
root.appendChild(style);
|
|
13347
13833
|
o.container.appendChild(root);
|
|
13348
|
-
const cross4 =
|
|
13349
|
-
cross4.appendChild(
|
|
13350
|
-
const top =
|
|
13351
|
-
const toolBox =
|
|
13352
|
-
const status =
|
|
13353
|
-
const prog =
|
|
13354
|
-
const progLabel =
|
|
13355
|
-
const progBar =
|
|
13356
|
-
const progFill =
|
|
13834
|
+
const cross4 = mk2("div", "tr-cross");
|
|
13835
|
+
cross4.appendChild(mk2("i"));
|
|
13836
|
+
const top = mk2("div", "tr-top");
|
|
13837
|
+
const toolBox = mk2("div", "tr-tool");
|
|
13838
|
+
const status = mk2("div", "tr-status");
|
|
13839
|
+
const prog = mk2("div", "tr-prog");
|
|
13840
|
+
const progLabel = mk2("b");
|
|
13841
|
+
const progBar = mk2("div", "tr-bar");
|
|
13842
|
+
const progFill = mk2("i");
|
|
13357
13843
|
progBar.appendChild(progFill);
|
|
13358
13844
|
prog.append(progLabel, progBar);
|
|
13359
13845
|
top.append(toolBox, status, prog);
|
|
13360
|
-
const bottom =
|
|
13361
|
-
const held =
|
|
13362
|
-
const hotbar =
|
|
13846
|
+
const bottom = mk2("div", "tr-bottom");
|
|
13847
|
+
const held = mk2("div", "tr-held");
|
|
13848
|
+
const hotbar = mk2("div", "tr-hotbar");
|
|
13363
13849
|
bottom.append(held, hotbar);
|
|
13364
|
-
const help =
|
|
13850
|
+
const help = mk2("div", "tr-help");
|
|
13365
13851
|
help.textContent = t("ui.help");
|
|
13366
|
-
const left =
|
|
13367
|
-
const insp =
|
|
13368
|
-
const logPanel =
|
|
13852
|
+
const left = mk2("div", "tr-left");
|
|
13853
|
+
const insp = mk2("div", "tr-panel tr-insp");
|
|
13854
|
+
const logPanel = mk2("div", "tr-panel tr-log");
|
|
13369
13855
|
left.append(insp, logPanel);
|
|
13370
|
-
const logTitle =
|
|
13371
|
-
const logBar =
|
|
13372
|
-
const logBody =
|
|
13856
|
+
const logTitle = mk2("h3", void 0, t("ui.log"));
|
|
13857
|
+
const logBar = mk2("div", "tr-logbar");
|
|
13858
|
+
const logBody = mk2("div", "tr-logbody");
|
|
13373
13859
|
const filters = { action: true, event: true, check: true };
|
|
13374
13860
|
const filterBtns = [];
|
|
13375
13861
|
for (const ch of ["action", "event", "check"]) {
|
|
13376
|
-
const b =
|
|
13862
|
+
const b = mk2("button", "tr-btn on", t(`ui.filter.${ch}`));
|
|
13377
13863
|
b.type = "button";
|
|
13378
13864
|
b.onclick = () => {
|
|
13379
13865
|
filters[ch] = !filters[ch];
|
|
@@ -13384,8 +13870,8 @@ void main() {
|
|
|
13384
13870
|
logBar.appendChild(b);
|
|
13385
13871
|
}
|
|
13386
13872
|
let autoscroll = true;
|
|
13387
|
-
const asLabel =
|
|
13388
|
-
const asBox =
|
|
13873
|
+
const asLabel = mk2("label");
|
|
13874
|
+
const asBox = mk2("input");
|
|
13389
13875
|
asBox.type = "checkbox";
|
|
13390
13876
|
asBox.checked = true;
|
|
13391
13877
|
asBox.onchange = () => {
|
|
@@ -13394,7 +13880,7 @@ void main() {
|
|
|
13394
13880
|
asLabel.append(asBox, document.createTextNode(" " + t("ui.autoscroll")));
|
|
13395
13881
|
logBar.appendChild(asLabel);
|
|
13396
13882
|
const logBtn = (label, fn) => {
|
|
13397
|
-
const b =
|
|
13883
|
+
const b = mk2("button", "tr-btn", label);
|
|
13398
13884
|
b.type = "button";
|
|
13399
13885
|
b.onclick = fn;
|
|
13400
13886
|
logBar.appendChild(b);
|
|
@@ -13402,7 +13888,7 @@ void main() {
|
|
|
13402
13888
|
logBtn(t("ui.copy_json"), () => {
|
|
13403
13889
|
const text = o.log.toJSON();
|
|
13404
13890
|
const select = () => {
|
|
13405
|
-
const ta =
|
|
13891
|
+
const ta = mk2("textarea");
|
|
13406
13892
|
ta.value = text;
|
|
13407
13893
|
ta.setAttribute("readonly", "");
|
|
13408
13894
|
ta.style.cssText = "position:fixed;left:8px;bottom:8px;width:min(480px,80vw);height:120px;z-index:99999";
|
|
@@ -13436,12 +13922,12 @@ void main() {
|
|
|
13436
13922
|
const fill = (n, e) => {
|
|
13437
13923
|
n.className = lineClass(e);
|
|
13438
13924
|
n.textContent = "";
|
|
13439
|
-
const tm =
|
|
13925
|
+
const tm = mk2("time", void 0, fmtTime(e.t));
|
|
13440
13926
|
n.append(tm, document.createTextNode(e.text));
|
|
13441
13927
|
};
|
|
13442
13928
|
const addNode = (e) => {
|
|
13443
13929
|
if (!filters[e.channel]) return;
|
|
13444
|
-
const n =
|
|
13930
|
+
const n = mk2("div");
|
|
13445
13931
|
fill(n, e);
|
|
13446
13932
|
nodes.set(e.seq, n);
|
|
13447
13933
|
logBody.appendChild(n);
|
|
@@ -13463,17 +13949,18 @@ void main() {
|
|
|
13463
13949
|
if (n) fill(n, e);
|
|
13464
13950
|
} else addNode(e);
|
|
13465
13951
|
});
|
|
13466
|
-
const right =
|
|
13467
|
-
const cat =
|
|
13468
|
-
const anim =
|
|
13469
|
-
const tests =
|
|
13470
|
-
|
|
13471
|
-
|
|
13472
|
-
|
|
13952
|
+
const right = mk2("div", "tr-right");
|
|
13953
|
+
const cat = mk2("div", "tr-panel tr-cat");
|
|
13954
|
+
const anim = mk2("div", "tr-panel tr-anim");
|
|
13955
|
+
const tests = mk2("div", "tr-panel tr-tests");
|
|
13956
|
+
const benchPanel = createBenchPanel({ t, num: (n, d) => num(n, d), onControl: o.onControl, onLayer: o.onLayer, onAction: o.onAction });
|
|
13957
|
+
right.append(cat, anim, benchPanel.element, tests);
|
|
13958
|
+
cat.appendChild(mk2("h3", void 0, t("ui.catalog")));
|
|
13959
|
+
const itemSearch = mk2("input", "tr-search");
|
|
13473
13960
|
itemSearch.type = "search";
|
|
13474
13961
|
itemSearch.placeholder = t("ui.search_items");
|
|
13475
|
-
const tabs =
|
|
13476
|
-
const items =
|
|
13962
|
+
const tabs = mk2("div", "tr-tabs");
|
|
13963
|
+
const items = mk2("div", "tr-items");
|
|
13477
13964
|
cat.append(itemSearch, tabs, items);
|
|
13478
13965
|
let activeGroup = "";
|
|
13479
13966
|
const visibleItems = () => {
|
|
@@ -13485,17 +13972,17 @@ void main() {
|
|
|
13485
13972
|
items.textContent = "";
|
|
13486
13973
|
const searching = itemSearch.value.trim() !== "";
|
|
13487
13974
|
const found = visibleItems();
|
|
13488
|
-
if (!found.length) items.appendChild(
|
|
13975
|
+
if (!found.length) items.appendChild(mk2("div", "tr-empty", t("ui.no_results")));
|
|
13489
13976
|
for (const { item: it, group } of found) {
|
|
13490
|
-
const wrap =
|
|
13491
|
-
const b =
|
|
13492
|
-
if (searching) b.appendChild(
|
|
13977
|
+
const wrap = mk2("div", "tr-item");
|
|
13978
|
+
const b = mk2("button", "tr-btn", it.name);
|
|
13979
|
+
if (searching) b.appendChild(mk2("small", void 0, group));
|
|
13493
13980
|
b.type = "button";
|
|
13494
13981
|
b.title = it.note ?? it.name;
|
|
13495
13982
|
b.onclick = () => o.onSpawn(it.id);
|
|
13496
13983
|
wrap.appendChild(b);
|
|
13497
13984
|
if (it.weapon) {
|
|
13498
|
-
const w =
|
|
13985
|
+
const w = mk2("button", "tr-btn", "\u2694");
|
|
13499
13986
|
w.type = "button";
|
|
13500
13987
|
w.title = t("ui.equip");
|
|
13501
13988
|
w.onclick = () => o.onEquip(it.id);
|
|
@@ -13509,7 +13996,7 @@ void main() {
|
|
|
13509
13996
|
const groupNames = [...o.groups().keys()];
|
|
13510
13997
|
if (!groupNames.includes(activeGroup)) activeGroup = groupNames[0] ?? "";
|
|
13511
13998
|
for (const g of groupNames) {
|
|
13512
|
-
const b =
|
|
13999
|
+
const b = mk2("button", g === activeGroup ? "tr-btn on" : "tr-btn", g);
|
|
13513
14000
|
b.type = "button";
|
|
13514
14001
|
b.onclick = () => {
|
|
13515
14002
|
activeGroup = g;
|
|
@@ -13522,9 +14009,9 @@ void main() {
|
|
|
13522
14009
|
renderTabs();
|
|
13523
14010
|
itemSearch.oninput = renderItems;
|
|
13524
14011
|
for (const box2 of [itemSearch]) box2.addEventListener("keydown", (e) => e.stopPropagation());
|
|
13525
|
-
const controls =
|
|
14012
|
+
const controls = mk2("div", "tr-row");
|
|
13526
14013
|
const ctl = (label, fn) => {
|
|
13527
|
-
const b =
|
|
14014
|
+
const b = mk2("button", "tr-btn", label);
|
|
13528
14015
|
b.type = "button";
|
|
13529
14016
|
b.onclick = () => fn(b);
|
|
13530
14017
|
controls.appendChild(b);
|
|
@@ -13545,29 +14032,29 @@ void main() {
|
|
|
13545
14032
|
o.onFast(fast);
|
|
13546
14033
|
});
|
|
13547
14034
|
const tapBtn = ctl(t("ui.tap"), () => o.onTap());
|
|
13548
|
-
const qSel =
|
|
14035
|
+
const qSel = mk2("select");
|
|
13549
14036
|
for (const q of ["low", "medium", "high"]) {
|
|
13550
|
-
const op =
|
|
14037
|
+
const op = mk2("option", void 0, t(`ui.quality.${q}`));
|
|
13551
14038
|
op.value = q;
|
|
13552
14039
|
qSel.appendChild(op);
|
|
13553
14040
|
}
|
|
13554
14041
|
qSel.value = o.quality;
|
|
13555
14042
|
qSel.onchange = () => o.onQuality(qSel.value);
|
|
13556
|
-
const qLabel =
|
|
14043
|
+
const qLabel = mk2("label", void 0, t("ui.quality") + " ");
|
|
13557
14044
|
qLabel.appendChild(qSel);
|
|
13558
14045
|
controls.appendChild(qLabel);
|
|
13559
|
-
const count =
|
|
14046
|
+
const count = mk2("span");
|
|
13560
14047
|
controls.appendChild(count);
|
|
13561
14048
|
cat.appendChild(controls);
|
|
13562
|
-
tests.appendChild(
|
|
13563
|
-
const runAll =
|
|
14049
|
+
tests.appendChild(mk2("h3", void 0, t("ui.tests")));
|
|
14050
|
+
const runAll = mk2("button", "tr-btn", t("ui.run_all"));
|
|
13564
14051
|
runAll.type = "button";
|
|
13565
|
-
const summary =
|
|
13566
|
-
const testSearch =
|
|
14052
|
+
const summary = mk2("div", "tr-summary");
|
|
14053
|
+
const testSearch = mk2("input", "tr-search");
|
|
13567
14054
|
testSearch.type = "search";
|
|
13568
14055
|
testSearch.placeholder = t("ui.search_tests");
|
|
13569
14056
|
testSearch.addEventListener("keydown", (e) => e.stopPropagation());
|
|
13570
|
-
const list =
|
|
14057
|
+
const list = mk2("div", "list");
|
|
13571
14058
|
tests.append(runAll, summary, testSearch, list);
|
|
13572
14059
|
testSearch.oninput = () => {
|
|
13573
14060
|
const term = fold(testSearch.value.trim());
|
|
@@ -13590,7 +14077,7 @@ void main() {
|
|
|
13590
14077
|
list.textContent = "";
|
|
13591
14078
|
scBtns.clear();
|
|
13592
14079
|
for (const s of o.scenarios()) {
|
|
13593
|
-
const b =
|
|
14080
|
+
const b = mk2("button", "tr-btn", s.name);
|
|
13594
14081
|
b.type = "button";
|
|
13595
14082
|
b.onclick = async () => {
|
|
13596
14083
|
if (busy) return;
|
|
@@ -13616,7 +14103,7 @@ void main() {
|
|
|
13616
14103
|
busy = false;
|
|
13617
14104
|
}
|
|
13618
14105
|
};
|
|
13619
|
-
const toggle =
|
|
14106
|
+
const toggle = mk2("button", "tr-btn tr-toggle", t("ui.panel"));
|
|
13620
14107
|
toggle.type = "button";
|
|
13621
14108
|
const togglePanels = () => {
|
|
13622
14109
|
root.classList.toggle("open");
|
|
@@ -13625,15 +14112,15 @@ void main() {
|
|
|
13625
14112
|
toggle.onclick = togglePanels;
|
|
13626
14113
|
root.append(cross4, top, bottom, help, left, right, toggle);
|
|
13627
14114
|
const bar = (value, max, cls) => {
|
|
13628
|
-
const b =
|
|
13629
|
-
const i =
|
|
14115
|
+
const b = mk2("div", `tr-bar ${cls}`);
|
|
14116
|
+
const i = mk2("i");
|
|
13630
14117
|
i.style.width = `${Math.max(0, Math.min(100, value / Math.max(1e-9, max) * 100))}%`;
|
|
13631
14118
|
b.appendChild(i);
|
|
13632
14119
|
return b;
|
|
13633
14120
|
};
|
|
13634
14121
|
const row = (k, v) => {
|
|
13635
|
-
const r =
|
|
13636
|
-
r.append(
|
|
14122
|
+
const r = mk2("div", "r");
|
|
14123
|
+
r.append(mk2("span", void 0, k), mk2("span", void 0, v));
|
|
13637
14124
|
return r;
|
|
13638
14125
|
};
|
|
13639
14126
|
const num = (n, d = 1) => {
|
|
@@ -13642,12 +14129,12 @@ void main() {
|
|
|
13642
14129
|
};
|
|
13643
14130
|
const renderInspector = (i) => {
|
|
13644
14131
|
insp.textContent = "";
|
|
13645
|
-
insp.appendChild(
|
|
14132
|
+
insp.appendChild(mk2("h3", void 0, t("ui.inspector")));
|
|
13646
14133
|
if (!i) {
|
|
13647
|
-
insp.appendChild(
|
|
14134
|
+
insp.appendChild(mk2("div", void 0, t("ui.aim_at_something")));
|
|
13648
14135
|
return;
|
|
13649
14136
|
}
|
|
13650
|
-
insp.appendChild(
|
|
14137
|
+
insp.appendChild(mk2("div", "t", `${i.object} \u203A ${i.part}`));
|
|
13651
14138
|
insp.appendChild(row(t("ins.material"), i.material));
|
|
13652
14139
|
insp.appendChild(row(t("ins.status"), i.status + (i.burning ? ` \xB7 ${t("ins.burning")}` : "")));
|
|
13653
14140
|
insp.appendChild(row(t("ins.hp"), `${num(i.hp, 0)} / ${num(i.maxHp, 0)}`));
|
|
@@ -13666,30 +14153,30 @@ void main() {
|
|
|
13666
14153
|
insp.appendChild(bar(Math.min(i.stress, 1.2), 1.2, i.stress > 1 ? "bad" : i.stress > 0.7 ? "warn" : "hp"));
|
|
13667
14154
|
}
|
|
13668
14155
|
if (i.joints.length) {
|
|
13669
|
-
insp.appendChild(
|
|
14156
|
+
insp.appendChild(mk2("div", void 0, t("ins.joints") + ":"));
|
|
13670
14157
|
for (const j of i.joints) {
|
|
13671
14158
|
const tools = j.tools.length ? j.tools.join(", ") : t("tool.none");
|
|
13672
|
-
insp.appendChild(
|
|
14159
|
+
insp.appendChild(mk2("div", void 0, `\u2022 ${j.kind} (${j.id})${j.open ? ` \xB7 ${t("ins.open")}` : ` \u2192 ${tools}`}`));
|
|
13673
14160
|
}
|
|
13674
14161
|
}
|
|
13675
|
-
if (i.note) insp.appendChild(
|
|
14162
|
+
if (i.note) insp.appendChild(mk2("div", void 0, i.note));
|
|
13676
14163
|
};
|
|
13677
14164
|
let lastAnim = "";
|
|
13678
14165
|
const renderAnimations = (a) => {
|
|
13679
14166
|
anim.textContent = "";
|
|
13680
|
-
anim.appendChild(
|
|
14167
|
+
anim.appendChild(mk2("h3", void 0, t("ui.animations")));
|
|
13681
14168
|
if (!a) {
|
|
13682
|
-
anim.appendChild(
|
|
14169
|
+
anim.appendChild(mk2("div", "sub", t("ui.animations_aim")));
|
|
13683
14170
|
return;
|
|
13684
14171
|
}
|
|
13685
|
-
anim.appendChild(
|
|
14172
|
+
anim.appendChild(mk2("div", "sub", a.name));
|
|
13686
14173
|
if (a.clips.length === 0) {
|
|
13687
|
-
anim.appendChild(
|
|
14174
|
+
anim.appendChild(mk2("div", "sub", t("ui.animations_none")));
|
|
13688
14175
|
return;
|
|
13689
14176
|
}
|
|
13690
|
-
const l =
|
|
14177
|
+
const l = mk2("div", "list");
|
|
13691
14178
|
for (const c of a.clips) {
|
|
13692
|
-
const b =
|
|
14179
|
+
const b = mk2("button", a.playing === c.id ? "tr-btn on" : "tr-btn", `\u25B6 ${t(`anim.${c.id}`) === `anim.${c.id}` ? c.id : t(`anim.${c.id}`)} (${num(c.duration, 1)} s)`);
|
|
13693
14180
|
b.type = "button";
|
|
13694
14181
|
b.onclick = () => o.onPlay(a.objectId, c.id);
|
|
13695
14182
|
l.appendChild(b);
|
|
@@ -13706,7 +14193,7 @@ void main() {
|
|
|
13706
14193
|
hud(s) {
|
|
13707
14194
|
toolBox.textContent = "";
|
|
13708
14195
|
toolBox.append(document.createTextNode(s.toolName));
|
|
13709
|
-
if (s.ammo) toolBox.append(
|
|
14196
|
+
if (s.ammo) toolBox.append(mk2("small", void 0, s.ammo));
|
|
13710
14197
|
if (s.status) status.textContent = s.status;
|
|
13711
14198
|
else if (!toastTimer) status.textContent = "";
|
|
13712
14199
|
if (s.progress) {
|
|
@@ -13727,7 +14214,7 @@ void main() {
|
|
|
13727
14214
|
];
|
|
13728
14215
|
if (s.held.weapon) keys.push(["H", t("hud.equip", { weapon: s.held.weapon })]);
|
|
13729
14216
|
for (const [k, label] of keys) {
|
|
13730
|
-
held.append(
|
|
14217
|
+
held.append(mk2("kbd", void 0, k), document.createTextNode(` ${label} `));
|
|
13731
14218
|
}
|
|
13732
14219
|
} else held.style.display = "none";
|
|
13733
14220
|
const key = s.slots.join("|") + s.page;
|
|
@@ -13737,9 +14224,9 @@ void main() {
|
|
|
13737
14224
|
slotsEl.length = 0;
|
|
13738
14225
|
hudPage = s.page - 1;
|
|
13739
14226
|
s.slots.forEach((name, i) => {
|
|
13740
|
-
const b =
|
|
14227
|
+
const b = mk2("button", "tr-slot");
|
|
13741
14228
|
b.type = "button";
|
|
13742
|
-
b.append(
|
|
14229
|
+
b.append(mk2("b", void 0, String((i + 1) % 10)), document.createTextNode(name));
|
|
13743
14230
|
b.title = name;
|
|
13744
14231
|
b.onclick = () => o.onTool(hudPage * 10 + i);
|
|
13745
14232
|
hotbar.appendChild(b);
|
|
@@ -13777,6 +14264,11 @@ void main() {
|
|
|
13777
14264
|
renderTabs();
|
|
13778
14265
|
},
|
|
13779
14266
|
togglePanels,
|
|
14267
|
+
bench: benchPanel.show,
|
|
14268
|
+
metrics: benchPanel.metrics,
|
|
14269
|
+
get benchVisible() {
|
|
14270
|
+
return benchPanel.visible;
|
|
14271
|
+
},
|
|
13780
14272
|
dispose() {
|
|
13781
14273
|
offLog();
|
|
13782
14274
|
if (toastTimer) clearTimeout(toastTimer);
|
|
@@ -13789,6 +14281,7 @@ void main() {
|
|
|
13789
14281
|
|
|
13790
14282
|
// src/room.ts
|
|
13791
14283
|
var FIXED_DT = 1 / 60;
|
|
14284
|
+
var METRICS_INTERVAL_S = 0.25;
|
|
13792
14285
|
var v3 = (v) => [v.x, v.y, v.z];
|
|
13793
14286
|
var dist3 = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
|
|
13794
14287
|
function createTestRoom(options) {
|
|
@@ -14420,7 +14913,7 @@ void main() {
|
|
|
14420
14913
|
}
|
|
14421
14914
|
});
|
|
14422
14915
|
player.kick(recoil[0], recoil[1]);
|
|
14423
|
-
emitHost({ type: "shot", weapon: toolId, shots: shots.length, recoil });
|
|
14916
|
+
emitHost({ type: "shot", weapon: toolId, shots: shots.length, recoil, rounds: shots.filter((s) => s.pellet === 0).length, mode: tool.mode });
|
|
14424
14917
|
const f = first;
|
|
14425
14918
|
const d = f ? num(f.distance, 1) : "";
|
|
14426
14919
|
let text;
|
|
@@ -14602,6 +15095,7 @@ void main() {
|
|
|
14602
15095
|
const detonate = (p, at, impact) => {
|
|
14603
15096
|
const w = toolName(p.weapon);
|
|
14604
15097
|
const results = world.explode(at, p.energy, p.radius, "player");
|
|
15098
|
+
renderer.explode(at, p.energy, p.radius);
|
|
14605
15099
|
action("explode", t("act.explode", { weapon: w, energy: num(p.energy, 0), radius: num(p.radius, 1), n: results.length }), { data: { weapon: p.weapon, energyJ: p.energy, radius: p.radius, point: at } });
|
|
14606
15100
|
if (p.fire) {
|
|
14607
15101
|
fluids.splash("fuel", [at[0], Math.max(at[1], groundAt(at[0], at[2])), at[2]], 0.5);
|
|
@@ -14945,9 +15439,12 @@ void main() {
|
|
|
14945
15439
|
say(t("hud.reloading"));
|
|
14946
15440
|
}
|
|
14947
15441
|
break;
|
|
14948
|
-
case "KeyQ":
|
|
14949
|
-
tool.
|
|
15442
|
+
case "KeyQ": {
|
|
15443
|
+
const before = tool.mode;
|
|
15444
|
+
const mode = tool.cycleMode();
|
|
15445
|
+
if (mode !== before) emitHost({ type: "mode", weapon: toolId, mode });
|
|
14950
15446
|
break;
|
|
15447
|
+
}
|
|
14951
15448
|
case "KeyZ":
|
|
14952
15449
|
tiltKey = 1;
|
|
14953
15450
|
break;
|
|
@@ -15035,6 +15532,10 @@ void main() {
|
|
|
15035
15532
|
let timeScale = 1;
|
|
15036
15533
|
let fastForward = false;
|
|
15037
15534
|
let hudAcc = 1;
|
|
15535
|
+
let metricsAcc = 0;
|
|
15536
|
+
let ownsLoop = false;
|
|
15537
|
+
const roomMetricIds = options.metrics === false ? [] : options.metrics ?? ROOM_METRICS;
|
|
15538
|
+
const roomMetrics = createRoomMetrics({ gl, destruction: renderer, fluids: fluidView, ids: roomMetricIds });
|
|
15038
15539
|
let qualityName = quality;
|
|
15039
15540
|
const syncContainers = (sdt) => {
|
|
15040
15541
|
for (const e of entries.values()) {
|
|
@@ -15098,6 +15599,7 @@ void main() {
|
|
|
15098
15599
|
flush();
|
|
15099
15600
|
};
|
|
15100
15601
|
const update = (dt) => {
|
|
15602
|
+
if (!ownsLoop) roomMetrics.frame(dt);
|
|
15101
15603
|
player.update(dt);
|
|
15102
15604
|
if (tiltKey || heldTilt > 0) {
|
|
15103
15605
|
heldTilt = Math.max(0, Math.min(2.4, heldTilt + (tiltKey ? 2.2 : -3) * dt));
|
|
@@ -15116,6 +15618,11 @@ void main() {
|
|
|
15116
15618
|
hudAcc = 0;
|
|
15117
15619
|
ui.hud(hudState());
|
|
15118
15620
|
}
|
|
15621
|
+
metricsAcc += dt;
|
|
15622
|
+
if (metricsAcc >= METRICS_INTERVAL_S && ui) {
|
|
15623
|
+
metricsAcc = 0;
|
|
15624
|
+
if (ui.benchVisible) ui.metrics(metricRows());
|
|
15625
|
+
}
|
|
15119
15626
|
};
|
|
15120
15627
|
const hudState = () => {
|
|
15121
15628
|
const page = Math.floor(toolIndex / 10);
|
|
@@ -15231,7 +15738,57 @@ void main() {
|
|
|
15231
15738
|
log.add({ channel: "check", type: ok ? "pass" : "fail", text: `${ok ? "\u2713" : "\u2717"} ${text}`, itemId, data: { ok, ...data } });
|
|
15232
15739
|
};
|
|
15233
15740
|
const modules = /* @__PURE__ */ new Map();
|
|
15234
|
-
const detachers =
|
|
15741
|
+
const detachers = /* @__PURE__ */ new Map();
|
|
15742
|
+
const benches = /* @__PURE__ */ new Map();
|
|
15743
|
+
let activeBench = null;
|
|
15744
|
+
const lastBench = () => [...benches.keys()].pop() ?? null;
|
|
15745
|
+
const benchView = () => {
|
|
15746
|
+
const store = activeBench ? benches.get(activeBench) : void 0;
|
|
15747
|
+
if (!store) return null;
|
|
15748
|
+
return { title: modules.get(store.moduleId)?.name ?? store.moduleId, controls: store.controls(), layers: store.layers() };
|
|
15749
|
+
};
|
|
15750
|
+
const METRIC_UNAVAILABLE = "n/a";
|
|
15751
|
+
const metricText = (value, digits, unit) => typeof value === "number" ? `${num(value, digits)}${unit ? ` ${unit}` : ""}` : value ?? METRIC_UNAVAILABLE;
|
|
15752
|
+
const reportedFailures = /* @__PURE__ */ new Set();
|
|
15753
|
+
const reportOnce = (key, text) => {
|
|
15754
|
+
if (reportedFailures.has(key)) return;
|
|
15755
|
+
reportedFailures.add(key);
|
|
15756
|
+
host.log(text);
|
|
15757
|
+
};
|
|
15758
|
+
const failureText = (error) => error instanceof Error ? error.message : String(error);
|
|
15759
|
+
const moduleMetrics = () => {
|
|
15760
|
+
const store = activeBench ? benches.get(activeBench) : void 0;
|
|
15761
|
+
const out = {};
|
|
15762
|
+
for (const metric of store?.metrics ?? []) {
|
|
15763
|
+
try {
|
|
15764
|
+
out[metric.id] = metric.read();
|
|
15765
|
+
} catch (error) {
|
|
15766
|
+
out[metric.id] = METRIC_UNAVAILABLE;
|
|
15767
|
+
reportOnce(`${store?.moduleId}:metric:${metric.id}`, t("mod.metric_failed", { name: modules.get(store?.moduleId ?? "")?.name ?? "", metric: metric.id, error: failureText(error) }));
|
|
15768
|
+
}
|
|
15769
|
+
}
|
|
15770
|
+
return out;
|
|
15771
|
+
};
|
|
15772
|
+
const metricRows = () => {
|
|
15773
|
+
const sampled = roomMetrics.sample();
|
|
15774
|
+
const room2 = roomMetricIds.flatMap((id) => {
|
|
15775
|
+
const value = sampled[id];
|
|
15776
|
+
return value === void 0 ? [] : [{ id, name: t(`metric.${id}`), text: num(value, id === "frame_ms" ? 1 : 0) }];
|
|
15777
|
+
});
|
|
15778
|
+
const values = moduleMetrics();
|
|
15779
|
+
const declared = (activeBench ? benches.get(activeBench)?.metrics : void 0) ?? [];
|
|
15780
|
+
const module = declared.map((metric) => {
|
|
15781
|
+
const value = values[metric.id];
|
|
15782
|
+
const ratio = metric.max && typeof value === "number" ? value / metric.max : void 0;
|
|
15783
|
+
return { id: metric.id, name: metric.name, text: metricText(value, metric.digits ?? 0, metric.unit), ...ratio === void 0 ? {} : { ratio } };
|
|
15784
|
+
});
|
|
15785
|
+
return { room: room2, module };
|
|
15786
|
+
};
|
|
15787
|
+
const showBench = () => {
|
|
15788
|
+
if (!ui) return;
|
|
15789
|
+
ui.bench(benchView());
|
|
15790
|
+
ui.metrics(metricRows());
|
|
15791
|
+
};
|
|
15235
15792
|
const hostListeners = /* @__PURE__ */ new Set();
|
|
15236
15793
|
const frameListeners = /* @__PURE__ */ new Set();
|
|
15237
15794
|
let reloadingSeen = false;
|
|
@@ -15247,7 +15804,7 @@ void main() {
|
|
|
15247
15804
|
camera,
|
|
15248
15805
|
locale,
|
|
15249
15806
|
groundAt,
|
|
15250
|
-
tool: () => ({ weapon: toolId, category: tool.def.category, ammo: tool.ammo, capacity: tool.capacity, reserve: tool.reserve, reloading: tool.reloading }),
|
|
15807
|
+
tool: () => ({ weapon: toolId, category: tool.def.category, ammo: tool.ammo, capacity: tool.capacity, reserve: tool.reserve, reloading: tool.reloading, mode: tool.mode }),
|
|
15251
15808
|
on(listener) {
|
|
15252
15809
|
hostListeners.add(listener);
|
|
15253
15810
|
return () => hostListeners.delete(listener);
|
|
@@ -15260,6 +15817,23 @@ void main() {
|
|
|
15260
15817
|
action("module_note", text, { data });
|
|
15261
15818
|
}
|
|
15262
15819
|
};
|
|
15820
|
+
const lendBench = (store) => ({
|
|
15821
|
+
value: store.value,
|
|
15822
|
+
layer: store.layer,
|
|
15823
|
+
onChange: store.onChange,
|
|
15824
|
+
patch: store.patch,
|
|
15825
|
+
look: (eye, target) => room.look(eye, target)
|
|
15826
|
+
});
|
|
15827
|
+
const checkModuleBench = (mod, fail) => {
|
|
15828
|
+
const store = createBenchStore(mod, locale, (error) => reportOnce(`${mod.id}:listener`, t("mod.listener_failed", { name: mod.name, error: failureText(error) })));
|
|
15829
|
+
const { controls, layers, metrics } = store.declared;
|
|
15830
|
+
if (controls.length + layers.length + metrics.length === 0) return null;
|
|
15831
|
+
const r = checkBench(store.declared);
|
|
15832
|
+
const text = r.ok ? t("mod.bench_ok", { name: mod.name, controls: controls.length, layers: layers.length, metrics: metrics.length }) : t("mod.bench_fail", { name: mod.name, problems: r.problems.join("; ") });
|
|
15833
|
+
moduleCheck(r.ok, text, { module: mod.id, check: "bench", problems: r.problems });
|
|
15834
|
+
for (const p of r.problems) fail(`bench: ${p}`);
|
|
15835
|
+
return r.ok ? store : null;
|
|
15836
|
+
};
|
|
15263
15837
|
const loadModule = (mod) => {
|
|
15264
15838
|
const report = { module: mod.id, items: [], scenarios: [], ok: true, problems: [] };
|
|
15265
15839
|
const fail = (problem) => {
|
|
@@ -15324,8 +15898,29 @@ void main() {
|
|
|
15324
15898
|
report.scenarios.push(sc.id);
|
|
15325
15899
|
}
|
|
15326
15900
|
action("module", t("act.module_loaded", { name: mod.name, items: report.items.length, scenarios: report.scenarios.length, result: t(report.ok ? "mod.all_ok" : "mod.some_failed") }), { data: { module: mod.id, items: report.items, scenarios: report.scenarios, ok: report.ok } });
|
|
15901
|
+
const bench = checkModuleBench(mod, fail);
|
|
15902
|
+
detachers.get(mod.id)?.();
|
|
15903
|
+
detachers.delete(mod.id);
|
|
15904
|
+
const previous = benches.get(mod.id);
|
|
15905
|
+
const kept = previous?.encode();
|
|
15906
|
+
previous?.dispose();
|
|
15907
|
+
benches.delete(mod.id);
|
|
15908
|
+
for (const key of [...reportedFailures]) if (key.startsWith(`${mod.id}:`)) reportedFailures.delete(key);
|
|
15327
15909
|
modules.set(mod.id, mod);
|
|
15328
|
-
if (
|
|
15910
|
+
if (bench) {
|
|
15911
|
+
const seed = options.bench?.state?.[mod.id];
|
|
15912
|
+
const restored = kept ?? (typeof seed === "string" ? seed : void 0);
|
|
15913
|
+
if (restored !== void 0) bench.restore(restored);
|
|
15914
|
+
benches.set(mod.id, bench);
|
|
15915
|
+
bench.watch(() => {
|
|
15916
|
+
options.bench?.onChange?.(mod.id, bench.encode());
|
|
15917
|
+
if (activeBench === mod.id) ui?.bench(benchView());
|
|
15918
|
+
});
|
|
15919
|
+
if (!activeBench || !benches.has(activeBench)) activeBench = mod.id;
|
|
15920
|
+
} else if (activeBench === mod.id) activeBench = lastBench();
|
|
15921
|
+
if (mod.attach) detachers.set(mod.id, mod.attach(bench ? { ...host, bench: lendBench(bench) } : host));
|
|
15922
|
+
bench?.sendInitial();
|
|
15923
|
+
showBench();
|
|
15329
15924
|
ui?.refresh();
|
|
15330
15925
|
return report;
|
|
15331
15926
|
};
|
|
@@ -15404,6 +15999,8 @@ void main() {
|
|
|
15404
15999
|
const mod = [...modules.values()].find((m) => m.id === area || m.group === area);
|
|
15405
16000
|
const label = mod?.group ?? S.groups[area] ?? area;
|
|
15406
16001
|
ui?.showGroup(label);
|
|
16002
|
+
activeBench = mod && benches.has(mod.id) ? mod.id : lastBench();
|
|
16003
|
+
showBench();
|
|
15407
16004
|
const layout = mod?.area?.(locale);
|
|
15408
16005
|
if (layout) return layArea(layout);
|
|
15409
16006
|
const items = catalog.filter((item) => item.group === label);
|
|
@@ -15495,8 +16092,18 @@ void main() {
|
|
|
15495
16092
|
loadModule,
|
|
15496
16093
|
animations: animationsOf,
|
|
15497
16094
|
playAnimation,
|
|
16095
|
+
bench(moduleId) {
|
|
16096
|
+
const id = moduleId ?? activeBench;
|
|
16097
|
+
return (id ? benches.get(id) : void 0) ?? null;
|
|
16098
|
+
},
|
|
16099
|
+
metrics() {
|
|
16100
|
+
return { room: roomMetrics.sample(), module: moduleMetrics() };
|
|
16101
|
+
},
|
|
15498
16102
|
dispose() {
|
|
15499
|
-
for (const detach of detachers.
|
|
16103
|
+
for (const detach of detachers.values()) detach();
|
|
16104
|
+
detachers.clear();
|
|
16105
|
+
for (const store of benches.values()) store.dispose();
|
|
16106
|
+
benches.clear();
|
|
15500
16107
|
cancelAnimationFrame?.(raf);
|
|
15501
16108
|
window.removeEventListener("resize", onResize);
|
|
15502
16109
|
player.dispose();
|
|
@@ -15567,19 +16174,33 @@ void main() {
|
|
|
15567
16174
|
onPlay: (objectId, clipId) => {
|
|
15568
16175
|
playAnimation(clipId, objectId);
|
|
15569
16176
|
},
|
|
15570
|
-
onResize: resize
|
|
16177
|
+
onResize: resize,
|
|
16178
|
+
onControl: (id, value) => {
|
|
16179
|
+
if (activeBench) benches.get(activeBench)?.set(id, value);
|
|
16180
|
+
},
|
|
16181
|
+
onLayer: (id, on) => {
|
|
16182
|
+
if (activeBench) benches.get(activeBench)?.setLayer(id, on);
|
|
16183
|
+
},
|
|
16184
|
+
onAction: (id) => {
|
|
16185
|
+
if (activeBench) benches.get(activeBench)?.act(id);
|
|
16186
|
+
}
|
|
15571
16187
|
});
|
|
15572
16188
|
ui.hud(hudState());
|
|
16189
|
+
showBench();
|
|
15573
16190
|
}
|
|
15574
16191
|
let raf = 0;
|
|
15575
16192
|
if (ownRenderer && typeof requestAnimationFrame === "function") {
|
|
15576
16193
|
let last = performance.now();
|
|
16194
|
+
ownsLoop = true;
|
|
15577
16195
|
const loop = (now) => {
|
|
15578
16196
|
raf = requestAnimationFrame(loop);
|
|
15579
|
-
const
|
|
16197
|
+
const realDelta = (now - last) / 1e3;
|
|
16198
|
+
roomMetrics.frame(realDelta);
|
|
16199
|
+
const dt = Math.min(0.05, realDelta);
|
|
15580
16200
|
last = now;
|
|
15581
16201
|
if (!scenarioRunning) update(dt);
|
|
15582
16202
|
gl.render(scene, camera);
|
|
16203
|
+
roomMetrics.rendered();
|
|
15583
16204
|
};
|
|
15584
16205
|
raf = requestAnimationFrame(loop);
|
|
15585
16206
|
}
|