--- type: 설계문서 project: 수상한잡화점 subject: 시뮬레이션 결과 출력 JSON 포맷 version: v1 date: 2026-04-17 status: 초판 author: 개발팀장 --- # 결과 JSON 포맷 v1 ## 1. 목적 `SimulationRunner` 실행 결과의 **출력 포맷**. 기획팀이 후처리·비교·차트화할 때 구조화된 형태로 활용. 단일 실행·스윕·배치 모두 동일 스키마의 확장. ## 2. 단일 실행 결과 ```json { "schema_version": "1.0", "scenario_id": "anchor_stage1_no_card_4mob_touch", "run_id": "run_20260417_142301_0001", "timestamp": "2026-04-17T14:23:01Z", "seed": 42, "result": { "pc_survived": true, "pc_remaining_hp": 23, "pc_remaining_hp_ratio": 0.23, "total_turns": 142, "duration_sec": 14.2, "monsters_killed": 4, "monsters_killed_ids": ["m1", "m2", "m3", "m4"], "pc_damage_taken_total": 77, "pc_damage_blocked_total": 33, "pc_damage_dealt_total": 60, "defence_activations": 14, "defence_active_ratio": 0.42, "attacks_by_pc": 8, "attacks_by_pc_blocked_ratio": 0.0 }, "breakdown": { "damage_taken_by_monster": {"m1": 16, "m2": 20, "m3": 22, "m4": 19}, "damage_blocked_by_monster": {"m1": 7, "m2": 9, "m3": 10, "m4": 7}, "defence_duration_sec": 5.96 }, "detail_log_path": null, "errors": [] } ``` ## 3. 필드 정의 ### 3-1. 메타 | 필드 | 설명 | |------|------| | `schema_version` | 결과 스키마 버전 | | `scenario_id` | 입력 시나리오 ID echo | | `run_id` | 실행별 고유 ID (스윕·배치에서 중복 방지) | | `timestamp` | ISO 8601 UTC | | `seed` | 사용된 난수 시드 | ### 3-2. result (결과 요약 — 밸런싱 판단 핵심 축) | 필드 | 타입 | 설명 | |------|------|------| | `pc_survived` | bool | PC 생존 여부 | | `pc_remaining_hp` | float | 종료 시점 PC HP | | `pc_remaining_hp_ratio` | float | remaining_hp / max_hp | | `total_turns` | int | 실행된 tick 수 | | `duration_sec` | float | 시뮬 내부 경과 시간 (tick × interval) | | `monsters_killed` | int | 처치된 몬스터 수 | | `monsters_killed_ids` | string[] | 처치된 몬스터 ID 목록 | | `pc_damage_taken_total` | float | PC가 받은 총 피해 (감소 후) | | `pc_damage_blocked_total` | float | 방어로 감소된 총 피해량 (= 원본피해 - 실피해) | | `pc_damage_dealt_total` | float | PC가 입힌 총 피해 | | `defence_activations` | int | 방어 상태 진입 횟수 | | `defence_active_ratio` | float | 방어 상태 유지 시간 / 전체 시뮬 시간 | | `attacks_by_pc` | int | PC 공격 발동 횟수 | | `attacks_by_pc_blocked_ratio` | float | PC 공격이 방어로 인해 취소된 비율 (기획팀 분석용) | ### 3-3. breakdown (세부 분해) | 필드 | 타입 | 설명 | |------|------|------| | `damage_taken_by_monster` | map | 몬스터별 가한 피해 (감소 후) | | `damage_blocked_by_monster` | map | 몬스터별 감소시킨 피해량 | | `defence_duration_sec` | float | 누적 방어 유지 시간 | ### 3-4. detail_log_path (선택) - 입력의 `simulation.record_detail == true` 이면 tick별 상세 로그 파일 경로 - false면 `null` ### 3-5. errors (진단) - 시뮬 중 발생한 경고·비치명 에러 목록 (string[]) - 치명 에러는 예외 throw로 종료되므로 본 필드에 오지 않음 ## 4. 스윕 결과 ```json { "schema_version": "1.0", "sweep_id": "Defence_Mul_Grid", "sweep_axes": [ {"path": "global_value.PCDefence_Mul", "values": [0.2, 0.3, 0.4, 0.5]}, {"path": "monsters[0].attack_dmg", "values": [3, 5, 7]} ], "runs_per_cell": 10, "cells": [ { "coords": {"PCDefence_Mul": 0.2, "monster_attack_dmg": 3}, "runs": [ /* 결과 10개 */ ], "stats": { "pc_survived_ratio": 1.0, "pc_remaining_hp_mean": 62.3, "pc_remaining_hp_std": 8.1, "total_turns_mean": 98, "total_turns_std": 12 } } /* N × M cells */ ] } ``` 스윕 cell별 자동 집계 통계: - `pc_survived_ratio`: 생존률 - `pc_remaining_hp_mean` / `std`: 종료 HP 평균·표준편차 - `total_turns_mean` / `std`: tick 수 평균·표준편차 ## 5. 배치 결과 ```json { "schema_version": "1.0", "batch_id": "Anchor_Strategies_Compare", "results": [ {"scenario_id": "strategy_never", /* 단일 결과 §2 */}, {"scenario_id": "strategy_always", /* ... */}, {"scenario_id": "strategy_touch_hold", /* ... */} ], "comparison": { "best_by": "pc_remaining_hp_ratio", "ranking": ["strategy_touch_hold", "strategy_always", "strategy_never"], "deltas": { "strategy_touch_hold_vs_never": {"pc_remaining_hp_ratio": 0.42, "total_turns": 34} } } } ``` ## 6. 출력 위치 선택지 기획팀 결정 영역 (1차 응답서 §3-2 참조). 현 설계는 3가지 지원: | 모드 | 출력 방식 | 용도 | |------|----------|------| | `stdout` | 표준 출력 스트림 | MCP `execute_code` 응답으로 즉시 수신 | | `file` | `Assets/Sim/Output/{scenario_id}_{timestamp}.json` | Editor 내 기록 보존 | | `path` | 사용자 지정 경로 (프로젝트 외부 가능) | `기획팀/.cache/` 등으로 직접 저장 | `ResultEmitter.Emit(result, mode, pathOptional)` 시그니처. ## 7. 세부 로그 포맷 (record_detail=true 시) 별도 파일 `detail_log_path`가 가리키는 JSON (tick별 상태): ```json { "scenario_id": "...", "ticks": [ { "t": 0.0, "pc": {"hp": 100, "is_defencing": false, "attack_acc": 0.0}, "monsters": [{"id": "m1", "hp": 15, "attack_acc": 0.0}] }, { "t": 0.1, "pc": {"hp": 100, "is_defencing": true}, "events": ["monster_m1_projectile_spawned", "pc_defence_activated"] } ] } ``` **주의**: 상세 로그 파일은 tick당 수 KB이므로 `max_turns: 500` 시나리오에서 수 MB까지 가능. 평소 off 권장, 밸런스 디버깅 시점만 on. ## 8. 후처리 가이드 (기획팀) ### 8-1. 단일 실행 분석 - `pc_remaining_hp_ratio` 를 "체감 난이도" 지표로 활용 (0.5 이상 = 여유, 0.2 이하 = 타이트) - `defence_active_ratio` 가 과도히 높으면 (>0.6) 방어가 의도보다 강력함 신호 ### 8-2. 스윕 분석 - `pc_survived_ratio == 1.0` 이지만 `pc_remaining_hp_ratio` 편차가 큰 cell = 불안정 밸런스 - `stats.total_turns_std / mean > 0.3` = 난수 편차 과다 (시나리오 취약) ### 8-3. 배치 분석 - `comparison.ranking` 로 전략 우위 확인 - `deltas` 로 구체 수치 차이 측정 ## 9. 변경 이력 - **v1 (2026-04-17)**: 초판. ## 10. 기각안 - **기각안 A**: Markdown 결과 출력 → 후처리 자동화 곤란. JSON 구조화가 밸런싱 툴 호환성 최고 - **기각안 B**: Excel/CSV 결과 → 중첩 구조(breakdown/sweep cells) 표현 제한. JSON + 필요 시 CSV 변환이 유연 - **기각안 C**: tick 로그 항상 포함 → 토큰·디스크 낭비. 플래그 제어가 효율