0.2.5-dev: 驭械术师 normal attacks keep the operator's own hit plus one drone hit, the drone multiplier and per-target map reset on redeploy, 洛洛 S2 keeps its lock until a kill or the skill ends (PRs #537 / #536 / #535 by @Xinglan233, ported); hit tiles use the official round-half-to-even with an edge inset (PR #520 by @Cloudnyco, ported); bots softly avoid living teammates' opening factions until they are eliminated (PR #518 by @IceCodeNew, ported with changes); golden: 43 more scenarios move, each named in docs/history/0.2.5.md

This commit is contained in:
sganggs committed 2026-10-12 14:45:01 +08:00
1 parent 7fd0478dc0
commit e0f3771821
41 files changed
+1939 -779

No files matched your search

+6 -5
View File
@@ -545,7 +545,7 @@ returns their current `weight` (including modifiers). The same tick's count and
list and budget (DESIGN §28.27); this is local to the kit, not a change to general displacement.
**Hit areas (`body.js`, user playtest #5 item 10).** A regular enemy is a point: in a grid range when the tile of its
position (`round(y)`, `round(x)`) is a range tile, in a radius when its position is (DESIGN §3). A huge enemy (巨型单位:
position (`roundHalfEven(y)`, `roundHalfEven(x)` — PRTS 作战机制 "四舍六入五成双"; `Math.round` would take 2.5 to 3) is a range tile, in a radius when its position is (DESIGN §3). An enemy held on the field edge is inset by `FIELD_EDGE_INSET` (1/1048576) [ASSUMED] so that rounding keeps it on the edge tile; movement, knockback and a unit's standing tile for range keys still use `Math.round` (§30.20). A huge enemy (巨型单位:
`enemy.hitArea` from data/enemies.json `hitArea` — 假想敌:胄 / 管 / 盐风主教昆图斯 / 阿利斯泰尔 / “萨米的意志”, PRTS
"受击判定区域为长4.95、宽2.95的长方形,向上偏移1.0") is hit anywhere on a rectangle `w` × `h` tiles centred on its position
moved `dx` columns right / `dy` rows up: it occupies every tile the rectangle overlaps (`bodyKeys` — 5 × 3 tiles; PRTS
@@ -1123,7 +1123,7 @@ Element conventions of the kits (user playtest #5 #3; official term dictionary:
targeting: { maxTargets, rangeGrid, rangeExtend, priority, allInRange, canHitFly,
noRangeExtend /* the skill's range ignores the unit's 攻击距离 (rangeExtend): 信仰搅拌机 S3, PRTS 备注 */,
showOwnRange /* rangeGrid only selects targets, no official range change: the card keeps the unit's own range */ },
attack: { dmgType, atkScale, healScale, splashRadius, splashScale, hits, projectile, maxTargets, dmgMul,
attack: { dmgType, atkScale, healScale, splashRadius, splashScale, hits, funnelMul, funnelBodyHits, projectile, maxTargets, dmgMul,
chain: {count, falloff, radius, sluggish}, heal: {mode, count, falloff, hpAtMost /* targets at or below this HP ratio only */}, onHitStatus: {key, duration, value},
onHit(ctx), // once per attack (main target; target may be null for a splash landing)
onEachHit(ctx) }, // per victim: main, every splash / chain victim — ctx += { dealt, kind:'main'|'splash'|'chain', isSplash, isChain, main, attackId }
@@ -1208,10 +1208,11 @@ chess_char_2_07_a: (bb) => ({
```
**3. Instant burst caster — 至简 `chess_char_3_13_a` 神工意匠** (bb `atk_scale 1.3, ct 2`; next attack 130 % arts, hits
twice; 2 charges; the funnel profile already ramps damage on the same target):
twice; 2 charges; the funnel profile's unreleased drones are additional damage that ramps on the same target, and this
charged attack sets two body hits):
```js
chess_char_3_13_a: (bb) => ({
skill: { kind: 'charges', attack: { atkScale: bb.atk_scale, hits: 2, dmgType: 'arts' } },
skill: { kind: 'charges', attack: { atkScale: bb.atk_scale, hits: 2, funnelBodyHits: 2, dmgType: 'arts' } },
}),
```
An AoE instant (德克萨斯 剑雨, bb `stun 2`: two hits of 120 % arts around her + stun) uses `onStart` instead:
@@ -1350,7 +1351,7 @@ header of professions.js).
| loopshooter | 回环射手 (user playtest #3): every attack throws a boomerang (`ai.js throwBoomerang`, projectile `'boomerang'`) out to the target at 15 tiles/s — it hits on arrival — and back to the thrower's current position at 3.75 tiles/s without damage (PRTS 跃跃 "投射物飞行速度15,返回时飞行速度3.75"); attacks only while holding it (every boomerang thrown caught — "必须回收全部回旋投掷物才可以进行下一次攻击", `unit.trait.boomerangsOut`) and with the attack cooldown ready, so the real interval is the longer of the two; a target dead mid-flight is not hit (it still flies to the last position and back); knocked out / withdrawn ⇒ lost, a redeployed thrower holds a fresh one; 跃跃 S2's extra boomerangs share the one flight (cnt hits); each catch fires `boomerangCaught`, and an attack profile's `boomerangOnward(ctx)` flies it on after the first hit before it turns back — ctx `hit` (a hit of the same attack), `comeBack(x, y)` once (a content error sends it back from the hit point): 娜仁图亚 S1 bounces, S2 dashes on and hits on its way back (kits/ops/op-narant.js) |
| reaperrange | hits every enemy in range; ×1.5 (bb atk_scale) on the trait front grid (or its own line ahead) — both along its direction |
| chain | chain N (trait text/bb max_target) with −15 % per jump (bb chain.atk_scale), 1.7-tile jumps (constants.js CHAIN_RADIUS, PRTS 溅射半径一览: 链术师 1.7; 1.8 until 0.1.1), sluggish on each hit |
| funnel | drone damage 20 % → +15 %/hit on the same target → 110 % (bb init/delta/max) |
| funnel | normal attack: one 100 % ATK body hit plus separate drone additional damage, 20 % → +15 %/hit on the same target → 110 % (bb init/delta/max; modules can change init/cap). `funnelMul` holds the drone ramp; `dmgMul` applies to both. Extra drones (`hits`) do not duplicate the body; an unreleased skill attack can set `funnelBodyHits` (至简 S2: two body hits and two additions). Body and additions share an `attackId`, but only the body is `isAttack`; additions carry `addition, funnel`. [ASSUMED] body instances first at the existing impact, one ramp per volley. Released-skill profiles keep their existing paths; this normal-attack correction does not settle their body behaviour. A normal deployment resets target, scale and any per-target ramp map to init, including revival against the same living target. A free move (`moveRedeploy`, the same carry as a 秘术师's stored energy and 乌尔比安 S3) keeps the ramp. An in-flight drone recomputes the ramp on impact. 洛洛 S2 detaches this multiplier from new body shots and inherits the trait ramp in an independent skill tick: `rockr:drone`, arts, `isSkill:true`, `isAttack:false`. Older projectiles cannot change its local ramp. A living selectable lock need not remain in body range; the drone returns on a kill or when the skill ends, and ending hands the ramp back and restores the normal multiplier (§30.19) |
| mystic | 秘术师 (PRTS 分支特性信息 秘术师, GitHub #181): at its attack check (the attack ready, able to act, not disarmed) with no valid target — no target, or its kit's `canAttack` false (深靛 never picks a bound enemy) — it stores one energy (`storeEnergy`, up to bb times: 3, 深靛's MSC-X 4), an attack action: the attack interval restarts; a full store idles with the attack ready. The energies leave with its next attack that happens (`releaseEnergy` in `performAttack`, after `beforeAttack`: an attack cancelled before its shot keeps them) and land with the main hit, one arts attack hit each (`hitsFn` reads the hit's `info.energy`); a redeployment holds none [ASSUMED]. 维伊 / 黑键 plug their own `storeEnergy` (转置能量, elite energies) into the same check |
| phalanx | no attack & DEF +200 %, RES +20 (bb) while the skill is off; while on, `rangeAoe`: each attack strikes every selectable enemy on its range at once (blocked enemies included; a stealthed one only when revealed or blocked), the same damage near and far, instant (`'beam'`) — "群体法术伤害" (secondary: Terra Wiki, Phalanx Caster: "attacks hit all enemies within their range"; supporting: PRTS 林 S3 备注 "单次普攻最多触发1次效果" — one normal attack can kill several — the same 锁定攻击范围 shape as the 轰击术师, and PRTS 溅射半径一览 documents no splash radius for it (it omits the 撼地者 too, so this is not proof); no primary source states a target cap; community report E3: it used to be one bolt + a 1.1 splash). 卡涅利安's charged S1 keeps the skill-off trait, 不攻击 included (kit `canAttack`, PRTS 备注) |
| physician | heal the lowest HP% injured ally in range (a skill `targeting.maxTargets` widens any heal profile) |
+26
View File
@@ -176,6 +176,32 @@ Profession traits (default behaviours implemented once in the engine, keyed by `
---
Unreleased funnel drones deal additional arts damage alongside the ordinary attack (PRTS 分支特性信息 驭械术师, §30.17).
The engine keeps a 100 % ATK body instance and a separate drone instance, each through mitigation and shields.
`funnelMul` holds only the drone ramp; the attack's `dmgMul` (至简's talent) is shared once. Extra attached drones
raise `hits`, not the body count; 至简's charged S2 sets `funnelBodyHits` to two. Both share one `attackId`, attack
hook and SP / charge use; additions have `addition` and `funnel` tags and do not count as `isAttack`. [ASSUMED] Body
instances precede additions at the existing projectile impact; consecutive hits keep one ramp per volley. This
correction covers ordinary attacks and unreleased skill attacks that set `funnelBodyHits`. Released skill paths retain
their existing behaviour; it does not establish whether those skills should also have body attacks (耶拉 S2, 澄闪 S1
and S2 remain to be reconciled separately).
Funnel ramps return to the initial scale on a normal deployment, including automatic revival against the same living
target (§30.18). The reset clears the current target, the scale and any per-target ramp map, and leaves other trait
state alone. A free move (`moveRedeploy`, the same carry as a 秘术师's stored energy and 乌尔比安 S3) keeps the ramp.
An in-flight shot recomputes the ramp when it lands, so a projectile launched before the reset can still step the new
ramp.
洛洛 S2 releases its trait drone into an independent skill tick (§30.19). Acquisition uses the body's normal target
selection; a living selectable lock stays valid outside the body's range and returns only when that enemy dies or the
skill ends. The body retains its own ranged ordinary attacks. The released ramp inherits the trait state but is
isolated from older projectiles, and while it is out the unreleased addition is off. Ending the skill returns that
ramp to the trait and restores the normal multiplier and the overload cap. S1 and other operators' funnel attacks keep
their existing paths. [ASSUMED] There is no flight delay; an untargetable lock is dropped and retargeted; body control
does not pause the released drone.
---
## 7. Content modules (server/sim/content + match/effectsMeta handlers)
Each domain file `server/sim/content/{tokens,bonds,garrisons,items,bands,enemies,bosses,devices,choices}.js` exports `install(battle)` (battle side; called once per Battle by `content/index.js → installContent`) and `registerMeta(registry)` (prep side; called once at server boot by `match/effectsMeta.js`, API documented in `docs/META.md`). Kits live one per file in `content/kits/ops/` (`export default { [baseChessId]: (bb, chess, def) => Kit }`), listed in `content/kits/index.js` (guide: `content/kits/README.md`). Content must be **data-driven from blackboards** (numbers never hard-coded when a blackboard key exists). Every effect gets at least one unit test in `test/content/*.test.js` using the `test/helpers/battleHarness.js` harness (provided by sim-core).
+4
View File
@@ -126,6 +126,10 @@ adjusted count and reset scope stay limited to its own operators; the DIY export
[ASSUMED] Bot purchasing applies one soft 18-point penalty to non-elite, non-DIY tier V/VI shared-pool cards whose
active bonds overlap another living teammate's opening strategy. Human and AI teammates are treated equally; it
neither reserves stock nor prevents a stronger purchase. Existing strategy presets and rehearsal counts are unchanged.
A living teammate's selected strategy also soft-reserves its bonds for bot strategy selection and focus planning
before any member is deployed (§30.21). The reservation ends when that teammate is eliminated. It does not hard-exclude
a strategy, reserve stock, or stop an immediate merge. A reserved strategy keeps a quarter of its unreserved draw
weight (`SOFT_BAND_RESERVE` = 0.25), so it can still be chosen. Mode-inactive bonds stay weight 0 (DESIGN §21.26).
[ASSUMED] AI reactions are cosmetic social choices, enabled by default and disabled with `SP_BOT_EMOTES=0`. Only
mixed human/AI matches emit them, through the existing whitelist and cooldown. A human emote gets at most one eligible
+126
View File
@@ -271,3 +271,129 @@ the preceding digest exactly; the changed partner/side also changes preparation
All five already differed from 0.2.4, so the final release-relative total remains 45 scenarios (§30.9 plus these
additional pairing effects). There are no new corpus cases or removals. The other follow-up fixes do not move this
corpus; their dedicated regressions cover the reported and newly found cases.
### 30.17 Funnel normal attacks keep a body hit
PR #537 by @Xinglan233. [PRTS 分支特性信息 驭械术师](https://prts.wiki/w/分支特性信息/data) says a drone that has not
been released by a skill deals additional damage on top of the operator's attack: 20 % ATK at first, +15 on the same
target, cap 110 %. A skill-released drone deals skill damage.
The profession used to return that ramp as the whole normal-attack multiplier, so the body hit was missing.
`resolveHit` now keeps a 100 % ATK body instance and separate drone instances. `funnelMul` holds only the drone ramp;
an attack's `dmgMul` (至简's talent) is applied once to both. Extra attached drones raise `hits`, not the body count.
至简's charged S2 sets `funnelBodyHits`, so two body hits and two additions share one ramp step. Both share one
`attackId` and one attack hook; only the body is `isAttack`. Additions carry `addition` and `funnel` and are not also
emitted as a normal attack.
[ASSUMED] Body instances precede additions at the existing projectile impact, and a multi-hit volley takes one ramp
step. This covers ordinary attacks and unreleased skill attacks that set `funnelBodyHits`. Released skill paths that
do not set it (耶拉 S2, 澄闪 S1 while its own drones are out) keep their existing skill-damage path and do not gain a
second body. Whether those released skills should also have a body attack is not settled here. 澄闪 S3 and the
skill-drone simulator on 淬羽 stay on their own `dealDamage` paths.
Kits that stored the ramp on `dmgMul` (至简, both 耶拉 forms, 澄闪 S2) now store it on `funnelMul`.
Tests: `test/sim/funnel-body.test.js`, and the funnel cases in `kits_t3`, `kits_t6`, `kits_alt_t6`, `kits_alt_t2`
and `op_gdglow`.
### 30.18 Redeploy resets the drone ramp
PR #536 by @Xinglan233. The same branch page says that changing target, or redeploying the operator, restores the
drone damage to its initial value at once.
`installFunnel` resets `funnelTarget`, `funnelScale` and the per-target `funnelMap` (耶拉 / 至简) on a normal
deployment, including automatic revival against the same living target. A free move (`moveRedeploy`, the same carry
as a 秘术师's stored energy and 乌尔比安 S3) keeps the ramp. An in-flight drone still applies the ramp it computes on
impact, so a shot launched before the reset can step the new ramp. That limit is documented and not changed.
Tests: `test/sim/funnel-redeploy.test.js`, `test/match/funnel-redeploy.test.js`.
### 30.19 洛洛 S2 keeps its lock outside her range
PR #535 by @Xinglan233. The official skill table `skchr_rockr_2`, as recorded in `docs/research/03-operators.md`,
says the released drone stays locked until the enemy is killed or the skill ends, then returns. Leaving her attack
range is not a return condition. The branch page says a skill-released drone deals skill damage, and that additional
damage belongs to drones that were not released.
S2 now runs one independent skill tick (`rockr:drone`, one drone at both ranks). While the skill is up the trait
multiplier is detached, so new body shots are ordinary attacks and the unreleased addition is off. The drone's hits
are arts skill damage (`isSkill`, not `isAttack`). It inherits the trait ramp on its own copy, so an older projectile
cannot change that ramp. A living selectable lock stays valid outside the body's range. Ending the skill writes the
ramp back and restores the multiplier and the overload cap. S1 and other operators' funnel attacks keep their
existing paths. 立于磐石 is unchanged.
[ASSUMED] There is no flight delay. A lock that becomes untargetable is dropped and the drone retargets. Body control
(stun and the like) does not pause the released drone.
Tests: `test/content/rockrock-s2-lock.test.js`, and the 洛洛 case in `kits_t1t2`.
### 30.20 Hit tiles use half-even rounding
PR #520 by @Cloudnyco. PRTS 作战机制 (oldid 433935, 「碰撞体积与位置识别」) says the struck unit's coordinates are
rounded half to even (四舍六入五成双) onto the tile they occupy. `Math.round` sends an exact .5 toward +∞, so an enemy
held on an outer half-tile was indexed off the board and dropped from the hit index.
`roundHalfEven` is used for hit tiles only: `body.js` `posKey`, the point cases of `bodyOnTile` and `bodyTileReach`,
the point-enemy index in `battle/queries.js`, and `targeting.js` `tileKeyOf`. Movement, knockback, `ai.js`, a unit's
standing tile for range keys, and `onFrontLine` still use `Math.round`. #519's signed-zero case in `powi` was already
on this candidate and is left as it is.
[ASSUMED] `FIELD_EDGE_INSET` = 1/1048576 pulls a clamped enemy just inside the outer half-tile, so half-even rounding
keeps it on the edge tile. An inset of 1/1024 was rejected because it moved unbalance steps. The inset has no
official source.
Tests: `test/sim/hit-tile-round.test.js`, `test/sim/detmath.test.js`, the edge landing in
`feedback5-raid-own-board`, the 连星 probe in `op_wang`, and the 麦哲伦 probe in `feedback5-wv-summon-triggers`.
### 30.21 Bots soft-reserve a living teammate's opening bonds
PR #518 by @IceCodeNew, for issue #514. This is not an official rule. It extends the accepted #407 card penalty.
Opening-band choice and focus planning used to ignore a living teammate's selected strategy until someone from it was
deployed, so a bot could take that faction's high cards. A living teammate's strategy now soft-reserves its bonds for
both choices, before any member is deployed. The reservation ends when that teammate is eliminated (`alivePlayers`).
It does not hard-exclude a strategy, reserve stock, or stop an immediate merge. A reserved band's draw weight is a
quarter of its unreserved weight (`SOFT_BAND_RESERVE` = 0.25) and stays above zero, so the strategy can still be
drawn. Focus planning applies the existing −6 teammate penalty to those bonds as well. Mode-inactive bonds stay
weight 0 (DESIGN §21.26). A solo match has no teammate to reserve against. The shared-pool −18 card penalty is
unchanged, and a stronger merge can still win it.
[ASSUMED] The quarter weight is a project choice. The PR text called the reservation soft; its diff hard-zeroed the
weight. This candidate follows the text. The reservation is equal for human and AI teammates.
Tests: the two `#514` cases in `test/match/bot-smarter.test.js`. One proves the focus leaves the reserved bond while
the teammate lives and returns after elimination. The other proves a reserved strategy is still drawn, and drawn more
often once that teammate is eliminated. The existing card test still shows a merge outscoring the −18 penalty.
### 30.22 Golden changes after the funnel, hit-tile and bot-reservation ports
All 287 scenarios were recomputed once after §30.17–§30.21 and compared with this candidate before those ports
(`HEAD` `7fd0478d`, `git show HEAD:test/golden/*.json`). No scenario was added or removed. 43 digests differ from
that candidate. They are additional to §30.9 and §30.16.
Against released 0.2.4 the total is now 64. Of the previous 45, 21 are byte-for-byte the digests already on this
candidate and are not repeated below: `roster-011`, `roster-012`, `roster-013`, `roster-032`, `roster-033`,
`roster-034`, `roster-039`, `roster-040`, `roster-043`, `bond-maniShip-high`, `bond-yanShip-low`,
`boss-boss_2-pair`, `boss-boss_5-solo`, `hidden-boss_8-pair`, `hidden-boss_9-solo`, `solo-NORMAL-2`,
`coop2-NORMAL-3`, `solo-HARD-9-boosted`, `coop2-ABYSS-10-boosted`, `standin-06`, `standin-07`. The other 24 of
those 45 moved again, and 19 scenarios that still matched 0.2.4 on this candidate now differ.
Opening strategy ids do not change in any of the 20 match digests. A solo match has no teammate, so §30.21 cannot
move one. Co-op seeds keep the same `bandId` and the same bot-draw counts except `coop4-ABYSS-7`. The recorded
match moves are battle totals from §30.17–§30.20, and the later gold and LP those totals change. No isolated
counterfactual was re-run; the groups below follow who is actually on the board.
| Cause | Changed scenarios |
|---|---|
| Funnel body hit plus redeploy reset, and 洛洛 S2's independent lock (§30.17–§30.19). These boards include 洛洛 | `roster-004`, `roster-005`, `roster-023`, `roster-024`, `bond-victoriaShip-low`, `bond-victoriaShip-high` |
| Funnel body hit plus redeploy reset (§30.17–§30.18). 至简 and/or 耶拉, not 洛洛 | `roster-007`, `roster-009`, `roster-026`, `roster-028`, `bond-deputShip-low`, `bond-emptyShip-low`, `bond-kjeragShip-high`, `bond-maniShip-low`, `bond-miraShip-low`, `bond-sargonShip-high`, `bond-soloShip-low`, `bond-visiShip-low` |
| Same body hit and redeploy reset on 荒芜拉普兰德 | `roster-017`, `roster-019`, `roster-036`, `roster-046`, `roster-047`, `boss-boss_3-pair`, `boss-boss_5-pair`, `boss-boss_7-pair`, `hidden-boss_9-pair` |
| 澄闪 DIY: the normal attack is a body hit plus one drone addition (§30.17) | `diy-098`, `diy-099` |
| Hit-tile half-even rounding and the edge inset (§30.20). No funnel operator is deployed; the damage total stays 1388100 and the hook counts and the snapshot sample at index 27 move | `standin-04` |
| Full matches. Opening bands are unchanged. Final lineups include a funnel operator, except `coop2-HARD-4` (both seats eliminated, lineup empty) and `coop3-ABYSS-5` (the surviving lineup has none), whose first divergence is round 7 and round 5; those two are the hit-tile change, or a funnel operator who is not left in the stored lineup | `solo-FUNNY-1`, `solo-FUNNY-2`, `solo-NORMAL-1`, `solo-HARD-1`, `solo-ABYSS-1`, `coop2-HARD-4`, `coop2-NORMAL-1-coverage-standins`, `coop2-NORMAL-5-coverage-diy`, `coop2-NORMAL-8-serverrun`, `coop2-NORMAL-70-diy`, `coop3-ABYSS-5`, `coop4-FUNNY-6`, `coop4-ABYSS-7` |
Result flips inside that match list: `solo-NORMAL-1` defeat → victory (team LP 0 → 2); `solo-FUNNY-1` team LP 15 → 23
and `solo-FUNNY-2` team LP 8 → 23, both still victories, with every settled round row unchanged; `coop2-NORMAL-8-serverrun`
team LP 26 → 27; `coop4-ABYSS-7` eliminated at round 12 → defeat at round 13, and its shop and bot draw counts change.
The other matches keep the same end reason. `standin-04` is the only battle in this set with no funnel operator on
the board.