Playtest #5 follow-up: boss-field blowers show in the normal rounds; 联防 timer restart confirmed

- act2 m01: the boss field's row-6 blower machines stand under the bench in the normal and 联防 views again
  (the user: "吹风机原版道中也该有"); the integration-QA filter (BOSS_WALL_ROW) is removed from the 3D and 2D
  boards, so every device on a built tile is drawn in every view, as the workstream had it
- 联防: a knocked-out operator's redeploy timer restarting in full is confirmed by the user ("进联防复活时间确实是
  重新算"); the [ASSUMED] tags in DESIGN §5.5 / §6.1 / §19.3, META §4 and unite.js say so
- DESIGN §19 intro / §19.7 and the docs-consistency assertions updated; 2595 unit tests, all render browser suites green
This commit is contained in:
sganggs committed 2026-09-30 20:32:47 +08:00
1 parent 15291c2293
commit e6628f0cfe
7 files changed
+53 -60

No files matched your search

+12 -21
View File
@@ -5,9 +5,9 @@
// const board = buildBoard(stage, { uv: resolveUvTable(tiles), crate?: objMesh, area?: AREAS.normal })
// → { grid, buckets: { board, decal, pipe }, gates: [{ r, c, kind: 'start'|'end' }], devices: [...],
// terrain: { water, mire, smog, infection: [[r,c]…] }, edges: [...cyan field edge strips], bounds, stats }
// `devices` = the active stage devices standing on a built tile — the separator row 13 included (act2 m01's blowers at
// the top edge of the normal / 联防 field); the boss field's (rows ≤ 6: its row-6 blowers) only when the boss field is
// built — user playtest #5 item 6 (stageDevices).
// `devices` = the active stage devices standing on a built tile — separator walls included (act2 m01's blowers: the
// row-13 ones at the top edge of the normal / 联防 field, the row-6 ones at its bottom edge and at the top edge of the
// Final Assault field — user playtest #5 item 6 and its follow-up).
//
// World space = render/projection.js: x = col, y = row, z = height (up); tile (r,c) covers [c±½]×[r±½].
// Heights follow render/style.js TILE_H (the Pixi layers stand units on the same tops: tiles.js heightAt).
@@ -52,8 +52,7 @@ const CONTENT_OF = (g) => g !== '#' && g !== 'X';
* boss field): the devices standing on it act on the field — act2 m01's 源石流发生装置 #001/#002 (#101/#102) at (13, 5 /
* 9 / 13 / 17) blow DOWN into rows 12–10 — and the official normal rounds show those machines at the field's top edge
* (user playtest #5 item 6: the wind lanes worked but the blowers were missing; the 2D board always drew row 13). The
* row-6 wall under the bench is built in these views too (in leftNormal), without the boss field's devices on it
* (stageDevices).
* row-6 wall under the bench is built in these views too (in leftNormal), with the boss field's blowers standing on it.
*/
export const AREAS = Object.freeze({
normal: Object.freeze([Object.freeze({ r0: 6, r1: 13, c0: 0, c1: 10 }), Object.freeze({ r0: 13, r1: 18, c0: 6, c1: 14 })]),
@@ -440,7 +439,7 @@ export function buildBoard(stage, opts = {}) {
}
}
const devices = opts.devices === false ? [] : stageDevices(stage, grid, opts.area || null);
const devices = opts.devices === false ? [] : stageDevices(stage, grid);
for (const d of devices) {
if (d.kind === 'platform' || d.kind === 'sealedFloor' || d.kind === 'waterPlatform') slabDevice(board, decal, UVT, d, grid);
}
@@ -487,23 +486,16 @@ export function tube(g, a, b, rad, seg = 8, col = [1, 1, 1]) {
const DEVICE_KINDS = new Set(['crate', 'platform', 'mound', 'blower', 'turret', 'waterPlatform', 'bush', 'sealedFloor']);
/** The boss field's top wall — the row under the normal field's bench (rows ≤ this one are the boss field's). */
export const BOSS_WALL_ROW = 6;
/**
* Active stage devices standing on drawn tiles: `{ kind, r, c, dir, z0, key, alias, rangeTiles }` — every visible
* predefined token of the level is a scene object, a device on the separator row 13 included (act2 m01's 源石流发生装置
* #001 / #002 blow DOWN into the normal field: the official normal rounds show them at its top edge although row 13
* lies outside every configBlackBoard area rect, user playtest #5 item 6). The boss field's devices (rows ≤ 6, its top
* wall under the bench included: act2 m01's #201 / #202 blow DOWN into rows 5–3) are drawn only when `area` builds the
* boss field (a rect below row 6): the official normal cameras never show that wall — it lies under the shop bar in
* prep and off-screen in battle and in the shop-collapsed prep view — while the remake's prep view showed the boss
* round's machines under the bench ("反而在关底吹风机的贴图又出现了"). `area` null = the whole map. The 2D board
* (render/tiles.js _stageDevices) follows the same rule.
* Active stage devices standing on drawn tiles: `{ kind, r, c, dir, z0, key, alias, rangeTiles }`. Every visible
* predefined token of the level is a scene object; a device on a separator wall is drawn wherever that wall is built,
* whichever field it blows into (act2 m01: row 6 belongs to both leftNormal and leftBoss, row 13 tops the normal field).
* The official client hides none of them by the way they face: the row-13 machines show in the official normal rounds
* although row 13 lies outside every configBlackBoard area rect (user playtest #5 item 6), and so do the boss field's
* row-6 machines under the bench (user playtest #5 follow-up: "吹风机原版道中也该有").
*/
export function stageDevices(stage, grid, area = null) {
export function stageDevices(stage, grid) {
const list = Array.isArray(stage?.devices) ? stage.devices : [];
const bossShown = !area || area.some((a) => a.r0 < BOSS_WALL_ROW);
const out = [];
for (const d of list) {
if (!d || !Array.isArray(d.pos) || !Number.isInteger(d.pos[0]) || !Number.isInteger(d.pos[1])) continue;
@@ -511,7 +503,6 @@ export function stageDevices(stage, grid, area = null) {
const active = typeof d.active === 'boolean' ? d.active : !d.hidden;
if (!active) continue;
const [r, c] = d.pos;
if (r <= BOSS_WALL_ROW && !bossShown) continue;
const t = grid?.[r]?.[c];
if (!t || !t.drawn) continue;
out.push({ kind: d.role, r, c, dir: ['UP', 'DOWN', 'LEFT', 'RIGHT'].includes(d.dir) ? d.dir : 'UP', z0: t.h, key: d.key || null, alias: d.alias || null, rangeTiles: Array.isArray(d.rangeTiles) ? d.rangeTiles : null });