import { describe, expect, it } from 'vitest' import { seasonOf } from '../src/renderer/game/data/season' import { tideOf, yearRing, seasonTint, seasonCssVars, TIDES } from '../src/renderer/game/core/timesense' import { FxGate, FxKind } from '../src/renderer/game/core/fxqueue' 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, 2, 4, 6, 9, 11, 12].map((m) => [m] as const))('月%i 两个节气均有效', (m) => { expect(TIDES).toContain(tideOf(m, 0)) expect(TIDES).toContain(tideOf(m, 1)) }) 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 模式:spark/mist/ripple/pulse 允许,blade 需 full', () => { 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']) expect(emitted).not.toContain('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 = FxGate.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(FxGate.deltas({}, {}))).toHaveLength(0) }) })