Files
thzxx 2369f342dd v0.1.33c: UI/动效打磨——过渡动画/停帧节能/季节对齐/反馈闭环
【动效】Modal 淡入+升起缓动(modalFadeIn/modalRise);LogFeed 入场动画(feedIn);
fx std 档放行刀光(战场核心反馈);任务凯旋补 spark/战殁补 ripple
【节能】DriftLayer 2.6s 转 onAnimationEnd 删除节点(消除空转 setInterval×slice 定期 setState);
fx-canvas 粒子空态停帧(空 rAF 取消+spawnBurst 唤醒)——低端机待机零开销
【季节】fx-canvas 季节色改读 data-season(游戏月驱动,不再用系统月——夏季粒子不再飘冬季色)
【性能】transition all 12 处→具体属性(background-color/color/transform/opacity);
backdrop-filter 保留(视觉主)
【UI 修复】MemberModal 装备栏空守卫(未知名器)+职事按钮 disabled={maxed} 统一;
议和按钮灵石不足 disabled+title(不再静默);事件弹窗 RES_NAME 补新材料/增筑显示中文名;
回卷按钮无存点 disabled+title;速度按钮 sel/gel 类名统一;MarketPanel 删死变量 stock;
debug 后门 __SMOKE__/DEV 门控(生产干净,冒烟仍可用——main 注入 __SMOKE__)
【测试】fx-timesense std 刀光断言更新 5030 全绿
2026-08-23 21:38:02 +08:00

91 lines
2.9 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { seasonOf } from '../src/renderer/game/data/season'
import { yearRing, seasonTint, seasonCssVars, TIDES } from '../src/renderer/game/engine/kernel/timesense'
import { FxGate, FxKind, deltas } from '../src/renderer/ui/fx'
describe('timesense 时间感知矩阵', () => {
it.each([[1, 'spring'], [3, 'spring'], [4, 'summer'], [6, 'summer'], [7, 'autumn'], [9, 'autumn'], [10, 'winter'], [12, 'winter']] as const)(
'月%i → %s',
(m, s) => {
expect(seasonOf(m)).toBe(s)
}
)
it.each([[1, '立春'], [2, '雨水'], [4, '清明'], [12, '冬至'], [12, '小寒']])('节气 24 枚全在', () => {
expect(TIDES.length).toBe(24)
})
it.each([1, 6, 12].map((m) => [m] as const))('年环 %i 月在 [0,1]', (m) => {
const r = yearRing(m)
expect(r).toBeGreaterThanOrEqual(0)
expect(r).toBeLessThan(1)
})
it('季相色均出自金碧墨色系(accent/tint 非空且不为霓虹)', () => {
for (const s of ['spring', 'summer', 'autumn', 'winter'] as const) {
const t = seasonTint(s)
expect(t.accent).toMatch(/^#[0-9a-f]{6}$/i)
expect(t.glow).toMatch(/^\d+, \d+, \d+$/)
expect(t.name.length).toBeGreaterThan(0)
const vars = seasonCssVars(s)
expect(vars['--season-accent']).toBe(t.accent)
}
})
})
describe('FxGate 竞态安全', () => {
function freshGate() {
const emitted: FxKind[] = []
const gate = new FxGate({ emit: (e) => emitted.push(e.kind) })
return { gate, emitted }
}
it('std 模式:全量允许(0.1.33 刀光下放)', () => {
const { gate, emitted } = freshGate()
gate.emit('spark')
gate.emit('mist')
gate.emit('ripple')
gate.emit('pulse')
gate.emit('blade')
expect(emitted).toEqual(['spark', 'mist', 'ripple', 'pulse', 'blade'])
})
it('soft 模式:仅 ripple/pulse/drift', () => {
const { gate, emitted } = freshGate()
gate.setMode('soft')
gate.emit('spark')
gate.emit('ripple')
gate.emit('drift')
expect(emitted).toEqual(['ripple', 'drift'])
})
it('prefers-reduced 时全发抑制(降级)', () => {
const { gate, emitted } = freshGate()
gate.setReduced(true)
gate.emit('spark')
gate.emit('pulse')
expect(emitted).toEqual([])
})
it('重入安全:队列 drain 期间再 emit 不丢不叠(单帧批处理语义)', () => {
const { gate, emitted } = freshGate()
gate.emit('spark')
gate.emit('spark')
gate.emit('spark')
expect(emitted.length).toBe(3)
expect(gate.pending()).toBe(0)
})
it('deltas:差异、净零、NaN 滤除', () => {
const d = deltas({ a: 10, b: 5, c: NaN }, { a: 14, b: 5, d: 3 })
expect(d.a).toBe(4)
expect(d.b).toBeUndefined()
expect(d.c).toBeUndefined() // NaN 原值被过滤
expect(d.d).toBe(3)
})
it('deltas 空对象不产生输出', () => {
expect(Object.keys(deltas({}, {}))).toHaveLength(0)
})
})