이 게임은 퍼즐을 손으로 만들지 않습니다. 숫자 하나에서 퍼즐을 키워낸 다음, 여러분이 보기도 전에 그 퍼즐을 스스로 플레이해 보며 너무 쉽지도, 불가능하지도 않은지 확인합니다.
영어로 집필하고 편집했습니다. 이 한국어 판은 기계 번역으로 만들어졌으며, 정확성이 중요한 경우에는 영어 원문이 기준이 됩니다. 영어 원문 읽기 →
Contraption의 레벨은 어디에도 저장되어 있지 않습니다. 골대가 어디에 있는지, 턱이 어떻게 배치되어 있는지 적어 둔 파일은 없습니다. 대신 레벨 전체, 즉 공의 출발점, 골대의 위치, 고정된 벽의 개수와 배치, 여러분에게 주어지는 부품의 목록이 시드라고 부르는 하나의 시작 숫자에서 계산됩니다. 생성기에 같은 시드를 넣으면 바이트 단위까지 똑같은 퍼즐이 나옵니다. 1
이것이 가능한 이유는 생성기의 유일한 "무작위성"의 원천이 작은 결정론적 의사 난수 생성기이기 때문입니다. Contraption이 사용하는 것은 토미 에팅거가 만든 간결한 32비트 생성기 mulberry32이며, 매번 값을 뽑을 때마다 상태에 더해지는 특유의 상수 0x6d2b79f5로 알아볼 수 있습니다. 2 빠르고, 몇 줄에 들어가며, 무엇보다 전혀 무작위가 아닙니다. 그저 뒤섞인 것처럼 보이는 고정된 수학적 수열일 뿐입니다. 시작점을 주면 영원히 같은 숫자의 흐름을 만들어 냅니다. 생성기는 그 흐름을 읽어 모든 것을 어디에 놓을지 결정합니다.
일일 모드는 이 결정론에 기대어 사회적인 일을 해냅니다. 그 시드는 무작위가 아니라 달력에서 만들어집니다. 게임은 세계 협정시 기준 오늘 날짜를 가져와 "contraption:2026-7-28" 같은 짧은 문자열로 쓴 다음, 그 텍스트를 해시 함수에 통과시켜 하나의 32비트 숫자로 졸여 냅니다. 1
이 해시는 잘 알려진 비암호화 혼합 함수인 FNV-1a입니다. 고정된 오프셋, 즉 십진수 2166136261 또는 0x811c9dc5에서 시작해, 각 문자마다 배타적 논리합으로 그 글자를 섞어 넣은 다음 FNV 소수 16777619를 곱합니다. 3 그 결과 특정 날짜에 대해서는 변하지 않지만 하루만 지나도 완전히 달라지는 숫자가 남습니다. 날짜의 글자 하나만 바뀌어도 시드가 완전히 흩어지는 것입니다. 달력은 모두에게 같으므로 시드도 같고, 퍼즐도 같습니다. 지구 반대편에 사는 낯선 두 사람이 일일 모드를 열면 완전히 같은 필드를 마주하게 되며, 바로 이것이 점수 비교를 의미 있게 만듭니다.
일일 도전이 공정한 이유는 서버가 모두에게 같은 레벨을 보내 주기 때문이 아니라, 모두의 사본이 같은 날짜로부터 같은 레벨을 각자 독립적으로 다시 계산해 내기 때문입니다.
무작위 배치는 쉽습니다. 좋은 무작위 배치는 쉽지 않습니다. 골대와 턱 몇 개를 그저 흩뿌리기만 하는 생성기는 때때로 꼭대기에서 떨어뜨린 공이 저절로 골대에 곧장 들어가는 레벨을 만들어 냅니다. 그것은 퍼즐이 아니라 컷신입니다. 그래서 Contraption은 후보 세계를 받아들이기 전에 코드에서 preSolved라고 부르는 조용한 검사를 실행합니다. 부품을 하나도 놓지 않은 상태로 레벨을 시뮬레이션하여 맨 공이 중력만으로 골대에 도달하는지 확인하는 것입니다. 도달한다면 그 레벨은 버려지고 생성기는 다시 굴립니다. 1
이는 물리 엔진을 바라보는 통상적인 관점을 미묘하게 뒤집는 것입니다. 여러분의 기계를 작동시키는 바로 그 simulate() 함수가 생성 시점에 안쪽으로 향해, 레벨 자체의 난이도를 감사하는 데 쓰입니다. 엔진은 여러분의 해법을 판정하는 심판이자 퍼즐의 품질 검사관이기도 합니다.
너무 쉬운 레벨을 걸러내는 것은 일의 절반에 불과합니다. 또 다른 위험은 불가능한 레벨, 즉 어떤 부품 배치로도 닿을 수 없는 곳에 골대가 숨어 있는 레벨입니다. Contraption은 plausiblySolvable이라는 작은 무차별 대입 해결기로 이를 막습니다. 이 해결기는 영리하게 생각하지 않습니다. 그저 시도할 뿐입니다. 1
구체적으로는 경사로 하나를 필드 위 격자 형태의 위치들에 차례로 훑어 가며 놓고, 각 지점에서 네 가지 다른 기울기로 시도해 봅니다. 그 시험 배치 하나하나마다 전체 시뮬레이션을 실행하고 묻습니다. 그 경사로 하나가 공을 집으로 보냈는가? 어느 배치라도 성공하는 순간 그 레벨은 풀 수 있다고 선언되어 여러분에게 전달됩니다. 전체를 훑고도 아무것도 통하지 않으면 생성기는 그 레벨을 버리고 새 시드로 시도하는 쪽을 택합니다.
| 훑기 변수 | 범위 | 의미 |
|---|---|---|
| 경사로 X | 15 → 85, 간격 14 | 필드를 가로지르는 열 |
| 경사로 Y | 30 → 120, 간격 18 | 필드를 내려가는 행 |
| 시도하는 각도 | 4 | ±0.45 및 ±0.8 라디안 |
| 배치당 검사 | 전체 시뮬레이션 1회 | 공이 골대에 도달하는가? |
이것이 무엇을 증명하는지에 대해서는 솔직해질 필요가 있습니다. 코드 역시 그 점에 솔직하기 때문입니다. 주석은 이를 "값싼 그럴듯함 검사 … 완전하지는 않음"이라고 부릅니다. 통과한 레벨에는 분명히 경사로 하나로 된 투박한 해법이 적어도 하나 존재합니다. 실패한 레벨이라도 팬이나 기어, 혹은 격자가 시도해 보지 않은 더 영리한 경사로로는 여전히 풀 수 있을지 모르므로, 생성기는 실패를 불가능의 증거로 취급하지 않습니다. 처음 찾아낸 너무 쉽지 않은 세계를 대비책으로 간직해 두고, 마흔 번을 시도해도 깔끔한 부품 하나짜리 해법이 나오지 않으면 영원히 반복하는 대신 그 대비책을 내보냅니다. 그 결과는 실용적인 보장입니다. 공짜 승리는 결코 없고, 거의 언제나 진짜 길이 있으며, "완벽한" 레벨을 위해 무한히 기다리는 일도 결코 없습니다. 1
한 걸음 물러서서 보면 Contraption의 레벨 생성기는 아주 오래되고 아주 일반적인 문제 해결 패턴, 즉 생성 후 검사의 깔끔한 예시입니다. 후보를 무작위로 제안하고, 요구 조건에 비추어 검사하고, 통과하면 남기고, 실패하면 버리고 다시 제안합니다. 이것은 수많은 게임의 절차적 콘텐츠 뒤에, 진화 연산의 상당 부분 뒤에, 그리고 조금 눈을 가늘게 뜨고 보면 자연 선택 그 자체 뒤에 있는 반복 과정입니다. 돌연변이가 제안하고, 환경이 검사하며, 살아남은 것이 지속됩니다. 4
Contraption의 방식이 우아한 이유는 검사가 플레이 가능성의 값싼 대용물이 아니라 플레이 가능성 그 자체라는 점입니다. 생성기는 턱의 개수를 세거나 거리를 재서 레벨이 공정한지 추정하지 않습니다. 말 그대로 레벨을 두 번 플레이합니다. 한 번은 빈 상태로 거저 주는 레벨이 아닌지 확인하고, 한 번은 탐색용 경사로를 놓고 길이 존재하는지 확인합니다. 여러분의 해법을 판정할 바로 그 결정론적 물리가 퍼즐을 검증한 물리입니다. 실제 규칙과 어긋날 수 있는 별도의 "레벨 검증기"는 존재하지 않습니다. 모든 것에 쓰이는 규칙은 단 한 벌뿐이기 때문입니다.
그리고 모든 것이 결정론적이기 때문에, 이 감사는 양쪽 방향 모두에서 운의 영향을 받지 않습니다. 일일 시드는 한 플레이어에게 다른 플레이어보다 쉬운 필드를 몰래 건네도록 공모할 수 없고, 누구에게도 불공정한 필드를 건넬 수 없습니다. 레벨이 도전이 되기 전에 이미 게임이 스스로를 상대로 플레이해 보는 과정을 살아남았기 때문입니다.