feat: complete bark battle playable demo

This commit is contained in:
2026-05-12 14:42:58 +08:00
parent 22810245f5
commit 33c9079d3b
16 changed files with 639 additions and 196 deletions

View File

@@ -4,30 +4,23 @@ import { DEFAULT_BARK_BATTLE_CONFIG } from '../../application/BarkBattleConfig';
import { BarkDetector } from '../BarkDetector';
describe('BarkDetector', () => {
it('超过阈值且持续时长合规时只计为一次有效叫声', () => {
it('每个监测点只检测瞬时响度,超过阈值立即触发', () => {
const detector = new BarkDetector({
threshold: 0.45,
minBarkGapMs: DEFAULT_BARK_BATTLE_CONFIG.minBarkGapMs,
minBarkDurationMs: 90,
maxBarkDurationMs: 900,
});
expect(detector.acceptSample({ atMs: 0, volume: 0.2 })).toEqual([]);
expect(detector.acceptSample({ atMs: 40, volume: 0.72 })).toEqual([]);
expect(detector.acceptSample({ atMs: 150, volume: 0.76 })).toEqual([]);
const events = detector.acceptSample({ atMs: 180, volume: 0.2 });
const events = detector.acceptSample({ atMs: 40, volume: 0.72 });
expect(events).toHaveLength(1);
expect(events[0]).toMatchObject({ side: 'player', peakVolume: 0.76 });
expect(events[0]?.durationMs).toBe(140);
expect(events[0]).toMatchObject({ side: 'player', atMs: 40, peakVolume: 0.72, durationMs: 0 });
});
it('持续噪音不会在每个 tick 无限计数', () => {
it('持续噪音按冷却间隔触发,不需要等待响度回落', () => {
const detector = new BarkDetector({
threshold: 0.4,
minBarkGapMs: 250,
minBarkDurationMs: 80,
maxBarkDurationMs: 600,
});
const allEvents = [
@@ -36,27 +29,55 @@ describe('BarkDetector', () => {
...detector.acceptSample({ atMs: 200, volume: 0.73 }),
...detector.acceptSample({ atMs: 300, volume: 0.75 }),
...detector.acceptSample({ atMs: 500, volume: 0.2 }),
...detector.acceptSample({ atMs: 560, volume: 0.76 }),
];
expect(allEvents).toHaveLength(1);
expect(allEvents.map((event) => event.atMs)).toEqual([0, 300, 560]);
});
it('低于阈值的背景噪音、过短脉冲和冷却内峰值不计数', () => {
it('低于阈值的背景噪音和冷却内峰值不计数,最短持续时长不再参与判断', () => {
const detector = new BarkDetector({
threshold: 0.5,
minBarkGapMs: 300,
minBarkDurationMs: 80,
maxBarkDurationMs: 800,
});
expect(detector.acceptSample({ atMs: 0, volume: 0.48 })).toEqual([]);
detector.acceptSample({ atMs: 20, volume: 0.9 });
expect(detector.acceptSample({ atMs: 60, volume: 0.2 })).toEqual([]);
expect(detector.acceptSample({ atMs: 20, volume: 0.9 })).toHaveLength(1);
expect(detector.acceptSample({ atMs: 60, volume: 0.95 })).toEqual([]);
detector.acceptSample({ atMs: 500, volume: 0.88 });
expect(detector.acceptSample({ atMs: 620, volume: 0.2 })).toHaveLength(1);
expect(detector.acceptSample({ atMs: 320, volume: 0.88 })).toHaveLength(1);
expect(detector.acceptSample({ atMs: 420, volume: 0.2 })).toEqual([]);
});
detector.acceptSample({ atMs: 700, volume: 0.9 });
expect(detector.acceptSample({ atMs: 820, volume: 0.2 })).toEqual([]);
it('支持 100ms 级别间隔的快速连续有效叫声', () => {
const detector = new BarkDetector({
threshold: 0.5,
minBarkGapMs: 100,
});
const allEvents = [
...detector.acceptSample({ atMs: 0, volume: 0.86 }),
...detector.acceptSample({ atMs: 60, volume: 0.9 }),
...detector.acceptSample({ atMs: 120, volume: 0.91 }),
...detector.acceptSample({ atMs: 180, volume: 0.92 }),
...detector.acceptSample({ atMs: 240, volume: 0.93 }),
];
expect(allEvents).toHaveLength(3);
expect(allEvents.map((event) => event.atMs)).toEqual([0, 120, 240]);
});
it('非有限音量会归零,超过 1 的音量会夹到 1', () => {
const detector = new BarkDetector({
threshold: 0.5,
minBarkGapMs: 100,
});
expect(detector.acceptSample({ atMs: 0, volume: Number.NaN })).toEqual([]);
expect(detector.acceptSample({ atMs: 120, volume: Number.POSITIVE_INFINITY })).toEqual([]);
const events = detector.acceptSample({ atMs: 240, volume: 2 });
expect(events).toHaveLength(1);
expect(events[0]?.peakVolume).toBe(1);
});
});