feedback6: merge R21B — 联防 plays on the round's battlefield again, its terrain, crates, water, devices and runes as in 0.1.4 (the owner's report 「联防阶段地形全消失了」 and knowledge of the official mode, GitHub #244's tournament video; 0.2.0's escaped level was the placeholder road every wave-template level carries); the 联防 battle input is byte-identical to 0.1.4's in 20 cases; 乌尔比安's comment keeps both streams' sentences; golden as R21B's commit (unite-1 / unite-2 back to their stages, coop2-NORMAL-3, coop2-HARD-4, coop3-ABYSS-5, coop4-ABYSS-7, coop2-NORMAL-8-serverrun, coop2-NORMAL-14-standins), the merged tree differs from the branch only in R21A's boss pair fields

This commit is contained in:
sganggs committed 2026-10-07 17:54:16 +08:00
commit 26c72a6d95
25 files changed
+334 -283

No files matched your search

+6 -5
View File
@@ -5,7 +5,7 @@
import { PHASE, GEO } from '../../../shared/constants.js';
import { deriveSeed } from '../../sim/rng.js';
import { uniteBattleOpts, uniteSurvivors, uniteStageId } from '../unite.js';
import { uniteBattleOpts, uniteSurvivors } from '../unite.js';
import { FieldRunner, timelineAt, uniteBillBounds } from '../fields.js';
import { uniteLeft } from '../../sim/spec.js';
import { FLOW_TICKER_PRIORITY, DELAYS } from './common.js';
@@ -40,9 +40,10 @@ export class MatchUnite {
}
/**
* 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.
* Battle options of the 联防 field (helpers' carried end state, the leakers' enemies) on the round's battlefield, its
* terrain, crates, water, devices and runes included (unite.js header; the owner's decision of 2026-10-07 — 0.2.0's
* escaped-level map is withdrawn). The field meta and the client-run spec carry the match stageId, so every viewer
* draws the battlefield the boards stand on.
*/
_uniteOpts(plan, limit) {
const { wave, players } = uniteBattleOpts(this, plan, limit);
@@ -51,7 +52,7 @@ export class MatchUnite {
kind: 'unite',
modeId: this.modeId,
round: this.round,
stageId: uniteStageId(this.gd, plan.helpers.length) ?? this.stageId,
stageId: this.stageId,
rect: { ...GEO.UNITE_RECT },
timeLimit: limit,
players,
+7 -21
View File
@@ -5,11 +5,13 @@
// 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. 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
// left half (colOffset 0); a lone helper plays escaped_single on its own field. The field is the round's battlefield —
// the match stage with its terrain, crates, water, devices and runes — opened to both halves (GEO.UNITE_RECT, cols
// 0–20; the stage's right half is its left half + 8 columns), every helper's pieces on their prep tiles
// ("按休整期位置部署在场"), the right-hand one shifted 8 columns (= the stage config's player_map_lr_offset 8): the
// owner's knowledge of the official mode, 2026-10-07 「官服保留地形」. 0.2.0 fielded it on the escaped levels' own map,
// an empty road (GitHub #41) — withdrawn in 0.2.1; data/stages.json keeps those two records (kind 'unite'), which no
// match fields. 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
@@ -120,22 +122,6 @@ 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);
+2 -1
View File
@@ -57,7 +57,8 @@ export class BattleEconomy {
* whose half of the field it fell on: PRTS 卫戍协议:盟约 决策 "该敌人于对应玩家所属区域倒下时,使相应玩家获得额外资金"
* [ASSUMED for 联防: no source names the payee of a death nobody caused there]. Until 0.2.0 that fallback was the
* leaker, who has no entry in a 联防 battle, and addCoins dropped the coins (community report of 2026-10-06
* 「被源石地板烫死的悬赏没给赏金」: until 0.1.3 the 联防 field was the round's map, 战场#04's 活性源石 included).
* 「被源石地板烫死的悬赏没给赏金」: in 0.1.x the 联防 field was the round's map, as it is again since 0.2.1, 战场#04's
* 活性源石 included).
*/
_bountyPayee(unit, killer = null) {
if (killer && killer.side === 'ally' && killer.ownerId != null && this._pp(killer.ownerId)) return killer.ownerId;
@@ -96,10 +96,9 @@ export default {
// PRTS 备注: landing tile > the tile one beyond it > his own tile (a deployable, free, unreserved melee tile).
// "可部署" is a tile his player may deploy on: his own board (Battle.onOwnBoard) — never the other half of a 联防 or
// boss field nor a boss field's hand / 临时整备区 rows; a teammate's half stays closed to him even where the 突袭
// landing may take it (Battle.onFieldBoard, DESIGN §26.1). Community report of 2026-10-06 (item 27)
// 「乌尔比安使用3技能会在联防阶段跳到红门后」: on the one-helper 联防 map (escaped_single, enemies from the middle gate at
// col 10) an anchor that met no enemy flew on to the right half, where no enemy ever walks, and he moved there for
// the whole skill
// landing may take it (Battle.onFieldBoard, DESIGN §26.1). Community report of 2026-10-06 (item 27) 「乌尔比安使用3技能会在
// 联防阶段跳到红门后」: in a one-helper 联防 (escaped_single's enemies come out of the middle gate at col 10) an anchor
// that met no enemy flew on to the right half, where no enemy ever walks, and he moved there for the whole skill
const ok = ([r, c]) => (r !== unit.tileR || c !== unit.tileC) && battle.grid.inRect(r, c) && battle.onOwnBoard(unit.player, r, c) && battle.grid.canStand(r, c) && !battle.grid.isObstacle(r, c) && !battle.isReservedTile(r, c);
const dest = [[sr, sc], frontOf(unit.tileR, unit.tileC, unit.dir, stop + 1)].find(ok);
if (dest == null) return;