Caputchin
마켓플레이스 게임 개발

리플레이 계약

Markdown으로 보기

리플레이 계약은 재생 가능한 게임이 플랫폼과 합의하는 하나의 필수 표면입니다. 그것은 @caputchin/replay-contract로 공개됩니다: 일부러 작고 의존성 없는 패키지(봉인된 리플레이 아이솔레이트에 로드되니, 아무것도 끌어들이지 않습니다). 게임에 대한 그 밖의 모든 것, 엔진, 렌더러, 트레이스 형식은 당신의 것입니다.

적합한 run을 내는 게임은 재생 가능하고 따라서 사이트 키를 게이트할 수 있습니다; 그것 없는 게임은 여전히 임베드 가능하지만, 오직 UX로만입니다(재생 불가로 보입니다). 그 구분은 마켓플레이스에 게임 출시하기를 보세요.

run 함수

재생 가능한 게임은 run이라는 함수 하나를 export하는 JS 또는 WASM 모듈을 냅니다:

run(seed, config, trace) -> { passed, score, durationMs }
type RunFn<C = unknown> = (
  seed: Seed,
  config: C | null,
  trace: Uint8Array | string,
) => Verdict | Promise<Verdict>
인자소스의미
seed서버라운드별 시드. 라운드의 서버 소유 설정.
config서버게임플레이 구성, nullable이고 불투명: 플랫폼은 그것을 결코 검사하지 않으며, 리플레이 시 서버 측에서 다시 해소되지 결코 클라이언트가 주장하지 않음. null은 "run 자체의 기본값을 쓰라"는 뜻. MVP에서는 서버가 null을 넘김; 사이트별 구성은 미뤄진 단계.
trace클라이언트플레이어의 클라이언트가 낸, 이 함수만이 해석하는 불투명 블롭. 플랫폼은 그것을 결코 파싱하거나 타입 지정하지 않음; 그것은 날 바이트나 문자열이며, 크기 상한과 아이솔레이트 CPU 제한으로만 한정됨.

인자 순서가 신뢰 모델을 인코딩합니다: seed와 config는 라운드의 서버 설정입니다; trace는 플레이어의 입력입니다. 게이트에 영향을 주는 값(통과 임계값, 목숨)은 config에서 읽으면 안전하지만 클라이언트 trace에서 읽으면 우회가 됩니다.

모듈은 함수를 run이라는 이름(상수 RUN_EXPORT_NAME)으로 export해야 합니다. 리플레이 호스트가 그것을 호출하고 결과를 기다립니다(WASM 인스턴스화는 첫 호출에서 비동기입니다).

시드

type Seed = readonly [number, number, number, number]   // 128 bits, 4 u32 words, MSW first

시드는 SHA-256(sessionId : gameId : roundIndex)의 하위 128비트로, 네 부호 없는 32비트 워드로, 최상위 워드 먼저 실립니다. 패키지의 deriveSeed(sessionId, gameId, roundIndex)가 그것을 계산합니다; 서버는 라운드를 발급할 때와 리플레이할 때 다시, 둘 다 그것을 도출하니, 그것은 결코 신뢰된 클라이언트 입력으로 와이어를 타지 않습니다.

결정적으로, 시드는 트레이스를 한 세션, 게임, 라운드에 묶습니다: 다른 시드 아래에서 외부 또는 이전 트레이스를 리플레이하면 passed: false를 냅니다. 그 묶음이 트레이스 주입과 리플레이 공격을 방어하는 방법이니, 라이브 플레이와 서버 리플레이가 일치하도록 모든 무작위성을 그것에서 시드해야 합니다.

판정

interface Verdict {
  readonly passed: boolean      // drives the verification decision
  readonly score: number        // game-defined, any finite number
  readonly durationMs: number   // finite, non-negative
}

passed는 captcha 결정을 모는 유일한 필드입니다; score와 durationMs는 발급된 토큰(그리고 미래의 점수판)에 실립니다. 패키지의 parseVerdict(value)는 신뢰되지 않는 반환 값을 검증하고 잘못된 결과를 통과 라운드가 아니라 거부된 라운드로 다룹니다, 그래서 던지거나 쓰레기를 반환하는 run은 닫힌 채 실패합니다.

결정론이 요건 전부입니다

계약의 하나의 단단한 규칙: run은 두 런타임에 걸쳐 순수하고 결정론적이어야 합니다, 플레이어의 브라우저와 서버 아이솔레이트. 동일한 (seed, config, trace)는 둘 다에서 동일한 판정을 내야 합니다. 이것을 깨는 흔한 방법들, 모두 공개 자체 확인이 run-not-conforming으로 잡습니다:

  • Date.now(), performance.now(), Math.random(), 또는 다른 비결정론적 글로벌 읽기;
  • 리플레이 아이솔레이트가 제공하지 않는 외부 상태(DOM, 네트워크, 스토리지) 읽기;
  • 런타임 사이에 다른 부동소수점 연산에 의존하기.

결정론을 어떻게 달성하는지(고정소수점, WASM 스펙 부동소수점, 또는 IEEE-754에 중립화 심)는 자유입니다; 플랫폼은 리플레이만 호스팅합니다. 엔진 키트는 직접 굴리고 싶지 않으면 기성 결정론적 프리미티브를 제공합니다.

서버가 그것을 쓰는 방식

아이솔레이트가 로드하는 산출물은 run 번들이며, 라이브 entry이거나 매니페스트의 전용 run 산출물입니다.

함께 보기

이 페이지에서