From dd23572b4a62a3f32b1f187bc9ba9cacb23e4312 Mon Sep 17 00:00:00 2001 From: sganggs Date: Thu, 8 Oct 2026 19:10:15 +0800 Subject: [PATCH] =?UTF-8?q?feedback7=20R22-RENDER:=20=E7=90=B3=E7=90=85?= =?UTF-8?q?=E8=AF=97=E6=80=80=E9=9B=85's=20=E9=A6=99=E6=A7=9F=E7=82=B8?= =?UTF-8?q?=E5=BC=B9=20never=20shows=20an=20HP=20bar=20=E2=80=94=20PRTS=20?= =?UTF-8?q?=E5=A4=87=E6=B3=A8=20"=E5=8D=B3=E4=BD=BF=E8=87=AA=E8=BA=AB?= =?UTF-8?q?=E7=94=9F=E5=91=BD=E5=80=BC=E6=9C=AA=E6=BB=A1=EF=BC=8C=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E4=B8=8B=E6=96=B9=E4=B9=9F=E4=B8=8D=E4=BC=9A=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E7=94=9F=E5=91=BD=E5=80=BC=E6=A7=BD"=20(owner=20item?= =?UTF-8?q?=2011);=20golden:=20unchanged?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The owner reported 「琳琅诗怀雅的2技能的香槟会掉血,会被治疗」. Allies always draw an HP bar (render/units.js _updateHud), so the bomb's every HP change showed. PRTS 香槟炸弹 (实体类型 装置, 1000 HP) says no HP bar is shown even below full HP; nothing there makes it invulnerable or unhealable. Its bar is now hidden at any HP (NO_HP_BAR). Its HP, the damage it takes (活性源石 hurts what stands on it — 70 true damage a second) and healing are left as they are: no invulnerability added; whether it should be unhealable is the owner's decision (no written source says so). Tests: test/render/champagne-hpbar.test.js (new, 2: no bar at full HP, hurt or healed; another summon keeps its bar) 2/2; render/unitview 35/35. --- public/js/render/units.js | 8 +++++- test/render/champagne-hpbar.test.js | 43 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 test/render/champagne-hpbar.test.js diff --git a/public/js/render/units.js b/public/js/render/units.js index a7de69ad..206c8278 100644 --- a/public/js/render/units.js +++ b/public/js/render/units.js @@ -117,6 +117,12 @@ const nowMs = () => (globalThis.performance ? globalThis.performance.now() : Dat * 无头像; data/assets.json has no avatar or model for it, so the token fallback showed 圣聆初雪's own face) — the marker of a * frozen gate. */ const ICE_TOKENS = new Set(['token_10058_sbell2_icetgt']); +/** + * Units that never show an HP bar: 琳琅诗怀雅's 香槟炸弹 — PRTS 香槟炸弹 备注 "即使自身生命值未满,模型下方也不会显示生命值槽" + * (the owner's report of 2026-10-08, 「香槟会掉血,会被治疗」: the bar showed every change). Only the bar: its HP, the + * damage it takes (活性源石 hurts what stands on it) and healing are unchanged — no source makes it invulnerable. + */ +const NO_HP_BAR = new Set(['token_10031_swire2_gdtrap']); const ICE_FRAME = 0x9fe6ff; /** How long a view waits for its avatar before showing the image-less placeholder diamond. */ const PIC_WAIT_MS = 400; @@ -1249,7 +1255,7 @@ export class UnitView { const prep = this.prep; const showBars = !prep && this.alive && this.info.kind !== 'item'; const damaged = this.hp < this.maxHp - 0.5; - const showHp = showBars && (!this.isEnemy || damaged || this.isBoss); + const showHp = showBars && (!this.isEnemy || damaged || this.isBoss) && !NO_HP_BAR.has(this.info.defId); const bw = clamp(s * (this.isBoss ? UNIT.bossBarWidth : UNIT.barWidth), 24, this.isBoss ? 260 : 96); const bh = clamp(s * (this.isBoss ? 0.12 : 0.075), 3, this.isBoss ? 12 : 7); // a knocked-down operator's HUD is its redeploy ring alone, drawn at full strength over the greyed model diff --git a/test/render/champagne-hpbar.test.js b/test/render/champagne-hpbar.test.js new file mode 100644 index 00000000..d05548d3 --- /dev/null +++ b/test/render/champagne-hpbar.test.js @@ -0,0 +1,43 @@ +// test/render/champagne-hpbar.test.js — 琳琅诗怀雅's 香槟炸弹 (token_10031_swire2_gdtrap) never shows an HP bar: PRTS 香槟炸弹 +// 备注 "即使自身生命值未满,模型下方也不会显示生命值槽" (the owner's report of 2026-10-08, 「香槟会掉血,会被治疗」). Allies +// otherwise always draw one (render/units.js _updateHud). Only the bar changes; its HP and healing stay as they are. +// Run: node --test test/render/champagne-hpbar.test.js + +import { test, before, after } from 'node:test'; +import assert from 'node:assert/strict'; +import { installFakePixi, fakeViewCtx } from './fakepixi.js'; +import { presetCamera } from '../../public/js/render/projection.js'; + +let fake, UnitView; +before(async () => { + fake = installFakePixi(); + ({ UnitView } = await import('../../public/js/render/units.js')); +}); +after(() => fake.restore()); + +const cam = () => presetCamera('normal', { width: 1280, height: 720 }); +const view = (defId, id) => new UnitView(fakeViewCtx(fake.P, { cam }), { id, side: 'ally', kind: 'summon', defId, tier: 1, x: 11, y: 10, maxHp: 1000, hp: 1000 }, {}); +const frames = (v) => { for (let i = 0; i < 2; i++) v.update(1 / 60, cam(), i / 60); }; +const bar = (v) => [v.hpBg.visible, v.hpFill.visible, v.hpGhost.visible]; + +test('香槟炸弹 shows no HP bar at full HP, below it, or healed back up', () => { + const bomb = view('token_10031_swire2_gdtrap', 1); + frames(bomb); + assert.deepEqual(bar(bomb), [false, false, false], 'full HP'); + bomb.sync({ x: 11, y: 10, hp: 860, maxHp: 1000, sp: 0, spMax: 0, flags: 0, anim: 0 }, 1); + frames(bomb); + assert.equal(bomb.hp, 860, 'the view keeps its HP'); + assert.deepEqual(bar(bomb), [false, false, false], 'hurt (活性源石: 70 true damage a second)'); + bomb.sync({ x: 11, y: 10, hp: 1000, maxHp: 1000, sp: 0, spMax: 0, flags: 0, anim: 0 }, 2); + frames(bomb); + assert.deepEqual(bar(bomb), [false, false, false], 'healed'); +}); + +test('another summon still draws its HP bar, full or hurt', () => { + const other = view('token_10000_silent_healrb', 2); + frames(other); + assert.deepEqual(bar(other), [true, true, true]); + other.sync({ x: 11, y: 10, hp: 400, maxHp: 1000, sp: 0, spMax: 0, flags: 0, anim: 0 }, 1); + frames(other); + assert.deepEqual(bar(other), [true, true, true]); +});