Playtest #4 fixes: tile picking, knocked-down state, element gauges, draft timer, live stats, shop items, drones, 赫默, 炎佑, UI flash

- #1 picking by the tile under the pointer (prep, battle, pen, equipment drops); the dragged model stays
  under the pointer (mouse and touch); pixel probe removed; phone ghost-click on the underframe fixed
- #2 two-tap confirm on 机变 cards (like the shop)
- #3 a 同盟 room with one human is untimed like 独立模拟 (no countdown outside battles)
- #4 strategy draft: one synced countdown, 30 s per human turn, timeout takes the highlighted band
- #5 special 维式重锤 (4) and 突变细胞 no longer sold; item card shows where they come from
- #6 operator skill-mode sounds no longer play on normal attacks (纯烬艾雅法拉 and 55 others)
- #7 detail card shows live battle stats and the prep-time battle-start stats (g.unitStats)
- #8 official element gauges on units; burst cooldown (阻回) per PRTS
- #9 knocked-down operators stay on their tile with a redeploy countdown ring
- #10 drone behaviour per PRTS (暴鸰, 先兆者 shells, 黑云, 萨科塔之眼)
- #11 赫默's drone only from her skill; placeable summons limited to talent tokens
- #12 炎佑 stays where it is with no enemies; 祛恶之焰 as a continuous channel
- #13 the occasional giant bond icon (loading texture scaled ×46) fixed
- docs (DESIGN §18), recordings, data and tests updated (2467 unit tests, all browser suites green)
This commit is contained in:
sganggs committed 2026-09-30 05:35:45 +08:00
1 parent 0aba602ecf
commit d4f868cd19
107 files changed
+4510 -1653

No files matched your search

+31 -4
View File
@@ -245,11 +245,37 @@ describe('in-match UI (mock harness, headless Chrome)', { skip: !ENABLED && 'set
await page.close();
});
test('机变 pick, band pick, briefing ready', async () => {
test('机变 pick (two taps, user playtest #4 item 2), band pick, briefing ready', async () => {
let { page, problems } = await open('phase=SP_DRAFT&variant=bounty');
await page.click('.spcard.is-pickable');
await sleep(200);
// the first tap only selects: the card lifts with its 确认选择 strip, the header offers 确认选择, nothing was sent
assert.ok(await page.$('.spcard.is-armed .spcard__confirm'), 'selected (armed)');
assert.ok(await page.$('.spov__confirm'), 'the header confirm');
assert.ok(!(await page.$('.spcard.is-mine')), 'not picked yet');
assert.ok(!(await page.evaluate(() => globalThis.__MOCK__.S().requests.some(([t]) => t === 'g.choice'))), 'no g.choice after one tap');
// another card moves the selection; Esc drops it
const pickable = await page.$$('.spcard.is-pickable');
await pickable[1].click();
await sleep(150);
assert.equal(await page.$$eval('.spcard.is-armed', (els) => els.length), 1, 'one selection');
await page.keyboard.press('Escape');
await sleep(150);
assert.ok(!(await page.$('.spcard.is-armed')), 'Esc drops the selection');
// select, then tap the same card again: picked
await page.click('.spcard.is-pickable');
await sleep(150);
await page.click('.spcard.is-armed');
await sleep(300);
assert.ok(await page.$('.spcard.is-mine'), 'picked card marked');
// the header's 确认选择 confirms as well
await page.close();
({ page, problems } = await open('phase=SP_DRAFT&variant=supply'));
await page.click('.spcard.is-pickable');
await sleep(150);
await page.click('.spov__confirm');
await sleep(300);
assert.ok(await page.$('.spcard.is-mine'), 'confirmed from the header');
assert.deepEqual(problems, []);
await page.close();
@@ -628,11 +654,12 @@ describe('in-match UI (mock harness, headless Chrome)', { skip: !ENABLED && 'set
await page.close();
});
test('engine: equipment released over an operator\'s upper body equips it', async (t) => {
test('engine: equipment dropped on an operator\'s tile equips it — the tile under the pointer decides (user playtest #4 item 1)', async (t) => {
if (RENDER !== 'engine') { t.skip('SP_RENDER=fallback'); return; }
const { page, problems } = await open('phase=PREP');
await sleep(1500); // spine models
const at = (uid, fy) => page.evaluate((u, f) => { const r = globalThis.__SP_VIEW__?.pieceScreenRect(u); return r && { x: r.left + r.width / 2, y: r.top + r.height * f }; }, uid, fy);
const tileAt = (row, col) => page.evaluate((r, c) => { const x = globalThis.__SP_VIEW__?.raw?.tileScreen?.(r, c); return x && { x: x.x, y: x.y }; }, row, col);
const drag = async (from, to) => {
await page.mouse.move(from.x, from.y);
await page.mouse.down();
@@ -646,9 +673,9 @@ describe('in-match UI (mock harness, headless Chrome)', { skip: !ENABLED && 'set
const items = s.hand.filter((x) => x && x.kind === 'item');
assert.ok(targets.length && items.length, 'mock board has an unequipped operator and a hand item');
const unit = targets[0];
await drag(await at(items[0].uid, 0.5), await at(unit.uid, 0.25));
await drag(await at(items[0].uid, 0.5), await tileAt(unit.row, unit.col));
s = await mockState(page);
assert.ok(s.board.find((x) => x.uid === unit.uid).items.some((i) => i.uid === items[0].uid), 'equipped from the head');
assert.ok(s.board.find((x) => x.uid === unit.uid).items.some((i) => i.uid === items[0].uid), 'equipped: dropped on its tile');
assert.deepEqual(problems, []);
await page.close();
});