fix(skill): change Enchant cast overlay to authentic one-shot playback (Fixes #438)
This commit is contained in:
parent
f12fd5674a
commit
ebea702572
|
|
@ -2203,7 +2203,7 @@ export function castSkill(
|
|||
player.fireMasterySlvl = fireMasterySlvl
|
||||
player.isSorceress = true
|
||||
|
||||
engine.spawnOverlay('enchant', { x: player.x, y: player.y }, 'player', { loop: true, isStateOverlay: true })
|
||||
engine.spawnOverlay('enchant', { x: player.x, y: player.y }, 'player', { loop: false })
|
||||
|
||||
player.cooldown = 0
|
||||
engine.metrics.casts += 1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* Issue #438: Enchant (#52) One-Shot Visual Playback Verification Suite
|
||||
*
|
||||
* 1.13c Ground Truth Invariants:
|
||||
* 1. One-Shot Visual: Enchant cast overlay (`enchant`, FireEnchant.dc6) plays 17 frames once (`loop: false`).
|
||||
* 2. Natural Dissolution: After 17 animation frames (~27 logic ticks), the overlay naturally expires and
|
||||
* is removed from `engine.overlays`.
|
||||
* 3. Buff Persistence: Player's enchant buff (`player.enchantTicks`, `player.enchantDamage`,
|
||||
* `player.enchantAttackRatingPercent`) remains active for its full duration (e.g. 3600+ ticks)
|
||||
* even after the visual overlay has expired.
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { GameEngine, type EngineInput } from '../../../src/game/engine.ts'
|
||||
import { castSkill } from '../../../src/scene/act-scene.ts'
|
||||
import { DEMO_SKILLS } from '../../../src/game/demo-data.ts'
|
||||
|
||||
const DEFAULT_INPUT: EngineInput = {
|
||||
movement: { x: 0, y: 0 },
|
||||
attacking: false,
|
||||
pickingUp: false,
|
||||
talking: false,
|
||||
digits: [],
|
||||
saving: false,
|
||||
loading: false,
|
||||
}
|
||||
|
||||
describe('Issue #438: Enchant (#52) One-Shot Visual Overlay', () => {
|
||||
it('spawns enchant overlay with loop: false which expires after 17 frames while buff remains active', () => {
|
||||
const engine = new GameEngine(
|
||||
{ widthPx: 1000, heightPx: 1000, overlap: () => 0 },
|
||||
{ spawn: { x: 300, y: 300 }, stats: [], questDefs: [], npcDefs: [], skills: DEMO_SKILLS } as any,
|
||||
)
|
||||
const player = engine.world.player
|
||||
player.x = 300
|
||||
player.y = 300
|
||||
player.mana = 500
|
||||
player.cooldown = 0
|
||||
|
||||
const statusEl = { textContent: '' } as HTMLElement
|
||||
const dummyRuntime = {
|
||||
walkable: { isWalkable: () => true },
|
||||
collision: { isWalkable: () => true },
|
||||
} as any
|
||||
|
||||
const castOk = castSkill(52, 300, 300, {
|
||||
engine,
|
||||
runtime: dummyRuntime,
|
||||
hudManager: null,
|
||||
status: statusEl,
|
||||
})
|
||||
expect(castOk).toBe(true)
|
||||
|
||||
// Verify buff is active on player
|
||||
expect(player.enchantTicks).toBeGreaterThan(1000)
|
||||
expect(player.enchantDamage).toBeDefined()
|
||||
expect(player.enchantAttackRatingPercent).toBeDefined()
|
||||
|
||||
// Verify overlay was spawned with loop: false
|
||||
const enchantOverlay = engine.overlays.find(ov => ov.overlayName === 'enchant')
|
||||
expect(enchantOverlay).toBeDefined()
|
||||
expect(enchantOverlay!.loop).toBe(false)
|
||||
expect(enchantOverlay!.maxFrames).toBe(17)
|
||||
|
||||
// Tick engine for 20 ticks: overlay is still playing (< 17 frames)
|
||||
for (let t = 0; t < 20; t++) {
|
||||
engine.tick(DEFAULT_INPUT)
|
||||
}
|
||||
expect(enchantOverlay!.expired).toBe(false)
|
||||
expect(engine.overlays.some(ov => ov.overlayName === 'enchant')).toBe(true)
|
||||
|
||||
// Tick engine for another 15 ticks (total 35 ticks > 27 ticks required for 17 frames at 16/25 frame rate)
|
||||
for (let t = 0; t < 15; t++) {
|
||||
engine.tick(DEFAULT_INPUT)
|
||||
}
|
||||
|
||||
// Verify overlay is now naturally expired and cleaned up from engine.overlays
|
||||
expect(enchantOverlay!.expired).toBe(true)
|
||||
const activeEnchantOverlay = engine.overlays.find(ov => ov.overlayName === 'enchant' && !ov.expired)
|
||||
expect(activeEnchantOverlay).toBeUndefined()
|
||||
|
||||
// Verify player buff is STILL ACTIVE and unexpired!
|
||||
expect(player.enchantTicks).toBeGreaterThan(1000)
|
||||
expect(player.enchantDamage).toBeDefined()
|
||||
expect(player.enchantAttackRatingPercent).toBeDefined()
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue