feedback7 R22-OSIM: a skill that 白铁's 铁钳号 opens now acts on it — 蕾缪安 S3 / 艾丽妮 S3 / 陈 S2 / 引星棘刺 S2 spend on it, skills that pick enemies only stay closed; golden: unchanged

Grok's review of the 铁钳号 change (HIGH): the device opened every non-heal skill, but kits that pick their victims
with enemiesInKeys only were spent on nothing — 蕾缪安 S3 went active with 0 locks, 艾丽妮 S3 and 陈 S2 struck nothing,
引星棘刺 S2 threw at the empty far tile. Audit: every skill of every chess (loadouts) and of the 自选 pool, placed with
only the device in range; of the 351 skills the device opened, 67 never touched it.

- skills.js `allyTargetsOk`: the device counts for a skill's start only if the skill acts on it — SkillSpec
  `allyTargets` (true / false) from the kit, else [ASSUMED] a skill acting through the unit's own attacks (timed and
  still attacking, or a cast that is the next attack) on a unit that attacks. An onStart-only cast or a no-attack timed
  skill picks enemies itself and waits for one (德克萨斯 S2 剑雨, 锡人 S1/S2, 铃兰 S3 … about 50 skills).
- Spend on the device (allyTargets: true; its kit cancels the hits, its 受击回复 counts): 蕾缪安 S3 locks it and spends
  her bullets (ammoUsed, so the per-bullet layers come) and shells it; 艾丽妮 S3 strikes it; 陈 S2 draws on it after
  every enemy; 引星棘刺 S2 throws onto it and its pulses hit it. queries.js allyTargetsInRadius for their areas.
- Explicitly closed ([ASSUMED], allyTargets: false): 普罗旺斯 S2 (never shoots above 80 % HP), 卡涅利安 S1 (charged: no
  attack), 山 S2 (blocked enemies), 余 S2 / S3, 迷迭香 S3 (blocked only), 荒芜拉普兰德 S1 / S2 (drone locks), 淬羽赫默 S1–S3
  and 遥 S1–S3 (heal attacks), 机械师 S3, 珊比 S2 / S3, 陈 S3 / 艾丽妮 S2 (their guards already refused).
- 雪猎's empty-magazine cast back to enemies only (its S2 does not act on the device).
Re-audit: 281 skills open on the device alone and all act on it (蕾缪安 S3 through her locks); none of those that
acted on it before stopped. Docs: SIM.md, design/engine.md, PLAYING.md.

Tests: test/content/feedback7-allytarget-kits.test.js (5; all fail on the parent).
This commit is contained in:
sganggs committed 2026-10-08 20:53:24 +08:00
1 parent e733171b13
commit 69d791f9c6
21 files changed
+217 -23

No files matched your search

+26 -3
View File
@@ -30,7 +30,9 @@
// rule AND such an ally on the trigger grid — the cast replaces the attack about to be made (塞雷娅 S1 "触发时会替换当
// 次攻击"); should that ally condition fail before the attack, the cast is withdrawn and its charge returned.
// A registered ally target (Battle.setAllyTarget: 白铁's 铁钳号·原型机, an enemy-camp summon our attacks select) counts
// like an enemy in each of these enemy conditions for a non-heal skill (`_allyTargetIn` — the owner's rule of 2026-10-08).
// like an enemy in each of these enemy conditions for a skill that acts on it (`_allyTargetIn`, `allyTargetsOk` — the
// owner's rule of 2026-10-08): one that acts through the unit's own attacks, or whose kit flags `allyTargets: true` (its
// selectors take ally targets too); a skill that picks its own victims among enemies only is not opened by it.
// Automatic operations cool down (constants.js AUTO_OP_COOLDOWN, "自动操作具有3s冷却,在完成一次操作或作战开始时部署的单位
// 将进入冷却"): the engine auto-casts a MANUAL skill (def.skillType) no sooner than 3 s after its previous cast (so a
// charged skill spends its charges 3 s apart) or after the unit's deployment at the battle start (Battle._deploy
@@ -44,7 +46,8 @@
// mods, flags, targeting {maxTargets, rangeGrid, priority, allInRange, rangeExtend, noRangeExtend (the range ignores
// the unit's 攻击距离), showOwnRange (rangeGrid only selects targets: the detail card keeps the unit's own range)},
// attack {dmgType, atkScale, splashRadius, splashScale, hits, projectile, maxTargets, dmgMul, onHit, heal…},
// heal (bool: heal-type skill for the trigger rule), onStart(ctx), onEnd(ctx), onHit(ctx), onAttack(ctx), onTick(ctx).
// heal (bool: heal-type skill for the trigger rule), allyTargets (bool: the skill acts on a registered ally target — see
// `allyTargetsOk`), onStart(ctx), onEnd(ctx), onHit(ctx), onAttack(ctx), onTick(ctx).
// ctx passed to spec callbacks: { battle, unit, skill, bb, target?, dealt?, targets?, dt?, reason? }; onAttack's ctx
// also carries `noAmmo` (set it to true: this attack spends no ammo). onEnd runs while the skill's mods / range are
// still applied (`active` is already false); they are removed right after it (unless onEnd re-activated the skill).
@@ -177,11 +180,31 @@ export class SkillRuntime {
* canTargetEnemy (every other selector keeps to enemies); a heal skill never counts it (禁疗: not a patient).
*/
_allyTargetIn(keys) {
if (this.healSkill || !keys) return false;
if (!keys || !this.allyTargetsOk) return false;
const b = this.battle;
return !!(b._allyTargets && b._allyTargets.size) && b.allyTargetsInKeys(keys, this.unit).length > 0;
}
/**
* Does this skill act on a registered ally target, so that one may start it (`_allyTargetIn`)? Never a heal skill.
* SkillSpec `allyTargets` (true / false) answers for a kit: true where its selectors take ally targets too
* (allyTargetsInKeys / allyTargetsInRadius — 蕾缪安 S3, 艾丽妮 S3, 陈 S2, 引星棘刺 S2), false where they never do although
* the skill keeps attacking. Without it: a skill that acts through the unit's own attacks — which select the device
* (ai.js acquireTargets) —, a timed one that keeps attacking (no `attack.noAttack`) or a cast that is the next attack
* (`spec.attack`), on a unit that attacks. [ASSUMED] Any other skill picks its victims itself among the enemies
* (enemiesInKeys, foesInRadius …) and would be spent on nothing: the device does not open it — it waits for an enemy.
* (Grok's review of round 22: 蕾缪安 S3 / 艾丽妮 S3 / 陈 S2 / 引星棘刺 S2 opened on the device alone and did nothing.)
*/
get allyTargetsOk() {
if (this.healSkill || this.noSkill || this.kind === 'passive') return false;
const s = this.spec;
if (s.allyTargets != null) return !!s.allyTargets;
const p = this.unit.profile;
if (p && p.noAttack) return false;
if (s.attack && s.attack.noAttack) return false;
return this.isTimed || !!s.attack;
}
get spCost() {
const c = Math.floor(this.baseSpCost * this.spCostMul + (this.unit.s ? this.unit.s.spCostFlat : 0));
// content writes spCostMul / spCostFlat: a NaN/Infinity cost would freeze SP forever — use the base cost instead