feedback5 WE: 联防 is played on its own map, the escaped template level (GitHub #41, item 3) — act2autochess constData escapedBattleTemplateMapSinglePlayer / MultiPlayer = level_act1autochess_escaped_single / _multi name the level of the 联防 battle, and its map is the field: two road halves (cols 3–9 and 11–17, rows 9–12) joined at col 10, no water, crates or devices (until 0.1.3 the round's stage, opened to both halves). tools/build-data.mjs builds both into data/stages.json after the 11 battle stages (kind 'unite', helpers 1 / 2, weight 0, inactive, no modes, the name 联防阵地(N名玩家) the remake's own label [ASSUMED]; validation ties config.unite.templates to them); regenerated offline, a JSON compare shows only the two added records. unite.js uniteStageId picks the map of the helper count (escaped_single's routes enter at col 10, escaped_multi's at col 18 through (9,10)); match/unitePhase.js fields the 联防 battle on it (the round's stage only with data that lacks it); each helper on its prep tiles, the first of two shifted 8 columns onto the right half as before (the official halves; player_map_lr_offset 8). Client: screens/game.js draws the shown field's own map and restores the board's on leaving it (a 2-line change in a file of the R5 split; render/app.js enterBattle already loads m.field stageId); the dev mock's 联防 follows. The 联防 BGM (PR #110) is unchanged. Tests: test/match/feedback5-unite-map.test.js (both combat modes, 1 and 2 helpers, degraded data), test/data.test.js, test/render/tiles.test.js. Docs: DATA §12, META §4, DESIGN §3, PLAYING §5. Golden: fields unite-1 / unite-2 (the battle's stage → the 联防 map) and the 6 co-op match scenarios with a 联防 round (coop2-NORMAL-3, coop2-HARD-4, coop3-ABYSS-5, coop4-FUNNY-6, coop4-ABYSS-7, coop2-NORMAL-8-serverrun); roster / bonds unchanged (tools/golden.mjs leaves the 联防 maps out of its stage list)

This commit is contained in:
sganggs committed 2026-10-05 18:17:08 +08:00
1 parent db1b44bb71
commit 8a43624935
19 files changed
+386 -174

No files matched your search

+7 -3
View File
@@ -5,7 +5,7 @@
import { PHASE, GEO } from '../../../shared/constants.js';
import { deriveSeed } from '../../sim/rng.js';
import { uniteBattleOpts, uniteSurvivors } from '../unite.js';
import { uniteBattleOpts, uniteSurvivors, uniteStageId } from '../unite.js';
import { FieldRunner, timelineAt, uniteBillBounds } from '../fields.js';
import { uniteLeft } from '../../sim/spec.js';
import { FLOW_TICKER_PRIORITY, DELAYS } from './common.js';
@@ -38,7 +38,11 @@ export class MatchUnite {
this.runner.start();
}
/** Battle options of the 联防 field (helpers' carried end state, the leakers' enemies). */
/**
* Battle options of the 联防 field (helpers' carried end state, the leakers' enemies) on its own map — the escaped
* template's (unite.js uniteStageId; GitHub #41), the round's stage only when the data lacks it. The field meta and the
* client-run spec carry that stageId, so every viewer draws the 联防 map.
*/
_uniteOpts(plan, limit) {
const { wave, players } = uniteBattleOpts(this, plan, limit);
return {
@@ -46,7 +50,7 @@ export class MatchUnite {
kind: 'unite',
modeId: this.modeId,
round: this.round,
stageId: this.stageId,
stageId: uniteStageId(this.gd, plan.helpers.length) ?? this.stageId,
rect: { ...GEO.UNITE_RECT },
timeLimit: limit,
players,
+21 -1
View File
@@ -5,7 +5,11 @@
// players chosen by most units on the field (downed included) > has an active bond (存疑) > most undowned units, then
// seat; with 2 helpers the one ranked first by most units > active bond > Σ active bond layers (存疑) > most undowned
// units "率先迎敌" on the RIGHT-hand field (colOffset +8, where the escaped_multi routes enter), the other keeps the
// left half (colOffset 0); a lone helper plays escaped_single on its own field. Their operators keep the HP ratio and
// left half (colOffset 0); a lone helper plays escaped_single on its own field. The field is the template's own map
// (uniteStageId: data/stages.json act1autochess_escaped_single / _multi, kind 'unite' — two road halves joined at col 10,
// the right half's gates at col 18; GitHub #41: until 0.1.3 it was the round's stage, its water, crates and devices
// included), every helper's pieces on their prep tiles ("按休整期位置部署在场"), the right-hand one shifted 8 columns
// (the official maps' halves, = the stage config's player_map_lr_offset 8). Their operators keep the HP ratio and
// the SP (技力, stored charges included) from the end of their own combat, nothing else — a skill still running then
// enters switched off (BattleResult.unitsEnd → PlayerBattleInput.units[].carryState `{ hpPct, sp }`, "阵地以其当前状态";
// community report #34 / GitHub #82: it used to restart for free). An operator knocked out at the end of its own combat (alive false) is fielded with
@@ -115,6 +119,22 @@ export function helperOrder(m, perfects, results) {
return select.sort((a, b) => S(b).units - S(a).units || (S(b).active - S(a).active) || S(b).layers - S(a).layers || S(b).standing - S(a).standing || a.seat - b.seat);
}
/**
* The map of the 联防 field (GitHub #41): act2autochess constData escapedBattleTemplateMapSinglePlayer (1 helper) /
* MultiPlayer (2 helpers) name one level for both the 联防 wave and its map — config.unite.templates[n], built into
* data/stages.json as the stage of the same id (kind 'unite'). null when the data has no such stage (degraded data:
* the caller keeps the round's stage).
* @param {import('./gamedata.js').GameData} gd
* @param {number} helperCount
* @returns {string|null}
*/
export function uniteStageId(gd, helperCount) {
const templates = gd.unite.templates || {};
const id = templates[String(helperCount)] ?? templates[helperCount] ?? null;
const st = id ? gd.stage(id) : null;
return st && st.kind === 'unite' ? id : null;
}
/** Battle options for the unite field (without data/logger, added by the match). */
export function uniteBattleOpts(m, plan, timeLimit) {
const wave = buildUniteWave(m.gd, plan.leaked, plan.helpers.length, timeLimit);