Files
ark-stronghold-protocol/test/match/feedback5-boss-prep-scout.test.js
T
sganggs 221e6b8dd2 0.2.5-dev: community round 30 and follow-ups
- Stock: fixed trait gifts (缪尔赛思 / 变形者集群 变形同构体, 耶拉 不融冰, 蜜蜡 / 卡涅利安 迅捷作战粮, 诗怀雅 盟约之币) still occupy stock but are granted past the cap; an operator card already shown in the shop or a reward can be taken after the pool runs out and overdraws (GitHub #498, the owner's decision); item buys and item rewards keep the stock check. 洛洛 / 焰尾 sub-pools use the measured weights (PR #497 by @2321Robin; the occupancy idea of PR #496 by @2321Robin re-implemented differently).
- Boss pairs are drawn at random once per match and fixed to battle start; the Hidden Core reuses them; a leaver does not split a full pair (the owner's decision).
- 阿戈尔 in 联防 no longer re-applies the opening devour HP loss or a false revive; Touch's team medic stays for the match after its holder leaves (owner-approved, official lifetime unverified).
- Auto-skill ranges: 司霆惊蛰 S2, 斩业星熊 S3 against air, 予愿安洁莉娜 S3; 自在's post-rebirth invulnerability is also untargetable (#510); Horn / Clementia / catapult / 忍冬 first hit (PR #502 by @Xinglan233); 不融冰 / tear gas trigger on real damage.
- Bond layer-up sound in round start / draft / prep / battle (#482): one channel, a new cue replaces the old, no extra cooldown; a prep update that adds layers and activates a bond plays one cue.
- Client: multi-swing fix (#506), refresh single-flight (PR #507 by @robll-v1), LP tower keeps the settled value with a −N tick (PR #504 by @robll-v1), export with only 潜能 / 练度 changes (PR #503 by @Xinglan233), spectator rosters list DIY members (PR #505 by @Xinglan233), banned members beside bond tooltips (PR #494 by @YuriRestia), recovery action above the away overlay (PR #511 by @RainoutOvO), the /dev/sim-check.html consistency page (PR #492 by @Cloudnyco), Korean (PR #490 by @D-Roote) and English Victoria wording (PR #499 by @YuriRestia).
- golden: 45 of 287 scenarios move against 0.2.4 (roster 17, bonds 3, fields 6, matches 17, standins 2), each attributed in docs/history/0.2.5.md §30.9 / §30.16.
2026-10-12 14:07:08 +08:00

81 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Community report of 2026-10-06, item 55: 「死亡后观战视角看不到场上的关底boss」. Reproduced in the browser (an eliminated
// guest at the boss round, notes i55-repro): in the Final Assault fight the watched boss field shows the leader, but in
// the boss round's prep the eliminated player saw its own empty normal board — and a scout of a teammate (m.field
// prep) was the normal board too, while the players themselves prepare on their half of the boss field with the leader
// standing at its spawn (render/app.js setLeader, only on the boss-field prep camera). Now the scout of a boss round's
// prep is built on the boss field: the scouted player's pieces on its half (bossFieldPlacement: rows − 7, the right
// half mirrored, RIGHT ↔ LEFT), `side` for the camera, the leader in nextEnemies with its spawn tile; with item 56 an
// eliminated viewer is pushed that scout at the round start without a tap.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { GEO, PHASE } from '../../shared/constants.js';
import { makeMatch, give, legalTileFor, chessOfTier } from './harness.js';
import { bossFieldPlacement } from '../../server/match/finalAssault.js';
import { viewKind, leaderShown } from '../../public/js/render/app/view.js';
import { leaderStand } from '../../public/js/render/prepfield.js';
function bossPrep(seed) {
const h = makeMatch({ mode: 'coop', difficulty: 'FUNNY', humans: 4, seed, fake: true }).start();
const m = h.m;
m.bossPairs = [['p_0', 'p_1'], ['p_2', 'p_3']]; // explicit mirrored-layout fixture, not the pairing policy
h.drive(() => m.phase === PHASE.PREP && m.round === m.gd.bossRound, { ready: true });
return h;
}
test('a scout of a boss round\'s prep is the boss field: the player\'s half (mirrored on the right), its side, the leader\'s spawn', () => {
const h = bossPrep(55);
const m = h.m;
assert.deepEqual(m.bossWaves.map((w) => w.players), [['p_0', 'p_1'], ['p_2', 'p_3']]);
const p1 = h.ps('p_1');
// a board piece facing RIGHT and one in the hand
for (const k of [...p1.board.keys()]) p1.board.delete(k);
const id = chessOfTier(1)[0];
const tile = legalTileFor(m, p1, id);
const onBoard = give(m, p1, id, 'board', tile);
onBoard.dir = 'RIGHT';
give(m, p1, chessOfTier(1)[1], 'hand', 2);
h.ps('p_3').eliminate(13);
assert.deepEqual(m.handle('p_3', { t: 'g.watch', fieldId: 'n:p_1' }), { ok: true });
const meta = h.lastTo('p_3', 'm.field');
assert.deepEqual([meta.fieldId, meta.prep, meta.kind, meta.side], ['n:p_1', true, 'boss', 'R']);
assert.deepEqual(meta.rect, GEO.BOSS_RECT);
const u = meta.units.find((x) => x.uid === onBoard.uid);
const want = bossFieldPlacement('R', tile[0], tile[1], 'RIGHT');
assert.deepEqual([u.y, u.x, u.dir, u.facing], [want.row, want.col, 'LEFT', -1], 'mirrored onto the right half, still facing the leader');
const bench = meta.units.find((x) => x.y === 0);
assert.ok(bench && bench.x === 20 - 2, `the hand row on the half's bench row (boss row 0, mirrored): ${JSON.stringify(bench)}`);
for (const x of meta.units.filter((x) => x.ownerId === 'p_1')) assert.ok(x.y >= 0 && x.y <= 5 && x.x >= 10 && x.x <= 20, `on the right half: ${x.y},${x.x}`);
// the leader stands at its spawn tile: the client's leaderStand finds it in the scout's nextEnemies
const stand = leaderStand(meta.nextEnemies, () => null, (x, y) => [[y, x]]);
assert.ok(stand && stand.entry.boss && stand.row >= 0 && stand.row <= 5, `the leader with its spawn tile: ${JSON.stringify(stand?.entry)}`);
assert.deepEqual(meta.nextEnemies, m.nextEnemiesFor(p1), 'the scouted player\'s own preview (its pair\'s wave)');
// the client frames a scout with that rect on the boss-field prep camera, which shows the leader
assert.equal(viewKind('prep', { rect: meta.rect, side: meta.side }), 'bossPrep');
assert.equal(leaderShown('bossPrep'), true);
// the left player of the pair: its own half, unmirrored
assert.deepEqual(m.handle('p_3', { t: 'g.watch', fieldId: 'n:p_0' }), { ok: true });
const left = h.lastTo('p_3', 'm.field');
assert.deepEqual([left.kind, left.side], ['boss', 'L']);
// its own pieces on the left half (since item 51 the partner p_1's board stands on the right half beside them)
for (const x of left.units.filter((x) => x.ownerId === 'p_0')) assert.ok(x.x <= 10, 'the left half');
assert.ok(left.units.some((x) => x.ownerId === 'p_1' && x.uid === onBoard.uid && x.x === want.col), 'p_1\'s piece on the right half');
m.dispose();
});
test('the eliminated viewer is pushed the boss-field scout at the boss round\'s start (item 56); a normal round\'s scout stays the normal board', () => {
const h = makeMatch({ mode: 'coop', difficulty: 'FUNNY', humans: 3, seed: 56, fake: true }).start();
const m = h.m;
m.bossPairs = [['p_0', 'p_1'], ['p_2']]; // keep this scout fixture's named left/right roles
h.toPrep(1);
h.ps('p_2').eliminate(1);
h.toPrep(2);
const normal = h.lastTo('p_2', 'm.field');
assert.deepEqual([normal.fieldId, normal.kind, normal.prep, normal.side], ['n:p_0', 'normal', true, undefined]);
assert.deepEqual(normal.rect, GEO.NORMAL_RECT);
h.drive(() => m.phase === PHASE.PREP && m.round === m.gd.bossRound, { ready: true });
const meta = h.lastTo('p_2', 'm.field');
assert.deepEqual([meta.fieldId, meta.kind, meta.prep, meta.side], ['n:p_0', 'boss', true, 'L']);
assert.ok(leaderStand(meta.nextEnemies, () => null, (x, y) => [[y, x]]), 'the leader at its spawn');
m.dispose();
});