BurningTimesAi/공유/기획/FirstRising/리뉴얼/tools/build_renewal_v1.py

274 lines
28 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# FirstRising 리뉴얼 기획서 v1 빌더 — 작업표시줄 상주 방치 × 디아블로식 파밍 × Steam 장터
import os, csv, sys
sys.path.insert(0, os.path.dirname(__file__))
from deckkit import *
BASE = r"E:\BurningTimes\공유\기획\FirstRising"
FR = lambda n: os.path.join(BASE, "시스템분석", "img", n)
TB = lambda n: os.path.join(BASE, "레퍼런스_TBH", "img", n)
MK = lambda n: os.path.join(BASE, "리뉴얼", "img", n)
OUT = os.path.join(BASE, "리뉴얼", "2026-09-19_FirstRising_리뉴얼_기획서_v1.pptx")
SRC = "근거: [FR] FirstRising 시스템 분석(2026-09-19) · [TBH] TBH 레퍼런스 분석 v2(클라이언트 데이터·실행 화면) · [공개] 공개 기사"
D = Deck("FirstRising 리뉴얼 v1")
# 1. 표지 -------------------------------------------------------------------------------
D.page += 1
sl = D.prs.slides.add_slide(D.blank)
D.rect(sl, 0, 0, 13.333, 7.5, fill=DARK)
D.image(sl, MK("mock_taskbar_concept.png"), 6.2, 0.9, 6.8, 3.83)
D.text(sl, 0.7, 0.9, 5.4, 0.5, "개발 승인 요청 · BT22-Plan", size=14, bold=True, color=ACCENT)
D.text(sl, 0.7, 1.45, 5.5, 1.6, ["FirstRising", "리뉴얼 기획서"], size=38, bold=True, color=WHITE, spacing=1.0)
D.text(sl, 0.7, 3.2, 5.4, 1.3, ["작업표시줄 위의 다크 판타지", ("상주 방치 × 디아블로식 파밍 × Steam 장터", {"color": ACCENT, "bold": True})],
size=19, color=RGBColor(0xD6, 0xDB, 0xE4))
D.rect(sl, 0.7, 5.0, 12.0, 0.04, fill=ACCENT)
D.text(sl, 0.7, 5.25, 12, 0.9, ["2026-09-19 · 총괄PM", "근거 자료: FirstRising 시스템 분석 기획서 · TBH 레퍼런스 분석 v2 (같은 폴더 상위)"],
size=12.5, color=RGBColor(0xB8, 0xC0, 0xCC))
# 2. 한 장 요약 -------------------------------------------------------------------------
sl = D.slide("한 장 요약", "EXECUTIVE SUMMARY", SRC)
cols = [("제안", NAVY, ["모바일 서바이벌 → Steam PC", "\"작업표시줄 상주형\"", "3D 방치 파밍 게임으로 리뉴얼"]),
("근거", TEAL, ["① 현 구조의 게임성 약점", " (정체성·반복·에너지·모바일 BM)", "② TBH가 3요소 조합을 입증", "③ 우리 3D·물량·타격감이 차별점"]),
("바뀌는 것", RED, ["플랫폼 · 코어 루프 · 클래스", "아이템화 · 순환 허브 · 메타", "경제 · BM · 서버/보안", "(9개 영역, 4~16쪽)"]),
("요청", ACCENT, ["1 Steam PC 우선 전환", "2 모바일 BM 폐기", "3 P0 프로토타입 착수", "4 법률 검토 착수"])]
for i, (t, col, lines) in enumerate(cols):
x0 = 0.55 + i * 3.1
D.rect(sl, x0, 1.3, 2.95, 0.62, fill=col)
D.text(sl, x0, 1.33, 2.95, 0.56, t, size=17, bold=True, color=WHITE, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
D.box(sl, x0, 2.02, 2.95, 3.25, lines[0], lines[1:], fill=LIGHT, size=14.5, bsize=13, line=LINE)
D.key_msg(sl, "새로 만드는 게임이 아니라, 검증된 조합 위에 우리 자산(3D 다크 판타지·ECS 물량·타격감)을 얹는 전환이다", y=5.55)
D.text(sl, 0.55, 6.3, 12.2, 0.6, "3요소 = TBH에서 검증된 '작업표시줄 상주 방치형' · '디아블로식 파밍' · 'Steam 장터 거래'", size=12, color=GRAY)
# 3. 왜 바꾸는가 — 현재 한계 ---------------------------------------------------------------
sl = D.slide("왜 바꾸는가 — 지금 구조의 한계 4가지", "WHY · AS-IS", "근거: [FR] 게임성 평가(부정 요인) · 실캡처")
D.image(sl, FR("capture_03_main_chapter.jpg"), 0.55, 1.25, 6.0, 3.38, frame=DARK)
D.caption(sl, 0.55, 4.66, 6.0, "[FR 실캡처] 챕터 입장 — 게임 시작 버튼에 에너지 ×5")
D.image(sl, FR("capture_07_shop_gacha.jpg"), 6.75, 1.25, 6.0, 3.38, frame=DARK)
D.caption(sl, 6.75, 4.66, 6.0, "[FR 실캡처] 상점·가챠 — 광고 무료 횟수, 10/50회 천장")
issues = [("캐릭터 정체성 없음", "플레이어블 2종 전 스탯 동일"), ("반복 위험", "맵 128개 · 전투 환경 5종, 전투 루프 동일"),
("세션 단절", "챕터당 에너지 5 → 5~6판이면 소진"), ("모바일 BM 의존", "광고·가챠·IAP 중심 수익 구조")]
for i, (t, b) in enumerate(issues):
D.box(sl, 0.55 + i * 3.1, 5.05, 2.95, 1.3, t, [b], fill=PALE_R, size=14, bsize=11.5)
D.key_msg(sl, "짧게 끊어 하는 모바일 서바이벌로는 '오래 켜두고 쌓는' 성장 경험을 만들 수 없다", y=6.5)
# 4. 왜 이 3요소인가 ---------------------------------------------------------------------
sl = D.slide("왜 이 3요소인가 — TBH가 검증한 조합", "WHY · EVIDENCE", "근거: [TBH] 클라이언트 데이터·실행 화면 · [공개] Inven Global 기사 제목(Steam 동시접속 2위·14만)")
pill = [("① 작업표시줄 상주 방치", TB("02_taskbar_strip.jpg"), ["켜두면 자동 전투·성장", "룬 골드의 94%가 '방치 효율'", "(드롭률·자동화·보관)"]),
("② 디아블로식 파밍", TB("03_hero_panel.jpg"), ["장비 5,954종 · 10등급", "옵션 57종 × 10티어", "고유 옵션 89 · 클래스 맞춤 드롭 75%"]),
("③ Steam 장터 거래", TB("09_trade_ship.jpg"), ["전설 이상 1,093종 거래 가능", "교역선에 실어 장터 등록", "후가공 아이템 거래 금지"])]
for i, (t, img, lines) in enumerate(pill):
x0 = 0.55 + i * 4.1
D.rect(sl, x0, 1.25, 3.95, 0.55, fill=[TEAL, BLUE, ACCENT][i])
D.text(sl, x0, 1.27, 3.95, 0.5, t, size=15.5, bold=True, color=WHITE, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
D.image(sl, img, x0, 1.9, 3.95, 2.55, frame=DARK)
D.bullets(sl, x0, 4.55, 3.95, 1.5, lines, size=12.5, gap=3)
D.key_msg(sl, "시장 반응 [공개]: Steam 동시접속 2위·14만 명 — '켜두는 파밍 + 현실 가치' 조합이 통했다", y=6.45)
# 5. 리뉴얼 컨셉 --------------------------------------------------------------------------
sl = D.slide("리뉴얼 컨셉 — 작업표시줄 위의 3D 다크 판타지", "CONCEPT", "이미지: 컨셉 목업(FirstRising 실캡처를 가로 띠로 합성) — 실제 구현 화면 아님")
D.image(sl, MK("mock_taskbar_concept.png"), 0.55, 1.2, 8.3, 4.67, frame=DARK)
D.box(sl, 9.05, 1.2, 3.7, 1.6, "한 줄 컨셉", ["일하는 동안 작업표시줄 위에서 파티가 싸우고, 돌아오면 전리품을 감정·합성·거래한다"], fill=PALE_A, size=14, bsize=12)
rows = [["", "TBH", "FirstRising"], ["그래픽", "2D 픽셀", ("3D 다크 판타지", RED)], ["물량", "소수 몬스터", ("동시 2,000(ECS)", RED)],
["타격감", "기본", ("판정 4종 연출", RED)], ["필드", "평면 스트립", ("기믹 8종·보스전", RED)]]
D.table(sl, 9.05, 2.95, 3.7, 2.9, rows, colw=[1.0, 1.2, 1.5], size=11, hsize=11.5)
D.key_msg(sl, "3요소는 TBH에서 가져오고, '보는 재미'는 우리 3D·물량·타격감으로 차별화한다", y=6.1)
# 6. 변경 총괄표 --------------------------------------------------------------------------
sl = D.slide("무엇을 바꾸나 — 9개 영역 한눈에", "AS-IS → TO-BE", SRC)
rows = [["영역", "지금 (As-Is)", "바뀐 뒤 (To-Be)", "이유 (한 줄)"],
["① 플랫폼", "Android 모바일", "Steam PC · 작업표시줄 상주 모드", "상주 방치는 데스크톱에서 성립"],
["② 코어 루프", "수동 이동 서바이벌 · 판당 스킬 선택", "무개입 자동 전투 · 지속 빌드", "켜둔 동안 입력이 없어야 한다"],
["③ 파티", "2종(스탯 동일)", "3인 파티 · 역할 클래스", "정체성 + 클래스 맞춤 파밍"],
["④ 아이템", "5등급 · 6부위 · 고정 옵션", "등급 확장 · 랜덤 옵션 티어 · 고유 옵션", "예측 불가 드롭 = 파밍의 재미"],
["⑤ 순환 허브", "합성·룬합성·진화 분산", "큐브형 허브 하나로 통합", "쌓이는 드롭을 한곳에서 처리"],
["⑥ 메타 성장", "진화 306 · 특별훈련(준비 중)", "방치 효율 트리(자동화 해금)", "방치할수록 방치가 편해진다"],
["⑦ 경제", "게임 내 폐쇄 경제", "Steam 장터 거래(상위 등급만)", "드롭의 현실 가치 = 강한 동기"],
["⑧ BM", "에너지 · 광고 · 가챠 · IAP", "DLC(클래스·펫·치장) + 장터 수수료", "상주·거래 구조와 충돌 제거"],
["⑨ 서버·보안", "로컬/서버 혼용 · TLS 검증 꺼짐", "서버 권한 인벤토리 · 보안 선결", "현금 가치 = 복제·해킹 피해"]]
D.table(sl, 0.55, 1.25, 12.2, 5.7, rows, colw=[1.6, 3.4, 3.9, 3.3], size=11, hsize=12, col_colors=[None, GRAY, RED, None])
# 7. ① 플랫폼 -----------------------------------------------------------------------------
sl = D.slide("① 플랫폼 — Android → Steam PC 상주 모드", "CHANGE 1 · PLATFORM", "근거: [TBH] 설정 화면·PlayerPrefs(창 위치·확대) · [FR] TestVisual 성능 실측(2026-09-19 세션 3)")
D.image(sl, TB("01_settings_general.jpg"), 0.55, 1.2, 3.6, 4.35, frame=DARK)
D.caption(sl, 0.55, 5.58, 3.6, "[TBH] 설정 — 항상 위·데스크톱 시작 시 자동 실행")
D.box(sl, 4.4, 1.2, 4.1, 2.35, "상주 모드 필수 기능", ["투명 창 + UI 밖 클릭 통과", "창 위치·확대 저장, 작업표시줄 스냅", "항상 위(선택) · 시작 시 자동 실행", "소리 분리(전투 로그·효과음)"], fill=PALE_T, size=14, bsize=12, align=PP_ALIGN.LEFT)
D.box(sl, 4.4, 3.7, 4.1, 1.85, "저전력 예산(안)", ["FPS 상한 · 렌더 스케일 하향", "그림자·후처리 끄기 · 스트립 영역만 렌더", "목표치는 P0에서 실측 후 확정"], fill=PALE_A, size=14, bsize=12, align=PP_ALIGN.LEFT)
D.box(sl, 8.75, 1.2, 4.0, 4.35, "왜 바꾸나", ["상주 방치는 '업무 중 PC'에서만 성립 — 모바일은 화면을 켜 둘 수 없다",
"",
"현재 TestVisual 전투 부하: Draw 250~270 · SetPass 최대 79 (에디터 실측)",
"→ 상시 실행에는 별도 예산 필요", "",
"TBH도 창 제어·자동 실행·항상 위를 '선택'으로 제공"],
fill=LIGHT, size=14, bsize=11.5, line=LINE, align=PP_ALIGN.LEFT)
D.key_msg(sl, "PC(Steam) 우선. 모바일은 리뉴얼 검증 후 '확인용 동반 앱' 여부를 별도로 판단한다", y=5.8)
# 8. ② 코어 루프 --------------------------------------------------------------------------
sl = D.slide("② 코어 루프 — 수동 서바이벌 → 무개입 자동 전투", "CHANGE 2 · CORE LOOP", "근거: [FR] SkillTable CoolTime(자동 발동) · PlayerMoveInputSystem(수동 이동) · 스킬 선택 UI · [TBH] 스킬 125종 전부 규칙 발동")
D.image(sl, FR("capture_01_wave_combat.jpg"), 0.55, 1.2, 6.2, 3.49, frame=DARK)
D.caption(sl, 0.55, 4.72, 6.2, "[FR 실캡처] 웨이브 전투 — 스킬은 이미 자동 발동, 이동만 수동")
rows = [["요소", "지금", "바뀐 뒤", "변경량"],
["스킬 발동", "쿨타임 자동", "그대로 유지", ("작음", TEAL)],
["이동", "플레이어 조작", "AI 자동 이동(접근·회피)", ("중간", ACCENT)],
["성장 선택", "판마다 레벨업 3택", "지속 빌드(장비·스킬 칸)", ("중간", ACCENT)],
["진행", "챕터 입장·종료", "웨이브 연속 → 포털로 다음 스테이지", ("중간", ACCENT)],
["실패", "부활 1회 후 종료", "자동 재도전 · 오프라인 보상", ("작음", TEAL)]]
D.table(sl, 7.0, 1.2, 5.75, 3.5, rows, colw=[1.3, 1.5, 2.3, 0.8], size=11, hsize=11.5)
D.bullets(sl, 0.55, 5.05, 12.2, 1.2, [(0, [("왜: ", {"bold": True}), ("상주형은 켜둔 동안 입력이 0이어야 한다. 우리 스킬은 이미 자동 발동이라, 바꿀 것은 '이동'과 '판당 선택'뿐이다.", {})])], size=13)
D.key_msg(sl, "전투 연출·물량·기믹은 그대로 — 입력만 걷어내 '보는 전투'로 바꾼다", y=5.95)
# 9. ③ 파티·클래스 -------------------------------------------------------------------------
sl = D.slide("③ 파티 — 2종(스탯 동일) → 3인 파티 · 역할 클래스", "CHANGE 3 · PARTY", "근거: [FR] PlayerTable Ariel·Barbarian 전 필드 동일 · [TBH] 3인 파티(스트립 화면)·드롭 행 75% 클래스 조건·DLC 영웅 3")
D.image(sl, TB("02_taskbar_strip.jpg"), 0.55, 1.25, 7.0, 1.52, frame=DARK)
D.caption(sl, 0.55, 2.8, 7.0, "[TBH 실행 화면] 작업표시줄 위 3인 파티 전투")
rows = [["역할(안)", "전투 방식", "파밍에서의 의미"], ["방패 탱커", "근접·막기·방어", "방어·저항 장비 수요"], ["근접 딜러", "광역·출혈", "주무기·치명 옵션 수요"],
["원거리 딜러", "투사체·관통", "활·석궁·투사체 옵션 수요"], ["서포터", "버프·회복·부활", "파티 전체 옵션 수요"]]
D.table(sl, 0.55, 3.2, 7.0, 2.6, rows, colw=[1.6, 2.2, 3.2], size=11.5, hsize=12)
D.box(sl, 7.8, 1.25, 4.95, 4.55, "왜 바꾸나", ["지금 캐릭터 선택은 외형 선택일 뿐이다(스탯 전부 동일)", "",
"클래스가 있어야 '내 파티에 맞는 장비'를 찾는 파밍 목표가 생긴다 — TBH 드롭 행의 75%가 클래스 조건", "",
"추가 클래스는 DLC 판매 축이 된다(TBH: 6종 중 3종 DLC)"],
fill=LIGHT, size=14.5, bsize=12, line=LINE, align=PP_ALIGN.LEFT)
D.key_msg(sl, "기본 3클래스로 무료 파티 완성 → 추가 클래스는 DLC", y=6.1)
# 10. ④ 아이템화 --------------------------------------------------------------------------
sl = D.slide("④ 아이템 — 고정 옵션 → 디아블로식 랜덤 파밍", "CHANGE 4 · ITEMIZATION", "근거: [FR] ItemOptionTable 등급 1~5·고정/조건부 능력, 부위 6 · [TBH] 10등급·옵션 57×10티어·고유 옵션 89·아이템 레벨 1~100")
D.image(sl, MK("mock_item_tooltip.png"), 0.55, 1.2, 4.2, 4.8, frame=DARK)
D.caption(sl, 0.55, 6.03, 4.2, "목업 — FirstRising 툴팁 예시(이름·수치는 예시)")
rows = [["항목", "지금 (FR)", "바뀐 뒤", "참고 (TBH)"],
["등급", "5단계", "8~10단계", "10단계"],
["부위", "6", "8~10(무기·보조·방어구·장신구)", "10"],
["옵션", "등급별 고정·조건부", "랜덤 옵션 + 티어", "57종 × 10티어"],
["아이템 레벨", "없음", "스테이지 레벨 연동", "1~100"],
["고유 옵션", "없음", "스킬 변형(원소·범위·투사체)", "89종"],
["드롭", "웨이브별 젬 가중치", "클래스 맞춤 + 등급 추첨", "행 75% 클래스 조건"]]
D.table(sl, 5.0, 1.2, 7.75, 3.9, rows, colw=[1.4, 1.9, 2.6, 1.8], size=11, hsize=11.5, col_colors=[None, GRAY, RED, None])
D.bullets(sl, 5.0, 5.2, 7.75, 1.0, [(0, [("왜: ", {"bold": True}), ("같은 아이템이 두 번 나오지 않는 '감정의 기대감'이 방치 중 접속 동기다. 고정 옵션은 한 번 맞추면 목표가 끝난다.", {})])], size=12.5)
D.key_msg(sl, "드롭 하나하나가 '열어볼 이유'가 되게 — 랜덤 옵션·티어·고유 옵션", y=6.4)
# 11. ⑤ 순환 허브 -------------------------------------------------------------------------
sl = D.slide("⑤ 순환 허브 — 흩어진 강화 시스템 → 큐브형 하나로", "CHANGE 5 · LOOP HUB", "근거: [FR] ItemComposeTable·RuneComposeTable·EvolutionAbilityTable·SpecialTraining · [TBH] 큐브 9기능·합성 확률(GradeInfoData)")
D.image(sl, TB("06_cube_panel.jpg"), 0.55, 1.2, 3.4, 4.8, frame=DARK)
D.caption(sl, 0.55, 6.03, 3.4, "[TBH 실행 화면] 큐브 — 연금술 레시피")
D.box(sl, 4.2, 1.2, 3.6, 4.8, "지금 (FR) — 분산", ["장비 합성", "룬 합성", "진화(306항목)", "특별 훈련(준비 중)", "", "→ 강화처가 흩어져 있고, 쓸모없는 드롭을 처리할 곳이 없다"],
fill=PALE_R, size=15, bsize=12.5)
D.arrow(sl, 7.9, 3.6, 8.5, 3.6, width=4)
D.box(sl, 8.6, 1.2, 4.15, 4.8, "바뀐 뒤 — 허브 하나", ["합성: 같은 등급 N개 → 상위 1개", "분해: 모든 아이템 → 골드", "재설정: 옵션 다시 굴리기", "후가공/제거: 슬롯 부여·해제", "제작: 부위 지정 제작", "",
"허브 레벨로 기능·등급 해금"], fill=PALE_T, size=15, bsize=12.5, align=PP_ALIGN.LEFT)
D.key_msg(sl, "방치로 쌓인 드롭 → 허브에서 합성·분해 → 보관 공간이 다시 비는 순환이 방치 루프를 닫는다", y=6.4)
# 12. ⑥ 메타 = 방치 효율 트리 -------------------------------------------------------------
sl = D.slide("⑥ 메타 성장 — '강해지는 트리' → '방치가 편해지는 트리'", "CHANGE 6 · META", "근거: [TBH] RuneLevelInfoData 833행(골드 499.5억) 분류 · 자동화 해금 순서 · [FR] 진화 306·특별훈련 준비 중(실캡처)")
D.image(sl, TB("07_rune_tree.jpg"), 0.55, 1.2, 6.3, 3.0, frame=DARK)
D.caption(sl, 0.55, 4.23, 6.3, "[TBH 실행 화면] 룬 트리 — 248노드")
rc = []
with open(os.path.join(BASE, "레퍼런스_TBH", "data", "derived_rune_categories.csv"), encoding="utf-8-sig") as fh:
rc = list(csv.DictReader(fh))
rc.sort(key=lambda r: -int(r["gold"])); tot = sum(int(r["gold"]) for r in rc)
top = rc[:4] # 상자 드롭률 · 자동화·편의 · 보관 한도 · 전투(전 영웅)
D.bar_chart(sl, 0.55, 4.55, 6.3, 1.75, [r["category"] for r in top], [round(int(r["gold"]) / tot * 100, 1) for r in top],
color=PURPLE, numfmt='0"%"')
D.box(sl, 7.1, 1.2, 5.65, 2.5, "TBH 룬 골드의 94%가 전투 외", ["상자 드롭률 48% · 자동화 25% · 보관 12% · 전투 스탯 6%"], fill=PALE_B, size=15, bsize=12.5)
D.box(sl, 7.1, 3.85, 5.65, 2.45, "FirstRising 방치 효율 트리(안)", ["오프라인 보상 해금 → 상자 자동 개봉 → 일괄 분해 필터", "→ 파티 슬롯 +1 → 스테이지 웨이브 단축 → 보관 한도 확장",
"진화·특별훈련 자리를 이 트리로 교체"], fill=PALE_T, size=15, bsize=12, align=PP_ALIGN.LEFT)
D.key_msg(sl, "방치 게임의 성장 목표는 '더 세게'보다 '더 편하게 더 많이' — 자동화를 보상으로 판다", y=6.45)
# 13. ⑦ 경제 — Steam 장터 ------------------------------------------------------------------
sl = D.slide("⑦ 경제 — 폐쇄 경제 → Steam 장터 거래", "CHANGE 7 · ECONOMY", "근거: [TBH] ItemInfoData 거래 가능 1,093종·교역선 10칸·후가공 거래 금지·장터 구매품 우편함 수령(로컬라이즈)")
D.image(sl, TB("09_trade_ship.jpg"), 0.55, 1.2, 3.3, 4.95, frame=DARK)
D.caption(sl, 0.55, 6.18, 3.3, "[TBH 실행 화면] 교역선")
steps = [("드롭·감정", "등급·옵션 확정"), ("보관", "가방·창고"), ("교역선 적재", "거래 가능만"), ("Steam 장터", "유저 간 판매"), ("우편함 수령", "구매품 도착")]
for i, (t, b) in enumerate(steps):
x0 = 4.1 + i * 1.75
D.box(sl, x0, 1.3, 1.55, 1.15, t, [b], fill=PALE_A if i % 2 == 0 else PALE_T, size=12.5, bsize=10)
if i < len(steps) - 1:
D.arrow(sl, x0 + 1.57, 1.88, x0 + 1.73, 1.88, width=2.5)
rows = [["규칙(안)", "이유"], ["상위 등급만 거래 가능", "저가 아이템 스팸·시세 붕괴 방지"], ["후가공(장식 등) 아이템은 거래 불가", "거래 대상 단순화 — 제거하면 거래 가능"],
["교역선 단계 경유 · 칸 제한", "거래량 통제 · 봇 속도 제한"], ["서버가 인벤토리의 원본", "복제·조작 차단(현금 가치)"], ["거래마다 수수료", "외부 매출원 [공개: Steam 수수료 구조 확인 필요]"]]
D.table(sl, 4.1, 2.75, 8.65, 3.4, rows, colw=[3.6, 5.0], size=11.5, hsize=12)
D.key_msg(sl, "드롭에 '현실 가치'가 붙는 순간 파밍 동기가 가장 강해진다 — 대신 통제 장치를 처음부터 설계", y=6.45)
# 14. ⑧ BM 재설계 --------------------------------------------------------------------------
sl = D.slide("⑧ BM — 에너지·광고·가챠 → DLC·치장·장터 수수료", "CHANGE 8 · BUSINESS MODEL", "근거: [FR] ShopTable 광고 무료 횟수·EventGachaTable·MapTable Energy·코스튬 6세트 · [TBH] 상점 DLC 번들·DLC 영웅 3·펫 3")
D.image(sl, FR("capture_07_shop_gacha.jpg"), 0.55, 1.2, 5.3, 2.98, frame=DARK)
D.caption(sl, 0.55, 4.21, 5.3, "[FR] 지금 — 가챠·광고 상점")
D.arrow(sl, 5.95, 2.7, 6.6, 2.7, width=4)
D.image(sl, TB("10_shop.jpg"), 6.7, 1.2, 2.3, 3.0, frame=DARK)
D.caption(sl, 6.7, 4.21, 2.3, "[TBH] DLC 번들 상점")
D.box(sl, 9.25, 1.2, 3.5, 1.45, "없앤다", ["에너지 · 광고 · 유료 랜덤 박스(가챠)"], fill=PALE_R, size=14, bsize=12)
D.box(sl, 9.25, 2.75, 3.5, 1.45, "판다", ["DLC 클래스·펫 · 치장(코스튬 재활용) · 시즌 치장"], fill=PALE_T, size=14, bsize=12)
D.bullets(sl, 0.55, 4.6, 12.2, 1.7, [
(0, [("에너지: ", {"bold": True}), ("상주 방치와 정면 충돌 — 켜둔 게임이 에너지 때문에 멈추면 안 된다", {})]),
(0, [("광고: ", {"bold": True}), ("PC 상주형에서 광고 시청 동선이 없다", {})]),
(0, [("가챠: ", {"bold": True}), ("유료 랜덤 결과물이 장터에서 현금 가치를 가지면 사행성 규제 위험 — 법률 검토 전까지 배제", {})])], size=12.5, gap=4)
D.key_msg(sl, "매출 축을 '확률 판매'에서 '콘텐츠(DLC)·치장 판매 + 거래 수수료'로 옮긴다", y=6.45)
# 15. ⑨ 서버·보안 ---------------------------------------------------------------------------
sl = D.slide("⑨ 서버·보안 — 현금 가치 경제의 선결 조건", "CHANGE 9 · SERVER & SECURITY", "근거: [FR] GameHub.cs:326-328 SelfSigned 무조건 통과 · GameHub local 분기 · [TBH] 안티치트·어뷰징 팝업·인벤토리 복구 UI · [공개] 출시 초 골드파밍·치터 기사")
checks = [("TLS 인증서 검증 복구", "지금 조건 없이 통과(중간자 공격 노출) — 가장 먼저 수정", RED),
("서버 권한 인벤토리·드롭 판정", "클라이언트는 표시만, 생성·삭제·거래는 서버가 결정", RED),
("Steam 인증·인벤토리 연동", "Google 로그인 → Steam 계정, 장터용 아이템 정의 등록", ACCENT),
("안티치트·봇 탐지·속도 제한", "비정상 획득량·자동 입력 탐지, 위반 시 거래 제한", ACCENT),
("거래·획득 감사 로그", "복제·롤백 사고 추적과 복구", TEAL)]
for i, (t, b, col) in enumerate(checks):
y0 = 1.25 + i * 1.0
D.rect(sl, 0.55, y0, 0.14, 0.82, fill=col)
D.text(sl, 0.8, y0 - 0.02, 7.2, 0.86, [(t, {"bold": True, "size": 15}), (b, {"size": 12, "color": GRAY})], size=12)
D.box(sl, 8.3, 1.25, 4.45, 4.8, "왜 먼저인가", ["아이템이 Steam 장터에서 돈이 되는 순간, 복제·조작은 곧 금전 피해가 된다", "",
"TBH도 안티치트 감지·장터 이용 제한·인벤토리 복구 장치를 따로 운영한다", "",
"출시 초 골드파밍·치터로 서버 장애 보도 [공개]"],
fill=PALE_R, size=15, bsize=12, align=PP_ALIGN.LEFT)
D.key_msg(sl, "경제를 여는 날보다 보안을 닫는 날이 먼저 — P2 착수 조건으로 둔다", y=6.3)
# 16. 유지·재활용 자산 ----------------------------------------------------------------------
sl = D.slide("그대로 쓰는 것 — 바꾸지 않는 우리 자산", "KEEP & REUSE", "근거: [FR] 시스템 분석(SpawnSystem 2,000 cap·DamageSystem 판정 4종·eGimmickType 8종·ChapterWave·Costume) · TestVisual 비주얼 개선")
imgs = [(FR("capture_01_wave_combat.jpg"), "ECS 대규모 물량 — 동시 2,000"), (FR("capture_02_battlefence.jpg"), "필드 기믹 8종 · 배틀펜스"), (FR("capture_05_item_costume.jpg"), "코스튬 6세트 → 치장 BM")]
for i, (p, c) in enumerate(imgs):
x0 = 0.55 + i * 4.1
D.image(sl, p, x0, 1.2, 3.95, 2.22, frame=DARK)
D.caption(sl, x0, 3.45, 3.95, "[FR 실캡처] " + c, color=TEXT, size=10.5)
keep = [("전투 연출", "타격 판정 4종 · 데미지 숫자"), ("웨이브 데이터", "ChapterWave 가중치 구조"), ("비주얼", "TestVisual 다크 판타지 렌더"),
("데이터 파이프라인", "JSON 테이블 89종"), ("오프라인 구동", "local 모드(서버 없이 테스트)")]
for i, (t, b) in enumerate(keep):
D.box(sl, 0.55 + i * 2.46, 3.95, 2.3, 1.55, t, [b], fill=PALE_T, size=13.5, bsize=11)
D.key_msg(sl, "보는 재미를 만드는 전투·연출·데이터는 재활용 — 새로 만드는 것은 '상주·파밍·거래' 층이다", y=5.8)
# 17. 로드맵 -------------------------------------------------------------------------------
sl = D.slide("로드맵 — 단계마다 게이트로 판단", "ROADMAP", "기간·인력은 개발팀 산정 후 확정 — 여기서는 순서와 게이트만 제안")
ph = [("P0", "상주 프로토타입", ["TestVisual 기반", "투명 창·스트립 카메라", "자동 전투·저전력 모드"], "게이트: 30분 상주 시 부하·가독성·'보는 재미' 사내 테스트", TEAL),
("P1", "파밍 코어", ["아이템화(등급·랜덤 옵션)", "클래스 3종·드롭", "순환 허브·방치 트리·오프라인"], "게이트: 사내 방치 플레이 3일 — 접속 동기·보관 압력 확인", BLUE),
("P2", "경제·보안", ["TLS·서버 권한 인벤토리", "Steam 인증·아이템 정의", "교역·안티치트·감사 로그"], "게이트: 법률 검토 완료 + 보안 점검 통과", RED),
("P3", "Steam 출시", ["체험판/앞서 해보기", "DLC 1차(클래스·펫)", "Steam 통계로 퍼널 계측"], "게이트: 체험판 지표로 정식 출시 판단", ACCENT)]
for i, (p, t, items, gate, col) in enumerate(ph):
x0 = 0.55 + i * 3.1
D.rect(sl, x0, 1.3, 2.95, 0.7, fill=col)
D.text(sl, x0 + 0.1, 1.32, 2.8, 0.66, [(p + " ", {"bold": True, "size": 18}), ], size=16, color=WHITE, anchor=MSO_ANCHOR.MIDDLE)
D.text(sl, x0 + 0.75, 1.36, 2.2, 0.6, t, size=15, bold=True, color=WHITE, anchor=MSO_ANCHOR.MIDDLE)
D.bullets(sl, x0, 2.15, 2.95, 1.9, items, size=12.5, gap=4)
D.box(sl, x0, 4.1, 2.95, 1.6, "게이트", [gate], fill=LIGHT, size=12.5, bsize=11, line=LINE)
if i < 3:
D.arrow(sl, x0 + 2.97, 1.65, x0 + 3.08, 1.65, width=3)
D.key_msg(sl, "P0만 먼저 승인 요청 — 상주 모드에서 3D 전투가 '보이고 가볍다'가 확인돼야 다음 단계가 의미 있다", y=5.95)
# 18. 리스크 -------------------------------------------------------------------------------
sl = D.slide("리스크와 대응", "RISKS", "법률 관련 항목은 전문가 검토 전 가정 — 결론 아님")
rows = [["리스크", "내용", "대응"],
["법·규제", "아이템 현금 거래·유료 확률형과의 결합(국내 법 해석 필요)", "P2 전 법률 검토 · 유료 랜덤 박스 배제"],
["경제 어뷰징", "봇 파밍·복제로 시세 붕괴", "서버 권한 · 거래 등급 제한 · 봇 탐지"],
["3D 가독성", "좁은 스트립에서 3D가 뭉개질 위험", "P0에서 카메라·실루엣·연출 검증"],
["상주 성능", "상시 실행 부하(현재 Draw 250~270)", "저전력 모드 · 스트립 영역만 렌더"],
["매출 공백", "모바일 BM 폐기 후 PC 매출 전까지", "P0·P1 게이트로 투자 단계화"],
["차별성", "TBH 아류로 보일 위험", "3D 다크 판타지·물량·타격감을 전면에"]]
D.table(sl, 0.55, 1.25, 12.2, 5.1, rows, colw=[1.8, 5.2, 5.2], size=12, hsize=12.5, col_colors=[None, None, TEAL])
# 19. 승인 요청 ----------------------------------------------------------------------------
sl = D.slide("승인 요청", "DECISION", "")
asks = [("1", "플랫폼 전환", "Steam PC(작업표시줄 상주) 우선 개발"), ("2", "모바일 BM 폐기", "에너지·광고·가챠 제거, DLC·치장·수수료로 전환"),
("3", "P0 프로토타입 착수", "TestVisual 기반 상주 모드 — 게이트 통과 시 P1 진행"), ("4", "법률 검토 착수", "Steam 장터 거래·아이템 현금 가치 관련")]
for i, (n, t, b) in enumerate(asks):
y0 = 1.3 + i * 1.18
D.rect(sl, 0.55, y0, 0.9, 1.0, fill=ACCENT, shape=MSO_SHAPE.ROUNDED_RECTANGLE, radius=0.12)
D.text(sl, 0.55, y0, 0.9, 1.0, n, size=30, bold=True, color=WHITE, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
D.text(sl, 1.65, y0 + 0.05, 10.5, 0.95, [(t, {"bold": True, "size": 20}), (b, {"size": 14, "color": GRAY})], size=14)
D.key_msg(sl, "승인 시 다음 단계: 개발팀 P0 범위·기간 산정 → 기획팀 P1 상세 설계(아이템화·순환 허브·방치 트리) 병행", y=6.2)
n = D.save(OUT)
print("saved", OUT, "slides:", n)