---
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<id, float> | 몬스터별 가한 피해 (감소 후) |
| `damage_blocked_by_monster` | map<id, float> | 몬스터별 감소시킨 피해량 |
| `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 로그 항상 포함 → 토큰·디스크 낭비. 플래그 제어가 효율
