8.2 KiB
Phase 1b — codeblock 메서드 모듈화 구현 계획
For agentic workers: REQUIRED SUB-SKILL: superpowers:executing-plans 로 태스크 단위 구현. 단계는
- [ ]체크박스.
Goal: gen-slaydeck.mjs writeCodeblocks()의 메서드 161개를 연속-런 모듈 cb/*.mjs로 분리하되 출력 SlayDeckController.codeblock은 바이트 동일.
Architecture: 단방향 의존 orchestrator→cb→lib. method/prop/codeblock 헬퍼+공유상수를 lib/codeblock.mjs로. 메서드는 원본 순서 보존을 위해 기능 버킷이 아닌 연속 구간으로 나눠 writeCodeblocks가 순서대로 spread-concat. prop 103개는 오케스트레이터 유지.
Tech Stack: Node ESM. 검증 = diffcheck(codeblock 바이트 동일) + 미러 node --test.
의존: Phase 1(#70)의 모듈 gen-slaydeck 위에 스택(feature/cb-modularization). #70 머지 후 main에 리베이스.
🔑 검증 게이트 (모든 Task 공통)
각 추출 후:
node tools/deck/gen-slaydeck.mjs
node tools/verify/diffcheck.mjs
합격: RootDesk/MyDesk/SlayDeckController.codeblock(+ui·common) IDENTICAL. 워킹트리 M은 autocrlf churn → git checkout --로 복원, 산출물은 커밋 안 함(소스만).
Task 1: lib/codeblock.mjs — 헬퍼 + 공유 상수 추출
Files: Create tools/deck/lib/codeblock.mjs; Modify tools/deck/gen-slaydeck.mjs
-
Step 1:
lib/codeblock.mjs생성. gen-slaydeck.mjs에서 이동:- 함수:
prop·method·codeblock(정의 본문 그대로). writeCodeblocks지역 상수 9개(현:292-300):RUN_LENGTH(5)GOLD_PER_WIN(25)CARD_PRICE(30)REST_HEAL(30)RELIC_PRICE(60)ACT_COUNT(5)ACT_MAPS(['map01'..'map05'])LOBBY_MAP('lobby')LOBBY_SPAWN('Vector3(-5, 0.03, 0)'). → writeCodeblocks 본문에서 제거(import로 대체).- 끝에
export { prop, method, codeblock, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN }; - ⚠️
prop/method/codeblock이 다른 헬퍼(없음 — 순수)·데이터를 참조하지 않는지 확인. 참조 시 함께 import.
- 함수:
-
Step 2: gen-slaydeck.mjs에
import { prop, method, codeblock, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from './lib/codeblock.mjs';추가(기존 lib import 옆). -
Step 3: 검증 게이트 → codeblock IDENTICAL → churn 복원.
-
Step 4: 커밋
git add tools/deck/lib/codeblock.mjs tools/deck/gen-slaydeck.mjs
git commit -m "refactor(cb): lib/codeblock.mjs로 헬퍼·상수 추출 (출력 바이트 동일)"
메서드 추출 공통 레시피 (Task 2~의 각 런)
writeCodeblocks의 const combat = codeblock('SlayDeckController','SlayDeckController', [<props>], [\n method('OnBeginPlay', …),\n method('ReqLoadAscension', …),\n … 161개 …\n]) 에서 메서드 배열을 런별로 분리:
tools/deck/cb/<name>.mjs생성:
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
import { /* lib/data.mjs 전체 — 자동 파생 */ } from '../lib/data.mjs';
import { /* lib/ui-helpers.mjs 전체 — 자동 파생 */ } from '../lib/ui-helpers.mjs';
export const <name>Methods = [
method('<First>', `…`, …),
…, // ← 원본 method() 호출 verbatim 이동
method('<Last>', `…`, …),
];
- writeCodeblocks의 해당 런 method() 호출 스팬을 제거하고, methods 배열을 spread로 교체:
codeblock(…, [<props>], [ ...bootMethods, ...stateMethods, …, ...shopMethods ]). - 검증 게이트(codeblock IDENTICAL) → churn 복원 → 커밋.
- ⚠️ 메서드가 writeCodeblocks 지역변수/다른 메서드를 JS레벨로 참조하면(드묾) undefined throw/diffcheck로 노출 → 그 변수도 lib로 옮기거나 인자화.
- import 이름은 lib export에서 자동 파생(누락 방지). 메서드 본문은 Lua 문자열이라 보간(
${RUN_LENGTH}·${luaCardsTable(...)})만 JS평가.
런 → 모듈 경계 (원본 순서, 161개)
| 모듈 | export | 첫 메서드 → 끝 메서드 | 수 |
|---|---|---|---|
cb/boot.mjs |
bootMethods |
OnBeginPlay → AscStartHpPenalty |
11 |
cb/state.mjs |
stateMethods |
HideGameHud → CloseBoard |
12 |
cb/soul.mjs |
soulMethods |
ShowSoulShop → ApplySoulUnlocks |
11 |
cb/charselect.mjs |
charSelectMethods |
ShowCharacterSelect → SetEntityEnabled |
5 |
cb/run.mjs |
runMethods |
StartRun → ReviveMonsterEntity |
6 |
cb/deckturn.mjs |
deckTurnMethods |
Shuffle → RenderPiles |
8 |
cb/deckview.mjs |
deckViewMethods |
OpenDeckInspect → ApplyAllDeckCardVisual |
12 |
cb/hand.mjs |
handMethods |
GetHandSlotX → SelectDiscardSlot |
18 |
cb/combat.mjs |
combatMethods |
PlayCard → ContinueAfterBoss |
20 |
cb/jobs.mjs |
jobMethods |
ShowJobChoice → SetJob |
5 |
cb/runend.mjs |
runEndMethods |
TeleportToActMap → EndRun |
3 |
cb/render.mjs |
renderMethods |
BuffsLabel → RenderRun |
12 |
cb/reward.mjs |
rewardMethods |
CardPool → PickReward |
4 |
cb/items.mjs |
itemMethods |
HasRelic → RenderRelics |
12 |
cb/tooltip.mjs |
tooltipMethods |
BuildCardKeywordTooltip → HideTooltip |
6 |
cb/map.mjs |
mapMethods |
ShowMap → PickNode |
7 |
cb/shop.mjs |
shopMethods |
ShowShop → OpenChest |
9 |
최종 concat 순서(= 원본): [ ...bootMethods, ...stateMethods, ...soulMethods, ...charSelectMethods, ...runMethods, ...deckTurnMethods, ...deckViewMethods, ...handMethods, ...combatMethods, ...jobMethods, ...runEndMethods, ...renderMethods, ...rewardMethods, ...itemMethods, ...tooltipMethods, ...mapMethods, ...shopMethods ].
Task 2: 런 추출 배치 A (말단부터 — 위험 낮은 순)
Files: Create cb/shop.mjs map.mjs tooltip.mjs items.mjs reward.mjs; Modify gen-slaydeck.mjs
- 레시피로 shop → map → tooltip → items → reward 추출·검증·커밋(런 1개당 또는 묶음당 게이트 통과 필수).
Task 3: 런 추출 배치 B
Files: Create cb/render.mjs runend.mjs jobs.mjs combat.mjs; Modify gen-slaydeck.mjs
- 레시피로 render → runend → jobs → combat 추출·검증·커밋.
Task 4: 런 추출 배치 C
Files: Create cb/hand.mjs deckview.mjs deckturn.mjs run.mjs; Modify gen-slaydeck.mjs
- 레시피로 hand → deckview → deckturn → run 추출·검증·커밋.
Task 5: 런 추출 배치 D (앞부분 — 마지막)
Files: Create cb/charselect.mjs soul.mjs state.mjs boot.mjs; Modify gen-slaydeck.mjs
- 레시피로 charselect → soul → state → boot 추출·검증·커밋. 완료 후 writeCodeblocks는 props 배열 +
[ ...17 spreads ]+ write만 남아야 함.
Task 6: 마무리 — RULES + 회귀 + PR
Files: Modify RULES.md
- Step 1: RULES §1의 gen-slaydeck 모듈 설명에
tools/deck/cb/*.mjs(메서드)·tools/deck/lib/codeblock.mjs(헬퍼·상수) 추가. 단일소스 표/보조 생성기 일관성 유지. - Step 2: 회귀:
node --test tools/balance/sim-balance.test.mjs·node --test tools/map/rogue-map.test.mjs(exit 0). - Step 3: 최종 재생성 + 검증 게이트(누적 codeblock IDENTICAL).
git status --short산출물 변경 없음. - Step 4: RULES 커밋 → push → PR(
node tools/git/gitea-pr.mjs create <spec.json>, UTF-8). PR 제목·본문 한국어(RULES §4).
Self-Review
- 스펙 커버리지: lib/codeblock(T1) · 메서드 17런 모듈화(T2~5) · prop 유지(범위 명시) · 바이트동일 게이트(공통) · RULES(T6) · 미러회귀(T6). 누락 없음.
- 플레이스홀더: 런 경계는 첫/끝 메서드로 구체 지정(161개 합), 상수 9개 명시, import 자동 파생. "verbatim 이동"은 리팩터 특성(바이트 검증이 정확성 보장).
- 타입 일관성: export명(
xMethods)↔concat spread 일치. lib/codeblock export↔orchestrator/cb import 일치. - 리스크: 메서드 JS레벨 외부참조 → diffcheck/throw 즉시 노출, 증분으로 범위 최소. 단방향 의존 순환 없음.