feedback5: merge R19E — every enemy finishes its attack clip after a strike before it moves on (PRTS 状态机: ATTACK → MOVE after the attack ends; the 鸭爵 swap's 流泪小子 no longer walks off on its strike tick, community report and GitHub #214), and the 鸭爵 strategy's swapped-in 流泪小子 hides again 1 s after a block ends instead of 0 s (the owner's decision of 2026-10-07, a deliberate deviation for that copy only); golden as R19E's commits (216 scenarios + solo-HARD-2), the merged tree differs from the branch only in R19G's three 蕾缪安 scenarios

This commit is contained in:
sganggs committed 2026-10-07 13:07:21 +08:00
commit 11fd65a5a5
19 files changed
+4061 -3741

No files matched your search

+16 -6
View File
@@ -23,7 +23,9 @@
// Every enemy attack strikes at its clip's damage frame after its swing starts (attackWindup; a stun before the frame
// cuts the swing — enemyAttack, GitHub #187). Unblocked ranged enemies attack allies within their radius and stand for
// each attack's clip — through its wind-up and until the clip ends — then walk on (attackStand, GitHub #58;
// ATTACK_PAUSE after the strike when no clip is known; 「不停止移动」 attackers never stop); the candidates pass the
// ATTACK_PAUSE after the strike when no clip is known; 「不停止移动」 attackers never stop); every enemy whose block ends
// after its strike (its blocker stunned by that strike, knocked out, retreated) stands for the rest of the clip too
// (PRTS 状态机: an enemy's COMBAT state ends with its attack, not with its blocker); the candidates pass the
// enemy's own rule (`e.profile.canTarget`) and are ordered
// blocker → taunt → latest deployed (targeting.js sortAllyTargets). An enemy's damage type is its data's unless content
// arms it (`e.profile.dmgType`: 转译基底's forms, whose data never attacks). Reaching the final leg's end = leak. A `fear` (恐惧) status suspends the route: the
@@ -687,9 +689,11 @@ function advanceRoute(b, e, dt, R, standing = false) {
}
/**
* How long an unblocked ranged enemy stands for one attack (GitHub #58 — the owner's decision of 2026-10-04 from
* first-hand memory of the official game: a ranged enemy stops for each attack's animation and walks on between
* attacks; the handbook names attacking on the move as a special ability, “十字路口”量产型's 「不停止移动的四向攻击」).
* How long an enemy stands for one attack: an unblocked ranged enemy for its whole clip (GitHub #58 — the owner's
* decision of 2026-10-04 from first-hand memory of the official game: a ranged enemy stops for each attack's animation
* and walks on between attacks; the handbook names attacking on the move as a special ability, “十字路口”量产型's
* 「不停止移动的四向攻击」), and any enemy for the rest of it after the strike once its block ends meanwhile (enemyAttack;
* PRTS 状态机 ATTACK / COMBAT "攻击结束后回退到MOVE状态", 0.2.0).
* [ASSUMED] the stand lasts exactly its attack clip — data/enemies.json `attackAnim` { dur, hit }: the clip the client
* plays for its attacks and its strike frame (tools/build-data.mjs, from the asset manifest) —, `hit` of it before
* the strike (the wind-up, while a target is in range) and the rest after it, both shortened when the attacks come
@@ -850,8 +854,14 @@ function enemyAttack(b, e, prevCd) {
}
if (b._hooks.attack) b.emit('attack', { attacker: e, targets, isSkill: false });
e.atkCd = e.s.interval;
// stands for the rest of its attack clip (attackStand; the wind-up was stood before the strike)
if (!e.blockedBy && radius > 0) e.atkStandUntil = b.time + attackStand(e, STAND).rest;
// stands for the rest of its attack clip (attackStand; the wind-up was stood before the strike) — every enemy, blocked
// or not: PRTS 状态机, an enemy's ATTACK / COMBAT state "攻击结束后回退到MOVE状态" and checks only 异常状态 every frame
// (only a character's COMBAT "检测到不存在阻挡对象时强行切回IDLE状态"), so an enemy whose block ends after its strike — the
// strike stunned its blocker (流泪小子: 晕眩 "无法…阻挡"), knocked it out, or the blocker retreated — finishes its clip
// before it walks on (until 0.2.0 it walked on at once; community report of 2026-10-07 「哭狗瞬间隐匿没有后摇」). A
// blocked enemy never walks, so the stand matters only once it is free; a stun / freeze / sleep / 浮空, hiding or a
// displacement (失衡) ends it (updateEnemy, advanceRoute, Battle.displace)
e.atkStandUntil = b.time + attackStand(e, STAND).rest;
return false;
}
+2
View File
@@ -139,6 +139,8 @@ export class BattleDisplacement {
}
if (moved > 0) {
this._unblock(e);
// 失衡 ends the attack clip it stood for (PRTS 状态机: the states are exclusive — UNBALANCE, then DEFAULT → MOVE)
e.atkStandUntil = -Infinity;
if (e.route) e.route.pts = null;
this.fx('displace', { x: e.x, y: e.y, id: e.id });
}
+3 -1
View File
@@ -373,7 +373,9 @@ K.preparation_start_add_special_goods_every_n_round = (ps) => ({
// share taken over that half [ASSUMED: "你和队友遭遇的敌人" — the enemies heading for the player's own protection point;
// each official client simulates its own battle, docs/research/08 appendix A]. The originals never exist, so none of
// their death / kill / leak effects happen. Reaching the protection point costs 1 LP ("但进入保护目标点将减少1点目标生命值":
// the enemies' data lpr, tools/build-data.mjs).
// the enemies' data lpr, tools/build-data.mjs). The swaps carry the spawn tag 'duck': their 隐匿 (流泪小子) comes back no
// sooner than 1 s after a block ends, not the official 0 s (content/enemies/helpers.js DUCK_STEALTH_RESTORE — the owner's
// decision of 2026-10-07, a deliberate deviation).
export const DUCK_BAND = 'band_ducklord';
/** "击倒这些敌人者获得1资金奖励" (the blackboard's `count` is 1 too). */
+6 -4
View File
@@ -3,9 +3,9 @@
// unbalanced, artsBarrier, husk, statue, bleed, pollution, dmgZone, skill(), blinkForward … and the archetype kits
// reused by several keys (kitEp, kitTimes …).
import { MOVE_SCALE, COLS } from '../../constants.js';
import { MOVE_SCALE, COLS, STEALTH_RESTORE } from '../../constants.js';
import {
HUSK_REBIRTH, BOOM_RADIUS, POLLUTION_INTERVAL, STEALTH_RESTORE_BY_KEY, nthOf, stOf, safe, num, T, elem, hurt,
HUSK_REBIRTH, BOOM_RADIUS, POLLUTION_INTERVAL, STEALTH_RESTORE_BY_KEY, DUCK_STEALTH_RESTORE, nthOf, stOf, safe, num, T, elem, hurt,
areaAllies, areaAlliesInTiles, zone, spawnChildren, stepToward, setHits, hitCount, setForm, absorbArts, auraBuff,
} from './helpers.js';
@@ -14,11 +14,13 @@ import {
/**
* 隐匿: permanent stealth. Blocked it is lifted; it hides again STEALTH_RESTORE s after the block ends, or after its own
* "(解除阻挡N秒后恢复)" (STEALTH_RESTORE_BY_KEY → buff `data.stealthRestore`; Battle._stealthSwitch).
* "(解除阻挡N秒后恢复)" (STEALTH_RESTORE_BY_KEY → buff `data.stealthRestore`; Battle._stealthSwitch) — a 鸭爵 swap (tag
* 'duck') no sooner than DUCK_STEALTH_RESTORE s (the owner's decision of 2026-10-07, a deliberate deviation).
*/
const stealth = () => ({
spawn(b, e, a, ab) {
const n = STEALTH_RESTORE_BY_KEY[ab.key];
let n = STEALTH_RESTORE_BY_KEY[ab.key];
if (e.tag === 'duck' && (n ?? STEALTH_RESTORE) < DUCK_STEALTH_RESTORE) n = DUCK_STEALTH_RESTORE;
b.addBuff(e, { key: 'ab:stealth', flags: { stealth: true }, persist: true, data: n != null ? { stealthRestore: n } : {} });
},
});
+10 -1
View File
@@ -44,6 +44,15 @@ const STEALTH_RESTORE_BY_KEY = Object.freeze({
enemy_1283_sgkill_2: 1, // 家族暗影灭迹人
});
/**
* 隐匿 of the 鸭爵 strategy's swapped-in enemies (spawn tag 'duck': content/bands/meta.js duckReplace) comes back no sooner
* than this many seconds after a block ends — in practice 流泪小子 (enemy_2034_sythef_2, the only one of the four with 隐匿;
* officially 0 s, STEALTH_RESTORE_BY_KEY). A deliberate deviation from the official game: the owner's decision of
* 2026-10-07 「鸭爵策略产生的泪眼汪汪…要不等待个1秒这样再重新隐匿」 (community report of 2026-10-07, GitHub #214). The same
* enemy anywhere else — the 流泪小子 of a bounty card (enemy_2034_sythef), a test spawn — keeps the official time.
*/
const DUCK_STEALTH_RESTORE = 1;
/** Splash radius (中点判定 around the target) of 烹泉 / 沏虹's attack (PRTS 天赋 "普通攻击对目标对及目标周围半径1.0范围内的所有
* 我方单位造成法术普通伤害(无视迷彩,不可对空)") and of 集团军重型火炮's shell (PRTS "对目标半径1.0范围内的所有我方单位造成攻击力
* 100%的物理伤害(此弹道会强制击中主目标,碰撞无视迷彩,不可对空)"). */
@@ -387,6 +396,6 @@ const onTerrain = (b, u, terrain) => { const [r, c] = tileOf(u); return b.grid.t
// used by the other enemy modules (content/enemies.js and content/enemies/*.js)
export {
BOOM_RADIUS, POLLUTION_INTERVAL, STEALTH_RESTORE_BY_KEY, TEA_SPLASH_RADIUS, SHELL_SPLASH_RADIUS, stOf, safe, dispatch,
BOOM_RADIUS, POLLUTION_INTERVAL, STEALTH_RESTORE_BY_KEY, DUCK_STEALTH_RESTORE, TEA_SPLASH_RADIUS, SHELL_SPLASH_RADIUS, stOf, safe, dispatch,
num, auraBuff, watchDeaths, onTerrain,
};