‹ 게임

아이템 DB (Supabase)

내부 기획

아이템 DB (Supabase)

게임 아이템 카탈로그를 Supabase에 테이블화한 문서입니다.
편집 원본은 TypeScript 정적 데이터 → pnpm db:seed-items로 DB에 올리고,
클라이언트 런타임은 부트 시 Supabase에서 카탈로그를 받아 getItemDef() 레지스트리에 hydrate합니다.
원격 로드 실패·환경변수 부재 시에는 번들 TS defs로 폴백합니다.

결정

항목
범위 ItemDef + adjacency combo
편집 원본 packages/shared/data/artifacts.ts, ooparts.ts, itemCombos.ts
이미지 Cloudflare R2 (바이너리). DB에는 sprite_key만 저장
런타임 부트 시 Supabase REST → hydrateItemCatalog(); 실패 시 번들 TS
로컬 스키마 supabase/migrations/ 필수

로컬 구조

supabase/
  config.toml
  migrations/
    20260709120000_items_catalog.sql
scripts/
  db/
    seed-items.mjs
packages/client/src/catalog/
  loadItemCatalog.ts   # 부트 fetch + hydrate

런타임 로딩

flowchart LR
  boot["main.tsx boot"] --> fetch["GET /rest/v1/items + combos"]
  fetch --> parse["parseItemCatalog"]
  parse --> hydrate["hydrateItemCatalog"]
  hydrate --> app["App / AdminBoard"]
  fetch -.->|fail / no env| bundled["bundled ITEM_DEFS"]
  bundled --> app
  1. packages/client/src/main.tsxloadItemCatalogAtBoot() 완료 후 React 마운트
  2. Publishable key로 RLS SELECT만 사용 (VITE_SUPABASE_URL, VITE_SUPABASE_PUBLISHABLE_KEY)
  3. Shared: hydrateItemDefs / hydrateAdjacencyCombos (registry.ts, comboTree.ts)

테이블

public.items

컬럼 타입 설명
id text PK 예: item.dagger.basic
origin enum artifact | oopart
color text UI 틴트
element enum nullable fire/water/earth/wind/void
max_tier int
activation_cost int nullable
max_durability int nullable
shop_pool boolean
wear_per_battle int nullable
shape jsonb [{ "x", "y" }, …]
atk / def / hp / spd jsonb 티어별 수치 배열
tags item_tag[]
sprite_key text R2 오브젝트 키
created_at / updated_at timestamptz

public.item_adjacency_combos

컬럼 타입 설명
item_id text FK → items
combo_key text 예: shield_link
implemented boolean
effects jsonb [{ "kind", "value" }]
related_def_ids text[]

PK: (item_id, combo_key)

RLS: anon / authenticatedSELECT만. 쓰기는 service role(SUPABASE_SECRET_KEY)만.

R2 스프라이트 키

로컬 public/assets/sprites/ 경로와 동일한 키를 씁니다.

  • artifact: items/artifacts/{id_dots_to_underscores}.png
  • oopart: items/ooparts/{id_dots_to_underscores}.png

예: item.dagger.basicitems/artifacts/item_dagger_basic.png

바이너리 업로드·Worker R2 바인딩은 후속 작업입니다. 당분간 클라이언트는 Vite public/ 정적 파일을 사용합니다.

아이콘 생성·투명화: (내부 문서) §10 — DT + rembg (색 키잉 아님).

환경 변수

루트 .env (gitignore) — 시드용:

SUPABASE_URL=https://….supabase.co
SUPABASE_PUBLISHABLE_KEY=…
SUPABASE_SECRET_KEY=…        # 시드·관리 쓰기 전용 (클라이언트에 넣지 말 것)

클라이언트 Vite (packages/client/.env / .env.example):

VITE_SUPABASE_URL=https://….supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=…   # publishable only

명령

# 원격 스키마 적용 (supabase/migrations/)
pnpm db:apply-migrations

# TS → Supabase upsert (items + combos)
pnpm db:seed-items

# Admin tuning bundle → game_tuning (id = live)
pnpm db:push-tuning data/admin-tuning.json

마이그레이션 SQL은 레포에 있으며, 원격 적용은 Supabase MCP / Dashboard / DB connection으로 수행합니다.
CLI supabase linkSUPABASE_ACCESS_TOKEN이 필요합니다.

시드 흐름

  1. ITEM_DEFS 로드 → sprite_key 생성 → items upsert (on_conflict=id)
  2. item_adjacency_combos 전체 delete 후 insert (TS에서 제거된 콤보 정리)
  3. 현재 규모: items 80 (유적 전용 25 포함), adjacency combos 26

유적 전용 아이템 (25)

유적 archetype 권장 포지션 오파츠 (2) 아티팩트 (3) 강화 소재 materialId
ember_foundry magnum oopart.foundry.overdrive_joint, oopart.foundry.heat_sink_spine item.foundry.gearblade, item.foundry.articulated_coil, item.foundry.overload_damper mat.ruin.ember_foundry
sunken_reliquary ochler oopart.reliquary.echo_cistern, oopart.reliquary.return_valve item.reliquary.condenser_core, item.reliquary.resonance_plate, item.reliquary.portable_still mat.ruin.sunken_reliquary
verdigris_menagerie chimera_sage oopart.menagerie.incubator_gland, oopart.menagerie.symbiote_lattice item.menagerie.culture_cradle, item.menagerie.specimen_hook, item.menagerie.sealing_clamp mat.ruin.verdigris_menagerie
hollow_observatory mage oopart.observatory.prismatic_focus, oopart.observatory.mana_astrolabe item.observatory.void_lens, item.observatory.channel_rod, item.observatory.tidal_charm mat.ruin.hollow_observatory
ossuary_bastion knight oopart.bastion.aegis_vertebra, oopart.bastion.oath_censer item.bastion.gate_shield_fragment, item.bastion.reliquary_plate, item.bastion.warden_sigil mat.ruin.ossuary_bastion

이번 패스에서 하지 않음

  • R2 버킷 생성·업로드 자동화
  • i18n 이름 테이블
  • admin board가 Supabase에 직접 쓰기 (shape·아이콘 Apply는 여전히 브라우저 localStorage; 공유 반영은 Export → pnpm db:push-tuning)

어드민 튜닝 DB (game_tuning)

어드민 보드에서 조정한 무기 VFX · 몬스터 배틀 · 스테이지 · 아이템 shape/아이콘 오버라이드를 Supabase에 올려 프로덕션 부트 시 자동 로드합니다.

항목
테이블 public.game_tuning
프로덕션 키 id = 'live'
번들 형식 AdminTuningBundle v1 (adminTuningBundle.ts)
마이그레이션 supabase/migrations/20260711190000_game_tuning.sql
시드 pnpm db:push-tuning [path] — 기본 data/admin-tuning.json
부트 로드 loadGameTuningAtBoot()main.tsx

컬럼

컬럼 localStorage 키 설명
weapon_projectile cf-weapon-projectile-overrides 무기 투사체/VFX 프로필
monster_battle cf-monster-battle-overrides 몬스터 스케일·오프셋
stage_design cf-stage-design-overrides-v1 층별 스테이지 오버라이드
item_shape cf-item-shape-overrides shape + 아이콘 회전/오프셋

RLS: anon / authenticated SELECT만. 쓰기는 SUPABASE_SECRET_KEY 시드 스크립트만.

워크플로

  1. 로컬 어드민에서 튜닝 → 튜닝 보내기로 JSON 저장 (data/admin-tuning.json 권장)
  2. 원격에 마이그레이션 적용 (Dashboard / Supabase CLI)
  3. pnpm db:push-tuning data/admin-tuning.json
  4. pnpm deploy:client — 부트 시 Supabase live 스냅샷이 localStorage에 replace 후 shape hydrate

어드민 DB에서 받기는 개발 중 live 스냅샷을 브라우저로 덮어씁니다 (읽기 전용 REST).

부트 순서

loadItemCatalogAtBoot → loadGameTuningAtBoot → applyShapeOverrides → preloadGameAssets

Supabase 미설정·행 없음·빈 스냅샷이면 기존처럼 브라우저 localStorage만 사용합니다.