feedback4: every melee chess whose trait reads 可以放置于远程位 may stand on a 高台, normal or elite, any module

The owner's decision of 2026-10-05 reverses the one of 2026-10-04 (elite 歌蕾蒂娅 with HOK-Y only) and follows PRTS:
the line is the 钩索师 / 推击手 branch trait (歌蕾蒂娅, 崖心, 见行者, normal and elite) and every module keeps it, so
the loadout no longer matters. Raised with the PRTS source in PR #69 by @sunstricken (its own change covered only
歌蕾蒂娅; its test page is not taken). GitHub #153.

- shared/highGround.js: meleeOnHighGround(rec) reads the trait without a module (traitBase, else trait); no module id.
- board.js positionClass(rec) widens those six records; placeClass(ps, rec) = positionClass(rec) for every server
  check (g.move, swaps, merge tile, evictions, audit, invariants). The client's piecePosition mirrors it.
- bot.js: the existing 高台 preference (a 高台 whose range covers the road) now applies to all six; comments.
- Docs: DESIGN §3 and §6.6, DATA (placement), PLAYING §4, META, research 03 C2, code comments. data/ unchanged
  (build-data --offline gives identical files).
- Tests: board, feedback1-placement (each holder with default / no module / every module on each 高台, a 重装
  refused, client highlights, swap, battle, bots), gameLogic.

Golden: 16 scenarios moved, every one because tools/golden.mjs lays operators out by positionClass and now places
歌蕾蒂娅 (normal, or elite with HOK-X) beside the enemy path like a ranged operator instead of on the busiest melee
tile, which shifts the others: roster-010, roster-012, roster-029, roster-040, roster-041; bond-deputShip-high,
bond-indomShip-low, bond-preciShip-low, bond-preciShip-high, bond-suntShip-low, bond-swiftShip-high; boss-boss_2-pair,
boss-boss_4-pair, boss-boss_6-pair, hidden-boss_8-pair, hidden-boss_10-pair. roster-031 (elite with HOK-Y, already
widened) and all matches are unchanged.
This commit is contained in:
sganggs committed 2026-10-05 23:20:56 +08:00
1 parent 02b76989fa
commit 83fc408a55
18 files changed
+734 -756

No files matched your search

+13 -14
View File
@@ -55,27 +55,26 @@ test('glyph rules: melee only on LOW buildable ALL/MELEE; ranged also on HIGH/RA
assert.ok(canPlace(buildDeployMap(null), 'melee', 10, 5));
});
test('positionClass: melee stays ground-only; only elite 歌蕾蒂娅 + HOK-Y is widened (owner 2026-10-04)', () => {
test('positionClass: a MELEE chess whose trait reads 「可以放置于远程位」 is widened, normal or elite, whatever the module (owner 2026-10-05)', () => {
assert.equal(positionClass({ position: 'MELEE' }), 'melee');
assert.equal(positionClass({ position: 'MELEE', placement: 'all' }), 'melee', 'the old placement field is ignored');
assert.equal(basePositionClass({ position: 'MELEE', isGolden: true, charId: 'char_474_glady' }), 'melee');
assert.equal(positionClass({ position: 'MELEE', trait: { desc: '技能可以使敌人产生位移\n可以放置于远程位' } }), 'all', 'the trait line');
assert.equal(positionClass({ position: 'MELEE', trait: { desc: '可以额外部署在远程位' } }), 'melee', 'another wording is not the line');
assert.equal(positionClass({ position: 'RANGED', trait: { desc: '可以放置于远程位' } }), 'ranged', 'a ranged record keeps its class');
assert.equal(basePositionClass({ position: 'MELEE', trait: { desc: '可以放置于远程位' } }), 'melee', 'the battle class stays melee');
assert.equal(positionClass({ position: 'RANGED' }), 'ranged');
assert.equal(positionClass({ position: 'ALL' }), 'all');
assert.equal(positionClass(null), 'all');
const HOK_Y = 'uniequip_003_glady';
const gladE = DATA.chess.chess_char_4_12_b;
const map = buildDeployMap(DATA.stages.act2autochess_m01);
assert.ok(!canPlace(map, positionClass(DATA.chess.chess_char_4_12_a, HOK_Y), 10, 4), 'normal 歌蕾蒂娅, even with the id, not');
assert.ok(!canPlace(map, positionClass(gladE), 10, 4), 'elite with no module id (default is not passed) not');
assert.ok(!canPlace(map, positionClass(gladE, 'uniequip_002_glady'), 10, 4), 'HOK-X not');
assert.ok(!canPlace(map, positionClass(gladE, 'none'), 10, 4), 'no module not');
assert.ok(canPlace(map, positionClass(gladE, HOK_Y), 10, 4), 'elite + HOK-Y on the 高台');
assert.ok(canPlace(map, positionClass(gladE, HOK_Y), 9, 3), 'and still on the ground');
assert.ok(!canPlace(map, positionClass(DATA.chess.chess_char_2_03_b, HOK_Y), 10, 4), '崖心 not');
assert.ok(!canPlace(map, positionClass(DATA.chess.chess_char_3_07_b, HOK_Y), 10, 4), '见行者 not');
// the six trait holders: 歌蕾蒂娅 / 崖心 (钩索师) and 见行者 (推击手), normal and elite
for (const id of ['chess_char_4_12_a', 'chess_char_4_12_b', 'chess_char_2_03_a', 'chess_char_2_03_b', 'chess_char_3_07_a', 'chess_char_3_07_b']) {
assert.equal(positionClass(DATA.chess[id]), 'all', id);
assert.ok(canPlace(map, positionClass(DATA.chess[id]), 10, 4), `${id} on the 高台`);
assert.ok(canPlace(map, positionClass(DATA.chess[id]), 9, 3), `${id} still on the ground`);
assert.equal(basePositionClass(DATA.chess[id]), 'melee', `${id} blocks like a melee unit`);
assert.equal(DATA.chess[id].placement, undefined);
}
assert.ok(!canPlace(map, positionClass(DATA.chess.chess_char_1_02_a), 10, 4), '角峰 (重装) not');
assert.equal(DATA.chess.chess_char_4_12_a.placement, undefined);
assert.equal(gladE.placement, undefined);
});
test('g.move: legality per chess position, BAD_TILE outside, deploy cap 8, swaps allowed at cap', () => {