diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 4adb0e3f..fb6f449a 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -1464,7 +1464,7 @@ TBD-22-intro ### 22.1 Back-facing operators play their fall (GitHub #25) — `render/units.js _wantsBack / _syncModel / die / revive / _acquireSpine / _dropActor / dieClipDur`, `render/spine.js SpineActor.die / dieClip / deployElapsed` -- **Seen** (public issue #25, "部分干员被击倒后在部署状态时仍有攻击动作"; the screenshot: 跃跃 seen from behind under its redeploy ring, still in its attack pose, while 拉普兰德 beside it lies down; the reporter also saw it on 拉普兰德, 斯卡蒂 and 德克萨斯, "并非稳定出现"). **Real** in 0.1.0 and 0.1.1, drawing only (the sim had the operator down; it never attacked): an operator deployed facing UP is drawn with its Back model (§9, research 07 §5.5), and 131 of the mode's 135 Back skeletons (data/assets.json) have no Die clip — only 薄绿, 玛恩纳, 松果 and 预备干员-重装 have one, while every operator's Front skeleton has one. Not every time: only operators facing UP. +- **Seen** (public issue #25, "部分干员被击倒后在部署状态时仍有攻击动作"; the screenshot: 跃跃 seen from behind under its redeploy ring, still in its attack pose, while 拉普兰德 beside it lies down; the reporter also saw it on 拉普兰德, 异德 (缄默德克萨斯), 斯卡蒂 and 德克萨斯, "并非稳定出现" — all five have a Back skeleton without a Die clip). **Real** in 0.1.0 and 0.1.1, drawing only (the sim had the operator down; it never attacked): an operator deployed facing UP is drawn with its Back model (§9, research 07 §5.5), and 131 of the mode's 135 Back skeletons (data/assets.json) have no Die clip — only 薄绿, 玛恩纳, 松果 and 预备干员-重装 have one, while every operator's Front skeleton has one. Not every time: only operators facing UP. - **Official**: PRTS 卫戍协议/帮助 says a knocked-out operator stays on its tile as a "倒地干员" (§21.24); which model the official client lies it down with is in no source. The project's asset plan already had the rule — research 07 §5.4 "`die`: `Die` (if the Back model lacks it, swap to the Front model for the death animation)", docs/ASSETS.md Roles `die: null` — but the renderer never implemented it. [ASSUMED] the official client does the same (a Back skeleton without a Die clip cannot show a fall), and the four Back skeletons with a Die clip fall with it. - **Cause**: `UnitView._wantsBack()` ignored whether the unit stands; `SpineActor.die()` found no clip, returned 0 and changed nothing, so the attack / skill / idle loop playing at the knock-out went on under the ring for the whole respawn timer. - **Now**: @@ -1475,7 +1475,7 @@ TBD-22-intro - Safety net: until the Front model is there, or when it fails to load, the Back model holds still — `SpineActor.die()` on a skeleton without a Die clip stops whatever looped and holds the first frame of its idle clip (frozen when it has none either), and nothing restarts an attack on a dead actor. The 20 summon / enemy skeletons without a Die clip (e.g. 浊心斯卡蒂's 海嗣) now fade holding still instead of looping [ASSUMED look]. - Unchanged: operators facing RIGHT / DOWN / LEFT, enemies and summons (no Back models in the manifest: `_wantsBack` is false for them), the redeploy ring, the sim. Nothing changes in render/interp.js or battle/runner.js: `die` / `deploy` are state events every client stage keeps (runner `keepsState`, game.js `keepEarly`, interp `isCosmeticEvent`), and b.snap `down` / `fieldMeta` rebuild the view of an operator already down; both now reach the model swap. - [ASSUMED]: the Front model for the fall and the knocked-down pose (above); the Back skeleton's own Die clip when it has one; the held first idle frame of a skeleton without a Die clip. -- Tests: `test/render/issue25-downback.test.js` (fake PIXI with the real manifest and asset helpers: the manifest counts; an UP knock-out mid-attack — the Back model holds still, then the Front model's Die is held under the ring, greyed, every acquire paired with a release; RIGHT unchanged; the redeploy back to the Back model with the deploy clip carried over; the snapshot's DIE anim and a snapshot revive; a death and a redeploy in one frame load nothing; a view built for an operator already down and the 联防 entry lie on the held end; a withdrawn one fades like a RIGHT one; 薄绿 keeps its Back Die clip; the SpineActor safety net; enemies and summons unchanged); `downring`, `feedback1f-down-tile`, `unitview`, `forms`, `test/ui/unitFacing.test.js` unchanged. +- Tests: `test/render/issue25-downback.test.js` (fake PIXI with the real manifest and asset helpers: the manifest counts; the five operators of the report lie down with their Front model's Die; an UP knock-out mid-attack — the Back model holds still, then the Front model's Die is held under the ring, greyed, every acquire paired with a release; RIGHT unchanged; the redeploy back to the Back model with the deploy clip carried over; the snapshot's DIE anim and a snapshot revive; a death and a redeploy in one frame load nothing; a view built for an operator already down and the 联防 entry lie on the held end; a withdrawn one fades like a RIGHT one; 薄绿 keeps its Back Die clip; the SpineActor safety net; enemies and summons unchanged); `downring`, `feedback1f-down-tile`, `unitview`, `forms`, `test/ui/unitFacing.test.js` unchanged. ### 22.2 突袭's idle redeploy lands where it can attack (#51) diff --git a/test/render/issue25-downback.test.js b/test/render/issue25-downback.test.js index dae13b2e..45e8e325 100644 --- a/test/render/issue25-downback.test.js +++ b/test/render/issue25-downback.test.js @@ -115,6 +115,21 @@ describe('an operator facing UP knocked out (Back model without a Die clip)', () assert.deepEqual(held(s), [], 'every acquire paired with a release'); }); + test('every operator of the report (跃跃, 拉普兰德, 异德 = 缄默德克萨斯, 斯卡蒂, 德克萨斯) lies down with its Front model\'s Die', async () => { + for (const id of [CAPER, 'char_140_whitew', 'char_1028_texas2', 'char_263_skadi', 'char_102_texas']) { + assert.equal(dieClipDur(back(id)), 0, `${id}: no Die clip in its Back skeleton`); + const v = await op(id, 'UP'); + v.onAttack(null, 1, 'none'); + v.setDown(DOWN(), 1); + frames(v, 1); + await settle(); + frames(v, 120); + assert.equal(v.actor.entry, front(id), id); + assert.equal(v.actor.current, front(id).anims.die, id); + v.destroy(); + } + }); + test('facing RIGHT it falls with its Front model as before (one model, no swap)', async () => { const s = store(); const v = await op(CAPER, 'RIGHT', s);