Installs into .claude/skills of the current project.
Sponsors
SSkills DirectorySkills Directory
Is that skill safe to install?
SSkills DirectorySkills Directory
Know which skills are safe — weekly.
Are you the author of Craft?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/snwlee-craft)
---
name: craft
description: 제작 층 — 코어 루프 설계, 계층 경계(엔진에서 로직을 떼는 방법), 수직 슬라이스, 서버 권위. 트리거 "코어 루프", "아키텍처", "폴더 구조", "프로토타입", "수직 슬라이스", "어디부터 만들지", "서버 권위".
---
# craft
nereus:common 규칙을 따른다. 담당 에이전트: gameplay-engineer.
**엔진과 무관하다** — 엔진별 금지 문자열은 `layers.json` 에 데이터로 있다.
워크플로·TDD 절차·게이트는 **nereus 코어가 소유한다**. 여기는 게임플레이 코드의 **판단**만 담는다.
## 0. 이 스킬이 생긴 이유
스택 스킬들이 똑같이 이렇게 적어놓았다 — **"로직을 엔진 객체에서 떼어내는 설계가 곧
1단 커버리지다."** 그리고 **어떻게 떼는지는 어디에도 없었다.** "spec 에서 태스크로 쪼갠다"로
넘겼는데 넘긴 곳에 아무것도 없었다. 그 자리를 채운다.
## 1. 코어 루프를 먼저 한 문장으로 쓴다
코어 루프는 플레이어가 **한 번에 겪는 가장 작은 완결 단위**다. 한 문장으로 못 쓰면
아직 게임이 아니라 기능 목록이다.
```
<무엇을 본다> → <무엇을 결정한다> → <무엇이 돌아온다> → <무엇이 남는다>
```
네 칸이 다 차야 한다.
| 빈 칸 | 증상 |
|---|---|
| 결정이 없다 | 플레이가 아니라 재생이다. 스킵해도 같은 결과면 결정이 아니다 |
| 돌아오는 게 없다 | 잘했는지 못했는지 모른다. 피드백은 연출이 아니라 정보다 |
| 남는 게 없다 | 다음 판을 할 이유가 없다. 남는 것이 진행도든 숙련이든 있어야 한다 |
**루프를 확정하기 전에 경제·레벨·UI 를 설계하지 않는다.** 루프가 바뀌면 전부 버린다.
루프는 `balance` · `level` 도메인의 입력이지 산출물이 아니다.
## 2. 계층 경계 — 엔진에서 로직을 떼는 방법
### 왜
엔진 객체 안에 규칙이 들어가면 **규칙을 검증하려고 엔진을 켜야 한다.**
엔진을 켜야 하는 검사는 느리고, 느린 검사는 CI 에서 빠지고, CI 에서 빠진 검사는 안 돈다.
그 결과 규칙 변경이 매번 불안정한 통합 테스트를 통과해야만 하게 된다.
**밀리초 안에 도는 규칙은 실제로 테스트된다.** 그게 전부다.
### 어떻게
세 층으로 나누고, **위에서 아래로만** 참조한다.
| 층 | 여기 있는 것 | 여기 없는 것 |
|---|---|---|
| **순수** | 수치·판정·상태 전이·시드 결정론 | 엔진 타입, I/O, 프레임워크, 플러그인, 전역 |
| **바인딩** | 순수 층을 엔진 수명주기에 붙이는 코드 | 규칙 판단 |
| **표현** | 화면·입력·연출 | 규칙 판단, 수치 |
바인딩 층은 **얇아야 한다.** 두꺼워지면 규칙이 새어 들어온 것이다.
"이 값이 왜 이렇게 나왔지"를 바인딩 층에서 물어야 한다면 이미 경계가 무너졌다.
### 어디가 순수 층인가 — 판정 질문
> **엔진을 켜지 않고 이 코드를 호출해 결과를 단언할 수 있는가?**
못 하면 순수 층이 아니다. 옮기거나, 못 옮기는 부분을 바인딩 층으로 밀어낸다.
### 설정과 규칙을 잇는 코드는 규칙 층에 두지 않는다
원격 설정·플레이버 설정이 규칙 층에 스며들면 **플래그 하나가 규칙을 바꾸는 경로가 숨는다.**
규칙은 값을 인자로 받아야 하고, 그 값을 어디서 읽는지는 몰라야 한다.
### 검사
경계는 **선언하는 것만으로는 유지되지 않는다.** 당장은 언제나 불편하고 이득은 나중에 온다 —
사람의 주의력에 맡기면 무너진다.
```bash
echo '{"engine":"flutter","sources":[{"file":"lib/core/a.dart","text":"..."}]}' \
| node "${CLAUDE_PLUGIN_ROOT}/lib/purity-check.mjs"
```
`layers.json` 에 엔진별 기본 경계가 있다. 프로젝트가 다르면 `declarations` 로 덮어쓴다.
**선언에 `why` 가 없으면 던진다** — 이유 없는 경계가 가장 먼저 지워지기 때문이다.
위반 보고에도 `why` 가 실려 나온다. "무엇을 어겼다"만 오면 읽는 사람은 경계를 지우는 쪽을 택한다.
줄 단위 문자열 포함으로 보는 **근사**다(`approx: true`). 언어 파서를 넣으면 언어마다
파서가 필요해지고 엔진 불가지론이 깨진다. 주석 줄은 건너뛴다 —
"이 파일은 엔진에 의존하지 않는다"라고 적은 문서 주석이 위반으로 잡힌 적이 있다.
## 3. 수직 슬라이스를 먼저 만든다
넓게 깔지 않는다. **코어 루프 한 바퀴가 끝까지 도는 가장 얇은 경로**를 먼저 만든다.
들어가는 것: 루프 한 바퀴 · 진짜 입력 · 진짜 판정 · 결과가 남는 것
빼는 것: 메뉴 · 설정 · 저장 · 튜토리얼 · 두 번째 콘텐츠 · 연출 · 최종 에셋
**수직 슬라이스가 재미없으면 콘텐츠를 더 넣어도 재미없다.** 그걸 여기서 알아내는 것이 목적이다.
슬라이스 전에 에셋을 만들지 않는다 — 루프가 바뀌면 에셋이 같이 버려진다.
## 4. 무엇을 서버가 결정하는가
> **이 값이 조작되면 다른 플레이어의 경험이 나빠지는가?**
그렇다면 서버가 정한다. 아니면 클라이언트가 정해도 된다.
| 판단 | 위치 |
|---|---|
| 랭킹·순위에 들어가는 점수 | 서버 검증. 필드 허용 목록과 단조 증가를 강제한다 |
| 재화 획득·소모 | 서버. 클라이언트가 올린 잔액을 그대로 쓰지 않는다 |
| 연출·카메라·입력 예측 | 클라이언트. 서버를 기다리면 조작감이 죽는다 |
| 혼자 하는 진행도 | 클라이언트. 조작해도 자기 게임만 망가진다 |
**서버가 없어도 되는 것을 서버에 두지 않는다.** 온라인 의존은 비용·지연·장애를 한꺼번에 들인다.
부가 서비스(랭킹·추천·크로스프로모)의 **부재·실패·타임아웃이 부팅·플레이·채점을 막으면 안 된다.**
막으면 그건 부가 서비스가 아니라 필수 의존이고, 그렇게 설계했다는 사실을 몰랐던 것이다.
여럿이 같은 것을 푸는 경험은 **서버 없이도 만들 수 있다** — 날짜와 id 로 시드를 유도하면
모두가 같은 판을 받는다. 서버 왕복도, 비용도, 장애 구간도 없다.
## 5. 다작이면 패리티가 규율이다
다작은 "게임을 여러 개 만든다"가 아니라 **한 코드베이스의 한계 비용을 0 에 붙여두는 것**이다.
코드가 갈리는 순간 새 앱 하나의 비용이 0 에서 멀어지고, 그게 다작이 성립하는 유일한 근거였다.
**분기하지 않는다. 설정으로 올린다.**
```bash
echo '{"flavors":["a","b"],"sources":[...]}' \
| node "${CLAUDE_PLUGIN_ROOT}/lib/parity-check.mjs"
```
플레이버가 둘 미만이면 **위반 0 이 아니라 `applicable: false`** 다 — 깊게 트랙에는
플레이버라는 개념이 없다. "검사해서 깨끗함"과 "검사 대상이 아님"은 다른 상태다.
식별자를 **다루는 것**은 위반이 아니다(설정을 읽는 코드는 당연히 id 를 만진다).
위반은 식별자를 **리터럴과 비교해 동작이 갈리는 것**이다. 널 검사·타입 검사·변수 대 변수는
갈리는 게 아니라 같은 코드가 모든 대상에서 도는 모습이다.
트랙 판정은 `track` 스킬이 한다. 여기는 다작으로 정한 뒤의 **코드 규율**이다.
## 하지 말 것
- 코어 루프를 확정하기 전에 경제·레벨·에셋을 만들지 않는다. 루프가 바뀌면 전부 버린다.
- 순수 층에 엔진 타입을 들이지 않는다. 들인 만큼 검증이 느려지고, 느린 검증은 결국 안 돈다.
- 경계를 `why` 없이 선언하지 않는다. 이유 없는 경계가 가장 먼저 지워진다.
- 수직 슬라이스 전에 최종 에셋을 만들지 않는다.
- 서버가 없어도 되는 것을 서버에 두지 않는다.
- 다작 트랙에서 코드로 분기하지 않는다. 설정으로 올린다.