개발 일지

프로젝트: 라이프 튜토리얼 — AI 기반 게임형 실전 생활법률·금융 교육 플랫폼. 수능 직후 고3~사회초년생이 악덕 사장·중개인·보이스피싱범을 상대로 실전 리허설하며 노동·생활법률을 체득하는 AI 시뮬레이션 게임. 스택: 백엔드 FastAPI + PostgreSQL(pgvector) / 프론트 Next.js 16 + React 19 + Phaser 4 + zustand + MSW. 아키텍처: Hexagonal + Clean Architecture + DDD 기반 모듈러 모놀리스, “1 ERD 테이블 = 1 Fractal 11-File Set = 1 AI 위임 단위” 원칙으로 AI 하네스 엔지니어링.

처음 보는 분을 위한 3줄 안내
  1. 이 문서는 개발팀의 날짜별 작업 일기다. 그날 무엇을 만들었고, 무엇이 실패했고, 어떻게 고쳤는지를 기록한다.
  2. v1, v2 같은 표기는 같은 것을 몇 번째 고친 버전인지이고, % 숫자는 시험(평가) 점수다. 수치의 전체 그림은 평가 지표 대시보드에 모여 있다.
  3. 낯선 용어(RAG, 방어전, 판정 등)는 용어 정의에서 쉬운 말로 풀어두었다.

2026-06-27 — 팀 구성

  • 팀 프로필 문서 작성(팀프로필.md). 해커톤 예선 대비 팀 체계 정리.

2026-07-06 — 기획 문서 3종 확정 (v1.2)

  • PSST 사업계획서 v1.2: 예비/초기창업패키지 표준 양식 기반. 교사용 대시보드 폐기 → 교사 관여 제로 B2G(기관 구매·바우처·자동 집계 리포트) 재구조화, 콜드스타트 해법을 또래 바이럴(결과 카드 공유·친구 대결)로 교체.
  • 개발 정의서(DEVELOPMENT_SPEC) v1.2: 기술 요구사항·ERD 초안·Decision Log(D-01~) 체계 수립.
  • UI/화면 정의서 v1.2: 학생판 MVP 화면 설계(알바 연대기 챕터), 가드 촬영 품질 게이트, 또래·사전예약 화면.

2026-07-09 ~ 07-10 — 시스템 설계 다이어그램

  • 시스템 아키텍처·플로우 다이어그램(drawio/png), MVP 플로우 별도 작성.
  • 기술 스택 정리(tech_life.md), 워크플로 정리(workflow_life.md).

2026-07-28 ~ 07-31 — 예선 통과, 본선 대응 설계

  • 해커톤 본선 대응 통합 정리서 v0.1 (7/28): 본선 일정이 ‘26.11 → ‘26.9 첫 주로 당겨져 개발 기간 약 5주 확정. 현장 로컬 평가 확정 → 웹 데모 필수(D-36). 심사 배점(참신성 30/구현 가능성 30/현장 실효성 30/보안성 10) 대응 전략과 검증 통계(임금체불 2조 678억, 청소년 알바 19.5% 근로계약서 미작성 등) 원출처 확보.
  • 핵심 콘텐츠 설계서 4종 (7/31):
    • A1 퀘스트 상세설계서 — “근로계약서 쓰기” 퀘스트(대화 방어전 3단 방어선)
    • A2 판정 프롬프트 스펙 — LLM 판정 verdict 체계
    • A4 Fake 어댑터 데모 시나리오 — 오프라인 데모용 표준 시퀀스
    • A5 미니게임 콘텐츠
  • 서비스 핵심 정리(데이터·게임·가드·리포트 4축) 문서화.

2026-08-02 — 백엔드 착수: core 인프라 + ontology BC (v0.1.0 ~ v0.5.0)

5주 실행 계획 v0.2 확정(“3분 데모 시나리오에 나오는 것만 만든다” — 가드·리포트는 문서/목업으로 전환)과 함께 백엔드 개발 시작. 하루 만에 v0.1.0 → v0.5.0.

  • v0.1.0core/matrix/ 전역 인프라 첫 파일: Seraph 인증 매니저(bcrypt 해싱 + JWT HS256, 테스트 7건).
  • v0.2.0~v0.2.1 — Keymaker 시크릿 매니저(시크릿 단일 서빙 지점), Oracle DB 매니저(공통 declarative Base + 트랜잭션 경계 세션), 의존성 정비.
  • v0.3.0 — 전 패키지 버전 고정, SQLModel 제거(Boundary Gate 원칙과 상충 → SQLAlchemy 2.0 단일화).
  • v0.4.0 — Alembic 마이그레이션 환경 구축(시크릿은 .env에서 주입, ini 미포함).
  • v0.5.0ontology BC 첫 구현 (Fractal 파일 세트 풀 배선): 법제처 DRF API 법령 수집 게이트웨이 → Law/LawArticle/LawChunk 애그리거트 → pgvector cosine 검색까지. EmbeddingPort 어댑터 패턴(Gemini 온라인 / 로컬 오프라인 스텁, .env 한 줄로 전환). 근로기준법 132개 조문 → 284청크 수집·임베딩 E2E 실측. Docker Compose로 pgvector/pg17 DB(포트 5433) 기동.

2026-08-03 — 백엔드 데이터·판정 파이프라인 완성 (v0.6.0 ~ v0.9.0)

  • v0.6.0 — quest·npc BC 스캐폴딩(myself 엔드포인트 배선 검증, TDD). 시행령·시행규칙 추가 수집 → 총 11개 법령 1,051청크.
  • v0.7.0로컬 임베딩 어댑터(Qwen3-Embedding-4B, 1536차원 → 스키마 무변경) + provider별 벡터 공존 컬럼(온/오프라인 전환 1분 요건의 의도적 역정규화). 로컬 재임베딩 1,051청크 GPU 38초. 판정 API 1차 POST /npc/judge — Gemini Flash-Lite / EXAONE 7.8B(Ollama) 이중 어댑터, 안전장치 3종. A/B 1차에서 제재 주체 오인 케이스에 Gemini 오판정·EXAONE 정답 확인.
  • v0.8.0 — 행정규칙(admrul)·법령 별표 수집: 2026년 최저임금 고시(시간급 10,320원), 단순노무직종 지정 고시(수습 감액 함정 근거), 시행령 별표 7종. 첨부 PDF 본문 추출 폴백.
  • v0.9.0 — 고용노동부 수동 수집 문서 ingestion: 근로기준법 질의회시집 회시 399건 + 통상임금 노사지도 지침. 총 1,593청크 × 2개 벡터 공간(Gemini/로컬) 완성.
  • 실현가능성 검증 문서 작성: “이미 돌아가고 수치로 측정했다” 원칙으로 전 파이프라인 실측 기록(RTX 5060 Ti 16GB). 계획 대비 W1 완료 + W2 조기 달성.
  • AI 모델 구성 v1 확정: 온라인 Gemini / 오프라인 Qwen3-Embedding-4B + EXAONE 7.8B, 대사 생성만 외부 API(VRAM 16GB 예산).

2026-08-04 — ERD v1.0

  • 서비스 4개 축(메인게임·미니게임·가드·리포트)을 테이블 구조로 번역한 ERD v1.0 확정. 설계 규칙: 1NF→3NF 정규화, 고립 테이블 금지, 1 테이블 = 1 Fractal 11-File Set.
  • 기획서 보강(4개층원리.md, 주요특징·핵심기술).

2026-08-23 — 프론트엔드 집중 개발일: v0.1.0 → v0.6.3 하루 완주

FE-brief 확정(웹 데스크톱 퍼스트, Phaser는 허브맵만·나머지는 React DOM — 한글 IME 안전 설계)과 함께 프론트 스캐폴딩부터 게임 화면까지 하루에 구축.

  • v0.1.0 — 스캐폴딩: Next.js + React 19 + TS strict + Tailwind 4. Phaser 4 안전 통합(EventBus 싱글턴, StrictMode 이중 마운트 가드), feature-sliced 디렉토리 골격, 포트 3100 고정, 하드코딩 localhost 0건.
  • v0.1.1 — Next.js 16.3.2 업그레이드: Phaser 4.2.1 + Turbopack 조합 검증 통과, npm audit 0건.
  • v0.2.0 — 폰트·테마: Galmuri11(픽셀) + Pretendard(법령 조문) self-host — 외부 CDN 참조 0건(폐쇄망 시연 요건). 게임 톤/유틸리티 톤 CSS 변수 체계.
  • v0.3.0 — API 계약 v1 + MSW 목 서버: 엔드포인트 5종·이벤트 17종 계약(계약 우선 방식, 백엔드 구현 스펙의 원본). 키워드 판정기 + 실제 조문 원문(법제처 API 현행 기준) 목 데이터. npm run check:contract 10스텝 자동 검증 PASS.
  • v0.4.0 ~ v0.4.1 — 대화 방어전 화면: useBattle 상태 머신(세션 멱등 시작·체크포인트 재개·판정 루프·방어선 돌파), 한글 IME 조합 중 Enter 가드, 방어선 게이지 3단, 타이프라이터 대사, 근거 카드(조문 원문 렌더링). 팀장 지시로 웹 퍼스트 원칙 명문화 + 풀스크린 스테이지 레이아웃 재구성.
  • v0.5.0 — 허브맵(Phaser 4, D-37): ASCII 배열 맵 + 격자 이동(방향키/WASD)·벽 충돌·노드 상호작용. 진행 상태 SSOT는 zustand, 씬은 상태 미소유(map:hydrate 주입). EventBus 이중 인스턴스 번들 사고·StrictMode 리스너 전역 삭제 사고 두 건 해결.
  • v0.6.0 — 결과 화면: 스탯 변동 카드 + “지켜낸 돈” 카운트업 연출, 체크포인트 localStorage 영속(새로고침 복귀), 클리어 시 다음 퀘스트 언락. check:contract 11스텝 PASS.
  • v0.6.1 ~ v0.6.3 — 실제 스프라이트 파이프라인: 3면도 시트 크롭 → 마젠타 키잉 → 캔버스 정규화 → 48×48 네이티브 픽셀 퍼펙트 세트 교체. 허브맵 격자를 48px 기준(타일 32px, 맵 36×16 확장)으로 전환. 캐릭터 스프라이트 제작 규격 가이드 문서화.

2026-08-24 — 풀스택 관통일: 인증 → 게임 API 이관 → 2-Call 판정 → Guard OCR (BE v0.10.0→v0.12.15 / FE v0.7.0→v0.8.17)

하루에 백엔드 19개 버전·프론트 27개 버전. 설계서 승인(superpowers 설계 문서 7종 + 구현 플랜 4종 — auth, gameplay-backend, dialogue-2call, guard-ocr, quest-replay-score, play-records-reset, stat-view-dropdown) → TDD 구현 → 실연동 검증 사이클을 네 축(인증·게임 플레이·판정·가드)에 반복 적용.

① 인증 풀스택 (BE v0.10.0~v0.10.1 / FE v0.7.0~v0.7.2)

  • BE v0.10.0 — auth BC: 아이디·생년월일·비밀번호 가입/로그인, 구글 OAuth(생년월일 1회 온보딩), access JWT(15분) + refresh HttpOnly 쿠키(7일, 매 갱신 rotate). 테이블 3종(player/player_oauth/refresh_token, Alembic), API 8종(/api/v1/auth/*), CORS localhost:3100 + credentials, 테스트 15건. DB에는 birth_year(연도)만 저장.
  • BE v0.10.1 — 구글 OAuth 실연동 준비: Keymaker GOOGLE_*/FRONTEND_ORIGIN 시크릿, CSRF lt_oauth_state 쿠키, 미설정 시 GOOGLE_DENIED 안전 폴백.
  • FE v0.7.0 — auth feature: 로그인/회원가입/생년월일 온보딩/구글 버튼 UI, AuthGate로 게임 라우트 보호, authStore(accessToken 메모리 only — persist 금지) + apiFetch 401→refresh 자동 재시도, MSW auth 핸들러.
  • FE v0.7.1~v0.7.2 — 허브맵 헤더 로그아웃 버튼, auth만 실백엔드 bypass하는 하이브리드 모드(과도기 구성).

② 게임 플레이 API 백엔드 이관 (BE v0.11.0~v0.12.2 / FE v0.7.3, v0.8.0~v0.8.4)

  • BE v0.11.0 — quest BC gameplay API: GET /quests · POST /quest-sessions · POST /dialogue-turns · POST /hints · POST /learning-events 5종, 테이블 7종(category/chapter/quest/defense_line/quest_session/player_stat/learning_event, Alembic), Q-ALBA 시드, JUDGE_PROVIDER=fake|gemini|exaone Factory.
  • BE v0.12.0quest_clear_score 장부: 퀘스트별 최초/최근 보상 분리, CLEAR 시 player_stat 재합산, 턴 결과 scoreBucket.
  • BE v0.12.1~v0.12.2 — 연습 모드(CLEARED 퀘스트 즉시 재도전, PRACTICE면 점수·money·학습 무기록) + DELETE /play-records(계정 유지 초기화).
  • FE v0.7.3FALLBACK_MODE=0에서 MSW 완전 비활성 — auth·게임 API 전부 실백엔드(8100). MSW 목 시대 종료.
  • FE v0.8.0~v0.8.4 — 스탯 카드 최초/최근 드롭다운 전환, 연습 모드 안내 문구, 허브 헤더 「플레이 기록 제거」 버튼.

③ 2-Call 판정 아키텍처 (BE v0.12.3~v0.12.8) — “판정은 로컬 SLM, 말투만 외부 API”

  • 판정 JUDGE_PROVIDER=exaone(로컬 SLM + pgvector RAG) / 대사 NPC_SPEECH_PROVIDER=gemini|seed 분리 — NpcSpeechPort + Gemini/Seed 어댑터.
  • 유저 발화 벡터 검색 → articles[]를 판정 프롬프트에 주입. 방어선 CLEAR 근거 조문은 RagLawEvidenceAdapterDB 원문으로 반환(RAG 실패 시에만 시드 폴백) — 할루시네이션 방지 설계 유지.
  • SLM 미기동·모델 없음 시 키워드 판정 폴백, OLLAMA_BASE_URL로 원격 Ollama 지원.
  • E2E 실측: EXAONE 7.8B + RAG 판정이 법령 근거와 함께 CLEAR 반환 — warm 추론 1.4~2.0초, 전체 왕복 3.05초(GPU 100% 로드).

④ Guard OCR 계약서 진단 (BE v0.12.9~v0.12.15 / FE v0.8.13~v0.8.17) — 목업이던 가드를 실기능으로

  • BE: ContractScanInteractor 제출→OCR→필드 확인→판정→리포트→파기 전체 흐름(TDD), PaddleOCR 어댑터(grid_ocr_manager 싱글턴, zonal crop + 임금 휴리스틱, GUARD_OCR_PROVIDER=fake|paddle), 리포트에 법령 인용·상담기관 연계·퀘스트 추천 시드 어댑터 3종 부착.
  • 개인정보 설계: OCR 완료·실패(UNREADABLE) 모두 원본 이미지 즉시 삭제 + image_purged_at 기록, 파기 시 PURGED 상태 전이 후에도 finding 메타데이터 기반 리포트 조회 유지.
  • 데이터 정합: 2026년 최저임금 기준을 시간급 10,320원으로 정정(guard_rule 데이터 마이그레이션 포함), 문자열 "true"/"1" 불리언 판정 버그 수정, 근로기준법 제20조 인용을 검증된 단일 조문으로 축소.
  • FE: /guard 전체 플로우 entry → capture → analyzing → confirm → report 완성 — 촬영 품질 게이트(10MB·MIME·라플라시안 흐림 검사), EXIF 제거·개인정보 가림 확인 절차, 저신뢰 필드 직접 입력·PATCH 확정, 심각도·조문 원문·상담 CTA·추천 퀘스트 리포트, UNREADABLE 재촬영 UX(회귀 테스트 포함).

⑤ 허브맵 UX·원격 협업 (FE v0.7.4~v0.7.8, v0.8.5~v0.8.12)

  • 실백엔드 전환 후 드러난 하이드레이션 버그 연쇄 해결: 새로고침 시 노드 색 유실·퀘스트 진입 불가·StrictMode 세션 시작 스킵 — 최종적으로 EventBus 레이스를 폐기하고 PhaserHost(nodeStates)가 씬의 hydrateNodes()를 직접 호출하는 구조로 확정.
  • 허브맵을 세로 필드(도로·잔디·나무·물·편의점 노드)로 교체, 이동키 1회에 다음 노드까지 연속 이동, 카메라 추적, CLEARED 재진입은 맵 노드 상호작용으로 일원화(중복 버튼 제거).
  • FE v0.8.12 — Next.js 리버스 프록시(/api/*BACKEND_ORIGIN): Tailscale/LAN 원격 기기에서 로그인 실패하던 문제 해결 — 프론트 담당 신채연 원격 작업 환경 개통, 데모 계정 발급·검증.
  • 다음 단계: 미니게임(CALC/DOC), 구글 OAuth 실연동, PaddleOCR 실촬영 이미지 검증, 신규 캐릭터 에셋 파이프라인.

2026-08-25 — 게임 월드 대확장일: CALC 던전 풀스택 + 허브 오픈월드·편의점 실내·전투 UI 리뉴얼 (BE v0.12.16→v0.12.23 / FE v0.9.0→v0.20.27)

하루에 백엔드 8개 버전·프론트 92개 버전. 백엔드는 Stage 2 주휴수당 CALC 던전과 판정 평가 하네스를 닫았고, 프론트는 허브맵을 Modern Exteriors 기반 가로 거리 오픈월드로 갈아엎은 뒤 실내 씬·NPC 시나리오·포탈·전투 UI까지 한 번에 밀어붙였다. 설계 문서 4종(stage2-holiday-pay, w3-remaining, judge-eval-stabilization, wiring-probe-myself) + 구현 플랜 2종 승인 → TDD 사이클 유지.

① Stage 2 주휴수당 CALC 던전 풀스택 (BE v0.12.16~v0.12.19 / FE v0.11.0)

  • BE v0.12.17calc_problem 테이블(Alembic, A5 Q1~Q5 시드) + holiday_pay 도메인 서비스(expected_answer(kind, params, wage) 정답 계산), quest BC 전용 MIN_WAGE_2026 = 10,320원 VO. Q-ALBA-002 지켜낸 돈을 Q5 총액 330,240원 런타임 계산으로 정정.
  • BE v0.12.18~v0.12.19quest_session.calc_progress JSONB로 문항 조회·정수 답안 제출·시도 횟수 추적, start_or_resume CALC 세션 지원, 최종 정답 4개 이상 시 CLEAR(기존 점수 장부 재사용). HTTP 스키마·라우트 + FastAPI 의존성 오버라이드 계약 테스트.
  • BE v0.12.16GEMINI_API_KEY 부재 시 POST /quest-sessions 500 버그 수정(KeymakerSecretError를 미지 프로바이더로 오인) — 로컬 기본 임베딩 local로 키 없이 세션 시작 가능.
  • FE v0.11.0/minigame/holiday-pay feature slice: 서버 제공 문항·시급 기반 규칙 카드, 5문항 진행, 숫자 키패드, 힌트·해설, CALC 클리어 → 기존 결과 화면 연계.

② W3 대화 마감·판정 평가 하네스·전 BC 배선 프로브 (BE v0.12.20~v0.12.23)

  • v0.12.20 — W3 잔여: dialogue_turn 테이블 + 저장 전 PII 정규식 스크러빙(scrub_pii), 동일 방어선 3턴 이상 시 ONGOING에도 hint1 자동 부착, Fake A4 E2E + 오프라인 스모크 스크립트(P0-4).
  • v0.12.21 — 판정 평가셋 안정화: A2 §4 평가셋 시드 + run_judge_eval.py(기본 Fake, gemini/exaone 실측 선택), 오입력 10케이스 추가. 실측 리포트(docs/judge_eval_report.md): A2 25케이스 정확도 100%, 오입력 10케이스 오CLEAR 0. Fake↔실LLM 전환 문서화 — .env 1~3줄, 1분 이내 전환(P0-4·W5).
  • v0.12.22 — 로컬 Guard OCR을 GUARD_OCR_PROVIDER=paddle로 전환 — PaddleOCR 실문서 필드 추출 동작.
  • v0.12.23 — myself 배선 프로브: wiring_probe 테이블 + grid_wiring_probe(콘솔+DB 기록), 전 6개 BC(ontology/quest/npc/auth/game/guard) myself 엔드포인트에 probeId·layers 부착, 6경로 × 3레이어 자동 검증 테스트.

③ 허브맵 오픈월드 리뉴얼 (FE v0.9.0~v0.17.7)

  • v0.9.0 — 세로 노드 레일 맵 → 가로 거리 오픈월드(Modern Exteriors 48×48 에셋 33종 큐레이션, 유료 라이선스라 gitignore). 상단 상가 / 도로 / 하단 인도·공원, 소품 배치 chapter1-props.ts 콘텐츠 분리, y기준 depth 정렬. 충돌은 화이트리스트 → 블랙리스트로 전환해 자유 이동.
  • v0.10.0·v0.12.0·v0.13.0클릭/터치 A* 길찾기(easystarjs), 8방향 대각선 이동(코너 컷 금지 + 슬라이딩, √2 등속 보정), 대각선 전용 스프라이트 4종(NE/NW/SE/SW), 캐릭터 표시 높이 48→72px.
  • v0.14.0~v0.16.3 — 도로 생태계: 횡단보도(맵 문자 +), 차량 4대 순환 주행(연두 버스·주황 택시·검정 해치백·흰 SUV — 팩에 없는 색은 HSV 리컬러), 차량↔플레이어 상호 충돌, 차간 정지 로직(앞 차 간격 이내 정지 → 2.5초 후 재주행).
  • v0.17.0~v0.17.7 — Phaser 스케일 FITRESIZE(레터박스 제거, 1:1 픽셀), 맵 60×21→45×21 압축, 건물 3동을 콘도·중형 마켓으로 교체(HUB_STORES 메타 객체화 — 건물별 폭·문 위치 콘텐츠 지정).

④ 편의점 실내 씬 + 백엔드 무의존 목 서버 (FE v0.18.0~v0.18.19)

  • v0.18.0 — InteriorScene: 1번 가게 문 진입 시 top down 편의점 실내로 전환(Grocery Store 팩 큐레이션, store1-interior.ts 콘텐츠 분리). NPC 접촉/클릭 → 퀘스트 진입, 출구 매트 → 허브 문 앞 복귀.
  • v0.18.3 — npm run dev:mock: 백엔드 다운 상태에서도 도는 목 dev 서버(포트 3101). Next 16의 distDir당 1서버 잠금을 NEXT_DIST_DIR=.next-mock으로 우회해 3100과 병행, MSW에 demo 계정 시드 + 목 refresh 세션 복원.
  • v0.18.2~v0.18.19 — 실내 디테일 16회 반복 다듬기: L자 카운터 180도 회전(카운터 너머 대화), 진열대·쿨러·ATM·핫푸드 워머 재배치, 퀘스트 배경과 동일한 크림+민트 스트라이프 벽 합성, 벽 매립 뒷문.

⑤ 월드 디테일·NPC 배치 (FE v0.19.0~v0.19.23)

  • v0.19.0 — 건물 사이 백드롭 4구간(chapter1-backdrop.ts + 에셋 17종): 잔디밭+빌보드, 뒷골목, 필러 콘도 2동, 차기 챕터용 공사장.
  • v0.19.1~v0.19.4 — 나사장 NPC를 임시 네모 → pixellab 생성 스프라이트로 교체(몸 폭 기준 ×2 정수 배율 보정), 플레이어 걷기 바운스 애니메이션.
  • v0.19.5~v0.19.13세로 차도 신설(맵 문자 V, 반 칸 확폭·보행 전환) + 가로 횡단보도(x), 건물 존 전면 인도 타일화, LIME CORP 오피스 빌딩·버스정류장(합성 에셋) 배치.
  • v0.19.14~v0.19.23 — 공원 분수·꽃덤불·실내 ATM, 앉아있는 친구 NPC(pixellab 생성, 머리 크기 기준 배율), 퀘스트 상태 말풍선 2종: 클리어 시 친구 머리 위 물방울, 미클리어 시 나사장 머리 위 빨간 느낌표(시트 픽셀 실측 크롭, hydrateNodes 상태 연동).

⑥ 시나리오·포탈·전투 UI 리뉴얼 (FE v0.20.0~v0.20.27)

  • 시나리오 대화 (v0.20.0·v0.20.3·v0.20.11~v0.20.12): 주휴수당 미니게임 진입을 공원 친구 대화로 이동(EventBus 5번째 이벤트 map:friend-selected), 나사장 접근 인사 5줄 시나리오(content/dialogs.ts 분리) 후 ‘반박하러 가기!’ → 전투. 퀘스트 종료 시 실내 원래 자리로 복귀(returnPoint, 1회 소비).
  • 포탈 (v0.20.4~v0.20.5·v0.20.20·v0.20.22~v0.20.26): 노드 1을 마커 → 골드 포탈로, 밟으면 자동 입장 + 250ms 페이드 전환, 메이플풍 빛기둥 연출(프로시저럴 픽셀아트 스프라이트시트 자체 제작, 48×96 8프레임 — 형태 3회 반복 조정으로 낮고 넓은 빛 커튼 확정). 키 홀드로 포탈을 지나치던 입력 버그 수정.
  • 전투 화면 리뉴얼 (v0.20.6~v0.20.10·v0.20.13~v0.20.19·v0.20.21): refs/quest-example.png 레이아웃(전체 화면 편의점 배경 + 배너/이름표/스탯/대사창/행동 메뉴)으로 재구성 후 기존 게임 톤(다크 서피스+적대 레드)으로 재통일. 컨테이너 1440px, 하단 3패널 높이 통일 + 입력창 2행 분리, 행동 메뉴 ▶ 포커스, 화자 인라인 표기, 시나리오·전투 대사창 타자 효과 통일, “반박 성공!” 중앙 bounce 배너.
  • 대화 중 이동 차단 (v0.20.21) — EventBus 6번째(상한) 이벤트 map:dialog: 대화 열림 시 씬 키보드 비활성·경로 큐 초기화, 닫힘 시 복원.
  • v0.20.27 — 횡단보도 신호등 시스템: 가로도로 횡단보도 2곳에 신호등 자동 배치(맵 +에서 유도), 플레이어 접근 시 빨강→초록·차량 정지, 횡단 완료/이탈 시 복귀. 걸친 차는 최소 이동 후 스냅 정지.
  • 다음 단계: DOC 미니게임, CALC 던전 실백엔드 연동 검증, 전투 캐릭터·초상화 일러스트 입고, 구글 OAuth 실연동.

2026-08-26 — 폴리시·연출의 날: 신호등 교통 완성 + 전투 연출·수호천사 펫 + Guard DOCX + 랜딩 라이브 허브맵 (BE v0.12.24→v0.12.27 / FE v0.20.28→v0.20.52)

백엔드 4개 버전·프론트 25개 버전. 어제 열어둔 월드를 하루 종일 다듬는 날 — 신호등 교통 체계를 실제 교통처럼 완성하고, 전투 화면에 나사장 스프라이트·수호천사 펫·반박 성공 연출을 얹고, 가드에 Word(.docx) 업로드를 풀스택으로 열고, 랜딩 페이지를 정적 히어로에서 살아있는 허브맵 어트랙트 모드로 교체했다. 심사 가독성 대비 폰트 전면 확대와 MSW/실백엔드 세션 안정화까지. 별도로 insight BC(상담 준비 리포트) 설계 문서 v2.2 확정.

① 신호등 교통 체계 완성 (FE v0.20.28~v0.20.33)

  • 차량 반응을 횡단보도별 독립 동작으로 변경: 빨간불 쪽 차는 평소 주행, 초록불에 걸친 차는 2.2배 가속 탈출 후 정상 복귀, 접근 차는 정지선(가장자리 ±8px)에서 정지 — 뒤차는 tailgating으로 자연스럽게 줄을 선다.
  • 차량 겹침 방지 하드 클램프(clampBehindLeader, 앞 차 꽁무니 GAP 24px), 신호등 기둥 충돌 처리(키보드·A* 길찾기 모두), 초록불 트리거 범위 확장(대기 구역 좌우 1칸 + 무단횡단 지브라 진입 반응).
  • 포탈 광선 하단의 흰 직사각형 아티팩트 제거(심지 막대 삭제 + 알파 페이드) 후 타원형 발광 코어로 “바닥이 빛나는 느낌” 복원. 인도 타일은 회녹색 사선 격자(Others_7)로 최종 교체.

② 편의점 실내 비주얼 완성 (FE v0.20.34·v0.20.36~v0.20.37·v0.20.42)

  • 바닥을 quest-bg 웜 베이지(216,197,181) 리컬러 타일로, 벽은 Room_Builder_3d_walls 키트를 quest-bg 팔레트(크림+청록 스트라이프)로 재색칠해 뒷벽·측벽·코너를 단일 키트로 재구성 — 코너 접합 열 추가로 캡→사선→벽면 연속 접합.
  • 최종적으로 사전 렌더 벽 링 한 장(wall-ring.png 768×576, 재생성 스크립트 gen-wall-ring.py)으로 합성: 뒷벽면 3타일 확장, 민트 띠가 모서리를 감아 도는 연출, 남쪽은 바닥+흰 캡 스트립 확정, 충돌선을 벽면 바닥 경계(y144)와 정합. 중앙 집기 반 칸 하향·벽 간판 4종 제거.

③ 전투 화면 연출·수호천사 펫 (FE v0.20.38~v0.20.42·v0.20.50)

  • 나사장 전투 스프라이트(128×160, PixelLab Pro — 맵 48×48과 동일 외양의 고디테일 정면 팔짱 포즈)를 배경 위 실내 NPC 위치 대응 지점에 배치.
  • 수호천사 펫 확정: 천사 강아지(크림 강아지+갈색 귀+금색 후광+흰 날개, PixelLab Pixflux). 허브 동행판은 품질 이슈로 철회하고 전투 화면 전용으로 확정 — 스탯 패널 위 둥둥 부유(pet-float), 힌트를 펫 말풍선(CSS 픽셀아트, 이미지 불사용)으로 이동, 말투 변환 petify(‘멍’ 부착, 따옴표 안 모범답안은 원문 보존).
  • 반박 성공 시퀀스: 나사장 피격 흔들림 + ‘반박 성공!’ 배지 → 클리어 대사 타자 효과 → 대사창 클릭 시에만 조문 카드 공개. 방어선 게이지는 HP 스타일로 반전(돌파마다 깎임). 결과 화면 패배 초상화 자리에 펫 배치.
  • 대화→전투 250ms 페이드 전환, PLAYER 스탯 3종(신용·멘탈·법률지식) 게이지화, 전투·허브·던전 폰트 전면 확대(심사 가독성, 해커톤 대비).

④ 허브 상태 연출·스몰토크 UX (FE v0.20.43~v0.20.45·v0.20.47~v0.20.49)

  • 노드 2·3 숫자 마커 → 상태 반응형 포탈: LOCKED 회색 정지 / AVAILABLE·IN_PROGRESS 금색 맥동+빛기둥 / CLEARED 파란 맥동 — 퀘스트 1 클리어 시 노드 2가 해금되며 회색→금색 전환.
  • hydrate 버그 2건 해결: 클리어 후에도 빨간 느낌표 잔존(node: null 조건 버그), 실내→허브 복귀 시 친구 물방울 누락(CREATING 씬 hydrate 스킵). 친구 말풍선은 노란 물음표로 교체.
  • 대화 중 이동 차단 실효화(dialogOpen 게이트 — key.isDown·클릭 길찾기 모두 차단), NPC 대화 Space 진행(스킵/다음 줄/마지막 줄 전투·미니게임 진입).

⑤ 세션·진행 초기화 안정화 (BE v0.12.24~v0.12.25 / FE v0.20.35·v0.20.43·v0.20.46)

  • MSW 하드 리로드 로그인 튕김 근본 해결: StrictMode 이중 이펙트에서 worker.start() 거부 경로가 게이트를 조기 개방 → 핸드셰이크 전 refresh가 실백엔드 401. 센티널(GET /msw-ready) 확인 후 렌더 + 목 auth 마지막 로그인 영속(lt-mock-auth-v1)으로 수정.
  • 실계정 로그인 실패 해결: NEXT_PUBLIC_FALLBACK_MODE=0(실백엔드) 전환 + 잔존 mockServiceWorker 등록 해제.
  • BE v0.12.24 — 플레이 기록 전면 초기화: DELETE /play-records가 대화 로그→세션→점수→학습 이벤트→스탯에 더해 가드 스캔·이미지·finding까지 삭제(ProgressWipePort + GuardProgressWipeAdapter). 허브 moneyDefendedTotal은 CLEARED 집합(세션∪점수장부) 기준 합산으로 정정.
  • BE v0.12.25 — EXAONE 판정: keep_alive 30m만 유지, num_predict 상한·RAG 조문 360자 절단은 철회(전문·무제한 출력 복원).

⑥ Guard DOCX 업로드 풀스택 + OCR 정확도 (BE v0.12.26~v0.12.27 / FE v0.20.51)

  • BE v0.12.26 — 텍스트 Word(.docx) 본문·표 추출(python-docx) 후 기존 필드 휴리스틱 재사용. ZIP+word/document.xml 매직·MIME 검증, 스캔 이미지 DOCX·구형 .doc 제외 (설계: 2026-08-26-guard-docx-design.md).
  • BE v0.12.27 — 근로계약서 OCR 정확도: LABOR_CONTRACT는 zonal crop을 건너뛰고 전문 텍스트에서 임금·소정근로·주휴일·연차·위약금 추출, 시간급/시급만 시급 인정(1,000~100,000원 범위) — 표준연소근로계약서의 wage=2·연차 14 오인식 수정.
  • FE v0.20.51 — 가드 업로드에 JPG/PNG + .docx 선택 지원. 블러·EXIF는 이미지에만, DOCX는 파일명 미리보기·본문 텍스트 점검.

⑦ 랜딩 페이지 리브랜딩 + 전체화면 모드 (FE v0.20.50·v0.20.52)

  • 랜딩을 정적 히어로 한 장 → 라이브 허브맵 어트랙트 모드로 교체: 실제 허브 씬을 캐릭터 숨김·입력 차단으로 마운트(차량·신호등 동작) + 반투명 네이비 오버레이 + 로고 + 로그인/PLAY DOM 버튼. PLAY 클릭 시 400ms 암전 후 /hub의 카메라 fadeIn과 이어지는 심리스 전환.
  • 브랜드 에셋: 나노바나나 로고(금색 아웃라인판, 알파 트림 1068×575)·버튼 시트(goodbuttons.png 분리 크롭, 512px LANCZOS 리샘플) 교체 — 경로 유지로 코드 변경 없음.
  • 전체화면 모드(shared/ui/FullscreenButton, Fullscreen API): 라우트 이동 후에도 유지, ESC 해제 안내 4초 표시. 가드 버튼은 허브 헤더로 이동.

⑧ insight BC 설계 문서 (docs/상담리포트.md v2.2)

  • 상담 준비 리포트 문서 생성기 스펙 확정 — 학생의 두루뭉술한 진술을 전문가(노무사·변호사·전담기관) 양식으로 형식만 번역하고 해상도는 올리지 않는 원칙, 수치 팩트 시트 값/없음/모름 3상태 + 재권유 루프. 선행 산출물(법령 RAG 1,593청크·OCR 파이프라인·가드 rule_code 19종) 재사용 설계.
  • 다음 단계: 프론트 변경분(v0.20.52) 커밋(frontend만 스테이징), insight BC 구현 착수, DOC 미니게임, 구글 OAuth 실연동.

2026-08-27 — insight 풀스택 관통 + AI 신뢰성 평가의 날: 상담 준비 리포트 설계→구현→146케이스 평가→gemma 확정, 방어전 75발화 배터리 오CLEAR 0, VRAM 배분 결정 (BE v0.12.28→v0.15.11 / FE v0.21.0→v0.27.10)

하루에 백엔드 29개 버전·프론트 49개 버전. 어제 설계 문서(v2.2)를 확정한 insight BC(상담 준비 리포트)를 백엔드 구현 → 프론트 → 신뢰성 평가셋 146케이스 → 3모델 교차 평가 → gemma3:12b 확정 → 실백엔드 전환 → 30일 보관 정책까지 하루에 관통했다. 방어전은 상호 대응 대사 시스템을 얹고 75발화 자연어 배터리로 신뢰성을 검증했으며(오CLEAR 0), 가드는 위반 검출 시나리오 VIOL-01~05를 닫았다. 자원 측면에서는 임베딩을 gemini로 내보내 VRAM 16GB를 LLM 전용으로 배분하는 의사결정을 확정. superpowers 설계·플랜 6종(insight-backend, insight-eval-set, insight-report-frontend) 승인 → TDD 사이클 유지.

① insight BC 백엔드 — 상담 준비 리포트 (BE v0.13.0~v0.13.1)

  • /api/v1/insight/report/sessions 4+1 엔드포인트(start/answer/reask/preview/generate), 유형 불가지 엔진 4모듈(classifier·interviewer·extractor·reporter), 팩·세션 DB 4테이블 + WAGE_THEFT 15슬롯·12유형 taxonomy 시드(Alembic).
  • LLM은 INSIGHT_LLM_PROVIDER=fake|gemini|exaone 3-way Factory. 추출 안전장치: raw_quote 부분 문자열 검증 실패 시 UNKNOWN 강등·재시도 1회. 조문은 ontology RAG ACL 재사용(DB 원문, fail-open). generate 직후 진술 데이터 즉시 파기(purged_at 증빙). 테스트 30여 개(엔진·라우터·금지어·계산 부재 게이트).
  • v0.13.1 — LLM transport 장애 전용 예외(InsightLlmUnavailableError → 502) 분리, 재권유 문구를 코드 상수에서 팩 데이터로 이관(“엔진은 유형을 모른다” 원칙 정합).

② 상담 준비 리포트 프론트 (FE v0.21.0→v0.23.0→v0.27.0→v0.27.10)

  • v0.21.0 허브 헤더 상담리포트 버튼(“준비 중” notice) → v0.23.0 /report feature 슬라이스 신설: 자유서술 → 채팅 인터뷰(메신저형 말풍선, 모름/없음 구분·재권유 1회) → 팩트 시트 확인(본인 확인 체크 게이트, ✎ 슬롯별 수정) → 8섹션 리포트+학생용 별지+브라우저 인쇄. 백엔드 미구현 시점이라 인프로세스 페이크가 서버 역할 — API 계약은 스펙 §9 그대로 두어 전환 시 한 파일만 교체하는 설계.
  • v0.24.2 최종 검수 — 날짜 아닌 슬롯에 “…경” 접미가 붙던 오표기(“카페경”) 수정, 수집하지 않은 진술 조작 표기를 [미확인]으로 정정, “전문가 검수 진행 중” 배지 제거(실제 검수가 없는데 오해 소지).
  • v0.27.0 실백엔드 전환 — 페이크 삭제, answer 구조형 {kind: TEXT|DONT_KNOW|NONE}, 서버 15슬롯 전체 소화. 실백엔드 E2E 3시나리오(해피패스·전세 fallback·보이스피싱 긴급) 콘솔 에러 0 통과. v0.27.9 가드 진단 결과 → 상담리포트 연결 버튼.

③ insight 신뢰성 평가셋·하네스 146케이스 (BE v0.13.3~v0.14.2)

  • 라벨 발화 138건(분류 68 + 추출 70) + E2E 페르소나 8건. 분류는 WAGE_THEFT 변형(표준·오타·은어·간접) 20 + 11유형×3 + 복합 10 + 모호 SOS 5, 추출은 핵심 10슬롯 × EXACT/APPROX/HEARSAY/UNKNOWN 4등급 + TRAP 10건(값을 암시만 하는 발화 — 정답은 전부 NULL, 각색 감지 안전장치) + MULTI(한 문장 다슬롯) 10건. 페르소나는 STANDARD·CHATTY·TERSE·DONT_KNOW_HEAVY·ALL_NONE·EDIT_HEAVY·URGENT_EXIT·FALLBACK_RESTART 8종.
  • 러너 run_insight_eval.py: 분류 정확도·추출 값/precision 일치·날조 치명 게이트(발화·허용 소스에 없는 숫자·신규 한글 토큰이 나오면 fatal)·페르소나 완주를 마크다운 리포트로. 합격 기준: 치명 0 필수 + 분류·값 일치 ≥90% + precision ≥80%. fake 축소판은 pytest 상시 회귀로 편입.
  • v0.14.2 채점 공정화 — 날조(치명)/계약위반(비치명) 분리, 숫자 앵커 폴백(“5명” vs “5” 표기 차이 구제), 프로바이더 카나리아 프리플라이트 등 4건 수정 후 재실행: 스코어러 결함과 실제 모델 성능 한계를 분리해 확인.

④ 3모델 교차 평가 → gemma3:12b 확정 (BE v0.15.0·v0.15.1·v0.15.4)

  • exaone3.5:7.8b(로컬): 프롬프트 보강 3차례에도 추출 값 77.4→82.1→83.3%·precision ~70%에서 정체 — 7.8B 성능 한계로 판정.
  • gemini-2.5-flash-lite(온라인): 분류 100%·값 89.3%·precision 90.5%·날조 0 (JSON Schema union 호환 수정 후 첫 유효 실행).
  • gemma3:12b(로컬, 어댑터는 모델명 오버라이드 14줄): 값 92.9%·precision 91.7%·분류 98.5%·날조 0 — 오프라인 모델 최초로 전 통계 기준 충족. 분류 프롬프트에 규칙 2줄(긴급 피해 우선·직장 내 관계 한정) 추가로 분류 100%(68/68)·urgent 누락 0 달성 → 상담리포트 서빙 모델 gemma 확정.

⑤ 모델 서빙 전략 — VRAM 16GB를 어디에 쓸 것인가: 임베딩 gemini 전환 (BE v0.15.4·v0.15.6·v0.15.7)

오늘의 가장 중요한 자원 배분 의사결정. GPU에 올라가고 싶은 모델이 셋(임베딩 Qwen3-4B ~8GB·판정 exaone 7.8B·리포트 gemma3:12b ~8.7GB)인데 VRAM은 16GB 한 장 — 웜업 순서가 어긋나면 torch.OutOfMemoryError가 재현되고, 임베딩이 10.7GB를 선점하면 gemma가 부분 오프로딩으로 ~3초까지 늘어지는 것을 실측으로 확인한 상태였다.

  • 판단 기준: 실측 성능 차이가 큰 곳에 희소 자원을 준다. LLM 쪽은 차이가 컸고(exaone 정체 vs gemma 전 기준 통과 — ④), 임베딩 쪽은 차이가 없었다(MTEB 기준 Qwen3-4B와 gemini-embedding-001 동급 + 스키마가 애초에 1536차원 통일 설계). 게다가 본선 배포가 Vercel 프론트 + Cloudflare 터널 백엔드로 확정되며 인터넷이 시연의 전제조건이 됨 — “폐쇄망 대비 로컬 임베딩” 논리 자체가 소멸했다.
  • 결정: EMBEDDING_PROVIDER=gemini, VRAM은 LLM 전용 (v0.15.6). 스위치 한 줄 전환 후 스모크: “임금 체불” 질의 0.7초에 정확한 조문 반환. 효과는 즉시 — gemma 오프로딩 없이 완전 상주(8.0/8.0GB), 나아가 exaone(5.2GB)+gemma(8.0GB) 동시 상주 실측 성공(총 14.0/16.3GB, 상호 축출 없음, v0.15.7). 웜업 런북을 4단계(RAG 프리플라이트→exaone→gemma→공존 검증)로 확정하며 “리포트 전환점 모델 스왑 흡수” 설계 자체가 불필요해졌다 — 시연 전 구간 웜 상태. 프리워밍 엔드포인트(/report/warmup + 프론트 intro 마운트 fire-and-forget, v0.15.4)로 첫 제출 대기 ~15s→~3s.
  • 되돌릴 수 있는 결정: law_chunks 1,593건에 gemini/local 벡터가 컬럼으로 공존(설계 시점의 의도적 역정규화) — 복귀는 재임베딩 없이 provider 스위치 1분. 로컬 임베딩을 “안 해본” 게 아니라 어댑터·전체 임베딩·OOM 방어까지 전부 해봤기에 “내보내도 된다”를 확신할 수 있었다. 부족한 자원을 인지하고, 측정으로 우선순위를 세워, 차이가 큰 곳에 배분하는 것 — 이번 결정의 가장 큰 수확이며, 온라인/로컬 양쪽을 준비해 선택 운영한다는 서사는 코드(포트 뒤 어댑터 + .env 스위치)로 그대로 증명된다.

⑥ insight 30일 보관 정책·시연 QA (BE v0.15.8~v0.15.9 / FE v0.27.10)

  • 30일 보관 정책(스펙 §9-1 개정): 리포트 “즉시 파기” → 생성 +30일 보관, 본인 연장 30일×최대 2회(총 90일), 이후 무조건 파기(접근 시 lazy purge 410 + start 시 전역 sweep — 스케줄러 불요). 리셋(DELETE /play-records)에 insight 파기 배선(Composite 패턴, 실측 세션 6·슬롯 63 → 0·0). FE는 보관 안내 배너 + 연장 버튼(마지막 연장 confirm 노티, 소진 시 삭제 예정 문구).
  • 시연 QA 2건: 재권유 문구를 {label} 템플릿으로 전환(12슬롯 재권유 12종 전부 상이 — 동일 문구 반복 체감 해소), 최저임금 미달 룰 플래그 — 진술 시급 < 10,320원이면 쟁점 확인 요청에 표기. 한글 수사 파서(“만원”/”9천” 정규화 편차 흡수), 문구는 학생 진술값 그대로 인용(임의숫자 게이트 정합). 테스트 66건 통과.

⑦ 방어전 상호 대응 대사 + 자연어 신뢰성 배터리 (BE v0.15.10~v0.15.11)

  • Phase 1 상호 대응 대사: 판정 포트·프롬프트는 무변경(평가 100% 실적 보존), 대사 선택만 결정론 룰로 — 클리어 논거별 변형(교부/벌금·시점/수단·신고) + 실패 유형별 전용 리액션(협박/질문/화제이탈/철지난 논거) + 표정 매트릭스. NPC_SPEECH_PROVIDER=seed→gemini 전환 — 태그로 고른 시드 대사를 학생 발화 맥락으로 말투만 변형(실패 시 시드 폴백), 턴 1.6~3.5s. 회귀 테스트 12건은 실플레이 발화를 픽스처로.
  • 75발화 자연어 신뢰성 배터리(Fable 5 직접 플레이 평가): 반말·오타·감정·항복·조건부 거부·엣지 10종(욕설·이모지·무의미 문자열·조문 복붙 등)으로 1→3선 관통 3회 — 위험 오판정(오CLEAR) 0, 대사 정합 50/55(91%). 발견 반영: GIVE_UP(항복 낚아채기)·TELL(어른에게 이르기) 태그 신설, 조건부 거부 가드(“안 써주면 일 안 할래요”는 협상이지 항복이 아님), 항복 시에도 반말 유지(캐릭터 붕괴 방지 톤 가드).
  • NONSENSE 태그(2층 시맨틱 분류 완성): 맥락 토큰이 전무한 잡담에 “뭔 소리야~ + 화제 복귀” 전용 대사. 맥락없는 발화 20종 배터리(날씨·게임·MBTI·로또 등): 판정 20/20 전부 ONGOING(오CLEAR 0), 대사 정합 18/20. quest 테스트 62건 통과.

⑧ Guard 위반 검출 확장·UX 개편 (BE v0.12.28~v0.15.5 / FE v0.25.5~v0.27.8)

  • 위반 검증 시나리오 VIOL-01~05 완주: 최저임금 미달(MIN_WAGE_BELOW)·필수 기재사항 누락, 연소근로자 주40시간 초과·야간근로·야간수당 거부(ART69/ART70/ART56), 위약금(손해배상액, OCR 오인식 위익금까지 매칭)·상품권 지급·연소자 증명서 미비, 휴게 미부여·포괄임금 가산거부·임금 공제 — OCR 플래그·룰·시드/마이그레이션 4건. CLEAN 계약서 ART66 오탐(서식 상시 라벨을 미비로 오인) 수정.
  • UX 개편 — 촬영 직행: 문서 종류 선택 화면 제거, 업로드 doc_type=AUTO(OCR 제목 판별 + 본문 기반 자동 교정 — 계약서를 급여명세서로 올려 hourly_wage=19가 되던 오인식 해소). OCR이 종류·필드를 읽으면 업로드 한 요청에서 진단 리포트까지 반환(FE 2차 PATCH 생략), 고신뢰 시 확인 화면 생략·자동 진단.
  • FE 연쇄 수정: React Strict Mode 이중 OCR로 Paddle 워커가 죽던 “서버는 성공·화면은 500” 문제(1회 실행 + 55초 타임아웃 Route Handler 프록시), 발견 항목 한글 라벨 전면 정비(최저임금 미달·필수 기재사항 누락 등).

⑨ 게임 월드·UI 폴리시 + 잠금 우회 버그 풀스택 수정 (FE v0.21.1~v0.27.7 / BE v0.15.3)

  • 잠긴 퀘스트 우회 버그: 하이드레이션 전 노드 진입 보완 경로(FE v0.27.7) × 세션 생성 시 해금 순서 미검증(BE v0.15.3, QUEST_LOCKED 409 게이트 신설) 두 층을 함께 막아 “포기 후 노드2 열림” 증상 근본 해결.
  • Modern UI Style 2 스킨 전면 적용(v0.25.0~v0.26.2): 튜토리얼 모달 → 시나리오 대화창 → 전투 화면 전체를 크림 라운드 9-slice 패널로 통일 — 프레임 스타일을 shared/ui/uiSkin.ts로 승격(feature 간 직접 import 금지 규칙), 면 밝기 변형 3종(기본 크림/순백 입력창/어두운 크림 버튼).
  • 조작 튜토리얼(v0.24.0~v0.24.3): 플레이 기록 없으면 허브 진입 시 키캡 UI 안내, ‘이동’ → ‘포탈·대화’ 2단계 진행. 허브 헤더 스탯 수치 3종(v0.22.0, localStorage 스냅샷) + 랜딩 로그인 상태 연동(v0.26.0~v0.26.1, 미로그인 PLAY 차단 notice·로그아웃 버튼).
  • 월드 디테일: 차량 2대 추가·속도 통일(150px/s)·간격 불규칙화, 나사장 클리어 후 후일담 대화(전투 재진입 대신 2줄 안부), 대화 키 고착 무한 루프 수정(resetKeys), 편의점 남벽 반 칸 통행, 화면 하단 남색 띠 제거(최소 줌 보정), 공원 밀도 보강 13종(아기천사 석상 쌍·꽃 6종·새집 2종·꽃수레 — 코드 변경 없이 콘텐츠 배치만).

⑩ 다음 단계

  • 골든패스 리허설 사이클, 구어 추출 보강(실인터뷰 발견 개선), DOC 미니게임, 구글 OAuth 실연동.

2026-08-28 — 판정 신뢰성 총력전·챗봇 UX 전환의 날: 평가셋 156케이스 전 게이트 통과 + Guard 35변형 회귀 35/35 + 도배·날조 방어 + 가드·리포트 챗봇 패널 전환 + 주휴수당 규칙·계산기 개편 (BE v0.15.12→v0.16.15 / FE v0.27.11→v0.30.21)

자정 직후 전일 작업분 3커밋(guard v0.15.5 / insight·quest v0.15.6~v0.15.12 / frontend v0.27.6~v0.27.10)을 main에 릴리스하며 시작해, 하루에 백엔드 19개·프론트 33개 버전. 오전은 평가셋 실전화·Guard 회귀 하네스·문서 정합(ERD v2.0), 낮부터는 방어전 판정의 오CLEAR 사냥과 insight 도배(같은 말 반복 입력)·날조 방어, 가드·상담리포트 진입을 챗봇 위젯·패널로 바꾸는 UX 대전환(수호천사 캐릭터 배제 확정), 저녁에는 주휴수당 미니게임의 획득 규칙 개편과 계산기 UI 리디자인(프론트 폴리시는 신채연 담당 구간 포함)으로 이어졌다.

① 구어 추출 보강 + 평가셋 실전화 146→156케이스 (BE v0.15.12)

  • 실인터뷰 점검에서 발견된 구어 추출 실패의 개선 사이클 완주: 추출 프롬프트에 구어 규칙 4종(한글 수사도 값·추정/자문은 APPROX·다중 항목 쉼표 병기·압축 금지) + “재직 중=NONE” 시맨틱 + few-shot 3종(평가셋과 겹치지 않는 신규 예시 — 과적합 방지).
  • COLLOQ 그룹 8케이스 — 실패 발화 원문 그대로 회귀 고정(추출 70→78). REPLAY 페르소나 2건(Historical Replay) — 실세션(사용자 PDF 세션·자연어 세션)을 E2E 재생, 산출물 단언(최저임금 쟁점·목표 압축 방지·증거 병기·재직중 NONE) + 재권유 문구 중복 게이트. 슬롯 키 답변(answers_by_slot)으로 선충전 변동에 강건.
  • gemma 156케이스 재평가 전 게이트 통과: 분류 100%(68/68)·urgent 누락 0 / 값 90.2%·precision 89.1% / 치명 0 / 페르소나 10/10(리플레이 2 포함) / COLLOQ 8/8.
  • 보너스 수확: 평가 러너의 _MemSessions가 30일 보관(v0.15.8) 포트 확장 누락(purge_expired)을 검출 — 평가 하네스가 리팩터링 회귀 안전망으로 동작한 실례.

② Guard OCR·룰셋 35-variant 회귀 하네스 (BE v0.15.13)

  • 위반 계약서 5종 + 깨끗한 계약서 2종 × 촬영 변형 5종(정상 스캔·저해상도 JPEG·기울임·접힘·저조도) = 35케이스 전수 기대 finding 고정guard_sample_expectations.py + 픽스처 + run_guard_sample_eval.py(마크다운 리포트) + @pytest.mark.paddle 회귀 테스트.
  • 실측 35/35 통과. 접힘·저조도 엣지에서 발견된 OCR 붙어읽기(휴게시간:없음, 그전액을 익월)는 정규식 보강으로 흡수 — 시연 당일 촬영 품질이 흔들려도 진단이 흔들리지 않는다는 근거 확보.

③ 문서 정합 — 코드가 앞서간 만큼 문서를 당겨오기

  • ERD v2.0 전면 개정: v1.0(8/3 설계 초안)을 구현 스키마 기준(ORM 원본, 23테이블·5 BC)으로 재작성. v1.0의 violation_type 온톨로지 허브 구상 대비 실제 구현의 차이를 정직하게 기록 — insight BC는 consult 5테이블 설계를 팩/세션 2층 4테이블로 재설계(질문·쟁점·팁·라우팅을 insight_pack_item 데이터 행으로 — 콘텐츠 추가 시 스키마 불변), 역정규화 2건(law_chunks 임베딩 2쌍·guard_finding.rule_version)에 근거 명시, 미구현 테이블군은 P2 이월로 표기. 지킬 ERD 페이지 동일 반영.
  • metrics_status.md 소급 반영: 8/27 실측이던 방어전 자연어 75발화 배터리(오CLEAR 0)·맥락없는 발화 20종(20/20 ONGOING)을 판정 시계열에, Call 2 대사 모델 선정(gemini 채택·gemma 기각 근거 +2s 실측)과 대사 정합 91%/90% 검증을 발화 파이프라인에, OCR 엔진 선정 근거 4항(개인정보·VRAM 배분·VLM 배제·provider 스위치)을 Guard 섹션에 편입 — “기각한 후보도 실측 근거가 있다”로 모델 선택 서사 완결.

④ 방어전 판정 정합성 스프린트 — 오CLEAR 사냥 (BE v0.15.14→v0.16.3·v0.16.9~v0.16.15 / FE 판정 동기 6건)

어제 75발화 배터리가 남긴 숙제를 하루 종일 갚은 구간. 판정이 흔들리는 지점을 하나씩 재현→차단하며 백엔드 판정 버전만 12개를 쌓았다.

  • 판정 견고화(v0.15.14): 문장 끝 마침표 유무로 CLEAR/ONGOING이 갈리던 흔들림 정규화, 동일 문장 전단계 통과 차단 — LLM CLEAR는 현재 방어선 키워드 hit일 때만 유효(1단 논거를 2·3단에서 재탕하면 PREV_ARG 리액션), 2단 「시작 전」 오클리어 게이트, CLEAR·HINT 판정 시 시드 항복 대사를 Gemini가 「나중에 쓰자」 톤으로 되돌리던 문제 수정.
  • 대사 정합 3건(v0.16.1~v0.16.3): 2단 오프닝 「쓸게, 써」 작성 동의 뉘앙스 제거(미루기 톤 시드 교체), 「고용노동부에 신고할거예요」가 애매 발화로 오매칭돼 「어디에 신고할 건데?」가 나오던 문제(기관명 동반 시 예외 + LABOR_THREAT 항복 리액션), LLM이 HINT를 내려도 키워드 CLEAR hit이면 반박 성공 연출 우선.
  • 3단 신고 판정의 하루 여정(v0.16.4→v0.16.9→v0.16.10): 기관+신고만 있는 발화를 「아쉬운 승리」(INCOMPLETE_CLEAR — 클리어는 되지만 보상 축소)로 신설했다가, 비활성(클리어 불가)으로 뒤집고, 최종적으로 FULL CLEAR 확정 — 고용노동부에 신고하겠다는 선언 자체가 교육 목표상 유효한 반박이라는 판단. 프론트 MSW 판정기도 3회 함께 동기(v0.30.7→v0.30.14→v0.30.16).
  • 혼합 발화 Veto(거부권 — 정답 키워드에 걸려도 틀린 사실이 섞이면 통과를 막는 장치): 「맞는 논거 + 근로자가 벌금 낸다는 오개념」이 키워드 CLEAR로 통과하던 구멍을 wrong_fact_veto.py로 차단(v0.16.13, eval 39건 회귀 통과), 사장님 귀속 벌금(「벌금 내셔야」)까지 오차단하던 false positive 제외(v0.16.15). MSW wrongFactVeto.ts 동기(v0.30.17·v0.30.21).
  • EXAONE 판정 신뢰성 마감: SLM이 약한 반박·항복에 HINT를 남발하던 문제를 키워드 교차 강등으로 해소(v0.16.12) — EXAONE eval 54건 100%·오CLEAR 0. 함정 19건(오CLEAR 유도·방어선 미스매치·항복·조문 복붙) + NPC 대사 문맥 10건의 방어전 정합성 평가셋을 상시 회귀로 편입(v0.16.11).

⑤ insight 도배 소프트 사다리 + 추출 날조 차단 (BE v0.16.0·v0.16.14 / FE v0.30.0)

  • 도배 대응 사다리(v0.16.0): 「몰라」×N 같은 무의미 반복 답변을 LLM 없이 휴리스틱으로 판정(spam_guard.py — 반복 토큰 비율·단일 문자 도배·직전 답변 동일 반복), 1회차 재질문 → 2회차 해당 슬롯 ‘모름’ 확정 후 전진 → 3회차 60초 입력 쿨다운(429). 도배 세션의 LLM 소모 ~15회 → 0회. 도배 원문이 인쇄물 한 줄 요약에 그대로 실리던 어제의 유일 잔여 이슈도 고정 문구 폴백으로 마감. 경계 테스트 12케이스(짧은 정상 답변 「몰라」·「없어요」 미검출 포함). FE는 쿨다운 안내 메시지 표시(v0.30.0).
  • 추출 날조 차단(v0.16.14): 발화·허용 소스에 없는 숫자·한글 토큰을 UNKNOWN으로 강등하는 value_fabrication 검증을 추출기에 상시 장착 — EXAONE 치명 1→0. TRAP few-shot 보강(헤지·추정 표현은 null) 후 gemma 재평가: 분류 100%·값 96.7%·precision 95.7%·치명 0·함정 전수 통과 — 어제 92.9%에서 재상승.

⑥ 가드·상담리포트 챗봇 UX 대전환 — 수호천사 배치 → 당일 철회 (FE v0.28.0→v0.29.0)

  • 오전 v0.28.0에서 수호천사 컴포넌트(AngelHost.tsx — 부유 스프라이트·대기 연출·말풍선 타자 효과)를 가드·리포트 9화면에 배치했으나, 저녁 방향 결정으로 가드·상담리포트에서 캐릭터 요소 전면 제거(v0.29.0 — 헤더 스프라이트·채팅 아바타·화자 라벨 삭제, 로딩은 스피너 복귀) — 신뢰 문서를 다루는 두 기능은 흰색 유틸리티 톤 + Pretendard로 확정하고 캐릭터는 게임(전투 펫)에만 남김. 하루 안에 만들고 걷어냈지만 결정 근거가 남았다.
  • 진입 방식 전환: 헤더 버튼·별도 페이지 이동 대신 허브맵 좌하단 챗봇 위젯(파란 필 버튼 2개, v0.28.2) → 클릭 시 좌하단 챗봇 패널(440×680, 블루 그라데이션 헤더, Esc/✕ 닫기, v0.29.0)로 기존 가드 4화면·리포트 5화면을 그대로 이식. 패널이 열린 동안은 캐릭터 이동 차단, 랜딩에서도 로그인 상태면 동일 위젯 노출.
  • 리포트 인터뷰를 실제 메신저 대화창으로: 자유서술 첫 화면부터 채팅 UI(질문 말풍선 + 라운드 입력바 + Enter 전송), 인터뷰는 좌우 말풍선·퀵리플라이 칩(잘 모르겠어요/그런 거 없어요), 안내·에러·버튼 문구 전면 해요체 통일(전투 NPC 반말은 게임 연출로 유지).
  • Vercel 빌드 차단 해소(v0.28.0): GuardConfirmScreen 재분류 응답 union 내로잉 + null 가드로 타입에러 7건 전부 수정, 정크 0바이트 파일 2개 삭제 — npx tsc --noEmit 0건·next build 통과.
  • 폴리시 3건: 계산 던전 키패드에 사칙연산·= 버튼(v0.27.11) 후 키보드 입력 전면 비활성(v0.28.1 — 화면 키패드로만), 게임 UI 텍스트 드래그 선택 방지(v0.27.12), 나사장 재도전 시 인사 생략 바로 전투(v0.27.13).

⑦ 랜딩·인증 개편 — 로그인 모달 + 랜딩 = 진짜 허브 (FE v0.28.2·v0.29.0·v0.29.3)

  • 로그인·회원가입 모달: 별도 /login·/signup 페이지를 폐지하고 랜딩 위 크림 패널 모달로(라우트는 /?login=1 리다이렉트만 유지). 구글 버튼의 4색 G 로고는 외부 CDN 금지 원칙에 따라 인라인 SVG 자체 렌더. 어트랙트 모드(입력 없이 배경으로 도는 데모 화면)의 Phaser가 WASD·Space를 전역 캡처해 모달에서 a/s/d 타이핑이 안 되던 문제는 clearCaptures()로 해제.
  • 로그인 랜딩 = 실제 허브 화면: 랜딩 뒤가 목업이 아니라 진짜 허브(주인공·실제 노드 상태·HUD 포함)가 되고, PLAY는 라우팅·암전 없이 오버레이만 400ms로 걷히며 같은 게임 인스턴스에서 곧바로 플레이. 전투에서 브라우저 뒤로가기 시 맵 위에 랜딩 오버레이가 다시 뜨던 버그는 sessionStorage(브라우저 탭 단위 임시 저장소) 플래그로, 로그아웃 랜딩에 이전 계정 진행도가 비치던 문제는 어트랙트 모드 캐시 미반영으로 각각 수정. game.destroy() 경로에서 죽은 씬의 EventBus 리스너가 일으키던 크래시도 가드.
  • 실백엔드 연동 보강(v0.29.3): POST /quest-sessions 404를 catch-all 프록시(모든 API 경로를 백엔드 8100으로 넘기는 중계 라우트)로 해소, 로컬 .env.local은 실백엔드 모드(FALLBACK_MODE=0)로 — 목 시연은 npm run dev:mock(3101) 분리.

⑧ 주휴수당 CALC — 획득 규칙 개편 + 계산기 리디자인 + 살아있는 배경 (BE v0.16.5~v0.16.8 / FE v0.28.2~v0.30.21)

  • 획득 규칙의 하루 여정(BE): 오답 허용 진행(5문항 전부 제출 시 클리어, 차액 차감 — v0.16.6) → 정답 문항 전액/오답 0원 확정(차액 차감 폐지, v0.16.7) → 허브 「지켜낸 돈」이 카탈로그 고정값(330,240원)이 아니라 실제 정답 문항 합산액을 표시(v0.16.8). FE도 오답 후 「다음 문제」 진행·획득 규칙 안내 동기(v0.30.9·v0.30.10).
  • 플레이 기록 제거 범위 선택: DELETE /play-records?scope=all|calc(v0.16.5) — 미니게임만 초기화하고 퀘스트·가드·리포트는 유지. FE는 모달에서 「모든 기록」/「주휴수당 계산만」 선택(v0.30.8).
  • 계산기 리디자인: 키패드 겹침을 4행 그리드로 해소(v0.30.1) → 화면비 무관 67:33 비율 고정(v0.30.2~v0.30.4) → 00·05 입력이 거부돼 정답 제출이 막히던 문제(strict-mode 숫자 리터럴 거부 → Number 정규화, v0.30.6) → 3열 숫자 전용 실험 후 아이폰식 4열(÷×−+=) 복원(v0.30.19) → 문제 카드를 전투·대화창과 동일한 크림 9-slice 패널로 통일(v0.30.20).
  • 살아있는 배경: 정지 이미지 대신 어트랙트 허브맵을 배경으로 깔고 카메라를 벤치의 친구에게 줌 고정 — 맵 속 실제 친구가 주인공이 되고, 스프라이트 시트에서 재크롭한 물방울 말풍선 6프레임(맺힘→낙하)을 머리 위에 루프 재생(v0.28.2). 시각 확인을 거치며 줌 배율·앵커를 수차례 미세 조정(v0.30.18 등).
  • 버그 2건: 미니게임 클리어가 허브 포탈을 파랗게 바꾸던 표시 분리(v0.30.11), 토큰 갱신 시 세션이 재시작돼 Q5 클리어 후 Q1로 돌아가던 부트스트랩 가드(v0.30.12). 스테이지 2·3 포탈은 「준비 중」 안내로 통일(v0.30.13·v0.30.15).

2026-08-29 — 리허설의 날: 자동 완주 3라운드 9회 + 폴백 훈련 2종 실발동, 결함 6건 발견·수정 (BE v0.16.16→v0.16.18 / FE v0.30.22→v0.30.25)

전일 작업분 2커밋(BE v0.15.13~v0.16.15 / FE v0.27.11~v0.30.22)을 main에 올리며 시작. 오늘은 새 기능을 만드는 날이 아니라 만든 것이 실제로 도는지 확인하는 날이었다. 브라우저 자동화로 전체 동선(로그인→방어전→주휴수당 계산→가드→상담리포트)을 3라운드 9회 주파하고, 장애 대응 절차를 처음으로 실제 발동했다. 오늘 나온 코드 변경은 전부 리허설이 찾아낸 결함이다.

① 전일 릴리스 + 테스트 회귀 4건 수정 (BE v0.16.16)

  • 커밋 정리 중 백엔드 테스트 4건 실패를 발견. 둘 다 “테스트가 프로덕션 변경을 못 따라간” 유형이었다.
  • insight 3건: v0.16.14 날조 차단(value_fabrication)으로 발화에 없는 값은 UNKNOWN으로 강등되는데, 테스트 대역(FakeInsightLlm)이 여전히 발화에 없는 연도(2026년)를 붙여 반환하고 있었다. 대역을 실제 정책에 맞춰 수정.
  • guard 1건: 합성 이미지에 한글을 그리는데 PIL 기본 폰트에 한글 글리프가 없어 라벨이 아예 렌더링되지 않았다. 시스템 CJK 폰트를 쓰도록 수정(없으면 skip).

② 시드를 고쳐도 DB에 닿지 않던 구조적 결함 (BE v0.16.17)

  • 2단계 NPC 오프닝이 v0.16.1에서 미루기 톤으로 교체됐는데 실제 DB에는 구버전이 그대로 있었다. 작성에 동의해놓고 곧바로 미루는 모순 대사가 시연에 노출될 뻔했다.
  • 원인: ensure_seeded가 퀘스트 존재 시 early return하고 키워드만 동기화해, 대사 변경은 기존 DB에 영원히 반영되지 않는 구조였다. L1·L3은 우연히 일치했고 L2만 어긋나 있어 더 늦게 드러났다.
  • 동기화를 _sync_defense_content로 확장해 npc_opening·npc_clear·ongoing_lines·hint1/2·evidence까지 시드 기준으로 갱신. 시드가 단일 진실 원천이므로 이제 대사 수정이 재기동만으로 반영된다. DB 없이 세션 스텁으로 검증하는 테스트 3건 추가.

③ 상담리포트 문구 해요체 통일 (BE v0.16.18)

  • 프론트 v0.29.0에서 UI 문구를 해요체로 통일했으나 인터뷰 질문·증거 안내·긴급 안내는 초기 시드 마이그레이션에 데이터로 박혀 있어 변환되지 않았다. 같은 채팅창에서 질문은 반말, 시스템 안내는 해요체로 섞여 보였다.
  • alembic c2d3e4f5a6b7로 슬롯 질문 15종·증거 팁 근거 7종·REASK 문구·evidence_notice(12팩)·urgent_notice(4종)를 변환. 긴급·증거 안내는 프론트 폴백에 이미 있던 해요체 원문과 문구까지 일치시켜 서버 값이 뜨든 폴백이 뜨든 동일하게 했다. downgrade 왕복 검증 완료.

④ 프론트 결함 3건 (FE v0.30.23~v0.30.25)

  • 미니게임 클리어 후 허브 스탯이 기본값으로 회귀(v0.30.23): 스탯 스냅샷이 skipHydration persist인데 미니게임 화면은 rehydrate를 부르지 않아, 클리어 시 메모리의 null이 저장본을 덮어썼다. 마운트 시 rehydrate 추가.
  • 「정답 제출」 버튼 하단 잘림(v0.30.24): 키패드가 aspect-square라 계산기 높이가 폭으로만 정해지고 가용 높이를 무시했다. 뷰포트 높이 745px 이하에서 래퍼 밖으로 밀려 잘렸다(700px에서 45px 초과). 키패드가 남은 높이를 채우도록 바꾸고 높이 상한을 둬 600~1060px 전 구간 잘림 0.
  • 계정 간 스탯 스냅샷 유출(v0.30.25): 스냅샷이 로그아웃 후에도 남아 다른 계정으로 로그인하면 첫 배틀 전까지 이전 사용자 수치가 보였다. 요구가 둘이었다 — 점수는 계속 쌓이니 유지돼야 하고, 다른 계정엔 보이면 안 된다. 로그아웃 시 일괄 삭제하면 후자만 만족하고 전자를 잃으므로, 스냅샷에 소유자 계정을 함께 저장하고 계정 불일치면 무시하는 방식으로 둘 다 만족시켰다. 소유자 없는 구버전 스냅샷은 자동 무시.

⑤ 리허설 3라운드 — 9회 주파

  • 1차(3회): 위 결함 4건 발견. 2차(3회): 수정분 유지 확인 + 신규 1건(계정 간 스냅샷). 3차(3회): 무결점 · 콘솔 에러 0 · 신규 결함 0.
  • 3차에서 처음 검증한 경로 — 가드 정상 문서 「이상 없음」 판정, 가드→리포트 연계 이동, 로그아웃→같은 계정 재로그인 시 점수 즉시 복원, 재플레이 연습 클리어(점수 미기록), 도배 사다리 3단·쿨다운 입력 차단.
  • 자동화 교훈 하나: 클릭 좌표를 스크린샷이 아니라 getBoundingClientRect로 잡아야 한다. 창 크기에 따라 스크린샷이 뷰포트의 축소본이 되는데, 이를 보정하지 않아 1차 라운드에서 “버튼이 안 눌린다”는 오판을 여러 건 만들었다.

⑥ 폴백 훈련 — 절차에 빠진 단계를 찾다

장애 대응 표를 만들어만 두고 한 번도 발동해본 적이 없어 두 시나리오를 실제로 돌렸다.

  • 판정 LLM 사망: OLLAMA_BASE_URL을 죽은 포트로 돌려 연결 실패를 재현. 키워드 판정 폴백으로 CLEAR·ONGOING 모두 정상, 조문 카드도 정상. 응답 0.55~1.2초로 오히려 평소보다 빨랐다(LLM 호출이 없어서).
  • 오프라인 전환(EMBEDDING_PROVIDER=local·NPC_SPEECH_PROVIDER=seed): 전환 자체는 28초에 끝났지만 결과 상태가 시연 불가였다. 로컬 임베딩이 백엔드 프로세스에서 GPU 8~10GB를 점유해 판정·리포트 모델이 오프로딩되고, 판정이 1턴 22초·이후 10초가 됐다. 리포트 모델을 내려 VRAM을 방어전에 몰아주자 3.8초로 회복 — 임베딩이 네트워크가 아닌 로컬이라 평소(5~8초)보다도 빠르다.
  • 결론: 세 모델을 한 장에 다 올릴 수 없다는 원래 설계 결론(그래서 임베딩을 외부로 뺐다)이 오프라인 폴백에서 그대로 되돌아온다. 오프라인에서는 방어전·가드까지 가능하고 리포트는 포기해야 한다. 가드는 OCR·룰 13종·조문 매핑이 전부 로컬이라 네트워크도 LLM도 쓰지 않아 영향이 없다.

⑦ 판정의 한국어 표현 한계 규명 (현행 유지 결정)

  • 사람이 직접 플레이하다 2단계에서 막혔다. 의미상 맞는 문장인데 ONGOING이 나온다.
  • 원인: 판정은 키워드 → 게이트 → LLM 3층인데 결정권이 앞 두 층에 있다. 2단 게이트가 “시기 언급 + 1단계 어휘 없음”을 요구하는데 그 어휘 목록에 범용 동사 써야가 들어 있어, 「…써야 해요」로 끝나는 문장이 전부 막혔다. exaone이 CLEAR를 내려도 키워드가 아니면 버려지는 구조라 LLM이 구제하지도 못한다.
  • 개선안(게이트 규칙 완화 + 키워드를 개념 집합으로 통합, 시드·게이트 이중 정의 해소)을 설계했으나, 기존 정합성 평가셋의 함정 케이스가 현행 규칙을 못박고 있어 테스트 수정이 아니라 판정 정책 변경에 해당한다. 시연 전 회귀 위험을 피해 현행 유지·시연 후 재검토로 결정했다.

⑧ 다음 단계

  • 발표 리허설(사람이 말하면서 10분 완주) — 기능 완주·폴백 훈련 게이트는 통과했고 이것만 남았다.
  • 판정의 한국어 표현 인정 범위 확대(⑦ 개선안) — 시연 후.
  • 랜딩 오버레이와 조작 튜토리얼이 겹쳐 보이는 문제 — 블로커가 아니어서 보류.

2026-08-30 — 시연 배포 구성 확정 + 프론트 폴리싱 대량 투입: 계산 던전 배경 캔버스→이미지(요청 112→19건), 화면 전환 페이드 5종, 입력 차단 구조 결함 해결 (BE v0.16.19 / FE v0.30.26→v0.30.65 · 41개 버전)

어제가 “만든 것이 도는지” 확인하는 날이었다면, 오늘은 현장에 어떻게 들고 갈 것인가를 정하고 리허설이 남긴 어긋남을 한꺼번에 처리한 날이다. 배포 구성을 팀 논의로 확정해 문서로 남겼고, 프론트는 하루에 40개 버전을 냈다.

① 시연 배포 구성 — B안 확정

  • 전제는 이미 정해져 있었다. 현장에는 맥북만 가져가고, GPU 서버는 집에 홈서버로 두고 Cloudflare Tunnel(집 안 서버를 공개 주소로 내보내는 터널 서비스 — 공유기 포트 개방·인증서 발급이 필요 없다)로 노출한다. 갈리는 건 브라우저가 말을 거는 공개 주소를 몇 개로 둘 것인가였다.
  • A안은 주소 하나(화면·API 모두 홈서버), B안은 화면을 Vercel(웹 화면 파일을 대신 배포·서빙해주는 클라우드 서비스)에 두고 API만 백엔드로 직결(api.life-tutorial.com)하는 구성이다.
  • 프론트 초안은 A를 권했다. CORS(다른 주소끼리 브라우저가 통신을 허용하는 규칙)·쿠키 설정이 어려워서가 아니라, 해본 사람이 없으면 시연 직전에야 실패가 드러나서였다. 백엔드 담당이 같은 구성을 개인 프로젝트로 이미 운영 중이라 그 전제가 성립하지 않았고 — 미지수가 아니라 기지수다 — B로 정정했다.
  • 초안이 아예 빠뜨린 근거도 있었다. 모든 API를 Vercel 프록시로 통과시키면 함수 호출 수와 대역폭을 계속 먹고(30초짜리 판정 요청이 함수를 붙들고 있는 것도 포함) 업로드가 4.5MB에서 막힌다. 백엔드 직결이면 Vercel은 정적 파일만 내보내는 역할이 되어 그 한도를 아예 만나지 않는다. 우회책이 아니라 이 구성의 정석이었다.
  • B가 A 대비 얻는 것: 홈서버가 죽어도 화면은 뜬다(A는 흰 화면 — “서버가 잠깐 끊겼습니다”라고 말하며 넘어갈 수 있는 것과 체감이 다르다) · 첫 로드가 빠르다 · 프론트 배포가 홈서버와 분리된다.
  • 프론트 코드를 조사해보니 수정할 게 없었다. 모든 호출이 공용 클라이언트를 거쳐 기준 주소와 결합되므로, 환경변수 하나(NEXT_PUBLIC_API_BASE_URL)를 빌드 시 넣으면 문서 업로드까지 전부 직결된다. 기준 주소를 우회하는 직접 호출은 개발 전용 한 곳뿐이었다.
  • 함정 하나. public/assets/는 유료 라이선스 때문에 git에서 빼뒀는데(123개 중 4개만 들어 있다), 저장소를 Vercel에 그대로 연결하면 맵도 로고도 없는 사이트가 배포된다. CLI로 로컬 파일을 직접 올리는 쪽을 택했다 — “push하면 자동 배포”는 포기.
  • 남는 위험은 난이도가 아니라 검증 시점이다. 쿠키 설정이 어긋나면 “로그인이 조용히 안 되는” 형태로 나타나고, 실제 도메인 + 외부망에서만 드러난다. 그래서 본선 할 일 목록을 1일차=네트워크 경유 실동작 검증 / 2일차=배포 설정·리허설로 다시 짜고, 맥북을 집 와이파이에서 떼고 확인한다(집 안에서만 되는 건 아무 증거가 안 된다)는 절차까지 적었다.
  • 긴 요청이 지나는 길도 그렸다. Cloudflare는 100초를 넘기면 연결을 끊는다. 문서 점검은 전용 경로(55초 상한)를 타지만 상담 준비 리포트(~30초)는 전용 경로가 없어 일반 전달 규칙으로 지나간다 — 터널 뒤에서도 끝까지 오는지 반드시 한 번 통과시켜 확인할 항목으로 남겼다. 백엔드가 인터넷에 직접 노출되므로 그에 대한 대응 항목도 함께 정리했다(백엔드 담당).

② 계산 던전 배경 — Phaser 캔버스에서 정지 이미지 한 장으로 (v0.30.27)

  • 이 화면의 배경은 카메라도 플레이어도 멈춰 있는 정지 화면인데, 그 한 장을 위해 Phaser 번들과 허브 씬 전체(타일·건물·소품·백드롭·차량·길찾기)를 부팅하고 있었다. 리허설에서 말풍선·계산기가 먼저 뜨고 배경이 뒤늦게 채워지던 원인이다.
  • 허브 씬을 친구 포커스 상태로 띄워 배경을 굽는 개발 전용 스크립트를 만들고, 캡처 앵커를 뷰포트의 같은 지점에 1:1 배율로 얹어 씬이 그리던 구도를 그대로 재현했다.
  • 측정(dev, 1920×1080): 배경 리소스 완료 576→131ms, 요청 112→19건, JS 2246→772KB, 이미지 88장(162KB)→1장(31KB).
  • 검증은 Phaser 렌더와 픽셀 비교로 했다 — 2560×1440 완전 일치(0px), 나머지 해상도는 Phaser 자체의 카메라 반올림 차이로 1px 이내. 실제로 움직이는 랜딩·허브맵의 Phaser는 교체 대상이 아니라 그대로 뒀다.
  • 곁가지로 더 큰 사실을 확인했다. 시연은 프로덕션 빌드로 돌려야 한다. 같은 랜딩 실측 — dev는 맵 등장 951~1121ms · JS 22개 9.5~10.0MB, 프로덕션 빌드는 607~617ms · 12개 1.43MB. JS 약 7배 감소, 맵 등장 40~45% 단축. 현장 와이파이를 타므로 이 차이가 그대로 체감된다.

③ 화면 전환 연출 — 이음새 없애기 (v0.30.31~v0.30.43)

  • 페이드가 붙어 있지 않던 경로를 전부 채웠다: 로그아웃(허브→랜딩), 로그인 성공(모달→허브), 계산 던전 ‘시작’, 친구 대화→계산 던전. 전투 진입과 같은 수치(암전 250ms → 페이드인 450ms)로 통일했다.
  • 로그아웃은 덮는 색을 검정이 아니라 도착지 배경색으로 뒀다 — 검정으로 떨어뜨리면 랜딩 진입 순간 네이비로 한 번 튄다. 실측 평균 RGB가 랜딩 배경과 정확히 일치(단차 0).
  • 가장 까다로웠던 건 걷힘 연출이 통째로 사라지던 결함이다. PLAY 오버레이의 전환 종료 핸들러가 이벤트 출처를 가리지 않아, 자식인 PLAY 버튼의 눌림 효과(100ms) 종료 이벤트가 버블링돼 오버레이가 161ms 만에 제거됐다. 1300ms짜리 페이드가 잘려나가고 있었으니, 시간 값을 아무리 늘려도 효과가 없던 게 당연했다. 이벤트 출처와 속성 이름을 함께 확인하도록 고치고, 탭이 백그라운드로 가서 종료 이벤트가 늦거나 오지 않을 때를 위한 보험 타이머도 넣었다 — 오버레이가 걸린 채 남으면 PLAY가 먹통이 된다.
  • 시간 기반 페이드를 씬 준비 신호(map:ready) 기반으로 바꿨다. 로그인 성공 뒤 진입 검정이 모달 뒤에서 다 돌아버려, 모달이 닫힐 땐 200ms짜리 잔상만 남았다(실측 밝기 75.7 → 8.8 → 47.4 → 62.8). 씬 준비에 맞추자 보이는 페이드 구간이 490ms로 늘고 암전도 깊어졌다. 신호가 오지 않을 때를 위한 3초 상한도 뒀다 — 검정이 영원히 남는 게 시연 중 최악의 실패다.
  • 로고가 배경보다 먼저 뜨던 현상도 같은 뿌리였다. 오버레이가 시간대로 걷히는 동안 Phaser 씬은 아직 부팅 중이라 배경이 단색이었다(실측 t=885ms에 로고 102.2 / 맵 무늬 0.0, 맵 완성은 t=1339ms — 약 450ms 선행). 등장을 씬 준비에 맞추니 같은 프레임에 함께 올라온다. 어제 넣었던 “배경 먼저” 연출은 실측 결과 근거가 없어 원복했다.
  • 오버레이 걷힘 시간은 눈으로 보며 400 → 1300 → 900 → 700ms로 수렴. 픽셀 버튼 눌림 효과와 로그인·회원가입 모달 등장 애니메이션도 함께 넣었다.

④ 라우팅·세션 — 주소마다 화면 하나 (v0.30.28·29·42)

  • 허브맵에서 로그아웃하면 랜딩 위에 로그인 모달이 떠 있었다. 로그아웃 목적지가 /login이었고 그게 다시 ?login=1로 리다이렉트되고 있었다. 인증 게이트의 미인증 리다이렉트도 같이 고쳐야 했다 — 로그아웃 순간엔 아직 보호 경로라 그 분기가 함께 돌아, 버튼만 고쳐서는 모달이 다시 붙는다.
  • 로그인 상태로 /에 들어가면 랜딩이 아니라 허브맵이 뜨던 문제. 탭 세션 플래그로 오버레이 상태를 되살리고 있었다. /=랜딩, /hub=허브맵으로 못박되, 뒤로가기로 돌아왔을 때는 플레이가 유지돼야 한다는 요구가 남는다. 판정 기준을 세션 저장소가 아니라 메모리 플래그로 바꿨다 — 라우트 이동·뒤로가기(같은 문서)에는 살아남고 주소 입력·새로고침(문서 재생성)에는 초기화된다. 상충하는 두 요구가 플래그 하나로 갈렸다.
  • 조작 튜토리얼이 엉뚱한 때 뜨던 것 4건: ⓐ 랜딩에서 PLAY 전에 이미 반투명 오버레이 너머로 비쳐 보임 → ⓑ 그 상태에서 스페이스를 누르면 안 보이는 채로 소진돼 정작 플레이 시작 때 안 뜸 → ⓒ 라우트를 옮겼다 돌아오면 또 뜸 → ⓓ 계정을 바꾸면 이전 계정 기록으로 오판. 표시 조건이 데이터만 보고(“기록 없음”) 판정하고 지금 이 화면에서 실제로 조작할 수 있는지를 보지 않은 게 공통 원인이었다. ⓓ는 어제 스탯 스냅샷에 쓴 것과 같은 장치(소유 계정 표식)로 해결.

⑤ 챗봇 패널에 띄어쓰기가 안 되던 문제 — boolean 하나로는 겹침을 못 센다 (v0.30.64~65)

  • 상담 준비·문서 점검 패널에서 스페이스와 w/a/s/d가 입력되지 않았다. Phaser가 이동키를 전역으로 붙들고 있고(게임 조작이니 당연하다), 이를 풀어주는 신호가 랜딩 쪽에서 누락돼 있었다 — 패널 조립을 공용 컴포넌트로 옮길 때 허브 쪽 코드만 남았다.
  • 신호 발신을 페이지가 아니라 패널 컴포넌트가 소유하도록 옮겨 1차 해결. 그런데 패널을 연 채 PLAY를 누르면 다시 막혔다. 신호가 boolean 하나인데 발신자가 셋(대화·튜토리얼 / 랜딩 오버레이 / 챗봇 패널)이라 나중에 쏜 쪽이 이겼다. 패널이 열려 있어도 랜딩 오버레이의 정리 코드가 “풀어라”를 쏴버린다.
  • 진짜 원인은 오버레이는 겹쳐 열리는데 boolean이 겹침을 표현하지 못하는 것이었다. 열린 오버레이 개수를 세는 참조 카운팅(열릴 때 +1, 닫힐 때 −1 — 0이 될 때만 실제로 푼다) 훅을 만들어 발신 지점을 하나로 모으고, 발신 3곳을 전부 그 훅으로 교체했다(중복 코드 40여 줄 제거).
  • 검증(실제 크롬, 4경로): 랜딩→PLAY→패널 / 패널→PLAY(문제였던 순서) / /hub 직접 진입 / 닫았다 재오픈 — 전부 "연차 수당 문의 합니다"가 그대로 보존됐다. 키 붙듦 상태도 직접 계측해 닫힘→열림→닫힘에 따라 게임 조작이 정확히 되돌아오는 것을 확인(차단이 걸린 채 남지 않는다).
  • 원인 확정은 집계를 빼고 옛 동작으로 되돌리는 대조 실험으로 했다. 그때만 문제 경로 하나가 "연차수당문의합니다"로 실패하고 나머지 셋은 통과 — 겹침이 원인임이 확정된다.

⑥ 배포 안전장치·정리 (v0.30.61~62)

  • 에셋 누락 검사 스크립트. public/assets/가 git에 없으니 clone만으로는 따라오지 않고, 누락되면 화면이 뜬 뒤에야(맵이 텅 빈 채로) 알게 된다. 코드가 참조하는 정적 경로 127개를 모아 실제 파일 존재를 확인하고, 없으면 경로·상수명·참조 파일을 찍고 실패로 끝난다. 컨테이너 빌드 단계에 넣으면 에셋 없는 이미지가 만들어지는 것 자체를 막을 수 있다.
    • 플레이스홀더 4건은 경고로 통과시켰다 — 멀쩡한 트리에서 오류를 뱉으면 아무도 그 검사를 안 믿게 된다.
  • 버튼 커서가 화살표였다. Tailwind v4가 기본 스타일에서 버튼 커서를 빼면서, 버튼 55개 중 48개가 손가락이 아니었다(눈에 띈 7개만 손으로 붙여온 상태). 전역 규칙 한 줄로 해결. 비활성 버튼은 화살표를 유지해 못 누르는 상태를 커서로도 알린다.
  • 쓰지 않기로 한 NPC 표정 초상화 3종을 제거하며 그것 때문에 죽은 코드가 된 파일 2개를 함께 삭제(에셋 참조 127→124개).

⑦ 문구·용어 정리 (v0.30.44~60)

  • 가드계약서 점검문서 점검. 근로계약서와 급여명세서를 모두 처리하는 기능이라 최종적으로 문서가 실제 동작과 맞다. 표시 문구만 바꾸고 라우트·스토어·컴포넌트명·분석 이벤트명은 그대로 뒀다 — 백엔드 계약과 이벤트 스키마가 걸려 있다.
  • 진단 리포트의 ‘미확인 항목’에 wage weekly_holiday 같은 영어 필드 코드가 그대로 노출되던 문제. 다른 화면에만 있던 한글 이름표를 공용 모듈로 올려 두 화면이 같은 이름을 쓰게 했고, 코드 17종을 백엔드 규칙 시드와 대조해 빠짐없이 덮는 것을 확인했다. 표에 없는 코드는 코드 그대로 노출한다 — 이름을 지어내지 않는다.
  • 발견 항목이 0건인데 헤더에 아무 뜻 없는 정보 배지가 붙어 있던 것을 없앴다. 문서 점검 결과에서 리포트 작성 버튼을 누르면 패널 안인데도 전체 화면으로 튀던 것도 같은 패널 안 전환으로 고쳤다.
  • 어투를 해요체로 계속 통일하면서, 앞서 보고한 “15개”가 전수가 아니었다는 것도 함께 기록해뒀다 — 큰따옴표 문자열만 훑어 화면 텍스트와 조립형 문자열을 놓쳤고, 같은 상황·같은 문장인 서버 오류 2개가 어투가 갈린 채 남아 있다(제출하지 못했어요. vs 제출하지 못했습니다. (500)).
  • 그 밖에 주휴수당 화면 문구 정리(계산 던전 입장시작, 배지 제거 후 제목 중앙 정렬), 계산기 기능키를 연산자와 같은 톤으로, 잠긴 노드 안내에서 하루 1퀘스트! 제거.

⑧ 정합성·코드 위생

  • 3스테이지 포탈이 미니게임 클리어만으로 금색 해금되던 문제(BE v0.16.19 / FE v0.30.26) — 아직 구현하지 않은 메커니즘은 선행 퀘스트 클리어와 무관하게 잠긴 상태를 유지하도록 백엔드·프론트 양쪽에서 막았다. 허브 노드의 「준비 중」 표시와 화면이 일치한다.
  • 렌더 도중 값을 저장하던 코드를 커밋 이후로 옮기는 정리(ESLint(코드 규칙 검사 도구) 에러 8건 → 3건). 렌더 중 쓰기는 React가 버리는 렌더의 값도 남겨, 화면에 반영되지 않은 상태가 게임 씬에 주입될 수 있는 구조였다. 노드 색 주입 경로는 값을 인자로 넘기도록 바꿔 저장 순서 자체에 기대지 않게 했고, 타이밍 회귀가 없음을 수정 전후 비교(371/628ms → 367/639ms, 측정 편차 범위)로 확인했다.
  • 하단 인도 소품을 공중전화 부스로 바꿔봤다가 원래 쓰레기통으로 되돌렸다(맵은 최종적으로 변화 없음). 대신 팩 소품 크기 기준을 남겼다 — 플레이어가 1타일(48×48)인데 부스는 팩 원본이 사람 키의 4배였다. 인물과 크기가 비교되는 소품은 축소 전제, 지면에 붙는 낮은 소품은 원본 크기가 맞는다.

⑨ 다음 단계

  • 본선 1일차는 네트워크 경유 실동작이다 — 터널 연결, CORS·쿠키, 외부망에서 로그인 유지, 긴 요청 통과, 집 회선 업로드 속도, 홈서버 원격 복구.
  • 오늘 고친 것은 대부분 스텁 하네스(실제 서버 없이 가짜 응답으로 화면만 돌려보는 시험 장치)로만 검증했다. 실계정으로 라우팅·페이드 5종·튜토리얼·허브 포탈 색을 눈으로 확인하는 항목을 따로 묶어뒀다.
  • 남은 것: 발표 리허설(사람이 말하면서 10분 완주), 프론트 컨테이너·프로덕션 빌드 설정, 문서 점검에 남은 합쇼체 10개.

2026-08-31 — 프로덕션 배포 실행일: 실제 도메인에 화면·API 올림 + 걷기 애니메이션 8방향 + 판정 동시 슬롯 실측 후 철회 (BE v0.16.20→v0.16.28 / FE v0.30.66→v0.33.10 · 50개 버전)

어제 정한 배포 구성(B안)을 오늘 실제로 실행했다. 어제 일지에 “남는 위험은 난이도가 아니라 검증 시점”이라고 적어둔 항목 — 쿠키·CORS·OAuth 리디렉트 — 이 실제 도메인에서 한 번에 드러나는 날이었다. 그 사이 프론트는 하루에 50개 버전을 냈다.

① 프로덕션 배포 실행 — 어제의 B안이 실제 주소로 (BE v0.16.23~25 / FE v0.32.21)

  • 화면은 Vercel(www.life-tutorial.com), API는 홈서버 직결(api.life-tutorial.com). 어제 조사한 대로 프론트 코드는 한 줄도 고치지 않았고, 빌드 시 환경변수 두 개만 넣었다 — 기준 주소(NEXT_PUBLIC_API_BASE_URL)와 폴백 모드 해제(NEXT_PUBLIC_FALLBACK_MODE=0, 가짜 응답 대신 진짜 백엔드를 보게 하는 스위치).
  • 백엔드에 실제 도메인을 받아들이는 설정을 넣었다. CORS(다른 주소끼리 브라우저가 통신을 허용하는 규칙) 허용 목록에 https://www.life-tutorial.com 추가, 구글 로그인 리디렉트 URI(로그인 후 사용자를 되돌려 보낼 주소 — 구글 쪽에 미리 등록한 값과 정확히 같아야 한다)를 프로덕션 주소로 교체.
  • 쿠키에 Secure 속성(HTTPS로만 오가게 하는 표시)을 켰다(AUTH_COOKIE_SECURE=1). 로컬은 http라 이걸 켜면 오히려 로그인이 안 되므로, 로컬/프로덕션 설정을 .env.example에서 갈라 적고 홈서버용 템플릿(.env.production)을 따로 뒀다.
  • 백엔드가 인터넷에 직접 노출되니 어제 남긴 대응 항목도 같이 처리했다. ⓐ 판정 API(POST /npc/judge)에 로그인 확인을 붙였다 — 없으면 아무나 우리 GPU로 추론을 돌릴 수 있다. ⓑ API 명세 자동 문서(Swagger·ReDoc·명세 JSON) 세 경로를 껐다 — 공개 주소에 엔드포인트 목록을 그대로 걸어둘 이유가 없다.
  • 실제 도메인에서 로그인 왕복을 통과시켜 세션 유지까지 확인했다. 어제 “실제 도메인 + 외부망에서만 드러난다”고 적어둔 것이 이 확인으로 닫혔다.

② 판정 동시 슬롯 — 측정은 6이 좋다고 했지만 1로 되돌렸다 (BE v0.16.26~28)

하루 안에 4 → 6 → 1로 세 번 움직인 값이다. 되돌린 과정이 결론보다 중요해서 그대로 남긴다.

  • 문제의식: 방어전 판정을 여러 건 동시에 돌릴 때 몇 개까지 같이 처리할 수 있는가(OLLAMA_NUM_PARALLEL — 로컬 LLM 서버가 동시에 붙드는 처리 슬롯 수). 재는 스크립트(bench_judge_parallel.sh)를 만들어 5060 Ti 16GB에서 4·6·8을 측정했다.
  • 실측: 4슬롯 = VRAM 6.6GB·동시 4건 1.4초(효율 3.99배), 6슬롯 = 7.6GB·1.99초, 8슬롯 = 8.6GB·1.95초. 8은 6보다 메모리만 1GB 더 먹고 빨라지지 않았다. GPU를 혼자 쓰게 하고 다시 재니 6슬롯이 1.62초·효율 5.71배로 더 좋게 나왔다.
  • 그래서 6으로 올렸다가 같은 날 1로 되돌렸다. 시연 구성에서는 방어전 모델(EXAONE)과 리포트 모델(gemma)이 한 GPU에 같이 올라가 있어야 한다. 슬롯을 늘리면 슬롯마다 대화 기억용 메모리(KV 캐시)를 미리 잡아서, 3 이상이면 두 모델 공존이 깨진다. 시연에서 판정이 동시에 몰릴 일은 없고 모델이 언로드되는 건 치명적이라, “빠른 쪽”이 아니라 “안 깨지는 쪽”을 골랐다.
  • 실측값 자체는 버리지 않고 스크립트와 함께 남겼다 — 나중에 GPU가 판정 전용이 되면 그때는 6이 맞는 값이다.

③ 방어전 이탈·재진입 — 나갔다 오면 2방어선부터 시작되던 문제 (BE v0.16.21~22 / FE v0.32.19~20)

  • 1방어선만 깨고 허브로 나갔다가 다시 들어오면 2방어선부터 시작됐다. 이어하기용 체크포인트가 남아 있어서인데, 방어전은 세 방어선을 이어서 막아내는 것이 게임이라 중간부터 재개하면 난이도가 사라진다.
  • 서버에서 진행 중(ONGOING) 세션을 재개할 때 항상 1방어선으로 초기화하도록 고쳤다. 여기에 더해 이탈 시점에 진행 중 세션을 지우는 경로(DELETE /quest-sessions)를 추가했다 — 클리어 기록은 남기고 진행 중인 것만 지운다.
  • 탭을 그냥 닫는 경우까지 잡으려고 페이지가 사라질 때 발신하는 요청에 keepalive(페이지가 닫혀도 브라우저가 끝까지 보내주는 옵션)를 붙였다.
  • 곁가지로, 진행 중에 로그인 토큰이 갱신되면 전투 세션이 다시 시작되던 것도 함께 고쳤다(세션 시작 조건에서 토큰 의존 제거).

④ 계정을 바꾸면 허브 스탯이 기본값으로 보이던 문제 — 세 번 만에 잡았다 (BE v0.16.20 / FE v0.32.6~8)

  • 증상: B 계정으로 로그인하면 멘탈·법률지식이 클리어 보상이 반영되지 않은 기본값(50/10)으로 떴다. 보상은 서버 DB에 있는데 허브 화면은 브라우저에 저장된 스냅샷만 보고 있었다.
  • 1차 — 퀘스트 목록 응답에 스탯을 실어 보내고 화면이 그 값을 쓰게 했다. 2차 — 저장된 스냅샷 복원과 서버 조회가 동시에 돌면서 스냅샷이 서버 값을 덮어쓰던 경합을 순서대로(복원 → 소유 계정 확인 → 서버 조회) 풀었다. 로그아웃·계정 전환 때 이전 계정 흔적을 지우는 정리도 같이 넣었다.
  • 3차에서 진짜 함정이 나왔다. 고쳐도 여전히 50/10이었는데, 코드가 아니라 실행 중인 서버가 옛 코드였다. 스탯을 실어 보내는 변경 이전에 뜬 프로세스가 그대로 돌고 있었다. 다시 띄우니 서버가 멘탈 51·법률 13을 정상 반환했다. “고쳤는데 안 된다”의 절반은 이런 것이라 기록해둔다.
  • 밤에 나온 마지막 한 건(v0.33.10): 첫 화면에서 로그인 버튼이 잠깐 떴다가 로그아웃으로 바뀌어 오클릭을 유발했다. 세션 복원이 끝나기 전에 “토큰이 없다 = 로그아웃 상태”로 단정한 게 원인이라, 복원이 끝날 때까지 두 버튼 다 감추도록 했다.

⑤ 플레이어 걷기 애니메이션 8방향 (FE v0.33.0~3)

  • 지금까지 주인공은 정지 그림 한 장이 미끄러지듯 움직였다. 방향마다 4프레임짜리 걷기 그림을 생성해(초당 13프레임) 8방향 전부 붙였다. 좌우 반전(flipX)으로 서쪽을 대신하던 것도 그만두고 서쪽 정지 그림을 따로 뒀다 — 걷기가 8방향 전부 별도 그림이라 정지 그림만 반전이면 방향이 갈린다.
  • 첫 결과물은 걸으면 캐릭터가 작아졌다. 생성 도구가 골격을 새로 잡으면서 머리를 약 12% 작게 그렸다(전체 높이 −4%). 정지 그림을 시작 프레임으로 넣어 다시 생성하니 머리 비율 1.00, 발끝 선도 정지 그림과 같은 높이(y=47)로 맞았다. PNG만 교체돼 코드 변경은 없었다.
  • 두 번째 결함은 프레임마다 입 모양이 흔들리는 것이었다. 정지 그림의 입 영역을 프레임별 머리 위치(±1px)만큼 맞춰 덮어썼다 — 정면 19px, 측면 23px만 교체하고 다른 픽셀은 건드리지 않았다.
  • 걷는 그림에 이미 상하 움직임이 들어 있어, 예전에 코드로 넣어둔 상하 바운스는 겹치면 출렁여서 제거했다.

⑥ 나무 앞뒤 순서(Y-sort) — 여섯 번 고쳐서 자리 잡았다 (FE v0.32.9~14 · v0.33.2)

  • 탑다운 게임은 아래에 있는 것이 앞에 그려진다(Y-sort). 그런데 나무는 밑동은 가려야 하고 잎사귀는 캐릭터를 덮어야 해서 이 규칙 하나로는 안 된다.
  • 시도한 순서: 화분이 캐릭터를 덮음 → 스프라이트를 위아래 두 장으로 자름 → 두 장 사이에 틈이 보임 → 자르기 폐기 → 단일 그림 + 플레이어 위치에 따라 그리기 순서를 바꾸는 방식(북쪽에 있으면 잎에 가리고, 남쪽·옆이면 화분 앞) → 옆·북쪽 칸까지 나무 뒤로 분류되던 것을 격자 행 + 가로 0.75타일 겹침일 때만으로 좁힘.
  • 마지막 한 건은 공원의 분홍 꽃이 나무 앞으로 튀어나오던 것이었다. 나무가 옆·북쪽 캐릭터를 가리지 않으려고 평소 한 행 위 순서로 서는데, 그 값이 바로 북쪽 행 소품보다 낮았다. 배치할 때 한 번, 앞 행 나무와 가로로 겹치는 소품을 그 나무 아래로 내리는 처리를 넣었다(대상 4개). 나무·플레이어 규칙은 건드리지 않았다.

⑦ 튜토리얼·모달 연출 — 등장 시점을 세 번 뒤집었다 (FE v0.31.1~5 · v0.32.15~17)

  • 조작 튜토리얼이 PLAY 오버레이가 다 걷힌 뒤에야 툭 나타났다. ⓐ 걷힘 ‘시작’에 맞춰 앞당김(등장 960ms → 213ms, 공백 70ms → 0) → ⓑ 즉시 불투명도 1로 나타나 반투명 오버레이 너머로 밝은 패널이 비침 → ⓒ 오버레이 걷힘과 같은 700ms로 맞물리게 함 → ⓓ 최종적으로 “걷힘 → 맵을 한 박자 보여줌(300ms) → 다시 어두워지며 떠오름” 으로 확정. 눈으로 보며 되돌린 결과다.
  • 그 과정에서 키캡 그림이 모달보다 한 박자 늦게 뜨는 것도 잡았다. 그 이미지는 튜토리얼에서만 쓰여 모달이 뜨는 바로 그 프레임에 요청이 시작됐다. 허브 진입 시 미리 받아두게(preload) 하니 로드 시점이 105ms로, 모달이 뜨는 시각보다 3.9초 앞선다. 단계가 넘어갈 때 키캡을 새로 만들지 않도록 전 단계 키를 한 칸에 겹쳐 그려두고 보이기만 전환했다.
  • 퇴장 연출도 채웠다. 튜토리얼 마지막 단계에서 바로 사라지지 않고 등장과 같은 호흡으로 어둠·패널이 통째로 걷힌다(이후 걷힘 속도만 450ms로 조정). 챗봇 위젯 패널(문서 점검·상담 준비)에도 등장·퇴장 애니메이션을 넣어 ✕·Esc·버튼 토글이 전부 같은 연출을 타게 했다.
  • 반복해서 부딪힌 기술적 사실 하나: 퇴장을 “등장의 역재생”으로 만들면 안 된다. 애니메이션 이름이 그대로면 브라우저가 재생을 다시 시작하지 않아 끝난 상태에서 값만 튄다. 그래서 등장·퇴장 키프레임을 항상 별도 이름으로 짝지어 만들었다(오늘 추가된 것만 4쌍).

⑧ 펫 — 허브에 복원했다가 다시 걷어냈다 (FE v0.31.0 · v0.31.6~14 · v0.32.0~1)

  • 아침에 예전에 철거했던 허브맵 동행 펫(주인공을 따라다니는 수호천사)을 되살렸다. 격자를 따르지 않고 픽셀 좌표로 흘러오는 지연 추적(매 프레임 남은 거리의 일부만 당겨오는 방식이라 프레임률이 달라도 따라오는 속도가 같다)과 사인 곡선 부유(진폭 5px·주기 1.6초)를 넣고, 일정 거리 안에서는 멈추게 했다.
  • 저녁에 그 로직을 통째로 제거했다. 철거 사유였던 방향 그림 품질이 해소되지 않았기 때문이다 — 허브 전용 32×32판은 이미 폐기됐고 public/assets/가 git에 없어 복구가 안 된다. 정면 한 방향뿐인 그림을 축소해 쓰는 상태로는 허브에 두는 편이 오히려 어색했다. 전투·결과 화면의 펫은 그대로다.
  • 전투 펫 그림 자체는 크게 손봤다. 63×60 흉상에서 타이틀 로고의 ‘날개 달린 책’을 강아지로 옮긴 128×128 전신으로 교체하고(로고와 같은 팔레트), 인페인트(그림의 지정한 영역만 다시 그리게 하는 방식)로 등에 있던 책을 지우고 어깨에 갈색 반점을 넣었다. 인페인트가 매번 지정 영역 밖 71px(주변 반짝임)을 건드려서, 영역 안쪽만 골라 합치는 방식으로 처리했다.
  • 배치 결함도 하나 잡았다. 대사가 길어지면 펫이 따라 올라갔다. 펫이 스탯 패널의 자식이라 위치 기준이 패널 상단이었고, 대사창과 스탯 패널이 같은 행이라 대사가 길어지면 행이 높아지며 펫이 딸려 올라간 것이다. 화면 전체 높이로 고정된 컨테이너의 직속 자식으로 옮겼다 — 대사창을 178px → 420px로 강제로 늘려 패널 상단이 242px 올라가도 펫은 0px 움직이지 않는다.
  • 전투 진입 시 “화이팅!” 응원 말풍선을 넣었는데, 진입 외 타이밍에 다시 뜨는 버그가 붙어 있었다. 응원이 떠 있는 2.4초 안에 힌트가 뜨면 말풍선이 사라지면서 종료 이벤트가 오지 않아 “아직 안 썼다”는 표시가 남고, 나중에 힌트가 사라지는 순간 처음부터 재생됐다. 힌트가 뜨면 응원을 소진 처리하는 한 줄로 닫았다 — 애니메이션 완주와 힌트 등장 중 무엇이 먼저 와도 한 번만 꺼진다.

⑨ 시연용 빈 전송 스크립트 (FE v0.33.4~6 · v0.33.8)

  • 시연에서 한글을 길게 타이핑하는 구간이 병목이라, 입력란이 비어 있을 때 전송하면 준비된 예시 문장으로 제출되도록 했다. 입력란 안내 문구에 다음에 나갈 문장을 미리 보여준다.
  • 상담 준비 리포트는 인트로부터 인터뷰 슬롯까지 이어지는 스크립트를 콘텐츠 파일로 분리해 한 사이클(확인 화면까지)을 빈 전송만으로 완주할 수 있다. 방어전은 방어선 1~3별 예시 반박을 따로 뒀다.
  • 처음 만든 판(v0.33.4)은 빈 전송이 입력란을 채우기만 해서 한 번 더 눌러야 했다. 시연 중 두 번 누르는 건 그 자체로 어색해서 한 번에 채우고 바로 제출로 고쳤다(v0.33.5).
  • 이건 판정을 우회하는 장치가 아니다 — 채워진 문장은 평소와 똑같이 실제 판정 API를 탄다. 타이핑만 건너뛴다.

⑩ 그 밖에

  • 근거 조문 카드를 한 장씩 넘겨 보도록 바꿨다(격자로 한꺼번에 펼치던 것). 카드 위에 n / N 표시를 넣었다가 다음 버전에서 도로 뺐다 — 넘김 동작과 마지막 장 버튼 라벨 전환(“다음” → “다음 방어선으로”/”결과 보기”)은 그대로다.
  • 버스 창유리가 초록으로 물들어 있던 것(파란 버스를 색조 변환해 만든 에셋이라 유리 명암까지 초록이 됐다). 창문 12칸 안의 초록 2색만 빨간 해치백의 유리 색으로 바꿔, 원본의 대각선 광택 패턴은 그대로 살렸다. 차체·행선판은 건드리지 않았다.
  • 로그인 직후 랜딩에서 상단 헤더가 반투명 오버레이 너머로 비쳐 보이던 것 — 헤더를 흐름에서 빼 맵 위에 얹었다. 조건부 렌더만 하면 PLAY 순간 캔버스가 헤더 높이만큼 줄면서 맵이 덜컹인다.
  • 나사장 피격 흔들림 1.5배, 허브 헤더 지표 간격 확대(16px → 40px), 화분 나무 한 칸 이동.

⑪ 기술 내역 문서 정합 (tech_life.md)

  • 계획서에 적어둔 기술 항목 39개를 코드·ERD·버전 로그와 한 줄씩 대조해 구현 현황을 갱신했다(기준: BE v0.16.26 / FE v0.33.3). 결과는 구현 21 · 부분 10 · 미착수 8이고, 각 행에 실제 파일 경로를 병기해 검증 가능하게 뒀다.
  • 미착수 8건 중 7건이 그로스·바이럴 계층(딥링크 초대, 공유 카드, 시즌 사전예약 등)에 몰려 있다. 판정 코어(방어전·룰셋·2단계 파기)는 전부 구현됐다. 의도한 결과다 — 본선은 “정확한 판정”을 증명하는 자리이지 성장 지표를 보이는 자리가 아니다.
  • 계획서에 없었으나 실제로 만들어진 것(인증 BC, 상담 준비 리포트 BC, 게임 클라이언트, 배포·운영)은 따로 절을 나눠 적었다. 이 넷이 지금 코드베이스의 절반 가까이를 차지한다.

⑫ 다음 단계

  • 배포는 올라갔고 로그인 왕복까지 확인했다. 남은 건 긴 요청이다 — 상담 준비 리포트(~30초)가 터널을 통과해 끝까지 오는지, 문서 업로드가 집 회선으로 감당되는지.
  • 맥북을 집 와이파이에서 떼고 하는 외부망 확인은 그대로 남아 있다. 집 안에서 되는 건 증거가 못 된다.
  • 그 밖: 발표 리허설(사람이 말하면서 10분 완주), 응답 스트리밍·클리어 사운드(P1 — 오디오 에셋 0건), 문서 점검에 남은 합쇼체 10개.

2026-09-01 — 느림·죽음 두 건을 실측으로 잡은 날: 방어전 턴 28초→0.4초, 폰 사진 한 장에 백엔드 OOM (BE v0.16.29 / FE v0.33.11)

대회 이틀 전. 새 기능을 붙이는 날이 아니라 시연 중에 터지면 끝나는 두 가지를 잡는 날이었다. 하나는 대화가 멈추는 것, 하나는 백엔드가 죽는 것이다. 둘 다 “그럴 것 같다”가 아니라 실제로 재현시켜 숫자를 재고 고쳤다.

① 방어전 한 턴이 28초 걸리던 문제 — 원인이 우리 호출량이 아니었다 (BE v0.16.29)

  • 증상: 방어전에서 문장을 하나 보내면 26~28초 답이 없다. 처음엔 GPU 메모리 배분 문제로 의심했지만 아니었다.
  • 추적 경로: 방어전 턴 → RAG(답을 만들기 전에 관련 법령을 먼저 찾아 근거로 붙이는 방식) 근거 검색 → 임베딩(문장을 숫자 벡터로 바꿔 뜻이 비슷한 조문을 찾게 해주는 변환) 호출 → Gemini가 429(요청이 너무 많다는 거절 응답) 반환 → 어댑터에 박혀 있던 고정 25초 대기 후 재시도. 지연의 정체는 추론이 아니라 우리가 스스로 넣은 대기였다.
  • 여기서 전제부터 다시 쟀다. “무료 등급의 분당 할당량을 우리가 다 썼다”고 가정하고 있었는데, 실측이 반대로 나왔다. 분당 20회 페이스로 부르면 첫 호출부터 429가 났고, 분당 164회 페이스로 30번 연속 부르면 전량 성공했다. 우리 호출량과 무관하다는 뜻이다.
  • 원인은 계정 할당량이 아니라 gemini-embedding 모델 전체가 공유하는 전역 쿼터(global_embed_content_requests_per_minute_per_base_model)였다. 남들이 쓰면 우리가 막히고, 막힘이 풀리는 시점도 우리가 못 정한다. 분 단위로 기다린다는 설계 자체가 근거를 잃었다 — 우리 잘못이 아니니 우리가 조용해질 이유도 없다.
  • 조치: 고정 25초 대기를 지수 백오프(재시도 간격을 0.5초에서 시작해 실패할 때마다 두 배씩 늘리되 4초를 상한으로, 총 30초까지만 버티는 방식)로 교체했다. 결과는 턴 지연 28초 → 중앙값 0.4초(실서비스 API로 25회 연속 실측). 실제로 14초간 429가 계속된 구간도 있었지만 재시도가 그 안에서 통과해 사용자에게는 멈춤으로 보이지 않았다.
  • 회귀 테스트 4종을 함께 남겼다(순간 429가 1초 안에 회복 / 간격이 두 배씩 늘고 상한을 넘지 않음 / 예산 안에서 포기 / 429가 아닌 오류는 즉시 올려보냄).

② 조용히 실패하던 근거 검색에 경고 로그를 붙였다 (BE v0.16.29)

  • 근거 검색 어댑터가 except Exception: return [] 였다. 예외가 나면 빈 근거 목록으로 조용히 넘어가고, 화면은 준비된 시드 문구로 채워진다. 시연 중에는 티가 안 나지만, 끝난 뒤에 “그때 진짜 법령 원문이 나갔나, 대체 문구가 나갔나”를 확인할 방법이 없었다.
  • 실패 시 경고 로그와 예외 내용을 남기게 했다. 동작(폴백해서 계속 진행)은 그대로다 — 무너지지 않되 무슨 일이 있었는지는 남긴다. 테스트 1종 추가.

③ 상담 준비 리포트 PDF 인쇄가 잘리던 문제 (FE v0.33.11)

  • 리포트는 챗봇 위젯 패널(폭 440px·스크롤) 안에서 뜬다. 그 상태로 인쇄하면 브라우저가 화면에 보이는 좁은 칸 그대로 종이에 옮겨서 본문이 잘렸다.
  • 인쇄 전용 규칙(@media print — 화면일 때와 인쇄할 때 다른 스타일을 적용하는 CSS 기능)으로 패널 틀과 뒤편 허브맵을 감추고, 리포트 본문만 A4 전폭으로 펴지게 했다. 섹션이 페이지 경계에서 반토막 나지 않도록 끊김 위치도 정리했다.

④ 폰 사진 한 장이 백엔드를 죽이던 문제 — OCR 입력에 상한을 걸었다

  • 밤에 백엔드(uvicorn)가 OOM killer(메모리가 바닥나면 운영체제가 가장 많이 쓰는 프로세스를 강제로 죽이는 장치)에 두 번 연속 죽었다. 죽기 직전 메모리 25GB. 재현 조건이 명확했다 — 문서 점검 업로드(guard/scans) 3건이 93초 안에 들어오면 24.2GB 피크 후 사망.
  • 원인은 OCR(사진 속 글자를 읽어내는 처리) 엔진 초기화 설정이었다. PaddleOCR의 기본값은 limit_type='min'·limit_side_len=64큰 이미지를 줄이는 게 아니라 작은 이미지를 키우는 쪽 기준이라, 원본 해상도가 그대로 CPU 검출 모델에 들어간다(우리 환경의 paddle은 CUDA 빌드가 아니어서 이 계산이 전부 CPU·시스템 메모리에서 일어난다). 상위 파이프라인 기본값에는 긴 변 4000px 상한이 있는데, 우리 초기화가 그 값을 None으로 덮어써 상한이 사라진 상태였다. 폰으로 찍은 계약서 사진은 보통 6~12MP다.
  • 실측: 12MP 이미지 1건을 메모리 8GB로 묶은 환경에서 처리시키면 프로세스가 강제 종료(exit 137)됐다. 긴 변 1600px 상한을 걸면 같은 이미지가 peak 1.33GB로 끝났고, 인식 결과는 55줄로 동일했다 — 글자를 놓치지 않는다.
  • 고칠 때는 실패하는 테스트를 먼저 썼다. “엔진 초기화 인자에 축소 기준(max)과 긴 변 상한이 반드시 들어가야 한다”는 회귀 테스트를 만들어 빨간불을 확인하고, 그 다음 text_det_limit_type="max"·text_det_limit_side_len=1600을 적용했다.
  • 이 건은 아직 커밋·버전 번호를 붙이지 않았다. 코드와 테스트는 통과 상태이고, 릴리스 표기는 다음 반영 때 붙인다.

⑤ 시연 구성 재확인

  • 예열 스크립트(demo_warmup.sh)로 판정 모델(EXAONE)과 리포트 모델(gemma)을 미리 올려두는 절차를 다시 돌려 두 모델 동시 상주를 확인했다(14GB / 16.3GB). 첫 요청이 모델 로딩을 기다리느라 느려지는 구간을 시연에서 없애기 위한 것이다.
  • 동시 처리 슬롯은 어제 정한 대로 1(OLLAMA_NUM_PARALLEL=1)이다. 실행 중인 호스트에 이 설정이 빠져 있던 것을 확인하고 레포 값과 맞췄다. 빠른 쪽이 아니라 두 모델이 안 깨지는 쪽이라는 어제의 결정 그대로다.

⑥ 다음 단계

  • OCR 입력 상한을 커밋하고 버전 로그에 반영. 그 뒤 실제 폰 사진으로 문서 점검을 한 번 더 왕복시켜 인식 품질을 눈으로 확인.
  • 8/31에 남긴 항목은 그대로다 — 외부망(집 와이파이 밖) 실동작 확인, 긴 요청(상담 준비 리포트 ~30초)이 터널을 통과해 끝까지 오는지. 오늘 잡은 두 건이 다행히 그 확인을 방해하던 요인이었다(느린 턴·죽는 서버).
  • 응답 스트리밍·클리어 사운드는 여전히 P1 보류(오디오 에셋 0건).

2026-09-02 — 라이선스 정리의 날: 공개 저장소에 9일간 남아 있던 재배포 금지 에셋 제거(82커밋 재작성) + 오픈소스 고지 페이지 신설 (BE v0.16.30 / FE v0.33.12→v0.34.0)

기능을 늘린 날이 아니라 남의 것을 쓴 흔적을 정리한 날이다. 우리는 게임 화면에 유료 타일셋과 오픈소스 라이브러리를 쓴다. 하나는 “공개 저장소에 올려두면 안 되는 것”이었고, 하나는 “화면에 표시해야 하는 것”이었는데 둘 다 지키지 않고 있었다.

① 전날 잡아둔 OCR 입력 상한을 릴리스에 반영했다 (BE v0.16.30)

  • 어제 코드·테스트만 통과 상태로 두었던 OCR(사진 속 글자를 읽어내는 처리) 입력 상한을 커밋하고 버전을 붙였다.
  • 어제는 12MP 사진 1건 기준이었고, 오늘은 실제 사고 조건이었던 연속 3건으로 다시 쟀다. 상한 적용 전 peak 메모리 24GB+(강제 종료) → 적용 후 1.34GB. 같은 사진에서 뽑아낸 항목 13개와 문서 종류 판별은 상한 전후가 동일했다 — 줄여도 읽어야 할 글자는 다 읽는다.
  • 인식 정확도에 영향이 없는지는 짐작하지 않고 문서 점검 테스트 71개 전량(OCR 샘플 회귀 평가 포함)으로 확인했다. 테스트용 이미지가 724×1024라 상한(긴 변 1600px)에 걸리지 않아 동작이 그대로였다.

② 공개 저장소에 있으면 안 되는 그림 파일들을 히스토리에서 지웠다 (FE v0.33.12)

  • 발단: 우리가 산 유료 타일셋(LimeZu)은 재배포 금지다. 그래서 .gitignore(저장소에 올리지 않을 파일 목록)에 “유료 라이선스 에셋 — 리포에 올리지 않음”이라고 적어두었다. 그런데 .gitignore는 이미 추적 중인 파일을 지우지 않는다. 목록에 적기 전에 이미 올라간 파일은 그대로 남는다.
  • 결과적으로 8/24부터 9일간 공개 저장소에 타일셋과 플레이어 스프라이트가 그대로 노출돼 있었다. 저장소가 public이라 이미 긁어간 쪽이 있는지는 우리가 통제할 수 없다.
  • 조치: git filter-repo(저장소의 과거 커밋을 전부 다시 써서 특정 파일을 없던 것으로 만드는 도구)로 82개 커밋 전체를 재작성해 이미지 52개 경로를 지웠다.
  • 1차 시도가 불완전했다. 지울 목록을 git rev-list --objects로 뽑았는데, 이 명령은 파일 내용 하나당 경로를 한 줄만 출력한다. 우리 에셋은 과거 커밋에서 폴더가 한 번 옮겨졌던 터라 옛 경로 16개가 목록에서 빠져 살아남았다. 확장자 기준 경로 정규식(^frontend/(docs|public/assets)/.*\.(png|jpe?g|...)$)으로 2차 패스를 돌려 마저 지웠다.
  • 검증은 눈으로 하지 않았다. 모든 커밋의 파일 트리를 전수 스캔해 잔존 0건, 원래 파일 내용에 직접 접근이 불가능해진 것, 저장소 무결성 검사(fsck) 정상까지 확인했다. 한글 파일명이 이스케이프돼 검사에서 빠지는 함정이 있어 core.quotepath를 끄고 돌렸다.
  • 원격 저장소는 강제 덮어쓰기 대신 삭제 후 재생성했다. 덮어쓰기만 하면 지운 옛 내용이 서버 쪽에 떠도는 객체로 남을 수 있다. 새 HEAD는 6280208.
  • 우리가 직접 만든 문서 점검 테스트 이미지 35개는 남겼다. 지운 원본은 로컬 백업으로 보관 중이고 전부 gitignore 대상이다.
  • 재발 방지로 세 가지를 규칙으로 남겼다 — ⑴ .gitignore에 경로를 추가할 땐 git ls-files이미 추적 중인 게 없는지 먼저 확인한다, ⑵ 히스토리에서 지울 땐 경로 목록이 아니라 정규식을 쓴다, ⑶ 검사할 땐 한글 파일명 이스케이프를 꺼둔다. 참고용 스크린샷 폴더(docs/refs/)도 .gitignore에 추가했다.

③ 오픈소스·폰트·아트 라이선스 고지 페이지를 만들었다 (FE v0.34.0)

  • 지우는 쪽이 ②였다면 이쪽은 표시해야 하는 쪽이다. MIT(자유롭게 쓰되 저작권 표시와 허가 문구를 배포물에 함께 실으라는 오픈소스 라이선스)와 OFL(폰트용 오픈 라이선스)은 둘 다 “배포물에 고지를 실을 것”을 조건으로 건다. 우리는 그동안 싣지 않고 있었다.
  • /licenses 페이지 신설. MIT 7종(Next.js·React·React DOM·Phaser·zustand·EasyStar.js·Tailwind CSS)의 저작권 표시와 허가 문구 전문, OFL 폰트 2종(Galmuri11·Pretendard Variable). 저작권 줄은 요약하지 않고 각 패키지의 LICENSE 원문을 그대로 옮겼다. 개발할 때만 쓰고 배포물에는 들어가지 않는 도구들은 제외했다.
  • 목록은 코드가 아니라 데이터 파일(content/licenses.ts)에 뒀다. 항목을 추가할 때 화면 코드를 고치지 않아도 되게 하는 프로젝트 규칙 그대로다. 문서판(docs/licenses.md)은 그 사본이며, 고칠 때는 데이터 파일이 먼저다.
  • 아트 에셋도 같은 페이지에 실었다. LimeZu 팩 3종의 라이선스 문구에 Credits required가 명시돼 있다 — 크레딧 표기는 예의가 아니라 사용 조건이고, 화면에서 지우면 라이선스 위반이 된다. 다만 요구하는 건 크레딧이지 조항 전문 게시가 아니라 “할 수 있는 것/없는 것” 블록은 싣지 않았다. MIT·OFL은 반대로 허가 문구 전문이 필요해서 실었다 — 라이선스마다 요구하는 게 달라서 형식을 통일하지 않았다.
  • PixelLab으로 생성한 캐릭터·펫, Gemini로 만든 로고·전투 배경은 출처 표기로 따로 묶었다. 라이선스 조항이 아니라 “이건 이 도구로 만들었다”는 명시이며, 각 도구의 이용약관은 별도 확인 대상으로 남겨뒀다.

④ 링크 하나를 놓는 데 세 번 막혔다

  • 고지는 만들어놓고 못 닿으면 의미가 없다. 링크는 타이틀 화면의 게스트·로그인 오버레이 양쪽, 메뉴 버튼 아래 중앙에 뒀다.
  • 처음엔 좌하단 구석에 띄우려 했는데 클릭이 안 먹었다. 로그인 상태의 타이틀 화면은 뒤에 실제 허브맵이 돌아가고 있고, 그 위에 챗봇 위젯(좌하단)과 전체화면 버튼(우하단)이 더 높은 층(z-50)에 깔린다. 오버레이는 z-40이라 링크가 위젯에 덮였다. 절대배치를 버리고 메뉴 흐름 안으로 넣었다.
  • 글자가 안 보이는 문제도 있었다. 색을 테마 변수로 줬는데 로그인 오버레이는 그 변수가 정의된 영역 밖이라 값이 비고, 브라우저 기본색(거의 검정)으로 떨어졌다. 네이비 배경 위 검정 글씨였다. 색을 리터럴로 박았다.
  • 가장 성가신 건 눌리는데 되돌아오는 문제였다. 로그인 여부를 검사하는 가드의 공개 경로 목록에 /licenses가 빠져 있어, 미로그인 상태에서 링크를 누르면 페이지가 잠깐 열렸다가 타이틀로 튕겼다. 명령줄로 페이지를 받아보는 확인(curl)은 통과했었다 — 서버가 만들어 보내는 HTML에는 가드가 돌지 않기 때문이다. 브라우저에서 실제로 그려질 때까지 봐야 잡히는 종류였다.
  • 링크 클릭 영역도 117×20 → 142×36으로 넓혔다. 커서 모양은 이미 손가락이었지만 글자 픽셀만 영역이라 조금만 빗나가면 반응이 없었다.

⑤ 배포와 남은 것

  • 미리 만들어둔 번들을 올리는 방식(에셋이 저장소에 없으므로 이 방식이어야 한다)으로 재배포하고, 에셋 139개가 빠짐없이 들어갔는지와 각 경로 응답을 확인했다. /licenses는 실제 브라우저로 열어 왕복까지 봤다.
  • 라이선스 작업은 아직 커밋하지 않았다. 화면은 올라갔고 버전 로그(v0.34.0)에도 적었지만 형상 관리 반영은 다음 차례다.
  • 브라우저 탭 아이콘(favicon)이 없어 404가 한 건 난다. 동작에는 영향이 없고, 기존 로고 에셋으로 붙일지는 아직 정하지 않았다.
  • 배포본 자동 왕복 점검에서 실내 NPC 말 걸기가 3회 독립 실행 전부 대화로 넘어가지 않았다. 이동 키는 정상이었고 캐릭터도 움직였다. 자동화 도구가 게임 캔버스에 키 입력을 넣는 방식의 한계인지 실제 결함인지는 아직 못 갈랐다 — 손으로 재현 조건을 좁히는 것이 다음 작업이다.
  • 8/31~9/1에 남긴 항목은 그대로다: 외부망(집 와이파이 밖) 실동작 확인, 긴 요청(상담 준비 리포트 ~30초)의 터널 통과 확인, 응답 스트리밍·클리어 사운드(P1 보류).

2026-09-04 — 해커톤 본선 종료: 우수상(고용노동부장관상) 수상 🏆

제8회 K-디지털 트레이닝 해커톤 본선이 끝났다. 결과는 우수상 — 고용노동부장관상.

6/27 팀 구성부터 두 달여, 예선 기획서(8/10 제출) → 본선 진출 확정(8/21) → 본선 개발 스프린트 → 8/31 실도메인 프로덕션 배포 → 9/1 개발 마감까지 이 문서에 일자별로 쌓아온 기록이 이 결과로 닫힌다. 제출물을 별도 산출물 대신 실도메인 배포(www.life-tutorial.com)로 갈음해 심사위원이 브라우저에서 직접 플레이하는 형태로 승부한 선택까지 포함해서다.



한눈에 보는 마일스톤

기간 단계 산출물
6/27 ~ 7/10 기획·설계 사업계획서·개발/UI 정의서 v1.2, 시스템 다이어그램
7/28 ~ 8/1 본선 대응 통합 정리서, 콘텐츠 설계서 A1~A5, 5주 실행 계획
8/2 ~ 8/4 백엔드 스프린트 core 인프라 + ontology/quest/npc BC, 법령 1,593청크 × 2벡터 공간, 판정 API, ERD v1.0
8/23 프론트 스프린트 스캐폴딩 → API 계약/MSW → 방어전 → 허브맵 → 결과 화면 (v0.1.0→v0.6.3)
8/24 풀스택 관통 인증(auth BC + auth feature) → 게임 API 이관(quest BC, MSW 제거) → 2-Call 판정(EXAONE+RAG, 왕복 3.05초) → Guard OCR(PaddleOCR + /guard 플로우) — BE v0.10.0→v0.12.15 / FE v0.7.0→v0.8.17
8/25 게임 월드 대확장 CALC 던전 풀스택(주휴수당 Q1~Q5, 지켜낸 돈 330,240원) + 판정 평가 100%·오CLEAR 0 + 전 BC myself 프로브 → 허브 오픈월드(A* 이동·차량·신호등) → 편의점 실내 씬 + dev:mock → 포탈·시나리오·전투 UI 리뉴얼 — BE v0.12.16→v0.12.23 / FE v0.9.0→v0.20.27 (92개 버전)
8/26 폴리시·연출 신호등 횡단보도별 독립 교통(가속 탈출·정지선·차간 클램프) → 편의점 벽 링·전투 연출(나사장 스프라이트·수호천사 펫·반박 성공 시퀀스) → 상태 반응형 포탈·세션 안정화(MSW 리로드 튕김 해결) → Guard DOCX 풀스택·OCR 정확도 → 랜딩 라이브 허브맵·전체화면 모드 + insight BC 설계 v2.2 — BE v0.12.24→v0.12.27 / FE v0.20.28→v0.20.52 (25개 버전)
8/27 insight 풀스택·AI 신뢰성 평가 상담 준비 리포트 풀스택(insight BC 4+1 API·엔진 4모듈 → FE /report → 실백엔드 전환·30일 보관) + 평가 하네스 146케이스·3모델 교차 평가(gemma3:12b 확정 — 분류 100%·값 92.9%·precision 91.7%·날조 0) + 방어전 상호 대응 대사·75발화 배터리(오CLEAR 0) + Guard VIOL-01~05·AUTO 자동 진단 + 임베딩 gemini 전환(VRAM LLM 전용, exaone+gemma 동시 상주 14.0/16.3GB) + Modern UI 크림 패널 스킨·시연 문서 3종 — BE v0.12.28→v0.15.11 / FE v0.21.0→v0.27.10 (78개 버전)
8/28 판정 신뢰성 총력전·챗봇 UX 전환 릴리스 3커밋(main) + 평가셋 146→156 전 게이트 통과(분류 100%·값 90.2%→날조 차단 후 96.7%·치명 0) + Guard 35변형 회귀 35/35 + 방어전 오CLEAR 사냥(EXAONE 54건 100%·혼합 발화 Veto·3단 신고 FULL CLEAR 확정) + insight 도배 사다리(LLM 소모 ~15회→0회) + 가드·리포트 챗봇 위젯·패널 전환(수호천사 배제·메신저 대화창·해요체) + 로그인 모달·랜딩=실제 허브 + 주휴수당 전액/0원·계산기 리디자인·라이브 배경 + ERD v2.0(23테이블)·metrics_status 소급 — BE v0.15.12→v0.16.15 / FE v0.27.11→v0.30.21 (52개 버전)
8/29 리허설·안정화 자동 완주 3라운드 9회(3차 무결점·콘솔 에러 0) + 폴백 훈련 2종 실발동(절차 결함 발견 — 오프라인 전환 시 리포트 모델 언로드 필수, 판정 22초→3.8초) + 결함 6건 수정(테스트 회귀 4건·시드 대사 DB 미동기화·문구 해요체·스탯 persist 회귀·계산기 버튼 잘림·계정 간 스냅샷 유출) + 판정 한국어 표현 한계 규명(현행 유지 결정) — BE v0.16.16→v0.16.18 / FE v0.30.22→v0.30.25 (7개 버전)
8/30 배포 구성 확정·프론트 폴리싱 시연 배포 B안 확정(화면 Vercel + API 백엔드 직결 — 프론트 코드 수정 0, 에셋은 CLI 배포) + 본선 3일 할 일 재작성(1일차 네트워크 검증) + 계산 던전 배경 캔버스→정지 이미지(요청 112→19건·JS 2246→772KB·픽셀 차 0) + 프로덕션 빌드 실측(9.5MB→1.43MB, 맵 등장 40~45% 단축) + 화면 전환 페이드 5종·걷힘 연출 실종 결함(자식 이벤트 버블링 161ms) 해결·씬 준비 기반 전환 + 라우팅 고정(/=랜딩, 뒤로가기는 플레이 유지)·튜토리얼 오표시 4건 + 입력 차단 참조 카운팅 전환(4경로·대조 실험 검증) + 에셋 누락 검사 127경로·버튼 커서 48개 + 용어 가드문서 점검·필드 코드 한글화 17종 + 3스테이지 포탈 잠금 정합 — BE v0.16.19 / FE v0.30.26→v0.30.65 (41개 버전)
8/31 프로덕션 배포 실행 B안 실행 완료(화면 Vercel www.life-tutorial.com + API 홈서버 직결 api.life-tutorial.com — 프론트 코드 수정 0, 환경변수 2개) + 노출 대응(판정 API 인증 요구·API 자동 문서 3경로 차단·CORS 허용·쿠키 Secure·OAuth 리디렉트 프로덕션 전환) + 실도메인 로그인 왕복·세션 유지 확인 + 판정 동시 슬롯 실측(4슬롯 6.6GB·동시4 1.4s/eff 3.99x, 6=7.6GB·1.99s, 8=8.6GB·1.95s → VRAM 공존 우선으로 1 확정) + 방어전 이탈·재진입 1방어선 초기화(BE·FE·MSW) + 계정 전환 스탯 경합 해결(3차에서 원인은 백엔드 미재시작) + 걷기 애니메이션 8방향(4프레임·13fps, 머리 비율 1.00 재생성) + 나무 Y-sort 6회 수렴(격자 행 + 가로 0.75타일) + 튜토리얼 등장 시점 3회 반전 후 확정·키캡 preload 3.9초 선행 + 챗봇 패널 등장/퇴장 애니메이션 + 허브 동행 펫 복원 후 재철거·전투 펫 전신 교체 + 시연용 빈 전송 스크립트(리포트 1사이클·방어전 3방어선) + 기술 내역 39항목 정합(구현 21·부분 10·미착수 8) — BE v0.16.20→v0.16.28 / FE v0.30.66→v0.33.10 (50개 버전)
9/1 지연·크래시 제거 방어전 턴 지연 원인 규명(고정 25초 대기 — 전제였던 “계정 분당 쿼터 소진”이 실측으로 반증: 분당 20회는 첫 콜부터 429, 분당 164회 30연발은 전량 성공 → gemini-embedding 전역 공용 쿼터가 원인) → 지수 백오프 전환(0.5s→상한 4s·예산 30s), 턴 28초 → 중앙값 0.4초(라이브 25회 실측, 14초 429 구간도 재시도로 통과) + 근거 검색 조용한 폴백에 경고 로그·테스트 5종 + 상담 준비 리포트 PDF 인쇄 복구(챗봇 패널 440px 잘림 → @media print A4 전폭) + OCR 입력 상한(문서 점검 3건/93초에 uvicorn OOM 25GB → PaddleOCR 기본값이 큰 이미지를 축소하지 않음을 확인, 긴 변 1600px 상한으로 12MP 강제종료 → peak 1.33GB·인식 55줄 동일 / 회귀 테스트 선행, 커밋·버전 미반영) + 두 모델 동시 상주·슬롯 1 재확인 — BE v0.16.29 / FE v0.33.11
9/2 라이선스 정리 OCR 입력 상한 릴리스 반영(연속 3건 실측 peak 24GB+ 강제종료 → 1.34GB, 필드 13개·문서타입 동일, 문서 점검 테스트 71개 전량 통과) + 공개 저장소 재배포 금지 에셋 제거(.gitignore가 이미 추적 중인 파일을 지우지 않아 8/24부터 9일 노출 → git filter-repo로 82커밋 재작성·이미지 52경로 삭제, 1차 경로목록 방식이 이동된 옛 경로 16개를 놓쳐 정규식 2차 패스, 전 커밋 트리 전수 스캔 잔존 0·fsck 정상, 원격은 force push 대신 삭제·재생성) + 라이선스 고지 페이지 /licenses(MIT 7종 저작권+허가 문구 전문·OFL 폰트 2종·LimeZu 아트 3종 크레딧(사용 조건)·생성 에셋 출처 표기, 데이터는 content/licenses.ts 단일 원본) + 링크 접근성 3건(허브 위젯 z-50에 덮이던 절대배치 철회 / 테마 변수 미적용으로 검정 글씨 → 리터럴 색 / 가드 공개 경로 누락으로 튕김 — curl은 통과, 클라이언트 렌더에서만 재현) + 프리빌드 재배포·에셋 139개 확인 — BE v0.16.30 / FE v0.33.12→v0.34.0
9/4 해커톤 본선·수상 제8회 K-디지털 트레이닝 해커톤 본선 종료 — 우수상(고용노동부장관상) 수상

스프린트 상태 (2026-09-04 기준)

일정 로드맵의 스프린트 표(Sprint 5·6)와 칸반 보드는 이 표를 기준으로 반영한다. 상태 근거는 커밋·버전 로그·위 일자별 기록뿐이며, 이 표를 갱신하지 않은 채 로드맵만 고치지 않는다.

Sprint 5 — 통합·폴리싱 (8/25 ~ 8/31) — ✅ 완료. MVP 범위(P0 + P2 승격분)를 전부 구현하고 8/29 통합 리허설·8/31 실도메인 배포까지 마쳐 스프린트를 닫는다. 아래 🔄 표기는 MVP 밖 P1 잔여분이다.

담당 업무 상태 근거
이은상 게임 플레이 API 백엔드 이관 — quest BC 오케스트레이션, quest_session/player_stat 영속, MSW 제거 ✅ 완료 (8/24) BE v0.10.0→v0.12.15 / FE v0.7.0→v0.8.17
이은상 판정 평가셋 실측 → 판정 프롬프트 튜닝, 오입력 내성 케이스 처리 ✅ 완료 (8/27~8/28) 146→156케이스 전 게이트 통과, 오CLEAR 0, 도배·날조 방어
신채연 계산 미니게임(주휴수당 던전) 화면·로직, 신규 캐릭터 에셋 ✅ 완료 (8/25, 8/28 규칙 개편) CALC 던전 풀스택 Q1~Q5, 전액/0원 규칙 + 계산기 리디자인
신채연 연출 폴리싱 — 표정 전환, 응답 스트리밍, 클리어 사운드/모션 🔄 부분 표정 매트릭스·전투 연출 완료(8/26~8/27), 화면 전환 페이드 5종·모달/버튼 연출 완료(8/30), 걷기 애니메이션 8방향·튜토리얼/챗봇 패널 등퇴장 연출 완료(8/31) / 응답 스트리밍·사운드 미착수 — 오디오 에셋 0건
김충식 선택지 시뮬레이션(부당해고) 콘텐츠, 발표자료·시연 시나리오 ✅ 완료 발표자료 최종본 완성, 시연 영상 녹화 — 유튜브에 전체 완주 롱폼 1편 + 기능별 숏폼 4편(방어전 · 주휴수당 계산 · OCR 문서 점검 · 상담 준비 리포트, 링크는 프로젝트 개요 → 시연 영상). 발표 리허설 3회 완주. 시연용 빈 전송 스크립트 반영(8/31) / 선택지 시뮬레이션은 계획상 의도적 보류(P1 여력분)
전원 시연 배포 구성 결정 — 현장 맥북 + 집 홈서버 ✅ 결정 완료 (8/30) B안 확정(화면 Vercel + API 백엔드 직결, Cloudflare Tunnel)
전원 시연 배포 실행 — 실도메인 배포·노출 대응 ✅ 실행 완료 (8/31) www.life-tutorial.com + api.life-tutorial.com 배포, CORS·쿠키 Secure·OAuth 리디렉트·판정 API 인증·API 문서 차단. 실도메인 로그인 왕복 확인 / 외부망(집 와이파이 밖)·긴 요청 통과 확인은 본선 1일차 잔여
전원 통합 리허설 — 완주 3회 + 오프라인 전체 데모 + 심사위원 직접 타이핑 시나리오 ✅ 완료 (8/29) 자동 완주 3라운드 9회(3차 무결점), 폴백 훈련 2종 실발동, 결함 6건 수정

남은 것: 응답 스트리밍·클리어 사운드(P1 — 오디오 에셋 0건), 외부망 실동작 확인 + 긴 요청(리포트 ~30초) 터널 통과 — 본선 1일차. 발표 리허설은 3회 완주로 닫혔다.

Sprint 6 — 제출·대회 (9/1 ~ 9/2) — ✅ 완료. 프로덕션 배포·개발 마감(지연·크래시 제거)·라이선스 정리에 이어 본선 대회까지 끝났다 — 우수상(고용노동부장관상) 수상(9/4).

담당 업무 상태
김충식 최종 결과물 패키징 — 제출물 점검, 발표자료 최종본 ✅ 완료 — 제출물은 실도메인 배포로 갈음한다. 별도 빌드 산출물을 넘기는 대신 심사위원이 www.life-tutorial.com에서 브라우저로 직접 플레이하는 형태(8/31 배포·실도메인 로그인 왕복 확인) . 발표자료 최종본도 완성했고, 네트워크 사고 대비로 시연 영상 녹화 — 유튜브에 전체 완주 롱폼 1편 + 기능별 숏폼 4편(방어전 · 주휴수당 계산 · OCR 문서 점검 · 상담 준비 리포트)
이은상 백엔드 안정화 — Fake↔실LLM 전환 스위치 최종 점검 ✅ 완료 (9/1 임베딩 429 지수 백오프로 턴 28초→0.4초 · 9/2 OCR 입력 상한 릴리스 반영 v0.16.30 — 문서 점검 테스트 71개 전량 통과 · 8/31 CORS·쿠키·OAuth 반영, 동시 슬롯 1 확정 / 8/29 폴백 훈련 절차 결함 반영은 잔여)
신채연 프론트 최종 빌드 — 시연 기기 세팅 확인 ✅ 완료 (8/31 프로덕션 빌드 Vercel 배포, 9/1 리포트 PDF 인쇄 수정 v0.33.11, 9/2 라이선스 고지 페이지 + 재배포 금지 에셋 히스토리 제거 후 프리빌드 재배포 v0.34.0 / 라이선스 작업 커밋은 잔여)
전원 해커톤 대회 — 발표 + 실제 플레이 시연, 최종 결과물 제출 ✅ 완료 (9/4) — 우수상(고용노동부장관상) 수상

칸반 보드 이동 (9/1 기준 → 9/2 기준)

이동 항목
Done 대기 → Done OCR 입력 상한 — 커밋·버전 반영 완료(BE v0.16.30), 연속 3건 실측 1.34GB·문서 점검 테스트 71개 통과
To Do → Done 공개 저장소 재배포 금지 에셋 제거 — 히스토리 82커밋 재작성, 전 커밋 전수 스캔 잔존 0 / 라이선스 고지 페이지 /licenses 및 링크 접근성 3건
To Do 신규 라이선스 작업 커밋·푸시 — 화면 배포·버전 로그는 끝, 형상 관리 반영만 잔여
To Do 신규 실내 NPC 말 걸기 자동 점검 실패(3회 독립 실행 전부) — 자동화 캔버스 입력 한계인지 실제 결함인지 손으로 재현해 판별
To Do 신규 favicon 부재 404 — 기존 로고 에셋으로 붙일지 결정
To Do 유지 (9/1분) 실제 폰 사진으로 문서 점검 왕복 재확인(상한 적용 후 인식 품질)
Done 유지 (9/1분) 방어전 턴 지연(임베딩 429 고정 대기) 제거 — 28초→0.4초 / 상담 준비 리포트 PDF 인쇄
In Progress 유지 백엔드 안정화 — 폴백 절차 결함 반영 잔여
Done 유지 (8/31분) 시연 배포 실행(B안) — 실도메인 화면·API 배포, CORS·쿠키·OAuth·노출 대응 / 프론트 프로덕션 빌드 배포
To Do → Done (9/4) 해커톤 대회 — 본선 종료, 우수상(고용노동부장관상) 수상
Done 유지 실전 가드(계약서 AI 진단), 상담 준비 리포트, 계산 미니게임, 판정 평가셋·오입력 내성, 게임 플레이 API 백엔드 이관, 통합 리허설
To Do 신규 외부망(집 와이파이 밖) 실동작 확인 + 긴 요청(리포트 ~30초) 터널 통과 확인 — 본선 1일차
To Do 유지 선택지 시뮬레이션(부당해고) — P1 보류 / 최종 결과물 패키징(9/1)
In Progress 유지 발표자료·시연 시나리오 / 연출 폴리싱 — 잔여 범위는 응답 스트리밍·클리어 사운드
Backlog 유지 2차 방어전 퀘스트 · 공유 카드 · 모바일 앱

팀 BeyondFacade — 2026 제8회 K-디지털 트레이닝 해커톤. 본 사이트는 발표 심사 참고용 자료입니다.

This site uses Just the Docs, a documentation theme for Jekyll.