- 표현 엔진: 채널 34개(mouthRound 추가), 표정 28종·강도 곡선·반응 클립 26종·지문 cue, 미세표정 누설 - 발화층: 한글 자모 비짐 9종, TTS 오디오 선분석 정렬, 60ms 앞당김·모음 간 비닫힘 - 발화 동반층: 억양·강세에 맞춘 고개 표류·끄덕임·질문 갸웃·들숨·눈썹 박·시선 회피·쉼 깜빡임 - P1 서연 리노컷 렌더러: 원화 픽셀 입술 띠 변형, 하관 띠 변형(턱·코 들썩), 볼 불룩, 작은 크기 선 보정, 모티프·배경 색면 - /dev/avatar-lab, check:avatar-presets·check:avatar-lipsync, avatar-lab E2E 19개
47 lines
1.4 KiB
TypeScript
47 lines
1.4 KiB
TypeScript
/* =====================================================================
|
|
아바타 v3 엔진 전용 시드 난수 — mulberry32.
|
|
엔진은 순수 함수여야 하므로(결정문 §3) Math.random을 직접 쓰지 않고
|
|
이 모듈이 주입하는 rng를 통해서만 난수를 소비한다.
|
|
===================================================================== */
|
|
|
|
export interface Rng {
|
|
/** [0,1) 균등 분포. */
|
|
next(): number;
|
|
/** [a,b) 균등 분포. */
|
|
range(a: number, b: number): number;
|
|
/** 배열에서 균등하게 하나 고른다. */
|
|
pick<T>(arr: readonly T[]): T;
|
|
}
|
|
|
|
export function createRng(seed: number): Rng {
|
|
let state = seed >>> 0;
|
|
|
|
function next(): number {
|
|
state = (state + 0x6d2b79f5) | 0;
|
|
let t = state;
|
|
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
}
|
|
|
|
return {
|
|
next,
|
|
range(a: number, b: number): number {
|
|
return a + (b - a) * next();
|
|
},
|
|
pick<T>(arr: readonly T[]): T {
|
|
const idx = Math.min(arr.length - 1, Math.floor(next() * arr.length));
|
|
return arr[idx];
|
|
},
|
|
};
|
|
}
|
|
|
|
/** FNV-1a 32bit 문자열 해시. */
|
|
export function hashString(text: string): number {
|
|
let hash = 0x811c9dc5;
|
|
for (let i = 0; i < text.length; i++) {
|
|
hash ^= text.charCodeAt(i);
|
|
hash = Math.imul(hash, 0x01000193);
|
|
}
|
|
return hash >>> 0;
|
|
}
|