# 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)