Commit Graph
768 Commits
Author SHA1 Message Date
sganggs 911e4bff2b feedback7 UIBACK: a 设置 button left of 玩法说明 in the lobby's and the room's top bar (GitHub #238 by RushFTK) — ui/settings.js SettingsButton opens the same settings modal as the title screen and the match; no background-music option and no board-style option (the owner's decision of 2026-10-07: neither is official); test/ui/settings-entry.e2e.test.js; golden: unchanged (client only) 2026-10-08 19:09:55 +08:00
sganggs ddf8c39ed0 feedback7 R22-VOICES: the Japanese voice dub as a second tree beside the Chinese one — audio.voiceJp (the same slots and file names from ArknightsAssets2 voice/: 2674 files, 89.4 MB, for the 191 voiced operators), 设置 → 语音语言 中文 / 日本語 (default 中文, not tied to the interface language), a JP line the manifest or the host lacks plays the Chinese one; the full zip ships both dubs (one switch, tools/package.mjs FULL_ZIP_JP_VOICE), the lite zip stays art-free (the owner's request of 2026-10-08 「全套的日配语音」); golden: unchanged
- tools/assets/plan.mjs: one voiceTree() per dub — audio.voice (--voice-lang, cn by default) and audio.voiceJp
  (VOICE_JP_LANG, always the JP dub, audio/voice/jp/<charId>/cn_nnn.mp3); fetch-assets plans and downloads both
  (stats.voiceJpChars), so setup (checkAssets walks every listed URL) fetches both too.
- data/assets.json regenerated with the project's tool (node tools/fetch-assets.mjs --add-only, raw.githubusercontent.com,
  107 s): the only change is audio.voiceJp + stats (+2674 files, +89,388,041 bytes, voiceJpChars 191) + hash — the rest
  is byte-identical; the JP tree is the Chinese one with the folder swapped (191 × 14 files, every file a valid MP3).
  The audio files are not committed (public/assets/ is git-ignored).
- public/js/audio.js voiceLine(): 'jp' draws from voiceJp and falls back to the Chinese line per slot (a JP entry the
  manifest lacks) and per line at play time (the Chinese file of the same name when the host lacks the JP one —
  same token, same gate); operators without voice (stand-ins, 盟约·辅助干员, summons) stay silent. setVoiceLang().
- settings: voiceLang 'cn' | 'jp' (VOICE_LANGS, sanitizeSettings), the 语音语言 row under 干员语音 (each dub named in
  its own language, like the language switch), 语音语言 in the en / ja / ko / zh-TW packs.
- tools/package.mjs FULL_ZIP_JP_VOICE (true): false holds the JP files out of the full zip (and its update zip) —
  not missing, not orphans, never in an update's `removed`; setup then downloads them.
- Sizes measured: art download 548 MiB (was 463), full zip ≈ 505 MB (0.2.1: 428 MiB + 76 MiB of zipped JP voice),
  unzipped 711 MB (package --dry-run); lite zip unchanged. README, DEPLOY, ASSETS, setup's fallback size updated;
  PLAYING voice paragraph; §21.30 "Changed in 0.2.2" note.
Tests: test/feedback7-voices.test.js (new: plan, manifest, settings, package switch), test/ui/audio.test.js (voiceLine,
the manager's dub choice and fallback), test/ui/gameLogic.test.js, test/docs-consistency.test.js — all fail on the parent.
2026-10-08 19:09:52 +08:00
xcdoge 2669b6ab57 feedback7 R22-RENDER: a pushed / pulled enemy slides to where the sim put it, a dying flyer keeps its lift through the Die clip, a long Die clip plays whole (PR #380 by @xcdoge); golden: unchanged
The sim displaces at once (battle/displacement.js), so the snapshots only show the destination: a ≤ 2.5-tile push was
lerped over one snapshot interval (3 frames, 0.58 tiles in one frame — frame-loop repro over a real battle), a longer
pull snapped. render/app.js now starts UnitView.slideTo as soon as the interval it samples ends on the snapshot that
carries the destination (a `displace` fx still queued up to SnapshotBuffer.nextSnapT); the view holds where it stood
until that snapshot is shown, then eases into the sampled position under a constant deceleration (v0 = 2D/T, a = v0/T,
T = 0.14·√D in 0.12–0.45 s — ours, [ASSUMED]: no source gives the official timing; docs/research/13 has the official
impulse-and-friction classes). A unit that dies on the way finishes the slide and lies where the push put it.
Death: DIE_CLIP_MAX 1.6 → 8 s (data/assets.json: 234 enemy Die clips, the longest 7.97 s, 25 over 1.6 s), and a
flyer holds FLY_HOVER until the fade tail (DIE_FADE_TAIL 0.55 s).

Substantially @xcdoge's code (PR #380: the slide, the death timing, research 13 / 14, most tests), authored as theirs.
Changed on review: the corpse no longer goes back to where it stood (the PR's test asserted that; the sim's body is at
the destination); the PR's pre-pass scanned (renderT − 0.5, renderT] after processEvents had taken those events, so
it never fired and the slide started a frame before landing — it now looks ahead to the next snapshot and holds the
view until then; the slide eases into the live sample instead of the fixed destination (no jump back onto an enemy
that walked on); each step uses its mean velocity (the end-of-step speed ran 9 % short, then jumped); the constants'
comments say the timing is ours; research 14 no longer cites a sweep script and a data guard that were not in the PR.

Golden: unchanged (client only).

Tests: test/render/unitview.test.js 35/35 (slide: deceleration, idempotence, facing, snapshots in flight, friction,
√D, corpse lies where pushed, hold-then-ease without a landing jump, a never-shown slide handed back; the app.js frame
order over a real battle: a 1.7-tile push over ≥ 8 frames with every step < 0.35 tiles, a push-kill's body staying at
the destination; death: a dying flyer's lift, every Die clip within DIE_CLIP_MAX and a 7.97 s clip scheduled whole);
test/sim/displace-fx.test.js 1/1; render/interp 16/16; docs tests pass.
2026-10-08 19:08:27 +08:00
weinibuliu b5c29b5335 feedback7 G-TOOLS: asset downloads honour HTTP(S)_PROXY on Node 22.21+ and 24 (PR #289 by @weinibuliu)
Did not apply the patch. It set a global dispatcher on the npm undici package and rewrote the lockfile. Node's built-in fetch does not use that dispatcher, and node:undici is not importable. fetch() reads HTTP_PROXY, HTTPS_PROXY and NO_PROXY only at startup, and only when that startup had NODE_USE_ENV_PROXY=1 (or --use-env-proxy). That is Node >=22.21.0 and >=24.0.0.

tools/fetch-assets.mjs restarts itself once with NODE_USE_ENV_PROXY=1 before downloading. The default fetch in the downloader and the index cache rejects when a proxy is set but this process cannot use it, including Node 22.0–22.20 and 23, instead of fetching directly. No new dependency. Engines stay >=22.

Tests: node --test test/assets-env-proxy.test.js — 8 pass, 0 fail. node --test test/asset-network.test.js test/assets-diy.test.js test/fetch-assets-shrink.test.js test/assets.test.js — 84 pass, 0 fail. Before this commit: npm run -s lint — 0 errors (135 existing warnings); npm run -s typecheck — ok; npm run -s check:imports — exit 0 (3 pre-existing sim-node notes). docs, version and package tests: 46 pass and 1 fail while env-proxy.mjs was still untracked; after git add, node --test test/package.test.js — 10 pass, 0 fail.
2026-10-08 19:07:49 +08:00
sganggs a0cd6826e5 feedback7 R22-GHSIM: 异客 S3's 雷暴 covers range x-1 — the 13-tile diamond — around the tile of the target's centre, not a 1.5-tile circle around its position (GitHub #322, PR #329 by @IceCodeNew, re-implemented); golden: roster-016/018/048, fields boss-boss_1/3/5-pair + hidden-boss_9-pair, matches solo-HARD-1, solo-ABYSS-1, coop2-HARD-4, coop2-ABYSS-10-boosted, coop2-NORMAL-70-diy move
PRTS 异客 S3 备注: 「开启技能时选择技能范围内的目标,以其判定中心所在格为中心生成一个x-1的雷暴区域」; range_table x-1 is the
13-tile diamond (|Δrow| + |Δcol| ≤ 2); x-3 (|技能范围|) stays the search range. The storm's tile is the rounding of the
target's centre (body.js posKey), fixed for its 4 s; a strike picks among the enemies she can select whose body is on
those tiles (enemiesInKeys, as her other range checks); each strike still chains on its own bounce radius. The fx ring is
drawn at radius 2 (visual only). Until now: [ASSUMED] radius 1.5 around the target's exact position — an enemy two tiles
straight off the target's tile was never struck, one at (+1.55, +1.05) off the tile centre was.

Re-implemented from the PR's claim (its golden files were not taken). Golden: every moved scenario fields 异客 casting
S3 — the zone holds other enemies and the random pick draws from another list.
2026-10-08 19:03:55 +08:00
sganggs df540b6210 feedback7 UIBACK: a stage device explains itself on a tap — 阻隔工事 / “双眼皮” / 射击台 / 源石流发生装置 get the terrain tip's card (GitHub #228, idea and first version in PR #229 by @rickylxw, ported onto the 0.2.1 modules with t() and the four packs): numbers from the stage's own device entry, the 「场地装置」 chip, the turret's stat grid, the tile fact; in a battle the crate and the turret are found by their device UNIT (the PR matched the stage pos: a turret outside the rect stands elsewhere, the units are not in the field meta of a battle shown from its start, a destroyed crate left the snapshots and still answered), the card carries the live HP and closes with the device; the dev mock boards ?stage= and streams crate units on ?variant=devices; golden: unchanged (client only) 2026-10-08 19:03:55 +08:00
2321Robin a87cc1e9fd feedback7 R22-FIELDS: boss-field portals teleport — a route ending on a portal entrance comes out of the far exit and leaks at that side's blue door, the four appear-less crossings reappear at the exit (GitHub #336, PR #337 by @2321Robin); golden: 8 fields, 6 matches
PR #337 by @2321Robin (GitHub #336, both with a recording of the official game), applied with small changes: the exit
distance uses detmath hypot (PR #373 bans Math.hypot in the sim), comments cite the data counts as measured here
(137 routes end on [1,3] / [1,17]; 95 explicit DISAPPEAR / APPEAR pairs, all → [5,10]), the end-teleport's zero wait
is marked [ASSUMED] beside the pairing, and the test reads the test enemy's own leaks (the templates' escorts leak too)
and checks the entrance itself is no leak.

- simdata.js portalAppear: act1autochess_h07_02 #4, act1autochess_h08_02 #5 / #9 and act2autochess_h07_05 #6 write
  DISAPPEAR → WAIT → MOVE[5,10] with no APPEAR (the level files too); the exit APPEAR is inserted before that move.
  Before, the enemy walked the rest of its route hidden and leaked unseen.
- ai.js portalPickup: a route that ends on tile_telin (the boss / Hidden Core circuits) vanishes the enemy, brings it
  out of the farthest telout ([5,10]) and walks it to the nearest blue door ([2,2] / [2,18]), where it leaks. Before,
  stepping into the corner portal was the leak. Mid-route crossings are unchanged (every MOVE onto an entrance in the
  data is followed by an explicit DISAPPEAR).

Golden — only boss / Hidden Core fields and the matches' final rounds move (no round row of any match moves):
- fields boss-boss_2-solo (铳): the 2 lrrook escorts still leak, now at the door after the portal, and take 120652
  damage instead of 66943; leader left 2761255 → 2763024.
- fields boss-boss_4-solo (昆图斯): the 2 dspred leaks now happen at the door; one more operator death (6 → 7);
  leader left 3759559 → 3759921.
- fields boss-boss_6-pair (阿利斯泰尔): the 2 hllevi_2 that leaked at the portals are killed on the way back:
  killed 4 → 6, both players perfect (1 leak each → 0); leader left 7017881 → 6972383.
- fields boss-boss_6-solo: killed 5 → 8, leaks 5 → 2 (the 3 msfshu that leaked are killed; the 2 hllevi_2 leak at the
  door); leader damage 105194 → 75209.
- fields hidden-boss_8-pair (胄): p2's one vtsk leak is gone — that vtsk is still walking to the door at the 200 s
  cap; p2 perfect.
- fields hidden-boss_8-solo: killed 3 → 8, total 13 → 19 (more sfshu die, so more msfshu split off: 3 → 9), leaks
  10 → 10; leader damage 402606 → 191576 (the escorts that come back out of [5,10] draw the fire).
- fields hidden-boss_9-pair (铳): from portalAppear alone — h08_02's routes #5 / #9 no longer count the portal hop as
  8 tiles of walking, so the targeting order of those escorts changes; leader left 3570058 → 3511007.
- fields hidden-boss_10-solo (管): the yokai_3 escort crosses back (taken 68252 → 101682), 4 more operator deaths;
  leader left 7323878 → 7323370.
- matches solo-FUNNY-2 (boss_5 Final Assault, lost at 8 rounds): pool left 94860 → 94412, kills 143 → 144.
- matches solo-NORMAL-1 (m03, hidden boss_8 胄, act1autochess_h08_01_s): the Hidden Core goes from cleared to failed
  (hiddenCleared true → false, team LP 26 → 0, roundsPassed 15 → 14, pool left 0 → 99894; victory stays true — the
  Final Assault was won). The fight is identical until 105 s, when the third vtsk escort reaches the corner portal
  [1,3]: before, it leaked there (27 → 26 LP) and the leader died at 214 s. Now it comes out of [5,10], walks through
  the team's line to the blue door [2,2] and leaks at 170 s; meanwhile only 0–2 of the 8 operators stand (2–4 before),
  the leader stalls near 120000 HP, the fight passes the 150 real-second mark (300 game s) and the overtime drain (1 LP
  per real second) plus two drone leaks at the door take the last 25 LP at 348 s with 99894 HP left.
- matches solo-HARD-1 (m03, hidden boss_8): still failed at 0 LP after 14 rounds; pool left 3046117 → 2839368, kills
  419 → 422.
- matches solo-HARD-9-boosted (hidden boss_10 管): still cleared; team LP 383 → 361 — the 管 echoes (acdums) leaked
  7 times at the portal before and once at the door now, but the longer fight pays 37 overtime LP instead of 9.
- matches coop2-ABYSS-10-boosted (hidden boss_9 铳): from portalAppear alone (the remaining-distance change above);
  still cleared at 800 LP, the two seats' leader damage shifts (6609998 / 7282442 → 7523075 / 6369365) and their
  titles swap (坚若磐石 / 卫戍之星 → 卫戍之星 / 不朽盟约).
- matches coop2-NORMAL-14-standins (boss_5 Final Assault, lost at 13 rounds): pool left 107915 → 97588.

Tests: test/sim/portal-appear.test.js (new, 9; 7 fail on the parent: the APPEAR insertion, the four real routes,
the two route-end portals).
2026-10-08 19:00:50 +08:00
LimitlessHPPK 5a50f7b5fa feedback7 R22-GHSIM: the module trait line 「攻击范围内存在N名及以上敌人时攻击速度+X」 (REA-Y: 圣约送葬人, 隐德来希) is applied by the engine instead of the two kits — on and off on the tick the count changes; a scan of every chess, 补位 and 自选 loadout shows no other operator gains it (PR #293 by @LimitlessHPPK); golden: unchanged
The contributor's code (server/sim/content/traitMods.js, the battle/players.js _setupUnit wiring after kit.install, the
crowdAspd removal from shared/tier5.js and the two kits, test/sim/trait_attack_speed.test.js), applied from PR #293, so
they are the author of this commit. Maintainer additions: the coverage lock also scans the 补位 stand-ins (266) and every
legal 自选 combination (pool operator × slot form × skill × non-other-mode module) — none gains the rule; 黑键 / 维伊
MSC-Y 「拥有已储存的攻击能量时,攻击速度+30」 (their 自选 kits own it) is listed as kit-owned instead of 'unknown'; the
comment's history note named a commit this repository does not have, and its line-number references are now names;
docs/ARCHITECTURE.md and kits/README.md name the file.

Official: battle_equip_table uniequip_003_excu2 (display trait + hidden talent attack_speed 12, trigger_cnt[equip] 2) and
uniequip_003_etlchi (trait attack_speed 12, cnt 2), both 「攻击范围内存在2名及以上敌人时攻击速度+12」; 仇白's
uniequip_003_qiubai carries the same sentence on her named talent, which her kit keeps reading (not seen by the engine).

Before: shared/tier5.js crowdAspd polled every 0.2 s and gave a 0.3 s buff — measured +12 0.167 s after the second enemy
arrived and 0.267 s after it died (scratchpad repro); now one tick each. Golden: unchanged (no sampled snapshot moves).
2026-10-08 18:56:12 +08:00
ZiyueW1116 8a64edc9e9 feedback7 R22-RENDER: an enemy standing out its attack recovery is drawn standing — the snapshot carries stand / standCut and the interpolator holds the position until the recovery ends (PR #381 by @ZiyueW1116, supersedes #307 / #314); golden: unchanged
Since 0.2.0 the sim stands an unblocked enemy for the rest of its attack clip (ai.js attackStand → atkStandUntil),
but render/interp.js lerped x/y over the whole snapshot gap, so the view slid through a recovery the sim held
(repro: a real 隐形弩手, snapshots 4.9 / 5.0, stand until 4.934 — sampled at 4.917 it slid at vx −0.2).

The snapshot now carries, only when non-empty, `stand: [[id, until]]` (atkStandUntil of enemies alive, deployed,
visible, neither feared nor stunned) and `standCut: [[id, at]]` (the latest time each listed enemy's recovery was cut
or ignored: a displacement, fear, a stun, hiding, leaving — Battle._cutAttackStand, display metadata the battle never
reads). sample() holds the older position until `until` and interpolates the rest (HP / SP keep the whole interval),
stays linear when the newer snapshot records a cut inside the interval, and extrapolates nothing for a newest snapshot
that shows a stand, a death, a stun or a block. Teleports and redeploys still snap first; old snapshots without the
fields interpolate as before.

Substantially @ZiyueW1116's code (PR #381), authored as theirs. Changed on review: the docs (SIM §9, design/network.md)
and code comments in English like the rest of them; the snapshot builds its listed-id set only when an enemy has a cut
to report.

Golden: unchanged — the digest canon hashes neither field and the sim's timing does not move (all 283 scenarios match).

Tests: test/render/attack-recovery-interp.test.js (new, 25: metadata validation, hold-then-slide, cuts, legacy
snapshots, extrapolation guards, real 隐形弩手 / Weedy push-kill / stun / freeze / sleep / levitate / block / fear
battles) 25/25; render/interp 16/16; sim battle, perf, robustness, downelem 69/69.
2026-10-08 18:54:54 +08:00
sganggs cb8865d9d8 feedback7 R22-VOICES: a tap on an operator says its 选中干员 line in every phase — the prep, the shop and the hand too, not only while a battle runs (the owner's request of 2026-10-08 「添加一下干员点击上去的语气一样的语音」, lifting 2026-10-03's 「整备阶段不需要干员语音」 for this line only; [ASSUMED] the official prep tap = the battle's FOCUS_CHAR); golden: unchanged
screens/game.js passes the detail panel voice=${true} instead of its combat flag, so the panel's existing
audio.voice(charId, 'select') runs on every opened operator: a piece on the field or in the hand, a shop /
reward card, a bond member. The gate follows the official FOCUS_CHAR row (audio_data.json battleVoice:
priority 10, cooldown 0, overlapIfSamePriority true): VOICE_TAP_SLOTS — on an idle channel a tap always
plays (no 1.2 s global gap; VOICE_COOLDOWN_MS.select 1500 → 0), a newer tap replaces the 选中 line on air,
and a higher-priority line (部署 20, 作战中 70, 开战 100 …) is never interrupted. Deploy / skill / engage /
result lines stay battle-only; stand-ins, 盟约·辅助干员 and summons stay silent (no voice entry).
Tests: test/ui/audio.test.js (new: the gate and two taps through the manager; fails on the parent),
test/docs-consistency.test.js pins updated. Docs: §21.30 "Changed in 0.2.2" note, PLAYING.md voice paragraph.
2026-10-08 18:54:02 +08:00
LimitlessHPPK 51c53de189 feedback7 G-TOOLS: listen on :: by default so one socket serves IPv6 and IPv4 (PR #291 by @LimitlessHPPK)
Ported only the dual-stack listen from PR #291 (the change accepted as #188). With HOST unset the server binds :: and, if that fails with EAFNOSUPPORT, EADDRNOTAVAIL or EINVAL, retries 0.0.0.0. An explicit HOST, including 0.0.0.0 and 127.0.0.1, is tried once. The boot banner and doctor list shareable IPv6 addresses in brackets, skip fe80::, and keep one URL per /64. An explicit IPv6 literal in the local URL is bracketed. The Windows service listing no longer drops those bracketed lines.

Not ported: /dev/grant, the settings-default migration, and the firewall scripts. The Docker image stays on 0.0.0.0; the alpine fallback was not booted.

Tests: node --test test/ipv6-bind.test.js test/doctor.test.js test/ci.test.js — 20 pass, 0 fail (8 bind, 4 doctor, 8 ci).
2026-10-08 18:53:24 +08:00
Convey123 ffa538c39d feedback7 R22-RENDER: 赤刃明霄陈 S3's sword wave is drawn along its path — the sim sends its position as a chen3Wave fx every 0.12 s, the client draws a 'qi' blade (PR #382 by @Convey123, owner item 12); golden: diy-073 / diy-074 events.fx and fx.chen3Wave only
The 赤霄·天喟 wave is a kit-managed probe (it turns corners and lives as long as the skill), not a snapshot
projectile, and the sim emitted one `dash` at the cast and a `slash` per hit — nothing along the way, so the client
drew no wave. op-chen3.js now emits fx('chen3Wave', { x, y, fromX, fromY, dr, dc, id }) every WAVE_FX_EVERY (0.12 s;
4 ticks), the trail restarting at a corner; render/fx registers chen3Wave → 'qi' (pt: drawn at the event, never on
her) and draws a streak plus a crescent head turned the way it travels. The look is a procedural stand-in, not the
official particle prefab.

Substantially @Convey123's code (PR #382), authored as theirs. Changed on review: the head is projected after
streak() — streak() reuses the effect system's projection scratch, so the PR drew the crescent on the trail's start,
one event behind the wave — and the trail, head and heading share one height; an unused test variable dropped.

Golden: diy-073 (5_diy*=chen3 S3 in two slots) events.fx 508 → 1558, fx.chen3Wave → 1050; diy-074 (6_diy*=chen3 S3)
events.fx 272 → 872, fx.chen3Wave → 600. HP, kills, damage, hooks and snapshot hashes unchanged: battle.fx only
records an event.

Tests: test/content/op_chen3.test.js (+1: the events, their cadence, id, direction, trail start, silence after the
skill) 12/12; test/render/fxchen3wave.test.js (new, 6: the archetype, drawn at the wave not at her, the head on the
wave and turned with it, a sweep joining into one trail, junk input, nothing left after) 6/6; boardart 15/15.
2026-10-08 18:48:53 +08:00
Cloudnyco e8ba091298 feedback7 G-TOOLS: add the battle performance bench under public/dev (PR #198 by @Cloudnyco)
Applied as-is. Captured real battles live in public/dev/perf (including the round-13 联防 on the wide round battlefield), with a browser speed page and a headless benchmark. public/dev stays out of the package. The Chrome benchmark was not run. The numbers in the original pull request are from 0.1.4 and are not quoted here.

Tests: node --test test/perfbench.test.js test/render/battle-perf.browser.test.js — 13 pass, 0 fail; the Chrome suite skipped (RENDER_E2E unset).
2026-10-08 18:48:42 +08:00
siruimei07 3356b340a7 feedback7 R22-GHSIM: three or more overlapping 同名效果取最高 applications resume strongest first — the middle one no longer drops out when the strongest ends (GitHub #342, PR #348 by @siruimei07); golden: diy-059, diy-122, diy-123, diy-124, diy-133, diy-138 move
The contributor's code (status.js _applyValuedStatus and both test files, applied as written from PR #348), so they are
the author of this commit; the maintainer side added the 同名效果取最高 label back to the JSDoc and a docs/SIM.md line.

buff.data.tail held ONE waiting application (the later-ending one): a third, weaker but longer one replaced a middle one
that was still running, so when the strongest ended the effect jumped to the weakest (#342: 30 % / 25 % / 20 % 减防 —
DEF 1000 → 700 → 800 instead of 750 → 800 → 1000). The tail is now a chain ordered by strength (decreasing) and end
(increasing): a weaker application is inserted in it, a stronger one takes over with the old value and its chain behind
it, an equal refresh trims waiters that end before it, and an expiry resumes the next live waiter with the rest of the
chain. removeStatus / removeBuff still drop the whole chain; two-application data shapes are unchanged.

Golden: six 自选 battles that stack three same-name applications on one unit — diy-059 / diy-122 / diy-123 / diy-124
(庇护 from several operators), diy-133 (Mon3tr's ASPD talent buff talent:monstr:aspd), diy-138 (脆弱) — the middle
application now resumes.
2026-10-08 18:48:26 +08:00
anAtheist987 1f147a288f feedback7 G-TOOLS: tools/ci.mjs and npm run ci run the CI checks locally (PR #367 by @anAtheist987)
Applied as-is. tools/ci.mjs runs the checks already listed in .github/workflows/ci.yml, in that order, and skips checkout, setup-node and npm ci. Children are spawned with process.execPath, not a shell. test/ci.test.js keeps the step list, smoke script, job env and package.json scripts in step with the workflow.

Tests: node --test test/ci.test.js — 8 pass, 0 fail. npm run ci was not run (it installs and runs the suite).
2026-10-08 18:48:16 +08:00
siruimei07 a2a3f52418 feedback7 G-TOOLS: SP_VERIFY re-runs slice work in the existing 8 ms budget instead of blocking the event loop (PR #352 by @siruimei07)
Applied as-is. All-mode verification runs the existing HeadlessJob in headlessSliceMs slices (8 ms on a real scheduler; synchronous when that value is not finite, including the virtual scheduler). The field still settles with the server digest. Sample mode still accepts the client result immediately and logs afterwards. A comparison error in all mode falls back to the server result. Dispose cancels the pending slice timer.

Tests: node --test test/match/verification-slices.test.js — 11 pass, 0 fail; node --test test/match/clientCombat.test.js test/match/clientCombat-review.test.js — 39 pass, 0 fail.
2026-10-08 18:47:53 +08:00
Xinglan233 5f0a06cc70 feedback7 G-UI: retreat with a full bench counts the slot of the own summon stack (PR #361 by @Xinglan233)
Applied as-is. The client precheck for withdrawing a board operator onto an occupied hand slot now treats that operator's own hand summon stack as a free slot, which the server already accepted. Temp stacks, board summons, another player's tokens, and an item that only carries ownerUid stay HAND_FULL. stack.count is not extra slots. Dropping on a chess still checks the swap first.

Tests: node --test test/ui/gameLogic.test.js test/ui/facing.test.js test/ui/uiFixes.test.js test/match/feedback1-placement.test.js test/match/feedback5-we2-placement.test.js — 116 pass, 0 fail. Before this commit: npm run -s lint — 0 errors (134 existing warnings); npm run -s typecheck — pass; npm run -s check:imports — pass; node --test test/docs-consistency.test.js test/docs-paths.test.js test/version.test.js test/package.test.js — 47 pass, 0 fail. No new UI strings, so the i18n check was not run. No golden update.
2026-10-08 18:45:01 +08:00
Xinglan233 bc22f9cbe6 feedback7 G-UI: Enter on the equip-replace dialog's buttons keeps its native behaviour (PR #358 by @Xinglan233)
Applied as-is. The replace dialog no longer treats Enter that started on a button as confirm. Enter or Space on 取消 closes the dialog and leaves the chosen equipment in place. Enter on an option only changes the selection. Enter on the confirm button still sends g.equip with that replaceUid. This matches the guard already used by DialogHost.

Tests: node --test test/ui/mock.e2e.test.js test/ui/leftovers.test.js test/ui/standin-ui.test.js — 31 pass, 0 fail. The mock e2e suite skipped because SP_E2E is unset. eslint on the touched files: 0 errors (one pre-existing unused-assignment warning in mock.e2e.test.js).
2026-10-08 18:44:09 +08:00
Xinglan233 5952539046 feedback7 G-UI: clear the 调度中心 upgrade confirm for the reached level after pressing D (PR #356 by @Xinglan233)
Applied as-is. The shop bar's two-tap arm for the level button is now lv:<level>. A successful upgrade, including the D shortcut, changes the level and drops that arm, so the next click only selects the new level. A freeze at the same level keeps the confirm. Another card's arm is left alone, and D still upgrades immediately.

Tests: node --test test/ui/mock.e2e.test.js test/ui/playtest6-elite.test.js test/ui/playtest6-ui.test.js — 20 pass, 0 fail. The mock e2e suite skipped because SP_E2E is unset. eslint on the touched files: 0 errors (one pre-existing unused-assignment warning in mock.e2e.test.js).
2026-10-08 18:43:40 +08:00
siruimei07 246622beff feedback7 G-UI: the spectator prep view updates effects and the enemy preview after a 机变 pick (PR #349 by @siruimei07)
Applied as-is. Prep-scout refresh now signs the public prepFieldMeta payload, so a 机变 or bounty change that only updates effects or nextEnemies is pushed to teammates and spectators. Shop, funds, and private views stay off that payload. An unchanged public view is still not sent twice, and an explicit g.watch still resends the current view.

Tests: node --test test/match/prep-watch.test.js test/match/prep-bench.test.js test/match/spectator.test.js test/match/feedback5-boss-prep-scout.test.js test/match/teammate-loadout.test.js test/ui/spectate-effects.test.js test/match/feedback5-boss-pair-prep.test.js — 25 pass, 0 fail. eslint on the touched files: 0 errors.
2026-10-08 18:43:24 +08:00
siruimei07 8597b66df3 feedback7 G-TOOLS: the boot build tag includes sim, shared and data changes (PR #351 by @siruimei07)
Applied as-is. BUILD_INPUTS now covers server/sim, shared and data as well as the page, so a restart after a simulation-only or data-only change is visible to an already-open page. The tag is still computed once per process. A page still waits until the match, including settlement, is over before it reloads, and a failed check still does not reload.

Tests: node --test test/build.test.js test/ui/build-runtime.browser.test.js — 8 pass, 1 skipped (the Chrome restart test; SP_E2E unset), 0 fail.
2026-10-08 18:42:46 +08:00
Xinglan233 e2503c7f60 feedback7 G-UI: the import dialog traps keyboard focus and yields while the guide is open (PR #341 by @Xinglan233)
Applied as-is. The shared Modal keeps Tab inside the topmost dialog, pulls focus back if it leaves, skips disabled, hidden, inert and zero-size controls, and does not trap while a .guide overlay is open. Escape still closes the dialog. Enter is unchanged, so a focused button keeps its native click.

Tests: node --test test/ui/modal.e2e.test.js test/ui/loadout.e2e.test.js test/ui/leftovers.test.js test/ui/match-info.test.js test/ui/loadout.test.js test/ui/playtest3.test.js test/ui/playtest4.test.js test/ui/uiFixes.test.js test/ui/localArt.test.js — 114 pass, 0 fail. The modal and loadout e2e suites skipped because SP_E2E is unset. eslint on the touched files: 0 errors.
2026-10-08 18:42:38 +08:00
siruimei07 063408e47b feedback7 G-TOOLS: build-data --refresh fails instead of falling back to an old cache (PR #350 by @siruimei07)
Applied as-is. When --refresh is set, a download that still fails after four attempts throws instead of returning the older cached file, so one build cannot mix a new table with an old one. --offline and a warm cache without --refresh are unchanged. Files already written to the cache stay; generated data/ and the previous report are left in place.

Tests: node --test test/build-data-refresh.test.js — 11 pass, 0 fail.
2026-10-08 18:42:23 +08:00
siruimei07 d07d97dc4c feedback7 G-UI: selling a summoner counts the bench slots its summon stacks free (PR #309 by @siruimei07)
Applied as-is. The sale pre-check now treats each of the operator's own hand or temp summon stacks as one empty slot, independent of stack.count, before the equipment returns. Board summons and another player's tokens still occupy their slots. A sale that does not fit still returns HAND_FULL before anything is detached.

Tests: node --test test/match/sell-capacity.test.js test/match/economy.test.js test/match/playtest6_summons.test.js — 28 pass, 0 fail. eslint on the touched files: 0 errors (one pre-existing unused-arg warning in economy.js).
2026-10-08 18:42:00 +08:00
siruimei07 5662b7ef65 feedback7 G-TOOLS: out-of-order language-pack responses no longer overwrite the newer choice (PR #304 by @siruimei07)
Applied as-is. initLang and switchLang record a request counter before the first await and apply a pack only when that request is still the latest. Choosing the language already on screen cancels an in-flight load. A failed latest choice still saves the preference and leaves the previous UI.

Tests: node --test test/ui/lang.test.js test/ui/i18n.e2e.test.js test/i18n-packs.test.js test/i18n-server.test.js — 29 pass, 0 fail. The Chrome suite in test/ui/i18n.e2e.test.js skipped (SP_E2E unset).
2026-10-08 18:41:58 +08:00
Xinglan233 25bcdff3a7 feedback7 G-UI: reselecting the same operator keeps its draft skill and module (PR #306 by @Xinglan233)
Applied as-is. Clicking the operator already in the 自选 draft no longer rebuilds the draft from the saved pick or the default, so an in-progress skill or module edit stays. Choosing a different operator still loads that operator's saved pick or defaultPick.

Tests: node --test test/ui/diy-ui.test.js test/ui/feedback5-module-trait.test.js test/ui/diy.e2e.test.js — 25 pass, 0 fail. The diy.e2e suites skipped because SP_E2E is unset.
2026-10-08 18:41:36 +08:00
siruimei07 7c307d8613 feedback7 G-TOOLS: a reconnect token cannot be claimed twice by two tabs started apart (PR #299 by @siruimei07)
Applied as-is. A tab that is still choosing a reconnect token now answers another tab's query for the same candidate hashes, so a tab started during the 150 ms window cannot adopt a token the earlier tab is also considering. The smaller tab id still wins, and a tab that already holds the token keeps it.

Tests: node --test test/client-identity-session.test.js test/client-static.test.js — 319 pass, 0 fail.
2026-10-08 18:41:33 +08:00
Cloudnyco ae22335cfe feedback7: the battle sim uses engine-independent math — Chrome, Firefox, Safari and the server agree bit for bit (PR #373 by @Cloudnyco)
- server/sim/detmath.js: hypot (sqrt of the squares, rescaled outside 2^±500), powi (integer powers in double-double,
  one rounding), sin / cos (fdlibm 5.3) and atan2 (fdlibm / FreeBSD msun) built only from correctly rounded IEEE
  operations; the sim's Math.hypot / sin / cos / atan2 / pow calls go through it (65 files); eslint.config.js forbids
  the approximated Math functions under server/sim and test/sim/detmath.test.js scans for them.
- sin / cos / atan2 are the algorithms V8 runs in Node, so the server's results stay; only hypot differs from V8's.
  Golden: 2 moved — diy-027 (a hypot distance, damage +80) and coop3-ABYSS-5 (a later card pick from round 9, same end).
- Applied unchanged after review (Grok static review: MERGE; the diff read in full: no network, no new dependency).
- Tests: broad node --test 5155 / 5155 with the new file tracked (detmath 7 / 7, package 10 / 10); lint 0 errors,
  typecheck, check:imports.
2026-10-08 18:35:55 +08:00
Cloudnyco 3eced7bdba 修复:Windows 上打包和读取 zip 时中文文件名出错(master 的 Windows CI 失败) (#311)
Windows' bsdtar fallback (no zip on the Actions Windows runners) now writes UTF-8 entry names (zip:hdrcharset=UTF-8), and the update tool's zip reader also takes Info-ZIP's Unicode Path extra field for names stored in the system code page. Test and maintainer-tool change only; nothing shipped changes.
2026-10-08 17:45:45 +08:00
sganggs c2a2ef778c feedback6: the 联防 down-ring render test reads 德克萨斯's redeploy time and cost from the data — at full potential 60 s and 11 (it hard-coded 70 s / 13; an opt-in RENDER_E2E suite, so the R21P stream's broad run did not reach it)
- RENDER_E2E=1 unitedown.browser 1 / 1 (failed in the 0.2.1 full pass: 54 s left at 6 s).
2026-10-07 20:04:10 +08:00
sganggs de0b830465 feedback6: merge fb6-diy-esc — Esc in the 自选 picker closes only the picker (GitHub #284 by @Xinglan233, idea from PR #286)
- Golden unchanged (UI only); broad node --test 5147 / 5147; lint 0 errors; the new browser test 1 / 1 on the branch.
2026-10-07 18:54:12 +08:00
sganggs b05845f507 feedback6: merge R21P — every operator at full potential (潜能 6; GitHub #252, PR #255 by @SpiritedAwayCN, the owner's decision of 2026-10-07)
- tools/build-data.mjs applies every potentialRanks attribute step (COST, RESPAWN_TIME, ATK, MAX_HP, DEF,
  MAGIC_RESISTANCE, ATTACK_SPEED) to the chess, elites, 补位 and 自选 units and picks talent / trait / module-talent
  candidates up to requiredPotentialRank 5; summons take the owner's potential for their talents, not its stat steps.
  data/chess.json, backups.json, tokens.json and the en / ja / ko / zh-TW data translations rebuilt; op-wang.js reads
  her stock cap (8 at full potential). 刺玫 435 ATK / cost 15, 砾 16 s, 至简 70 s.
- No conflicts outside the golden files (regenerated). Golden vs HEAD: roster 49, bonds 46, fields 22, matches 18,
  diy 133 (standins and diy-012 / diy-135…138 unchanged: no potential ranks or never hit). Union check against the
  branch and the merge base: every scenario only the branch moved equals the branch, except roster-048 and
  solo-ABYSS-2: at full potential a 频次 器物 is reached there (roster-048: one more enemy dies instead of leaking, 86 → 87
  units), so R21H's hit count (data × hpMul ÷ supplyHpMul) applies where it did not before — with only that rule put back
  (archetypes.js times as at the merge base) both equal the branch exactly.
- Tests: broad node --test 5147 / 5147; lint 0 errors.
2026-10-07 18:48:38 +08:00
sganggs 10ef14817b feedback6: Esc in the 自选 picker closes only the picker, like its 取消 (GitHub #284 by @Xinglan233, idea from PR #286)
- The 干员调配 overlay's capture-phase Esc closed the whole overlay while a 自选 picker was open (it only gave way to a
  .modal; the picker is a .diy-pick section without its own key handling), so one Esc went back to the lobby.
- DiyPicker registers its own capture-phase Escape handler (calls onClose, as 取消 and the × do; skipped while a .modal
  — the 导入 dialog — is open, which handles Esc first); loadout.js returns early on Escape while a .diy-pick is open.
  The second Esc closes the overlay as before. Saved picks are untouched (the draft is dropped, as with 取消).
- test/ui/diy.e2e.test.js: a real-server browser test that needs no art (5阶 slot, then a 6阶 slot with Esc pressed in
  the search field): the picker closes, the overlay and its four slots stay, sp.pref.diy is unchanged, the next Esc
  closes the overlay. Fails on the parent (「Esc keeps the 干员调配 overlay」), passes here.
- Tests: SP_E2E=1 diy.e2e (the new test) 1 / 1; client-static, i18n, ui/diy*, ui/loadout* 369 / 369; eslint clean.
2026-10-07 18:38:20 +08:00
sganggs 806a047717 feedback6 R21P: every operator fights at full potential (潜能 6) — the owner's decision of 2026-10-07 (GitHub #252, PR #255; no host slider): tools/build-data.mjs OPERATOR_POTENTIAL adds every character_table potentialRanks attribute step (部署费用 / 再部署时间 / ATK / HP / DEF / RES / ASPD, all ADDITION, before rounding) to every chess (normal and elite), 补位 and 自选 unit form and picks talent / trait / module-talent candidates up to requiredPotentialRank 5 (「天赋效果增强」); a summon picks its talent candidates at its owner's potential (their requiredPotentialRank mirrors the owner's — PRTS 凯尔希: Mon3tr 3.5 s / 1400) and takes none of the owner's attribute steps; the 原型干员 stand-ins have no potential ranks (unchanged). 刺玫 413 / 17 → 435 / 15 as in the tournament video, 砾 18 → 16 s, 至简 80 → 70 s. data/chess.json (256 records), backups.json (the 71 owned picks, 8 summons), tokens.json (5 summons) and the data translations en / ja / ko / zh-TW rebuilt — only potential-driven fields (JSON-compared); the 干员调配 局内数值 and detail cards read the records. op-wang.js reads her stock cap 「最多拥有8(+1)枚」 through the potential step (the pattern fell back to 7: she sat at the cap from the start). Docs: DATA §2 / §18 / tokens, PLAYING (detail card), research 03, kits README, shared/diy.js / standIn.js, the 71 自选 kit headers and the kit comments that quoted potential-0 numbers. Tests: test/full-potential.test.js (new, fails on the parent); data.test.js re-derivation adds the potential steps (+ respawnTime); every test that hard-coded a potential-0 number carries the full-potential one (from the data and the test's own formula; no check loosened); golden: moves almost everywhere — roster 49/49, bonds 46/46, fields 22/22, matches 18/18, diy 133/138 (each battle parts on a stat: roster-001 / roster-020 at tick 1 on 角峰's HP (生命上限+250), bond-arcaneShip-high at 4.73 s and diy-001 at 3.70 s on an enemy's HP from a higher ATK; diy-110 / 111 also by 望's cap; the matches through those battles); unchanged: standins 10/10 and diy-135…138 (no potential ranks), diy-012 (twelve 夜莺 never hit, never redeployed) 2026-10-07 18:35:39 +08:00
sganggs a9fe794c3f feedback6: merge R21H — 器物 (频次) enemies take the 攻坚 HP share and not 补给线's (the data's exclusion list names exactly the 14 器物), leader summons take the round's enemy scaling (PR #272 by @CXUtk, re-implemented from the data); tools/golden.mjs keeps R21H's roundMods import and drops uniteStageId (removed by R21B); golden regenerated on the merge — vs the parent exactly R21H's 74 scenarios, vs the branch only the other streams' moves 2026-10-07 18:21:01 +08:00
sganggs 3f918c9f03 feedback6: merge fb6-small-fixes (see its commits) 2026-10-07 18:16:10 +08:00
sganggs 3c36cdff48 feedback6: merge R21D — with both slots full, every consumable (博士投影, 盟约之币 …) opens the replace dialog and destroys only the picked item once its effect succeeds (PRTS 卫戍协议/帮助 「达到上限强行佩戴会改为替换装备…并销毁被指定的装备」; GitHub #263, PR #264 by @SpiritedAwayCN, ported); golden as R21D's commit (10 matches: bots using a consumable on a full operator), the merged tree differs from the branch only in R21B's 联防 scenarios 2026-10-07 18:11:53 +08:00
sganggs e33ee3db8f feedback6 R21H: the round's enemy effects reach the 频次 器物 and the leaders' mid-fight summons (PR #272, 感谢 @CXUtk); golden: 74 moved
器物 (the 14 hit-count keys): hits = data × the spawn's hpMul ÷ supplyHpMul, rounded as before (setHits) — 攻坚装备 / II / III
leave out only 炎佑, 补给线 / 补给线II also exactly these 14 keys (activity_table aceffect_enemy_2 / 2_2 enemy_exclude,
data/effects.json); until now setHits(def.maxHp) overwrote the round's HP (青铜镜 30 hits at every difficulty). build-data
writes enemyScale[r].supplyHp = 1.2^(kHp − kAtk) · extra (the HP-only 补给线II steps and 补给线's 1.08; co-op 终极 R5–R15
only — the rebuild changed config.json alone), gamedata returns it as supplyHpMul and the wave / 悬赏 mods carry it
(waves.js roundMods; a 频次 enemy's death spawn inherits it). Not taken from the PR: its 1.08 / 1.2 back-calculation (it
counted the 补给线II steps as 攻坚装备) and its fractional-HP ceiling. Leader summons (死亡集群's 妖怪, 刺胄之弹,
“裂管之音”/“断弦之音”, 不祥幻影): the boss / hidden field carries the round's scale as flags.enemyScale (bossRounds.js,
golden fields, tools/balance.mjs) and bosses.js summonMods gives them HP / ATK / speed (死亡集群's summon.hp_ratio on
top; 刺胄之弹 / 余音 hit counts follow their HP); the leader's pool stays exempt; 王权号令's invulnerable equipment
unchanged; 炎佑 is an ally token, never scaled (no change). Unchanged and open: the 逐火 / 再生 husk counts (talent
prop_max_hp).

Golden — 器物 counts: roster 37 (002 003 004 005 007–020 022 023 025 026 029–034 036–041 043 045 049: each holds a 器物
extra spawn / 频次 wave / death spawn), bond-soloShip-high, diy 9 (036 040 056 062 068 073 091 114 115), boss-boss_6-solo
(俗心's 小说卷轴 escorts); matches 16 (all but solo-ABYSS-2 and coop2-NORMAL-70-diy: 频次 rounds / 悬赏 and the Final
Assault summons). Summons: boss-boss_1-pair/solo, hidden-boss_8-pair/solo (妖怪, 刺胄之弹), boss-boss_3-pair/solo,
hidden-boss_10-pair/solo (余音), boss-boss_5-pair/solo (不祥幻影). standins unchanged.
2026-10-07 18:05:42 +08:00
sganggs f2f4657e75 feedback6 R21E: .gitignore lists .npm-cache/ — a local npm cache (npm_config_cache=./.npm-cache, npm ci --cache .npm-cache) is never committed by any contributor (GitHub #258); test/gitignore.test.js asks git itself; golden: unchanged 2026-10-07 18:03:54 +08:00
sganggs 8cef750e14 feedback6 R21D: a consume-on-equip item on a full carrier opens the replace dialog and replaces the picked item first (GitHub #263, PR #264); golden: matches 10 of 18 moved (a targeted consumable on a full carrier now destroys the oldest item), the rest unchanged
PRTS 卫戍协议/帮助 §装备: 「达到上限强行佩戴会改为替换装备:指定一件已佩戴装备替换为将要佩戴的装备,并销毁被指定的装备」,
no exception for the 装备时销毁 items. Before: a normal 博士投影 opened no dialog and its kept item pushed the OLDEST
equipped item out (replaceUid ignored); 盟约之币 and the other consumables kept both items.

- server PlayerState.equip (player/items.js): on a full carrier the consume path takes the pick (replaceUid, else the
  oldest) off before onEquip (_takeReplaced), destroys it (onDestroy reason replace) once the effect went through —
  the normal 博士投影 takes the freed slot —, and puts it back at its index when the effect refuses (博士投影 on an elite:
  nothing destroyed)
- client gameLogic equipReplaces: the replace dialog opens for every item on a full carrier, consumables included,
  except an attaching item that completes an item merge and a 博士投影 on an elite; itemAttaches removed
- bots (itemTarget): a consumable whose effect is the same on anyone goes to a carrier with a free slot first (PR #264)
- docs: design/match.md equipment rules, META §2.5, PLAYING 装备 row, research 04

golden (matches only; each traced equip by equip against the parent — the first change of state is a bot's, or the
AI 托管 human seat's, targeted consumable on a full carrier destroying its oldest item): solo-FUNNY-1 (r9 ai_0 随身身份牌),
solo-FUNNY-2 (r9 ai_0 寻呼模块), solo-HARD-2 (r9 ai_0 随身身份牌, r12 简易通讯机), coop2-NORMAL-3 (r9 ai_1 随身身份牌),
coop3-ABYSS-5 (r11 ai_1 随身身份牌), coop4-FUNNY-6 (r4 ai_1 随身身份牌), solo-HARD-9-boosted (r14 ai_0 随身身份牌),
coop2-ABYSS-10-boosted (r13 ai_0 随身身份牌, r14 ai_1 简易通讯机), coop2-NORMAL-14-standins (r9 ai_0 随身身份牌; now won,
14 rounds), coop2-NORMAL-70-diy (r14 h_0 随身身份牌); the bots' free-slot carrier for target-agnostic consumables moves
no scenario by itself; roster / bonds / fields / standins / diy unchanged
2026-10-07 18:03:53 +08:00
sganggs 1e58843fb4 feedback6: merge R21F — enemy flyers hover 1.3 tiles above the road instead of climbing each raised tile (GitHub #277, @FrogThai); enemy animation states from PR #275 by @xcdoge, client part: numbered skill clips, the Stun_1 / Dizzy_* spellings, run cycles (猎狗pro, 深池侦察犬), the rebirths of 巨大的丑东西 and 自在 (data/assets.json regenerated offline: enemies only); golden unchanged 2026-10-07 18:03:47 +08:00
sganggs ce2bd75b49 feedback6 R21E: a refresh runs the 刷新时 特质 of the chess that stood on the board / in the hand when it happened (EffectDispatcher.dispatch takes them before its first handler; each still runs where it stands now, one that left is skipped): the 拉普兰德 贾维 grants on every 6th refresh — or the elite that gift completes — fired in the very refresh that granted her and spent her 「本回合首次主动刷新」; now her first is the next manual refresh; round counters are still not copied on a merge (a merged elite is a new 拉普兰德, §25.13.3); the part of PR #196 0.2.0 lacked, re-implemented; golden: unchanged 2026-10-07 18:02:48 +08:00
sganggs 76a2db7dd0 feedback6 R21F: enemy animation states, the client part of PR #275 by @xcdoge re-implemented — the resolver gives an enemy every numbered skill clip (anims.skills; the caller's first index stays the primary, operators unchanged, 宴's PREFAB_SPINE_ROLES fix kept), the stun spellings Stun_1 and Dizzy_Begin/Loop/End, a separate run role (Run_Begin/Loop/End); the renderer walks an enemy faster than moveSpeed 1 on its Run cycle (猎狗pro, 深池侦察犬 ×2; [ASSUMED] threshold), swaps an enemy's skill clip on a cast event (UnitView.setSkillSlot; a form in force is kept), and plays the 重生 of 巨大的丑东西 (Revive, then the fleeing 大祭司 Idle_2 / Move_2 / Stun_2) and 自在 (Revive_01); data/assets.json regenerated offline: 18 enemies gain anims.skills, 6 anims.run, 3 a stun clip (巨大的丑东西, 斩胄之剑, 破胄之锤), nothing else; the sim's cast slot (a new event) is not part of it; golden: unchanged 2026-10-07 17:59:11 +08:00
sganggs c225559d4d feedback6 R21E: an operator's 凋亡 burst drains the official 技力 — stored charges and the ready charge included (Skill.setSpTotal(spTotal − 1) each second; PRTS 技能 可充能 「当持有者的技力流失时,充能次数也会实时降低」, PRTS 元素 「每秒损失1点技力」): the drain took only the SP towards the next charge, so a full skill kept every charge and a one-charge skill stayed ready through the 15 s; a running timed skill is left alone as before; PR #262 (#261 its closed duplicate), re-implemented; golden: 5 moved, each by a burst on an operator with SP stored (假想敌:管's 凋亡 strikes, the 萨卡兹王庭军's 凋亡 on hit) — fields hidden-boss_10-solo (泥岩 loses its stored charge: casts 1 → 0, the move PR #262 reported; the team's heals and the snapshots after it), boss-boss_3-solo / hidden-boss_10-pair and diy-058 (later snapshots and event counts only, the same results), diy-059 (黍 forms against the 萨卡兹王庭军 waves: fewer casts — 11 → 5, 6 → 2, 9 → 5, 6 → 1 — the battle goes otherwise, kills 39 → 47, leaks 57 → 49) 2026-10-07 17:58:45 +08:00
sganggs 53ccf5d8fe feedback6: merge R21G — battle control messages that arrive while the sim loads or catches up (takeover, forced end, a duplicate start, pool, pause) are held and applied in order (PR #266 by @siruimei07, ported); golden unchanged 2026-10-07 17:57:18 +08:00
sganggs d0d59f4012 feedback6: merge R21C — the player's 自选 picks are listed in their bonds' member lists (dimmed until owned, ✕ when banned; a watched teammate's 自选 row opens its operator); golden unchanged 2026-10-07 17:55:35 +08:00
sganggs c623550f29 feedback6 R21E: the 外勤医疗 Touch casts 恳切福音 on an injured ally inside its 5-2 (ACTIVE_RANGE in touchKit — the larger-range rule the Touch 补位 already runs by its data; the map character's record keeps DEFAULT), and the Touch / 预备干员-医疗 card is an operator card (干员 · 医疗 · 远程位, 特性, skill row with SP / duration, talents from the tokens.json record — it took the summon path: 召唤物, base stats only); 恳切福音 gets a line under its text that the boost is strictly below half (PRTS 修正 of 不高于, what touchGospel does; the operator card of the 补位 Touch too: SKILL_TEXT_NOTES); GitHub #260, PR #278; golden: roster-041 (its strategy Touch heals 8438 → 17072, 19 → 27 heal actions, casts unchanged at 2, the same numbers as PR #278; p1 deaths 7 → 4) and matches coop2-NORMAL-14-standins (h_0 plays 外勤医疗: defeat in round 13 → victory, teamLp 16) — both battles with that strategy's Touch, no other scenario moved 2026-10-07 17:54:17 +08:00
sganggs 26c72a6d95 feedback6: merge R21B — 联防 plays on the round's battlefield again, its terrain, crates, water, devices and runes as in 0.1.4 (the owner's report 「联防阶段地形全消失了」 and knowledge of the official mode, GitHub #244's tournament video; 0.2.0's escaped level was the placeholder road every wave-template level carries); the 联防 battle input is byte-identical to 0.1.4's in 20 cases; 乌尔比安's comment keeps both streams' sentences; golden as R21B's commit (unite-1 / unite-2 back to their stages, coop2-NORMAL-3, coop2-HARD-4, coop3-ABYSS-5, coop4-ABYSS-7, coop2-NORMAL-8-serverrun, coop2-NORMAL-14-standins), the merged tree differs from the branch only in R21A's boss pair fields 2026-10-07 17:54:16 +08:00
sganggs d84b961c80 feedback6 R21G: a b.start is registered as pending before the sim loads, so a b.end (takeover / forced, the first end counts), a second b.start of the same battle, b.pool and the solo pause that arrive during the load or the silent catch-up are applied to it in order once the Battle exists (they were dropped); a superseded authority is still built, the next prep's clear stops the preparation (PR #266 by @siruimei07, re-implemented on the current runner); docs/design/network.md §14 Client runner + b.end; golden: unchanged 2026-10-07 17:51:26 +08:00
sganggs 14df10a5a3 feedback6 R21F: an enemy flyer hovers FLY_HOVER above the road whatever tile it crosses (GitHub #277: one high-ground / forbidden block on its way lifted it 0.42 / 0.3 tiles and dropped it again like a step) — UnitView.sync keeps every enemy at z 0, operators and summons keep their tile top, the flyer's shadow lies on the tile under it (shadowZ) on the 2D and the 3D board; golden: unchanged 2026-10-07 17:49:51 +08:00