두 점으로 개장하는 전시
숙지Generative Hours라는 이름의 전시관을 엽니다. 아트리움으로 들어와 갤러리에서 작품을 고르고, 전시실에서 작품 하나를 전체화면으로 만집니다. 개장 시점의 소장품은 단 두 점이지만, 구조는 예순 점을 걸 수 있게 짭니다.
왼쪽이 01 Whispering Currents, 노이즈 벡터장 위를 흐르는 수만 개의 입자. 오른쪽이 02 Morphogenesis, 두 화학종이 서로를 먹으며 자라는 격자. 전자는 에이전트를 매 프레임 옮기고, 후자는 격자 전체를 수식으로 갱신합니다. 일부러 코드 모양이 정반대인 짝을 골랐습니다.
이 캡스톤을 마치면 다음 네 가지를 설명할 수 있고, 다시 만들 수 있습니다.
| 목표 | 검증되는 순간 |
|---|---|
| "선 구체, 후 추상"의 리팩터 감각 | M3에서 중복 두 벌을 눈으로 본 뒤 계약을 뽑는데, 화면은 전과 똑같을 때 |
| 공통 엔진 위의 인터페이스(Piece) 설계 | 코드 모양이 정반대인 두 작품이 같은 계약으로 한 전시에서 돌 때 |
| 데이터 주도 확장 구조 | 작품 추가가 "data.js 한 항목 + 파일 하나"로 끝날 때 (강화 카드 A에서 체감) |
| 기계 검증을 배포 게이트로 세우는 법 | M5에서 verify.mjs가 전부 PASS한 뒤에만 전시가 열릴 때 |
이 캡스톤은 새 기술을 가르치지 않습니다. Ch1~6에서 배운 것을 실전 배치합니다.
| 챕터 | 이 캡스톤에서 재등장하는 곳 |
|---|---|
| Ch1 설치부터 Headless까지 | M0~전체: 세션 운용, 지시 → 실행 → 검증 루프 |
| Ch2 Subagents | M3~M4: 계약 추출 리팩터의 diff 리뷰를 code-reviewer에게, 전시 구조와 작품 다듬기 병행 |
| Ch3 Admin Setup | 배치 결정의 배경: 이 캡스톤은 Bedrock을 쓰지 않아 모델 통제선 밖에서도 완주됩니다, "AI가 필요 없는 곳엔 안 쓴다"도 설계입니다 |
| Ch4 Settings, 훅과 스킬 | OPT: 하네스 성숙화, 훅과 스킬을 프로젝트 자산으로 승격하는 마무리 의식 |
| Ch5 CLI Reference | 확장 방향: verify.mjs를 헤드리스 파이프라인의 게이트로, "PASS 없이는 배포 없음"의 자동화 |
| Ch6 Agent SDK | 확장 방향: 작품 생성기, query 한 번이 새 Piece 파일과 data.js 항목을 만드는 다음 단계 |
구현 청사진, 전시의 뼈대
참고M1 설계가 이 구조로 수렴하도록 방향을 잡아 주는 구현 지도입니다. 계획이 이상하게 흐르면 여기로 돌아와 대조하세요.
[프레임 루프] requestAnimationFrame, 매 프레임
engine.js: 리사이즈/DPR ──> dt/t 시계 ──> piece.update(dt) ──> piece.draw(ctx)
│
전시실 전환 시: 이전 piece.dispose() ──> 새 Piece 생성 (누수 없이, 속도 그대로)
[관람 경로] 방문자의 동선
아트리움(랜딩) ──> 갤러리(data.js 카탈로그 → 카드 그리드) ──> 전시실(#work-NN 해시 딥링크)
[출고 경로] 단 한 번, 열기 전에
verify.mjs(카탈로그 무결 + Piece 계약 검사) ── 전부 PASS ──> cdk deploy ──> S3(비공개) + CloudFront(OAC)
Level 200의 핵심 구분입니다: 매 프레임 도는 루프, 데이터가 이끄는 동선, 기계가 지키는 게이트. 작품은 그리기에만 집중하고 나머지는 엔진과 카탈로그와 검사기가 맡습니다.
| 구성 | 파일 | 핵심 계약 |
|---|---|---|
| 엔진 | site/js/engine.js | 캔버스 리사이즈, DPR, RAF 루프, dt/t 시계를 공통 제공, 작품은 그리기만 |
| 작품 01 | site/js/pieces/01-currents.js | 벡터장 흐름결, 마우스를 끌면 결이 휘어진다 |
| 작품 02 | site/js/pieces/02-morphogenesis.js | 길게 누르면 소용돌이가 자라고 놓으면 방출, 01과 코드 모양이 정반대인 짝 |
| 계약 | Piece 기반 클래스 | 두 작품이 상속하는 공통 인터페이스, M3에서 중복을 보고 나서야 추출한다 |
| 카탈로그 | site/js/data.js | 순수 데이터, 작품 추가 = 항목 하나 + 파일 하나 |
| 전시 | site/index.html + main.js | 아트리움 → 갤러리 → 전시실 3단, #work-NN 해시 라우팅 |
| 검증과 인프라 | verify.mjs + CDK 스택 | 카탈로그/계약 검사가 배포 게이트, S3 비공개 + CloudFront OAC |
| 선택 | 이유 |
|---|---|
| 빌드 도구 없는 정적 사이트 | 작품 파일 하나를 브라우저로 열면 바로 도는 즉시성, 전시는 가벼워야 한다 |
| 선 구체, 후 추상 | 중복을 눈으로 보기 전의 추상은 추측이다, 두 벌을 만들고 나서 계약을 뽑는다 |
| 데이터 주도 카탈로그 | 구조(코드)와 내용(데이터)의 분리, 전시 확장이 편집이 된다 |
| verify.mjs 게이트 | 렌더 루프의 단위 테스트 대신, 계약과 카탈로그의 무결을 기계가 지킨다 |
사전 준비
10분프로젝트 폴더, 배포용 CDK 뼈대, 그리고 브라우저로 보는 통로를 준비합니다. Bedrock 키는 필요 없습니다.
aws sts get-caller-identity --query Account --output text
node -v && npx cdk --version
mkdir -p ~/capstone/media-art && cd ~/capstone/media-art
[ -f cdk.json ] || npx cdk init app --language typescript
(npx cdk bootstrap > /tmp/bootstrap.log 2>&1 &)
git config user.name "capstone" && git config user.email "capstone@lab.local"
mkdir -p site/js/pieces site/css
git add -A && git commit -m "chore: scaffold" 2>/dev/null || true
이 랩은 Phase마다 브라우저로 확인합니다. 정적 서버를 띄우고 프록시 경로로 접근하는 방식이 가장 간단합니다.
cd ~/capstone/media-art && python3 -m http.server 8000 --directory site
브라우저 주소창의 code-server URL 끝에 /proxy/8000/을 붙여 접속합니다.
아직 site/가 비어 있으니 빈 목록만 보이면 성공입니다.
/proxy/8000/)를 빠뜨리지 않았는지, 서버가 --directory site로 떠 있는지 확인하세요.
그래도 막히면 미리보기 없이 진행하고 M5 배포 후 CloudFront URL로 한 번에 확인해도 됩니다.
단 그 경우 M2와 M3의 체크포인트를 눈으로 못 보므로, 되도록 통로를 확보하세요.
/plugin marketplace add jarrodwatts/claude-hud
/plugin install claude-hud@claude-hud
/reload-plugins
/claude-hud:setup
설치가 끝나면 세션 하단 상태줄에 모델, 컨텍스트 사용량, 세션 상태가 표시됩니다. 긴 캡스톤에서 컨텍스트가 얼마나 남았는지 한눈에 보이므로, Phase 사이에 정리 시점을 잡기 좋습니다.
설계, 브레인스토밍
15분요구사항 7개 조항을 superpowers에 넘겨 설계와 Phase 계획을 뽑습니다. 두 작품의 알고리즘 상수가 프롬프트에 들어 있습니다.
이 미션의 바탕 학습: Ch1의 세션 운용과 Capstone Setup의 superpowers 워크플로.
cd ~/capstone/media-art && claude
/superpowers:brainstorming 다음 요구사항으로 "Generative Hours" 제너러티브 아트 전시 사이트를 설계하자.
1) 빌드 도구 없는 정적 사이트: site/index.html, site/css/style.css, site/js/*.js.
ES 모듈만 쓰고 외부 런타임 라이브러리는 금지(구글 폰트 CSS만 허용).
python3 -m http.server로 열리고 같은 파일이 그대로 S3에 올라간다
2) 작품 2점, 일부러 코드 모양이 정반대인 짝으로 만든다:
01 "Whispering Currents" - 2옥타브 Perlin 노이즈를 각도로 읽은 벡터장 위를 입자가 흐른다.
입자 수는 화면 면적에 비례해 6000~34000 사이로 묶는다.
잔상은 캔버스를 지우지 않고 매 프레임 rgba(5,5,7,0.066)으로 덮어 만든다. 합성은 lighter.
마우스 이동은 반경 150px의 바람. 길게 누르면 소용돌이가 1.4초에 걸쳐 충전되고(반경 90px에서
min(w,h)*0.42까지, 강도 1.4에서 5.2까지) 놓으면 방출되어 서서히 풀린다
02 "Morphogenesis" - Gray-Scott 반응-확산. Du=0.16, Dv=0.08, dt는 상수에 접어넣어 1로 둔다.
Float32Array 격자와 이중 버퍼, 3x3 라플라시안(중심 -1, 직교 0.2, 대각 0.05), 경계는 wrap.
프레임당 8스텝. 격자는 화면보다 거칠게 잡고(약 8만 칸) ImageData로 그려 부드럽게 확대한다.
F/k 프리셋 4개: 세포분열 .0367/.0649, 산호 .0545/.062, 표범 .035/.065, 미로 .029/.057.
드래그하면 V를 붓처럼 심는다
3) 전시 구조는 3단: 아트리움(랜딩) -> 갤러리(작품 카드 그리드) -> 전시실(작품 하나 전체화면 + 플래카드).
body[data-view]로 전환한다. 전시실의 캔버스는 단 하나이고 작품이 그 위에 마운트/언마운트된다
4) 카탈로그는 site/js/data.js에 순수 데이터로 분리한다. 작품을 더하는 일이 "data.js 한 항목 + 파일 하나"여야
한다. module이 null인 작품은 갤러리에 "Soon" 카드로 남는다
5) 작품 파일은 공통 Piece 기반 클래스를 상속한다. 엔진이 캔버스 리사이즈와 DPR, RAF 루프, dt/t 시계,
포인터 정규화, mount/destroy를 소유하고 작품은 setup()과 frame(dt,t)만 구현한다.
engine.js는 모듈 스코프에서 DOM을 만지지 않는다(브라우저 없이 계약을 검사하기 위해)
6) 구현 순서: Phase 1(작품 01을 추상 없이 한 파일로 끝까지) -> Phase 2(작품 02를 통째로 베껴 쓴 뒤
Piece 계약을 추출) -> Phase 3(3단 전시 구조와 카탈로그) -> Phase 4(CDK로 S3 + CloudFront 배포)
7) 시간 제약 2시간: 렌더 루프의 단위 테스트는 만들지 않는다. 대신 verify.mjs로 카탈로그와 Piece 계약을
정적 검사하고 나머지는 브라우저 육안 확인으로 갈음한다. 이 사이트는 Bedrock을 쓰지 않는다
결정이 필요하면 이 범위 안에서 최소한으로만 물어봐.
auto mode on을 선언해 두면 superpowers가
brainstorming → write-plan → execute-plan의 단계 전환과 승인 대기를 자동으로 이어 갑니다.
시간이 빠듯할 때 유용합니다. 되돌리려면 auto mode off입니다.
브레인스토밍 후 /superpowers:writing-plans으로 계획을 만들고,
Phase 4개 구성을 확인해 승인하세요.
Phase 1, 첫 픽셀
25분추상 없이 한 파일로 작품 01을 완주합니다. 20분 안에 화면이 살아 있어야 합니다.
이 미션의 바탕 학습: Ch1의 지시 → 실행 → 검증 루프. "추상 금지"는 게으름이 아니라 M3를 위한 포석입니다.
/superpowers:executing-plans Phase 1(첫 픽셀)만 구현하고 멈춰줘.
지금은 추상을 만들지 마. site/index.html에 전체화면 캔버스 하나, site/js/currents.js 한 파일만 두고
그 안에서 캔버스 리사이즈와 DPR 스케일, requestAnimationFrame 루프, dt/t 시계, 포인터 좌표를 직접 처리해라.
그 위에 2옥타브 Perlin 노이즈 벡터장, 입자 흐름, 잔상, 길게 누르는 소용돌이까지 완성해.
dt에는 1/20초 상한을 둬라(탭을 나갔다 돌아왔을 때 물리가 터지지 않게).
setup 마지막에 캔버스를 한 번 검정으로 칠해라(첫 프레임이 하얗게 번쩍이지 않게).
끝나면 로컬에서 보는 방법을 알려줘.
완성되면 코드에서 캔버스를 덮는 한 줄을 찾아 알파값을 바꿔 보세요. 이 한 줄이 점과 혜성의 차이입니다.
// 0.066 -> 0.4 로 올리면 잔상이 짧아지고, 0.01 로 내리면 화면이 하얗게 눌어붙는다.
// clearRect를 쓰면 잔상은 완전히 사라진다.
ctx.fillStyle = "rgba(5,5,7,0.066)";
ctx.fillRect(0, 0, w, h);
clamp(width * height / 38, 6000, 34000). 상수 하나를 박아두면 노트북에서는 느리고
4K 모니터에서는 텅 빕니다. 비용을 픽셀에 비례시키는 습관은 이 랩을 나가서도 씁니다.
Phase 2, 두 번째 작품과 계약의 탄생
30분이 랩의 정점입니다. 두 번째 작품을 일부러 통째로 베껴 쓴 다음, 그 중복을 보고 나서 계약을 뽑아냅니다.
이 미션의 바탕 학습: Ch2의 code-reviewer 서브에이전트, 계약 추출 리팩터의 diff 리뷰를 맡기기 딱 좋은 순간입니다.
/superpowers:executing-plans Phase 2의 앞쪽만 해줘. site/js/morphogenesis.js를 새로 만들어
Gray-Scott 반응-확산 작품을 구현해라. Du=0.16, Dv=0.08, 3x3 라플라시안(중심 -1, 직교 0.2, 대각 0.05),
경계는 wrap, 프레임당 8스텝, F/k 프리셋 4개, 드래그로 V 심기까지.
중요: 지금은 currents.js와 공통 코드를 공유하지 마라. 캔버스 셋업, DPR, RAF 루프, dt/t 시계, 포인터 처리를
일부러 그대로 다시 써라. 두 파일을 나란히 놓고 무엇이 중복인지 내 눈으로 보고 싶다.
다 되면 두 파일에서 거의 똑같은 부분의 줄 번호를 짚어서 알려줘.
이제 중복이 눈앞에 있습니다. 무엇이 공통인지 추측하지 않고 알게 된 상태에서 뽑아냅니다.
이제 currents.js와 morphogenesis.js의 중복을 site/js/engine.js의 Piece 기반 클래스로 추출해라.
Piece가 소유할 것: 캔버스 리사이즈와 DPR 스케일(DPR은 2로 상한), RAF 루프, dt/t 시계(dt는 1/20초 상한),
포인터 정규화({x, y, vx, vy, active, down}), mount()과 destroy().
작품이 구현할 것: setup()과 frame(dt, t)는 필수, onResize/onPointerDown/onPointerUp/controls(host)는 선택.
슬라이더와 버튼을 만드는 헬퍼도 engine.js에 두고, 작품 파일은 document를 직접 만지지 않게 해라.
두 작품을 site/js/pieces/01-currents.js와 site/js/pieces/02-morphogenesis.js로 옮겨 Piece를 상속시켜라.
제약 하나: engine.js는 모듈 스코프에서 document나 window를 만지면 안 된다. 클래스와 함수 정의만 둬라.
리팩터 후 두 작품이 이전과 똑같이 동작하는지 내가 확인할 수 있게 실행 방법을 알려줘.
막힐 때 열어보기 - Piece 계약의 최소 시그니처
엔진이 부르는 쪽과 작품이 채우는 쪽이 이렇게 갈립니다. 이름만 맞으면 나머지는 자유입니다.
export class Piece {
constructor(canvas) { /* canvas, dpr, w, h, pointer 초기화 */ }
mount() {
this._measure(); // canvas.width = clientWidth * dpr
this.setup(); // 작품이 채운다
this._bindPointer(); // pointerdown/move/up -> this.pointer
this._observeResize();
// RAF 루프 시작. dt = min((now - last) / 1000, 1/20)
}
destroy() {
// mount에서 얻은 모든 것을 여기서 놓는다:
// cancelAnimationFrame, removeEventListener, ResizeObserver.disconnect
}
// --- 작품이 구현하는 것 ---
setup() {} // 필수
frame(dt, t) {} // 필수
onResize() {} // 선택
onPointerDown() {} // 선택
onPointerUp() {} // 선택
controls(host) {} // 선택, 플래카드에 슬라이더/버튼을 붙인다
}
engine.js가 모듈 스코프에서 DOM을 안 만지게 하라는 제약이 붙은 이유는
M5에서 드러납니다. 지금은 그냥 지키세요.
destroy()가 정말로 mount()의 대칭인지 확인하세요.
Phase 3, 전시관 개장
20분작품 두 점을 전시로 만듭니다. 아트리움, 갤러리, 전시실 3단과 카탈로그를 세웁니다.
이 미션의 바탕 학습: Ch2의 병렬 감각. 전시 구조를 세우는 동안 작품 플래카드 문구를 서브에이전트로 병행할 수 있습니다.
/superpowers:executing-plans Phase 3(전시관 개장)을 구현해줘.
1) site/js/data.js - 순수 데이터, 로직 0.
WINGS 2개: flow "Forces & Flow / 힘과 흐름" accent #7cc6ff, life "Living Systems / 살아있는 계" accent #8fe39a
WORKS - 각 항목은 no, wing, title, ko, medium, year, note, hint, module.
작품 01과 02 외에 module이 null인 항목 2개도 넣어라(갤러리에 "Soon" 카드로 남는지 확인용)
2) site/index.html - 한 문서 안에 세 뷰. body[data-view]가 atrium/gallery/room을 전환한다.
전시실에는 캔버스 #stage 하나, 작품 설명을 담는 플래카드, 이전/다음 액션바, 나가기 버튼
3) site/js/main.js - 뷰 컨트롤러.
갤러리 그리드는 한 번만 빌드한다. 카드를 누르면 await import(work.module)로 마운트하고
플래카드에 no/title/ko/medium/note를 주입하고 piece.controls()로 컨트롤을 붙인다.
이전/다음, Esc로 나가기, #work-01 딥링크를 지원한다
4) 규율 두 가지를 지켜라:
- 작품을 바꿀 때 반드시 이전 piece.destroy()를 먼저 부른다. 캔버스는 끝까지 하나다
- import가 실패하면 검은 화면으로 두지 말고 플래카드에 실패 사유를 적는다
5) CSS는 어두운 미술관 톤. 전시관의 accent를 CSS 변수로 주입해 카드와 플래카드가 물려받게 해라
(전시관을 더해도 CSS를 고치지 않아도 되게)
#work-02 해시 라우팅은 "지금 어느 전시실인가"라는 상태를 URL에 싣는 것,
새로고침에도 살아남고 링크로 공유되는 가장 가벼운 라우터입니다. 전환 시 이전 작품의 dispose를
부르는 규율이 있어야 세 번 오가도 처음 속도가 유지됩니다(누수 없음).
Phase 4, 배포와 개장
20분계약을 기계가 검사하게 만든 다음, S3 + CloudFront로 전시를 엽니다.
이 미션의 바탕 학습: Ch1의 검증 루프가 기계 게이트로 승격됩니다. 자동화가 궁금하면 Ch5의 헤드리스 패턴이 다음입니다.
렌더 루프에 단위 테스트를 붙이는 건 값이 없습니다. 하지만 카탈로그 계약이 깨지면 조용히 깨집니다 - 카드를 눌러도 아무 일이 없고, 아무도 모릅니다. 그것만 기계에게 맡깁니다.
verify.mjs를 만들어줘. 브라우저 없이 `node verify.mjs`로 돌아가야 한다.
site/js/data.js, details.js(있다면), engine.js를 import해서 확인한다:
1) 모든 WORKS[].module 파일이 실제로 존재하는가 (경로는 site/js/ 기준 상대 경로다)
2) 각 piece가 default export이고, Piece를 상속하며, prototype에 setup과 frame이 있는가
3) 모든 WORKS[].wing이 WINGS에 존재하는가
4) WORKS[].no가 중복되지 않는가
5) 모든 WINGS가 6자리 hex accent를 가지는가
PASS/FAIL을 한 줄씩 찍고, 실패가 하나라도 있으면 exit code 1로 끝나라.
이게 브라우저 없이 돌아가는 이유를 주석으로 한 줄 남겨줘.
Piece contract
PASS 2 work(s) claim an engine (2 marked Soon)
PASS 01 Whispering Currents: extends Piece, implements setup + frame
PASS 02 Morphogenesis: extends Piece, implements setup + frame
...
20 checks passed. The exhibition can open.
engine.js가 모듈 스코프에서 DOM을 만지지 않기 때문에 Node가 이 파일들을
import할 수 있습니다. 테스트 가능성을 따로 만들지 않았습니다 - 경계를 깨끗하게 그은 대가로
공짜로 얻었습니다. 이것이 좋은 설계가 실제로 값을 내는 방식입니다.
이제 CDK 스택을 써서 site/를 S3 + CloudFront로 배포해줘.
- 버킷은 퍼블릭 접근 전면 차단, OAC로 CloudFront만 읽게 한다. removalPolicy는 DESTROY + autoDeleteObjects
- Distribution은 defaultRootObject "index.html", HTTPS 리다이렉트, 압축 켜기
- BucketDeployment로 site/ 전체를 올리고 distributionPaths ["/*"]로 배포마다 무효화한다
- errorResponses로 404를 index.html로 돌리지 마라. 이건 SPA가 아니다.
모듈 경로가 틀렸을 때 조용한 검은 화면이 되는 대신 요청이 실패한 게 보여야 한다
- CfnOutput으로 전시 URL을 뱉어라
끝나면 배포 명령을 알려줘.
node verify.mjs || echo "계약 검사 실패, 배포하지 말 것"
npx cdk deploy --require-approval never
URL=$(aws cloudformation describe-stacks --stack-name MediaArtStack \
--query "Stacks[0].Outputs[?OutputKey=='ExhibitionUrl'].OutputValue" --output text)
echo "$URL"
curl -sI "$URL" | head -1
curl -sI "${URL}js/main.js" | grep -i '^content-type'
curl -sI "${URL}js/pieces/01-currents.js" | head -1
HTTP/2 200
content-type: text/javascript
HTTP/2 200
Content-Type이 틀리면 브라우저가 실행을 거부합니다.
화면은 검고 콘솔에만 에러가 뜨므로, 콘솔을 열지 않으면 원인을 짐작조차 못 합니다.
BucketDeployment는 확장자마다 MIME을 알아서 붙여주니 이 경로로 올리면 안전합니다.
급하다고 aws s3 cp로 손수 올리면 .js가
binary/octet-stream이 되어 정확히 이 함정에 빠집니다.
위 스모크 테스트의 두 번째 줄이 그것만을 확인합니다.
전시 퀄리티 강화 라운드, 몰입을 올려라
+α 자유전시가 열렸다면 이제 전시를 내 것으로 만드는 시간입니다. 강화 카드 중 끌리는 것을 골라 하나씩 주문하세요. 요령은 같습니다: 한 번에 하나, 작품으로 확인하고, 마음에 들면 커밋.
작품 03을 추가하자. 공통 Piece 계약을 그대로 구현하는 세 번째 알고리즘:
수백 개의 파티클이 보이지 않는 인력점 몇 개를 공전하다, 마우스가 다가가면 흩어지고
멀어지면 서서히 궤도로 복귀하는 "중력 정원". 색은 궤도 반지름에 따라 그라디언트,
갤러리의 Soon 카드 하나를 Open으로 승격하고 #work-03 딥링크까지.
각 전시실에 도슨트 캡션을 달자. 작품 하단에 얇은 캡션 바: 작품 번호, 제목,
"이 작품이 쓰는 규칙" 한 문장(예: 벡터장의 각도는 노이즈에서 온다), 그리고 조작 힌트.
캡션은 입장 3초 후 스르륵 나타나고 마우스가 움직이면 잠시 숨어 작품을 방해하지 않는다.
자동 관람 모드를 추가하자. 갤러리에서 P 키(또는 Play 버튼) → 열린 작품들을
30초씩 자동 순회, 작품 전환은 부드러운 페이드, 어떤 입력이든 들어오면 관람 모드 해제.
전시장 키오스크처럼 손을 대지 않아도 살아 있는 전시가 되게.
갤러리 입장 연출을 다듬자. 첫 로드에 타이틀이 어둠 속에서 떠오르고,
Open 카드들이 시차를 두고 하나씩 켜진다(stagger). 카드 호버 시 해당 작품의
미니 프리뷰가 은은하게 움직이면 최고. 연출은 2초 안에 끝나고 재방문(세션 내)에는 생략.
여기부터는 정해진 카드가 없습니다. 당신이 작가입니다. 좋아하는 시각적 현상 하나를 골라, 위 카드들처럼 직접 요구사항을 써서 주문하세요. 아이디어가 필요하면: 리사주 곡선, 반응 확산 무늬, 별이 흐르는 은하, 잉크 번짐, 만화경 대칭, 픽셀 비.
내 전시에 [내가 원하는 작품/연출]을 추가하자. 규칙:
1) 규칙: [무엇이 무엇을 따라 움직이는지, 알고리즘을 한두 문장으로]
2) 인터랙션: [마우스/키보드에 어떻게 반응하는지]
3) 분위기: [색, 속도, 밀도처럼 느낌을 좌우하는 요소]
4) 공통 Piece 계약과 기존 작품의 성능은 깨지 않는다
구현 전에 이 규칙을 네 계획으로 다시 말해주고, 승인하면 진행해.
하네스 엔지니어링, 작품을 성숙시키고 등급을 받다
+15분 옵션지금까지 만든 것은 결과물입니다. 이 옵션은 그 결과물을 만든 작업 환경, 즉 하네스 (CLAUDE.md, hooks, skills, commands, agents)를 표준 구조로 성숙시키고(project-init), 그 품질을 6개 차원(정확성, 안전성, 완전성, 실행 가능성, 일관성, 검증 가능성)에서 채점받는(harness-eval) 단계입니다. superpowers의 3단(brainstorm, write-plan, execute-plan) 뒤에 4단 성숙화와 5단 평가를 붙이는 셈입니다. 캡스톤 공통의 마무리 의식입니다.
이 미션의 바탕 학습: Ch4의 훅과 스킬을 팀 자산으로 승격하는 감각, 그리고 Reference 3의 플러그인 생태계.
claude plugin marketplace add https://github.com/whchoi98/project-init
claude plugin install project-init@project-init
claude plugin marketplace add https://github.com/whchoi98/harness-eval
claude plugin install harness-eval@harness-eval
claude plugin list
설치 후 새 Claude 세션을 시작하세요, superpowers 때와 같은 규칙입니다.
/project-init:init-project 이 저장소는 빌드 도구 없는 정적 제너러티브 아트 전시(site/)와
CDK 배포 스택(bin/, lib/)으로 이루어져 있다. 다음을 하네스에 반영해줘.
- CLAUDE.md에 Piece 계약을 규범으로 적어라: 작품 파일은 engine.js만 import하고 서로를 모른다,
engine.js는 모듈 스코프에서 DOM을 만지지 않는다, 작품 추가는 data.js 한 항목 + 파일 하나다
- site/js/**를 편집한 뒤에는 node verify.mjs가 자동으로 돌게 훅을 걸어라
- 새 작품을 뼈대부터 만들어 주는 skill을 하나 추가해라(카탈로그 항목과 Piece 하위 클래스 스켈레톤)
/harness-eval:standard
점수와 등급, 그리고 개선 로드맵이 나옵니다. 상위 항목 하나만 고쳐 다시 돌려 보세요 - 점수가 오르는 것을 확인하는 데까지가 이 옵션의 목적입니다.
컷라인과 트러블슈팅
참고시간이 부족할 때 무엇을 버리고 무엇을 지키는가. 미리 정해두면 당황하지 않습니다.
| 순서 | 버릴 것 | 대가 |
|---|---|---|
| 1 | 그레인, 비네트 오버레이, 사운드 | 분위기만 줄어듭니다. 기능 손실 0 |
| 2 | 카드 썸네일 페인트 | 갤러리가 단조로워집니다. 카드는 그대로 동작 |
| 3 | 아트리움 히어로 (갤러리로 바로 시작) | 3단이 2단이 됩니다. body[data-view]는 그대로 두세요 |
| 4 | 작품 02의 프리셋 3개 (세포분열만 남김) | F/k 탐험의 재미가 줄지만 알고리즘은 온전합니다 |
| 5 | details.js 기술 노트 패널 | 플래카드가 짧아집니다 |
destroy(), Piece 계약, 그리고 배포.
이 셋이 이 캡스톤의 내용 전부입니다. 작품이 한 점만 완성됐어도 배포까지는 가세요 -
링크가 없으면 아무것도 보여줄 수 없습니다.
openRoom()만 있으면 전시입니다. 아트리움과 딥링크는 나중에 붙습니다.
| 증상 | 먼저 볼 곳 |
|---|---|
| 화면이 검고 콘솔에 MIME 관련 거부 메시지 | aws s3 cp로 손수 올렸는지. BucketDeployment를 쓰면 Content-Type이 맞습니다 |
| 화면이 검고 콘솔에 404 또는 403 | data.js의 module 경로. site/js/ 기준 상대 경로입니다. node verify.mjs가 배포 전에 잡습니다 |
| 작품을 오갈수록 점점 빨라진다 | destroy()에서 cancelAnimationFrame을 부르는지. 리스너와 ResizeObserver도 함께 |
| 탭에 다시 돌아오면 화면이 터진다 | dt에 상한이 없는 것. min(dt, 1/20) |
| 무늬가 전혀 자라지 않는다 (작품 02) | 씨를 안 뿌렸을 가능성. 아무것도 없는 판은 평형이라 영원히 아무 일도 없습니다. 또는 F/k가 죽는 영역 |
| 무늬가 한쪽으로 비스듬히 번진다 | 이중 버퍼를 안 쓴 것. 제자리에서 갱신하고 있습니다 |
| 작품 02가 무겁다 | 격자 칸 수 × 프레임당 스텝 수. 격자를 거칠게 잡고 확대하세요 |
| 입자가 화면 중앙만 텅 빈다 (작품 01) | 리스폰을 화면 전체 균등으로 한 것. 가장자리 쪽으로 편향시키면 흐름이 계속 들어옵니다 |
| 첫 프레임이 하얗게 번쩍인다 | setup() 끝에서 캔버스를 한 번 검정으로 칠하지 않은 것 |
| 고해상도 화면에서만 느리다 | DPR 상한이 없는 것. min(devicePixelRatio, 2) |
cdk deploy가 bootstrap 오류 | M0에서 백그라운드로 돌린 npx cdk bootstrap이 끝났는지. cat /tmp/bootstrap.log |
미션 종료
마무리전시가 열렸습니다. 이 랩에서 실제로 배운 것은 미디어 아트가 아닙니다.
| 남는 것 | 어디서 다시 쓰는가 |
|---|---|
| 두 번째 사례가 오기 전에는 추상하지 않는다 | 모든 리팩터. Phase 1과 2의 순서가 이 랩의 본론이었습니다 |
mount()와 destroy()의 대칭 | 구독, 타이머, 워커, 연결 - 얻은 곳에서 놓습니다 |
| 경계를 깨끗이 그으면 테스트 가능성이 따라온다 | engine.js가 DOM을 안 만졌기에 verify.mjs가 가능했습니다 |
| 비용을 상수가 아니라 규모에 비례시킨다 | 입자 수, 격자 칸 수, 배치 크기, 페이지 크기 |
| 조용히 깨지는 것만 기계에게 검사시킨다 | 렌더 루프는 눈으로, 카탈로그 계약은 CI로 |
| Ch2 서브에이전트 | Phase 2의 리팩터에서 두 작품을 병렬로 옮길 때 |
| Ch4 settings와 hooks | site/js/** 저장 시 verify.mjs를 자동 실행 |
| 같은 배포 패턴 | S3 + CloudFront + BucketDeployment는 정적 배포의 표준 패턴. 이제 눈 감고도 |
data.js에 항목 하나, pieces/에 파일 하나입니다.
그 외 어떤 파일도 고치지 않습니다. 갤러리에 남겨둔 "Soon" 카드가 그 증거입니다.
다음 후보를 하나 골라 붙여 보세요 - 군무(Boids), 점균(Physarum), 메타볼, 시간을 세는 법.
각각 30분 안에 들어갑니다.
npx cdk destroy로 스택을 내리세요.
autoDeleteObjects를 걸어 두었으니 버킷도 함께 비워집니다.