feedback5: merge feedback4 (7ee815e) — W4D ported: a sleeping enemy cannot be blocked and takes no block slot (_checkBlock in server/sim/battle/blocking.js, the applyStatus release in battle/status.js, the plain-buff release re-applied to ai.js updateEnemy's cannot-act branch after WE1's swing cut, 瑕光's comment in kits/ops/chess_char_3_12-blemsh.js, buffs.js; 酒神's 迷狂牢笼 skips a sleeper too); golden: roster-003 / 005 / 013 / 032 / 038 / 039 / 042, bond-maniShip-high, solo-FUNNY-1, coop4-FUNNY-6, coop4-ABYSS-7, coop2-NORMAL-8-serverrun, coop2-NORMAL-70-diy, diy-131 / 132 moved
New on feedback4 since the last merge: W4D (bbe59b9, merged as7ee815e) and the 0.1.4 version commit (30e7ace). feedback5 keeps its own package.json / package-lock.json / APP_VERSION / README / DEPLOY version lines (0.2.0 sets its own version later) and takes feedback4's CHANGELOG 0.1.4 entry, with W4D's line in it. - Port (PRTS 异常效果 SLEEPING 沉睡 = 无法行动+无敌+不可阻挡; GitHub #140; the owner's decision of 2026-10-06): `_checkBlock` refuses `flags.sleep` (battle/blocking.js) and `applyStatus` releases an enemy that falls asleep (battle/status.js). Battle.js stays feedback5's façade, and tier3.js stays deleted (the 瑕光 comment is in her kits/ops file). ai.js: WE1 (89f730d) cuts a swing before enemyAttack on a stun / freeze / sleep. The cannot-act branch is still there, and a plain-buff `flags.sleep` still makes the enemy `stun` (units.js), so the plain-buff release stays in that branch, after the cut, with its comment rewritten for the new order. - New on feedback5: 酒神's 迷狂牢笼 (kits/ops/op-phatm2.js cageBlock, O22) duplicated the old three-flag 不可阻挡 check. It now skips a sleeper too. No golden scenario spawns a 牢笼 over a sleeper: the corpus is identical with that line reverted. 予愿安洁莉娜's S3 helper is unchanged, since canTargetEnemy already drops sleepers. - Readers that follow without code: blockedTargets (blocked-first targeting, the DEFAULT trigger, hitAllBlocked), the 要塞 branch of acquireTargets (u.blocking), “庞贝”'s clock (blockedBy), the snapshot's UF.BLOCKED and the client's block marker (render/units.js). 死芒 S2 折朽 (自选) lets go a linked enemy that her 悲叹的仆役 held. - Docs: DESIGN §24.9, its §24 intro entry, the §5.5 Blocking sentence (on feedback5's 阻挡半径倍率 text) and §22.8's "(since §24.9)", all as on feedback4; SIM.md as on feedback4 (the plain-buff sentence merged with feedback5's noNewBlock / undying text). - Golden: a probe (counting successful sleeps on a blocked enemy, plain-buff sleeps and 牢笼 spawns over sleepers) ran over all 283 scenarios. Every scenario that moved sleeps blocked enemies; the counts are in brackets: roster-003 (小满 S2 [5]: same fight, blocked 27 → 29, snapshot hashes); roster-005 (elite 小满 S2 [3]: the 140 s timeout with 83 kills and 1 leak, where it used to clear at 135.43 s with 84); roster-013 (缇缇 S3 [11]: same kills, blocked 22 → 33); roster-032 (缇缇 S1 [10]: 71 kills instead of 68, 8 leaks instead of 11, 4 knock-outs instead of 8, 萨尔贡 layers 41 → 35); roster-038 (elite 缇缇 S2 [1]: ends at 197.40 s instead of 197.27 s); roster-039 (瑕光 S2 normal + elite [31 + 12]: same kills and counted leaks, the 窃贼 gets away, blocked 53 → 87); roster-042 (缇缇 S2 [3]: ends at 168.77 s instead of 166.93 s); bond-maniShip-high (小满 [1]: blocked 29 → 30, snapshot hashes); solo-FUNNY-1 (ai_0's 缇缇 [3]: only ai_0's damage dealt); coop4-FUNNY-6 (ai_0's 缇缇 [15 + 56]: from round 9 ai_0's 精准 / 萨尔贡 layers and boss damage move, ai_1 loses no LP from round 11, team LP 124 → 130); coop4-ABYSS-7 (ai_2's 小满 [18]: ai_2 one kill fewer, ai_0 one more LP lost in round 9); coop2-NORMAL-8-serverrun (ai_1's 缇缇 [83]: only ai_1's damage dealt; WE1 had already turned this match into a loss); coop2-NORMAL-70-diy (the 自选 seat h_0's 缇缇 [171], ai_0's 缇缇 [74] and 小满 [19 + 21]: h_0's layers move from round 4, then its AI 托管 buys differently (boards from round 7); ai_0 246 kills instead of 233, same end); diy-131 (死芒 S2 at tiers 5 and 6 [1 + 1]: snapshot hashes only); diy-132 (elite 死芒 S2 [2]: ends at 199.50 s instead of 198.13 s, same kills). One scenario sleeps a blocked enemy and did not move: coop2-HARD-4 [1]. ai_1's 小满 lets go the enemy it holds; another enemy takes the slot; the sleeper wakes with no room and is blocked further on by 2_16_a. The match digest has no snapshot hashes, and its kills, leaks, damage and funds are identical. fields and standins did not move. Most outcomes differ from feedback4's list because feedback5's baselines differ (WE1's damage frame, etc.). - Tests: test/sim/feedback4-sleep-unblock.test.js (from feedback4) passes 5/5 on the split layout. Every case fails with the three sim hunks reverted, and case 3 (plain buff) fails with only the ai.js release reverted. Targeted sim tests pass 184/184 (blocking, block targets, enemy swing, feedback2-enemy-attack-stand, combat / statuses, robustness, stun SP, professions, healers, fidelity …). Content tests plus kits_layout and docs-consistency pass 658/658 (kits t1t2 / t3 / t5 and alt t2 / t3 / t5 for 小满 / 瑕光 / 缇缇, 酒神, 死芒, garrisons, bosses, items). Stealth / liftoff / enemies pass 36/36. npm run golden: all 283 match. GOLDEN_FULL=1 test/golden.test.js passes 9/9 (no stored engine error). lint 0 errors, check:imports and typecheck clean.
This commit is contained in:
commit
ac6454d171
14 files changed
+558
-261
No files matched your search
+12
-3
@@ -11,7 +11,9 @@
|
||||
// path always follows `motion` — a hovering (近地悬浮) enemy is an air unit for targeting and blocking (Unit.isFlying)
|
||||
// but walks the ground. An unblocked enemy touching an ally with free block capacity — within its block radius (0.7071
|
||||
// ground, 0.8944 air, devices 0.4472; Battle._checkBlock) — is blocked, moving or not, so an enemy overlapping an
|
||||
// operator is taken over once its blocker is gone. Blocked enemies fight their blocker (ranged ones may pick anyone in
|
||||
// operator is taken over once its blocker is gone; never one holding 不可阻挡 (恐惧, 诱导, 浮空, 沉睡): an enemy falling
|
||||
// asleep is let go by its blocker, whose slot frees, and stays where it is until it wakes (DESIGN §24.9). Blocked
|
||||
// enemies fight their blocker (ranged ones may pick anyone in
|
||||
// range, blocker first); every blocker whose attack hits enemies — a ranged operator on a melee tile included — may
|
||||
// always target the enemies it blocks, in range or not, whatever its facing, and targets them first (acquireTargets,
|
||||
// Battle.blockedTargets; user playtest #6: "阻挡了就一定要能打到"); a heal attack keeps selecting injured allies while
|
||||
@@ -533,8 +535,15 @@ export function updateEnemy(b, e, dt) {
|
||||
// true: an unblocked ranged enemy in the wind-up of its next attack with a target in range (it stands)
|
||||
const winding = !e.hidden && !stunned && enemyAttack(b, e, prevCd);
|
||||
if (!e.alive) return;
|
||||
// a stun / freeze / sleep cuts the attack clip short: no stand left once it ends [ASSUMED]
|
||||
if (stunned && !e.hidden) { e.atkStandUntil = -Infinity; return; }
|
||||
// a stun / freeze / sleep cuts the attack clip short: no stand left once it ends [ASSUMED]. 沉睡 also holds 不可阻挡
|
||||
// (PRTS 异常效果 SLEEPING = 无法行动+无敌+不可阻挡): a sleeper's blocker lets go — its swing was cut above; Battle.applyStatus
|
||||
// (battle/status.js) releases it at once, this catches a sleep added as a plain buff (`flags.sleep` makes it `stun` too,
|
||||
// units.js) — and it stays where it is until it wakes (DESIGN §24.9)
|
||||
if (stunned && !e.hidden) {
|
||||
e.atkStandUntil = -Infinity;
|
||||
if (e.blockedBy && e.s.flags.sleep) b._unblock(e);
|
||||
return;
|
||||
}
|
||||
if (e.blockedBy) {
|
||||
const bl = e.blockedBy;
|
||||
// (unblockable/levitate/fear may also arrive through a plain addBuff, which does not unblock by itself; a
|
||||
|
||||
@@ -17,11 +17,14 @@ export class BattleBlocking {
|
||||
* ties → the first in row-then-column scan order. The air radius grows with the blocker's 阻挡半径倍率 (PRTS 游戏数据基础
|
||||
* "飞行阻挡半径 = 0.8944 × 阻挡半径倍率"; mod `blockRadiusScale` = 倍率 − 1: 凯尔希·思衡托's 遗尘守望 / S1, +0.23); ground
|
||||
* blocking ignores it (PRTS 数值范围 "对地面阻挡的单位不生效"). Below 1.5 tiles the 3×3 scan still finds every blocker.
|
||||
* Never blocked: an enemy holding 不可阻挡 (PRTS 异常效果 BLOCK_FREE 「无法阻挡/被阻挡,自动解除阻挡」) — the flag itself
|
||||
* (恐惧 / 诱导 carry it), 浮空, and 沉睡 (SLEEPING = 无法行动+无敌+不可阻挡: a sleeper takes no block slot, DESIGN §24.9);
|
||||
* once it wakes it is blocked again only by a blocker with room.
|
||||
*/
|
||||
_checkBlock(e) {
|
||||
if (e.blockedBy || e.hidden || !e.alive) return !!e.blockedBy;
|
||||
const f = e.s.flags;
|
||||
if (f.unblockable || f.levitate || f.fear) return false;
|
||||
if (f.unblockable || f.levitate || f.fear || f.sleep) return false;
|
||||
const r0 = Math.round(e.y), c0 = Math.round(e.x);
|
||||
const w = e.blockWeight ?? 1;
|
||||
let u = null, bd = Infinity;
|
||||
|
||||
@@ -143,7 +143,8 @@ export class BattleStatus {
|
||||
* (7级~专精二)…的虚弱(仅影响叠加优先级,不影响实际效果)"; other statuses refresh to the longer duration. 诱导 (`attract`) walks the enemy to `point`
|
||||
* ([r, c] or {x, y}; default the source's tile — a new application moves the point); 恐惧 (`fear`) stamps where it
|
||||
* was applied and from where (fear.js stampFear: the fan of 恐惧可达地块 its movement uses). A stunned/sleeping operator
|
||||
* releases the enemies it blocks; a feared/levitated/unblockable/attracted enemy is released by its blocker.
|
||||
* releases the enemies it blocks; a feared/levitated/unblockable/attracted/sleeping enemy is released by its blocker
|
||||
* (沉睡 = 无法行动+无敌+不可阻挡, PRTS 异常效果: the slot frees for the next enemy, the sleeper stays put — DESIGN §24.9).
|
||||
* `statusApplied` reports the final duration and `entered` (the target did not carry the status before) — or, with
|
||||
* `reenter`, entered anyway: a pulse whose own short status the caller re-applies as a fresh one each time (缇缇 S2's
|
||||
* sleep ward, DESIGN §24.8); the buff itself is refreshed as usual.
|
||||
@@ -213,7 +214,7 @@ export class BattleStatus {
|
||||
if (key === 'fear' && b && target.side === 'enemy') stampFear(this, target, b, source);
|
||||
}
|
||||
const f = tpl.flags;
|
||||
if (f && target.side === 'enemy' && (f.levitate || f.unblockable || f.fear)) this._unblock(target);
|
||||
if (f && target.side === 'enemy' && (f.levitate || f.unblockable || f.fear || f.sleep)) this._unblock(target);
|
||||
if (f && target.side === 'ally' && f.noBlock) this.releaseBlocked(target);
|
||||
if (this._hooks.statusApplied) this.emit('statusApplied', { source, target, status: key, duration, value, entered });
|
||||
return true;
|
||||
|
||||
+3
-1
@@ -62,7 +62,9 @@ export const STATUS = Object.freeze({
|
||||
// 寒冷: ASPD −30. A second cold while this one remains applies 冻结 for max(remaining, incoming)
|
||||
// (Battle.applyStatus; PRTS 术语释义 寒冷 「持续时间取双方之中最高」).
|
||||
cold: { flags: { cold: true }, mods: { aspd: COLD_ASPD } },
|
||||
// 沉睡: 无敌且无法行动 (untargetable, takes no damage — except from `hitSleep` attackers — and blocks nothing)
|
||||
// 沉睡: 无敌且无法行动 (untargetable, takes no damage — except from `hitSleep` attackers — and blocks nothing); PRTS 异常效果
|
||||
// SLEEPING = 无法行动+无敌+不可阻挡: an enemy asleep is not blocked either — its blocker lets go and the slot frees, it
|
||||
// stays put until it wakes (the `sleep` flag: Battle._checkBlock / applyStatus, ai.js updateEnemy; DESIGN §24.9)
|
||||
sleep: { flags: { sleep: true, noBlock: true }, immune: 'sleep' },
|
||||
// value = fraction of speed removed (0.8 ⇒ moveMul 0.2). Default 0.5.
|
||||
slow: { mods: (v) => ({ moveMul: 1 - clamp01(v ?? 0.5) }), valued: 0.5 },
|
||||
|
||||
@@ -87,7 +87,9 @@ export default {
|
||||
const dur = skill.timeLeft > 0 ? skill.timeLeft : Math.max(0.1, num(s.duration, 10));
|
||||
let n = 0;
|
||||
// PRTS 备注: "技能生效对象实际为“自身这格内的所有地面敌人及自身阻挡的敌人”,即使阻挡的是飞行敌人" — a blocked
|
||||
// enemy stands at the block radius, outside her tile (Battle._checkBlock)
|
||||
// enemy stands at the block radius, outside her tile (Battle._checkBlock). Asleep they are no longer blocked
|
||||
// (沉睡 = 不可阻挡, DESIGN §24.9; GitHub #140): her slots free for the next enemies, and a sleeper that wakes
|
||||
// is held again only while she has room
|
||||
for (const e of battle.enemies) {
|
||||
if (!e.alive || e.hidden) continue;
|
||||
if (e.blockedBy !== unit && (e.isFlying || !bodyOnTile(e, unit.tileR, unit.tileC))) continue;
|
||||
|
||||
@@ -56,8 +56,9 @@
|
||||
// phatm2_s_3[token_cooldown].interval (30) s more; her leaving withdraws them.
|
||||
// 迷狂牢笼 (kit below): 孤立 + 禁疗, no attack, 迷彩 (only the enemies it blocks target it), every damage it takes from
|
||||
// one of them is 1, any other damage (无来源 too) none — 3 HP: three hits; at its spawn / refresh it blocks every unblocked
|
||||
// enemy on its tile, ground or air, whatever its capacity, back to full HP, block count = their weight; it takes no other
|
||||
// enemy by contact (flag `noNewBlock`); with nobody blocked it leaves at once.
|
||||
// enemy on its tile, ground or air, whatever its capacity — none holding 不可阻挡 (恐惧, 诱导, 浮空, 沉睡: DESIGN §24.9) —,
|
||||
// back to full HP, block count = their weight; it takes no other enemy by contact (flag `noNewBlock`); with nobody blocked
|
||||
// it leaves at once.
|
||||
// Statuses: 束缚 (S1), 停顿 + 诱导 (本能的召唤); 神经损伤 bursts (engine: 3 麻痹 + 6000 元素伤害).
|
||||
|
||||
import { num, up, traitBb, talentBb, skillRec, once } from '../shared/tier1.js';
|
||||
@@ -195,13 +196,17 @@ function cageKit() {
|
||||
};
|
||||
}
|
||||
|
||||
/** A 牢笼 blocks every unblocked enemy on its tile (spawn / refresh): full HP again, block count = their weight. */
|
||||
/**
|
||||
* A 牢笼 blocks every unblocked enemy on its tile (spawn / refresh): full HP again, block count = their weight. Never one
|
||||
* holding 不可阻挡 — as Battle._checkBlock: the flag (恐惧 / 诱导 carry it), 浮空, 沉睡 (SLEEPING = 无法行动+无敌+不可阻挡, DESIGN
|
||||
* §24.9: a sleeper on its tile stays free and is not counted).
|
||||
*/
|
||||
function cageBlock(battle, cage) {
|
||||
let added = 0;
|
||||
for (const e of battle.enemies) {
|
||||
if (!e.alive || e.hidden || e.blockedBy || Math.round(e.y) !== cage.tileR || Math.round(e.x) !== cage.tileC) continue;
|
||||
const f = e.s.flags;
|
||||
if (f.unblockable || f.levitate || f.fear) continue;
|
||||
if (f.unblockable || f.levitate || f.fear || f.sleep) continue;
|
||||
e.blockedBy = cage;
|
||||
cage.blocking.push(e);
|
||||
e.moving = false;
|
||||
|
||||
Reference in new issue
Block a user