feedback7 UIBACK: the top-bar capsule counts like the official — resolved = killed + leaked among the enemies the round scheduled (opening 0/3, a leak 1/3, a kill 2/3, the splitter 3/3); a split child, a summon and a boss summon are in neither number but still cost LP and break 完美作战 (PR #308 by @xcdoge, the capsule half of PR #157 — authored by them: the sim / protocol / HUD hunks applied as reviewed): inTotal / killedInTotal / leakedInTotal / resolved, b.snap / BattleResult / b.progress (optional field) / m.public progress carry resolved, killed may exceed total (validation bounds it by the content-spawn allowance); dropped the PR's stray design-doc line — it had replaced the S→C b.end bullet of docs/design/network.md, which is back; the 联防 live-counter change mark follows the enemy list now that a spawn no longer moves total (uniteLeftMark + test); golden: only total / players.*.total / snaps move, outcomes bit-identical — roster 23 (roster-002 003 007 008 009 010 011 012 013 015 017 018 020 023 024 025 026 027 037 045 047 048 049), bonds 2 (bond-maniShip-low, bond-soloShip-high), fields 11 (boss-boss_1 3 5 pair+solo, boss-boss_6-solo, hidden-boss_8 10 pair+solo), diy 11 (diy-036 040 056 062 068 073 083 091 095 114 115), matches 12 (solo-FUNNY-1 NORMAL-1 HARD-1 ABYSS-1 ABYSS-2, coop2-NORMAL-3 coop2-HARD-4 coop3-ABYSS-5 coop4-ABYSS-7 coop2-NORMAL-8-serverrun solo-HARD-9-boosted coop2-NORMAL-70-diy: the total column of 36 round rows), standins unchanged
This commit is contained in:
1 parent
d7c665dbf6
commit
b4bc89ad4e
39 files changed
+836
-252
No files matched your search
@@ -35,11 +35,15 @@ export class MatchClientCombat {
|
||||
const battleId = seq.length + 1 + String(fieldId).length <= 64 ? `${seq}.${fieldId}` : seq;
|
||||
const spec = buildBattleSpec({ ...opts, battleId, fieldId, kind, content: this.battleContent, boss });
|
||||
let total = 0;
|
||||
for (const x of spec.spawns) if (x && x.tag !== 'boss' && x.tag !== 'part') total += Math.max(1, Math.floor(Number(x.count) || 1));
|
||||
for (const x of spec.spawns) if (x && x.tag !== 'boss' && x.tag !== 'part' && x.countInTotal !== false) total += Math.max(1, Math.floor(Number(x.count) || 1));
|
||||
return {
|
||||
cc: true, fieldId, kind, players: players.slice(), battleId, spec, battle: null, live: true, done: false,
|
||||
mode: null, authority: null, startAt: this.sched.now(), result: null, resultSource: null, timeline: null, endGt: null,
|
||||
progress: { gt: 0, killed: 0, total, leaks: 0, done: false }, lastProgressAt: this.sched.now(),
|
||||
// progress: the field's own numbers as far as they are known before its first b.progress / result (`total` = the
|
||||
// spec's scheduled enemies — the capsule's denominator). `resolved` starts at **null**: it is only adopted from a
|
||||
// real report, so `resolved ?? killed` fallbacks (an unreported field, a synthetic result) keep working — a 0 here
|
||||
// would read as "nothing resolved yet" and could never be told apart from a reported 0.
|
||||
progress: { gt: 0, killed: 0, total, leaks: 0, resolved: null, done: false }, lastProgressAt: this.sched.now(),
|
||||
bossAcked: 0, bossBy: {}, lpAcked: 0, lpCum: 0, deadlineTimer: null, doneTimer: null, waitTimer: null,
|
||||
// boss fields: the latest client reports (re-credited as the plausibility budget grows), the server run's
|
||||
// CreditPool, humans demoted for an implausible result (never the authority of this field again), a 'cleared'
|
||||
|
||||
@@ -25,6 +25,11 @@ export class MatchReports {
|
||||
// the latest total (spawns never reached before the limit leave it at the end)
|
||||
p.total = Math.min(maxTotal, Math.max(0, msg.total | 0));
|
||||
p.killed = Math.min(p.total, Math.max(p.killed, msg.killed | 0));
|
||||
// the HUD capsule's numerator as the authority reported it (shared/protocol.js b.progress `resolved`). Only a real
|
||||
// integer is adopted — an absent field leaves the null placeholder alone, so "not reported" and a reported 0 differ
|
||||
// (a `Number(null) === 0` here would defeat every `resolved ?? killed` fallback); a lower value is kept (the final
|
||||
// report drops never-spawned enemies from `total`, and a takeover may report a different number)
|
||||
if (Number.isInteger(msg.resolved)) p.resolved = Math.max(0, Math.min(1e5, msg.resolved));
|
||||
f.lastProgressAt = this.sched.now();
|
||||
if (f.kind === 'boss' || f.kind === 'hidden') {
|
||||
this._creditBoss(f, msg.bossDmg, msg.by);
|
||||
|
||||
@@ -126,8 +126,9 @@ export class MatchUnite {
|
||||
let live = null;
|
||||
if (f && f.cc) {
|
||||
if (f.mode === 'server' && f.timeline) {
|
||||
// the sample is [gt, killed, total, resolved(, left)]: a 联防 sample's `left` is its 5th element
|
||||
const sample = timelineAt(f.timeline, this._fieldElapsed(f));
|
||||
live = sample && sample[3] && typeof sample[3] === 'object' ? sample[3] : null;
|
||||
live = sample && sample[4] && typeof sample[4] === 'object' ? sample[4] : null;
|
||||
} else live = f.progress && f.progress.left && typeof f.progress.left === 'object' ? f.progress.left : null;
|
||||
} else if (f && f.battle) {
|
||||
try { live = uniteLeft(f.battle); } catch { live = null; }
|
||||
|
||||
@@ -13,6 +13,14 @@ import { timelineAt } from '../fields.js';
|
||||
import { bossFieldPlacement } from '../finalAssault.js';
|
||||
import { battleProgress } from '../../sim/spec.js';
|
||||
|
||||
/** A reported capsule numerator clamped to its denominator, else null (unknown — never a fabricated 0). */
|
||||
const finiteOrNull = (v, cap = Infinity) => {
|
||||
if (v == null) return null; // Number(null) === 0: an unreported value must not read as "0 resolved"
|
||||
const n = Number(v);
|
||||
if (!Number.isFinite(n)) return null;
|
||||
return Math.max(0, Math.min(Number.isFinite(cap) ? cap : Infinity, Math.trunc(n)));
|
||||
};
|
||||
|
||||
export class MatchViews {
|
||||
statusOf(ps) {
|
||||
if (ps.left) return 'left';
|
||||
@@ -127,25 +135,40 @@ export class MatchViews {
|
||||
return v;
|
||||
}
|
||||
|
||||
/** m.public.fields[].progress: { killed, total, done } (teammates' waiting UI). */
|
||||
/**
|
||||
* m.public.fields[].progress: { killed, resolved, total, done } (teammates' waiting UI). `resolved` is the HUD
|
||||
* capsule's numerator — the field's own scheduled enemies knocked out or leaked (Battle.resolved / the reported
|
||||
* b.progress `resolved`); it is `null` (never 0) while unknown, so the client's `resolved ?? killed` fallback holds.
|
||||
* `total` is the capsule's denominator: only the enemies the round scheduled (runtime splits / summons — boss summons
|
||||
* included — are in neither part).
|
||||
*/
|
||||
_fieldProgress(f) {
|
||||
if (!f) return null;
|
||||
if (!f.cc) {
|
||||
const b = f.battle;
|
||||
if (!b) return null;
|
||||
return { killed: Number(b.killed) || 0, total: Number(b.total) || 0, done: !f.live };
|
||||
const total = Number(b.total) || 0;
|
||||
return { killed: Number(b.killed) || 0, resolved: finiteOrNull(b.resolved, total), total, done: !f.live };
|
||||
}
|
||||
if (f.done && f.result) {
|
||||
let killed = 0, total = 0;
|
||||
for (const pp of Object.values(f.result.perPlayer || {})) { killed += Number(pp && pp.killed) || 0; total += Number(pp && pp.total) || 0; }
|
||||
if (f.result.synthetic) { killed = f.progress.killed; total = f.progress.total; }
|
||||
return { killed, total, done: true };
|
||||
let killed = 0, total = 0, resolved = 0, known = true;
|
||||
for (const pp of Object.values(f.result.perPlayer || {})) {
|
||||
killed += Number(pp && pp.killed) || 0;
|
||||
total += Number(pp && pp.total) || 0;
|
||||
if (Number.isFinite(pp && pp.resolved)) resolved += Number(pp.resolved); else known = false;
|
||||
}
|
||||
if (f.result.synthetic) { killed = f.progress.killed; total = f.progress.total; resolved = f.progress.resolved; known = Number.isFinite(resolved); }
|
||||
return { killed, resolved: known ? Math.min(total, resolved) : null, total, done: true };
|
||||
}
|
||||
if (f.mode === 'server' && f.timeline) {
|
||||
const [, killed, total] = timelineAt(f.timeline, this._fieldElapsed(f));
|
||||
return { killed, total, done: false };
|
||||
// a server-run / bot field: no authority ever sends a b.progress, so the capsule reads the battle's own counters —
|
||||
// the timeline sample carries `resolved` (Battle.resolved: knocked out + leaked among the field's own enemies),
|
||||
// never the report-driven `progress.leaks`, which would leave such a field at 0 forever
|
||||
const [, killed, total, resolved] = timelineAt(f.timeline, this._fieldElapsed(f));
|
||||
return { killed, resolved: finiteOrNull(resolved, total), total, done: false };
|
||||
}
|
||||
return { killed: f.progress.killed, total: f.progress.total, done: false };
|
||||
const total = f.progress.total;
|
||||
return { killed: f.progress.killed, resolved: finiteOrNull(f.progress.resolved, total), total, done: false };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user