Caputchin
마켓플레이스 게임 개발

리플레이 계약

리플레이 계약은 재생 가능한 게임이 플랫폼과 합의하는 하나의 필수 표면입니다. 그것은 @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 제한으로만 한정됨.

인자 순서가 신뢰 모델을 인코딩합니다: seedconfig는 라운드의 서버 설정입니다; 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 결정을 모는 유일한 필드입니다; scoredurationMs는 발급된 토큰(그리고 미래의 점수판)에 실립니다. 패키지의 parseVerdict(value)는 신뢰되지 않는 반환 값을 검증하고 잘못된 결과를 통과 라운드가 아니라 거부된 라운드로 다룹니다, 그래서 던지거나 쓰레기를 반환하는 run은 닫힌 채 실패합니다.

결정론이 요건 전부입니다

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

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

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

서버가 그것을 쓰는 방식

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

함께 보기

이 페이지에서