feedback7 UIBACK: a finished 联防 / boss-pair field publishes the capsule's own resolved, not the sum of the players' clamps — an enemy that enters one helper's half and leaks on the other's is billed to one player's total and to the other's leakedInTotal, so each player's min(total, killedInTotal + leakedInTotal) read 0 while the field read 1/1 and the teammates' m.public progress dropped to 0/1 once the field finished (found reviewing the capsule port of PR #308): validateClientResult keeps the result's field-level resolved (Battle.resolved, clamped to the field's total; junk counts as not reported), _fieldProgress publishes it for client-run and server-run fields alike, the players' own numbers are only the fallback of a result with none, and none at all stays null; SIM.md §1.3 and network.md (顶栏胶囊, result validation) say so; test/match/hud-capsule-resolved.test.js +5 (a 3-human match whose 联防 field runs the real sim with unarmed helpers: capsule 4/4 and m.public 4/4 not 0/4; the boss pair's crossing route; a server-run 联防 field and its timeline; validateClientResult's field-level rule; _fieldProgress's precedence) — all failing before; golden: unchanged (no sim change; compare: roster 49, bonds 46, fields 22, matches 18, standins 10, diy 138 match)
This commit is contained in:
1 parent
f632e0b009
commit
1a88069968
5 files changed
+186
-16
No files matched your search
+15
-3
@@ -751,7 +751,11 @@ const sameMods = (a, b) => {
|
||||
* flat bound when one is uncapped) and ≤ the room left under BOND_LAYER_CAP (999) from the bond's starting layers, only
|
||||
* on bonds the player's lineup / band / effects / items name, none when the spec disables gains; coins ≤ the spawns' bounty coins;
|
||||
* perfect consistent with the counted leaks; unit states only for the player's own units, within range. `resolved` (the
|
||||
* HUD capsule's numerator) is kept as reported, clamped to the player's `total`; an absent one stays absent.
|
||||
* HUD capsule's numerator): the result's own — the FIELD's, what the client's capsule showed — is kept as reported,
|
||||
* clamped to the field's `total`, and is what m.public publishes (never the sum of the players': an enemy that crosses
|
||||
* the midline of a two-half field is billed to one player's `total` and to the other's leak, so their own numbers
|
||||
* clamp it to 0); the players' own (each clamped to their `total`) are the fallback of a result with none; absent when
|
||||
* neither is complete.
|
||||
* @returns {{ ok: true, result: object } | { ok: false, reason: string }}
|
||||
*/
|
||||
export function validateClientResult(spec, raw, { gd = null } = {}) {
|
||||
@@ -888,12 +892,20 @@ export function validateClientResult(spec, raw, { gd = null } = {}) {
|
||||
}
|
||||
if (coinsSum > B.bountyCoins + 1e-6) return bad('coins');
|
||||
const result = { time: raw.time, reason: raw.reason, perPlayer, killed: 0, total: 0, errors: Math.max(0, Math.trunc(Number(raw.errors) || 0)) };
|
||||
let ownResolved = 0, ownKnown = pids.length > 0;
|
||||
for (const pid of pids) {
|
||||
result.killed += perPlayer[pid].killed;
|
||||
result.total += perPlayer[pid].total;
|
||||
result.resolved = (result.resolved ?? 0) + (Number.isInteger(perPlayer[pid].resolved) ? perPlayer[pid].resolved : 0);
|
||||
if (Number.isInteger(perPlayer[pid].resolved)) ownResolved += perPlayer[pid].resolved; else ownKnown = false;
|
||||
}
|
||||
if (pids.some((pid) => !Number.isInteger(perPlayer[pid].resolved))) delete result.resolved;
|
||||
// The FIELD's capsule numerator (Battle.resolved, what the client's own capsule showed), kept as reported and clamped
|
||||
// to the field's total — NOT the sum of the players' own numbers: an enemy that spawns on one half and leaks on the
|
||||
// other (the 联防 lane, the boss pair's crossing routes) is billed to one player's `total` and to the other's
|
||||
// `leakedInTotal`, so each player's own min(total, …) clamps it away (0 and 0 for an enemy the field resolved). The sum
|
||||
// is only the fallback of a result with no field-level number; none of either stays absent (the teammate UI then
|
||||
// falls back to `killed`, never to a fabricated 0).
|
||||
if (Number.isInteger(raw.resolved) && raw.resolved >= 0) result.resolved = Math.min(result.total, raw.resolved);
|
||||
else if (ownKnown) result.resolved = Math.min(result.total, ownResolved);
|
||||
if (spec.kind === 'unite' && Array.isArray(raw.unspawned)) {
|
||||
const unspawned = [];
|
||||
for (const u of raw.unspawned) {
|
||||
|
||||
@@ -151,14 +151,19 @@ export class MatchViews {
|
||||
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, resolved = 0, known = true;
|
||||
let killed = 0, total = 0, own = 0, ownKnown = 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 (Number.isFinite(pp && pp.resolved)) own += Number(pp.resolved); else ownKnown = 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 };
|
||||
// the FIELD's numerator (the validated client result / Battle.result(): what the capsule showed) — not the sum of the
|
||||
// players' own: an enemy that spawns on one half and leaks on the other (a 联防 lane, the boss pair's crossing routes)
|
||||
// is billed to one player's `total` and to the other's leak, so their own min(total, …) clamp it to 0
|
||||
let resolved = finiteOrNull(f.result.resolved, total);
|
||||
if (resolved == null && ownKnown) resolved = Math.min(total, own);
|
||||
if (f.result.synthetic) { killed = f.progress.killed; total = f.progress.total; resolved = finiteOrNull(f.progress.resolved, total); }
|
||||
return { killed, resolved, total, done: true };
|
||||
}
|
||||
if (f.mode === 'server' && f.timeline) {
|
||||
// a server-run / bot field: no authority ever sends a b.progress, so the capsule reads the battle's own counters —
|
||||
|
||||
Reference in new issue
Block a user