CAPSTONE 3 / FRAME IT / 120 MIN

Generative Hours, 코드로 그린 전시를 개장하라

두 점의 제너러티브 아트로 여는 인터랙티브 미디어 아트 전시

미디어 아트는 화면이 곧 테스트입니다. 벡터장이 틀리면 흐름이 뭉치고, 이중 버퍼를 빼먹으면 무늬가 자라지 않습니다. 이 캡스톤에서는 작품 한 점을 추상 없이 끝까지 밀어붙인 다음, 두 번째 작품이 도착하는 순간 공통 계약을 추출합니다. 추상은 선포되는 것이 아니라 필요해질 때 태어난다는 것을, 손으로 겪는 랩입니다. 이 캡스톤은 Bedrock을 쓰지 않으므로 API 키가 필요 없습니다.

소요 시간 120분 미션 6개 + DoD 산출물 전시 URL Update 2026.07
MISSION BRIEFING

두 점으로 개장하는 전시

숙지

Generative Hours라는 이름의 전시관을 엽니다. 아트리움으로 들어와 갤러리에서 작품을 고르고, 전시실에서 작품 하나를 전체화면으로 만집니다. 개장 시점의 소장품은 단 두 점이지만, 구조는 예순 점을 걸 수 있게 짭니다.

왼쪽이 01 Whispering Currents, 노이즈 벡터장 위를 흐르는 수만 개의 입자. 오른쪽이 02 Morphogenesis, 두 화학종이 서로를 먹으며 자라는 격자. 전자는 에이전트를 매 프레임 옮기고, 후자는 격자 전체를 수식으로 갱신합니다. 일부러 코드 모양이 정반대인 짝을 골랐습니다.

이 랩의 진짜 주제 Phase 1에서는 일부러 추상을 만들지 않습니다. 작품 하나를 한 파일에 몰아넣고 끝까지 밀어붙입니다. Phase 2에서 두 번째 작품을 또 한 번 통째로 베껴 쓴 다음, 그 중복을 보고 나서야 공통 계약을 뽑아냅니다. "먼저 추상을 설계하라"는 조언이 왜 자주 틀리는지 - 두 번째 사례 없이는 무엇이 공통인지 알 수 없다는 것을, 30분 안에 손으로 확인하게 됩니다.
규칙, 캡스톤 공통 ① 파일을 손으로 짜지 말고 Claude에게 시켜서 만든다 ② 각 Phase의 DoD를 통과해야 다음으로 ③ 막히면 "막힐 때 열어보기"의 힌트를 쓴다. 구현 경로는 자유입니다.
L학습 목표, Level 200

이 캡스톤을 마치면 다음 네 가지를 설명할 수 있고, 다시 만들 수 있습니다.

목표검증되는 순간
"선 구체, 후 추상"의 리팩터 감각M3에서 중복 두 벌을 눈으로 본 뒤 계약을 뽑는데, 화면은 전과 똑같을 때
공통 엔진 위의 인터페이스(Piece) 설계코드 모양이 정반대인 두 작품이 같은 계약으로 한 전시에서 돌 때
데이터 주도 확장 구조작품 추가가 "data.js 한 항목 + 파일 하나"로 끝날 때 (강화 카드 A에서 체감)
기계 검증을 배포 게이트로 세우는 법M5에서 verify.mjs가 전부 PASS한 뒤에만 전시가 열릴 때
R챕터 연결 지도, 배운 내용이 재등장하는 곳

이 캡스톤은 새 기술을 가르치지 않습니다. Ch1~6에서 배운 것을 실전 배치합니다.

챕터이 캡스톤에서 재등장하는 곳
Ch1 설치부터 Headless까지M0~전체: 세션 운용, 지시 → 실행 → 검증 루프
Ch2 SubagentsM3~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 항목을 만드는 다음 단계
BLUEPRINT

구현 청사진, 전시의 뼈대

참고

M1 설계가 이 구조로 수렴하도록 방향을 잡아 주는 구현 지도입니다. 계획이 이상하게 흐르면 여기로 돌아와 대조하세요.

0세 개의 경로 한 눈에
프레임 루프, 관람 경로, 출고 경로
[프레임 루프]  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의 핵심 구분입니다: 매 프레임 도는 루프, 데이터가 이끄는 동선, 기계가 지키는 게이트. 작품은 그리기에만 집중하고 나머지는 엔진과 카탈로그와 검사기가 맡습니다.

1구조 지도
구성파일핵심 계약
엔진site/js/engine.js캔버스 리사이즈, DPR, RAF 루프, dt/t 시계를 공통 제공, 작품은 그리기만
작품 01site/js/pieces/01-currents.js벡터장 흐름결, 마우스를 끌면 결이 휘어진다
작품 02site/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
2기술 선택 4
선택이유
빌드 도구 없는 정적 사이트작품 파일 하나를 브라우저로 열면 바로 도는 즉시성, 전시는 가벼워야 한다
선 구체, 후 추상중복을 눈으로 보기 전의 추상은 추측이다, 두 벌을 만들고 나서 계약을 뽑는다
데이터 주도 카탈로그구조(코드)와 내용(데이터)의 분리, 전시 확장이 편집이 된다
verify.mjs 게이트렌더 루프의 단위 테스트 대신, 계약과 카탈로그의 무결을 기계가 지킨다
MISSION 00

사전 준비

10분

프로젝트 폴더, 배포용 CDK 뼈대, 그리고 브라우저로 보는 통로를 준비합니다. Bedrock 키는 필요 없습니다.

Terminal, 전체 복사
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
1미리보기 통로 확인

이 랩은 Phase마다 브라우저로 확인합니다. 정적 서버를 띄우고 프록시 경로로 접근하는 방식이 가장 간단합니다.

Terminal, 미리보기 서버 (별도 탭에서 계속 띄워둡니다)
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의 체크포인트를 눈으로 못 보므로, 되도록 통로를 확보하세요.
2claude-hud 설치, 상태줄 HUD
Claude 세션 입력, 전체 복사
/plugin marketplace add jarrodwatts/claude-hud
/plugin install claude-hud@claude-hud
/reload-plugins
/claude-hud:setup

설치가 끝나면 세션 하단 상태줄에 모델, 컨텍스트 사용량, 세션 상태가 표시됩니다. 긴 캡스톤에서 컨텍스트가 얼마나 남았는지 한눈에 보이므로, Phase 사이에 정리 시점을 잡기 좋습니다.

CHECKPOINT
MISSION 01

설계, 브레인스토밍

15분

요구사항 7개 조항을 superpowers에 넘겨 설계와 Phase 계획을 뽑습니다. 두 작품의 알고리즘 상수가 프롬프트에 들어 있습니다.

이 미션의 바탕 학습: Ch1의 세션 운용과 Capstone Setup의 superpowers 워크플로.

Terminal (~/capstone/media-art)
cd ~/capstone/media-art && claude
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 세션에 auto mode on을 선언해 두면 superpowers가 brainstorming → write-plan → execute-plan의 단계 전환과 승인 대기를 자동으로 이어 갑니다. 시간이 빠듯할 때 유용합니다. 되돌리려면 auto mode off입니다.

브레인스토밍 후 /superpowers:writing-plans으로 계획을 만들고, Phase 4개 구성을 확인해 승인하세요.

계획을 승인하기 전에 확인할 것 Phase 1의 산출물에 engine.js나 Piece가 등장하면 계획이 틀렸습니다. Phase 1은 한 파일이어야 하고, 추상은 Phase 2에서만 나옵니다. Claude가 친절하게 미리 추상을 만들어 두려 하면 되돌리세요 - 이 랩에서 그 순서는 교육 내용 자체입니다.
CHECKPOINT
MISSION 02

Phase 1, 첫 픽셀

25분

추상 없이 한 파일로 작품 01을 완주합니다. 20분 안에 화면이 살아 있어야 합니다.

이 미션의 바탕 학습: Ch1의 지시 → 실행 → 검증 루프. "추상 금지"는 게으름이 아니라 M3를 위한 포석입니다.

Claude 세션 입력
/superpowers:executing-plans Phase 1(첫 픽셀)만 구현하고 멈춰줘.
지금은 추상을 만들지 마. site/index.html에 전체화면 캔버스 하나, site/js/currents.js 한 파일만 두고
그 안에서 캔버스 리사이즈와 DPR 스케일, requestAnimationFrame 루프, dt/t 시계, 포인터 좌표를 직접 처리해라.
그 위에 2옥타브 Perlin 노이즈 벡터장, 입자 흐름, 잔상, 길게 누르는 소용돌이까지 완성해.
dt에는 1/20초 상한을 둬라(탭을 나갔다 돌아왔을 때 물리가 터지지 않게).
setup 마지막에 캔버스를 한 번 검정으로 칠해라(첫 프레임이 하얗게 번쩍이지 않게).
끝나면 로컬에서 보는 방법을 알려줘.
1잔상의 정체를 확인한다

완성되면 코드에서 캔버스를 덮는 한 줄을 찾아 알파값을 바꿔 보세요. 이 한 줄이 점과 혜성의 차이입니다.

site/js/currents.js - 이 한 줄이 잔상이다
// 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 모니터에서는 텅 빕니다. 비용을 픽셀에 비례시키는 습관은 이 랩을 나가서도 씁니다.
CHECKPOINT
MISSION 03

Phase 2, 두 번째 작품과 계약의 탄생

30분

이 랩의 정점입니다. 두 번째 작품을 일부러 통째로 베껴 쓴 다음, 그 중복을 보고 나서 계약을 뽑아냅니다.

이 미션의 바탕 학습: Ch2의 code-reviewer 서브에이전트, 계약 추출 리팩터의 diff 리뷰를 맡기기 딱 좋은 순간입니다.

1중복을 만든다
Claude 세션 입력
/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 시계, 포인터 처리를
일부러 그대로 다시 써라. 두 파일을 나란히 놓고 무엇이 중복인지 내 눈으로 보고 싶다.
다 되면 두 파일에서 거의 똑같은 부분의 줄 번호를 짚어서 알려줘.
이중 버퍼, 이 작품에서만 나오는 함정 한 스텝은 반드시 옛 격자를 읽어 새 격자에 써야 합니다. 제자리에서 고치면 어떤 이웃은 이미 갱신된 값이라 의도한 방정식이 아닌 다른 것을 풀게 됩니다. 증상은 미묘합니다 - 터지지 않고, 그저 무늬가 한쪽으로 비스듬히 번지거나 영원히 자라지 않습니다. 버퍼 두 장을 만들고 스텝 끝에서 복사하지 말고 교체하세요.
2계약을 추출한다

이제 중복이 눈앞에 있습니다. 무엇이 공통인지 추측하지 않고 알게 된 상태에서 뽑아냅니다.

Claude 세션 입력
이제 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 계약의 최소 시그니처

엔진이 부르는 쪽과 작품이 채우는 쪽이 이렇게 갈립니다. 이름만 맞으면 나머지는 자유입니다.

site/js/engine.js
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에서 드러납니다. 지금은 그냥 지키세요.

개념 짚기 (Level 200), 선 구체 후 추상 중복을 눈으로 보기 전에 만든 추상은 추측이고, 추측으로 만든 계약은 대개 틀립니다. 작품 02를 일부러 통째로 베껴 쓰는 이유가 그것입니다: 두 벌이 나란히 있어야 "무엇이 같고 무엇이 다른가"가 사실이 되고, 그 사실에서 뽑은 Piece 계약만이 세 번째 작품도 받아냅니다. 리팩터의 합격 기준은 단순합니다, 화면이 전과 똑같을 것.
CHECKPOINT
두 번째 체크포인트가 통과되지 않는다면 작품을 오갈수록 화면이 점점 빨라지는 증상이라면 축하합니다, 가장 흔한 버그를 스스로 밟았습니다. 루프를 하나 더 만들었는데 이전 것을 세우지 않은 것입니다. 어디를 봐야 할지는 이미 알고 있습니다 - destroy()가 정말로 mount()의 대칭인지 확인하세요.
MISSION 04

Phase 3, 전시관 개장

20분

작품 두 점을 전시로 만듭니다. 아트리움, 갤러리, 전시실 3단과 카탈로그를 세웁니다.

이 미션의 바탕 학습: Ch2의 병렬 감각. 전시 구조를 세우는 동안 작품 플래카드 문구를 서브에이전트로 병행할 수 있습니다.

Claude 세션 입력
/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를 고치지 않아도 되게)
module: null 이 하는 일 비어 있는 카드는 낭비가 아니라 계약의 증거입니다. 갤러리에 "Soon"으로 걸리는 작품이 보인다면, 그 자리에 파일 하나를 놓고 경로 한 줄만 채우면 전시가 열린다는 뜻입니다. 이 랩이 두 점으로 끝나도 구조는 예순 점을 견딥니다.
개념 짚기 (Level 200), 데이터 주도 전시와 해시 딥링크 갤러리가 data.js를 읽어 카드를 그리는 순간, 전시 확장은 코딩이 아니라 편집이 됩니다. 그리고 #work-02 해시 라우팅은 "지금 어느 전시실인가"라는 상태를 URL에 싣는 것, 새로고침에도 살아남고 링크로 공유되는 가장 가벼운 라우터입니다. 전환 시 이전 작품의 dispose를 부르는 규율이 있어야 세 번 오가도 처음 속도가 유지됩니다(누수 없음).
CHECKPOINT
MISSION 05

Phase 4, 배포와 개장

20분

계약을 기계가 검사하게 만든 다음, S3 + CloudFront로 전시를 엽니다.

이 미션의 바탕 학습: Ch1의 검증 루프가 기계 게이트로 승격됩니다. 자동화가 궁금하면 Ch5의 헤드리스 패턴이 다음입니다.

1계약 검사기를 만든다

렌더 루프에 단위 테스트를 붙이는 건 값이 없습니다. 하지만 카탈로그 계약이 깨지면 조용히 깨집니다 - 카드를 눌러도 아무 일이 없고, 아무도 모릅니다. 그것만 기계에게 맡깁니다.

Claude 세션 입력
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.
M3의 그 제약이 여기서 값을 냅니다 engine.js가 모듈 스코프에서 DOM을 만지지 않기 때문에 Node가 이 파일들을 import할 수 있습니다. 테스트 가능성을 따로 만들지 않았습니다 - 경계를 깨끗하게 그은 대가로 공짜로 얻었습니다. 이것이 좋은 설계가 실제로 값을 내는 방식입니다.
2배포한다
Claude 세션 입력
이제 CDK 스택을 써서 site/를 S3 + CloudFront로 배포해줘.
- 버킷은 퍼블릭 접근 전면 차단, OAC로 CloudFront만 읽게 한다. removalPolicy는 DESTROY + autoDeleteObjects
- Distribution은 defaultRootObject "index.html", HTTPS 리다이렉트, 압축 켜기
- BucketDeployment로 site/ 전체를 올리고 distributionPaths ["/*"]로 배포마다 무효화한다
- errorResponses로 404를 index.html로 돌리지 마라. 이건 SPA가 아니다.
  모듈 경로가 틀렸을 때 조용한 검은 화면이 되는 대신 요청이 실패한 게 보여야 한다
- CfnOutput으로 전시 URL을 뱉어라
끝나면 배포 명령을 알려줘.
Terminal, 배포와 스모크 테스트
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
여기서 사람들이 20분을 잃습니다 - Content-Type ES 모듈은 Content-Type이 틀리면 브라우저가 실행을 거부합니다. 화면은 검고 콘솔에만 에러가 뜨므로, 콘솔을 열지 않으면 원인을 짐작조차 못 합니다. BucketDeployment는 확장자마다 MIME을 알아서 붙여주니 이 경로로 올리면 안전합니다. 급하다고 aws s3 cp로 손수 올리면 .jsbinary/octet-stream이 되어 정확히 이 함정에 빠집니다. 위 스모크 테스트의 두 번째 줄이 그것만을 확인합니다.
개념 짚기 (Level 200), 검증이 게이트다 렌더 루프의 단위 테스트는 비용 대비 효용이 낮습니다. 대신 verify.mjs가 계약과 카탈로그의 무결 (모든 작품이 Piece를 상속하는가, data.js 항목과 파일이 일치하는가)을 검사하고, 전부 PASS일 때만 배포합니다. 사람 눈 대신 기계가 지키는 게이트, 이것이 CI의 가장 작은 형태입니다. 다음 단계는 이 스크립트를 파이프라인에 앉히는 것뿐입니다.
CHECKPOINT
BONUS

전시 퀄리티 강화 라운드, 몰입을 올려라

+α 자유

전시가 열렸다면 이제 전시를 내 것으로 만드는 시간입니다. 강화 카드 중 끌리는 것을 골라 하나씩 주문하세요. 요령은 같습니다: 한 번에 하나, 작품으로 확인하고, 마음에 들면 커밋.

A강화 카드 1, 세 번째 작품
Claude 세션 입력
작품 03을 추가하자. 공통 Piece 계약을 그대로 구현하는 세 번째 알고리즘:
수백 개의 파티클이 보이지 않는 인력점 몇 개를 공전하다, 마우스가 다가가면 흩어지고
멀어지면 서서히 궤도로 복귀하는 "중력 정원". 색은 궤도 반지름에 따라 그라디언트,
갤러리의 Soon 카드 하나를 Open으로 승격하고 #work-03 딥링크까지.
B강화 카드 2, 도슨트 캡션
Claude 세션 입력
각 전시실에 도슨트 캡션을 달자. 작품 하단에 얇은 캡션 바: 작품 번호, 제목,
"이 작품이 쓰는 규칙" 한 문장(예: 벡터장의 각도는 노이즈에서 온다), 그리고 조작 힌트.
캡션은 입장 3초 후 스르륵 나타나고 마우스가 움직이면 잠시 숨어 작품을 방해하지 않는다.
C강화 카드 3, 관람 모드
Claude 세션 입력
자동 관람 모드를 추가하자. 갤러리에서 P 키(또는 Play 버튼) → 열린 작품들을
30초씩 자동 순회, 작품 전환은 부드러운 페이드, 어떤 입력이든 들어오면 관람 모드 해제.
전시장 키오스크처럼 손을 대지 않아도 살아 있는 전시가 되게.
D강화 카드 4, 개장 연출
Claude 세션 입력
갤러리 입장 연출을 다듬자. 첫 로드에 타이틀이 어둠 속에서 떠오르고,
Open 카드들이 시차를 두고 하나씩 켜진다(stagger). 카드 호버 시 해당 작품의
미니 프리뷰가 은은하게 움직이면 최고. 연출은 2초 안에 끝나고 재방문(세션 내)에는 생략.
E강화 카드 5, 나만의 작품으로

여기부터는 정해진 카드가 없습니다. 당신이 작가입니다. 좋아하는 시각적 현상 하나를 골라, 위 카드들처럼 직접 요구사항을 써서 주문하세요. 아이디어가 필요하면: 리사주 곡선, 반응 확산 무늬, 별이 흐르는 은하, 잉크 번짐, 만화경 대칭, 픽셀 비.

Claude 세션 입력, 빈칸을 내 아이디어로
내 전시에 [내가 원하는 작품/연출]을 추가하자. 규칙:
1) 규칙: [무엇이 무엇을 따라 움직이는지, 알고리즘을 한두 문장으로]
2) 인터랙션: [마우스/키보드에 어떻게 반응하는지]
3) 분위기: [색, 속도, 밀도처럼 느낌을 좌우하는 요소]
4) 공통 Piece 계약과 기존 작품의 성능은 깨지 않는다
구현 전에 이 규칙을 네 계획으로 다시 말해주고, 승인하면 진행해.
강화 라운드의 요령 ① 카드 하나 = 커밋 하나, 되돌아갈 지점을 만들며 갑니다. ② 작품은 느낌을 말로 다듬으세요, "소용돌이가 너무 빨리 풀린다"처럼. ③ 마지막엔 재배포로 전시 URL을 최신으로.
BONUS DONE, 진행률 미집계
OPTION

하네스 엔지니어링, 작품을 성숙시키고 등급을 받다

+15분 옵션

지금까지 만든 것은 결과물입니다. 이 옵션은 그 결과물을 만든 작업 환경, 즉 하네스 (CLAUDE.md, hooks, skills, commands, agents)를 표준 구조로 성숙시키고(project-init), 그 품질을 6개 차원(정확성, 안전성, 완전성, 실행 가능성, 일관성, 검증 가능성)에서 채점받는(harness-eval) 단계입니다. superpowers의 3단(brainstorm, write-plan, execute-plan) 뒤에 4단 성숙화와 5단 평가를 붙이는 셈입니다. 캡스톤 공통의 마무리 의식입니다.

이 미션의 바탕 학습: Ch4의 훅과 스킬을 팀 자산으로 승격하는 감각, 그리고 Reference 3의 플러그인 생태계.

1플러그인 2종 설치
Terminal, 전체 복사
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 때와 같은 규칙입니다.

2성숙화, /project-init:init-project와 /project-init:sync-docs
Claude 세션 입력
/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 하위 클래스 스켈레톤)
Claude 세션 입력
/harness-eval:standard

점수와 등급, 그리고 개선 로드맵이 나옵니다. 상위 항목 하나만 고쳐 다시 돌려 보세요 - 점수가 오르는 것을 확인하는 데까지가 이 옵션의 목적입니다.

CHECKPOINT
SURVIVAL

컷라인과 트러블슈팅

참고

시간이 부족할 때 무엇을 버리고 무엇을 지키는가. 미리 정해두면 당황하지 않습니다.

1버리는 순서
순서버릴 것대가
1그레인, 비네트 오버레이, 사운드분위기만 줄어듭니다. 기능 손실 0
2카드 썸네일 페인트갤러리가 단조로워집니다. 카드는 그대로 동작
3아트리움 히어로 (갤러리로 바로 시작)3단이 2단이 됩니다. body[data-view]는 그대로 두세요
4작품 02의 프리셋 3개 (세포분열만 남김)F/k 탐험의 재미가 줄지만 알고리즘은 온전합니다
5details.js 기술 노트 패널플래카드가 짧아집니다
절대 버리지 않는 것 destroy(), Piece 계약, 그리고 배포. 이 셋이 이 캡스톤의 내용 전부입니다. 작품이 한 점만 완성됐어도 배포까지는 가세요 - 링크가 없으면 아무것도 보여줄 수 없습니다.
정말 시간이 없다면 M3의 리팩터까지 마친 상태라면 M4를 최소 갤러리로 축약해도 됩니다. 카드 두 장과 openRoom()만 있으면 전시입니다. 아트리움과 딥링크는 나중에 붙습니다.
2증상별 원인
증상먼저 볼 곳
화면이 검고 콘솔에 MIME 관련 거부 메시지aws s3 cp로 손수 올렸는지. BucketDeployment를 쓰면 Content-Type이 맞습니다
화면이 검고 콘솔에 404 또는 403data.jsmodule 경로. 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
WRAP UP

미션 종료

마무리

전시가 열렸습니다. 이 랩에서 실제로 배운 것은 미디어 아트가 아닙니다.

남는 것어디서 다시 쓰는가
두 번째 사례가 오기 전에는 추상하지 않는다모든 리팩터. Phase 1과 2의 순서가 이 랩의 본론이었습니다
mount()destroy()의 대칭구독, 타이머, 워커, 연결 - 얻은 곳에서 놓습니다
경계를 깨끗이 그으면 테스트 가능성이 따라온다engine.js가 DOM을 안 만졌기에 verify.mjs가 가능했습니다
비용을 상수가 아니라 규모에 비례시킨다입자 수, 격자 칸 수, 배치 크기, 페이지 크기
조용히 깨지는 것만 기계에게 검사시킨다렌더 루프는 눈으로, 카탈로그 계약은 CI로
Ch2 서브에이전트Phase 2의 리팩터에서 두 작품을 병렬로 옮길 때
Ch4 settings와 hookssite/js/** 저장 시 verify.mjs를 자동 실행
같은 배포 패턴S3 + CloudFront + BucketDeployment는 정적 배포의 표준 패턴. 이제 눈 감고도
전시를 계속 키우려면 작품을 더하는 일은 data.js에 항목 하나, pieces/에 파일 하나입니다. 그 외 어떤 파일도 고치지 않습니다. 갤러리에 남겨둔 "Soon" 카드가 그 증거입니다. 다음 후보를 하나 골라 붙여 보세요 - 군무(Boids), 점균(Physarum), 메타볼, 시간을 세는 법. 각각 30분 안에 들어갑니다.
끝났으면 치우세요 CloudFront 배포는 켜져 있는 동안 비용이 발생합니다. 공유가 끝났다면 npx cdk destroy로 스택을 내리세요. autoDeleteObjects를 걸어 두었으니 버킷도 함께 비워집니다.
NEXT

포털로

다른 캡스톤(1 Press Start, 2 Market Desk on Web)도 같은 리듬입니다. 워크샵 포털에서 다음 미션을 고르세요.