본문으로 건너뛰기

진행 기록

clover 문서 목록으로


단계마다 무엇이 확인되면 끝인가를 적고, 끝난 것에 결과를 적습니다.

단계무엇판정상태
P0규격 — 원작 수치 수집과 대조표조커 150종에 「원작 → 우리 → VM 변종」이 적혀 있습니다
P1데이터 설계 — .tbs · 테이블 · VM 규격테이블 40개의 배치표가 있습니다
P2데이터 저작 — 격자 · 워크북 · 변환변환이 끝까지 돌고 verify.py 24개가 통과합니다
P3코어 (TypeScript)헤드리스가 예외 없이 종료 상태까지 돌고, 리플레이가 같은 해시를 다시 냅니다
P4웹 게임 (PixiJS)브라우저가 오류 없이 그리고, 구운 화면이 그것을 보여줍니다
P5유니티에디터에서 완주대기
P6대조 게이트리플레이 20개가 양쪽에서 같은 해시대기
P7출시 형태세이브 · 옵션 · 지역화 2종 · 도전과제 · 빌드대기

P0 — 규격 (끝)

원작 수치를 수집하고 대조표 8갈래를 썼습니다.

갈래수집
진행 구조 · 족보와 카드 · 경제와 상점
바우처 32 · 태그 24 · 덱 15 · 스테이크 8
타로 22 · 행성 12 · 유령 18
보스 28
조커 150 — 이름 자작과 VM 변종 판정

수집되지 않은 것 4개를 경제와 상점에 적어 두었습니다. 추측으로 채우지 않았습니다 — 틀린 수치가 들어가면 대조가 의미를 잃습니다.

P1 — 데이터 설계 (끝)

효과 VM상태데이터 설계를 썼습니다. 축 4개와 변종 목록이 정해졌고, 테이블 40개가 워크북 9개에 배치되었습니다.

설계에서 드러난 것 셋입니다.

무엇어떻게 다뤘는가
효과 테이블 6개가 같은 컬럼 구성Condition 계열이 테이블마다 따로 선언되던 것이 선언된 struct의 신원으로 하나가 되었습니다. 형식은 무변경이라 데이터는 그대로입니다
Suit 가 enum과 테이블 양쪽에 필요목록은 .tbs, 데이터는 시트. 테이블의 키가 enum입니다
카드 한 장이 개체이고 이력을 가집니다덱이 런 상태이고 uid 로 구분합니다. 세이브가 카드마다 한 줄입니다

EOD

P2 — 데이터 저작 (끝)

격자 40개를 채우고, 워크북 9개로 렌더링하고, 변환을 통과시켰습니다.

python samples/clover/design-data/tools/seed.py # 격자를 처음 만들 때만
dotnet run --project samples/clover/design-data/tools/Authoring # 격자 → 워크북
dotnet run --project src/Tabbit.csproj -- --recipe samples/clover/design-data/recipe.jsonc
python samples/clover/design-data/tools/verify.py
무엇
격자40개
데이터 행1,300 남짓
조커150종 · 효과 행 178
효과 행 전체400 남짓 (조커 · 소모품 · 보스 · 바우처 · 태그 · 덱 · 강화 · 인장)
Custom1건pruning_shears 하나
verify.py통과 24 · 실패 0

효과의 99% 이상이 데이터에 있습니다. 그것이 이 단계의 답입니다 — 두 구현이 갈라질 수 있는 자리가 하나뿐입니다.

도구 쪽에서 3건이 나왔고 도구 보고에 있습니다. 하나는 결함이고 (변종의 멤버가 배열이면 C# 생성이 예외) 둘은 설계된 제약입니다. 셋 다 우회하였고, 우회가 남아 있는지는 verify.py 가 봅니다.

남은 것 하나는 조커 그림입니다 — Assets.OnMissing 이 지금 warn 이고 경고가 150건입니다. P4 에서 그림이 들어오면 error 로 바꿉니다.

P3 — 코어 (TypeScript) (끝)

화면을 모르는 규칙 엔진과 헤드리스 러너입니다. 코어의 표면은 함수 하나입니다apply(data, state, action) 이 상태와 이벤트 배열을 돌려줍니다.

cd samples/clover/web
npm install
npm run check # 타입 검사
npm test # 테스트 56개
npm run headless -- --seed CLOVER-0001 --random 600
무엇어디
결정론 원시core/units.ts · core/rng.ts · core/hash.ts
족보 판정core/hand.ts
효과 VMcore/vm/ — 조건 41종 · 연산 36종
득점 파이프라인core/scoring.ts. 규격의 줄 순서가 곧 이 파일의 줄 순서입니다
런의 흐름core/run.ts · core/shop.ts
헤드리스headless.ts — 리플레이 재생과 무작위 완주

판정

기준결과
타입 검사통과. 우리 코드는 strict 입니다
테스트56개 통과 — 족보 12종 · 판정 규칙 5종 · 연산 15종 · 결정론 13종
완주시드 8개에서 예외 없이 종료 상태까지 돕니다
리플레이13개를 구웠고, 다시 돌리면 같은 해시를 냅니다
Custom득점 경로에서 도는 것은 pruning_shears 하나뿐입니다

안테 8에 대하여

계획에는 「헤드리스로 안테 8까지 완주」라고 적었습니다. 그것은 코어의 판정 기준이 아니었습니다 — 지금 헤드리스의 수를 두는 부분은 「낼 수 있는 조합 중 점수가 가장 높은 것」을 고르는 정도이고, 그 실력으로 도달하는 곳이 안테 4~5입니다.

시드결과
CLOVER-0001 · 0003 · 0007 · 0008안테 4
CLOVER-0005안테 5
CLOVER-0002안테 1
CLOVER-0004 · 0006안테 2

코어가 못 가는 것이 아니라 두는 쪽이 못 가는 것입니다. 그래서 판정을 「예외 없이 종료 상태까지 돈다」로 바꾸고, 도달 안테는 러너가 좋아질수록 올라가는 수로 기록해 둡니다.

규격에 더한 것 하나

재발동 중에는 재발동을 받지 않습니다. Red Seal 이 그 카드를 한 번 더 발동하는데, 그 한 번에서 인장이 또 요청하면 자기 자신을 끝없이 부릅니다. 깊이 상한(Const_ScoreMaxRetriggerDepth)은 그 위의 안전장치이지 규칙이 아닙니다.

테스트를 쓰다가 나왔습니다 — 「인장 하나가 칩 2를 더한다」가 16이 아니라 30이 나왔고, 그 자리에서 규격이 비어 있던 것이 드러났습니다.

도구 쪽에서 나온 것 셋

도구 보고의 §4 · §5 · §6 입니다. 생성 코드를 실제로 읽고 돌려 보아야 나오는 것들이고, 변환은 셋 다 성공으로 끝납니다.

무엇무게
다형 변종의 옵셔널 멤버에서 「비었음」이 사라집니다규약으로 덮었습니다
typescript 산출물이 strict 에서 컴파일되지 않습니다결함. 옵셔널 foreign 의 타입이 사실과 다릅니다
readAllBytes 가 ESM 에서 돌지 않습니다결함. "type": "module" 프로젝트에서 파일 읽기가 전부 막힙니다

P4 — 웹 게임 (PixiJS) (끝)

코어가 내는 이벤트를 받아 그리는 층입니다. 규칙은 여기 없습니다 — 어디에 놓을지와 얼마나 세게 보일지뿐이고, 뒤쪽의 수치는 Const_Feel 이므로 데이터입니다.

cd samples/clover/web
npm run dev # 브라우저에서 열립니다
npm run build # 정적 산출물
npx tsx tools/shoot.ts # 화면을 굽습니다
무엇어디
카드와 조커의 그림render/card-view.ts · render/joker-view.ts. 그림 파일이 없습니다 — 그립니다
에디션 셰이더 4종shader/editions.ts. 파라미터가 EditionVisual 테이블입니다
연출render/juice.ts. 이벤트를 박자로 만드는 것과 재생하는 것이 갈려 있습니다
소리render/audio.ts. 음원 파일이 없습니다 — 파형을 만듭니다
화면render/game.ts · ui/widgets.ts

판정

기준결과
타입 검사통과
테스트75개 — 연출 11개와 팩 8개가 더해졌습니다
번들npm run build 가 나옵니다
브라우저콘솔 오류 0. 셰이더 4종이 컴파일되고 화면이 돕니다
구운 화면11장. 게임 방법 · 블라인드 선택 · 라운드 · 득점 중 · 득점 후 · 상점 · 팩 · 조커

눈으로 본 것이 잡은 것 셋

타입 검사와 테스트가 통과해도 화면은 틀릴 수 있습니다. 그림을 굽고 사람이 본 뒤에 고친 것이 셋입니다.

무엇어떻게 드러났는가
득점이 어디서 일어나는지 보이지 않았습니다낸 카드가 곧바로 사라져서 숫자가 화면 가운데에 떴습니다. 낸 카드를 연출이 끝날 때까지 붙잡습니다
족보 이름이 ThreeOfAKind 로 나왔습니다식별자를 그대로 그렸습니다. 표시 이름을 StringTable 에 넣고 거기서 읽습니다
상점에 손패와 칩·배수가 남았습니다코어의 결함입니다 — 라운드가 끝날 때 패를 비우지 않았습니다

셋째는 화면의 문제가 아니라 코어의 문제였고, 그림이 없었으면 P6 대조에서야 드러났을 것입니다.

그림에 대하여

조커 150종의 일러스트가 아직 없습니다. 지금은 식별자에서 만든 문양으로 그립니다 — 같은 조커는 언제나 같은 모양이고 희귀도가 테두리 색입니다. Assets.OnMissingwarn 이고 경고가 150건입니다.

그림이 들어오면 error 로 바꾸고 문양을 스프라이트로 갈아 끼웁니다. 그때부터 그 검사가 자리표가 아니라 게임이 띄우는 파일을 봅니다.


P4 뒤에 고친 것

손으로 해 보고 나서 드러난 것들입니다. 타입 검사도 테스트도 그림도 통과한 상태에서 사람이 해 보고 나온 것이므로, 이것들이 왜 자동 검사에 걸리지 않았는지도 같이 적습니다.

무엇왜 그랬는가검사가 왜 못 봤는가
화면 오른쪽과 아래에 빈 곳이 남았습니다화면 크기를 renderer.width 로 쟀습니다. 그것은 픽셀 밀도가 곱해진 물리 크기라서 밀도 2인 화면에서 배율이 두 배로 계산됩니다스크린샷 도구가 기준 해상도·밀도 1로만 찍었습니다. 배율이 늘 1이면 배율 계산이 틀려도 그림이 맞습니다
카드 아래쪽에 마우스를 올리면 카드가 떨었습니다마우스가 올라오면 카드가 위로 들리고, 들린 카드가 마우스 밑에서 빠져나가 곧바로 내려옵니다. 내려오면 다시 들립니다Pixi 의 pointerover·pointerout 이 정상으로 오간 것이므로 오류가 없습니다
무엇을 하는 게임인지 알 수 없었습니다규칙을 적어 둔 곳이 문서뿐이었습니다화면에 없는 것은 그림 검사의 대상이 아닙니다
조커를 한 번도 못 봤습니다조커는 상점에서만 오고, 상점은 블라인드를 하나 격파해야 열립니다. 그 사실이 화면에 적혀 있지 않았습니다자동으로 두는 러너는 규칙을 알고 있으므로 막히지 않습니다

고친 것은 넷입니다.

무엇어디
논리 크기로 재기main.ts. 스크린샷 도구도 1600 × 1000 · 밀도 2로 올려 같은 조건을 만듭니다
쉬는 자리로 호버 판정render/game.tsupdateHover. 이벤트를 쓰지 않고 마우스 자리와 카드가 쉬는 네모를 견줍니다. 조커도 같은 문제가 있었으므로 같이 고쳤습니다
게임 방법 판ui/guide.ts. 첫 판에서 한 번 저절로 열리고, 그 뒤로는 버튼입니다
지시문 한 줄render/game.tshintText. 국면마다 다음에 누를 것을 적습니다

부스터 팩

표에만 있고 규칙도 화면도 없던 것입니다. BoosterPack 15종이 상점 상태에 채워지기는 하였지만 살 수 없었습니다.

팩은 사는 것이 아니라 뜯는 것이므로 상태가 하나 더 필요합니다.

무엇어디
뜯어 놓은 팩state.pack. 펼쳐진 것과 앞으로 몇 장 더 고를 수 있는가
액션 3개buy_pack · pick_pack · skip_pack
무엇이 들어 있는가core/shop.tsrollPackCard. 갈래가 어느 표에서 뽑을지를 정합니다
화면render/game.tsdrawPackRowsyncPack

규칙 셋을 규격으로 둡니다.

  • 다 고르거나 건너뛰기 전에는 상점을 나가지 못합니다. 나가는 액션이 무효가 됩니다.
  • 자리가 없으면 고르지 못합니다. 팩은 그대로 열려 있으므로 다른 것을 고르거나 건너뜁니다.
  • 뜯어 놓은 팩이 해시에 들어갑니다. 두 구현이 여기서 갈라진 것을 그 자리에서 알 수 있어야 합니다. 필드는 canonical 의 끝에 붙였습니다.

Standard 팩 카드의 강화·인장·에디션 확률은 원작에서 수집하지 못한 값이므로 Const_Economy 에 세 줄로 두었습니다 — 수치가 확인되면 그 세 줄만 고칩니다.

판정

기준결과
테스트75개 — 팩 8개가 더해졌습니다
리플레이13개 전부 같은 해시. 자동으로 두는 쪽이 팩을 사므로 buy_pack 이 리플레이에 들어갑니다
검증verify.py 33개 전부 통과

연출을 다시 짠 것

「돌아간다」와 「할 만하다」는 다릅니다. 손으로 해 보고 나온 지적을 하나씩 고친 기록입니다.

초록 단색이던 것을 남색 바탕에 강조색 셋으로 바꿨습니다. 채도가 높은 것은 셋뿐입니다 — 칩은 파랑, 배수는 빨강, 돈은 금색. 나머지는 회청색 계열이므로 이 셋이 눈에 들어옵니다.

블라인드마다 배경과 딱지의 색이 다릅니다. 스몰은 파랑, 빅은 보라, 보스는 붉고, 상점은 청록, 끝났으면 색이 빠집니다 — 이름을 읽지 않아도 어디까지 왔는지가 보입니다.

한 방을 만드는 것들

무엇어디
히트스톱game.tsstop때린 순간 연출의 시계가 멈춥니다. 용수철과 파티클은 계속 움직이므로 화면이 얼어붙어 보이지 않습니다
배경 펄스shader/background.tsuPulse무늬가 통째로 밝아지고 고리가 가운데에서 바깥으로 퍼집니다
패널 번쩍임game.tsflashPanel왼쪽의 숫자가 바뀌는 자리에 파티클을 뿌리면 숫자를 가립니다. 패널 자체가 빛납니다
화면 번쩍임flashScreen큰 것에만 씁니다 — 배수 곱하기, 점수 합치기, 격파, 런의 끝
색수차shader/punch.ts화면 흔들림만 있으면 「움직였다」로 읽히고, 색이 어긋나면 「맞았다」로 읽힙니다
음이 올라가는 소리showBeatchain득점 사건마다 반음이 올라갑니다. 사람이 「쌓인다」로 읽는 것이 이것입니다
커지는 세기chain 을 5로 나눈 값뒤로 갈수록 튀는 크기와 흔들림이 커집니다. 마지막 한 방이 앞의 것보다 확실히 큽니다

고른 카드

셰이더 하나가 두 가지를 합니다 — 고른 카드는 밝아지고 사선 광택이 흐르고 테두리에 빛이 돌고, 고르지 않은 카드는 색이 빠지고 어두워집니다. 고른 것만 밝히면 몇 장을 골랐는지가 세어야 알 수 있고, 나머지가 물러나면 한눈에 읽힙니다.

버튼 줄 오른쪽에 칸 다섯이 있습니다. 「최대 5장」 이라고 적어 두는 것보다 칸이 채워지는 것이 빠릅니다.

동전

돈이 오갈 때 동전이 날아갑니다. 벌면 번 자리에서 금액 칸으로 날아가 하나씩 꽂히고 꽂히는 소리의 음이 하나씩 올라갑니다. 잃으면 금액 칸에서 튀어나가 떨어집니다.

라운드가 끝날 때의 돈은 갈래마다 따로 알립니다 — 격파 보상 · 이자 · 남은 핸드 · 남은 버리기. 코어가 MoneyChanged 를 내지 않아 연출이 볼 것이 없던 것을 고쳤습니다.

국면 화면의 시점

국면이 바뀌었어도 앞 국면의 연출이 돌고 있으면 화면을 갈지 않습니다. 낸 카드가 아직 판에 있는데 상점이 그 위에 그려지던 것과, 마지막 카드의 결과를 보기도 전에 끝난 판이 덮이던 것이 같은 결함이었습니다.

presented 하나가 그 조건입니다 — 연출이 끝났고, 낸 카드가 치워졌고, 동전이 다 꽂혔는가. 상점·버튼·딱지·배경 색이 전부 이것을 봅니다.

끝난 판은 럼블과 함께 뜹니다. 크기가 0.45에서 1.16을 지나 1로 돌아오고 그동안 판이 떨립니다. 문구도 「패배」·「승리」로 바꾸고, 왜 끝났는지를 적습니다 — 「안테 3 · 빅 블라인드에서 216점이 모자랐습니다」.

눈으로 본 것이 잡은 것

무엇어떻게 드러났는가
조커 두 장이 무지개 잡티로 덮여 이름을 읽을 수 없었습니다홀로그래픽 셰이더가 노이즈를 무지개의 위상에 더했습니다. 픽셀마다 색이 달라지므로 카드가 잡티가 됩니다. 노이즈는 밝기에만 얹고, 섞는 양은 데이터가 어떤 값이어도 절반을 넘지 않게 묶었습니다
폴리크롬의 strength 가 1을 넘어 밝은 자리가 통째로 무지개가 되었습니다셰이더에서 묶습니다. 데이터가 범위를 벗어나도 화면이 무너지지 않아야 합니다
낸 카드가 게이지에 붙어 있었습니다머리글과 게이지를 위로, 낸 카드를 아래로 옮겼습니다
「+$1」 같은 글씨가 흰 카드 위에 겹쳐 읽히지 않았습니다낸 카드 아래로 내렸습니다

에디션 확인 페이지

에디션은 상점 추첨으로만 붙으므로 게임 안에서는 홀로그래픽 하나를 보려고 여러 판을 돌려야 합니다. web/editions.html 이 다섯 가지를 조커와 카드로 한 줄에 세웁니다. 같은 EditionVisual 을 읽으므로 여기서 맞으면 게임에서도 맞습니다.

판정

기준결과
테스트75개
검증verify.py 33개 전부 통과
구운 화면16장. 게임 방법 · 블라인드 선택 · 고른 카드 · 득점 중 · 마지막 한 방 · 상점 · 팩 · 조커 · 동전 · 끝난 판 · 에디션