Playtest #6 fixes: summons placed by hand, boss pool stall, bounties, 联防 leak counter, distinct offers, 机变 card text, phone buy tap, skill triggers, blocking, push force, 孤立, enemies, element bar
- #1 #2 skill / device summons (赫默's drone, 巫恋's doll, 凯瑟琳's devices) are hand cards placed by the player; placed summons deploy once at battle start, then a skill summon re-appears at its tile on each cast (the user's call) - #3 叙拉古 隐匿 ends on time (32 + 0.4 × layers s); 忍冬 S3 迷彩 lasts until her next cast (official), kept apart from 隐匿 - #4 multi-round bounty cards last two battles (the user's call; MULTI_ROUND_BOUNTY_BATTLES), blue / red card text - #5 the shared boss pool's float dust (≈3.6e-12 HP) stalled Final Assault leaders at 0 %; < 1 HP now clears the pool, and the team LP reaching 0 first is a defeat (PRTS) - #6 #10 机变 cards show their official effect text; an invisible 详情 button no longer swallows the confirming tap on phones - #7 live ×N counter of a leaker's enemies still on the 联防 field (LP loss stays capped at 10) - #8 #15 #16 skill triggers per 技能策略: skill-range triggers, injured-ally heals (古米, 塞雷娅), tank take-damage triggers with a 3 s cooldown; 新约能天使 S2 shields the highest-aggro operator in range - #9 official per-enemy model scales (威龙) - #11 element bar under the unit's HP bar (white = 元素值 left, element-coloured refill after a burst), floor rounding - #12 枯朽战车 kit per PRTS; every ranged enemy reaches its target's 0.25 collider - #13 恐惧 wander (萨科塔之翼 at half HP, operator fears make enemies flee) with re-planning after pushes - #14 push / pull distance by 力度 − 重量 (PRTS table); #17 every blocker attacks what it blocks; ASPD floor 20 - #18 炎佑 is 孤立: no heals or ally buffs - #19 every free pick-one chess offer (promotion reward, 信标, 寻呼模块) shows different operators - DESIGN §20 and the normative lines, SIM / META / DATA / PLAYING updated; 2747 unit tests and all 27 browser suites green; dev recordings regenerated
This commit is contained in:
1 parent
e6628f0cfe
commit
6464e4b46a
138 files changed
+7946
-1379
No files matched your search
@@ -168,11 +168,12 @@ test('#4: a blocker attacks the enemy it blocks even when it stands outside its
|
||||
assert.ok(h.hooksOf('damaged').some((c) => c.source === u && c.target === x()), 'it hits its blocked enemy');
|
||||
});
|
||||
|
||||
test('#4: a ranged operator on a melee tile blocks, but never attacks or casts for the enemy held behind it (阻挡(近战限定))', () => {
|
||||
// PRTS 索敌的概念: "我方索敌优先级:阻挡(近战限定)"; "远程位干员通常而言不会优先攻击自己阻挡的目标;甚至如果这个目标被阻挡在
|
||||
// 该干员的身后,该干员可能因为敌人未处于自身攻击范围而无法攻击到这个敌人". Facing LEFT, its range (9,6),(9,5) never holds
|
||||
// the enemy blocked at x ≈ 6.71; a melee operator in the same spot hits it and casts its DEFAULT skill (帮助: "敌人被近战
|
||||
// 干员自身阻挡" satisfies the target condition).
|
||||
test('#4 (revised by the user after playtest #6): a ranged operator on a melee tile also hits and casts for the enemy it holds behind it', () => {
|
||||
// The playtest #5 QA had made blocked-first melee-only (PRTS 索敌的概念 "阻挡(近战限定)"); the user's rule after
|
||||
// playtest #6 — "阻挡了就一定要能打到": officially the collision pushes a blocked enemy to its blocker's front, so
|
||||
// whatever blocks an enemy can hit it. Facing LEFT, the range (9,6),(9,5) never holds the enemy blocked at x ≈ 6.71;
|
||||
// the ranged blocker hits it and casts its DEFAULT skill like a melee one (帮助: the blocked enemy satisfies the
|
||||
// target condition).
|
||||
const run = (id, profession) => {
|
||||
const h = makeBattle({
|
||||
defs: { chess: { [id]: chessRec({ id, profession, stats: { atk: 100, blockCnt: 1, maxHp: 1e6 }, skill: { spCost: 1, initSp: 1, duration: 5 } }) }, enemies: { enemy_x: foe('enemy_x') } },
|
||||
@@ -187,13 +188,12 @@ test('#4: a ranged operator on a melee tile blocks, but never attacks or casts f
|
||||
checkInvariants(h.b);
|
||||
return { hits: h.hooksOf('damaged').filter((c) => c.source === u && c.target === x()).length, casts: u.skill.activations, still: x().blockedBy === u };
|
||||
};
|
||||
const ranged = run('t_rg', 'SNIPER');
|
||||
assert.equal(ranged.hits, 0, 'the ranged blocker never hits the enemy behind it');
|
||||
assert.equal(ranged.casts, 0, 'nor casts its skill for it');
|
||||
assert.ok(ranged.still, 'it still holds the enemy');
|
||||
const melee = run('t_ml', 'WARRIOR');
|
||||
assert.ok(melee.hits > 0, 'a melee blocker hits it');
|
||||
assert.equal(melee.casts, 1, 'and casts its skill');
|
||||
for (const [id, profession] of [['t_rg', 'SNIPER'], ['t_ml', 'WARRIOR']]) {
|
||||
const r = run(id, profession);
|
||||
assert.ok(r.hits > 0, `${profession}: hits the enemy it blocks behind it`);
|
||||
assert.equal(r.casts, 1, `${profession}: and casts its skill`);
|
||||
assert.ok(r.still, `${profession}: it still holds the enemy`);
|
||||
}
|
||||
});
|
||||
|
||||
test('#4: "melee" is the deploy position — a 领主 with a ranged attack (银灰) still hits the enemy it holds behind it', () => {
|
||||
@@ -213,10 +213,10 @@ test('#4: "melee" is the deploy position — a 领主 with a ranged attack (银
|
||||
assert.ok(h.hooksOf('damaged').some((c) => c.source === u && c.target === x()), 'it hits the enemy it blocks');
|
||||
});
|
||||
|
||||
test('#4: blocked-first is a melee priority — a ranged blocker shoots the enemy nearest the goal', () => {
|
||||
test('#4 (revised after playtest #6): blocked-first holds for every blocker — a ranged blocker keeps shooting the enemy it blocks', () => {
|
||||
// range (9,5)–(9,7) facing RIGHT: the walker it blocks stands on (9,7); a flyer passes over the operator towards the
|
||||
// goal. The ranged operator picks by its own order (least remaining distance first), a melee one (able to hit air)
|
||||
// keeps hitting the enemy it blocks.
|
||||
// goal. Melee or ranged, the blocker keeps hitting the enemy it blocks (user's rule: the blocked enemy stands in front
|
||||
// of its blocker officially) — the ranged one no longer switches to the flyer nearer the goal.
|
||||
const run = (id, o) => {
|
||||
const h = makeBattle({
|
||||
defs: {
|
||||
@@ -232,10 +232,13 @@ test('#4: blocked-first is a melee priority — a ranged blocker shoots the enem
|
||||
assert.ok(h.runUntil(() => walker()?.blockedBy === u, 30), 'the walker is blocked');
|
||||
assert.ok(h.runUntil(() => flyer() && flyer().x < 4.4, 30), 'the flyer passed');
|
||||
assert.equal(walker().blockedBy, u);
|
||||
return h.hooksOf('damaged').filter((c) => c.source === u && c.target === flyer()).length;
|
||||
return { fl: h.hooksOf('damaged').filter((c) => c.source === u && c.target === flyer()).length, wk: h.hooksOf('damaged').filter((c) => c.source === u && c.target === walker()).length };
|
||||
};
|
||||
assert.ok(run('t_rg2', { profession: 'SNIPER' }) > 0, 'the ranged blocker shot the flyer while it was nearer the goal');
|
||||
assert.equal(run('t_ml2', { profession: 'WARRIOR', attackKind: 'melee', canHitFly: true }), 0, 'the melee blocker kept to its blocked enemy');
|
||||
for (const [id, o] of [['t_rg2', { profession: 'SNIPER' }], ['t_ml2', { profession: 'WARRIOR', attackKind: 'melee', canHitFly: true }]]) {
|
||||
const r = run(id, o);
|
||||
assert.equal(r.fl, 0, `${o.profession}: kept to its blocked enemy`);
|
||||
assert.ok(r.wk > 0, `${o.profession}: hit the walker it blocks`);
|
||||
}
|
||||
});
|
||||
|
||||
test('#4: a skywalker (起飞: blockFly) blocks flyers at the air block radius 0.8944', () => {
|
||||
@@ -257,7 +260,8 @@ test('#4: 瑕光 S2 puts the enemy she blocks to sleep although it stands outsid
|
||||
const id = 'chess_char_3_12_a';
|
||||
const skillIndex = ds.rawChess(id).skills.find((s) => s.skillId === 'skchr_blemsh_2').index;
|
||||
const h = makeBattle({
|
||||
defs: { enemies: { enemy_x: foe('enemy_x') } }, timeLimit: 60,
|
||||
// (a MANUAL 重装 skill: cast by the blocked enemy's hit — TAKE_DAMAGE)
|
||||
defs: { enemies: { enemy_x: foe('enemy_x', { atk: 100, bat: 1 }) } }, timeLimit: 60,
|
||||
units: [{ chessId: id, row: 9, col: 6, skillIndex }], enemies: [{ key: 'enemy_x', time: 0 }],
|
||||
});
|
||||
const u = h.unit(id);
|
||||
|
||||
Reference in new issue
Block a user