156 lines
5.9 KiB
TypeScript
156 lines
5.9 KiB
TypeScript
import { describe, expect, test } from 'vitest'
|
|
import {
|
|
DEFAULT_ANIMATED_TILE_FRAME_DURATION_MS,
|
|
frameDurationMsFromAnimSpeed,
|
|
updateAnimatableTile,
|
|
updateAnimatableTiles,
|
|
} from '../src/game/animated-tiles.ts'
|
|
import type { AnimatableTile } from '../src/game/animated-tiles.ts'
|
|
import { MAZE_LEVEL_TYPE_PROFILES } from '../src/game/maze.ts'
|
|
|
|
describe('Animated Tile Speed from LvlPrest.txt Animate Column (Issue #85)', () => {
|
|
describe('frameDurationMsFromAnimSpeed formula', () => {
|
|
test('defaults to 128ms for 0, negative, undefined, or missing speed', () => {
|
|
// In 1.13c: if (nAnimationSpeed == 0) nAnimationSpeed = 80;
|
|
// At 25Hz tick rate and 80/256 frames per tick:
|
|
// (256 / 80) * (1000 / 25) = 3.2 * 40 = 128 ms
|
|
expect(frameDurationMsFromAnimSpeed(undefined)).toBe(128)
|
|
expect(frameDurationMsFromAnimSpeed(0)).toBe(128)
|
|
expect(frameDurationMsFromAnimSpeed(-1)).toBe(128)
|
|
expect(frameDurationMsFromAnimSpeed(-80)).toBe(128)
|
|
expect(frameDurationMsFromAnimSpeed(Number.NaN)).toBe(128)
|
|
})
|
|
|
|
test('calculates 128ms for standard speed 80', () => {
|
|
expect(frameDurationMsFromAnimSpeed(80)).toBe(128)
|
|
expect(frameDurationMsFromAnimSpeed(80)).toBe(DEFAULT_ANIMATED_TILE_FRAME_DURATION_MS)
|
|
})
|
|
|
|
test('calculates 10240ms for Act 4 lava speed 1', () => {
|
|
// Speed 1: (256 / 1) * (1000 / 25) = 256 * 40 = 10240 ms per frame (~0.09765 FPS)
|
|
expect(frameDurationMsFromAnimSpeed(1)).toBe(10240)
|
|
})
|
|
|
|
test('calculates correct durations for various speeds', () => {
|
|
// Speed 160: twice as fast as default -> 64ms per frame
|
|
expect(frameDurationMsFromAnimSpeed(160)).toBe(64)
|
|
|
|
// Speed 256: 1 frame per tick (25 FPS) -> 40ms per frame
|
|
expect(frameDurationMsFromAnimSpeed(256)).toBe(40)
|
|
|
|
// Speed 40: half speed of default -> 256ms per frame
|
|
expect(frameDurationMsFromAnimSpeed(40)).toBe(256)
|
|
|
|
// Speed 20: quarter speed -> 512ms per frame
|
|
expect(frameDurationMsFromAnimSpeed(20)).toBe(512)
|
|
})
|
|
})
|
|
|
|
describe('updateAnimatableTiles with per-level frame duration', () => {
|
|
const createTile = (frameCount: number): AnimatableTile => {
|
|
const frames = Array.from({ length: frameCount }, (_, i) => ({
|
|
frame: { x: i * 160, y: 0, width: 160, height: 80 },
|
|
page: 0,
|
|
}))
|
|
return {
|
|
frame: frames[0]!.frame,
|
|
page: frames[0]!.page,
|
|
animatedFrames: frames,
|
|
currentFrameIndex: 0,
|
|
}
|
|
}
|
|
|
|
test('advances at 10240ms per frame when frameDurationMs is 10240 (Act 4 Lava)', () => {
|
|
const tile = createTile(4)
|
|
const durationMs = frameDurationMsFromAnimSpeed(1) // 10240ms
|
|
expect(durationMs).toBe(10240)
|
|
|
|
// At 0ms -> frame 0
|
|
updateAnimatableTile(tile, 0, 'time', durationMs)
|
|
expect(tile.currentFrameIndex).toBe(0)
|
|
|
|
// At 5000ms -> still frame 0 (not reached 10240ms yet)
|
|
updateAnimatableTile(tile, 5000, 'time', durationMs)
|
|
expect(tile.currentFrameIndex).toBe(0)
|
|
|
|
// At 10239ms -> still frame 0
|
|
updateAnimatableTile(tile, 10239, 'time', durationMs)
|
|
expect(tile.currentFrameIndex).toBe(0)
|
|
|
|
// At 10240ms -> advances to frame 1
|
|
updateAnimatableTile(tile, 10240, 'time', durationMs)
|
|
expect(tile.currentFrameIndex).toBe(1)
|
|
|
|
// At 20480ms -> advances to frame 2
|
|
updateAnimatableTile(tile, 20480, 'time', durationMs)
|
|
expect(tile.currentFrameIndex).toBe(2)
|
|
|
|
// At 40960ms -> wraps back to frame 0
|
|
updateAnimatableTile(tile, 40960, 'time', durationMs)
|
|
expect(tile.currentFrameIndex).toBe(0)
|
|
})
|
|
|
|
test('advances at 64ms per frame when frameDurationMs is 64 (speed 160)', () => {
|
|
const tile = createTile(4)
|
|
const durationMs = frameDurationMsFromAnimSpeed(160) // 64ms
|
|
expect(durationMs).toBe(64)
|
|
|
|
updateAnimatableTile(tile, 0, 'time', durationMs)
|
|
expect(tile.currentFrameIndex).toBe(0)
|
|
|
|
updateAnimatableTile(tile, 63, 'time', durationMs)
|
|
expect(tile.currentFrameIndex).toBe(0)
|
|
|
|
updateAnimatableTile(tile, 64, 'time', durationMs)
|
|
expect(tile.currentFrameIndex).toBe(1)
|
|
|
|
updateAnimatableTile(tile, 128, 'time', durationMs)
|
|
expect(tile.currentFrameIndex).toBe(2)
|
|
})
|
|
|
|
test('updateAnimatableTiles propagates custom frameDurationMs to all tiles', () => {
|
|
const tiles = [createTile(3), createTile(4), createTile(5)]
|
|
const lavaDuration = frameDurationMsFromAnimSpeed(1) // 10240ms
|
|
|
|
// At 10240ms, all should be on frame 1
|
|
updateAnimatableTiles(tiles, 10240, 'time', lavaDuration)
|
|
expect(tiles[0]!.currentFrameIndex).toBe(1)
|
|
expect(tiles[1]!.currentFrameIndex).toBe(1)
|
|
expect(tiles[2]!.currentFrameIndex).toBe(1)
|
|
|
|
// At 30720ms:
|
|
// tile 0 (3 frames): 30720 / 10240 = 3 -> 3 % 3 = frame 0
|
|
// tile 1 (4 frames): 3 % 4 = frame 3
|
|
// tile 2 (5 frames): 3 % 5 = frame 3
|
|
updateAnimatableTiles(tiles, 30720, 'time', lavaDuration)
|
|
expect(tiles[0]!.currentFrameIndex).toBe(0)
|
|
expect(tiles[1]!.currentFrameIndex).toBe(3)
|
|
expect(tiles[2]!.currentFrameIndex).toBe(3)
|
|
})
|
|
|
|
test('updateAnimatableTiles falls back to DEFAULT_ANIMATED_TILE_FRAME_DURATION_MS (128ms) when omitted', () => {
|
|
const tile = createTile(4)
|
|
|
|
updateAnimatableTiles([tile], 0, 'time')
|
|
expect(tile.currentFrameIndex).toBe(0)
|
|
|
|
updateAnimatableTiles([tile], 127, 'time')
|
|
expect(tile.currentFrameIndex).toBe(0)
|
|
|
|
updateAnimatableTiles([tile], 128, 'time')
|
|
expect(tile.currentFrameIndex).toBe(1)
|
|
|
|
updateAnimatableTiles([tile], 256, 'time')
|
|
expect(tile.currentFrameIndex).toBe(2)
|
|
})
|
|
})
|
|
|
|
describe('Act 4 Lava Profile configuration', () => {
|
|
test('Act 4 - Lava profile specifies animSpeed: 1', () => {
|
|
const lavaProfile = MAZE_LEVEL_TYPE_PROFILES.find(p => p.levelTypeName === 'Act 4 - Lava')
|
|
expect(lavaProfile).toBeDefined()
|
|
expect(lavaProfile?.animSpeed).toBe(1)
|
|
})
|
|
})
|
|
})
|