Case Document 프로토콜

Casebook 사건 파일(.casedoc)의 구조를 형식 버전 2 기준으로 설명해요.

좋은 사건을 쓰는 법은 사건 제작 가이드에 있어요. 이 문서는 파일을 직접 만들거나 도구로 내보낼 때 필요한 형식만 다뤄요.

  • 사건 하나는 이미지까지 담은 JSON 파일 하나예요.
  • 잠긴 증거(unlocks)가 있으면 형식 버전 2, 없으면 1이에요.
  • 이미지는 파일 이름을 키로, 내용을 Base64 문자열로 넣어요. 사건 안의 이미지 이름과 키가 같아야 해요.
  • 공공 사건기록보관소에는 15MB까지 올릴 수 있어요.

1. 파일 한눈에 보기

항목내용
확장자.casedoc(Casebook Case Document). 예전 확장자 .sherlockcase도 열 수 있지만, 새로 저장할 때는 .casedoc을 써요.
내용UTF-8 JSON 객체 하나. 폴더가 아니라 파일 하나라서 AirDrop이나 파일 앱으로 그대로 주고받을 수 있어요.
크기공공 사건기록보관소에 올릴 수 있는 파일은 15MB(15,728,640바이트)까지예요. 넘으면 "사건 파일이 너무 커요. 15MB 이하로 줄여 주세요."라고 안내해요. 기기 안에서 열고 플레이하는 데는 이 제한이 없어요.
모르는 키앱은 아래 표에 없는 키를 읽을 때 무시하고, 다시 저장하면 그 키는 사라져요.

맨 위 객체의 키는 다섯 개예요.

키타입필수설명
formatVersion정수필수형식 버전. 2장을 보세요.
caseFile객체필수사건 내용. 3장을 보세요.
images객체(이름 → Base64)필수이미지 파일. 이미지가 없으면 빈 객체 {}. 8장을 보세요.
author객체선택사건을 만든 사람 { "id": 문자열, "name": 문자열 }. 앱이 채우는 칸이라 직접 만들 때는 넣지 않아요. 없으면 가져온 사람이 고칠 수 있는 내 사건으로 들어가요.
modifiedAt숫자선택사건 내용이 마지막으로 바뀐 시각(2001년 1월 1일 UTC부터 센 초). 앱이 편집할 때 기록하니 직접 만들 때는 넣지 않아요. 없으면 파일의 수정 날짜로 대신해요.

2. 형식 버전

버전언제 쓰나요
1잠긴 증거가 없는 사건. caseFile에 unlocks 키를 넣지 않아요.
2unlocks가 하나라도 있는 사건(결정적 증거 해금). 지금 앱이 읽을 수 있는 가장 높은 버전이에요.

3. caseFile

사건 내용이에요. "선택"이라고 적힌 키는 빼도 되고, 빼면 기본값으로 읽어요. 필수 키도 빈 문자열이나 빈 배열은 넣을 수 있지만, 9장의 검사에 걸릴 수 있어요.

키타입필수설명
id문자열필수사건 ID. 예: case.glass-greenhouse
title문자열필수사건 제목
clientName문자열필수의뢰인 이름
briefing문자열필수목록 · 둘러보기에 보이는 짧은 3인칭 소개(2~3문장)
clientRequest문자열선택(기본 "")의뢰인이 직접 말하듯 쓴 의뢰 내용. 조사 화면의 '사건 개요' 탭에 보여요. 비어 있으면 briefing을 대신 보여 줘요.
clientPortraitImageName문자열선택(기본 "")의뢰인 사진의 이미지 이름. 비어 있으면 이름 첫 글자로 그려요.
genre문자열선택(기본 murder)장르. 6장의 일곱 값 중 하나
backdropImageName문자열필수사건 현장 전경의 이미지 이름. 증거 탭 배너와 몰입 공간 배경에 써요.
sceneEvidence배열필수현장 증거. 4장
documentEvidence배열필수(빈 배열 가능)문서 증거. 4장
interviews배열필수관계자와 진술. 5장
motiveOptions배열선택(기본: 장르 기본 선택지)'왜' 선택지. 6장
methodOptions배열선택(기본: 장르 기본 선택지)'어떻게' 선택지. 6장
solution객체필수정답. 6장
hints객체필수힌트 세 문장. 6장
story문자열필수사건의 실제 전말. 해결했거나 힌트를 보고도 못 풀었을 때 보여요.
unlocks배열선택(형식 버전 2)잠긴 증거. 7장. 없으면 키를 넣지 않아요.

4. 증거

현장 증거(sceneEvidence의 항목). 키는 모두 필수예요.

키타입설명
id문자열증거 ID. 예: evidence.cut-glass
imageName문자열증거 사진의 이미지 이름
caption문자열짧은 이름. 화면과 검사 안내에 이 이름이 나와요.
detail문자열자세한 설명
immersivePlacement객체 { x, y, z }(숫자)Apple Vision Pro 몰입 공간에서 사용자 기준으로 놓이는 위치(미터)

문서 증거(documentEvidence의 항목). 키는 모두 필수예요.

키타입설명
id문자열증거 ID. 예: document.ledger
title문자열문서 제목
body문자열문서 내용

5. 관계자와 진술

interviews의 항목이에요. 키는 모두 필수예요. 앱은 관계자 전원을 범인 후보로 보여 줘요.

키타입설명
id문자열관계자 ID. 예: person.tom
personName문자열이름
role문자열witness(목격자) · suspect(용의자) · client(의뢰인) 중 하나
portraitImageName문자열프로필 사진의 이미지 이름
transcript배열진술. 한 줄은 { "id": 문자열, "speaker": 문자열, "text": 문자열 }

speaker는 holmes(탐정의 질문) 또는 interviewee(관계자의 답)예요.

6. 장르 · 선택지 · 정답 · 힌트

장르마다 추리 문항 세 개(누가 · 왜 · 어떻게)의 이름과 기본 선택지가 달라요. motiveOptions · methodOptions를 빼면 아래 기본 선택지로 읽어요.

genre화면 이름문항 이름기본 '왜' id기본 '어떻게' id
murder살인·사망범인 · 동기 · 범행 수법revenge · greed · jealousy · selfDefense · coverUppoison · bluntForce · stagedAccident · strangulation · firearm
theft도난·절도범인 · 동기 · 수법greed · revenge · collectorObsession · debt · coverUpforgerySwap · insiderKey · breakIn · divertedInTransit · fakeTheft
disappearance실종·행방불명열쇠를 쥔 인물 · 사라진 이유 · 실종의 진상escapeThreat · keepSecret · elopement · inheritanceDispute · witnessedCrimevoluntaryHiding · kidnapping · accident · concealedMurder · assumedIdentity
fraud협박·사기·사칭범인 · 목적 · 수법extortion · inheritanceStatus · revenge · hideSecret · romanceblackmailLetter · impersonation · forgedDocument · investmentFraud · conspiracy
cipher암호·문서·정보작성자·유출자 · 목적 · 숨긴 방법sellSecrets · contactAccomplice · coverUp · prepareBlackmail · frameSomeonesubstitutionCipher · bookCipher · invisibleInk · forgedDocument · hiddenLetters
lockedRoom저택·밀실·기이한 공간범인 · 동기 · 밀실 트릭revenge · greed · jealousy · coverUp · inheritanceDisputesecretPassage · clockTampering · mechanism · lockedFromOutside · crimeAfterDiscovery
ghostStory괴담배후 인물 · 목적 · 괴현상의 정체driveAway · seizeEstate · coverUp · revenge · pranklightAndShadow · soundDevice · disguise · animal · mirrorIllusion

선택지(motiveOptions · methodOptions의 항목)는 { "id": 문자열, "title": 문자열 }이에요. id는 정답 비교와 저장에 쓰고, 화면에는 title이 보여요. 기본 선택지를 고치거나 더해도 돼요.

정답(solution). 키는 모두 필수예요.

키타입설명
culpritID문자열범인인 관계자의 id
motive문자열motiveOptions 중 정답의 id
method문자열methodOptions 중 정답의 id
keyEvidenceIDs문자열 배열결정적 증거(현장 · 문서 증거)의 id. 순서는 상관없어요.

힌트(hints)는 { "culprit": 문자열, "motive": 문자열, "method": 문자열 }이에요. 장르마다 문항 이름은 달라도 키 이름은 같아요. 채점과 힌트를 보여 주는 방식은 사건 제작 가이드 1장에 있어요.

7. 잠긴 증거(unlocks)

처음에는 잠겨 있다가 단서를 확인하거나 답을 맞히면 열리는 증거예요. 하나라도 있으면 형식 버전 2예요.

키타입필수설명
evidenceID문자열필수잠글 현장 증거 또는 문서 증거의 id
from문자열 배열선택(기본 [])확인해야 하는 단서(현장 · 문서 증거, 관계자)의 id. 증거는 상세 화면을 열면, 관계자는 진술을 열면 확인한 것으로 봐요.
needCount정수선택from 중 몇 개를 확인하면 열리는지. 없으면 전부
answer객체선택답을 맞혀야 열리는 잠금이면 그 문제. from 조건을 채운 뒤(비어 있으면 처음부터) 입력칸이 나와요.

answer의 키예요.

키타입필수설명
prompt문자열필수입력칸 위에 보이는 질문. 예: "금고의 번호는?"
accepted문자열 배열필수정답으로 받는 값들
match문자열선택(기본 text)비교 방법. 앱이 모르는 값도 text로 읽어요.
match비교 방법쓰는 곳
digits숫자만 남겨 비교해요(전각 숫자도 0~9로 맞춰요).금고 번호, 연도
text대소문자 · 공백 · 문장부호 · 기호를 무시해요.낱말, 이름
exact앞뒤 공백만 무시하고 그대로 비교해요.정확히 맞혀야 하는 답

세 방법 모두 한글은 자모가 나뉘어 들어와도 같은 글자로 봐요.

8. 이미지

9. 앱이 검사하는 것

플레이할 수 없는 문제. 편집기의 '확인할 점'에 나오고, 하나라도 있으면 플레이할 수 없어요.

  1. title · story가 비어 있지 않아요.
  2. 현장 증거가 하나 이상, 관계자가 한 명 이상이에요.
  3. solution.culpritID가 관계자 중 한 명의 id예요.
  4. motiveOptions · methodOptions는 각각 두 개 이상이고, title이 빈 것이 없고, 같은 배열 안에서 id가 겹치지 않아요.
  5. solution.motive · solution.method가 각 선택지에 있는 id예요.
  6. solution.keyEvidenceIDs가 하나 이상이고, 모두 있는 증거의 id예요.
  7. 현장 증거 · 문서 증거 · 관계자의 id가 서로 겹치지 않아요.
  8. hints의 세 문장이 모두 비어 있지 않아요.

unlocks가 있으면 아래도 검사해요.

  1. evidenceID는 있는 현장 · 문서 증거이고, 한 증거는 한 번만 잠가요.
  2. from에는 있는 증거 · 관계자만 넣고, 잠긴 증거 자신이나 같은 단서를 두 번 넣지 않아요.
  3. answer가 없으면 from이 비면 안 돼요. needCount는 1 이상, from 개수 이하예요.
  4. answer.prompt가 비어 있지 않고, accepted가 하나 이상이며 비교 방법을 적용해도 비지 않아요(digits면 숫자가 있어야 해요).
  5. 막다른 길이 없어요. 처음부터 열린 증거와 모든 관계자에서 시작해 확인을 이어 가면 모든 잠금이 열려요(답 잠금은 조건을 채우면 열 수 있다고 봐요).

사진이 빠진 곳. 플레이는 되지만 그 자리가 빈 칸으로 보여요. 편집기의 '확인할 점'과 공공 사건기록보관소에 올리기 전 검사에 나와요.

10. 예시

형식 버전 2(잠긴 증거 하나)인 지어낸 사건이에요. images의 값은 줄여 적었어요. 실제 파일에는 이미지 파일 전체를 Base64로 넣어야 열려요.

{
  "formatVersion": 2,
  "caseFile": {
    "id": "case.glass-greenhouse",
    "title": "유리 온실의 사라진 난초",
    "clientName": "큐 식물원 관리인 해리엇 웨스트",
    "briefing": "식물원 유리 온실에서 하룻밤 사이에 귀한 난초가 사라졌다. 문은 안에서 잠겨 있었다.",
    "clientRequest": "",
    "clientPortraitImageName": "",
    "genre": "theft",
    "backdropImageName": "greenhouse.jpg",
    "sceneEvidence": [
      {
        "id": "evidence.cut-glass",
        "imageName": "cut-glass.jpg",
        "caption": "잘린 유리창",
        "detail": "지붕 가까운 유리 한 장이 안쪽에서 잘려 있다.",
        "immersivePlacement": { "x": 0, "y": 0.1, "z": -1 }
      }
    ],
    "documentEvidence": [
      {
        "id": "document.ledger",
        "title": "출입 장부",
        "body": "밤 10시 이후 서명은 정원사 톰 한 사람뿐이다."
      }
    ],
    "interviews": [
      {
        "id": "person.tom",
        "personName": "톰 베이커 (정원사)",
        "role": "suspect",
        "portraitImageName": "tom.jpg",
        "transcript": [
          { "id": "l1", "speaker": "holmes", "text": "어젯밤 온실 열쇠는 누가 가지고 있었나요?" },
          { "id": "l2", "speaker": "interviewee", "text": "제가요. 하지만 10시에 문을 잠그고 나왔습니다." }
        ]
      },
      {
        "id": "person.lucy",
        "personName": "루시 그레이 (수집가)",
        "role": "suspect",
        "portraitImageName": "lucy.jpg",
        "transcript": [
          { "id": "l1", "speaker": "holmes", "text": "그 난초를 사려고 하셨다고요?" },
          { "id": "l2", "speaker": "interviewee", "text": "값을 불렀다가 거절당했어요. 그뿐이에요." }
        ]
      }
    ],
    "motiveOptions": [
      { "id": "greed", "title": "금전적 이득" },
      { "id": "collectorObsession", "title": "수집 집착" }
    ],
    "methodOptions": [
      { "id": "insiderKey", "title": "내부자의 열쇠" },
      { "id": "breakIn", "title": "외부 침입" }
    ],
    "solution": {
      "culpritID": "person.tom",
      "motive": "greed",
      "method": "insiderKey",
      "keyEvidenceIDs": ["document.ledger"]
    },
    "hints": {
      "culprit": "출입 장부에 남은 이름을 다시 보세요.",
      "motive": "난초가 누구에게 팔릴 수 있었는지 생각해 보세요.",
      "method": "잘린 유리는 어느 쪽에서 잘렸나요?"
    },
    "story": "정원사 톰은 수집가에게 난초를 팔기로 하고 열쇠로 온실에 들어갔다. 침입처럼 보이려고 유리를 안쪽에서 잘랐지만, 출입 장부의 서명이 그를 가리켰다.",
    "unlocks": [
      {
        "evidenceID": "document.ledger",
        "from": ["evidence.cut-glass", "person.tom"],
        "answer": { "prompt": "장부 상자의 번호는?", "accepted": ["1887"], "match": "digits" }
      }
    ]
  },
  "images": {
    "greenhouse.jpg": "/9j/4AAQSkZJRg...",
    "cut-glass.jpg": "/9j/4AAQSkZJRg...",
    "tom.jpg": "/9j/4AAQSkZJRg...",
    "lucy.jpg": "/9j/4AAQSkZJRg..."
  }
}