Playtest #5 fixes: hovering enemies, target order, blocking hand-over, 联防 knocked-out, elements, huge-boss hit area, maps, gear icon, phone HUD, 至简 price

- #1 近地悬浮 enemies count as air units (Unit.isFlying): ground-only attacks, splash, terrain and traps skip them;
  PRTS 不可对空 skill effects made ground-only; 掠海漂移体 / 吉兆飞鳞 drop modes; the bot plans them as air
- #2 an operator knocked out before 联防 is fielded and force-exited (PRTS 强制退场): it lies on its tile with the
  redeploy ring and redeploys (full timer [ASSUMED]); partners, observers and the server fallback included
- #3 element audit of every operator, item and enemy: 海嗣 numbers are official; bursts are 无来源, 元素脆弱 only
  raises 元素伤害, 元素抗性 applied, 盟约·辅助干员 fills all three gauges, 菲莱 / 哈洛德 / 纯烬 / enemy kits fixed
- #4 blocking by the official contact radius (0.7071): another operator in contact takes over at once when the
  blocker dies, withdraws or is stunned; melee blockers attack what they block
- #5 enemy target order per PRTS 索敌: blocker, special priority, taunt, last deployed; initial deploy 从上到下>从左到右,
  operators before summons; special priorities break ties by taunt then deploy order
- #6 act2 m01's normal-field blower machines (row 13) are drawn in the normal / 联防 views
- #7 Final Assault prep legality reads the player's half of the boss field (fence tiles deployable); all 11 stages
  audited against the official level files
- #8 regular settings gear; #9 the prep camera keeps the bench and temp rows clear of the shop bar on phones
- #10 huge bosses have the official 4.95 × 2.95 hit area (sim/body.js) and are 自缚 / unblockable
- #11 至简 costs 1: 购买价格为N's bb.price is a discount off the tier price, applied before other price effects
- DESIGN §19 and the normative §3 / §5 / §6 / §7 lines, SIM / META / DATA / PLAYING updated; 2595 unit tests and
  every browser suite green; dev recordings regenerated
This commit is contained in:
sganggs committed 2026-09-30 19:12:36 +08:00
1 parent d4f868cd19
commit 15291c2293
104 files changed
+5539 -662

No files matched your search

+20 -3
View File
@@ -12,8 +12,10 @@
// usable (move to a free hand slot, place, equip, destroy, sell) through the NEXT prep — nothing is destroyed before
// the player saw it in a prep ("处于临时整备区的调度资源,在进入下一回合后会自动销毁").
// * Board: own region rows 9–12 × cols 2–10, legality from the stage legend (board.js); deploy cap 8 (+effects);
// tokens (placeable summons) don't use deploy slots. Board↔hand swaps are always allowed. A terrain change
// (terrain 机变 cards, content overrides) withdraws the pieces left on tiles they may no longer occupy (hand,
// tokens (placeable summons) don't use deploy slots. Board↔hand swaps are always allowed. In a boss round the
// legality reads the player's half of the boss field (Match.deployFieldOf → board.js field 'bossL' / 'bossR',
// user playtest #5 item 7); board coordinates are unchanged. A terrain change (terrain 机变 cards, content
// overrides) or a change of the deploy field withdraws the pieces left on tiles they may no longer occupy (hand,
// overflow temp; summons back onto their stack) — see _evictIllegal.
// * Shop: per-level chess slots + item slot, copy-weighted rolls from the SHARED pool (pool.js); refresh 1 (free
// refreshes first), one toggle freezes all unsold slots until the next round start (a manual refresh while frozen
@@ -122,6 +124,8 @@ export class PlayerState {
/** last combat result for this player (unite carry state, bounties) */
this.lastResult = null;
this._deployMap = null;
/** the deploy field of `_deployMap` ('normal' | 'bossL' | 'bossR', Match.deployFieldOf) */
this._deployField = undefined;
/** the deploy map changed since the board's legality was last checked (invalidateDeployMap) */
this._legalityStale = false;
/** Match.scheduleBotPrep: the latest bot prep of this seat (older sliced rehearsals drop out) */
@@ -199,8 +203,19 @@ export class PlayerState {
return resolveLoadout(this.loadout, chessRecord, (id) => this.gd.chess(id));
}
/**
* Deploy classes of the board tiles (server/match/board.js buildDeployMap) on the field the player deploys on now
* (Match.deployFieldOf: the own board, or its half of the boss field in a boss round — user playtest #5 item 7).
* A change of that field (the boss round begins, a re-pairing) re-checks the board's legality like a terrain change.
*/
deployMap() {
if (!this._deployMap) this._deployMap = this.m.deployMapFor(this);
const field = typeof this.m.deployFieldOf === 'function' ? this.m.deployFieldOf(this) : 'normal';
if (this._deployMap && this._deployField !== undefined && this._deployField !== field) {
this._deployMap = null;
this._legalityStale = true;
}
if (!this._deployMap) this._deployMap = this.m.deployMapFor(this, field);
this._deployField = field;
return this._deployMap;
}
/**
@@ -1280,6 +1295,7 @@ export class PlayerState {
}
recompute() {
this.deployMap(); // a change of the deploy field (a boss round's prep) marks the legality stale
if (this._legalityStale) this._evictIllegal();
this.bonds = computeBonds(this.gd, this);
this.dirty();
@@ -1292,6 +1308,7 @@ export class PlayerState {
battleInput({ side = 'L', colOffset = 0, carry = null } = {}) {
// a terrain change not yet followed by a recompute (a content hook at the prep end) never fields an illegal board
this.deployMap();
if (this._legalityStale) this.recompute();
const units = [];
for (const { r, c, piece } of boardOrder(this.board)) {