본문으로 건너뛰기

두 런타임의 구조

clover 문서 목록으로


같은 데이터셋으로 웹과 유니티에 각각 온전한 게임을 만듭니다. 두 게임이 같은 규칙으로 도는지를 리플레이로 대조하고, 그 대조가 이 샘플의 판정 기준입니다.

층 3개

데이터 (테이블 40개)

┌──────────┴──────────┐
│ │
코어 코어
(TypeScript) (C#)
│ │
연출 (PixiJS) 연출 (Unity)
무엇규칙
데이터생성된 읽기 코드와 .tcb · .bytes양쪽이 같은 시트에서 나옵니다
코어규칙 엔진. 상태와 이벤트화면을 참조하지 않습니다. 입력은 액션, 출력은 상태와 이벤트 배열
연출화면 · 소리 · 입력코어를 호출하고 이벤트를 받습니다. 코어를 바꾸지 않습니다

코어가 화면을 모르는 것이 두 가지를 가능하게 합니다 — 화면 없이 리플레이를 돌리는 것과, 같은 코어를 두 연출이 쓰는 것입니다.

코어의 경계

core.apply(action) → { state, events[] }
core.snapshotHash() → string

이것이 전부입니다. 코어는 시간을 모르고 프레임을 모르고 애니메이션을 모릅니다. 연출이 이벤트 배열을 자기 속도로 재생합니다.

snapshotHash()가 대조의 도구입니다 — 액션 하나마다 해시를 내면 갈라진 지점을 이분해서 찾습니다.

무엇무엇으로
렌더링PixiJS v8 — WebGL2, 없으면 WebGL. 커스텀 필터로 에디션 셰이더
빌드Vite + TypeScript
데이터 읽기생성된 typescript 산출물 + lib/ts/tabbit/tcb_reader.ts
소리WebAudio 직접. 음높이를 바꾸므로 재생기가 아니라 그래프가 필요합니다
세이브localStorage
헤드리스같은 코어를 Node에서 돌립니다. 렌더링을 임포트하지 않습니다

tcb_reader.tsUint8Array 위에서 도므로 브라우저에서 그대로 돕니다 — 이 샘플이 그 사실의 첫 사용처입니다.

유니티

무엇무엇으로
렌더링URP 2D. 에디션 셰이더는 웹 GLSL과 같은 수식의 HLSL
데이터 읽기생성된 csharp 산출물 + StreamingAssets.bytes
세이브Application.persistentDataPath
헤드리스-batchmode 로 리플레이를 돌립니다

코어를 두 번 쓰는 이유

한 번 쓰고 옮기는 방법이 둘 있었습니다 — C#을 TypeScript로 트랜스파일하는 것과, 코어를 WebAssembly로 만들어 양쪽에서 쓰는 것입니다. 둘 다 하지 않습니다.

방법왜 아닌가
트랜스파일생성된 코드는 사람이 읽지 않습니다. 그 게임은 「유니티 게임 하나와 그 이식본」이고, 두 플랫폼에서 만들었다는 증거가 되지 않습니다
공용 WebAssembly규칙이 한 곳에 있으므로 갈라질 수 없습니다. 갈라질 수 없으면 대조가 아무것도 증명하지 않습니다

두 번 쓰는 것이 비용입니다. 그 비용이 이 샘플이 답하려는 질문의 값입니다 — 독립적으로 구현한 둘이 같은 데이터로 같은 답을 내는가.

그래서 규격이 정본이고 구현이 둘입니다. 효과 VM상태를 보고 각각 씁니다. 한쪽을 보고 다른 쪽을 옮기지 않습니다 — 그러면 같은 오해가 두 번 들어가고 대조가 통과합니다.

대조 게이트

# 웹
node samples/clover/web/dist/headless.js --replay <파일> --out <결과>

# 유니티
Unity.exe -batchmode -quit -projectPath samples/clover/unity \
-executeMethod Clover.Check.CloverReplayCheck.RunFromCommandLine -logFile -

# 비교
python samples/clover/design-data/tools/verify.py

리플레이 20개를 준비합니다.

갈래무엇을 덮는가
완주 5개덱을 바꿔 안테 8까지. 서로 다른 조커 조합
효과 집중 10개VM 변종마다 하나 이상 지나갑니다. Custom 은 전부
경계 3개old_bones 의 25% · 빚 -$20 · 조커 슬롯 0
패배 2개보스에서 지는 것과 첫 안테에서 지는 것

리플레이는 손으로 만들지 않습니다. 헤드리스 러너에 무작위 액션을 먹여 완주하는 것을 골라내고, 그것을 파일로 굽습니다. 그러면 리플레이가 코어를 따라 늘어납니다.

실패의 증상과 원인

증상무엇이 잘못되었는가
해시가 마지막에만 다릅니다누적 오차. 만분율 정수 규격을 어긴 곳
해시가 특정 액션에서 갈라집니다그 액션이 쓰는 스트림의 난수 파생
한쪽만 예외로 끝납니다그쪽 구현의 결함. 규격이 모호했을 가능성이 높습니다
양쪽이 같고 둘 다 원작과 다릅니다데이터의 결함입니다. 대조표로 돌아갑니다

마지막 줄이 중요합니다 — 두 구현이 같다는 것은 데이터가 맞다는 뜻이 아닙니다. 그래서 대조표가 따로 있습니다.


EOD