# AI 꾸러미 — 코드로 그린 도트 무늬
> 씨앗 고정 16×16 도트 텍스처  
> 견본: https://ai-techstudio.web.app/#t/u53

이 문서를 코드 도우미(Claude Code · Cursor · ChatGPT 등)에 그대로 주면 돼요. 「## 주문서」 가 할 일, 나머지는 참고 자료예요.

## 주문서

### 만들어 줘: 코드로 그린 도트 무늬

#### 1. 목표
2D 게임 화면에 「코드로 그린 도트 무늬」 기술을 깔끔한 기본 느낌으로 넣어 줘. — 씨앗 고정 16×16 도트 텍스처

#### 2. 핵심 기술 용어
- 코드로 그린 도트 무늬 — 씨앗 고정 16×16 도트 텍스처

#### 3. 환경
- 플랫폼: HTML Canvas 2D (TypeScript), requestAnimationFrame 루프, 라이브러리 없이
- 화면: 2D · 브라우저 — PC 와 폰(390px 폭) 모두, devicePixelRatio 맞춰 또렷하게, 60fps 목표

#### 4. 조건
- devicePixelRatio 를 맞춰 또렷하게
- 매 프레임 다시 그릴 필요 없는 그림은 화면 밖 캔버스에 미리 구워 두기
- 한 프레임 16ms 안 (폰 포함)

#### 5. 완성 기준 (이게 보이면 성공)
- 견본처럼 「씨앗 고정 16×16 도트 텍스처」 이(가) 첫 화면에서 바로 보인다
- 켬/끔(또는 전/후) 비교 단추로 이 기술이 만드는 차이를 확인할 수 있다
- 중요한 값 1~3개를 슬라이더로 바꿔 볼 수 있다
- 콘솔 오류 0, 폰 폭(390px)에서도 동작한다

#### 6. 진행 방식
- 핵심 코드 위주로, 설명은 짧게. 내 프로젝트에 끼워 넣기 쉬운 함수 · 클래스로 나눠 줘.
- 처음 화면에 바로 결과가 보이게, 그리고 켬/끔(또는 전/후) 비교를 할 수 있게 만들어 줘.
- 그림 · 소리 · 모델 파일이 필요하면 코드로 만든 임시 대체물로 먼저 돌아가게 하고, 진짜 파일로 바꿀 자리를 표시해 줘.
- 마지막에 「확인 방법」(무엇을 보면 성공인지)과 「조절할 값」 목록을 짧게 정리해 줘.
- 답변과 코드 주석은 한국어로 해 줘.

## 원리
- 씨앗 고정 16×16 도트 텍스처


## 흔한 실수 · 확인 목록
- (아직 정리된 것이 없어요)

## 완성 기준 체크리스트
- [ ] 견본처럼 「씨앗 고정 16×16 도트 텍스처」 이(가) 첫 화면에서 바로 보인다
- [ ] 켬/끔(또는 전/후) 비교 단추로 이 기술이 만드는 차이를 확인할 수 있다
- [ ] 중요한 값 1~3개를 슬라이더로 바꿔 볼 수 있다
- [ ] 콘솔 오류 0, 폰 폭(390px)에서도 동작한다

## 이 기술 정보
- id: `u53` · 분류: 2D · 화면 › 2D 그리기 · 2D · 난이도 보통 · 폰 부담 보통 (폰 주의)
- 라이브 견본 (브라우저에서 직접 조작): https://ai-techstudio.web.app/#t/u53

## 견본 실제 코드 (라이브 견본이 돌리는 코드 — three.js · TypeScript)
### u53 견본 항목 — `src/demos/demosDraw2d.ts:345`
```ts
  u53: {
    kind: '2d',
    caption: '왼쪽 16×16 도트 한 장(씨앗 번호로 늘 같은 무늬) → 오른쪽 창고 바닥 · 담 · 상자에 깔기',
    make() {
      const cache = new Map<string, HTMLCanvasElement>();
      let auto = true;
      let fixedSeed = 3;
      const MAP = ['GGGGGGGGG', 'GWWWWWWWG', 'GWFFFFFWG', 'GWFFFFFWG', 'GWWWWWWWG', 'GGGGGGGGG'];
      const kindOf: Record<string, number> = { F: 0, W: 1, G: 3 };
      return {
        draw(g, w, h, t) {
          const u = scaleOf(w, h);
          bg(g, w, h, '#22305e', '#111733');
          g.imageSmoothingEnabled = false;
          const seed = auto ? 1 + (Math.floor(t / 4) % 9) : fixedSeed;
          const kind = Math.floor(t / 1.5) % 4;
          // 왼쪽 큰 한 장
          const S = Math.min(h * 0.6, w * 0.34);
          const lx = w * 0.06;
          const ly = (h - S) / 2 + 6 * u;
          g.drawImage(pixTile(kind, seed, cache), lx, ly, S, S);
          g.strokeStyle = 'rgba(0,0,0,.18)';
          g.lineWidth = 1;
          g.beginPath();
          for (let i = 1; i < 16; i++) {
            g.moveTo(lx + (i * S) / 16, ly);
            g.lineTo(lx + (i * S) / 16, ly + S);
            g.moveTo(lx, ly + (i * S) / 16);
            g.lineTo(lx + S, ly + (i * S) / 16);
          }
          g.stroke();
          g.strokeStyle = '#fff';
          g.lineWidth = 2 * u;
          g.strokeRect(lx, ly, S, S);
          txt(g, `${PIX[kind]![0]} · 16×16`, lx + S / 2, ly - 9 * u, 9 * u, '#fff');
          pill(g, `씨앗 ${seed}`, lx + S / 2, ly + S + 11 * u, 8 * u, '#ffb84d', '#3a2000');
          // 오른쪽 창고 방
          const cols = 9;
          const rows = 6;
          const rx0 = w * 0.47;
          const cs = Math.min((w * 0.5) / cols, (h * 0.8) / rows);
          const ry0 = (h - cs * rows) / 2;
          for (let y = 0; y < rows; y++)
            for (let x = 0; x < cols; x++) {
              const ch = MAP[y]![x]!;
              const k = kindOf[ch] ?? 0;
              g.drawImage(pixTile(k, seed, cache), rx0 + x * cs, ry0 + y * cs, cs + 0.5, cs + 0.5);
              if (k === kind) {
                g.fillStyle = `rgba(255,255,255,${0.12 + 0.1 * Math.sin(t * 6)})`;
                g.fillRect(rx0 + x * cs, ry0 + y * cs, cs, cs);
              }
            }
          // 미는 상자
          const cx = 2 + 4 * swing(t, 1.1, 1.3);
          const crate = pixTile(2, seed, cache);
          g.fillStyle = 'rgba(0,0,0,.3)';
          g.fillRect(rx0 + cx * cs + cs * 0.1, ry0 + 2 * cs + cs * 0.85, cs * 0.9, cs * 0.18);
          g.drawImage(crate, rx0 + cx * cs, ry0 + 2 * cs, cs, cs);
          g.drawImage(crate, rx0 + 5 * cs, ry0 + 3 * cs, cs, cs);
          if (kind === 2) {
            g.strokeStyle = `rgba(255,255,255,${0.6 + 0.3 * Math.sin(t * 6)})`;
            g.lineWidth = 2 * u;
            g.strokeRect(rx0 + cx * cs, ry0 + 2 * cs, cs, cs);
            g.strokeRect(rx0 + 5 * cs, ry0 + 3 * cs, cs, cs);
          }
          g.imageSmoothingEnabled = true;
          txt(g, '→', w * 0.43, h / 2 + 6 * u, 16 * u, '#ffd36b', 'center', F, 900);
        },
        controls: [
          { type: 'toggle', label: '씨앗 자동으로 바꾸기', value: true, on: (v) => (auto = v) },
          { type: 'range', label: '씨앗 (자동 끔일 때)', min: 1, max: 30, step: 1, value: 3, on: (v) => (fixedSeed = v) },
        ] as Control[],
      };
    },
  }
```

## 관련 기술
- 이 효과를 만든 기술: [캔버스로 그린 무늬 (CanvasTexture)](https://ai-techstudio.web.app/ai/t/u09.md) `u09`
