fix: correct community-reported combat and strategy draft behavior

Guard lethal stuns against a replacement deployment; keep bard auras running
through stun; require an active Aegir bond for Clementia's strategy; allow
Compass SP to bypass recovery locks; and refresh Hidden Core gun mitigation
against current gun/spring companions. Set multiplayer strategy picks to 50s
and describe renderer fallback without diagnosing unsupported hardware.

Golden moves (full 285-scenario regeneration):
- roster-008: inactive Aegir no longer gains 18 strategy layers.
- boss-boss_4-solo: bard regeneration continues through stun.
- hidden-boss_9-pair: mitigation covers first hits/current companions.
- standin-07: hammer stun cannot follow an immediate revival deployment.
No ending-reason changes. Sources and report-by-report findings: DESIGN 28.25.
This commit is contained in:
sganggs committed 2026-10-10 00:45:41 +08:00
1 parent 77cd29f07a
commit 708caf7d06
37 files changed
+277 -103

No files matched your search

+2 -2
View File
@@ -84,7 +84,7 @@ const BY_KEY = {
}, { priority: PRIO_BAND_REVIVE });
},
// 克莱门莎 崇高牺牲
// 克莱门莎 崇高牺牲: PRTS requires the 阿戈尔 bond to be active.
act1autochess_band13_buff(battle, ps, p, bandId) {
const bond = typeof p.bond_id === 'string' && p.bond_id ? p.bond_id : null;
if (!bond) return;
@@ -93,7 +93,7 @@ const BY_KEY = {
const u = c.unit;
if (c.reason !== 'killed' || !isOp(u) || u.ownerId !== ps.playerId || !unitBonds(u).includes(bond)) return;
const n = byLevel ? tierOf(u) : Math.max(1, Math.floor(num(p.value, 1)));
if (gainLayers(battle, { playerId: ps.playerId, bonds: bond, n, requireActive: false, source: u, reason: 'band' }) > 0) {
if (gainLayers(battle, { playerId: ps.playerId, bonds: bond, n, requireActive: true, source: u, reason: 'band' }) > 0) {
fxOn(battle, 'layer', u, keyOf(bandId), bandId, { bond, n });
}
});
+10 -7
View File
@@ -621,13 +621,16 @@ function kitGun(ab, e, b) {
},
];
if (hidden) {
list.push({
iv: 0.25,
tick(b2, e2) { // 【未尽的告解】 physical / arts damage taken ×4.damage_scale while the 碎铳之簧 it came with remain
const springs = b2.enemies.some((o) => o.alive && isSpring(o));
if (springs) { const m = T(ab, '4.damage_scale') ?? 1; b2.addBuff(e2, { key: 'boss:confession', duration: 0.35, refresh: 'replace', mods: { physTakenMul: m, artsTakenMul: m } }); }
},
});
// PRTS 未尽的告解: another gun OR spring keeps the reduction. Refresh on hit as well so the first hit and
// a hit immediately after the last companion dies see the current condition, not a stale polling result.
const confession = (b2, e2) => {
const other = b2.enemies.some((o) => o !== e2 && o.alive && (isGun(o) || isSpring(o)));
if (other) {
const m = T(ab, '4.damage_scale') ?? 1;
b2.addBuff(e2, { key: 'boss:confession', duration: 0.35, refresh: 'replace', mods: { physTakenMul: m, artsTakenMul: m } });
} else b2.removeBuff(e2, 'boss:confession');
};
list.push({ iv: 0.25, tick: confession, hitIn(c, b2, e2) { confession(b2, e2); } });
if (s3) list.push({
iv: s3.bb.interval ?? 1,
tick(b2, e2) { // 【盲信之誓】 links: phys per second on operators standing on a line ("无视无法选择、迷彩": 起飞 too)
+4 -1
View File
@@ -138,7 +138,10 @@ const nthAttackStatus = (n, key, dur, sil = true) => ({
attack(c, b, e, a) {
a.n++;
if (a.n % n) return;
for (const t of c.targets) if (t.alive && dur > 0) b.applyStatus(t, key, { duration: dur, source: e });
for (const t of c.targets) {
if (!t.alive || !(dur > 0) || (c.targetDeployments && c.targetDeployments.get(t) !== t.deploySeq)) continue;
b.applyStatus(t, key, { duration: dur, source: e });
}
},
});
+3 -3
View File
@@ -735,7 +735,7 @@ const BY_ITEM = {
}
});
},
// 黄沙罗盘: 初始技力 +init_sp each deploy; (萨尔贡) first skill end +sp; + 萨尔贡浓茶: each cast all 萨尔贡 +addition_sp
// 黄沙罗盘 (PRTS: all its SP ignores 阻回): 初始技力 +init_sp each deploy; (萨尔贡) first skill end +sp; + 萨尔贡浓茶: each cast all 萨尔贡 +addition_sp
chess_item_6_07_e(battle, u, rec, S) {
const p = bp(rec, 'act1autochess_equip_acarm103_global_buff');
if (!p) return;
@@ -749,14 +749,14 @@ const BY_ITEM = {
S.on('skillEnd', (c) => {
if (c.unit !== u || firstDone || c.reason === 'death' || !memberOf(battle, u, 'sargonShip')) return;
firstDone = true;
if (u.skill && u.alive) u.skill.gainSp(num(p.sp), 'item');
if (u.skill && u.alive) u.skill.gainSp(num(p.sp), 'item', false, { ignoreLock: true });
});
S.on('skillStart', (c) => {
if (c.unit !== u || !partner || !memberOf(battle, u, 'sargonShip') || !carries(battle, u, partner)) return;
const n = num(p.addition_sp);
if (!(n > 0)) return;
for (const a of battle.allyUnits) {
if (a.kind === 'op' && a.ownerId === u.ownerId && onField(a) && a.skill && memberOf(battle, a, 'sargonShip')) a.skill.gainSp(n, 'item');
if (a.kind === 'op' && a.ownerId === u.ownerId && onField(a) && a.skill && memberOf(battle, a, 'sargonShip')) a.skill.gainSp(n, 'item', false, { ignoreLock: true });
}
});
},
@@ -82,7 +82,7 @@ function skadi2(bb, chess, def) {
if (unit.skill) unit.skill.addTriggerRange(() => seaborns(battle, unit));
let n = 0;
battle.every(0.5, () => {
if (!unit.canAct) return;
if (!live(unit)) return; // the continuous trait (including its skill conversion) survives stun
for (let i = unit.buffs.length - 1; i >= 0; i--) if (isInspire(unit.buffs[i])) battle.removeBuff(unit, unit.buffs[i]);
n++;
const toks = seaborns(battle, unit);
+1 -1
View File
@@ -19,7 +19,7 @@ const GROUND = Object.freeze({ canHitFly: false, groundOnly: true });
const AIR = Object.freeze({ canHitFly: true });
const SPEED = 2, DISTANCE = 3, CATCH_HALF = 0.55, VORTEX_RADIUS = 1.5;
const SPLASH_RADIUS = 1.5; // [ASSUMED] PRTS has not published S2's normal attack splash radius.
const FIRST_BOMB = 0.5; // [ASSUMED] PRTS lists the landing delay as unknown.
const FIRST_BOMB = 0.5; // [ASSUMED] Neither PRTS nor the edited official demo settles the first landing offset.
const CAST = 'clemnt:cast', CARRY = 'clemnt:carry:';
const skillBb = (raw, id) => skillRec(raw, id)?.bb ?? {};
const tile = (u) => [Math.round(u.y), Math.round(u.x)];