feedback5 CB3: 乌尔比安's S3 moves only onto a tile of his own board — no longer behind the red gate of the one-helper 联防 map nor onto a boss field's other half (community report of 2026-10-06, item 27)

Official: PRTS 乌尔比安 S3 备注 ③ — the 【移动】 takes the anchor tile if it can be deployed on, else the tile one beyond, else
his own tile. Cause: "deployable" was any standable tile of the battle rect; UNITE_RECT holds both halves of the 联防 map, and
on escaped_single (the lone helper on the left half, the enemies out of the middle gate at col 10) an anchor that met no
enemy flew on to the right half, where no enemy walks, and he stood there for the whole skill. Now the landing must also
lie on his player's board: Battle.onOwnBoard(ps, r, c), the board region GEO.FIELD mapped onto the field (the hunk is
byte-identical to CB4's 219ab31, which uses it for the 突袭 landing, so the two merge cleanly).

Golden: 1 scenario — fields hidden-boss_9-solo: his first cast from (2,5) landed on (2,11), the solo boss field's right
half (checked instrumented); he now stays and the fight differs from there. Tests: test/sim/feedback5-ulpia-own-board.test.js
(4; the one-helper and the two-helper 联防 cases fail on the parent); test/sim/facing-invariance.test.js — its open field
has no boards, so its setup counts every tile as the player's own (the own-board rule is a board-position rule, like the
deployment order the scenario already avoids).
This commit is contained in:
sganggs committed 2026-10-07 02:30:07 +08:00
1 parent 212a073af6
commit 9b164fdc66
5 files changed
+126 -30

No files matched your search

+25 -26
View File
@@ -1190,38 +1190,37 @@
"killed": 3,
"total": 3,
"unspawned": 0,
"bossHpLeft": 1870565,
"rngDraws": 124,
"bossHpLeft": 1903562,
"rngDraws": 67,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":3,"total":3,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":2969443,"bossDmg":929435,"heal":76069,"deaths":5,"layerGains":{}}},
"players": {"p1":{"killed":3,"total":3,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":2843148,"bossDmg":896438,"heal":73550,"deaths":3,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_5_08_b",2,"uniequip_002_horn",2775,1,1,0,377438,9366,0,127,4],
["p1",2,"op","chess_char_5_11_b",1,"uniequip_002_demkni",4794.8,1,3,0,832,3417,28568,44,33],
["p1",3,"op","chess_char_5_05_b",2,"uniequip_002_ulpia",10629,1,36,0,414083,36990,19701,39,5],
["p1",4,"op","chess_char_5_06_b",2,"uniequip_002_etlchi",2567.95,1,7,1,1490203,17115,16047,201,5],
["p1",5,"op","chess_char_5_13_b",1,"uniequip_002_ghost2",3364.8,1,9,1,118420,12547,3364,79,3],
["p1",6,"op","chess_char_5_07_b",2,"uniequip_002_surtr",2524.94,1,39,0,15153,19432,3930,10,2],
["p1",7,"op","chess_char_5_02_b",2,"uniequip_002_titi",1439,1,5,0,0,0,0,0,0],
["p1",8,"op","chess_char_5_03_b",2,"uniequip_002_blaze2",968.1,1,6,0,289738,1471,967,189,6],
["p1",9,"op","chess_char_5_10_b",2,"uniequip_002_lisa",1480,1,35,0,19621,10891,3491,28,6],
["p1",10,"op","chess_char_5_12_b",0,"uniequip_002_dusk",1787,1,2,1,243954,0,0,53,41],
["p1","~1","token","token_10039_ulpia_block",null,null,3485,0,17,0,0,15,0,0,null],
["p1","~2","token","token_10039_ulpia_block",null,null,3350,0,24,0,0,150,0,0,null],
["p1","~3","token","token_10039_ulpia_block",null,null,3335,0,31,0,0,165,0,0,null]
["p1",1,"op","chess_char_5_08_b",2,"uniequip_002_horn",2698.4,1,1,0,324837,9374,0,127,4],
["p1",2,"op","chess_char_5_11_b",1,"uniequip_002_demkni",4804.8,1,3,0,848,2750,31550,44,34],
["p1",3,"op","chess_char_5_05_b",2,"uniequip_002_ulpia",6503.23,1,31,0,255230,37295,14964,28,4],
["p1",4,"op","chess_char_5_06_b",2,"uniequip_002_etlchi",3543.16,1,7,2,1502942,13705,13608,201,5],
["p1",5,"op","chess_char_5_13_b",1,"uniequip_002_ghost2",3364.8,1,9,1,107792,11970,2438,65,2],
["p1",6,"op","chess_char_5_07_b",2,"uniequip_002_surtr",238.16,1,25,0,8629,21618,5871,17,2],
["p1",7,"op","chess_char_5_02_b",2,"uniequip_002_titi",1439,1,5,0,6082,0,2705,17,1],
["p1",8,"op","chess_char_5_03_b",2,"uniequip_002_blaze2",351.58,1,6,0,364837,2591,1471,188,6],
["p1",9,"op","chess_char_5_10_b",2,"uniequip_002_lisa",1480,1,24,0,15428,5383,943,40,5],
["p1",10,"op","chess_char_5_12_b",0,"uniequip_002_dusk",1777,1,2,0,256524,80,0,53,41],
["p1","~1","token","token_10039_ulpia_block",null,null,3350,0,20,0,0,150,0,0,null],
["p1","~2","token","token_10039_ulpia_block",null,null,3335,0,28,0,0,165,0,0,null]
],
"enemies": {
"enemy_10067_ftsjc": [1,1,0,1161,21565],
"enemy_5601_entlec": [7,6,0,0,174538],
"enemy_9007_acelem": [2,2,0,747,119439],
"enemy_9017_achunt_2": [1,0,0,33211,929435],
"enemy_9018_actrpa": [1,0,0,16277,517062],
"enemy_9019_actrpb": [1,0,0,18530,477352],
"enemy_9020_actrpc": [1,0,0,15909,730051]
"enemy_5601_entlec": [6,6,0,0,166866],
"enemy_9007_acelem": [2,2,0,1054,119439],
"enemy_9017_achunt_2": [1,0,0,28371,896438],
"enemy_9018_actrpa": [1,0,0,14779,508719],
"enemy_9019_actrpb": [1,0,0,19659,453678],
"enemy_9020_actrpc": [1,0,0,17888,676442]
},
"hooks": [1,40,6000,876,876,3547,8777,6397,14,19,105,102,118,25207,1875,10,14,0,1,109,0,2,0],
"events": {"atk":1409,"deploy":26,"die":19,"dmg":8204,"engage":9,"fx":850,"heal":383,"skill":207,"spawn":27,"status":303},
"fx": {"anchor":5,"aoe":2,"beam":20,"burst":2,"candle":7,"charge":7,"dash":12,"downed":1,"ember":2,"explode":1,"healAoe":33,"link":597,"overload":4,"palsy":4,"phase":12,"revive":1,"shieldBreak":10,"splash":118,"substitute":3,"swap":3,"teleport":6},
"snaps": "37ac48f3 092a4bc2 41b7e5d4 56d6fd20 79e6de1e 5c8059ed c19e7138 677e05fa cbd43f62 8f0f65b8 4d76189a 297c0633 3bc4d3c3 9ff783eb 79b3ec5e 5c6f5ce5 a8d3399b 8de60a18 1eaa3427 f2260143"
"hooks": [1,33,6000,894,894,3465,8457,4295,12,15,104,100,124,25057,1653,12,13,0,1,78,0,2,0],
"events": {"atk":1389,"deploy":20,"die":15,"dmg":7934,"engage":10,"fx":855,"heal":375,"skill":204,"spawn":25,"status":337},
"fx": {"anchor":4,"aoe":2,"beam":24,"burst":2,"candle":6,"charge":7,"dash":12,"downed":1,"ember":1,"explode":1,"healAoe":34,"link":597,"overload":4,"palsy":2,"phase":12,"revive":1,"shieldBreak":11,"sleepGuard":1,"splash":124,"substitute":2,"swap":2,"teleport":4,"wake":1},
"snaps": "37ac48f3 f657c2f6 e25b0f01 5ea69d76 c3a7469d 9558529b 0fd83c8e 2c035602 b69af21f c2a0eb54 b31f2ad7 8b64e08b bbbd8e09 0dca9aae ce7aae50 b0cd3d8c 35382dd2 d3b4e413 db5e9c68 41e7d434"
},
"hidden-boss_10-pair": {
"about": "假想敌:管 pair pool 15200000: chess_char_5_19_b chess_char_4_01_b chess_char_5_14_b chess_char_5_17_b chess_char_5_15_b chess_char_5_20_b chess_char_5_21_b chess_char_5_22_b chess_char_5_23_b chess_char_4_02_b | chess_char_4_11_b chess_char_4_04_b chess_char_4_07_b chess_char_4_16_b chess_char_4_09_b chess_char_4_06_b chess_char_4_10_b chess_char_4_12_b chess_char_4_13_b chess_char_4_14_b token_10041_cathy_catsld token_10041_cathy_catsld",
+6 -1
View File
@@ -48,7 +48,12 @@ function scenario(chessId, dir, secs) {
e_fly: enemyRec({ key: 'e_fly', hp: 1e6, speed: 0, motion: 'FLY', def: 50, res: 30 }),
},
},
setup: (b) => b.on('deploy', ({ unit }) => { if (unit.uid >= 100) unit.hp = unit.s.maxHp * (0.3 + 0.1 * (unit.uid - 100)); }),
setup: (b) => {
b.on('deploy', ({ unit }) => { if (unit.uid >= 100) unit.hp = unit.s.maxHp * (0.3 + 0.1 * (unit.uid - 100)); });
// the open field has no boards: "a tile of the player's own board" (Battle.onOwnBoard — 乌尔比安 S3's landing) is a
// board-position rule (DESIGN), not a facing one — every tile counts here, so the anchor's landing turns with him
b.onOwnBoard = () => true;
},
});
h.run(secs);
checkInvariants(h.b);
@@ -0,0 +1,63 @@
// test/sim/feedback5-ulpia-own-board.test.js — community report of 2026-10-06 (item 27) 「乌尔比安使用3技能会在联防阶段跳到红门
// 后」. 乌尔比安 S3 必须开辟的通路 throws the anchor up to 6 tiles ahead (the nearest enemy of the line, else the farthest tile) and
// moves there when the tile "可以部署" (PRTS 备注 ③: the landing tile > the tile one beyond > his own tile). On the one-helper 联防
// map (act1autochess_escaped_single: the helper's pieces on the left half, the enemies out of the middle gate at col 10) the
// right half is no tile his player deploys on, yet an anchor that met no enemy flew there and he moved behind the red gate for
// the whole skill. Now the landing must lie on his own board (Battle.onOwnBoard, the board region mapped onto the field).
// Run: node --test test/sim/feedback5-ulpia-own-board.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
const ULPIA = 'chess_char_5_05_a';
const dummy = enemyRec({ key: 'enemy_anchor_dummy', hp: 1e9, speed: 0, mass: 0, atk: 0 });
/** 乌尔比安 cast S3 on `tile` of `kind`'s field; `enemy` = a parked enemy's tile or null. Returns where he stands after the cast. */
function cast({ kind = 'unite', stageId = 'act1autochess_escaped_single', tile = [10, 7], enemy = null, colOffset = 0 } = {}) {
const h = makeBattle({
kind, stageId, seed: 5, autoFinish: false, timeLimit: 120, defs: { enemies: { enemy_anchor_dummy: dummy } },
players: [{ playerId: 'p1', seat: 0, side: 'L', colOffset, units: [{ uid: 1, kind: 'chess', chessId: ULPIA, row: tile[0], col: tile[1] }], bonds: {} }],
});
h.step();
const u = h.unit(ULPIA);
const home = [u.tileR, u.tileC];
if (enemy) { h.spawn('enemy_anchor_dummy', { pos: enemy }); h.step(); } // (one tick: the enemy joins the tile index)
u.skill.gainSp(999, 'test');
// (its CUSTOM_RANGE trigger waits for an enemy on the line: with none the cast is made by hand, as a player's would be)
if (!u.skill.active) assert.ok(u.skill.activate('test'), 'S3 cast');
h.run(0.2);
const at = [u.tileR, u.tileC];
checkInvariants(h.b);
return { h, u, home, at };
}
test('one-helper 联防: an anchor that meets no enemy flies on past the middle gate — he stays on his own half (until 0.2.0 he moved behind the red gate)', () => {
const { h, u, home, at } = cast({ tile: [10, 7] });
assert.deepEqual(home, [10, 7]);
assert.ok(h.b.rect.c1 >= 13, 'the 联防 field spans both halves');
assert.deepEqual(at, home, `stays on (10,7) — not ${at}`);
assert.ok(h.b.onOwnBoard(u.player, 10, 7) && !h.b.onOwnBoard(u.player, 10, 13), 'cols 11+ are not his board');
assert.equal(h.b.allyUnits.filter((a) => a.kind === 'token' && a.alive).length, 0, 'no 从不混淆的方向: nothing to return from');
});
test('one-helper 联防: an enemy on his half still draws the anchor and he moves next to it (own board)', () => {
const { at } = cast({ tile: [10, 4], enemy: [10, 8] });
assert.deepEqual(at, [10, 8], 'he moves onto the anchor tile on his half');
});
test('two-helper 联防: the left helper never lands on the right helper\'s half; the right helper moves on its own half', () => {
const left = cast({ stageId: 'act1autochess_escaped_multi', tile: [10, 7] });
assert.deepEqual(left.at, left.home, `left helper stays (${left.at})`);
// the right-hand helper (colOffset 8): board col 3 → field col 11, enemy 3 tiles ahead on its own half
const right = cast({ stageId: 'act1autochess_escaped_multi', tile: [10, 3], colOffset: 8, enemy: [10, 14] });
assert.deepEqual(right.home, [10, 11]);
assert.deepEqual(right.at, [10, 14], 'moves on its own half');
assert.ok(right.h.b.onOwnBoard(right.u.player, 10, 14) && !right.h.b.onOwnBoard(right.u.player, 10, 7));
});
test('a normal battle is unchanged: the anchor stops at the field edge and he moves onto the farthest tile of his board', () => {
const { at, h, u } = cast({ kind: 'normal', stageId: 'flat', tile: [10, 3] });
assert.deepEqual(at, [10, 9], `6 tiles ahead, the last road tile before the gate column (${at})`);
assert.ok(h.b.onOwnBoard(u.player, 10, 9));
});