feedback3 WF: the initial deployment goes by column — the left column first, top to bottom within a column (PRTS 从上到下>从左到右 read as a scan, the 阿戈尔 devour's order; mirrored boss side from its own left); a merge's elite among several deployed copies follows it (server and client); tests whose target was the latest deployed keep it
This commit is contained in:
1 parent
0e3429aa1f
commit
ca3d0572ad
25 files changed
+94
-69
No files matched your search
@@ -321,7 +321,8 @@ for (const key of ['enemy_1203_sfhu', 'enemy_1203_sfhu_2']) {
|
||||
// 持续时间和效果独立计算"; "普通攻击对目标对及目标周围半径1.0范围内的所有我方单位造成法术普通伤害" (0.1.3: the blast took the attack
|
||||
// radius 2 and left a steam zone; the attack hit its target only)
|
||||
test(`${nm(key)}: death spawns 4 茶器; the blast (r 1.25, ATK arts) gives each unit hit one ${tb(key, 'DeadBoom.duration')} s layer of ASPD ${tb(key, 'DeadBoom.attack_speed')}; its attacks splash r 1.0`, () => {
|
||||
const h = arena({ units: [{ chessId: 't_gun', row: 10, col: 6 }, { chessId: 't_wall', row: 11, col: 8 }, { chessId: 't_wall2', row: 10, col: 5 }], hooks: ['damaged'], captureNoisy: true });
|
||||
// t_gun deploys last (column 6, below t_wall): the attack's target, t_wall2 and t_wall within 1.0 of it
|
||||
const h = arena({ units: [{ chessId: 't_gun', row: 10, col: 6 }, { chessId: 't_wall', row: 11, col: 6 }, { chessId: 't_wall2', row: 10, col: 5 }], hooks: ['damaged'], captureNoisy: true });
|
||||
h.step();
|
||||
const e = put(h, key, [10, 7]);
|
||||
h.runUntil(() => h.hooksOf('damaged').some((c) => c.source === e && c.dmg.isAttack), 10);
|
||||
@@ -1372,7 +1373,8 @@ test(`${nm('enemy_1050_lslime')}: 4 targets, burning DoT on hit, ASPD up below h
|
||||
// 目标造成物理普通伤害,对溅射目标造成物理溅射伤害") — community report #14: until 0.1.3 the target took a full attack and the 4
|
||||
// orthogonal neighbours 100 % ATK (no diagonal), DEF down for 3 s
|
||||
test(`${nm('enemy_1500_skulsr')}: an unblocked attack is a grenade — 26 % ATK on the target and on its 8 surrounding tiles, DEF −50 % for 5 s; blocked a plain hit; ATK up below half`, () => {
|
||||
const h = arena({ units: [{ chessId: 't_wall', row: 11, col: 3 }, { chessId: 't_wall2', row: 12, col: 6 }, { chessId: 't_wall3', row: 11, col: 5 }], captureNoisy: true, hooks: ['statusApplied', 'damaged'] });
|
||||
// t_wall3 is the one in range (√5): the target; t_wall2 on its diagonal, t_wall two tiles away
|
||||
const h = arena({ units: [{ chessId: 't_wall', row: 11, col: 3 }, { chessId: 't_wall2', row: 12, col: 4 }, { chessId: 't_wall3', row: 11, col: 5 }], captureNoisy: true, hooks: ['statusApplied', 'damaged'] });
|
||||
h.step();
|
||||
const e = put(h, 'enemy_1500_skulsr', [10, 7]);
|
||||
h.runUntil(() => e.stats.attacks >= 1, 10);
|
||||
|
||||
@@ -298,6 +298,10 @@ test('余: S3 fire wall adds burn to allied arts damage crossing it and clears c
|
||||
enemies: [{ key: 'enemy_dummy', pos: [10, 6] }, { key: 'enemy_shooter', pos: [11, 8] }],
|
||||
seed: 11,
|
||||
});
|
||||
// the shooter aims at 优等生 behind the wall: a taunt, since 余 (his column further right) now deploys last — the
|
||||
// latest deployed, the shooter's pick at equal taunt (deployment by column since 0.1.3)
|
||||
h.step();
|
||||
h.b.addBuff(h.unit('chess_char_1_15_a'), { key: 'test:taunt', mods: { taunt: 1 }, persist: true });
|
||||
const u = h.unit('chess_char_6_03_a');
|
||||
assert.ok(h.runUntil(() => u.skill.active, 10));
|
||||
h.run(15);
|
||||
|
||||
@@ -515,7 +515,8 @@ test('user playtest #6 follow-up: a merge consuming a deployed copy puts the eli
|
||||
assert.equal(promo.eliteTileAmongSeveralDeployed?.assumed, true);
|
||||
for (const [name, text] of [['research 01', R01], ['00-INDEX', INDEX], ['META', META], ['SIM', SIM], ['PlayerState', PS]]) assert.match(text.replace(/\s+|\/\/\s/g, ''), official, `${name} quotes PRTS`);
|
||||
assert.match(INDEX, /to \*\*that copy's board position\*\*/);
|
||||
assert.match(META, /of several, the one that deploys first \(row desc, then col asc; `board\.js mergeTile`/);
|
||||
// the deployment order is by column since 0.1.3 (Battle.start; DESIGN §23.f4), so is the merge's tile among several copies
|
||||
assert.match(META, /of several, the one that deploys first \(col asc, then row desc; `board\.js mergeTile`/);
|
||||
assert.ok(!/it takes a freed\s+board tile of a consumed copy only when the hand and temp are both full/.test(META), 'META: the old fallback-only wording is gone');
|
||||
assert.ok(!/only with the hand and temp both full does it take a/.test(PS), 'PlayerState header: the old fallback-only wording is gone');
|
||||
assert.match(PLAYING, /精锐会直接出现在\*\*那名干员的位置\*\*/);
|
||||
|
||||
@@ -88,7 +88,7 @@ test('merge with a full hand and a full temp: the elite takes its deployed copy\
|
||||
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { ok: true }, 'a purchase that completes a merge is allowed with a full hand');
|
||||
const elite = [...ps.board.values()].find((p) => p.id === m.gd.goldenIdOf(id));
|
||||
assert.ok(elite, 'the elite stands on a consumed copy\'s tile');
|
||||
assert.equal(ps.find(elite.uid).key, `${t1[0]},${t1[1]}`, 'the copy that deploys first (legalTileFor walks the deploy order)');
|
||||
assert.equal(ps.find(elite.uid).key, `${t1[0]},${t1[1]}`, 'the copy that deploys first (legalTileFor walks the top row left to right: on one row, the deploy order)');
|
||||
assert.equal(elite.poolCopies, 3, 'it holds all three copies');
|
||||
assert.deepEqual(elite.items.map((x) => x.id).sort(), ['chess_item_1_01_e_a', 'chess_item_2_04_e_a'], 'returned items are kept on it');
|
||||
checkInvariants(m);
|
||||
|
||||
@@ -116,7 +116,8 @@ test('merge from two board copies with a full hand: the elite takes the copy tha
|
||||
stock(m, ps, id);
|
||||
assert.equal(ps.deployCount, 2);
|
||||
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { ok: true });
|
||||
// deploy order = row desc, then col asc: (9,3) before (9,4) — [ASSUMED] the copy the battle deploys first
|
||||
// deploy order = by column from the left (col asc, then row desc; Battle.start): (9,3) before (9,4) — [ASSUMED] the copy
|
||||
// the battle deploys first
|
||||
const elite = ps.board.get('9,3');
|
||||
assert.ok(elite && elite.id === DATA.chess[id].goldenId, 'the elite stands on (9,3)');
|
||||
assert.equal(elite.dir, 'DOWN', 'with the facing of the copy that stood there');
|
||||
@@ -358,10 +359,10 @@ test('a merge completed during SETTLE (突变细胞) keeps its reward offer for
|
||||
if (holderTile) assert.ok(!ps.board.has(holderTile), `${label}: the carrier's tile is empty`);
|
||||
if (deployedTiles.length) {
|
||||
// PRTS 卫戍协议/帮助: a merge consuming a deployed copy sends the elite to that copy's tile — outside PREP too; of
|
||||
// several, the first in deploy order (row desc, col asc)
|
||||
// several, the first in deploy order (col asc, then row desc — by column from the left, Battle.start)
|
||||
const map = ps.deployMap();
|
||||
const pos = positionClass(m.gd.chess(elite.id));
|
||||
const legal = deployedTiles.map((k) => k.split(',').map(Number)).filter(([r, c]) => canPlace(map, pos, r, c)).sort((a, b) => b[0] - a[0] || a[1] - b[1]);
|
||||
const legal = deployedTiles.map((k) => k.split(',').map(Number)).filter(([r, c]) => canPlace(map, pos, r, c)).sort((a, b) => a[1] - b[1] || b[0] - a[0]);
|
||||
assert.ok(legal.length, `${label}: a legal deployed tile exists`);
|
||||
assert.equal(loc.area, 'board', `${label}: the elite stands on the board`);
|
||||
assert.equal(loc.key, `${legal[0][0]},${legal[0][1]}`, `${label}: on the first deployed tile in deploy order`);
|
||||
|
||||
@@ -86,11 +86,12 @@ test('buy: one deployed copy + one hand copy → the elite takes the deployed co
|
||||
m.dispose();
|
||||
});
|
||||
|
||||
test('buy: two deployed copies → the copy that deploys first (row desc, then col asc) gives its tile and facing, in either placement order', () => {
|
||||
test('buy: two deployed copies → the copy that deploys first (col asc, then row desc: by column from the left) gives its tile and facing, in either placement order', () => {
|
||||
for (const order of ['first-placed-first', 'first-placed-last']) {
|
||||
const { m, ps } = prep({ seed: 42 });
|
||||
const [id] = meleeTier1(m);
|
||||
// two legal tiles, the first of them in deploy order
|
||||
// two legal tiles, the first of them in deploy order (legalTileFor walks the top row left to right: on one row, the
|
||||
// deploy order)
|
||||
const t1 = legalTileFor(m, ps, id);
|
||||
const t2 = legalTileFor(m, ps, id, new Set([tileKey(...t1)]));
|
||||
const [early, late] = order === 'first-placed-first' ? [t1, t2] : [t2, t1];
|
||||
@@ -101,7 +102,7 @@ test('buy: two deployed copies → the copy that deploys first (row desc, then c
|
||||
stock(m, ps, id);
|
||||
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { ok: true });
|
||||
const want = mergeTile([t1, t2].map((t) => ({ key: tileKey(...t) })));
|
||||
assert.equal(want.key, tileKey(...t1), 'legalTileFor walks the deploy order');
|
||||
assert.equal(want.key, tileKey(...t1), 'the first in deploy order');
|
||||
const elite = ps.board.get(want.key);
|
||||
assert.ok(elite && m.gd.isGolden(elite.id), `${order}: the elite on ${want.key}`);
|
||||
assert.equal(elite.dir, want.key === tileKey(...early) ? 'UP' : 'LEFT', `${order}: the facing of the copy that stood there`);
|
||||
|
||||
+10
-8
@@ -13,25 +13,27 @@ const approx = (a, b, eps = 1e-6) => assert.ok(Math.abs(a - b) <= eps, `${a} ≈
|
||||
const guard = (o = {}) => chessRec({ id: 't_guard', profession: 'WARRIOR', stats: { atk: 300, blockCnt: 2 }, skill: null, ...o });
|
||||
const walker = (o = {}) => enemyRec({ key: 'enemy_walker', hp: 1e6, speed: 1, ...o });
|
||||
|
||||
test('initial deployment order: top→bottom then left→right; right boss side right→left', () => {
|
||||
test('initial deployment order: by column from the left, top to bottom within a column (PRTS 从上到下>从左到右 as a scan); the right boss side from its own left', () => {
|
||||
// PRTS 卫戍协议/帮助 §作战阶段 "按从上到下>从左到右的顺序部署" (since 2026-03-14; act 1: "从左到右>从下到上") — down each column,
|
||||
// the columns from the left: the order PRTS gives the 阿戈尔 devour ("更靠左和靠上"). Until 0.1.3: the top row first
|
||||
const h = makeBattle({
|
||||
defs: { chess: { a: guard({ id: 'a' }), b: guard({ id: 'b' }), c: guard({ id: 'c' }), d: guard({ id: 'd' }) } },
|
||||
units: [{ chessId: 'a', row: 9, col: 5 }, { chessId: 'b', row: 12, col: 7 }, { chessId: 'c', row: 12, col: 3 }, { chessId: 'd', row: 10, col: 4 }],
|
||||
defs: { chess: { a: guard({ id: 'a' }), b: guard({ id: 'b' }), c: guard({ id: 'c' }), d: guard({ id: 'd' }), e: guard({ id: 'e' }) } },
|
||||
units: [{ chessId: 'a', row: 9, col: 5 }, { chessId: 'b', row: 12, col: 7 }, { chessId: 'c', row: 12, col: 3 }, { chessId: 'd', row: 10, col: 4 }, { chessId: 'e', row: 12, col: 5 }],
|
||||
content: 'none',
|
||||
});
|
||||
h.step();
|
||||
assert.deepEqual(h.hooksOf('deploy').filter((c) => c.initial).map((c) => c.unit.defId), ['c', 'b', 'd', 'a']);
|
||||
assert.deepEqual(h.hooksOf('deploy').filter((c) => c.initial).map((c) => c.unit.defId), ['c', 'd', 'e', 'a', 'b']);
|
||||
assert.equal(h.hooksOf('battleStart').length, 1);
|
||||
const hb = makeBattle({
|
||||
kind: 'boss',
|
||||
defs: { chess: { a: guard({ id: 'a' }), b: guard({ id: 'b' }) } },
|
||||
players: [{ playerId: 'R1', side: 'R', colOffset: 8, units: [{ uid: 1, chessId: 'a', row: 10, col: 3 }, { uid: 2, chessId: 'b', row: 10, col: 6 }] }],
|
||||
defs: { chess: { a: guard({ id: 'a' }), b: guard({ id: 'b' }), c: guard({ id: 'c' }) } },
|
||||
players: [{ playerId: 'R1', side: 'R', colOffset: 8, units: [{ uid: 1, chessId: 'a', row: 10, col: 3 }, { uid: 2, chessId: 'b', row: 10, col: 6 }, { uid: 3, chessId: 'c', row: 12, col: 6 }] }],
|
||||
content: 'none',
|
||||
});
|
||||
hb.step();
|
||||
const order = hb.hooksOf('deploy').filter((c) => c.initial).map((c) => [c.unit.defId, c.unit.tileR, c.unit.tileC, c.unit.facing]);
|
||||
// board row 10 → boss row 3; mirrored cols: 3 → 17, 6 → 14; right→left in field coords
|
||||
assert.deepEqual(order, [['a', 3, 17, -1], ['b', 3, 14, -1]]);
|
||||
// board rows 10 / 12 → boss rows 3 / 5; mirrored cols: 3 → 17, 6 → 14; its own left first = the highest field column
|
||||
assert.deepEqual(order, [['a', 3, 17, -1], ['c', 5, 14, -1], ['b', 3, 14, -1]]);
|
||||
});
|
||||
|
||||
test('DP: starts at 10, +1/s, cap 99; dead operator redeploys after respawnTime when DP ≥ cost', () => {
|
||||
|
||||
@@ -543,7 +543,7 @@ test('ranged enemies attack allies in radius: blocker → taunt → latest deplo
|
||||
chess: { t_a: guard({ id: 't_a', stats: { maxHp: 1e6, atk: 0 } }), t_b: guard({ id: 't_b', stats: { maxHp: 1e6, atk: 0, tauntLevel: taunt } }) },
|
||||
enemies: { enemy_caster: enemyRec({ key: 'enemy_caster', hp: 1e6, speed: 1, atk: 100, range: 2.5, dmgType: 'arts' }) },
|
||||
},
|
||||
// deploy order: top→bottom, so t_b (row 11) deploys before t_a (row 10) ⇒ t_a is the latest deployed
|
||||
// deploy order: one column, top to bottom, so t_b (row 11) deploys before t_a (row 10) ⇒ t_a is the latest deployed
|
||||
units: [{ chessId: 't_a', row: 10, col: 8 }, { chessId: 't_b', row: 11, col: 8 }],
|
||||
enemies: [{ key: 'enemy_caster', pos: [10, 10] }], content: 'none', autoFinish: false,
|
||||
});
|
||||
|
||||
@@ -256,16 +256,16 @@ function lastTarget(h, enemy) {
|
||||
return ev ? h.b.unitById(ev[2]) : null;
|
||||
}
|
||||
|
||||
test('#5 unblocked ranged enemies: highest taunt, then the latest deployed — the bottom row, rightmost (从上到下 > 从左到右)', () => {
|
||||
const units = [[12, 5], [11, 3], [9, 4], [9, 7], [10, 8]].map(([row, col], i) => ({ chessId: 't_wall', row, col, uid: i + 1 }));
|
||||
test('#5 unblocked ranged enemies: highest taunt, then the latest deployed — the rightmost column, its bottom (从上到下 > 从左到右: down each column, the columns left to right)', () => {
|
||||
const units = [[12, 5], [11, 3], [9, 4], [9, 7], [10, 8], [12, 8]].map(([row, col], i) => ({ chessId: 't_wall', row, col, uid: i + 1 }));
|
||||
const h = arena({ units, enemies: [{ key: 'enemy_t_shooter', pos: [10.5, 6] }] });
|
||||
h.step();
|
||||
const order = h.hooksOf('deploy').filter((c) => c.initial).map((c) => [c.unit.tileR, c.unit.tileC]);
|
||||
assert.deepEqual(order, [[12, 5], [11, 3], [10, 8], [9, 4], [9, 7]]);
|
||||
assert.deepEqual(order, [[11, 3], [9, 4], [12, 5], [9, 7], [12, 8], [10, 8]]);
|
||||
h.run(1.2);
|
||||
const e = h.enemy('enemy_t_shooter');
|
||||
const t = lastTarget(h, e);
|
||||
assert.deepEqual([t.tileR, t.tileC], [9, 7], 'the last deployed');
|
||||
assert.deepEqual([t.tileR, t.tileC], [10, 8], 'the last deployed');
|
||||
// a taunt level beats deployment order
|
||||
const h2 = arena({ units, enemies: [{ key: 'enemy_t_shooter', pos: [10.5, 6] }] });
|
||||
h2.step();
|
||||
@@ -279,7 +279,7 @@ test('#5 the initial deployment puts every operator before the summons (PRTS 卫
|
||||
const h = arena({ units: [{ chessId: 't_wall', row: 12, col: 3, uid: 1 }, { chessId: 't_wall', row: 9, col: 8, uid: 2 }, tok], enemies: [{ key: 'enemy_t_shooter', pos: [10.5, 6] }] });
|
||||
h.step();
|
||||
const order = h.hooksOf('deploy').filter((c) => c.initial).map((c) => c.unit.uid);
|
||||
assert.deepEqual(order, [1, 2, 3], 'operators top→bottom, then the summon');
|
||||
assert.deepEqual(order, [1, 2, 3], 'the operators (by column from the left), then the summon');
|
||||
const t = h.unit(3);
|
||||
assert.ok(t.aggroSeq > h.unit(1).aggroSeq && t.aggroSeq > h.unit(2).aggroSeq);
|
||||
h.run(1.2);
|
||||
|
||||
@@ -56,11 +56,13 @@ describe('gameLogic.mergeTarget (mirror of server board.js mergeTile)', () => {
|
||||
assert.deepEqual(mergeTarget({ board: [onBoard(A, 10, 4, 'UP')], hand: [inHand(A, 2)] }, A, getChess), { row: 10, col: 4, dir: 'UP' });
|
||||
assert.deepEqual(mergeTarget({ board: [onBoard(A, 10, 4)], temp: [inHand(A, 2)] }, A, getChess), { row: 10, col: 4, dir: 'RIGHT' }, 'temp copies count');
|
||||
});
|
||||
test('two deployed copies → the one that deploys first: row desc, then col asc', () => {
|
||||
test('two deployed copies → the one that deploys first: col asc, then row desc (by column from the left, Battle.start)', () => {
|
||||
const priv = { board: [onBoard(A, 9, 2, 'DOWN'), onBoard(A, 11, 7, 'LEFT')], hand: [] };
|
||||
assert.deepEqual(mergeTarget(priv, A, getChess), { row: 11, col: 7, dir: 'LEFT' });
|
||||
assert.deepEqual(mergeTarget(priv, A, getChess), { row: 9, col: 2, dir: 'DOWN' }, 'the left column, though lower (row-major until 0.1.3)');
|
||||
const same = { board: [onBoard(A, 10, 8, 'UP'), onBoard(A, 10, 3, 'DOWN')], hand: [] };
|
||||
assert.deepEqual(mergeTarget(same, A, getChess), { row: 10, col: 3, dir: 'DOWN' });
|
||||
const column = { board: [onBoard(A, 9, 4, 'UP'), onBoard(A, 12, 4, 'LEFT')], hand: [] };
|
||||
assert.deepEqual(mergeTarget(column, A, getChess), { row: 12, col: 4, dir: 'LEFT' }, 'one column: the top first');
|
||||
});
|
||||
test('elites and other operators are ignored; 风丸 merges with one deployed copy', () => {
|
||||
const priv = { board: [{ ...onBoard(golden(A), 12, 2), golden: true }, onBoard('chess_char_1_02_a', 12, 3), onBoard(A, 9, 9)], hand: [inHand(A, 5)] };
|
||||
|
||||
Reference in new issue
Block a user