Files
maplecontest/RULES.md
gahusb 4d8fa0f40f docs(rules): 카드 kind↔효과 규칙(§9)+검증도구 + codex 규칙 보강 (협업자 하네스 반영)
이번 세션에서 발견·수정한 하네스 학습을 저장소(공유 매개)에 반영해
협업자(codex 등)도 적용받게 한다. 메모리는 로컬이라 공유 안 됨.

- RULES.md §9 신설 (카드 kind ↔ 효과 일치): ResolveCardDrop 라우팅
  (Attack=몬스터드롭/Skill·Power=스윕/Status=unplayable)·Power 분기가
  damage/aoe 무시 → 데미지=Attack, block/유틸=Skill, 지속효과=Power.
  안 맞으면 사용불가/死카드(아이언 바디·분노 사고).
- tools/verify/cardkinds.mjs 신설: kind↔효과 위반(Attack-무데미지/
  Power-무효과/미지원 kind) 정적 검출(이상 0=exit 0). 현재 main 147장 0,
  Defend=Attack·Rage=Power 위반은 2건 검출 확인.
- docs/codex-working-rules.md 6~9 추가: ⑥ main 머지 충돌 시 머지 전체
  revert 금지(소스 충돌만 해소·산출물 재생성 — #98/#99가 #96 날린 사고)
  ⑦ 카드 kind 일치+cardkinds 검증 ⑧ 변경 후 검증 스위트 ⑨ RULES.md 권위.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUvHKjrt8jqLzDeCsRRGmj
2026-06-30 17:11:11 +09:00

14 KiB

RULES.md — SlayMaple 하네스 엔지니어링 규칙

AI 에이전트(Claude Code 등)와 협업자가 이 저장소에서 토큰을 낭비하지 않고 안전하게 작업하기 위한 공용 규칙. Claude Code는 CLAUDE.md가 이 파일을 임포트하므로 자동 적용된다. 다른 도구(Codex 등)를 쓰면 세션 시작 시 이 파일을 읽혀라.


1. 생성 산출물은 읽지도, 고치지도 않는다 (가장 중요)

이 저장소의 큰 파일들은 전부 생성기 산출물이다. 직접 읽으면 토큰이 증발하고, 직접 고치면 다음 재생성 때 사라진다.

산출물 (절대 Read/Edit 금지) 크기 단일 소스 (여기만 편집) 재생성 명령
ui/*.ui (Default·Select·Lobby·Run·Deck·Popup·Toast UIGroup 7종) 9KB~4.5MB 메이커 저작 (생성기 미생성, 2026-06-17~) — 메이커에서 시각 편집 (없음)
RootDesk/MyDesk/SlayDeckController.codeblock ~270KB data/*.json + tools/deck/(gen-slaydeck.mjs+lib/+cb/) node tools/deck/gen-slaydeck.mjs
Global/common.gamelogic ~1KB
map/map01.map~map05.map, map/lobby.map 각 ~210KB tools/map/·tools/monster/·tools/camera/·tools/player/ (↓ 보조 생성기) 해당 생성기
RootDesk/MyDesk/CombatMonster.codeblock ~2KB tools/monster/gen-combat-monster.mjs node tools/monster/gen-combat-monster.mjs
RootDesk/MyDesk/PlayerLock.codeblock ~2KB tools/player/gen-player-lock.mjs node tools/player/gen-player-lock.mjs
RootDesk/MyDesk/MapCamera.codeblock ~2KB tools/camera/gen-camera.mjs (값: data/camera.json) node tools/camera/gen-camera.mjs
RootDesk/MyDesk/LobbyNpc.codeblock·LobbyMobility.codeblock 각 ~2-3KB tools/player/gen-lobby-npc.mjs node tools/player/gen-lobby-npc.mjs
Global/SectorConfig.config ~1KB tools/map/gen-maps.mjs·gen-lobby-map.mjs (패치) 해당 생성기
  • .claude/settings.json의 permissions.deny가 위 파일의 Read/Edit/Write 도구 사용을 차단한다 (이 저장소를 열면 자동 적용). deny는 globui/*.ui·map/*.map·RootDesk/MyDesk/*.codeblock·Global/common.gamelogic·Global/SectorConfig.config. 따라서 메이커 저작 codeblock/UI(Monster·MonsterAttack·PlayerAttack·PlayerHit·UIPopup·UIToast.codeblock, 그리고 모든 ui/*.ui — UI는 6개 UIGroup으로 메이커 저작) Read/Edit 금지 — 이들은 생성기가 없으니 메이커에서 편집한다(텍스트 도구로 X). codeblock은 한 줄짜리 JSON이라 Read 시 토큰 폭발.
  • 게임 로직 수정 = tools/deck/gen-slaydeck.mjs(오케스트레이터) + tools/deck/cb/*.mjs(codeblock Lua) 또는 data/*.json(데이터) 수정 → 재생성(SlayDeckController.codeblock+common.gamelogic만, .ui 미접근) → 통째로 커밋. UI 수정 = 메이커에서(생성기는 UI를 안 만든다).
    • codeblock 메서드(Lua)는 관심사별 모듈 tools/deck/cb/*.mjs(boot·screens·npc·navigation·layout·combat·hand·deckview·items·map·shop 등 20종 — 화면전환=screens·NPC=npc·포지션=navigation(월드 텔레포트)/layout(UI 슬롯 배치). 새 메서드는 관심사에 맞는 모듈에 작성하고, 한 모듈이 비대해지면 분할한다. 횡단 관심사를 한 모듈에 몰아넣지 않는다). 공유분: 상수·데이터·lua 테이블 = tools/deck/lib/{ui-helpers,data,codeblock}.mjs(cb가 import — MAX_MONSTERS=4 등). prop 103개는 오케스트레이터 writeCodeblocks에 유지. 특정 메서드 수정은 cb/<name>.mjs만(의존: orchestrator→cb→lib 단방향). cb 모듈은 원본 메서드 순서 보존이 바이트동일 조건. UI emit(옛 hud/*.mjs 15종·gen-cardhand.mjs)은 tools/deck/legacy/로 이관 — 휴면(생성기 미사용): UI가 메이커 저작이라 생성기가 안 만든다. (롤백용 legacy/upsert-ui.mjs는 직접 실행 시에만 옛 DefaultGroup.ui를 재생성.)
    • 리팩터 시 출력 바이트-동일 검증: node tools/deck/gen-slaydeck.mjsnode tools/verify/diffcheck.mjs [ref](워킹트리 vs ref(기본 HEAD) 줄바꿈 정규화 비교 — 산출물 경로를 명령줄에 노출 안 해 deny 회피). 산출물 M은 보통 autocrlf churn이니 git checkout --로 복원.
    • UI 전면 메이커 저작 (2026-06-17~): 단일 DefaultGroup을 7개 UIGroup으로 분리 — DefaultGroup(MainMenu+월드조작), SelectUIGroup(charselect/job), LobbyUIGroup(lobby/board/soulshop), RunUIGroup(combat/map/shop/rest/treasure/reward/cardhand/deck), DeckUIGroup(덱 도감), PopupGroup·ToastGroup. 컨트롤러(cb/*.mjs)는 엔티티 경로(/ui/<UIGroup>/<Hud>/...)로 텍스트·이미지·표시숨김·상태기반 위치/크기/색을 런타임 주입(레이아웃=메이커, 내용=컨트롤러 — 메이커가 이 경로 유지 필수). 몬스터 슬롯 = RunUIGroup/CombatHud/MonsterStatus{1..4}(자식 Name·Hp·Intent·HpBarFill·Buffs·BlockBadge·TargetMarker; TargetFrame 없음). 부트 흐름: OnBeginPlay→MainMenu→(MainMenu/NewGameButton)→로비→run NPC(OnLobbyNpcInteract id=="run")→charselect→런. 재연결 검증: node tools/verify/cbgap.mjs(cb 참조 경로↔.ui GAP 0이어야) + 재생성 후 git status -- ui/ 변경 0(생성기 .ui 미접근 증명). 섹션→UIGroup 일괄 remap 마이그레이션은 tools/deck/reconnect-ui-paths.mjs(멱등). UIGroup별 .ui 분포 확인은 tools/verify/uimap.mjs.
    • 머지 충돌(gen-slaydeck.mjs): 다른 브랜치가 단일체를 수정해 충돌나면, 그쪽 버전(git checkout --theirs tools/deck/gen-slaydeck.mjs)을 취해 콘텐츠 마커 기반으로 재모듈화(라인인덱스 X — 줄 추가에 안전·export 이름 자동 파생·const x=[] 직전 전문 상수 walk-back 포함) 후 node tools/verify/diffcheck.mjs origin/main으로 ui·codeblock 바이트-동일 확인(손실 0 증명). codeblock 메서드·patchCommon은 오케스트레이터 잔류라 그쪽 변경은 자동 보존됨.
  • 보조 생성기(각자 자기 산출물의 단일 소스 — 위 표의 메인 gen-slaydeck.mjs 외):
    • tools/camera/gen-camera.mjsMapCamera.codeblock + map01~05 카메라 부착 (값 data/camera.json)
    • tools/map/gen-maps.mjsmap02~05 + Global/SectorConfig.config (map01 템플릿 클론)
    • tools/map/gen-lobby-map.mjsmap/lobby.map + SectorConfig.config
    • tools/map/gen-map-encounters.mjs → map01~05 노드 타입별 몬스터 그룹 재구성
    • tools/monster/gen-combat-monster.mjsCombatMonster.codeblock + map01~05 부착
    • tools/monster/freeze-turn-monsters.mjs → 몬스터 .model·맵 AI 컴포넌트 제거
    • tools/player/gen-player-lock.mjsPlayerLock.codeblock + map01~05 부착
    • tools/player/gen-lobby-npc.mjsLobbyNpc.codeblock·LobbyMobility.codeblock
    • tools/player/freeze-turn-player.mjsGlobal/DefaultPlayer.model 이동 0 고정
    • (옛 tools/deck/gen-cardhand.mjs·hud/*.mjstools/deck/legacy/로 이관 — 휴면, UI 메이커 저작 전환)

2. 산출물 검증은 카운트로, 내용 출력 금지

재생성 결과 확인이 필요하면 본문을 출력하지 말고 존재/개수만 확인한다:

node tools/deck/gen-slaydeck.mjs                       # 성공 메시지 1줄
grep -c "TreasureHud" ui/DefaultGroup.ui               # 개수만
grep -c "CalcPlayerAttack" RootDesk/MyDesk/SlayDeckController.codeblock
  • ⚠️ codeblock은 한 줄이 수만 자(JSON 직렬화)다. grep(내용 출력)·sed -n 광역 범위 출력은 한 줄만 걸려도 토큰 폭발 → grep -c/grep -l/grep -o '짧은패턴'만 사용.
  • Claude Code의 Grep 도구를 산출물에 쓸 때는 output_mode: count 또는 files_with_matches만. content 모드 금지.
  • 진짜 내용 확인이 필요하면 좁은 grep -o 또는 python으로 슬라이스해서 수 줄 이내로.

3. 탐색 규칙

  • 코드 탐색은 tools/·data/·docs/만 대상으로. 저장소 전체 grep은 산출물이 걸리므로 경로를 지정한다.
  • git add -Agit status --short로 산출물 외 의도치 않은 변경이 없는지 확인 (산출물 diff는 보지 않는다 — 생성기가 결정적이므로 소스 리뷰로 충분).
  • 게임 동작 확인은 메이커 플레이테스트(스크린샷·로그)로 한다. 산출물 정독으로 동작을 추론하지 않는다.

4. Git/PR 절차

  • 브랜치 → 커밋(기능 단위) → push → PR은 반드시 node tools/git/gitea-pr.mjs (인라인 curl -d 한글 본문은 Windows에서 CP949로 깨짐 — PR #34~41 사고).
    • 제목/본문은 UTF-8 spec JSON 파일로 작성 후 create <spec.json> / merge <번호>.
    • PR 제목과 본문은 한국어로 작성한다.
  • 산출물 재생성 커밋은 소스 변경 커밋과 분리하거나, 메시지에 "산출물 재생성"을 명시.
  • PR 머지 후 브랜치 삭제: 머지된 feature/*·docs/* 브랜치는 로컬·원격 모두 삭제한다. 삭제 전 git merge-base --is-ancestor origin/<브랜치> origin/main로 완전 머지 확인(종료코드 0=완전 머지 → 삭제 가능). main에 없는 커밋이 남은 브랜치와 codex/* 등 작업 중 브랜치는 보존한다.
  • ⚠️ main 머지 충돌 시 "머지 전체 revert" 금지 (타인 작업 유실 방지): 작업 브랜치에 git merge main(또는 origin/main) 했다가 충돌·문제가 나도 그 머지 커밋을 통째로 git revert 하지 말 것. main에 먼저 들어간 타인의 작업이 collateral로 전부 사라진다. 대신 소스 충돌만 해소하고 산출물(codeblock 등)은 재생성한다. 충돌이 산출물뿐이면 git checkout --theirs/재생성으로 끝. (2026-06-30 사고: codex #98/#99가 main 머지 후 그 머지를 revert해 #96의 버그수정 11개를 전부 날림 → 다시 재통합해야 했다. 복구는 git diff <pre-merge> <내브랜치> -- <소스> | git apply --3way 로 소스만 재적용 후 재생성하면 codex 변경과 충돌 없이 양립.)

5. 메이커(MSW) 연동 주의

  • git pull 후 메이커에서 로컬 워크스페이스 reload 필수 (안 하면 메이커의 stale 상태가 디스크를 덮어씀).
  • 재생성 후 메이커가 켜져 있으면 refresh → 빌드 콘솔 0 에러 확인.
  • 카드/유물/물약 이미지는 공식 maplestory 리소스 RUID만 (계정 업로드 리소스는 로컬 워크스페이스에서 흰 박스).

6. 밸런스·맵 규칙 동기화

전투 규칙과 맵 생성은 Lua(생성기 내장)와 JS가 이중 구현이다. 한쪽을 고치면 반드시 다른 쪽도:

영역 Lua (gen-slaydeck.mjs 내) JS 미러 테스트
전투 규칙 PlayCard·CalcPlayerAttack 등 tools/balance/sim-balance.mjs node --test tools/balance/sim-balance.test.mjs
맵 생성 GenerateMap tools/map/rogue-map.mjs node --test tools/map/rogue-map.test.mjs

7. UI 숫자 표기

  • UI 텍스트에서는 정수값인 숫자에 .0을 붙이지 않는다. 1.0/1.0이 아니라 1/1처럼 표시한다.
  • 생성기 내 Lua UI 코드에서 number 또는 숫자 문자열을 텍스트에 붙일 때는 FormatNumber 같은 포맷 헬퍼를 우선 사용한다.
  • 소수부가 플레이어에게 의미 있을 때만 소수점 표기를 유지한다.

8. codeblock 변수명

  • cb(tools/deck/cb/*.mjs)의 Lua 지역변수는 의미가 드러나는 이름으로 작성한다(eentity, ncount, mmonster, lplocalPlayer, ssoulPoints, trtransform). a/b/c 같은 무의미 단일문자 변수는 금지.
  • 단, 순수 반복 인덱스 i/j/r/c는 관용상 허용한다.
  • 새 cb 메서드를 작성하거나 기존 메서드를 손댈 때 이 규칙을 적용한다(대규모 일괄 개명은 별도 작업으로).

9. 카드 데이터 규칙 (kind ↔ 효과 일치)

새 카드를 추가/수정할 때 data/cards.jsonkind는 카드의 효과·사용 메커니즘과 반드시 일치해야 한다. 안 맞으면 카드가 사용 불가거나 재생 시 아무 효과 없는 死카드가 된다(런타임 에러도 안 나고 sim 테스트도 못 잡음 — 정적 검증 필수).

  • ResolveCardDrop 사용 라우팅이 kind별로 다름: Attack=몬스터 위에 드롭(FindMonsterAtTouch>0 필요)·Skill/Power=위로 스윕(ui.y>-180Status=unplayable. → block·디버프·드로우 등 유틸만 있고 데미지가 없는 카드를 Attack으로 두면 위로 스윕으로 사용할 수 없다(2026-06-30 아이언 바디 사고: block만 있는 방어카드가 Attack이라 전사 시작덱 4장이 먹통 → Skill로 수정).
  • PlayCardPower 분기는 PlayerPowers 등록만 하고 damage/aoe를 무시한다. → 데미지 카드=Attack, 방어/유틸=Skill, 지속효과=Power(단 powerEffect 또는 지속/온플레이 power 필드 — turnStart*·dex·thorns·intangible·attackPoison·drawDamage·shivX·cardPlayed* 등 — 이 있어야 함). Power인데 power 효과 필드가 없으면 死카드(2026-06-30 분노 사고: damage:4/aoe만 있어 Power 분기서 무시됨 → kind Power→Attack으로 기능화).
  • 새 효과 필드는 docs/card-effect-fields.md 사전에 등록하고 Lua(tools/deck/cb/*.mjs) + JS 미러(tools/balance/sim-balance.mjs) 양쪽에 핸들러 구현(§6). 한쪽만 있으면 게임↔시뮬 드리프트.
  • 검증: node tools/verify/cardkinds.mjs — kind↔효과 위반(Attack-무데미지 / Power-무효과 / 미지원 kind)을 정적 검출(이상 0 = exit 0). 카드 추가/수정 후 반드시 실행. (관련 가드: 미선언 self.X = cbprops.mjs, UI 경로 = cbgap.mjs, 이중구현 = sim-balance.test.mjs.)