fix(sim): keep Wisdel shadows off the boss bench (#557)
This commit is contained in:
5 files changed
+40
-2
No files matched your search
@@ -1617,3 +1617,7 @@ general hook guard. The queue is emptied in `finally`; it introduces no timer or
|
||||
- Healing skill activation includes elemental-only injury when the operator's heal profile can cure it. Pinecone S2 growth is deployment-local. Skadi S1 and Stead preserve redirect ancestry and take both shares from the pre-redirect hit, so the total damage is conserved.
|
||||
|
||||
`champagneHold` includes the internal `isolated` ally-selection flag (§29.11): device-type champagne bombs are excluded from ordinary allied auras/inspiration in both creation paths. Other unhealable summons retain their own selection behavior.
|
||||
|
||||
维什戴尔's `shadowTile` filters the boss/hidden bench rows before ranking summon candidates. See
|
||||
[DESIGN §3](design/geometry-time.md#3-coordinates-fields-and-geometry) for the deployment boundary; `grid.inRect`
|
||||
alone also admits those bench rows.
|
||||
@@ -9,7 +9,7 @@ Part of [DESIGN.md](../DESIGN.md) (the index; section numbers are global).
|
||||
- **Normal field** (own board): rows 9–12, cols 2–10. Enemy gates `S` at (9,10) and (12,10) (R1–3 only the lower one); protection objective `E` at (9,2). Col 9 lane is not deployable (tile type decides).
|
||||
- **Hand (整备区)**: row 7, cols 0–9 (10 regular slots, index = col). **Temp slots**: row 8, cols 4–8 (5 slots). Hand/temp tiles are never part of a battle.
|
||||
- **Partner display** cols 11–18 of rows 9–12 (the unite field uses them).
|
||||
- **Boss field**: rows 1–5; left half cols 2–10, right half cols 10–18.
|
||||
- **Boss field**: combat deployment rows 2–5; left half cols 2–10, right half cols 10–18. The mapped hand/temp rows are 0/1, inside the simulation rect but not combat deployment tiles. 维什戴尔's talent and S3 exclude them when placing 魂灵之影; without a free deployable tile in range, no shadow is summoned (§30.31).
|
||||
- Enemy preview pen rows 14–18 × cols 7–13 (row 16 empty): during prep the next round's enemies idle there (upper-gate enemies rows 17–18, lower-gate rows 14–15, ≤ 50 models, spawn-time order) — research 08 §4 / 09 §2.
|
||||
- Tile semantics come from `data/stages.json` legend: height (LOW/HIGH), buildable (ALL/MELEE/RANGED/NONE — the **effective** deploy type: the 深水区 `tile_deepsea` refuses deployment, NONE with the level's `buildableType` ALL kept, PRTS 深水区 地形信息 "拒绝部署(待补充)"; an active 特制水上平台 makes its tile deployable in the prep deploy map only, §21.3), ground-passable, fly-only, special terrain (mire, smog, deepsea, infection). The fenced tiles (围墙 `tile_fence_bound` / 围栏 `tile_fence`, legend `b`: LOW, buildable ALL, passable to flyers only) take units, which attack normally from there but block no ground enemy (PRTS 地形机制, §21.23). Melee chess may stand only on LOW tiles with buildable ALL/MELEE — except a melee chess whose trait reads 「可以放置于远程位」 (the 钩索师 / 推击手 branch trait: 歌蕾蒂娅, 崖心, 见行者, normal and elite, with any module or none), which may use the ranged tiles too (`shared/highGround.js`; the owner's decision of 2026-10-05, following PRTS — it reverses the 2026-10-04 one that allowed only elite 歌蕾蒂娅 with HOK-Y; on a 高台 it attacks and blocks nothing); ranged chess on ALL/RANGED **and** on LOW ALL/MELEE tiles ("所有行动内远程干员可部署在近战位"). Tokens follow their own `position`; a summon marked `ownerRange` (狼群, 流形: "只能部署在召唤者攻击范围内"; Mon3tr's 重构体 by her talent) goes only on a tile of its owner's attack range — the loadout grid rotated by the owner's facing around its tile (`PlayerState.summonRange`, §21.3) —, one marked `ownerRangeOutside` / `rangedTilesOnly` (凯尔希·思衡托's 战术锚点: "仅可以部署在凯尔希·思衡托攻击范围外的远程位") only on a 高台 outside it (`summonExcluded`, placement class `high`; 0.2.0).
|
||||
- **Facing (corrected, see research 09 §1.2):** like standard Arknights, every board piece has a direction `dir ∈ UP|RIGHT|DOWN|LEFT` chosen with the 4-direction deploy wheel after dropping it on a tile (also re-orientable in place); default `RIGHT`; persists across rounds. Range grid entries are `[dRow, dCol]` relative to facing RIGHT and are rotated: RIGHT `(dr,dc)`, UP `(dc,−dr)`, LEFT `(−dr,−dc)`, DOWN `(−dc,dr)` (row 0 = bottom); `rangeExtend` applies along +dCol before rotating — to a running skill's range too, unless that skill ignores 攻击距离 (`targeting.noRangeExtend`, 信仰搅拌机 S3, §21.16). The right-side Final Assault player is mirrored col `c→20−c` with RIGHT↔LEFT (UP/DOWN unchanged).
|
||||
|
||||
@@ -704,3 +704,15 @@ default skill is S2, which is the skill this correction changes.
|
||||
| 耶拉 was on the pre-change lineup; that seat is then eliminated and the stored lineup is empty | `solo-ABYSS-1`: defeat at round 13 → eliminated at round 12, team LP 0 → null. The pre-change lineup had 耶拉 `3_20_a` and 至简 `3_13_a`. First divergence round 8: LP 27 → 18, killed 19 → 10, leaks 0 → 9. `coop4-ABYSS-7`: still a defeat at round 13, team LP 0, boss pool 4821366 → 3417504. ai_1 is eliminated at round 13; the pre-change lineup had 至简 `3_13_a` and 耶拉 `3_20_a`. First divergence round 8: ai_1 LP 19 → 20, killed 2 → 3, leaks 7 → 6. ai_0's lineup also changes and has no funnel chess id before or after |
|
||||
| 耶拉 plus other funnel operators, lineup unchanged, same victory and the same team LP 130 | `coop4-FUNNY-6`: ai_0 耶拉 `3_20_a`, ai_1 荒芜拉普兰德 `6_18_a`, ai_3 洛洛 `2_10_a`. First divergence round 11, ai_0's kjeragShip layers 26 → 23 |
|
||||
| No 耶拉 on the final lineup. The diverging round's pieces and skill choice are not stored | `coop2-NORMAL-1-coverage-standins`: still a defeat, team LP 0, boss pool 25040 unchanged. ai_0's final lineup includes 荒芜拉普兰德 `6_18_a`. First divergence round 7, the other seat's miraShip layer 5 → 6. `coop2-NORMAL-14-standins`: victory, team LP 31 → 40 (ai_0 LP 13 → 22). Neither final lineup contains a funnel chess id. First divergence round 10 bond layers (raidShip 26 → 28, steadShip 16 → 18); that round's board hash is unchanged |
|
||||
|
||||
### 30.31 魂灵之影 excludes the boss-field bench (#557)
|
||||
|
||||
The boss/hidden simulation rect includes rows 0–1, the mapped hand and temporary bench. `shadowTile` previously
|
||||
accepted their buildable terrain, so a bottom-row 维什戴尔 could summon there through her deployment talent or S3.
|
||||
It now excludes rows below the combat deployment boundary before choosing the nearest free tile. With no candidate,
|
||||
the summon is skipped. The change preserves range, occupancy and tie-breaking, including cross-half candidates.
|
||||
|
||||
Sources: [PRTS 维什戴尔](https://prts.wiki/w/维什戴尔), the talent's nearest/lower-row/left-column deployable-tile rule;
|
||||
the existing `GEO.FIELD` and `BOSS_ROW_OFFSET` mapping; [issue #557](https://github.com/sganggs/Stronghold-Protocol/issues/557).
|
||||
No new [ASSUMED] rule. The regression covers boss and hidden fields, both mirrored sides, a full range and one free
|
||||
combat tile, through the deployment talent and an actual S3 activation. Normative correction: DESIGN §3; API note: SIM.
|
||||
@@ -60,7 +60,8 @@
|
||||
import { num, talentBb, traitBb, skillRec, up, batMod } from '../shared/tier1.js';
|
||||
import { absoluteRangeKeys, sortEnemyTargets } from '../../../targeting.js';
|
||||
import { hasHp } from '../../../damage.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { COLS, BOSS_ROW_OFFSET } from '../../../constants.js';
|
||||
import { GEO } from '../../../../../shared/constants.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_wisdel_1';
|
||||
@@ -138,6 +139,8 @@ function shadowTile(battle, host) {
|
||||
let best = null, bd = Infinity;
|
||||
for (const k of host.rangeKeys ?? []) {
|
||||
const r = Math.floor(k / COLS), c = k % COLS;
|
||||
// BOSS_RECT includes the hand/temp rows (0–1); they are not combat deployment tiles.
|
||||
if ((battle.kind === 'boss' || battle.kind === 'hidden') && r < GEO.FIELD.r0 + BOSS_ROW_OFFSET) continue;
|
||||
if (!battle.grid.inRect(r, c) || !battle.grid.canStand(r, c, { ranged: true }) || battle.isReservedTile(r, c)) continue;
|
||||
const d = hypot(r - host.tileR, c - host.tileC);
|
||||
if (d < bd - 1e-9 || (Math.abs(d - bd) <= 1e-9 && (r < best[0] || (r === best[0] && c < best[1])))) { bd = d; best = [r, c]; }
|
||||
|
||||
@@ -184,6 +184,25 @@ test('T2 死魂灵的余息: a 魂灵之影 (the form\'s stats, 禁疗, no attac
|
||||
done(h);
|
||||
});
|
||||
|
||||
test('user #557: Given a bottom-row boss lineup, When the talent and S3 summon, Then shadows skip the bench and use only free combat tiles', () => {
|
||||
for (const kind of ['boss', 'hidden']) for (const side of ['L', 'R']) for (const free of [false, true]) {
|
||||
// Board (9, 7) becomes field (2, 7)/(2, 13). Row 1 is the temporary bench, inside BOSS_RECT.
|
||||
const units = [{ uid: 1, chessId: SLOT[5], diy: { charId: WD, skillIndex: 2 }, row: 9, col: 7, dir: 'DOWN' }];
|
||||
for (let col = 5; col <= 9; col++) {
|
||||
if (col === 7 || free && col === 8) continue;
|
||||
units.push({ uid: col, chessId: 'chess_char_1_01_a', row: 9, col });
|
||||
}
|
||||
const h = makeBattle({ kind, stageId: 'act2autochess_m02', players: [{ playerId: 'p1', side, units }], autoFinish: false });
|
||||
h.step();
|
||||
const u = h.unit(1);
|
||||
const expected = free ? [[2, side === 'L' ? 8 : 12]] : [];
|
||||
assert.deepEqual(shadowsOf(h, u).map(t => [t.tileR, t.tileC]), expected, `${kind}/${side}/${free}: deployment talent`);
|
||||
assert.ok(h.runUntil(() => u.skill.activations === 1, 40), 'S3 actually activates without an enemy');
|
||||
assert.deepEqual(shadowsOf(h, u).map(t => [t.tileR, t.tileC]), expected, `${kind}/${side}/${free}: S3 cannot spill into the bench`);
|
||||
done(h);
|
||||
}
|
||||
});
|
||||
|
||||
test('魂灵之影\'s skill: every 5 SP (time; +0–2 after each cast) a ground enemy of 维什戴尔\'s range — unmarked first — takes its ATK as arts damage, 停顿 1 s and her 残影; never an air unit', () => {
|
||||
for (const [tier, elite] of [[5, false], [6, true]]) {
|
||||
const v = shadowOf(tier, elite);
|
||||
|
||||
Reference in new issue
Block a user