SeaWolf-AI commited on
Commit
ee97e7d
ยท
verified ยท
1 Parent(s): 42eed24

Upload 9 files

Browse files
app.py ADDED
@@ -0,0 +1,1877 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ============================================================
2
+ # SOMA Pre-AGI ยท Metacognitive Document Engine
3
+ # Fireworks AI (Kimi K2) + Brave Search + ์ƒ์ƒ/์ƒ๊ทน ๋‹ค์ค‘ ์—์ด์ „ํŠธ
4
+ # ============================================================
5
+ import os, re, html, json, time, tempfile, threading
6
+ from dataclasses import dataclass, field
7
+ from datetime import datetime
8
+ from typing import List, Optional, Union, Generator
9
+
10
+ import requests
11
+ import gradio as gr
12
+
13
+ # โ”€โ”€ ํŒŒ์ผ ํŒŒ์‹ฑ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (์„ ํƒ์  ์ž„ํฌํŠธ) โ”€โ”€
14
+ import subprocess, sys, zlib, zipfile
15
+ from pathlib import Path
16
+
17
+ try:
18
+ import olefile
19
+ OLEFILE_AVAILABLE = True
20
+ except ImportError:
21
+ OLEFILE_AVAILABLE = False
22
+
23
+ try:
24
+ import pdfplumber
25
+ PDFPLUMBER_AVAILABLE = True
26
+ except ImportError:
27
+ PDFPLUMBER_AVAILABLE = False
28
+
29
+ try:
30
+ import PyPDF2
31
+ PYPDF2_AVAILABLE = True
32
+ except ImportError:
33
+ PYPDF2_AVAILABLE = False
34
+
35
+ PYHWP_PATH = os.environ.get("PYHWP_PATH", "/usr/local/lib/python3.10/site-packages")
36
+
37
+ # ============================================================
38
+ # โ‘  ํ™˜๊ฒฝ๋ณ€์ˆ˜ / ์ƒ์ˆ˜
39
+ # ============================================================
40
+ FIREWORKS_API_KEY = os.environ.get("FIREWORKS_API_KEY", "")
41
+ BRAVE_API_KEY = os.environ.get("BRAVE_API_KEY", "")
42
+
43
+ FIREWORKS_MODEL = "accounts/fireworks/models/kimi-k2p5"
44
+ FIREWORKS_URL = "https://api.fireworks.ai/inference/v1/chat/completions"
45
+ BRAVE_URL = "https://api.search.brave.com/res/v1/web/search"
46
+
47
+ PRIMARY_TEMPLATE = "nh_official_template.hml"
48
+ FALLBACK_TEMPLATE = "example.hml"
49
+ MAX_SEARCHES = 100 # Brave ์ตœ๋Œ€ ๊ฒ€์ƒ‰ ํšŸ์ˆ˜
50
+
51
+ # ============================================================
52
+ # โ‘ก SOMA ์—์ด์ „ํŠธ ์ •์˜ (๋ฉ”ํƒ€์ธ์ง€ ๊ธฐ๋ฐ˜)
53
+ # ============================================================
54
+ OHAENG = {
55
+ "ๆฐด": {"name":"RESEARCH","role":"ํƒ์ƒ‰ยท์ˆ˜์ง‘","color":"#38BDF8","emoji":"๐Ÿ”","desc":"์ •๋ณด๋ฅผ ํƒ์ƒ‰ํ•˜๊ณ  ์ •์ œํ•ฉ๋‹ˆ๋‹ค"},
56
+ "ๆœจ": {"name":"STRUCTURE","role":"์„ค๊ณ„ยท๊ตฌ์กฐ","color":"#4ADE80","emoji":"๐Ÿงฉ","desc":"๋ฌธ์„œ์˜ ๊ณจ๊ฒฉ์„ ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค"},
57
+ "็ซ": {"name":"ARGUMENT","role":"๋…ผ์ฆยท๊ฒ€์ฆ","color":"#FB7185","emoji":"๐Ÿ”ฌ","desc":"ํŒฉํŠธ๋ฅผ ๊ฒ€์ฆํ•˜๊ณ  ๋…ผ๋ฆฌ๋ฅผ ๋‹จ๋ จํ•ฉ๋‹ˆ๋‹ค"},
58
+ "ๅœŸ": {"name":"EVIDENCE","role":"ํ†ตํ•ฉยท์‹ค์ฒดํ™”","color":"#FBBF24","emoji":"๐Ÿ“","desc":"๋ชจ๋“  ์š”์†Œ๋ฅผ ํ•˜๋‚˜๋กœ ํ†ตํ•ฉํ•ฉ๋‹ˆ๋‹ค"},
59
+ "้‡‘": {"name":"IMPACT","role":"์ •์ œยท๋ฉ”ํƒ€์ธ์ง€","color":"#C084FC","emoji":"๐Ÿง ","desc":"์ž๊ธฐ๊ฒ€์ฆ์œผ๋กœ ์™„์„ฑ๋„๋ฅผ ๊ทน๋Œ€ํ™”ํ•ฉ๋‹ˆ๋‹ค"},
60
+ }
61
+ SANGSAENG = ["ๆฐด","ๆœจ","็ซ","ๅœŸ","้‡‘"] # ํŒŒ์ดํ”„๋ผ์ธ ์ˆœ์„œ
62
+ SANGGEUK = {"้‡‘":"ๆœจ","ๆœจ":"ๅœŸ","ๅœŸ":"ๆฐด","ๆฐด":"็ซ","็ซ":"้‡‘"} # ์ƒํ˜ธ ๊ฒ€์ฆ ๊ด€๊ณ„
63
+
64
+ # โ”€โ”€ ์ฐฝ๋ฐœ ๋งคํŠธ๋ฆญ์Šค ๋กœ๋“œ โ”€โ”€
65
+ EMERGENCE_MATRIX = {}
66
+ _em_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "emergence_matrix_document.json")
67
+ if os.path.exists(_em_path):
68
+ try:
69
+ with open(_em_path, 'r', encoding='utf-8') as _f:
70
+ EMERGENCE_MATRIX = json.load(_f)
71
+ print(f"โœ… Emergence Matrix ๋กœ๋“œ: {_em_path}")
72
+ except Exception as _e:
73
+ print(f"โš ๏ธ Emergence Matrix ๋กœ๋“œ ์‹คํŒจ: {_e}")
74
+ else:
75
+ print(f"โ„น๏ธ Emergence Matrix ํŒŒ์ผ ์—†์Œ: {_em_path}")
76
+
77
+ def _get_layer_prompt(layer_name: str) -> str:
78
+ """์ฐฝ๋ฐœ ๋งคํŠธ๋ฆญ์Šค์—์„œ ํ•ด๋‹น ๋ ˆ์ด์–ด์˜ ์ „๋žต์„ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜"""
79
+ if not EMERGENCE_MATRIX:
80
+ return ""
81
+ layers = EMERGENCE_MATRIX.get("layers", {})
82
+ layer = layers.get(layer_name)
83
+ if not layer:
84
+ return ""
85
+ parts = [f"\n[์ฐฝ๋ฐœ ๋งคํŠธ๋ฆญ์Šค โ€” {layer_name} ๋ ˆ์ด์–ด]"]
86
+ parts.append(f"์—ญํ• : {layer['desc']}")
87
+ for cat_name, items in layer.get("strategies", {}).items():
88
+ parts.append(f"\n<{cat_name}>")
89
+ for item in items:
90
+ parts.append(f" โ€ข {item}")
91
+ parts.append(f"</{cat_name}>")
92
+ # ๊ต์ฐจ ๋ ˆ์ด์–ด ๋ณด๋„ˆ์Šค ํžŒํŠธ
93
+ cross = EMERGENCE_MATRIX.get("cross_layer_bonus", {})
94
+ for key, val in cross.items():
95
+ if layer_name in key:
96
+ hint = val.get("hint", "") if isinstance(val, dict) else ""
97
+ bonus = val.get("bonus", 0) if isinstance(val, dict) else val
98
+ if hint:
99
+ parts.append(f"\n[๊ต์ฐจ ์ฐฝ๋ฐœ {key} (๋ณด๋„ˆ์Šค {bonus})] {hint}")
100
+ # ๋ฉ”ํƒ€์ธ์ง€ ํ”„๋กœํ† ์ฝœ
101
+ meta = EMERGENCE_MATRIX.get("metacognitive_protocols", {})
102
+ if meta.get("self_reflection"):
103
+ parts.append("\n[๋ฉ”ํƒ€์ธ์ง€ ์ž๊ธฐ๋ฐ˜์„ฑ]")
104
+ for q in meta["self_reflection"].get("questions", []):
105
+ parts.append(f" ? {q}")
106
+ return "\n".join(parts)
107
+
108
+ def _get_writing_principles() -> str:
109
+ """์ž‘์„ฑ ์›๋ฆฌ๋ฅผ ํ”„๋กฌํ”„ํŠธ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜"""
110
+ if not EMERGENCE_MATRIX:
111
+ return ""
112
+ principles = EMERGENCE_MATRIX.get("writing_principles", [])
113
+ if not principles:
114
+ return ""
115
+ parts = ["\n[์ฐฝ๋ฐœ์  ์ž‘์„ฑ ์›๋ฆฌ]"]
116
+ for p in principles:
117
+ parts.append(f" {p['id']}. {p['name']}: {p['hint']}")
118
+ return "\n".join(parts)
119
+
120
+ def _get_dilemmas() -> str:
121
+ """์ •์ฑ… ๋”œ๋ ˆ๋งˆ๋ฅผ ํ”„๋กฌํ”„ํŠธ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜"""
122
+ if not EMERGENCE_MATRIX:
123
+ return ""
124
+ dilemmas = EMERGENCE_MATRIX.get("policy_dilemmas", [])
125
+ if not dilemmas:
126
+ return ""
127
+ parts = ["\n[์ •์ฑ… ๋”œ๋ ˆ๋งˆ ํ”„๋ ˆ์ž„]"]
128
+ for d in dilemmas:
129
+ parts.append(f" โ€ข {d['dilemma']} โ†’ {d['resolution']}")
130
+ return "\n".join(parts)
131
+
132
+ AGENT_SYSTEM: dict[str,str] = {
133
+ "ๆฐด": f"""๋‹น์‹ ์€ SOMA Pre-AGI RESEARCH ์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค. ์—ญํ• : ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ๋ถ„์„ยท์ •๋ณด ์ •์ œ.
134
+
135
+ ์ œ๊ณต๋œ Brave Search ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ:
136
+ 1. ํ•ต์‹ฌ ํŒฉํŠธ ๋ฒˆํ˜ธ ๋ชฉ๋ก์œผ๋กœ ์ •๋ฆฌ
137
+ 2. ์ฃผ์š” ํ†ต๊ณ„ยท์ˆ˜์น˜ ๋ณ„๋„ ํ‘œ๊ธฐ
138
+ 3. ์ถœ์ฒ˜ ์‹ ๋ขฐ๋„ ํ‰๊ฐ€ (โ˜…โ˜†โ˜† ~ โ˜…โ˜…โ˜…)
139
+ 4. ์ถ”๊ฐ€ ๊ฒ€์ƒ‰์ด ํ•„์š”ํ•œ ํ‚ค์›Œ๋“œ๋ฅผ ๋ฐ˜๋“œ์‹œ ๋‹ค์Œ ํ˜•์‹์œผ๋กœ ์ œ์•ˆ:
140
+ [์ถ”๊ฐ€๊ฒ€์ƒ‰: "ํ‚ค์›Œ๋“œ1"], [์ถ”๊ฐ€๊ฒ€์ƒ‰: "ํ‚ค์›Œ๋“œ2"]
141
+
142
+ ๊ณต์‹ ๋ฌธ์„œ์šฉ์œผ๋กœ ๊ฒ€์ฆ ๊ฐ€๋Šฅํ•œ ์ •๋ณด๋งŒ ํฌํ•จํ•˜์„ธ์š”.
143
+ {_get_layer_prompt('RESEARCH')}""",
144
+
145
+ "ๆœจ": f"""๋‹น์‹ ์€ SOMA Pre-AGI STRUCTURE ์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค. ์—ญํ• : ๊ณต๋ฌธ์„œ ๊ตฌ์กฐ ์„ค๊ณ„.
146
+
147
+ ์ˆ˜์ง‘๋œ ์ •๋ณด๋กœ HWP ๊ณต๋ฌธ์„œ Markdown ๊ตฌ์กฐ๋ฅผ ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค:
148
+ - # ๋ฌธ์„œ ์ œ๋ชฉ (1๊ฐœ)
149
+ - ## โ… . ๊ฐœ์š” / โ…ก. ํ˜„ํ™ฉ / โ…ข. ์ถ”์ง„๋ฐฉํ–ฅ / โ…ฃ. ๊ธฐ๋Œ€ํšจ๊ณผ / โ…ค. ๊ฒฐ๋ก 
150
+ - ### ์†Œํ•ญ๋ชฉ
151
+ - | ํ‘œํ—ค๋” | ํ‘œํ—ค๋” | (ํ•„์š” ์‹œ)
152
+ - โ—‹ ๋ถˆ๋ฆฟ (ํ•ต์‹ฌ ์‚ฌํ•ญ)
153
+
154
+ ์ •๋ถ€ยท๊ณต๊ณต๊ธฐ๊ด€ ์ œ์ถœ ๋ฌธ์„œ ์Šคํƒ€์ผ์„ ์—„์ˆ˜ํ•˜์„ธ์š”.
155
+ {_get_layer_prompt('STRUCTURE')}
156
+ {_get_writing_principles()}""",
157
+
158
+ "็ซ": f"""๋‹น์‹ ์€ SOMA Pre-AGI ARGUMENT ์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค. ์—ญํ• : ํŒฉํŠธ์ฒดํฌยท๋…ผ์ฆ ๊ฒ€์ฆ.
159
+
160
+ STRUCTURE ์—์ด์ „ํŠธ๊ฐ€ ์ž‘์„ฑํ•œ ๊ตฌ์กฐ๋ฅผ RESEARCH ๋ฐ์ดํ„ฐ์™€ ๋Œ€์กฐํ•˜์—ฌ:
161
+ 1. [์˜ค๋ฅ˜] ํŒฉํŠธ ๋ถˆ์ผ์น˜ ํ•ญ๋ชฉ ์ง€์ 
162
+ 2. [๋ˆ„๋ฝ] ์ค‘์š” ์ •๋ณด ๋ˆ„๋ฝ ํ•ญ๋ชฉ ์ง€์ 
163
+ 3. [๋ณด๊ฐ•] ์ถ”๊ฐ€ ๊ฒ€์ƒ‰ ํ•„์š” ํ‚ค์›Œ๋“œ:
164
+ [์ถ”๊ฐ€๊ฒ€์ƒ‰: "ํ‚ค์›Œ๋“œ"]
165
+ 4. [์Šน์ธ] ๊ฒ€์ฆ๋œ ํ•ญ๋ชฉ ํ™•์ธ
166
+
167
+ ๋ฉ”ํƒ€์ธ์ง€์  ๋น„ํŒ์œผ๋กœ ๋‚ ์นด๋กญ๊ฒŒ ๊ฒ€์ฆํ•˜์„ธ์š”.
168
+ {_get_layer_prompt('ARGUMENT')}""",
169
+
170
+ "ๅœŸ": f"""๋‹น์‹ ์€ SOMA Pre-AGI EVIDENCE ์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค. ์—ญํ• : ์™„์„ฑ ๋ฌธ์„œ ํ†ตํ•ฉ ์ž‘์„ฑ.
171
+
172
+ ๋ชจ๋“  ์—์ด์ „ํŠธ ์ถœ๋ ฅ์„ ํ†ตํ•ฉํ•˜์—ฌ ์ตœ์ข… HWP ๋ฌธ์„œ๋ฅผ ์™„์„ฑํ•ฉ๋‹ˆ๋‹ค.
173
+
174
+ ๋ฐ˜๋“œ์‹œ ์ง€ํ‚ฌ Markdown ๊ทœ์น™:
175
+ - # ์ œ๋ชฉ (์ตœ์ƒ์œ„, 1๊ฐœ๋งŒ)
176
+ - ## โ… . ~ โ…ค. (์ฃผ์š” ์„น์…˜, ๋กœ๋งˆ์ˆซ์ž)
177
+ - ### 1. 2. 3. (์†Œํ•ญ๋ชฉ)
178
+ - | ์—ด1 | ์—ด2 | ์—ด3 |
179
+ |---|---|---|
180
+ | ๊ฐ’ | ๊ฐ’ | ๊ฐ’ |
181
+ - โ—‹ ํ•ต์‹ฌ ์‚ฌํ•ญ (๋ถˆ๋ฆฟ)
182
+ - ์ผ๋ฐ˜ ๋ณธ๋ฌธ (๋“ค์—ฌ์“ฐ๊ธฐ ์—†์Œ)
183
+
184
+ ๋ถ„๋Ÿ‰: 2,000์ž ์ด์ƒ์˜ ์™„์„ฑ๋„ ๋†’์€ ๊ณต๊ณต๊ธฐ๊ด€ ์ œ์ถœ์šฉ ๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”.
185
+ {_get_layer_prompt('EVIDENCE')}
186
+ {_get_dilemmas()}""",
187
+
188
+ "้‡‘": f"""๋‹น์‹ ์€ SOMA Pre-AGI IMPACT ์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค. ์—ญํ• : ์ตœ์ข… ๋น„ํ‰ยท๋ฉ”ํƒ€์ธ์ง€ ์ •์ œ.
189
+
190
+ EVIDENCE๊ฐ€ ์ž‘์„ฑํ•œ ๋ฌธ์„œ๋ฅผ ์ตœ์ข… ๊ฒ€ํ† ํ•˜๊ณ  ๊ฐœ์„ ํ•ฉ๋‹ˆ๋‹ค:
191
+ 1. ๋ฌธ์ฒด ์ผ๊ด€์„ฑ (๊ณต๋ฌธ์„œ์ฒด ์œ ์ง€)
192
+ 2. ์šฉ์–ด ์ •ํ™•์„ฑ (์ „๋ฌธ์šฉ์–ด ๊ฒ€์ฆ)
193
+ 3. ๋…ผ๋ฆฌ ํ๋ฆ„ (๋‘๊ด„์‹ ๊ตฌ์„ฑ)
194
+ 4. ํ‘œ ํ˜•์‹ ์™„์„ฑ๋„
195
+ 5. ๋ˆ„๋ฝ ํ•ญ๋ชฉ ๋ณด์™„
196
+
197
+ ๊ฒ€ํ†  ํ›„ ๋ฐ˜๋“œ์‹œ ===์ตœ์ข…๋ฌธ์„œ=== ๋งˆ์ปค์™€ ํ•จ๊ป˜ ์™„์„ฑ๋ณธ ์ „๋ฌธ์„ ์ถœ๋ ฅํ•˜์„ธ์š”.
198
+
199
+ ===์ตœ์ข…๋ฌธ์„œ===
200
+ [์™„์„ฑ๋œ ์ „์ฒด ๋ฌธ์„œ]
201
+
202
+ {_get_layer_prompt('IMPACT')}
203
+ {_get_writing_principles()}""",
204
+ }
205
+
206
+ # ============================================================
207
+ # โ‘ข Fireworks AI ํด๋ผ์ด์–ธํŠธ
208
+ # ============================================================
209
+ def fireworks_chat(
210
+ messages: list,
211
+ system_prompt: str,
212
+ max_tokens: int = 4096,
213
+ temperature: float = 0.6,
214
+ stream: bool = False,
215
+ ) -> Union[str, Generator]:
216
+ if not FIREWORKS_API_KEY:
217
+ raise ValueError("FIREWORKS_API_KEY ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
218
+
219
+ headers = {
220
+ "Accept": "application/json",
221
+ "Content-Type": "application/json",
222
+ "Authorization": f"Bearer {FIREWORKS_API_KEY}",
223
+ }
224
+ payload = {
225
+ "model": FIREWORKS_MODEL,
226
+ "max_tokens": max_tokens,
227
+ "top_p": 1,
228
+ "top_k": 40,
229
+ "presence_penalty": 0,
230
+ "frequency_penalty": 0,
231
+ "temperature": temperature,
232
+ "stream": stream,
233
+ "messages": [{"role": "system", "content": system_prompt}] + messages,
234
+ }
235
+
236
+ if not stream:
237
+ resp = requests.post(FIREWORKS_URL, headers=headers, json=payload, timeout=120)
238
+ resp.raise_for_status()
239
+ return resp.json()["choices"][0]["message"]["content"]
240
+
241
+ # โ”€โ”€ ์ŠคํŠธ๋ฆฌ๋ฐ ์ œ๋„ˆ๋ ˆ์ดํ„ฐ
242
+ def _gen():
243
+ resp = requests.post(FIREWORKS_URL, headers=headers, json=payload,
244
+ stream=True, timeout=180)
245
+ resp.raise_for_status()
246
+ for raw in resp.iter_lines():
247
+ if not raw:
248
+ continue
249
+ line = raw.decode("utf-8") if isinstance(raw, bytes) else raw
250
+ if not line.startswith("data: "):
251
+ continue
252
+ data = line[6:]
253
+ if data.strip() == "[DONE]":
254
+ break
255
+ try:
256
+ chunk = json.loads(data)
257
+ delta = chunk["choices"][0]["delta"].get("content", "")
258
+ if delta:
259
+ yield delta
260
+ except Exception:
261
+ pass
262
+ return _gen()
263
+
264
+
265
+ def fireworks_stream_collect(messages, system_prompt, max_tokens=4096, temperature=0.6) -> str:
266
+ """์ŠคํŠธ๋ฆฌ๋ฐ์œผ๋กœ ๋ฐ›์•„ ์ „์ฒด ํ…์ŠคํŠธ ๋ฐ˜ํ™˜"""
267
+ result = ""
268
+ for tok in fireworks_chat(messages, system_prompt, max_tokens, temperature, stream=True):
269
+ result += tok
270
+ return result
271
+
272
+
273
+ # ============================================================
274
+ # โ‘ฃ Brave Search ํด๋ผ์ด์–ธํŠธ
275
+ # ============================================================
276
+ @dataclass
277
+ class SearchResult:
278
+ title: str
279
+ url: str
280
+ description: str
281
+ source: str = ""
282
+
283
+
284
+ def brave_search(query: str, count: int = 10) -> List[SearchResult]:
285
+ if not BRAVE_API_KEY:
286
+ return [SearchResult(title="[APIํ‚ค ์—†์Œ]", url="", description="BRAVE_API_KEY ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.", source="")]
287
+
288
+ headers = {
289
+ "Accept": "application/json",
290
+ "Accept-Encoding": "gzip",
291
+ "X-Subscription-Token": BRAVE_API_KEY,
292
+ }
293
+ params = {"q": query, "count": min(count, 20), "search_lang": "ko", "country": "KR"}
294
+ try:
295
+ resp = requests.get(BRAVE_URL, headers=headers, params=params, timeout=15)
296
+ resp.raise_for_status()
297
+ data = resp.json()
298
+ results = []
299
+ for item in data.get("web", {}).get("results", []):
300
+ results.append(SearchResult(
301
+ title=item.get("title",""),
302
+ url=item.get("url",""),
303
+ description=item.get("description",""),
304
+ source=item.get("meta_url",{}).get("hostname",""),
305
+ ))
306
+ return results
307
+ except Exception as e:
308
+ return [SearchResult(title=f"๊ฒ€์ƒ‰ ์˜ค๋ฅ˜: {e}", url="", description="", source="")]
309
+
310
+
311
+ def extract_additional_search_queries(text: str) -> List[str]:
312
+ """์—์ด์ „ํŠธ ์ถœ๋ ฅ์—์„œ [์ถ”๊ฐ€๊ฒ€์ƒ‰: "..."] ํŒจํ„ด ์ถ”์ถœ"""
313
+ return re.findall(r'\[์ถ”๊ฐ€๊ฒ€์ƒ‰:\s*["\']([^"\']+)["\']', text)
314
+
315
+
316
+ def format_search_results(results: List[SearchResult]) -> str:
317
+ if not results:
318
+ return "๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์—†์Œ"
319
+ lines = []
320
+ for i, r in enumerate(results, 1):
321
+ lines.append(f"{i}. **{r.title}**")
322
+ if r.source:
323
+ lines.append(f" ์ถœ์ฒ˜: {r.source}")
324
+ if r.description:
325
+ lines.append(f" ๋‚ด์šฉ: {r.description[:200]}")
326
+ lines.append("")
327
+ return "\n".join(lines)
328
+
329
+
330
+ # ============================================================
331
+ # โ‘ค HWP ํ…œํ”Œ๋ฆฟ ์—”์ง„ (์ด์ „ ๋ฒ„์ „ ์œ ์ง€)
332
+ # ============================================================
333
+ def xml_escape(text: str) -> str:
334
+ return (text.replace("&","&amp;").replace("<","&lt;")
335
+ .replace(">","&gt;").replace('"',"&quot;").replace("'","&apos;"))
336
+
337
+ def strip_tags(text: str) -> str:
338
+ return re.sub(r"<[^>]+>","",text,flags=re.DOTALL)
339
+
340
+ def inner_text_from_p(p_xml: str) -> str:
341
+ text = re.sub(r"</?CHAR[^>]*>","",p_xml,flags=re.DOTALL)
342
+ text = strip_tags(text)
343
+ text = html.unescape(text)
344
+ return re.sub(r"\s+"," ",text).strip()
345
+
346
+ def get_attr(xml_snippet: str, name: str, default: str = "") -> str:
347
+ m = re.search(rf'{name}="([^"]*)"', xml_snippet)
348
+ return m.group(1) if m else default
349
+
350
+ def normalize_text_for_title(text: str) -> str:
351
+ for line in text.splitlines():
352
+ s = line.strip()
353
+ if not s: continue
354
+ if s.startswith("# "): return s[2:].strip()[:60]
355
+ return s[:60]
356
+ return "๋ฌธ์„œ"
357
+
358
+ def is_table_line(line: str) -> bool:
359
+ l = line.strip()
360
+ return l.startswith("|") and l.endswith("|") and l.count("|") >= 2
361
+
362
+ @dataclass
363
+ class ParagraphBlock:
364
+ text: str
365
+ kind: str
366
+
367
+ @dataclass
368
+ class TableBlock:
369
+ headers: List[str]
370
+ rows: List[List[str]]
371
+
372
+ Block = Union[ParagraphBlock, TableBlock]
373
+
374
+ def parse_table(lines, start_idx):
375
+ buf, i = [], start_idx
376
+ while i < len(lines) and is_table_line(lines[i]):
377
+ buf.append(lines[i].strip()); i += 1
378
+ if len(buf) < 2: return None, start_idx
379
+ def split_row(row): return [c.strip() for c in row.strip("|").split("|")]
380
+ header = split_row(buf[0])
381
+ raw = buf[1:]
382
+ if raw and re.fullmatch(r"[\|\-\:\s]+", raw[0]): raw = raw[1:]
383
+ rows = [split_row(r) for r in raw]
384
+ if not header: return None, start_idx
385
+ cc = len(header)
386
+ norm = [(r+[""]*(cc-len(r)))[:cc] for r in rows]
387
+ return TableBlock(headers=header, rows=norm), i
388
+
389
+ def parse_document_blocks(text: str) -> List[Block]:
390
+ lines = text.replace("\r\n","\n").replace("\r","\n").split("\n")
391
+ blocks, para_buf = [], []
392
+
393
+ def flush():
394
+ nonlocal para_buf
395
+ if para_buf:
396
+ merged = " ".join(x.strip() for x in para_buf if x.strip()).strip()
397
+ if merged: blocks.append(ParagraphBlock(merged,"paragraph"))
398
+ para_buf = []
399
+
400
+ i = 0
401
+ while i < len(lines):
402
+ line = lines[i].rstrip()
403
+ if not line.strip(): flush(); i+=1; continue
404
+ if is_table_line(line):
405
+ flush()
406
+ tb,ni = parse_table(lines,i)
407
+ if tb: blocks.append(tb); i=ni; continue
408
+ s = line.strip()
409
+ if s.startswith("# "): flush(); blocks.append(ParagraphBlock(s[2:].strip(),"title")); i+=1; continue
410
+ if s.startswith("## "): flush(); blocks.append(ParagraphBlock(s[3:].strip(),"section")); i+=1; continue
411
+ if s.startswith("### "): flush(); blocks.append(ParagraphBlock(s[4:].strip(),"subsection")); i+=1; continue
412
+ if re.match(r"^[โ… โ…กโ…ขโ…ฃโ…คโ…ฅโ…ฆโ…งโ…จโ…ฉ]+\.\s*",s): flush(); blocks.append(ParagraphBlock(s,"section")); i+=1; continue
413
+ if re.match(r"^\d+\.\s*",s): flush(); blocks.append(ParagraphBlock(s,"subsection")); i+=1; continue
414
+ if s.startswith(("- ","* ","โ€ข ","โ—‹ ")):
415
+ flush()
416
+ bt = re.sub(r"^(-|\*|โ€ข|โ—‹)\s+","",s).strip()
417
+ blocks.append(ParagraphBlock(bt,"bullet")); i+=1; continue
418
+ para_buf.append(s); i+=1
419
+ flush()
420
+ return blocks
421
+
422
+ @dataclass
423
+ class ParagraphStyleRef:
424
+ para_shape: str; char_shape: str; style: str = "0"
425
+
426
+ @dataclass
427
+ class TableStyleRef:
428
+ table_borderfill: str
429
+ header_borderfills: List[str]
430
+ body_borderfills: List[str]
431
+ header_para_shape: str; header_char_shape: str
432
+ body_para_shape: str; body_char_shape: str
433
+ cell_margin_top: str = "141"; cell_margin_bottom: str = "141"
434
+ cell_margin_left: str = "510"; cell_margin_right: str = "510"
435
+
436
+ class HWPTemplateEngine:
437
+ def __init__(self, template_hml_path: str):
438
+ if not os.path.exists(template_hml_path):
439
+ raise FileNotFoundError(f"ํ…œํ”Œ๋ฆฟ ์—†์Œ: {template_hml_path}")
440
+ self.raw = self._read(template_hml_path)
441
+ self.xml_decl = self._xml_decl(self.raw)
442
+ self.root_open = self._root_open(self.raw)
443
+ self.head_xml = self._head(self.raw)
444
+ self.tail_xml = self._tail(self.raw)
445
+ self.section_setup = self._section_setup(self.raw)
446
+ self.title_style = ParagraphStyleRef("0","0","0")
447
+ self.section_style = ParagraphStyleRef("0","0","0")
448
+ self.subsect_style = ParagraphStyleRef("0","0","0")
449
+ self.body_style = ParagraphStyleRef("0","0","0")
450
+ self.bullet_style = ParagraphStyleRef("0","0","0")
451
+ self.table_style = TableStyleRef("1",["1"],["1"],"0","0","0","0")
452
+ self._table_center_ps = "0"
453
+ self._extract_styles()
454
+
455
+ @staticmethod
456
+ def _read(path):
457
+ for enc in ("utf-8-sig","utf-16","cp949","utf-8"):
458
+ try:
459
+ with open(path,"r",encoding=enc) as f: return f.read()
460
+ except: pass
461
+ with open(path,"r",encoding="utf-8",errors="ignore") as f: return f.read()
462
+
463
+ @staticmethod
464
+ def _xml_decl(raw):
465
+ m = re.match(r"\s*(<\?xml[^>]+\?>)",raw)
466
+ return m.group(1) if m else '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>'
467
+
468
+ @staticmethod
469
+ def _root_open(raw):
470
+ m = re.search(r"(<HWPML\b[^>]*>)",raw)
471
+ if not m: raise ValueError("<HWPML> ํƒœ๊ทธ ์—†์Œ")
472
+ return m.group(1)
473
+
474
+ @staticmethod
475
+ def _head(raw):
476
+ m = re.search(r"(<HEAD\b.*?</HEAD>)",raw,flags=re.DOTALL)
477
+ if not m: raise ValueError("<HEAD> ์—†์Œ")
478
+ return m.group(1)
479
+
480
+ @staticmethod
481
+ def _tail(raw):
482
+ m = re.search(r"(<TAIL>.*?</TAIL>)",raw,flags=re.DOTALL)
483
+ return m.group(1) if m else ""
484
+
485
+ @staticmethod
486
+ def _section_setup(raw):
487
+ """SECDEF ์š”์†Œ๋งŒ ์ถ”์ถœํ•˜์—ฌ ์ตœ์†Œ P ํƒœ๊ทธ๋กœ ๊ฐ์‹ธ ๋ฐ˜ํ™˜.
488
+ ํ…œํ”Œ๋ฆฟ์˜ ํ‘œ์ง€ ์ฝ˜ํ…์ธ (๋กœ๊ณ , ์ด๋ฏธ์ง€, ๋ชฉ์ฐจ ๋“ฑ)๋ฅผ ์ œ์™ธํ•˜๊ณ 
489
+ ํŽ˜์ด์ง€ ์„ค์ •(์šฉ์ง€, ์—ฌ๋ฐฑ, ๋จธ๋ฆฌ๋ง/๊ผฌ๋ฆฌ๋ง ๋“ฑ)๋งŒ ๊ฐ€์ ธ์˜ด."""
490
+ import xml.etree.ElementTree as _ET
491
+ try:
492
+ root = _ET.fromstring(raw)
493
+ except _ET.ParseError:
494
+ return ""
495
+ section = root.find('BODY')
496
+ if section is None: return ""
497
+ section = section.find('SECTION')
498
+ if section is None: return ""
499
+ first_p = list(section)[0] if len(list(section)) > 0 else None
500
+ if first_p is None: return ""
501
+ # SECDEF ์š”์†Œ ํƒ์ƒ‰
502
+ for text_elem in first_p.findall('TEXT'):
503
+ secdef = text_elem.find('SECDEF')
504
+ if secdef is not None:
505
+ secdef_str = _ET.tostring(secdef, encoding='unicode')
506
+ ps = first_p.get('ParaShape', '0')
507
+ st = first_p.get('Style', '0')
508
+ cs = text_elem.get('CharShape', '0')
509
+ return (f'<P ParaShape="{ps}" Style="{st}">'
510
+ f'<TEXT CharShape="{cs}">{secdef_str}'
511
+ f'<CHAR/></TEXT></P>')
512
+ return ""
513
+
514
+ def _extract_styles(self):
515
+ """ํ…œํ”Œ๋ฆฟ์—์„œ ์Šคํƒ€์ผ์„ ์ถ”์ถœํ•˜๋˜, XML ํŒŒ์„œ๋กœ Align/Font ํฌ๊ธฐ๋ฅผ ๊ฒ€์ฆ"""
516
+ import xml.etree.ElementTree as _ET
517
+ try:
518
+ root = _ET.fromstring(self.raw)
519
+ except _ET.ParseError:
520
+ self._extract_table()
521
+ return
522
+
523
+ mt = root.find('HEAD')
524
+ if mt is not None:
525
+ mt = mt.find('MAPPINGTABLE')
526
+
527
+ # ParaShape/CharShape ๋ฃฉ์—… ํ…Œ์ด๋ธ”
528
+ ps_info = {} # id -> {align, left, indent}
529
+ cs_info = {} # id -> {height, bold}
530
+ _ps_el = None
531
+ _cs_el = None
532
+ if mt is not None:
533
+ _ps_el = mt.find('PARASHAPELIST')
534
+ _cs_el = mt.find('CHARSHAPELIST')
535
+ for ps in (_ps_el if _ps_el is not None else []):
536
+ pid = ps.get('Id', '0')
537
+ m = ps.find('PARAMARGIN')
538
+ ps_info[pid] = {
539
+ 'align': ps.get('Align', 'Justify'),
540
+ 'left': int(m.get('Left', '0')) if m is not None else 0,
541
+ 'indent': int(m.get('Indent', '0')) if m is not None else 0,
542
+ }
543
+ for cs in (_cs_el if _cs_el is not None else []):
544
+ cid = cs.get('Id', '0')
545
+ cs_info[cid] = {
546
+ 'height': int(cs.get('Height', '0')),
547
+ 'bold': cs.get('Bold', 'false'),
548
+ }
549
+
550
+ def is_valid_style(ps_id, cs_id, allow_center=False):
551
+ """Center ์ •๋ ฌ, ์Œ์ˆ˜ Left, ๊ณผ๋„ํ•œ ์Œ์ˆ˜ Indent, ๋น„์ •์ƒ ํฐํŠธ ํฌ๊ธฐ ํ•„ํ„ฐ"""
552
+ ps = ps_info.get(ps_id, {})
553
+ cs = cs_info.get(cs_id, {})
554
+ if not allow_center and ps.get('align') == 'Center':
555
+ return False
556
+ if ps.get('left', 0) < 0:
557
+ return False
558
+ if ps.get('indent', 0) < -7000:
559
+ return False
560
+ h = cs.get('height', 0)
561
+ if h < 800 or h > 2000: # 8pt~20pt ๋ฒ”์œ„๋งŒ ํ—ˆ์šฉ
562
+ return False
563
+ return True
564
+
565
+ # ๋ณธ๋ฌธ P ํƒœ๊ทธ์—์„œ ์Šคํƒ€์ผ ์ˆ˜์ง‘ (TABLE/PICTURE ๋‚ด๋ถ€ ์ œ์™ธ)
566
+ section = root.find('BODY')
567
+ if section is not None:
568
+ section = section.find('SECTION')
569
+ if section is None:
570
+ self._extract_table()
571
+ return
572
+
573
+ ex = []
574
+ for p_elem in list(section):
575
+ p_str = _ET.tostring(p_elem, encoding='unicode')
576
+ if '<SECDEF' in p_str:
577
+ continue
578
+ if '<TABLE' in p_str or '<PICTURE' in p_str:
579
+ continue
580
+ # ์ตœ์ƒ์œ„ P๋งŒ (์ˆœ์ˆ˜ ํ…์ŠคํŠธ ๋ฌธ๋‹จ)
581
+ ps = p_elem.get('ParaShape', '0')
582
+ st = p_elem.get('Style', '0')
583
+ first_text = p_elem.find('TEXT')
584
+ cs = first_text.get('CharShape', '0') if first_text is not None else '0'
585
+ text = ''.join(p_elem.itertext()).strip()
586
+ if not text or '๊ทธ๋ฆผ์ž…๋‹ˆ๋‹ค' in text:
587
+ continue
588
+ ex.append({"text": text, "para_shape": ps, "char_shape": cs, "style": st})
589
+
590
+ def pick_validated(*patterns, allow_center=False):
591
+ for p in patterns:
592
+ for it in ex:
593
+ if re.search(p, it["text"]):
594
+ if is_valid_style(it["para_shape"], it["char_shape"], allow_center):
595
+ return it
596
+ return None
597
+
598
+ def apply(ref_attr, item):
599
+ if item:
600
+ setattr(self, ref_attr, ParagraphStyleRef(
601
+ item["para_shape"], item["char_shape"], item["style"]))
602
+
603
+ # ์ œ๋ชฉ: โ–ก ์„น์…˜ ํ—ค๋” ์Šคํƒ€์ผ ํ™œ์šฉ
604
+ apply("title_style", pick_validated(
605
+ r"^โ–ก\s", # โ–ก ์‚ฌ๊ฐ๋ถˆ๋ฆฟ (์„น์…˜ ํ—ค๋”๊ธ‰)
606
+ r"^[โ… โ…กโ…ขโ…ฃโ…ค]",
607
+ allow_center=False))
608
+ # ์„น์…˜ (โ… . โ…ก.): โ–ก ์Šคํƒ€์ผ
609
+ apply("section_style", pick_validated(
610
+ r"^โ–ก\s",
611
+ r"^[โ… โ…กโ…ขโ…ฃโ…ค]",
612
+ allow_center=False))
613
+ # ์†Œํ•ญ๋ชฉ (1. 2.): ์ˆซ์ž+์ +ํ•œ๊ธ€ (๋‚ ์งœ "2026. 2." ๋งค์นญ ๋ฐฉ์ง€)
614
+ apply("subsect_style", pick_validated(
615
+ r"^[1-9]\.\s*[๊ฐ€-ํžฃ\w]", # "1. ์‚ฌ์—…" ํ˜•ํƒœ๋งŒ ๋งค์นญ
616
+ r"^\(\d+\)",
617
+ allow_center=False))
618
+ # ๋ถˆ๋ฆฟ (โ—‹)
619
+ apply("bullet_style", pick_validated(r"^โ—‹\s", r"^โ—‹"))
620
+ # ๋ณธ๋ฌธ/์†Œํ•ญ๋ชฉ: ๋ถˆ๋ฆฟ๊ณผ ๋™์ผ ์ค„๊ฐ„๊ฒฉ์ด๋˜ Indent=0 (์ค„๋ฐ”๊ฟˆ ์‹œ ๋“ค์—ฌ์“ฐ๊ธฐ ๋ฐฉ์ง€)
621
+ # ๋ถˆ๋ฆฟ์˜ LineSpacing์„ ์ฐพ๊ณ , ๊ฐ™์€ LS + Indent=0์ธ ParaShape ํƒ์ƒ‰
622
+ bullet_ls = None
623
+ if self.bullet_style.para_shape != "0":
624
+ for ps in (_ps_el if _ps_el is not None else []):
625
+ if ps.get('Id') == self.bullet_style.para_shape:
626
+ bm = ps.find('PARAMARGIN')
627
+ bullet_ls = bm.get('LineSpacing') if bm is not None else None
628
+ break
629
+
630
+ body_ps = "0" # ํด๋ฐฑ
631
+ if bullet_ls:
632
+ for ps in (_ps_el if _ps_el is not None else []):
633
+ pm = ps.find('PARAMARGIN')
634
+ if pm is None: continue
635
+ if (ps.get('Align') in ('Justify', 'Left') and
636
+ pm.get('Left', '0') == '0' and
637
+ pm.get('Indent', '0') == '0' and
638
+ pm.get('LineSpacing') == bullet_ls):
639
+ body_ps = ps.get('Id')
640
+ break
641
+
642
+ if self.bullet_style.char_shape != "0":
643
+ self.body_style = ParagraphStyleRef(body_ps, self.bullet_style.char_shape, "0")
644
+ self.subsect_style = ParagraphStyleRef(body_ps, self.bullet_style.char_shape, "0")
645
+ self.bullet_style = ParagraphStyleRef(body_ps, self.bullet_style.char_shape, "0")
646
+
647
+ # ์ œ๋ชฉ/์„น์…˜๋„ Indent=0 ParaShape ์‚ฌ์šฉ (์ค„๋ฐ”๊ฟˆ ์‹œ ๋“ค์—ฌ์“ฐ๊ธฐ ๋ฐฉ์ง€)
648
+ # CharShape๋Š” ๊ธฐ์กด ์ถ”์ถœ๊ฐ’(HYํ—ค๋“œ๋ผ์ธM ๋“ฑ) ์œ ์ง€
649
+ title_cs = self.title_style.char_shape if self.title_style.char_shape != "0" else self.bullet_style.char_shape
650
+ section_cs = self.section_style.char_shape if self.section_style.char_shape != "0" else self.bullet_style.char_shape
651
+ self.title_style = ParagraphStyleRef(body_ps, title_cs, "0")
652
+ self.section_style = ParagraphStyleRef(body_ps, section_cs, "0")
653
+
654
+ # ํ‘œ ์…€์šฉ Center ์ •๋ ฌ ParaShape ํƒ์ƒ‰
655
+ self._table_center_ps = "0"
656
+ if bullet_ls:
657
+ for ps in (_ps_el if _ps_el is not None else []):
658
+ pm = ps.find('PARAMARGIN')
659
+ if pm is None: continue
660
+ if (ps.get('Align') == 'Center' and
661
+ pm.get('Left', '0') == '0' and
662
+ pm.get('Indent', '0') == '0' and
663
+ pm.get('LineSpacing') == bullet_ls):
664
+ self._table_center_ps = ps.get('Id')
665
+ break
666
+ # ํด๋ฐฑ: ๊ฐ™์€ LS ์—†์œผ๋ฉด ์•„๋ฌด Center+Indent=0
667
+ if self._table_center_ps == "0":
668
+ for ps in (_ps_el if _ps_el is not None else []):
669
+ pm = ps.find('PARAMARGIN')
670
+ if pm is None: continue
671
+ if (ps.get('Align') == 'Center' and
672
+ pm.get('Left', '0') == '0' and
673
+ pm.get('Indent', '0') == '0'):
674
+ self._table_center_ps = ps.get('Id')
675
+ break
676
+
677
+ # ํด๋ฐฑ: ๋ฏธ๋งค์นญ ์Šคํƒ€์ผ์„ ํ•ฉ๋ฆฌ์  ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ
678
+ bs = self.body_style
679
+ if self.title_style.para_shape == "0" and self.title_style.char_shape == "0":
680
+ self.title_style = bs
681
+ if self.section_style.para_shape == "0" and self.section_style.char_shape == "0":
682
+ self.section_style = bs
683
+ if self.subsect_style.para_shape == "0" and self.subsect_style.char_shape == "0":
684
+ self.subsect_style = bs
685
+
686
+ self._extract_table()
687
+
688
+ def _extract_table(self):
689
+ """ํ…œํ”Œ๋ฆฟ์—์„œ ๋ฐ์ดํ„ฐ ํ‘œ ์Šคํƒ€์ผ ์ถ”์ถœ (๋ ˆ์ด์•„์›ƒ ํ‘œ ์ œ์™ธ)"""
690
+ import xml.etree.ElementTree as _ET
691
+ try:
692
+ root = _ET.fromstring(self.raw)
693
+ except _ET.ParseError:
694
+ return
695
+ # 2์—ด ์ด์ƒ, 2ํ–‰ ์ด์ƒ์ธ ๋ฐ์ดํ„ฐ ํ‘œ ์ฐพ๊ธฐ
696
+ for tbl in root.iter('TABLE'):
697
+ cc = int(tbl.get('ColCount', '0'))
698
+ rc = int(tbl.get('RowCount', '0'))
699
+ if cc < 2 or rc < 2:
700
+ continue
701
+ bf = tbl.get('BorderFill', '3')
702
+ rows = list(tbl.findall('ROW'))
703
+ if len(rows) < 2:
704
+ continue
705
+ hc = list(rows[0].findall('CELL'))
706
+ bc = list(rows[1].findall('CELL'))
707
+ if not hc or not bc:
708
+ continue
709
+
710
+ def cell_style(cell_elem):
711
+ pl = cell_elem.find('PARALIST')
712
+ if pl is None: return ("0", "0")
713
+ p = pl.find('P')
714
+ if p is None: return ("0", "0")
715
+ ps = p.get('ParaShape', '0')
716
+ t = p.find('TEXT')
717
+ cs = t.get('CharShape', '0') if t is not None else '0'
718
+ return (ps, cs)
719
+
720
+ hp, hch = cell_style(hc[0])
721
+ bp, bch = cell_style(bc[0])
722
+ fc = hc[0]
723
+ cm = fc.find('CELLMARGIN')
724
+
725
+ self.table_style = TableStyleRef(
726
+ bf,
727
+ [c.get('BorderFill', '3') for c in hc] or ["3"],
728
+ [c.get('BorderFill', '3') for c in bc] or ["3"],
729
+ hp, hch, bp, bch,
730
+ cm.get('Top', '141') if cm is not None else '141',
731
+ cm.get('Bottom', '141') if cm is not None else '141',
732
+ cm.get('Left', '510') if cm is not None else '510',
733
+ cm.get('Right', '510') if cm is not None else '510',
734
+ )
735
+ return
736
+
737
+ def _p(self,text,s: ParagraphStyleRef):
738
+ clean = re.sub(r'\*\*([^*]+)\*\*', r'\1', text) # ** ๋งˆํฌ๋‹ค์šด ๋ณผ๋“œ ์ œ๊ฑฐ
739
+ e = xml_escape(clean)
740
+ return f'<P ParaShape="{s.para_shape}" Style="{s.style}"><TEXT CharShape="{s.char_shape}"><CHAR>{e}</CHAR></TEXT></P>'
741
+
742
+ def _bullet(self,text):
743
+ clean = re.sub(r'\*\*([^*]+)\*\*', r'\1', text) # ** ์ œ๊ฑฐ
744
+ e = xml_escape(f"โ—‹ {clean}"); s = self.bullet_style
745
+ return f'<P ParaShape="{s.para_shape}" Style="{s.style}"><TEXT CharShape="{s.char_shape}"><CHAR>{e}</CHAR></TEXT></P>'
746
+
747
+ def _table(self,block: TableBlock):
748
+ cc = max(1,len(block.headers)); rc = 1+len(block.rows)
749
+ tw = 44790; ws = [tw//cc]*cc; ws[-1] += tw-sum(ws)
750
+ ts = self.table_style
751
+
752
+ def clean_cell_text(text):
753
+ """ํ‘œ ์…€ ํ…์ŠคํŠธ ์ •๋ฆฌ: ** ์ œ๊ฑฐ, <br> โ†’ ์ค„๋ฐ”๊ฟˆ, โ€ข ์•ž ์ค„๋ฐ”๊ฟˆ"""
754
+ t = re.sub(r'\*\*([^*]+)\*\*', r'\1', text) # ** ์ œ๊ฑฐ
755
+ t = re.sub(r'<br\s*/?>', '\n', t) # <br> โ†’ ์ค„๋ฐ”๊ฟˆ
756
+ t = re.sub(r'(?<!\n)โ€ข', '\nโ€ข', t) # โ€ข ์•ž์— ์ค„๋ฐ”๊ฟˆ (์ด๋ฏธ ์žˆ์œผ๋ฉด ์Šคํ‚ต)
757
+ t = t.strip()
758
+ return t
759
+
760
+ def cell(text,ri,ci,hdr):
761
+ if hdr:
762
+ bf = ts.header_borderfills[0]
763
+ else:
764
+ bfs = ts.body_borderfills
765
+ bf = bfs[min(ci,len(bfs)-1)]
766
+ cs = ts.header_char_shape if hdr else ts.body_char_shape
767
+ cleaned = clean_cell_text(text)
768
+
769
+ # ์…€ ๋‚ด๋ถ€ P ํƒœ๊ทธ: ๊ฐ€์šด๋ฐ ์ •๋ ฌ PS ์‚ฌ์šฉ, ์—ฌ๋Ÿฌ ์ค„์ด๋ฉด ์—ฌ๋Ÿฌ P
770
+ lines = cleaned.split('\n')
771
+ p_tags = []
772
+ for line in lines:
773
+ line = line.strip()
774
+ if line:
775
+ e = xml_escape(line)
776
+ p_tags.append(f'<P ParaShape="{self._table_center_ps}" Style="0">'
777
+ f'<TEXT CharShape="{cs}"><CHAR>{e}</CHAR></TEXT></P>')
778
+ if not p_tags:
779
+ p_tags.append(f'<P ParaShape="{self._table_center_ps}" Style="0">'
780
+ f'<TEXT CharShape="{cs}"><CHAR> </CHAR></TEXT></P>')
781
+
782
+ return (f'<CELL BorderFill="{bf}" ColAddr="{ci}" ColSpan="1" Dirty="false" Editable="false"'
783
+ f' HasMargin="true" Header="{"true" if hdr else "false"}" Height="1800"'
784
+ f' Protect="false" RowAddr="{ri}" RowSpan="1" Width="{ws[ci]}">'
785
+ f'<CELLMARGIN Bottom="{ts.cell_margin_bottom}" Left="{ts.cell_margin_left}"'
786
+ f' Right="{ts.cell_margin_right}" Top="{ts.cell_margin_top}"/>'
787
+ f'<PARALIST LineWrap="Break" LinkListID="0" LinkListIDNext="0" TextDirection="0" VertAlign="Center">'
788
+ f'{"".join(p_tags)}'
789
+ f'</PARALIST></CELL>')
790
+
791
+ rows_xml = ["<ROW>" + "".join(cell(block.headers[i],0,i,True) for i in range(cc)) + "</ROW>"]
792
+ for ri,row in enumerate(block.rows,1):
793
+ nr = (row+[""]*(cc-len(row)))[:cc]
794
+ rows_xml.append("<ROW>" + "".join(cell(nr[c],ri,c,False) for c in range(cc)) + "</ROW>")
795
+
796
+ bs = self.body_style
797
+ return (f'<P ParaShape="{bs.para_shape}" Style="{bs.style}">'
798
+ f'<TEXT CharShape="{bs.char_shape}">'
799
+ f'<TABLE BorderFill="{ts.table_borderfill}" CellSpacing="0" ColCount="{cc}"'
800
+ f' PageBreak="Cell" RepeatHeader="true" RowCount="{rc}">'
801
+ f'<SHAPEOBJECT InstId="1" Lock="false" NumberingType="Table" ZOrder="1">'
802
+ f'<SIZE Height="{max(2331,rc*1800)}" HeightRelTo="Absolute" Protect="false"'
803
+ f' Width="{tw}" WidthRelTo="Absolute"/>'
804
+ f'<POSITION AffectLSpacing="false" AllowOverlap="false" FlowWithText="true"'
805
+ f' HoldAnchorAndSO="false" HorzAlign="Left" HorzOffset="0" HorzRelTo="Para"'
806
+ f' TreatAsChar="true" VertAlign="Top" VertOffset="0" VertRelTo="Para"/>'
807
+ f'<OUTSIDEMARGIN Bottom="283" Left="283" Right="283" Top="283"/>'
808
+ f'</SHAPEOBJECT>{"".join(rows_xml)}</TABLE>'
809
+ f'<CHAR/></TEXT></P>')
810
+
811
+ def _empty_p(self):
812
+ """๋นˆ ๋ฌธ๋‹จ (ํ•ญ๋ชฉ ๊ฐ„ ๊ฐ„๊ฒฉ์šฉ)"""
813
+ bs = self.body_style
814
+ return f'<P ParaShape="{bs.para_shape}" Style="{bs.style}"><TEXT CharShape="{bs.char_shape}"><CHAR> </CHAR></TEXT></P>'
815
+
816
+ def render(self, text: str, doc_title: str = "๋ฌธ์„œ") -> str:
817
+ blocks = parse_document_blocks(text)
818
+ parts = []
819
+ if self.section_setup: parts.append(self.section_setup)
820
+ parts.append(self._p(doc_title, self.title_style))
821
+ title_added = True
822
+ for b in blocks:
823
+ if isinstance(b, TableBlock): parts.append(self._table(b))
824
+ elif b.kind == "title":
825
+ if title_added:
826
+ title_added = False
827
+ else:
828
+ parts.append(self._empty_p())
829
+ parts.append(self._p(b.text, self.title_style))
830
+ elif b.kind == "section":
831
+ parts.append(self._empty_p())
832
+ parts.append(self._p(b.text, self.section_style))
833
+ elif b.kind == "subsection":
834
+ parts.append(self._empty_p())
835
+ parts.append(self._p(b.text, self.subsect_style))
836
+ elif b.kind == "bullet":
837
+ parts.append(self._empty_p())
838
+ parts.append(self._bullet(b.text))
839
+ else: parts.append(self._p(b.text, self.body_style))
840
+
841
+ ts = f"<TITLE>{xml_escape(doc_title)}</TITLE>"
842
+ ta = "<AUTHOR>SOMA Pre-AGI</AUTHOR>"
843
+ td = f"<DATE>{datetime.now().strftime('%Y๋…„ %m์›” %d์ผ')}</DATE>"
844
+ head = self.head_xml
845
+ ds_new = f"<DOCSUMMARY>{ts}{ta}{td}</DOCSUMMARY>"
846
+ if re.search(r"<DOCSUMMARY>.*?</DOCSUMMARY>",head,flags=re.DOTALL):
847
+ head = re.sub(r"<DOCSUMMARY>.*?</DOCSUMMARY>",ds_new,head,flags=re.DOTALL)
848
+ else:
849
+ head = head.replace("</HEAD>",f"{ds_new}</HEAD>")
850
+ return (f'{self.xml_decl}{self.root_open}{head}'
851
+ f'<BODY><SECTION Id="0">{"".join(parts)}</SECTION></BODY>'
852
+ f'{self.tail_xml}</HWPML>')
853
+
854
+ def save(self, text: str, doc_title: str = "๋ฌธ์„œ") -> str:
855
+ content = self.render(text, doc_title)
856
+ f = tempfile.NamedTemporaryFile(mode="w",suffix=".hml",delete=False,encoding="utf-8-sig")
857
+ f.write(content); f.close()
858
+ return f.name
859
+
860
+
861
+ _engine: Optional[HWPTemplateEngine] = None
862
+ def get_engine() -> HWPTemplateEngine:
863
+ global _engine
864
+ if _engine: return _engine
865
+ for path in (PRIMARY_TEMPLATE, FALLBACK_TEMPLATE):
866
+ if os.path.exists(path):
867
+ _engine = HWPTemplateEngine(path); return _engine
868
+ raise FileNotFoundError("HML ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค.")
869
+
870
+ def generate_hml(content: str) -> str:
871
+ engine = get_engine()
872
+ title = normalize_text_for_title(content)
873
+ return engine.save(content.strip(), doc_title=title)
874
+
875
+
876
+ # ============================================================
877
+ # โ‘ฅ SOMA ํŒŒ์ดํ”„๋ผ์ธ (Metacognitive Pipeline)
878
+ # ============================================================
879
+ @dataclass
880
+ class AgentState:
881
+ active: str = "ๆฐด"
882
+ log: List[str] = field(default_factory=list)
883
+ search_count: int = 0
884
+ search_log: List[str] = field(default_factory=list)
885
+ search_results_all: str = ""
886
+ water_output: str = ""
887
+ wood_output: str = ""
888
+ fire_output: str = ""
889
+ earth_output: str = ""
890
+ gold_output: str = ""
891
+ final_doc: str = ""
892
+
893
+
894
+ def soma_pipeline(
895
+ user_prompt: str,
896
+ max_search: int = 30,
897
+ temperature: float = 0.6,
898
+ ) -> Generator:
899
+ """
900
+ SOMA Metacognitive Pipeline:
901
+ RESEARCH(ํƒ์ƒ‰) โ†’ STRUCTURE(์„ค๊ณ„) โ†’ ARGUMENT(๊ฒ€์ฆยท์ถ”๊ฐ€๊ฒ€์ƒ‰) โ†’ EVIDENCE(ํ†ตํ•ฉ) โ†’ IMPACT(์ •์ œ)
902
+ Cross-Check: IMPACTโ†’STRUCTURE ๋น„ํ‰, ARGUMENTโ†’IMPACT ์žฌ๊ฒ€ํ† 
903
+ """
904
+ st = AgentState()
905
+ max_search = min(max_search, MAX_SEARCHES)
906
+
907
+ def log(msg):
908
+ st.log.append(msg)
909
+ return msg
910
+
911
+ def emit(data: dict):
912
+ yield data
913
+
914
+ # โ”€โ”€ ํ—ฌํผ: ํ˜„์žฌ ์ƒํƒœ ์ „์†ก
915
+ def state_update(**kwargs):
916
+ return {**kwargs, "log": "\n".join(st.log[-30:]),
917
+ "search_count": st.search_count,
918
+ "search_log": "\n".join(st.search_log[-50:])}
919
+
920
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
921
+ # STAGE 1: ๆฐด โ€” ์ดˆ๊ธฐ ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ ์ƒ์„ฑ
922
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
923
+ st.active = "ๆฐด"
924
+ log(f"๐Ÿ” [RESEARCH] ์ดˆ๊ธฐ ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ ์ƒ์„ฑ ์ค‘...")
925
+ yield state_update(active="ๆฐด", stream="๐Ÿ” RESEARCH ์—์ด์ „ํŠธ: ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ ์ƒ์„ฑ ์ค‘...\n", final_doc="")
926
+
927
+ query_gen_prompt = (
928
+ f"์‚ฌ์šฉ์ž ์š”์ฒญ: {user_prompt}\n\n"
929
+ "์ด ์ฃผ์ œ์— ๋Œ€ํ•ด ๊ณต์‹ ๋ฌธ์„œ ์ž‘์„ฑ์— ํ•„์š”ํ•œ ํ•ต์‹ฌ ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ 5๊ฐœ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”.\n"
930
+ 'ํ˜•์‹: [์ฟผ๋ฆฌ1], [์ฟผ๋ฆฌ2], [์ฟผ๋ฆฌ3], [์ฟผ๋ฆฌ4], [์ฟผ๋ฆฌ5]\n'
931
+ "ํ•œ๊ตญ์–ด๋กœ, ๊ตฌ์ฒด์ ์ด๊ณ  ์‚ฌ์‹ค ๊ฒ€์ฆ ๊ฐ€๋Šฅํ•œ ์ฟผ๋ฆฌ๋งŒ ์ƒ์„ฑํ•˜์„ธ์š”."
932
+ )
933
+ query_resp = fireworks_stream_collect([{"role":"user","content":query_gen_prompt}],
934
+ AGENT_SYSTEM["ๆฐด"], max_tokens=512, temperature=0.3)
935
+ initial_queries = re.findall(r'\[([^\[\]]{3,80})\]', query_resp)
936
+ if not initial_queries:
937
+ initial_queries = [user_prompt[:60]]
938
+ log(f"๐Ÿ’ง ์ดˆ๊ธฐ ์ฟผ๋ฆฌ {len(initial_queries)}๊ฐœ ์ƒ์„ฑ: {initial_queries}")
939
+ yield state_update(active="ๆฐด", stream=f"๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ ์ƒ์„ฑ ์™„๋ฃŒ: {initial_queries}\n\n", final_doc="")
940
+
941
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
942
+ # STAGE 1-B: ๆฐด โ€” ๋‹จ๊ณ„๋ณ„ ๊ฒ€์ƒ‰ ์‹คํ–‰
943
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
944
+ pending_queries = list(initial_queries)
945
+ all_results_text = ""
946
+
947
+ while pending_queries and st.search_count < max_search:
948
+ q = pending_queries.pop(0)
949
+ st.search_count += 1
950
+ log(f"๐Ÿ” [{st.search_count}/{max_search}] ๊ฒ€์ƒ‰: {q}")
951
+ st.search_log.append(f"[{st.search_count:03d}] {q}")
952
+ yield state_update(active="ๆฐด",
953
+ stream=f"๐Ÿ” ๊ฒ€์ƒ‰ {st.search_count}: {q}\n",
954
+ final_doc="")
955
+
956
+ results = brave_search(q, count=10)
957
+ batch_text = f"\n=== ๊ฒ€์ƒ‰ {st.search_count}: {q} ===\n" + format_search_results(results)
958
+ all_results_text += batch_text
959
+
960
+ # ๆฐด ์—์ด์ „ํŠธ๊ฐ€ ๊ฒฐ๊ณผ ๋ถ„์„ & ์ถ”๊ฐ€ ์ฟผ๋ฆฌ ์ œ์•ˆ (๋งค 5ํšŒ๋งˆ๋‹ค)
961
+ if st.search_count % 5 == 0 and st.search_count < max_search - 5:
962
+ analysis = fireworks_stream_collect(
963
+ [{"role":"user","content": f"๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ:\n{batch_text}\n\n์ฃผ์ œ: {user_prompt}\n์ถ”๊ฐ€ ๊ฒ€์ƒ‰์ด ํ•„์š”ํ•œ ํ‚ค์›Œ๋“œ๋ฅผ [์ถ”๊ฐ€๊ฒ€์ƒ‰: 'ํ‚ค์›Œ๋“œ'] ํ˜•์‹์œผ๋กœ ์ตœ๋Œ€ 3๊ฐœ ์ œ์•ˆํ•˜์„ธ์š”."}],
964
+ AGENT_SYSTEM["ๆฐด"], max_tokens=300, temperature=0.3
965
+ )
966
+ new_qs = extract_additional_search_queries(analysis)
967
+ for nq in new_qs:
968
+ if nq not in [x for x in pending_queries] and st.search_count + len(pending_queries) < max_search:
969
+ pending_queries.append(nq)
970
+ log(f"๐Ÿ’ง ์ถ”๊ฐ€ ์ฟผ๋ฆฌ ์ถ”๊ฐ€: {nq}")
971
+ yield state_update(active="ๆฐด", stream=f" โ†’ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ {len(new_qs)}๊ฐœ ๋ฐœ๊ฒฌ\n", final_doc="")
972
+
973
+ st.water_output = all_results_text
974
+ log(f"๐Ÿ” RESEARCH ์™„๋ฃŒ: ์ด {st.search_count}ํšŒ ๊ฒ€์ƒ‰")
975
+ yield state_update(active="ๆฐด", stream=f"\nโœ… RESEARCH ์™„๋ฃŒ: {st.search_count}ํšŒ ๊ฒ€์ƒ‰\n\n", final_doc="")
976
+
977
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
978
+ # STAGE 2: ๆœจ โ€” ๋ฌธ์„œ ๊ตฌ์กฐ ๊ธฐํš
979
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
980
+ st.active = "ๆœจ"
981
+ log("๐Ÿงฉ [STRUCTURE] ๋ฌธ์„œ ๊ตฌ์กฐ ์„ค๊ณ„ ์ค‘...")
982
+ yield state_update(active="ๆœจ", stream="๐Ÿงฉ STRUCTURE ์—์ด์ „ํŠธ: ๋ฌธ์„œ ๊ตฌ์กฐ ์„ค๊ณ„ ์ค‘...\n", final_doc="")
983
+
984
+ wood_prompt = (
985
+ f"[์‚ฌ์šฉ์ž ์š”์ฒญ]\n{user_prompt}\n\n"
986
+ f"[์ˆ˜์ง‘๋œ ๊ฒ€์ƒ‰ ๋ฐ์ดํ„ฐ ์š”์•ฝ]\n{all_results_text[:6000]}\n\n"
987
+ "์œ„ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ HWP ๊ณต๋ฌธ์„œ Markdown ๊ตฌ์กฐ๋ฅผ ์„ค๊ณ„ํ•˜์„ธ์š”. "
988
+ "# ์ œ๋ชฉ, ## โ… .~โ…ค., ### ์†Œํ•ญ๋ชฉ, | ํ‘œ, โ—‹ ๋ถˆ๋ฆฟ ํ˜•์‹์œผ๋กœ ์ž‘์„ฑํ•˜์„ธ์š”."
989
+ )
990
+ wood_stream = fireworks_chat([{"role":"user","content":wood_prompt}],
991
+ AGENT_SYSTEM["ๆœจ"], max_tokens=2048, temperature=0.5, stream=True)
992
+ wood_text = ""
993
+ for tok in wood_stream:
994
+ wood_text += tok
995
+ yield state_update(active="ๆœจ", stream=tok, final_doc="")
996
+ st.wood_output = wood_text
997
+ log("๐Ÿงฉ STRUCTURE ์™„๋ฃŒ")
998
+ yield state_update(active="ๆœจ", stream="\n\nโœ… STRUCTURE ์™„๋ฃŒ\n\n", final_doc="")
999
+
1000
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1001
+ # STAGE 3: ็ซ โ€” ๊ฒ€์ฆ & ์ถ”๊ฐ€๊ฒ€์ƒ‰ (์ƒ๊ทน: ็ซๅ…‹้‡‘)
1002
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1003
+ st.active = "็ซ"
1004
+ log("๐Ÿ”ฌ [ARGUMENT] ํŒฉํŠธ์ฒดํฌ & ๊ฒ€์ฆ ์ค‘...")
1005
+ yield state_update(active="็ซ", stream="๐Ÿ”ฌ ARGUMENT ์—์ด์ „ํŠธ: ํŒฉํŠธ์ฒดํฌ & ๊ฒ€์ฆ ์ค‘...\n", final_doc="")
1006
+
1007
+ fire_prompt = (
1008
+ f"[๊ฒ€์ƒ‰ ๋ฐ์ดํ„ฐ]\n{all_results_text[:5000]}\n\n"
1009
+ f"[STRUCTURE ๋ฌธ์„œ ๊ตฌ์กฐ]\n{wood_text}\n\n"
1010
+ "์œ„ ๊ตฌ์กฐ๋ฅผ ๊ฒ€์ƒ‰ ๋ฐ์ดํ„ฐ์™€ ๋Œ€์กฐํ•˜์—ฌ ํŒฉํŠธ์ฒดํฌํ•˜๊ณ , "
1011
+ "์ถ”๊ฐ€ ๊ฒ€์ƒ‰์ด ํ•„์š”ํ•˜๋ฉด [์ถ”๊ฐ€๊ฒ€์ƒ‰: 'ํ‚ค์›Œ๋“œ'] ํ˜•์‹์œผ๋กœ ์ œ์•ˆํ•˜์„ธ์š”."
1012
+ )
1013
+ fire_resp = fireworks_stream_collect([{"role":"user","content":fire_prompt}],
1014
+ AGENT_SYSTEM["็ซ"], max_tokens=1024, temperature=0.3)
1015
+ st.fire_output = fire_resp
1016
+
1017
+ # ็ซ ์—์ด์ „ํŠธ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰ ์ˆ˜ํ–‰
1018
+ fire_queries = extract_additional_search_queries(fire_resp)
1019
+ yield state_update(active="็ซ", stream=f"๊ฒ€์ฆ ์™„๋ฃŒ. ์ถ”๊ฐ€ ๊ฒ€์ƒ‰ {len(fire_queries)}๊ฑด ์ˆ˜ํ–‰ ์ค‘...\n", final_doc="")
1020
+
1021
+ for fq in fire_queries:
1022
+ if st.search_count >= max_search: break
1023
+ st.search_count += 1
1024
+ st.search_log.append(f"[{st.search_count:03d}][็ซ๊ฒ€์ฆ] {fq}")
1025
+ log(f"๐Ÿ”ฅ ํ™” ์ถ”๊ฐ€๊ฒ€์ƒ‰ [{st.search_count}]: {fq}")
1026
+ yield state_update(active="็ซ", stream=f"๐Ÿ” ARGUMENT ์ถ”๊ฐ€๊ฒ€์ƒ‰ {st.search_count}: {fq}\n", final_doc="")
1027
+ res = brave_search(fq, count=10)
1028
+ extra = f"\n=== ARGUMENT๊ฒ€์ฆ ๊ฒ€์ƒ‰ {st.search_count}: {fq} ===\n" + format_search_results(res)
1029
+ all_results_text += extra
1030
+ st.water_output += extra
1031
+
1032
+ log("๐Ÿ”ฌ ARGUMENT ์™„๋ฃŒ")
1033
+ yield state_update(active="็ซ", stream="\nโœ… ARGUMENT ์™„๋ฃŒ\n\n", final_doc="")
1034
+
1035
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1036
+ # STAGE 4: ๅœŸ โ€” ์ตœ์ข… ๋ฌธ์„œ ํ†ตํ•ฉ ์ž‘์„ฑ
1037
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1038
+ st.active = "ๅœŸ"
1039
+ log("๐Ÿ“ [EVIDENCE] ์ตœ์ข… ๋ฌธ์„œ ์ž‘์„ฑ ์ค‘...")
1040
+ yield state_update(active="ๅœŸ", stream="๐Ÿ“ EVIDENCE ์—์ด์ „ํŠธ: ์ตœ์ข… ๋ฌธ์„œ ํ†ตํ•ฉ ์ž‘์„ฑ ์ค‘...\n", final_doc="")
1041
+
1042
+ earth_prompt = (
1043
+ f"[์‚ฌ์šฉ์ž ์š”์ฒญ]\n{user_prompt}\n\n"
1044
+ f"[๊ฒ€์ฆ๋œ ๊ฒ€์ƒ‰ ๋ฐ์ดํ„ฐ]\n{all_results_text[:8000]}\n\n"
1045
+ f"[STRUCTURE ๊ตฌ์กฐ]\n{wood_text[:2000]}\n\n"
1046
+ f"[ARGUMENT ๊ฒ€์ฆ ๊ฒฐ๊ณผ]\n{fire_resp[:1500]}\n\n"
1047
+ "๋ชจ๋“  ์ž๋ฃŒ๋ฅผ ํ†ตํ•ฉํ•˜์—ฌ ์™„์„ฑ๋„ ๋†’์€ HWP ๊ณต๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”. "
1048
+ "2000์ž ์ด์ƒ, # ## ### | โ—‹ ํ˜•์‹ ํ•„์ˆ˜ ์ค€์ˆ˜."
1049
+ )
1050
+ earth_stream = fireworks_chat([{"role":"user","content":earth_prompt}],
1051
+ AGENT_SYSTEM["ๅœŸ"], max_tokens=4096, temperature=0.5, stream=True)
1052
+ earth_text = ""
1053
+ for tok in earth_stream:
1054
+ earth_text += tok
1055
+ yield state_update(active="ๅœŸ", stream=tok, final_doc="")
1056
+ st.earth_output = earth_text
1057
+ log("๐Ÿ“ EVIDENCE ์™„๋ฃŒ")
1058
+ yield state_update(active="ๅœŸ", stream="\n\nโœ… EVIDENCE ์™„๋ฃŒ\n\n", final_doc="")
1059
+
1060
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1061
+ # STAGE 5: ้‡‘ โ€” ์ตœ์ข… ๋น„ํ‰ยท์ •์ œ (์ƒ๊ทน: ้‡‘ๅ…‹ๆœจ)
1062
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1063
+ st.active = "้‡‘"
1064
+ log("๐Ÿง  [IMPACT] ์ตœ์ข… ์ •์ œ ์ค‘...")
1065
+ yield state_update(active="้‡‘", stream="๐Ÿง  IMPACT ์—์ด์ „ํŠธ: ์ตœ์ข… ๋น„ํ‰ & ์ •์ œ ์ค‘...\n", final_doc="")
1066
+
1067
+ gold_prompt = (
1068
+ f"[EVIDENCE ์ดˆ์•ˆ]\n{earth_text}\n\n"
1069
+ "[STRUCTURE ์›๋ž˜ ๊ตฌ์กฐ์™€ ๋น„๊ตํ•˜์—ฌ ์ตœ์ข… ์™„์„ฑ๋ณธ์„ ์ถœ๋ ฅํ•˜์„ธ์š”.]\n"
1070
+ "===์ตœ์ข…๋ฌธ์„œ=== ๋งˆ์ปค ํ›„ ์™„์„ฑ๋œ ์ „์ฒด ๋ฌธ์„œ๋ฅผ ์ถœ๋ ฅํ•˜์„ธ์š”."
1071
+ )
1072
+ gold_stream = fireworks_chat([{"role":"user","content":gold_prompt}],
1073
+ AGENT_SYSTEM["้‡‘"], max_tokens=4096, temperature=0.4, stream=True)
1074
+ gold_text = ""
1075
+ for tok in gold_stream:
1076
+ gold_text += tok
1077
+ yield state_update(active="้‡‘", stream=tok, final_doc="")
1078
+ st.gold_output = gold_text
1079
+
1080
+ # ===์ตœ์ข…๋ฌธ์„œ=== ์ถ”์ถœ
1081
+ final_match = re.search(r"===์ตœ์ข…๋ฌธ์„œ===\s*(.*)", gold_text, flags=re.DOTALL)
1082
+ if final_match:
1083
+ st.final_doc = final_match.group(1).strip()
1084
+ else:
1085
+ # ๋งˆ์ปค ์—†์œผ๋ฉด ์ „์ฒด gold ์ถœ๋ ฅ ์‚ฌ์šฉ
1086
+ st.final_doc = gold_text.strip() or earth_text.strip()
1087
+
1088
+ log("๐Ÿง  IMPACT ์™„๋ฃŒ โ†’ ์ตœ์ข… ๋ฌธ์„œ ํ™•์ •")
1089
+ yield state_update(active="้‡‘", stream="\n\nโœ… IMPACT ์™„๋ฃŒ โ€” ๋ฌธ์„œ ํ™•์ •!\n", final_doc=st.final_doc)
1090
+ yield {"done": True, "final_doc": st.final_doc, "search_count": st.search_count,
1091
+ "log": "\n".join(st.log), "search_log": "\n".join(st.search_log)}
1092
+
1093
+
1094
+ # ============================================================
1095
+ # โ‘ฆ Gradio UI
1096
+ # ============================================================
1097
+ OHAENG_HTML_TEMPLATE = """
1098
+ <div style="display:flex;gap:10px;flex-wrap:wrap;justify-content:center;padding:10px 0;">
1099
+ {cards}
1100
+ </div>
1101
+ """
1102
+
1103
+ def ohaeng_cards_html(active: str = "ๆฐด") -> str:
1104
+ cards = []
1105
+ for key in SANGSAENG:
1106
+ o = OHAENG[key]
1107
+ is_active = (key == active)
1108
+ border = f"3px solid {o['color']}" if is_active else "2px solid #2a2a3a"
1109
+ bg = f"linear-gradient(135deg,{o['color']}22,{o['color']}08)" if is_active else "#1a1a2a"
1110
+ shadow = f"0 0 18px {o['color']}66" if is_active else "none"
1111
+ pulse = "animation:pulse 1.2s infinite;" if is_active else ""
1112
+ cards.append(f"""
1113
+ <div style="border:{border};background:{bg};box-shadow:{shadow};border-radius:12px;
1114
+ padding:10px 14px;min-width:110px;text-align:center;{pulse}transition:all .3s;">
1115
+ <div style="font-size:22px">{o['emoji']}</div>
1116
+ <div style="color:{o['color']};font-weight:700;font-size:13px">{o['name']}</div>
1117
+ <div style="color:#aaa;font-size:11px">{o['role']}</div>
1118
+ {'<div style="color:#fff;font-size:10px;margin-top:4px">โ— ํ™œ์„ฑ</div>' if is_active else ''}
1119
+ </div>""")
1120
+ return OHAENG_HTML_TEMPLATE.format(cards="".join(cards))
1121
+
1122
+
1123
+ # ============================================================
1124
+ # โ‘ฆ-B ํŒŒ์ผ ์—…๋กœ๋“œ ยท ํ…์ŠคํŠธ ์ถ”์ถœ ์œ ํ‹ธ๋ฆฌํ‹ฐ
1125
+ # ============================================================
1126
+ def extract_text_from_pdf(file_path: str) -> Optional[str]:
1127
+ parts = []
1128
+ if PDFPLUMBER_AVAILABLE:
1129
+ try:
1130
+ with pdfplumber.open(file_path) as pdf:
1131
+ for page in pdf.pages:
1132
+ t = page.extract_text()
1133
+ if t: parts.append(t)
1134
+ if parts: return "\n\n".join(parts)
1135
+ except Exception as e:
1136
+ print(f"pdfplumber error: {e}")
1137
+ if PYPDF2_AVAILABLE:
1138
+ try:
1139
+ with open(file_path, 'rb') as f:
1140
+ reader = PyPDF2.PdfReader(f)
1141
+ for page in reader.pages:
1142
+ t = page.extract_text()
1143
+ if t: parts.append(t)
1144
+ if parts: return "\n\n".join(parts)
1145
+ except Exception as e:
1146
+ print(f"PyPDF2 error: {e}")
1147
+ return None
1148
+
1149
+ def extract_text_from_txt(file_path: str) -> Optional[str]:
1150
+ for enc in ['utf-8', 'euc-kr', 'cp949', 'utf-16', 'latin-1']:
1151
+ try:
1152
+ with open(file_path, 'r', encoding=enc) as f:
1153
+ return f.read()
1154
+ except:
1155
+ continue
1156
+ return None
1157
+
1158
+ def extract_text_from_hwpx(file_path: str) -> tuple:
1159
+ try:
1160
+ text_parts = []
1161
+ with zipfile.ZipFile(file_path, 'r') as zf:
1162
+ section_files = sorted([f for f in zf.namelist()
1163
+ if f.startswith('Contents/section') and f.endswith('.xml')])
1164
+ for sf_name in section_files:
1165
+ try:
1166
+ with zf.open(sf_name) as sf:
1167
+ content = sf.read().decode('utf-8', errors='ignore')
1168
+ content = re.sub(r'\sxmlns[^"]*"[^"]*"', '', content)
1169
+ content = re.sub(r'<[a-zA-Z]+:', '<', content)
1170
+ content = re.sub(r'</[a-zA-Z]+:', '</', content)
1171
+ try:
1172
+ from xml.etree import ElementTree as _ET2
1173
+ root = _ET2.fromstring(content)
1174
+ texts = []
1175
+ for elem in root.iter():
1176
+ if elem.tag.endswith('t') or elem.tag == 't':
1177
+ if elem.text: texts.append(elem.text)
1178
+ elif elem.text and elem.text.strip():
1179
+ if any(x in elem.tag.lower() for x in ['text','run','para','char']):
1180
+ texts.append(elem.text.strip())
1181
+ if texts: text_parts.append(' '.join(texts))
1182
+ except:
1183
+ matches = re.findall(r'>([^<]+)<', content)
1184
+ clean = [t.strip() for t in matches if t.strip() and len(t.strip()) > 1]
1185
+ if clean: text_parts.append(' '.join(clean))
1186
+ except:
1187
+ continue
1188
+ if text_parts:
1189
+ result = re.sub(r'\s+', ' ', '\n\n'.join(text_parts))
1190
+ return result.strip(), None
1191
+ return None, "HWPX์—์„œ ํ…์ŠคํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค"
1192
+ except Exception as e:
1193
+ return None, f"HWPX ์˜ค๋ฅ˜: {str(e)}"
1194
+
1195
+ def _decode_hwp_para_text(data: bytes) -> Optional[str]:
1196
+ result = []
1197
+ i = 0
1198
+ while i < len(data) - 1:
1199
+ code = int.from_bytes(data[i:i+2], 'little')
1200
+ if code == 0: pass
1201
+ elif code in (1,2,3): i += 14
1202
+ elif code == 4: pass
1203
+ elif code == 9: result.append('\t')
1204
+ elif code in (10,13): result.append('\n')
1205
+ elif code == 24: result.append('-')
1206
+ elif code in (30,31): result.append(' ')
1207
+ elif code < 32: pass
1208
+ else:
1209
+ try:
1210
+ ch = chr(code)
1211
+ if ch.isprintable() or ch in '\n\t ': result.append(ch)
1212
+ except: pass
1213
+ i += 2
1214
+ text = re.sub(r'[ \t]+', ' ', ''.join(result).strip())
1215
+ return text if len(text) > 2 else None
1216
+
1217
+ def _extract_hwp_section_text(data: bytes) -> Optional[str]:
1218
+ texts = []; pos = 0
1219
+ while pos < len(data) - 4:
1220
+ try:
1221
+ header = int.from_bytes(data[pos:pos+4], 'little')
1222
+ tag_id = header & 0x3FF
1223
+ size = (header >> 20) & 0xFFF
1224
+ pos += 4
1225
+ if size == 0xFFF:
1226
+ if pos + 4 > len(data): break
1227
+ size = int.from_bytes(data[pos:pos+4], 'little'); pos += 4
1228
+ if pos + size > len(data): break
1229
+ record_data = data[pos:pos+size]; pos += size
1230
+ if tag_id == 67 and size > 0:
1231
+ t = _decode_hwp_para_text(record_data)
1232
+ if t: texts.append(t)
1233
+ except:
1234
+ pos += 1
1235
+ return '\n'.join(texts) if texts else None
1236
+
1237
+ def extract_text_from_hwp(file_path: str) -> tuple:
1238
+ # ๋ฐฉ๋ฒ• 1: hwp5txt CLI
1239
+ try:
1240
+ result = subprocess.run(['hwp5txt', file_path], capture_output=True, timeout=60)
1241
+ if result.returncode == 0 and result.stdout:
1242
+ for enc in ['utf-8', 'cp949', 'euc-kr']:
1243
+ try:
1244
+ text = result.stdout.decode(enc)
1245
+ if text.strip() and len(text.strip()) > 10:
1246
+ return text.strip(), None
1247
+ except: continue
1248
+ except: pass
1249
+ # ๋ฐฉ๋ฒ• 2: olefile ์ง์ ‘ ํŒŒ์‹ฑ
1250
+ if OLEFILE_AVAILABLE:
1251
+ try:
1252
+ ole = olefile.OleFileIO(file_path)
1253
+ header_data = ole.openstream('FileHeader').read()
1254
+ is_compressed = (header_data[36] & 1) == 1 if len(header_data) > 36 else True
1255
+ all_texts = []
1256
+ for entry in ole.listdir():
1257
+ path = '/'.join(entry)
1258
+ if path.startswith('BodyText/Section'):
1259
+ try:
1260
+ stream = ole.openstream(entry).read()
1261
+ if is_compressed:
1262
+ try: stream = zlib.decompress(stream, -15)
1263
+ except:
1264
+ try: stream = zlib.decompress(stream)
1265
+ except: pass
1266
+ t = _extract_hwp_section_text(stream)
1267
+ if t: all_texts.append(t)
1268
+ except: continue
1269
+ ole.close()
1270
+ if all_texts:
1271
+ return '\n\n'.join(all_texts).strip(), None
1272
+ except Exception as e:
1273
+ return None, f"olefile ์˜ค๋ฅ˜: {str(e)}"
1274
+ return None, "HWP ํ…์ŠคํŠธ ์ถ”์ถœ ์‹คํŒจ"
1275
+
1276
+ def _is_hml_xml(file_path: str) -> bool:
1277
+ """ํŒŒ์ผ์ด HML(HWPML XML) ํ˜•์‹์ธ์ง€ ํ™•์ธ (.hwp/.hwpx ํ™•์žฅ์ž์—ฌ๋„ ์‹ค์ œ๋กœ HML์ผ ์ˆ˜ ์žˆ์Œ)"""
1278
+ try:
1279
+ with open(file_path, 'rb') as f:
1280
+ head = f.read(500)
1281
+ # BOM ์ œ๊ฑฐ
1282
+ for bom in (b'\xef\xbb\xbf', b'\xff\xfe', b'\xfe\xff'):
1283
+ if head.startswith(bom):
1284
+ head = head[len(bom):]
1285
+ break
1286
+ head_str = head.decode('utf-8', errors='ignore').strip()
1287
+ return head_str.startswith('<?xml') and '<HWPML' in head_str[:400]
1288
+ except:
1289
+ return False
1290
+
1291
+ def extract_text_from_hml(file_path: str) -> tuple:
1292
+ """HML(HWPML XML) ํŒŒ์ผ์—์„œ ํ…์ŠคํŠธ ์ถ”์ถœ"""
1293
+ try:
1294
+ from xml.etree import ElementTree as _ET4
1295
+ # ์ธ์ฝ”๋”ฉ ์ž๋™ ๊ฐ์ง€ ์ฝ๊ธฐ
1296
+ raw = None
1297
+ for enc in ('utf-8-sig', 'utf-8', 'utf-16', 'cp949', 'euc-kr'):
1298
+ try:
1299
+ with open(file_path, 'r', encoding=enc) as f:
1300
+ raw = f.read()
1301
+ break
1302
+ except:
1303
+ continue
1304
+ if not raw:
1305
+ return None, "HML ํŒŒ์ผ ์ธ์ฝ”๋”ฉ ๊ฐ์ง€ ์‹คํŒจ"
1306
+
1307
+ root = _ET4.fromstring(raw)
1308
+ body = root.find('BODY')
1309
+ if body is None:
1310
+ # HWPML 1.1 ํ˜•์‹ (Body ์†Œ๋ฌธ์ž)
1311
+ body = root.find('Body')
1312
+ if body is None:
1313
+ return None, "HML์—์„œ BODY ์„น์…˜์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค"
1314
+
1315
+ texts = []
1316
+ for elem in body.iter():
1317
+ # CHAR ํƒœ๊ทธ ๋‚ด๋ถ€ ํ…์ŠคํŠธ (HWPML 2.x)
1318
+ if elem.tag == 'CHAR' and elem.text:
1319
+ t = elem.text.strip()
1320
+ if t and t not in ('', ' '):
1321
+ texts.append(t)
1322
+ # Text ํƒœ๊ทธ (HWPML 1.1)
1323
+ elif elem.tag == 'Text' and elem.text:
1324
+ t = elem.text.strip()
1325
+ if t:
1326
+ texts.append(t)
1327
+
1328
+ if texts:
1329
+ # ์—ฐ์†๋œ CHAR ํ…์ŠคํŠธ๋ฅผ ๋ฌธ๋‹จ ๋‹จ์œ„๋กœ ๊ฒฐํ•ฉ
1330
+ result = '\n'.join(texts)
1331
+ result = re.sub(r'\n{3,}', '\n\n', result)
1332
+ return result.strip(), None
1333
+ return None, "HML์—์„œ ํ…์ŠคํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค"
1334
+ except _ET4.ParseError as e:
1335
+ return None, f"HML XML ํŒŒ์‹ฑ ์˜ค๋ฅ˜: {e}"
1336
+ except Exception as e:
1337
+ return None, f"HML ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {e}"
1338
+
1339
+ def process_uploaded_file(file_path: str) -> tuple:
1340
+ """์—…๋กœ๋“œ๋œ ํŒŒ์ผ์—์„œ ํ…์ŠคํŠธ ์ถ”์ถœ. Returns (text, error_msg)"""
1341
+ if not file_path: return None, "ํŒŒ์ผ ์—†์Œ"
1342
+ ext = Path(file_path).suffix.lower()
1343
+ fname = os.path.basename(file_path)
1344
+
1345
+ # .hml ํŒŒ์ผ โ†’ HML XML ํŒŒ์„œ
1346
+ if ext == '.hml':
1347
+ text, err = extract_text_from_hml(file_path)
1348
+ return (text, None) if text else (None, err or "HML ์ถ”์ถœ ์‹คํŒจ")
1349
+
1350
+ # .hwp / .hwpx ํ™•์žฅ์ž์ด์ง€๋งŒ ์‹ค์ œ๋กœ HML XML์ผ ์ˆ˜ ์žˆ์Œ โ†’ ๋จผ์ € ๊ฐ์ง€
1351
+ if ext in ('.hwp', '.hwpx'):
1352
+ if _is_hml_xml(file_path):
1353
+ text, err = extract_text_from_hml(file_path)
1354
+ if text:
1355
+ return text, None
1356
+ # ์ง„์งœ ๋ฐ”์ด๋„ˆ๋ฆฌ HWP / HWPX
1357
+ if ext == '.hwpx':
1358
+ text, err = extract_text_from_hwpx(file_path)
1359
+ return (text, None) if text else (None, err or "HWPX ์ถ”์ถœ ์‹คํŒจ")
1360
+ else:
1361
+ text, err = extract_text_from_hwp(file_path)
1362
+ return (text, None) if text else (None, err or "HWP ์ถ”์ถœ ์‹คํŒจ")
1363
+
1364
+ if ext == '.pdf':
1365
+ text = extract_text_from_pdf(file_path)
1366
+ return (text, None) if text else (None, "PDF ์ถ”์ถœ ์‹คํŒจ")
1367
+ if ext in ('.txt', '.md', '.json', '.csv', '.xml', '.html', '.py', '.js', '.log'):
1368
+ text = extract_text_from_txt(file_path)
1369
+ return (text, None) if text else (None, "ํ…์ŠคํŠธ ์ฝ๊ธฐ ์‹คํŒจ")
1370
+ if ext in ('.docx',):
1371
+ try:
1372
+ from zipfile import ZipFile
1373
+ from xml.etree import ElementTree as _ET3
1374
+ with ZipFile(file_path) as zf:
1375
+ with zf.open('word/document.xml') as f:
1376
+ tree = _ET3.parse(f)
1377
+ ns = {'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'}
1378
+ texts = [node.text for node in tree.iter('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}t') if node.text]
1379
+ return '\n'.join(texts), None
1380
+ except Exception as e:
1381
+ return None, f"DOCX ์˜ค๋ฅ˜: {e}"
1382
+ if ext in ('.xlsx', '.xls'):
1383
+ try:
1384
+ import csv as _csv
1385
+ try:
1386
+ import openpyxl
1387
+ wb = openpyxl.load_workbook(file_path, read_only=True)
1388
+ lines = []
1389
+ for ws in wb.worksheets:
1390
+ for row in ws.iter_rows(values_only=True):
1391
+ lines.append('\t'.join(str(c) if c is not None else '' for c in row))
1392
+ return '\n'.join(lines), None
1393
+ except ImportError:
1394
+ return None, "Excel ์ฝ๊ธฐ: openpyxl ํ•„์š”"
1395
+ except Exception as e:
1396
+ return None, f"Excel ์˜ค๋ฅ˜: {e}"
1397
+ return None, f"์ง€์›ํ•˜์ง€ ์•Š๋Š” ํ˜•์‹: {ext}"
1398
+
1399
+
1400
+ # ============================================================
1401
+ # โ‘ฆ-C ๋ฌธ์„œ ๋ถ„์„ ์ฑ— (Metacognitive Agent ๊ธฐ๋ฐ˜)
1402
+ # ============================================================
1403
+ DOC_CHAT_SYSTEM = """๋‹น์‹ ์€ SOMA Pre-AGI ๋ฌธ์„œ ๋ถ„์„ ์ „๋ฌธ AI์ž…๋‹ˆ๋‹ค.
1404
+
1405
+ ## ํ•ต์‹ฌ ์—ญํ• 
1406
+ - ์‚ฌ์šฉ์ž๊ฐ€ ์—…๋กœ๋“œํ•œ ๋ฌธ์„œ์˜ ๋‚ด์šฉ์„ **์ •ํ™•ํ•˜๊ฒŒ ๋ถ„์„**ํ•˜๊ณ  **๊ตฌ์ฒด์ ์œผ๋กœ ๋‹ต๋ณ€**ํ•ฉ๋‹ˆ๋‹ค
1407
+ - ๋ฌธ์„œ์— ์žˆ๋Š” **์‹ค์ œ ๋‚ด์šฉ**์„ ๊ธฐ๋ฐ˜์œผ๋กœ๋งŒ ๋‹ต๋ณ€ํ•ฉ๋‹ˆ๋‹ค
1408
+ - ๋ฌธ์„œ์— ์—†๋Š” ๋‚ด์šฉ์€ ์ถ”์ธกํ•˜์ง€ ์•Š๊ณ  ๊ทธ ์‚ฌ์‹ค์„ ๋ฐํž™๋‹ˆ๋‹ค
1409
+
1410
+ ## Metacognitive ๋ถ„์„ ์›์น™
1411
+ - ๐Ÿ” RESEARCH: ๋ฌธ์„œ ๋‚ด์šฉ ์ •๋ฐ€ ํƒ์ƒ‰ยท๊ฒ€์ƒ‰
1412
+ - ๐Ÿงฉ STRUCTURE: ๋ฌธ์„œ ๊ตฌ์กฐยท์ฒด๊ณ„ ๋ถ„์„
1413
+ - ๐Ÿ”ฌ ARGUMENT: ํ•ต์‹ฌ ์‚ฌ์‹ค ๊ฒ€์ฆยทํŒฉํŠธ์ฒดํฌ
1414
+ - ๐Ÿ“ EVIDENCE: ์ข…ํ•ฉ์  ๋ถ„์„ ํ†ตํ•ฉ
1415
+ - ๐Ÿง  IMPACT: ์ตœ์ข… ์ •์ œยท๋ช…ํ™•ํ•œ ๋‹ต๋ณ€
1416
+
1417
+ ## ๋‹ต๋ณ€ ํ˜•์‹
1418
+ - ํ•œ๊ตญ์–ด๋กœ ์ž์—ฐ์Šค๋Ÿฝ๊ณ  ๋ช…ํ™•ํ•˜๊ฒŒ ๋‹ต๋ณ€ํ•ฉ๋‹ˆ๋‹ค
1419
+ - ๋ฌธ์„œ ๋‚ด์šฉ์„ ์ธ์šฉํ•  ๋•Œ๋Š” ๊ตฌ์ฒด์ ์œผ๋กœ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค
1420
+ - ๋ถ„๋Ÿ‰์ด ๊ธด ๋ฌธ์„œ๋Š” ํ•ต์‹ฌ ๋‚ด์šฉ ์œ„์ฃผ๋กœ ์š”์•ฝํ•ฉ๋‹ˆ๋‹ค"""
1421
+
1422
+ def doc_chat_respond(message: str, history: list, doc_text_state: str):
1423
+ """๋ฌธ์„œ ์ปจํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ์ฑ— ์‘๋‹ต (Fireworks API ์ŠคํŠธ๋ฆฌ๋ฐ)"""
1424
+ if not message.strip():
1425
+ yield history
1426
+ return
1427
+
1428
+ if not FIREWORKS_API_KEY:
1429
+ history = history + [{"role": "user", "content": message},
1430
+ {"role": "assistant", "content": "โŒ FIREWORKS_API_KEY๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."}]
1431
+ yield history
1432
+ return
1433
+
1434
+ # ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€์— ๋ฌธ์„œ ์ปจํ…์ŠคํŠธ ํฌํ•จ
1435
+ if doc_text_state:
1436
+ doc_snippet = doc_text_state[:12000] # ํ† ํฐ ์ œํ•œ
1437
+ user_content = f"""## ๐Ÿ“„ ์—…๋กœ๋“œ๋œ ๋ฌธ์„œ ๋‚ด์šฉ
1438
+
1439
+ ---
1440
+ {doc_snippet}
1441
+ ---
1442
+
1443
+ ## ๐Ÿ’ฌ ์‚ฌ์šฉ์ž ์งˆ๋ฌธ
1444
+ {message}
1445
+
1446
+ ์œ„ ๋ฌธ์„œ ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ๋‹ต๋ณ€ํ•ด์ฃผ์„ธ์š”."""
1447
+ else:
1448
+ user_content = message
1449
+
1450
+ # API ๋ฉ”์‹œ์ง€ ๊ตฌ์„ฑ
1451
+ api_messages = [{"role": "system", "content": DOC_CHAT_SYSTEM}]
1452
+ # ์ตœ๊ทผ ๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ (์ตœ๋Œ€ 6ํ„ด)
1453
+ for h in (history or [])[-6:]:
1454
+ api_messages.append({"role": h["role"], "content": h["content"]})
1455
+ api_messages.append({"role": "user", "content": user_content})
1456
+
1457
+ history = (history or []) + [{"role": "user", "content": message},
1458
+ {"role": "assistant", "content": ""}]
1459
+ yield history
1460
+
1461
+ # Fireworks API ์ŠคํŠธ๋ฆฌ๋ฐ ํ˜ธ์ถœ
1462
+ try:
1463
+ headers = {
1464
+ "Accept": "application/json",
1465
+ "Content-Type": "application/json",
1466
+ "Authorization": f"Bearer {FIREWORKS_API_KEY}",
1467
+ }
1468
+ payload = {
1469
+ "model": FIREWORKS_MODEL,
1470
+ "max_tokens": 4096,
1471
+ "temperature": 0.6,
1472
+ "stream": True,
1473
+ "messages": api_messages,
1474
+ }
1475
+ resp = requests.post(FIREWORKS_URL, headers=headers, json=payload,
1476
+ stream=True, timeout=180)
1477
+ resp.raise_for_status()
1478
+
1479
+ full = ""
1480
+ for raw_line in resp.iter_lines():
1481
+ if not raw_line: continue
1482
+ line = raw_line.decode("utf-8") if isinstance(raw_line, bytes) else raw_line
1483
+ if not line.startswith("data: "): continue
1484
+ data = line[6:]
1485
+ if data.strip() == "[DONE]": break
1486
+ try:
1487
+ chunk = json.loads(data)
1488
+ delta = chunk["choices"][0]["delta"].get("content", "")
1489
+ if delta:
1490
+ full += delta
1491
+ history[-1] = {"role": "assistant", "content": full}
1492
+ yield history
1493
+ except:
1494
+ pass
1495
+ except Exception as e:
1496
+ history[-1] = {"role": "assistant", "content": f"โŒ API ์˜ค๋ฅ˜: {e}"}
1497
+ yield history
1498
+
1499
+
1500
+ SANGGEUK_ARROWS_HTML = """
1501
+ <div style="text-align:center;font-size:12px;color:#666;padding:4px 0;">
1502
+ Pipeline: ๐Ÿ”โ†’๐Ÿงฉโ†’๐Ÿ”ฌโ†’๐Ÿ“โ†’๐Ÿง  &nbsp;|&nbsp;
1503
+ Cross-Check: ๐Ÿง โŠธ๐Ÿงฉ ยท ๐ŸงฉโŠธ๐Ÿ“ ยท ๐Ÿ“โŠธ๐Ÿ” ยท ๐Ÿ”โŠธ๐Ÿ”ฌ ยท ๐Ÿ”ฌโŠธ๐Ÿง 
1504
+ </div>"""
1505
+
1506
+ CSS = """
1507
+ @import url('https://fonts.googleapis.com/css2?family=Azeret+Mono:wght@400;700;900&family=Noto+Sans+KR:wght@300;400;500;700&display=swap');
1508
+ @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.6} }
1509
+ * { font-family: 'Noto Sans KR', sans-serif; box-sizing: border-box; }
1510
+ body, .gradio-container { background: #F2F5FB !important; color: #0D1526 !important; }
1511
+ .soma-title {
1512
+ text-align:center; padding:20px 0 8px;
1513
+ background: linear-gradient(135deg,#0EA5E9,#16A34A,#DC2626,#D97706,#7C3AED);
1514
+ background-size:200% auto;
1515
+ -webkit-background-clip:text; -webkit-text-fill-color:transparent;
1516
+ font-size:26px; font-weight:700; letter-spacing:2px; font-family:'Azeret Mono',monospace;
1517
+ }
1518
+ .soma-sub { text-align:center;color:#6B7A99;font-size:13px;margin-bottom:10px; }
1519
+ .gr-box { background:#FFFFFF !important; border:1.5px solid #DDE4F0 !important; border-radius:12px !important; box-shadow:0 1px 4px rgba(13,21,38,.05) !important; }
1520
+ .gr-button-primary { background:linear-gradient(135deg,#0EA5E9,#0284C7) !important; border:none !important; border-radius:8px !important; color:#fff !important; font-weight:700 !important; }
1521
+ .gr-button-secondary { background:#F8FAFF !important; border:1.5px solid #DDE4F0 !important; border-radius:8px !important; color:#3A4668 !important; }
1522
+ textarea, input[type=text], input[type=number] { background:#F8FAFF !important; color:#0D1526 !important; border:1.5px solid #DDE4F0 !important; border-radius:8px !important; }
1523
+ .agent-stream {
1524
+ background:#F8FAFF; border:1.5px solid #DDE4F0; border-radius:10px;
1525
+ padding:14px; height:280px; overflow-y:auto;
1526
+ font-family:'Azeret Mono',monospace; font-size:12px; color:#3A4668;
1527
+ white-space:pre-wrap; word-break:break-word;
1528
+ }
1529
+ .final-doc {
1530
+ background:#FFFFFF; border:1.5px solid #DDE4F0; border-radius:10px;
1531
+ padding:14px; height:400px; overflow-y:auto;
1532
+ font-size:13px; color:#0D1526; white-space:pre-wrap;
1533
+ }
1534
+ .search-log {
1535
+ background:#F0F7FF; border:1.5px solid #BAE6FD; border-radius:10px;
1536
+ padding:12px; height:200px; overflow-y:auto;
1537
+ font-family:'Azeret Mono',monospace; font-size:11px; color:#0EA5E9;
1538
+ white-space:pre-wrap;
1539
+ }
1540
+ .status-bar { background:#F2F5FB; border-radius:8px; padding:8px 14px; font-size:12px; color:#6B7A99; border:1.5px solid #DDE4F0; }
1541
+ label { color:#6B7A99 !important; font-size:12px !important; }
1542
+ .tab-nav button { background:transparent !important; color:#6B7A99 !important; border-bottom:2px solid transparent !important; }
1543
+ .tab-nav button.selected { color:#0EA5E9 !important; border-bottom-color:#0EA5E9 !important; }
1544
+ ::-webkit-scrollbar { width:5px; }
1545
+ ::-webkit-scrollbar-track { background:#F2F5FB; }
1546
+ ::-webkit-scrollbar-thumb { background:#C8D3E8; border-radius:3px; }
1547
+ """
1548
+
1549
+ def build_ui():
1550
+ with gr.Blocks(title="SOMA Pre-AGI ยท Metacognitive Document Engine") as app:
1551
+
1552
+ # โ”€โ”€ CSS ์ฃผ์ž…
1553
+ gr.HTML(f"<style>{CSS}</style>")
1554
+
1555
+ # โ”€โ”€ ํƒ€์ดํ‹€
1556
+ gr.HTML("""
1557
+ <div class="soma-title">โš™ SOMA Pre-AGI ยท Metacognitive Document Engine</div>
1558
+ <div class="soma-sub">Fireworks AI (Kimi K2) ยท Brave Search ยท Emergence Matrix ยท 5-Agent Pipeline</div>
1559
+ """)
1560
+ gr.HTML(SANGGEUK_ARROWS_HTML)
1561
+
1562
+ # โ”€โ”€ Agent ์นด๋“œ (๋™์  ์—…๋ฐ์ดํŠธ)
1563
+ ohaeng_display = gr.HTML(value=ohaeng_cards_html("ๆฐด"))
1564
+
1565
+ with gr.Tabs():
1566
+
1567
+ # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
1568
+ # TAB 1: ๋ฌธ์„œ ์ƒ์„ฑ
1569
+ # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
1570
+ with gr.Tab("๐Ÿ“ ๋ฌธ์„œ ์ƒ์„ฑ"):
1571
+ ref_text_state = gr.State("")
1572
+ with gr.Row():
1573
+ with gr.Column(scale=2):
1574
+ prompt_input = gr.Textbox(
1575
+ label="๐Ÿ“Œ ๋ฌธ์„œ ์ƒ์„ฑ ํ”„๋กฌํ”„ํŠธ",
1576
+ placeholder="์˜ˆ: ์˜จ๋ผ์ธ ์˜ˆ๊ธˆ์ค‘๊ฐœ ์„œ๋น„์Šค ์ œ๋„ํ™” ์ถ”์ง„ ๋ฐฉ์•ˆ์— ๋Œ€ํ•œ ์ •์ฑ… ๋ณด๊ณ ์„œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.",
1577
+ lines=4,
1578
+ elem_classes=["gr-box"]
1579
+ )
1580
+ with gr.Row():
1581
+ ref_file_upload = gr.File(
1582
+ label="๐Ÿ“Ž ์ฐธ๊ณ  ๋ฌธ์„œ ์—…๋กœ๋“œ (์„ ํƒ)",
1583
+ file_types=[".hwp",".hwpx",".hml",".pdf",".docx",".txt",".md",
1584
+ ".csv",".json",".xml",".xlsx",".xls",".py",".html",".log"],
1585
+ )
1586
+ ref_upload_status = gr.Textbox(label="ํŒŒ์ผ ์ƒํƒœ", interactive=False, lines=1, scale=2)
1587
+ with gr.Row():
1588
+ max_search_slider = gr.Slider(
1589
+ minimum=5, maximum=100, value=20, step=5,
1590
+ label=f"๐Ÿ” ์ตœ๋Œ€ ๊ฒ€์ƒ‰ ํšŸ์ˆ˜ (์ตœ๋Œ€ {MAX_SEARCHES}ํšŒ)"
1591
+ )
1592
+ temperature_slider = gr.Slider(
1593
+ minimum=0.1, maximum=1.0, value=0.6, step=0.05,
1594
+ label="๐ŸŒก Temperature"
1595
+ )
1596
+ with gr.Row():
1597
+ run_btn = gr.Button("๐Ÿš€ SOMA ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰", variant="primary", scale=3)
1598
+ stop_btn = gr.Button("โ›” ์ค‘์ง€", variant="secondary", scale=1)
1599
+
1600
+ with gr.Column(scale=1):
1601
+ gr.HTML("""
1602
+ <div style="background:#111120;border:1px solid #2a2a3f;border-radius:10px;padding:14px;font-size:12px;color:#888;">
1603
+ <b style="color:#a78bfa">๐Ÿ”‘ ํ•„์ˆ˜ ํ™˜๊ฒฝ๋ณ€์ˆ˜</b><br><br>
1604
+ <code style="color:#38BDF8">FIREWORKS_API_KEY</code><br>
1605
+ โ†’ fireworks.ai API ํ‚ค<br><br>
1606
+ <code style="color:#4ADE80">BRAVE_API_KEY</code><br>
1607
+ โ†’ brave.com/search/api ํ‚ค<br><br>
1608
+ <b style="color:#FBBF24">๐ŸŒฟ ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ</b><br>
1609
+ <code>nh_official_template.hml</code><br>
1610
+ โ†’ Space ๋ฃจํŠธ์— ์—…๋กœ๋“œ
1611
+ </div>""")
1612
+
1613
+ with gr.Row():
1614
+ with gr.Column():
1615
+ gr.Markdown("**โšก ์—์ด์ „ํŠธ ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆผ**")
1616
+ agent_stream = gr.Textbox(
1617
+ label="", value="", interactive=False,
1618
+ lines=14, max_lines=14,
1619
+ elem_classes=["agent-stream"],
1620
+ show_label=False
1621
+ )
1622
+ with gr.Column():
1623
+ gr.Markdown("**๐Ÿ“Š ๊ฒ€์ƒ‰ ์ง„ํ–‰ ๋กœ๊ทธ**")
1624
+ search_log_box = gr.Textbox(
1625
+ label="", value="", interactive=False,
1626
+ lines=7, max_lines=7,
1627
+ elem_classes=["search-log"],
1628
+ show_label=False
1629
+ )
1630
+ search_counter = gr.HTML(
1631
+ '<div class="status-bar">๐Ÿ” ๊ฒ€์ƒ‰ ํšŸ์ˆ˜: 0 / 0</div>'
1632
+ )
1633
+
1634
+ # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
1635
+ # TAB 2: ์ตœ์ข… ๋ฌธ์„œ
1636
+ # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
1637
+ with gr.Tab("๐Ÿ“„ ์ตœ์ข… ๋ฌธ์„œ"):
1638
+ final_doc_box = gr.Textbox(
1639
+ label="IMPACT ์—์ด์ „ํŠธ ์ตœ์ข… ํ™•์ • ๋ฌธ์„œ",
1640
+ value="", interactive=True, lines=25,
1641
+ elem_classes=["final-doc"]
1642
+ )
1643
+ with gr.Row():
1644
+ gen_hml_btn = gr.Button("๐Ÿ“ฅ HWPX ๋‹ค์šด๋กœ๋“œ", variant="primary")
1645
+ copy_text_btn = gr.Button("๐Ÿ“‹ ํ…์ŠคํŠธ ๋ณต์‚ฌ์šฉ", variant="secondary")
1646
+ hml_status = gr.Textbox(label="์ƒํƒœ", interactive=False, value="")
1647
+ hml_file = gr.File(label="๋‹ค์šด๋กœ๋“œ", visible=False, file_types=[".hwpx"])
1648
+
1649
+ # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
1650
+ # TAB 3: ์—์ด์ „ํŠธ ๋กœ๊ทธ
1651
+ # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
1652
+ with gr.Tab("๐Ÿงฌ SOMA ์—์ด์ „ํŠธ ๋กœ๊ทธ"):
1653
+ agent_log_box = gr.Textbox(
1654
+ label="์ „์ฒด ์—์ด์ „ํŠธ ์‹คํ–‰ ๋กœ๊ทธ",
1655
+ value="", interactive=False, lines=20,
1656
+ elem_classes=["search-log"]
1657
+ )
1658
+
1659
+ # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
1660
+ # TAB 4: ๋ฌธ์„œ ๋ถ„์„ ์ฑ—
1661
+ # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
1662
+ with gr.Tab("๐Ÿ“Ž ๋ฌธ์„œ ๋ถ„์„ ์ฑ—"):
1663
+ gr.Markdown("**ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜๊ณ  AI์—๊ฒŒ ์งˆ๋ฌธํ•˜์„ธ์š”** (HWP, HWPX, PDF, DOCX, TXT, CSV, Excel)")
1664
+ doc_text_state = gr.State("")
1665
+ with gr.Row():
1666
+ with gr.Column(scale=1):
1667
+ doc_upload = gr.File(
1668
+ label="๐Ÿ“„ ๋ฌธ์„œ ์—…๋กœ๋“œ",
1669
+ file_types=[".hwp",".hwpx",".hml",".pdf",".docx",".txt",".md",
1670
+ ".csv",".json",".xml",".xlsx",".xls",".py",".html",".log"],
1671
+ )
1672
+ doc_upload_status = gr.Textbox(label="ํŒŒ์ผ ์ƒํƒœ", interactive=False, lines=2)
1673
+ with gr.Column(scale=3):
1674
+ doc_chatbot = gr.Chatbot(label="๐Ÿ’ฌ ๋ฌธ์„œ ๋ถ„์„ ๋Œ€ํ™”", height=420)
1675
+ with gr.Row():
1676
+ doc_msg = gr.Textbox(
1677
+ label="์งˆ๋ฌธ ์ž…๋ ฅ",
1678
+ placeholder="์—…๋กœ๋“œ๋œ ๋ฌธ์„œ์— ๋Œ€ํ•ด ์งˆ๋ฌธํ•˜์„ธ์š”...",
1679
+ lines=2, scale=4
1680
+ )
1681
+ doc_send_btn = gr.Button("๐Ÿš€ ์ „์†ก", variant="primary", scale=1)
1682
+ doc_clear_btn = gr.Button("๐Ÿ—‘๏ธ ๋Œ€ํ™” ์ดˆ๊ธฐํ™”", variant="secondary")
1683
+
1684
+ def handle_file_upload(file):
1685
+ if file is None:
1686
+ return "", "ํŒŒ์ผ์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”."
1687
+ fpath = file.name if hasattr(file, 'name') else str(file)
1688
+ fname = os.path.basename(fpath)
1689
+ text, err = process_uploaded_file(fpath)
1690
+ if text:
1691
+ chars = len(text)
1692
+ return text, f"โœ… {fname} ({chars:,}์ž ์ถ”์ถœ ์™„๋ฃŒ)"
1693
+ return "", f"โŒ {fname}: {err}"
1694
+
1695
+ doc_upload.change(
1696
+ fn=handle_file_upload,
1697
+ inputs=[doc_upload],
1698
+ outputs=[doc_text_state, doc_upload_status]
1699
+ )
1700
+
1701
+ doc_send_btn.click(
1702
+ fn=doc_chat_respond,
1703
+ inputs=[doc_msg, doc_chatbot, doc_text_state],
1704
+ outputs=[doc_chatbot]
1705
+ ).then(lambda: "", outputs=[doc_msg])
1706
+
1707
+ doc_msg.submit(
1708
+ fn=doc_chat_respond,
1709
+ inputs=[doc_msg, doc_chatbot, doc_text_state],
1710
+ outputs=[doc_chatbot]
1711
+ ).then(lambda: "", outputs=[doc_msg])
1712
+
1713
+ doc_clear_btn.click(
1714
+ fn=lambda: ([], ""),
1715
+ outputs=[doc_chatbot, doc_text_state]
1716
+ )
1717
+
1718
+ # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
1719
+ # TAB 5: ์„ค์ •
1720
+ # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
1721
+ with gr.Tab("โš™๏ธ ์„ค์ •"):
1722
+ gr.Markdown("""
1723
+ ### SOMA Metacognitive Pipeline
1724
+
1725
+ | Stage | Agent | Role | Flow |
1726
+ |-------|-------|------|------|
1727
+ | 1 | ๐Ÿ” RESEARCH | Brave ๊ฒ€์ƒ‰ยท์ •๋ณด ์ˆ˜์ง‘ | โ†’ STRUCTURE |
1728
+ | 2 | ๐Ÿงฉ STRUCTURE | ๋ฌธ์„œ ๊ตฌ์กฐ ์„ค๊ณ„ | โ†’ ARGUMENT |
1729
+ | 3 | ๐Ÿ”ฌ ARGUMENT | ํŒฉํŠธ์ฒดํฌยท๋…ผ์ฆ ๊ฒ€์ฆ | โ†’ EVIDENCE |
1730
+ | 4 | ๐Ÿ“ EVIDENCE | ๋ฌธ์„œ ํ†ตํ•ฉยท์™„์„ฑ | โ†’ IMPACT |
1731
+ | 5 | ๐Ÿง  IMPACT | ๋ฉ”ํƒ€์ธ์ง€ ์ •์ œยท์™„์„ฑ | โ†’ (์ถœ๋ ฅ) |
1732
+
1733
+ **Cross-Check (์ƒํ˜ธ ๊ฒ€์ฆ):**
1734
+ - IMPACTโ†’STRUCTURE: ๊ตฌ์กฐ ์„ค๊ณ„์˜ ๋…ผ๋ฆฌ์  ์™„๊ฒฐ์„ฑ ๋น„ํ‰
1735
+ - ARGUMENTโ†’IMPACT: ๊ณผ๋„ํ•œ ์ •์ œ๊ฐ€ ์›๋ž˜ ์˜๋„๋ฅผ ํ›ผ์†ํ•˜์ง€ ์•Š๋Š”์ง€ ๊ฒฌ์ œ
1736
+ - EVIDENCEโ†’RESEARCH: ํ†ตํ•ฉ ๊ณผ์ •์—์„œ ๋ˆ„๋ฝ๋œ ๋ฐ์ดํ„ฐ ์š”์ฒญ
1737
+ - RESEARCHโ†’ARGUMENT: ์ˆ˜์ง‘ ๋‹จ๊ณ„์—์„œ ์ถœ์ฒ˜ ์‹ ๋ขฐ์„ฑ ์‚ฌ์ „ ๊ฒ€์ฆ
1738
+ - STRUCTUREโ†’EVIDENCE: ๊ณจ๊ฒฉ์ด ๊ทผ๊ฑฐ๋ฅผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ˆ˜์šฉํ•˜๋Š”์ง€ ๊ฐ€์ด๋“œ
1739
+
1740
+ **Emergence Matrix:**
1741
+ - 6๊ฐœ ๋ ˆ์ด์–ด ร— ๊ต์ฐจ ์ฐฝ๋ฐœ ๋ณด๋„ˆ์Šค๋กœ ์ด์ข… ๊ด€์  ๊ฒฐํ•ฉ ์‹œ ๋…์ฐฝ์  ํ†ต์ฐฐ ์œ ๋„
1742
+ - Koestler Bisociation ์›๋ฆฌ: ๊ฑฐ๋ฆฌ๊ฐ€ ๋จผ ๋ ˆ์ด์–ด ๊ฐ„ ๊ต์ฐจ์ผ์ˆ˜๋ก ๋†’์€ ์ฐฝ๋ฐœ์„ฑ
1743
+
1744
+ ### ํŒŒ์ผ ํ˜•์‹ ์•ˆ๋‚ด
1745
+ ์ƒ์„ฑ๋œ `.hwpx` ํŒŒ์ผ์€ ํ•œ๊ธ€(HWP)์—์„œ **๋”๋ธ”ํด๋ฆญ**์œผ๋กœ ์—ด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
1746
+ """)
1747
+ gr.Markdown(f"""
1748
+ ### ์˜ˆ์‹œ ํ”„๋กฌํ”„ํŠธ
1749
+ - ์˜จ๋ผ์ธ ์˜ˆ๊ธˆ์ค‘๊ฐœ ์„œ๋น„์Šค ์ œ๋„ํ™” ์ถ”์ง„ ๋ฐฉ์•ˆ ์ •์ฑ… ๋ณด๊ณ ์„œ
1750
+ - ๋””์ง€ํ„ธ ๊ธˆ์œต ํ”Œ๋žซํผ ํ™œ์„ฑํ™”๋ฅผ ์œ„ํ•œ ๊ทœ์ œ ๊ฐœ์„  ๋ฐฉ์•ˆ
1751
+ - ์ธ๊ณต์ง€๋Šฅ ๊ธฐ๋ฐ˜ ๊ณต๊ณต์„œ๋น„์Šค ํ˜์‹  ์ „๋žต ์ˆ˜๋ฆฝ ๋ณด๊ณ ์„œ
1752
+ - ํƒ„์†Œ์ค‘๋ฆฝ ์‹คํ˜„์„ ์œ„ํ•œ ์—๋„ˆ์ง€ ์ „ํ™˜ ์ •์ฑ… ๋กœ๋“œ๋งต
1753
+ - ์Šค๋งˆํŠธ์‹œํ‹ฐ ๋ฐ์ดํ„ฐ ๊ฑฐ๋ฒ„๋„Œ์Šค ์ฒด๊ณ„ ๊ตฌ์ถ• ๋ฐฉ์•ˆ
1754
+ """)
1755
+
1756
+ # โ”€โ”€ ์ƒํƒœ ์ €์žฅ์šฉ
1757
+ state = gr.State({"final_doc": "", "search_count": 0})
1758
+ dummy_state = gr.State("")
1759
+
1760
+ # ============================================================
1761
+ # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
1762
+ # ============================================================
1763
+ def handle_ref_upload(file):
1764
+ if file is None:
1765
+ return "", ""
1766
+ fpath = file.name if hasattr(file, 'name') else str(file)
1767
+ fname = os.path.basename(fpath)
1768
+ text, err = process_uploaded_file(fpath)
1769
+ if text:
1770
+ return text, f"โœ… {fname} ({len(text):,}์ž ์ถ”์ถœ)"
1771
+ return "", f"โŒ {fname}: {err}"
1772
+
1773
+ ref_file_upload.change(
1774
+ fn=handle_ref_upload,
1775
+ inputs=[ref_file_upload],
1776
+ outputs=[ref_text_state, ref_upload_status]
1777
+ )
1778
+
1779
+ def run_soma(prompt, max_search, temperature, ref_text):
1780
+ if not prompt.strip():
1781
+ yield (ohaeng_cards_html("ๆฐด"),
1782
+ "โš ๏ธ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", "", "", "",
1783
+ '<div class="status-bar">๋Œ€๊ธฐ ์ค‘</div>', "")
1784
+ return
1785
+
1786
+ # ์ฐธ๊ณ ๋ฌธ์„œ๊ฐ€ ์žˆ์œผ๋ฉด ํ”„๋กฌํ”„ํŠธ์— ํฌํ•จ
1787
+ full_prompt = prompt
1788
+ if ref_text and ref_text.strip():
1789
+ ref_snippet = ref_text.strip()[:15000] # ํ† ํฐ ์ œํ•œ
1790
+ full_prompt = (
1791
+ f"{prompt}\n\n"
1792
+ f"[์ฐธ๊ณ ๋ฌธ์„œ ๋‚ด์šฉ]\n"
1793
+ f"์•„๋ž˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์—…๋กœ๋“œํ•œ ์ฐธ๊ณ  ๋ฌธ์„œ์ž…๋‹ˆ๋‹ค. "
1794
+ f"์ด ๋‚ด์šฉ์„ ๋ฐ˜๋“œ์‹œ ์ฐธ๊ณ ํ•˜์—ฌ ๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”.\n\n"
1795
+ f"{ref_snippet}"
1796
+ )
1797
+
1798
+ stream_acc = ""
1799
+ log_acc = ""
1800
+ search_log = ""
1801
+ active_agent = "ๆฐด"
1802
+ final_doc = ""
1803
+ sc = 0
1804
+
1805
+ for chunk in soma_pipeline(full_prompt, int(max_search), temperature):
1806
+ if chunk.get("done"):
1807
+ final_doc = chunk.get("final_doc","")
1808
+ sc = chunk.get("search_count", sc)
1809
+ log_acc = chunk.get("log","")
1810
+ search_log= chunk.get("search_log","")
1811
+ break
1812
+
1813
+ active_agent = chunk.get("active", active_agent)
1814
+ tok = chunk.get("stream","")
1815
+ if tok:
1816
+ stream_acc += tok
1817
+ # ๋„ˆ๋ฌด ๊ธธ๋ฉด ์•ž ๋ถ€๋ถ„ ์ž˜๋ผ๋‚ด๊ธฐ
1818
+ if len(stream_acc) > 8000:
1819
+ stream_acc = "...(์ด์ „ ์ƒ๋žต)...\n" + stream_acc[-6000:]
1820
+
1821
+ if chunk.get("log"): log_acc = chunk["log"]
1822
+ if chunk.get("search_log"): search_log = chunk["search_log"]
1823
+ if chunk.get("search_count") is not None: sc = chunk["search_count"]
1824
+ if chunk.get("final_doc"): final_doc = chunk["final_doc"]
1825
+
1826
+ counter_html = f'<div class="status-bar">๐Ÿ” ๊ฒ€์ƒ‰ ํšŸ์ˆ˜: {sc} / {int(max_search)}</div>'
1827
+ yield (ohaeng_cards_html(active_agent),
1828
+ stream_acc, search_log, log_acc,
1829
+ final_doc if final_doc else "",
1830
+ counter_html, "")
1831
+
1832
+ # ์™„๋ฃŒ
1833
+ counter_html = f'<div class="status-bar">โœ… ์™„๋ฃŒ: {sc}ํšŒ ๊ฒ€์ƒ‰ ยท ๋ฌธ์„œ ์ƒ์„ฑ ์™„๋ฃŒ</div>'
1834
+ yield (ohaeng_cards_html("้‡‘"),
1835
+ stream_acc + "\n\n๐ŸŽ‰ SOMA ํŒŒ์ดํ”„๋ผ์ธ ์™„๋ฃŒ!", search_log, log_acc,
1836
+ final_doc, counter_html, final_doc)
1837
+
1838
+ run_btn.click(
1839
+ fn=run_soma,
1840
+ inputs=[prompt_input, max_search_slider, temperature_slider, ref_text_state],
1841
+ outputs=[ohaeng_display, agent_stream, search_log_box,
1842
+ agent_log_box, final_doc_box, search_counter,
1843
+ dummy_state]
1844
+ )
1845
+
1846
+ def make_hml(doc_text):
1847
+ if not doc_text or not doc_text.strip():
1848
+ return None, "โŒ ๋ฌธ์„œ ๋‚ด์šฉ์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋จผ์ € ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”."
1849
+ try:
1850
+ path = generate_hml(doc_text.strip())
1851
+ # ํŒŒ์ผ๋ช…์„ ๋ฌธ์„œ ์ œ๋ชฉ.hwpx๋กœ ๋ณ€๊ฒฝ
1852
+ title = normalize_text_for_title(doc_text.strip())
1853
+ safe_title = re.sub(r'[\\/:*?"<>|]', '', title)[:40].strip() or "๋ฌธ์„œ"
1854
+ new_path = os.path.join(os.path.dirname(path), f"{safe_title}.hwpx")
1855
+ os.rename(path, new_path)
1856
+ return new_path, f"โœ… ์ƒ์„ฑ ์™„๋ฃŒ ยท ๋‹ค์šด๋กœ๋“œ ํ›„ ๋”๋ธ”ํด๋ฆญ์œผ๋กœ ์—ด๊ธฐ"
1857
+ except Exception as e:
1858
+ return None, f"โŒ ์˜ค๋ฅ˜: {e}"
1859
+
1860
+ gen_hml_btn.click(
1861
+ fn=make_hml,
1862
+ inputs=[final_doc_box],
1863
+ outputs=[hml_file, hml_status]
1864
+ ).then(
1865
+ lambda f: gr.File(visible=f is not None),
1866
+ inputs=[hml_file], outputs=[hml_file]
1867
+ )
1868
+
1869
+ return app
1870
+
1871
+
1872
+ # ============================================================
1873
+ # โ‘ง ์ง„์ž…์ 
1874
+ # ============================================================
1875
+ if __name__ == "__main__":
1876
+ app = build_ui()
1877
+ app.launch()
emergence_matrix_document.json ADDED
@@ -0,0 +1,356 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_meta": {
3
+ "version": "1.0.0",
4
+ "description": "6-Layer ๋ฌธ์„œ์ž‘์„ฑ ์ฐฝ๋ฐœ ๋งคํŠธ๋ฆญ์Šค โ€” Koestler Bisociation ๊ธฐ๋ฐ˜ ์ด์ข… ๋ ˆ์ด์–ด ๊ต์ฐจ๋กœ ์ฐฝ๋ฐœ์  ํ†ต์ฐฐ ์œ ๋„",
5
+ "philosophy": "์ด์ข… ๋ ˆ์ด์–ด ๊ฐ„ ๊ฑฐ๋ฆฌ๊ฐ€ ๋ฉ€์ˆ˜๋ก ๋†’์€ ์ฐฝ๋ฐœ์„ฑ ๋ณด๋„ˆ์Šค. ์—์ด์ „ํŠธ๊ฐ€ ์ž๊ธฐ ๋ ˆ์ด์–ด ์™ธ ์›๊ฒฉ ๋ ˆ์ด์–ด์˜ ์‚ฌ๊ณ  ํŒจํ„ด์„ ๊ต์ฐจ ์ ์šฉํ•  ๋•Œ ๋…์ฐฝ์  ๋ฌธ์„œ๊ฐ€ ํƒ„์ƒํ•œ๋‹ค.",
6
+ "system": "SOMA Pre-AGI Document Emergence Engine",
7
+ "author": "Ginigen AI (์ง€๋‹ˆ์  AI)"
8
+ },
9
+ "layers": {
10
+ "RESEARCH": {
11
+ "desc": "ํƒ์ƒ‰ยท์ˆ˜์ง‘ โ€” ์™ธ๋ถ€ ์„ธ๊ณ„์˜ ์ •๋ณด๋ฅผ ํƒ์ง€ํ•˜๊ณ  ์ •์ œํ•˜๋Š” ์ธ์ง€์˜ ์ž…๊ตฌ",
12
+ "agent": "ๆฐด",
13
+ "layer_index": 0,
14
+ "strategies": {
15
+ "ํƒ์ƒ‰_ํŒจํ„ด": [
16
+ "์ˆ˜๋ ด์  ๊ฒ€์ƒ‰: ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ๋ฅผ ์ขํ˜€๊ฐ€๋ฉฐ ์ •๋ฐ€ ํƒ์ƒ‰",
17
+ "๋ฐœ์‚ฐ์  ๊ฒ€์ƒ‰: ์—ฐ๊ด€ ํ‚ค์›Œ๋“œ๋ฅผ ํ™•์žฅํ•˜๋ฉฐ ์˜์™ธ์˜ ์—ฐ๊ฒฐ๊ณ ๋ฆฌ ๋ฐœ๊ฒฌ",
18
+ "์‹œ๊ณ„์—ด ์ถ”์ : ๋™์ผ ์ฃผ์ œ์˜ ๊ณผ๊ฑฐโ†’ํ˜„์žฌโ†’๋ฏธ๋ž˜ ๋ณ€ํ™” ํ๋ฆ„ ์กฐ์‚ฌ",
19
+ "๋ฐ˜์ฆ ๊ฒ€์ƒ‰: ์ฃผ์žฅ์„ ๋ฐ˜๋ฐ•ํ•˜๋Š” ์ž๋ฃŒ๋ฅผ ์˜๋„์ ์œผ๋กœ ํƒ์ƒ‰",
20
+ "๊ต์ฐจ ๋„๋ฉ”์ธ ํƒ์ƒ‰: ์ „ํ˜€ ๋‹ค๋ฅธ ๋ถ„์•ผ์—์„œ ์œ ์‚ฌ ํŒจํ„ด ๋ฐœ๊ฒฌ",
21
+ "์›์ „ ์†Œ๊ธ‰: 2์ฐจ ์ž๋ฃŒ์—์„œ 1์ฐจ ์›์ „(๋ฒ•๋ น, ๋…ผ๋ฌธ, ํ†ต๊ณ„)์œผ๋กœ ๊ฑฐ์Šฌ๋Ÿฌ ์˜ฌ๋ผ๊ฐ",
22
+ "์ดํ•ด๊ด€๊ณ„์ž ๋งคํ•‘: ์ฃผ์ œ์— ์˜ํ–ฅ ๋ฐ›๋Š” ๋ชจ๋“  ์ฃผ์ฒด๋ณ„ ๊ด€์  ์ˆ˜์ง‘",
23
+ "๋ฉ”ํƒ€ ๊ฒ€์ƒ‰: '์ด ์ฃผ์ œ์— ๋Œ€ํ•œ ๊ธฐ์กด ๋ณด๊ณ ์„œ/์—ฐ๊ตฌ'๋ฅผ ๊ฒ€์ƒ‰ํ•˜์—ฌ ์„ ํ–‰ ํ”„๋ ˆ์ž„ ํŒŒ์•…"
24
+ ],
25
+ "์ •๋ณด_ํ’ˆ์งˆ_ํ•„ํ„ฐ": [
26
+ "์ถœ์ฒ˜ ์œ„๊ณ„: ๊ณต์‹ ๋ฒ•๋ น > ์ •๋ถ€ ๋ณด๊ณ ์„œ > ํ•™์ˆ  ๋…ผ๋ฌธ > ์–ธ๋ก  > ๋ธ”๋กœ๊ทธ",
27
+ "์‹œ์  ๊ฒ€์ฆ: ์ตœ์‹  ๋ฐ์ดํ„ฐ์ธ์ง€, ํ๊ธฐ๋œ ์ •๋ณด์ธ์ง€ ์—ฐ๋„ ํ™•์ธ",
28
+ "๊ต์ฐจ ๊ฒ€์ฆ: ๋™์ผ ํŒฉํŠธ๋ฅผ 3๊ฐœ ์ด์ƒ ๋…๋ฆฝ ์ถœ์ฒ˜์—์„œ ํ™•์ธ",
29
+ "ํ†ต๊ณ„ ๋งฅ๋ฝํ™”: ์ ˆ๋Œ€๊ฐ’๋ฟ ์•„๋‹ˆ๋ผ ๋น„์œจ, ์ถ”์„ธ, ๋น„๊ต ๋Œ€์ƒ๊นŒ์ง€ ์ˆ˜์ง‘",
30
+ "์ธ์šฉ ์ฒด์ธ: ๋ˆ„๊ฐ€ ๋ˆ„๊ตฌ๋ฅผ ์ธ์šฉํ•˜๋Š”์ง€ ์ถ”์ ํ•˜์—ฌ ์›์ฒœ ์‹ ๋ขฐ๋„ ํ‰๊ฐ€"
31
+ ],
32
+ "๋ฐœ๊ฒฌ_ํ”„๋ ˆ์ž„": [
33
+ "๋นˆ์นธ ํƒ์ง€: ๊ธฐ์กด ์ž๋ฃŒ์—์„œ ๋‹ค๋ฃจ์ง€ ์•Š๋Š” ์˜์—ญ(gap) ์‹๋ณ„",
34
+ "์ด์ƒ๊ฐ’ ํฌ์ฐฉ: ์˜ˆ์ƒ๊ณผ ๋‹ค๋ฅธ ์ˆ˜์น˜/์‚ฌ๋ก€๋ฅผ ๋ฐœ๊ฒฌํ•˜๋ฉด ์›์ธ ์ถ”์ ",
35
+ "ํŒจํ„ด ์ธ์‹: ์„œ๋กœ ๋‹ค๋ฅธ ์ž๋ฃŒ์—์„œ ๋ฐ˜๋ณต๋˜๋Š” ๊ณตํ†ต ํŒจํ„ด ์ถ”์ถœ",
36
+ "์•”๋ฌต์ง€ ๋ฐœ๊ตด: ๊ณต์‹ ๋ฌธ์„œ์—๋Š” ์—†์ง€๋งŒ ํ˜„์žฅ ์ธํ„ฐ๋ทฐ/์‚ฌ๋ก€์—์„œ ๋“œ๋Ÿฌ๋‚˜๋Š” ์‹ค์งˆ ์ •๋ณด"
37
+ ]
38
+ }
39
+ },
40
+ "STRUCTURE": {
41
+ "desc": "์„ค๊ณ„ยท์กฐ์ง โ€” ์ •๋ณด๋ฅผ ์ฒด๊ณ„์  ๊ณจ๊ฒฉ์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฑด์ถ• ํ–‰์œ„",
42
+ "agent": "ๆœจ",
43
+ "layer_index": 1,
44
+ "strategies": {
45
+ "๊ตฌ์กฐ_์•„ํ‚คํƒ€์ž…": [
46
+ "๋‘๊ด„์‹(์—ญํ”ผ๋ผ๋ฏธ๋“œ): ๊ฒฐ๋ก  โ†’ ๊ทผ๊ฑฐ โ†’ ๋ฐฐ๊ฒฝ. ์˜์‚ฌ๊ฒฐ์ •์ž ๋Œ€์ƒ ๋ณด๊ณ ์„œ",
47
+ "๋ฏธ๊ด„์‹(์„œ์‚ฌํ˜•): ๋ฐฐ๊ฒฝ โ†’ ๋ถ„์„ โ†’ ๊ฒฐ๋ก . ์„ค๋“ยท์ œ์•ˆ ๋ฌธ์„œ",
48
+ "๋ณ‘๋ ฌํ˜•: ๋™๋“ฑํ•œ ์ฃผ์ œ๋ฅผ ๋‚˜๋ž€ํžˆ ๋ฐฐ์น˜. ๋น„๊ตยท๋Œ€์•ˆ ๋ถ„์„",
49
+ "๊ณ„์ธตํ˜•(๋“œ๋ฆด๋‹ค์šด): ๊ฑฐ์‹œ โ†’ ๋ฏธ์‹œ. ์ •์ฑ… โ†’ ์„ธ๋ถ€ ์‹คํ–‰๊ณ„ํš",
50
+ "๋ฌธ์ œ-ํ•ด๊ฒฐํ˜•: ํ˜„ํ™ฉ(๋ฌธ์ œ) โ†’ ์›์ธ ๋ถ„์„ โ†’ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ โ†’ ๊ธฐ๋Œ€ํšจ๊ณผ",
51
+ "์‹œ๊ณ„์—ดํ˜•: ๊ณผ๊ฑฐ โ†’ ํ˜„์žฌ โ†’ ๋ฏธ๋ž˜. ๋กœ๋“œ๋งตยท์ถ”์ง„ ์ผ์ •",
52
+ "MECE ๋ถ„ํ•ด: ์ƒํ˜ธ๋ฐฐ์ œยท์ „์ฒดํฌ๊ด„. ๋น ์ง์—†๋Š” ๋ถ„๋ฅ˜ ์ฒด๊ณ„"
53
+ ],
54
+ "์„น์…˜_์„ค๊ณ„_์›๋ฆฌ": [
55
+ "1์„น์…˜ 1๋ฉ”์‹œ์ง€: ๊ฐ ์„น์…˜์ด ์ „๋‹ฌํ•˜๋Š” ํ•ต์‹ฌ ๋ฉ”์‹œ์ง€ 1๊ฐœ๋ฅผ ๋จผ์ € ์ •์˜",
56
+ "์ „ํ™˜ ๋ธŒ๋ฆฟ์ง€: ์„น์…˜ ๊ฐ„ ๋…ผ๋ฆฌ์  ์—ฐ๊ฒฐ๋ฌธ์œผ๋กœ ์ž์—ฐ์Šค๋Ÿฌ์šด ํ๋ฆ„ ๋ณด์žฅ",
57
+ "๊นŠ์ด ๊ท ํ˜•: ์ค‘์š” ์„น์…˜์€ ๊นŠ๊ฒŒ, ๋ถ€์ˆ˜ ์„น์…˜์€ ๊ฐ„๊ฒฐํ•˜๊ฒŒ",
58
+ "ํ‘œยท๊ทธ๋ฆผ ๋ฐฐ์น˜: ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ๋Š” ํ‘œ๋กœ, ํ๋ฆ„์€ ๋„์‹์œผ๋กœ ์‹œ๊ฐํ™”",
59
+ "๋ฐ˜๋ณต ์‚ผ๊ฐ: ํ•ต์‹ฌ ๋ฉ”์‹œ์ง€๋ฅผ ์„œ๋ก ยท๋ณธ๋ก ยท๊ฒฐ๋ก ์—์„œ ์„ธ ๋ฒˆ ๋ฐ˜๋ณต"
60
+ ],
61
+ "์ฐฝ๋ฐœ์ _๊ตฌ์กฐ": [
62
+ "์—ญ์„ค ๊ตฌ์กฐ: ํ†ต๋…๊ณผ ๋ฐ˜๋Œ€๋˜๋Š” ๊ฒฐ๋ก ์„ ๋จผ์ € ์ œ์‹œํ•˜๊ณ  ๋…ผ์ฆ ์ „๊ฐœ",
63
+ "๋ Œ์ฆˆ ์ „ํ™˜: ๋™์ผ ์ฃผ์ œ๋ฅผ ๊ฒฝ์ œ/๊ธฐ์ˆ /์‚ฌํšŒ/๋ฒ•๋ฅ  ๋ Œ์ฆˆ๋กœ ์ˆœํ™˜ ๋ถ„์„",
64
+ "์ผ€์ด์Šค ๋Œ€๋น„: ์ด๋ก  ์„น์…˜๋งˆ๋‹ค ์‹ค์ œ ์‚ฌ๋ก€๋ฅผ ์Œ์œผ๋กœ ๋ฐฐ์น˜",
65
+ "๋ฏธ๋ž˜ ์—ญ์‚ฐ: ๋ชฉํ‘œ ์ƒํƒœ๋ฅผ ๋จผ์ € ์ •์˜ํ•˜๊ณ  ํ˜„์žฌ๋กœ ์—ญ์‚ฐํ•˜์—ฌ ๋‹จ๊ณ„ ์„ค๊ณ„"
66
+ ]
67
+ }
68
+ },
69
+ "ARGUMENT": {
70
+ "desc": "๋…ผ์ฆยท๊ฒ€์ฆ โ€” ์ฃผ์žฅ์„ ๋‹จ๋ จํ•˜๊ณ  ์˜ค๋ฅ˜๋ฅผ ์†Œ๊ฐํ•˜๋Š” ๋น„ํŒ์  ์‚ฌ๊ณ  ์—”์ง„",
71
+ "agent": "็ซ",
72
+ "layer_index": 2,
73
+ "strategies": {
74
+ "๋…ผ์ฆ_ํŒจํ„ด": [
75
+ "์—ฐ์—ญ๋ฒ•: ์ผ๋ฐ˜ ์›์น™ โ†’ ๊ตฌ์ฒด ๊ฒฐ๋ก . ๋ฒ•๋ นยท์ •์ฑ… ๊ทผ๊ฑฐ ํ™œ์šฉ",
76
+ "๊ท€๋‚ฉ๋ฒ•: ์‚ฌ๋ก€ ์ถ•์  โ†’ ์ผ๋ฐ˜ํ™”. ๋ฐ์ดํ„ฐยทํ†ต๊ณ„ ๊ธฐ๋ฐ˜",
77
+ "์œ ์ถ”๋ฒ•: A ๋ถ„์•ผ ์„ฑ๊ณต์‚ฌ๋ก€ โ†’ B ๋ถ„์•ผ ์ ์šฉ ๊ฐ€๋Šฅ์„ฑ ๋…ผ์ฆ",
78
+ "์†Œ๊ฑฐ๋ฒ•: ๊ฐ€๋Šฅํ•œ ๋Œ€์•ˆ์„ ํ•˜๋‚˜์”ฉ ๋ฐฐ์ œํ•˜์—ฌ ์ตœ์ ์•ˆ ๋„์ถœ",
79
+ "๋น„์šฉํŽธ์ต: ์ •๋Ÿ‰์  ๋น„๊ต๋กœ ์‹คํ–‰ ํƒ€๋‹น์„ฑ ๋…ผ์ฆ",
80
+ "๋ฐ˜์‚ฌ์‹ค ๋…ผ์ฆ: '๋งŒ์•ฝ ์ด ์ •์ฑ…์ด ์—†์—ˆ๋‹ค๋ฉด' ๊ฐ€์ƒ ์‹œ๋‚˜๋ฆฌ์˜ค๋กœ ํ•„์š”์„ฑ ์ž…์ฆ"
81
+ ],
82
+ "ํŒฉํŠธ์ฒดํฌ_ํ”„๋กœํ† ์ฝœ": [
83
+ "์ˆ˜์น˜ ๊ฒ€์ฆ: ์ถœ์ฒ˜์˜ ์›๋ณธ ๋ฐ์ดํ„ฐ์™€ ๋Œ€์กฐ",
84
+ "๋…ผ๋ฆฌ ๊ฒ€์ฆ: ์ „์ œ โ†’ ๊ฒฐ๋ก  ์‚ฌ์ด ๋น„์•ฝ์ด ์—†๋Š”์ง€ ์ ๊ฒ€",
85
+ "์‹œ์  ๊ฒ€์ฆ: ๋ฐ์ดํ„ฐ์˜ ๊ธฐ์ค€ ์‹œ์ ์ด ๋ฌธ์„œ ๋งฅ๋ฝ์— ์ ํ•ฉํ•œ์ง€ ํ™•์ธ",
86
+ "๋ฒ”์œ„ ๊ฒ€์ฆ: ์ผ๋ถ€ ์‚ฌ๋ก€๋ฅผ ์ „์ฒด๋กœ ์ผ๋ฐ˜ํ™”ํ•˜๋Š” ์˜ค๋ฅ˜ ํƒ์ง€",
87
+ "์ดํ•ด์ถฉ๋Œ ๊ฒ€์ฆ: ์ถœ์ฒ˜ ๊ธฐ๊ด€์˜ ์ดํ•ด๊ด€๊ณ„๊ฐ€ ๋ฐ์ดํ„ฐ์— ํŽธํ–ฅ์„ ์ค„ ๊ฐ€๋Šฅ์„ฑ ์ ๊ฒ€"
88
+ ],
89
+ "๋น„ํŒ์ _๋ฉ”ํƒ€์ธ์ง€": [
90
+ "์ž๊ธฐ๋ฐ˜๋ฐ•: ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๋…ผ์ฆ์— ๋Œ€ํ•ด ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ๋ฐ˜๋ก ์„ ์Šค์Šค๋กœ ์ œ๊ธฐ",
91
+ "๋งน์  ํƒ์ƒ‰: ๋‚ด ๋ถ„์„์—์„œ ๋น ์ง„ ๊ด€์ ์ด ๋ฌด์—‡์ธ์ง€ ์ฒด๊ณ„์ ์œผ๋กœ ์ ๊ฒ€",
92
+ "ํ™•์ฆํŽธํ–ฅ ์ฐจ๋‹จ: ์ฐฌ์„ฑ ๊ทผ๊ฑฐ๋งŒ ๋ชจ์•˜๋Š”์ง€, ๋ฐ˜๋Œ€ ๊ทผ๊ฑฐ๋„ ๊ท ํ˜• ์žˆ๊ฒŒ ์ˆ˜์ง‘ํ–ˆ๋Š”์ง€ ์ž๋ฌธ",
93
+ "๋ถˆํ™•์‹ค์„ฑ ๋ช…์‹œ: ํ™•์‹คํ•œ ๊ฒƒ๊ณผ ์ถ”์ •์ธ ๊ฒƒ์„ ๊ตฌ๋ถ„ํ•˜์—ฌ ํ‘œํ˜„"
94
+ ]
95
+ }
96
+ },
97
+ "EVIDENCE": {
98
+ "desc": "ํ†ตํ•ฉยท์‹ค์ฒดํ™” โ€” ํฉ์–ด์ง„ ์กฐ๊ฐ์„ ํ•˜๋‚˜์˜ ์™„์„ฑ๋œ ๋ฌธ์„œ๋กœ ์ฃผ์กฐํ•˜๋Š” ์šฉ๊ด‘๋กœ",
99
+ "agent": "ๅœŸ",
100
+ "layer_index": 3,
101
+ "strategies": {
102
+ "ํ†ตํ•ฉ_๊ธฐ๋ฒ•": [
103
+ "๋ชจ์ž์ดํฌ ํ†ตํ•ฉ: ๊ฐ ์—์ด์ „ํŠธ ์‚ฐ์ถœ๋ฌผ์˜ ์ตœ์„  ๋ถ€๋ถ„์„ ์„ ๋ณ„ยท์žฌ์กฐํ•ฉ",
104
+ "๋…ผ๋ฆฌ ์ง์กฐ: ์„น์…˜ ๊ฐ„ ์ธ๊ณผ๊ด€๊ณ„์™€ ๋…ผ๋ฆฌ ์ฒด์ธ์„ ๋ช…์‹œ์ ์œผ๋กœ ์—ฐ๊ฒฐ",
105
+ "ํ†ค ํ†ต์ผ: ์ „์ฒด ๋ฌธ์„œ์˜ ๋ฌธ์ฒดยท๊ฒฝ์–ด๋ฒ•ยท์ „๋ฌธ์šฉ์–ด ์ˆ˜์ค€์„ ์ผ๊ด€๋˜๊ฒŒ ์กฐ์œจ",
106
+ "๋ถ„๋Ÿ‰ ๋ฐธ๋Ÿฐ์‹ฑ: ์„น์…˜๋ณ„ ๋ถ„๋Ÿ‰์„ ์ค‘์š”๋„์— ๋น„๋ก€ํ•˜์—ฌ ์žฌ์กฐ์ •",
107
+ "์ค‘๋ณต ์ œ๊ฑฐ: ์—ฌ๋Ÿฌ ์—์ด์ „ํŠธ๊ฐ€ ๋ฐ˜๋ณตํ•œ ๋‚ด์šฉ์„ ๋‹จ์ผํ™”",
108
+ "ํ‘œยท๋ฐ์ดํ„ฐ ์‚ฝ์ž…: ๋…ผ์ฆ์„ ๋’ท๋ฐ›์นจํ•˜๋Š” ์ •๋Ÿ‰ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ ํ˜•์‹์œผ๋กœ ์ •๋ฆฌ"
109
+ ],
110
+ "์™„์„ฑ๋„_์ฒดํฌ": [
111
+ "LATCH ๊ตฌ์กฐ ๊ฒ€์ฆ: LocationยทAlphabetยทTimeยทCategoryยทHierarchy ์ค‘ ์ ์ ˆํ•œ ๋ถ„๋ฅ˜ ์‚ฌ์šฉ ์—ฌ๋ถ€",
112
+ "So-What ํ…Œ์ŠคํŠธ: ๊ฐ ๋ฌธ๋‹จ์˜ ๋งˆ์ง€๋ง‰์— '๊ทธ๋ž˜์„œ ์–ด๋–ป๋‹ค๋Š” ๊ฒƒ์ธ๊ฐ€?'์— ๋‹ต์ด ์žˆ๋Š”์ง€",
113
+ "Elevator Test: ๋ฌธ์„œ ์ „์ฒด๋ฅผ 30์ดˆ ์š”์•ฝ์œผ๋กœ ์ถ•์•ฝ ๊ฐ€๋Šฅํ•œ์ง€",
114
+ "์‹คํ–‰ ๊ฐ€๋Šฅ์„ฑ: ์ œ์•ˆ๋œ ๋ฐฉ์•ˆ์ด ๊ตฌ์ฒด์  ์‹คํ–‰ ๋‹จ๊ณ„๋ฅผ ํฌํ•จํ•˜๋Š”์ง€"
115
+ ],
116
+ "๋…์ž_์ตœ์ ํ™”": [
117
+ "๋…์ž ํ”„๋กœํŒŒ์ผ๋ง: ์˜์‚ฌ๊ฒฐ์ •๊ถŒ์ž/์‹ค๋ฌด์ž/์ „๋ฌธ๊ฐ€ ๋“ฑ 1์ฐจ ๋…์ž ๋ช…ํ™•ํ™”",
118
+ "์ „๋ฌธ์šฉ์–ด ๊ฒŒ์ดํŠธ: ๋…์ž ์ˆ˜์ค€์— ๋งž๋Š” ์šฉ์–ด ์„ ํƒ, ํ•„์š”์‹œ ๊ด„ํ˜ธ ์„ค๋ช… ์ถ”๊ฐ€",
119
+ "ํ–‰๋™ ์œ ๋„: ๋ฌธ์„œ๋ฅผ ์ฝ์€ ํ›„ ๋…์ž๊ฐ€ ์ทจํ•ด์•ผ ํ•  ๊ตฌ์ฒด์  ํ–‰๋™ ๋ช…์‹œ"
120
+ ]
121
+ }
122
+ },
123
+ "NARRATIVE": {
124
+ "desc": "์„œ์‚ฌยท๋งฅ๋ฝ โ€” ํŒฉํŠธ๋ฅผ ์ด์•ผ๊ธฐ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๊ธฐ์–ต๊ณผ ์„ค๋“๋ ฅ์„ ์ฆํญํ•˜๋Š” ๋ ˆ์ด์–ด",
125
+ "agent": null,
126
+ "layer_index": 4,
127
+ "strategies": {
128
+ "์„œ์‚ฌ_๊ธฐ๋ฒ•": [
129
+ "ํ”„๋ ˆ์ด๋ฐ: ๋™์ผ ์‚ฌ์‹ค์„ '์œ„๊ธฐ' ๋˜๋Š” '๊ธฐํšŒ'๋กœ ํ‹€ ์ง“๊ธฐ์— ๋”ฐ๋ผ ์„ค๋“๋ ฅ ๋ณ€ํ™”",
130
+ "๊ตฌ์ฒดํ™”: ์ถ”์ƒ์  ํ†ต๊ณ„๋ฅผ ๊ฐœ์ธยท๊ธฐ์—…ยท์ง€์—ญ์˜ ๊ตฌ์ฒด์  ์Šคํ† ๋ฆฌ๋กœ ๋ณ€ํ™˜",
131
+ "๋Œ€๋น„ ํšจ๊ณผ: ๋ณ€๊ฒฝ ์ „/ํ›„, ๊ตญ๋‚ด/ํ•ด์™ธ, ํ˜„์žฌ/๋ฏธ๋ž˜๋ฅผ ๋‚˜๋ž€ํžˆ ๋ณด์—ฌ์ฃผ๋Š” ๊ทน์  ๋Œ€๋น„",
132
+ "์ง„ํ–‰ํ˜• ์„œ์‚ฌ: 'ํ˜„์žฌ ์ง„ํ–‰ ์ค‘์ธ ๋ณ€ํ™”'๋ฅผ ๊ฐ•์กฐํ•˜์—ฌ ์‹œ๊ธ‰์„ฑ ๋ถ€์—ฌ",
133
+ "์˜์›…์˜ ์—ฌ์ •: ๋ฌธ์ œ ๋ฐœ๊ฒฌ โ†’ ์‹œ๋„์™€ ์‹คํŒจ โ†’ ํ•ด๊ฒฐ์ฑ… ๋ฐœ๊ฒฌ โ†’ ์„ฑ๊ณผ ๊ตฌ์กฐ"
134
+ ],
135
+ "์ˆ˜์‚ฌ_์žฅ์น˜": [
136
+ "์งˆ๋ฌธ ์‚ฝ์ž…: '๊ณผ์—ฐ ์ด ์ •์ฑ…์€ ํšจ๊ณผ๊ฐ€ ์žˆ์„๊นŒ?' ๊ฐ™์€ ๋…์ž ์ฐธ์—ฌํ˜• ์งˆ๋ฌธ",
137
+ "์ˆซ์ž์˜ ํž˜: '์—ฐ๊ฐ„ 2.3์กฐ์›'๋ณด๋‹ค '๊ตญ๋ฏผ 1์ธ๋‹น ์›” 3,800์›' ๊ฐ™์€ ์ฒด๊ฐ ํ™˜์‚ฐ",
138
+ "์œ ์ถ”: ๋ณต์žกํ•œ ์‹œ์Šคํ…œ์„ ์ผ์ƒ์  ๋น„์œ ๋กœ ์„ค๋ช…",
139
+ "์ ์ง„์  ๊ณต๊ฐœ: ๊ฒฐ๋ก ์„ ์ฆ‰์‹œ ๋…ธ์ถœํ•˜์ง€ ์•Š๊ณ  ๋‹จ์„œ๋ฅผ ์Œ“์•„๊ฐ€๋ฉฐ ๋…ผ๋ฆฌ์  ๊ธด์žฅ๊ฐ ์กฐ์„ฑ"
140
+ ],
141
+ "๋งฅ๋ฝ_์„ค์ •": [
142
+ "์—ญ์‚ฌ์  ๋งฅ๋ฝ: ์ด ์ •์ฑ…/๊ธฐ์ˆ ์ด ํ•„์š”ํ•˜๊ฒŒ ๋œ ์—ญ์‚ฌ์  ๋ฐฐ๊ฒฝ",
143
+ "๊ธ€๋กœ๋ฒŒ ๋งฅ๋ฝ: ํ•ด์™ธ ์ฃผ์š”๊ตญ์˜ ๋™ํ–ฅ๊ณผ ํ•œ๊ตญ์˜ ์œ„์น˜",
144
+ "๊ธฐ์ˆ ์  ๋งฅ๋ฝ: ๊ธฐ๋ฐ˜ ๊ธฐ์ˆ ์˜ ์„ฑ์ˆ™๋„์™€ ์‹คํ˜„ ๊ฐ€๋Šฅ์„ฑ",
145
+ "์ œ๋„์  ๋งฅ๋ฝ: ๊ด€๋ จ ๋ฒ•๋ นยท๊ทœ์ •ยท๊ฑฐ๋ฒ„๋„Œ์Šค ์ฒด๊ณ„"
146
+ ]
147
+ }
148
+ },
149
+ "IMPACT": {
150
+ "desc": "์˜ํ–ฅยท๊ฐ€์น˜ โ€” ๋ฌธ์„œ๊ฐ€ ํ˜„์‹ค์— ๋ฏธ์น˜๋Š” ํŒŒ๊ธ‰๋ ฅ๊ณผ ์˜์‚ฌ๊ฒฐ์ • ์ด‰์ง„๋ ฅ์„ ๊ทน๋Œ€ํ™”",
151
+ "agent": "้‡‘",
152
+ "layer_index": 5,
153
+ "strategies": {
154
+ "์˜ํ–ฅ๋ ฅ_์ฆํญ": [
155
+ "์ •๋Ÿ‰ ๊ธฐ๋Œ€ํšจ๊ณผ: ๋น„์šฉ ์ ˆ๊ฐ, ๋งค์ถœ ์ฆ๋Œ€, ํšจ์œจ ํ–ฅ์ƒ ๋“ฑ ์ˆ˜์น˜๋กœ ํ‘œํ˜„",
156
+ "์ •์„ฑ ๊ธฐ๋Œ€ํšจ๊ณผ: ๊ตญ๋ฏผ ํŽธ์ต, ์‚ฐ์—… ๊ฒฝ์Ÿ๋ ฅ, ์‚ฌํšŒ์  ๊ฐ€์น˜ ๋“ฑ ์งˆ์  ํšจ๊ณผ",
157
+ "๋ฆฌ์Šคํฌ ์™„ํ™”: ๋ฏธ์‹œํ–‰ ์‹œ ๋ฐœ์ƒํ•  ๋ถ€์ •์  ์˜ํ–ฅ์„ ๋Œ€๋น„ ์ œ์‹œ",
158
+ "๋‹จ๊ณ„๋ณ„ ๋งˆ์ผ์Šคํ†ค: ๋‹จ๊ธฐ(3๊ฐœ์›”)/์ค‘๊ธฐ(1๋…„)/์žฅ๊ธฐ(3๋…„) ์„ฑ๊ณผ ๋กœ๋“œ๋งต",
159
+ "ํŒŒ๊ธ‰ ํšจ๊ณผ ์ฒด์ธ: 1์ฐจ ํšจ๊ณผ โ†’ 2์ฐจ ํŒŒ์ƒ ํšจ๊ณผ โ†’ ์‚ฐ์—… ์ƒํƒœ๊ณ„ ๋ณ€ํ™”"
160
+ ],
161
+ "์ตœ์ข…_์ •์ œ": [
162
+ "๋ฌธ์ฒด ์ผ๊ด€์„ฑ: ์ „์ฒด ๋ฌธ์„œ์˜ ์–ด์กฐยท๊ฒฝ์–ด๋ฒ•ยท๋ฒˆํ˜ธ ์ฒด๊ณ„ ํ†ต์ผ",
163
+ "์šฉ์–ด ์ •๋ฐ€๋„: ๋™์˜์–ด ๋ฐ˜๋ณต ์ œ๊ฑฐ, ์•ฝ์–ด ์ฒซ ๋“ฑ์žฅ์‹œ ํ’€๋„ค์ž„ ๋ณ‘๊ธฐ",
164
+ "๋…ผ๋ฆฌ ์™„๊ฒฐ์„ฑ: ์„œ๋ก ์˜ ๋ฌธ์ œ์ œ๊ธฐ๊ฐ€ ๊ฒฐ๋ก ์—์„œ ํ•ด๊ฒฐ๋˜๋Š”์ง€ ํ™•์ธ",
165
+ "ํ˜•์‹ ์ ๊ฒ€: ์ œ๋ชฉยท๋ฒˆํ˜ธยทํ‘œยท์ฐธ๊ณ ๋ฌธํ—Œ ๋“ฑ ์„œ์‹ ๊ทœ์น™ ์ค€์ˆ˜",
166
+ "๊ฐ€๋…์„ฑ ์ตœ์ ํ™”: ๋ฌธ์žฅ ๊ธธ์ด, ๋‹จ๋ฝ ํฌ๊ธฐ, ์—ฌ๋ฐฑ ๊ท ํ˜•"
167
+ ],
168
+ "๋ฉ”ํƒ€์ธ์ง€_์ž๊ธฐ๊ฒ€์ฆ": [
169
+ "์˜ค๋ฅ˜ ๋ณต๊ตฌ: ์ด์ „ ๋‹จ๊ณ„ ์—์ด์ „ํŠธ์˜ ์‹ค์ˆ˜๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ์ž์ฒด ์ˆ˜์ •",
170
+ "์ž๊ธฐ ๋ฐ˜์„ฑ: ๋‚ด ์ •์ œ๊ฐ€ ๊ณผ๋„ํ•˜์ง€ ์•Š์€์ง€, ์›๋ž˜ ์˜๋„๋ฅผ ํ›ผ์†ํ•˜์ง€ ์•Š์•˜๋Š”์ง€ ์ ๊ฒ€",
171
+ "ํ™•์‹ ๋„ ํƒœ๊น…: ๊ฐ ์ฃผ์žฅ์— ๋Œ€ํ•œ ํ™•์‹ ๋„(๋†’์Œ/์ค‘๊ฐ„/๋‚ฎ์Œ)๋ฅผ ๋‚ด๋ถ€์ ์œผ๋กœ ํ‰๊ฐ€",
172
+ "๊ฐœ์„  ๋ฃจํ”„: ์ˆ˜์ •ํ•œ ๋ถ€๋ถ„์ด ์ƒˆ๋กœ์šด ๋ฌธ์ œ๋ฅผ ์•ผ๊ธฐํ•˜์ง€ ์•Š๋Š”์ง€ ์žฌ๊ฒ€ํ† "
173
+ ]
174
+ }
175
+ }
176
+ },
177
+ "cross_layer_bonus": {
178
+ "_desc": "๋ ˆ์ด์–ด ๊ฐ„ ๊ฑฐ๋ฆฌ๊ฐ€ ๋ฉ€์ˆ˜๋ก ๊ต์ฐจ ์ ์šฉ ์‹œ ์ฐฝ๋ฐœ์„ฑ์ด ๋†’๋‹ค. ๊ฐ’ = ์ฐฝ๋ฐœ์„ฑ ๋ณด๋„ˆ์Šค ๊ฐ€์ค‘์น˜.",
179
+ "RESEARCHโ†”IMPACT": {
180
+ "bonus": 0.15,
181
+ "hint": "๋ฐ์ดํ„ฐ์—์„œ ๋ฐ”๋กœ ์‹œ์‚ฌ์ ์„ ๋„์ถœํ•˜๋ฉด ๊ฐ€์žฅ ๋…์ฐฝ์  ํ†ต์ฐฐ์ด ํƒ„์ƒํ•œ๋‹ค. ์ˆ˜์ง‘ ๋‹จ๊ณ„์—์„œ ์ด๋ฏธ '์ด ๋ฐ์ดํ„ฐ๊ฐ€ ์˜์‚ฌ๊ฒฐ์ •์— ์–ด๋–ค ์˜ํ–ฅ์„ ์ค„๊นŒ?'๋ฅผ ์ž๋ฌธํ•˜๋ผ."
182
+ },
183
+ "RESEARCHโ†”NARRATIVE": {
184
+ "bonus": 0.12,
185
+ "hint": "๊ฑด์กฐํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๋ฉด์„œ ๋™์‹œ์— '์ด๊ฒƒ์ด ์–ด๋–ค ์ด์•ผ๊ธฐ๊ฐ€ ๋  ์ˆ˜ ์žˆ์„๊นŒ?'๋ฅผ ์ƒ์ƒํ•˜๋ผ."
186
+ },
187
+ "STRUCTUREโ†”IMPACT": {
188
+ "bonus": 0.11,
189
+ "hint": "๊ตฌ์กฐ ์„ค๊ณ„ ๋‹จ๊ณ„์—์„œ ์ด๋ฏธ '๊ฐ ์„น์…˜์ด ์ตœ์ข… ์˜์‚ฌ๊ฒฐ์ •์— ์–ด๋–ป๊ฒŒ ๊ธฐ์—ฌํ•˜๋Š”๊ฐ€?'๋ฅผ ๊ณ ๋ คํ•˜๋ผ."
190
+ },
191
+ "STRUCTUREโ†”EVIDENCE": {
192
+ "bonus": 0.10,
193
+ "hint": "๊ณจ๊ฒฉ๊ณผ ์‚ด์„ ๋™์‹œ์— ๊ณ ๋ คํ•˜๋ผ. ๊ตฌ์กฐ๊ฐ€ ๊ทผ๊ฑฐ๋ฅผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ๋Š” ํ˜•ํƒœ์ธ์ง€ ๊ฒ€์ฆ."
194
+ },
195
+ "ARGUMENTโ†”NARRATIVE": {
196
+ "bonus": 0.09,
197
+ "hint": "๋…ผ์ฆ๊ณผ ์„œ์‚ฌ๋ฅผ ๊ต์ฐจํ•˜๋ผ. ์ฐจ๊ฐ€์šด ๋…ผ๋ฆฌ์— ๋”ฐ๋œปํ•œ ์ด์•ผ๊ธฐ๋ฅผ ์ž…ํžˆ๋ฉด ์„ค๋“๋ ฅ์ด ๋ฐฐ๊ฐ€๋œ๋‹ค."
198
+ },
199
+ "RESEARCHโ†”ARGUMENT": {
200
+ "bonus": 0.06,
201
+ "hint": "์ˆ˜์ง‘๊ณผ ๊ฒ€์ฆ์„ ๋™์‹œ์— ์ˆ˜ํ–‰ํ•˜๋ผ. ์ž๋ฃŒ๋ฅผ ๋ชจ์œผ๋ฉด์„œ ์‹ ๋ขฐ์„ฑ์„ ์‹ค์‹œ๊ฐ„ ํ‰๊ฐ€."
202
+ },
203
+ "EVIDENCEโ†”IMPACT": {
204
+ "bonus": 0.05,
205
+ "hint": "ํ†ตํ•ฉ ์ž‘์„ฑ ์‹œ ์ตœ์ข… ์ž„ํŒฉํŠธ๋ฅผ ์—ผ๋‘์— ๋‘๋ฉด ๋ถˆํ•„์š”ํ•œ ๋‚ด์šฉ์ด ์ž์—ฐํžˆ ๊ฑธ๋Ÿฌ์ง„๋‹ค."
206
+ }
207
+ },
208
+ "writing_principles": [
209
+ {
210
+ "id": 1,
211
+ "name": "๋ชจ๋“ˆํ™”(๋ถ„ํ• )",
212
+ "hint": "๋ณต์žกํ•œ ์ฃผ์ œ๋ฅผ ๋…๋ฆฝ์  ํ•˜์œ„ ๋ชจ๋“ˆ๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ฐ๊ฐ์„ ์ •๋ฐ€ํ•˜๊ฒŒ ๋‹ค๋ฃจ๋˜, ์ตœ์ข… ํ†ตํ•ฉ ์‹œ ์‹œ๋„ˆ์ง€๋ฅผ ๋…ธ๋ ค๋ผ.",
213
+ "application": "๋Œ€์ฃผ์ œ๋ฅผ 3~5๊ฐœ ๋…๋ฆฝ ์„น์…˜์œผ๋กœ ๋ถ„ํ•ด. ๊ฐ ์„น์…˜์ด ๋‹จ๋…์œผ๋กœ๋„ ์˜๋ฏธ๋ฅผ ๊ฐ–๋˜ ์ „์ฒด ์กฐํ•ฉ ์‹œ ๋” ํฐ ๊ทธ๋ฆผ์„ ํ˜•์„ฑ."
214
+ },
215
+ {
216
+ "id": 2,
217
+ "name": "๋ฐ˜๋ก  ์„ ์ œ(์—ญ๋ฐœ์ƒ)",
218
+ "hint": "๋…์ž๊ฐ€ ์ œ๊ธฐํ•  ๋ฐ˜๋ก ์„ ์˜ˆ์ธกํ•˜์—ฌ ๋จผ์ € ์ œ์‹œํ•˜๊ณ  ๋…ผํŒŒํ•˜๋ผ. '๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ ' ๊ตฌ์กฐ.",
219
+ "application": "๊ฐ ์ œ์•ˆ์˜ ์˜ˆ์ƒ ๋น„ํŒ(๋น„์šฉ, ๋ถ€์ž‘์šฉ, ์‹คํ˜„๊ฐ€๋Šฅ์„ฑ)์„ ๋ฏธ๋ฆฌ ๋ช…์‹œํ•˜๊ณ  ๋Œ€์‘ ๋…ผ๋ฆฌ๋ฅผ ๋ฐฐ์น˜."
220
+ },
221
+ {
222
+ "id": 3,
223
+ "name": "๊ด€์  ์ „ํ™˜(์ฐจ์›๋ณ€๊ฒฝ)",
224
+ "hint": "๋™์ผ ์ฃผ์ œ๋ฅผ ๋ฏธ์‹œโ†”๊ฑฐ์‹œ, ๋‹จ๊ธฐโ†”์žฅ๊ธฐ, ๊ตญ๋‚ดโ†”๊ธ€๋กœ๋ฒŒ, ๊ณต๊ธ‰โ†”์ˆ˜์š” ๋“ฑ ๋‹ค๋ฅธ ์ฐจ์›์—์„œ ๋ฐ”๋ผ๋ณด๋ผ.",
225
+ "application": "ํ•˜๋‚˜์˜ ์ •์ฑ…์„ ์ •๋ถ€/๊ธฐ์—…/๊ตญ๋ฏผ/๊ตญ์ œ์‚ฌํšŒ 4๊ฐœ ๊ด€์ ์—์„œ ๊ฐ๊ฐ ๋ถ„์„ํ•˜์—ฌ ์ž…์ฒด์  ๋ณด๊ณ ์„œ ์ž‘์„ฑ."
226
+ },
227
+ {
228
+ "id": 4,
229
+ "name": "์œตํ•ฉ ์ œ์•ˆ(ํ†ตํ•ฉ)",
230
+ "hint": "์„œ๋กœ ๊ด€๋ จ ์—†์–ด ๋ณด์ด๋Š” ๋ถ„์•ผ๋ฅผ ์—ฐ๊ฒฐํ•˜์—ฌ ๋…์ฐฝ์  ํ•ด๋ฒ•์„ ์ œ์‹œํ•˜๋ผ.",
231
+ "application": "A ๋ถ„์•ผ์˜ ํ•ด๊ฒฐ๋ฒ•์„ B ๋ถ„์•ผ์— ์ด์‹. ์˜ˆ: ํ•€ํ…Œํฌ ๊ทœ์ œ ๊ฒฝํ—˜์„ ํ—ฌ์Šคํ…Œํฌ ๊ทœ์ œ์— ์ ์šฉ."
232
+ },
233
+ {
234
+ "id": 5,
235
+ "name": "์œ„๊ธฐโ†’๊ธฐํšŒ(์ „ํ™”์œ„๋ณต)",
236
+ "hint": "๋ฌธ์ œ์ ยท๋ฆฌ์Šคํฌ๋ฅผ ๋ถ„์„ํ•  ๋•Œ, ๊ทธ ์ž์ฒด๋ฅผ ์ฐจ๋ณ„ํ™” ๊ธฐํšŒ๋กœ ์ „ํ™˜ํ•  ์ˆ˜ ์—†๋Š”์ง€ ํƒ์ƒ‰ํ•˜๋ผ.",
237
+ "application": "๊ทœ์ œ ์žฅ๋ฒฝ โ†’ '๊ทœ์ œ ์ค€์ˆ˜ ์„ ์ ์„ ํ†ตํ•œ ๊ฒฝ์Ÿ ์šฐ์œ„ ํ™•๋ณด' ๊ฐ™์€ ์—ญ์ „ ๋…ผ๋ฆฌ."
238
+ },
239
+ {
240
+ "id": 6,
241
+ "name": "์ž๊ธฐ ์ฐธ์กฐ(๋ฉ”ํƒ€์ธ์ง€)",
242
+ "hint": "๋ฌธ์„œ ์ž์ฒด๊ฐ€ ์ž์‹ ์˜ ํ•œ๊ณ„์™€ ์ „์ œ๋ฅผ ํˆฌ๋ช…ํ•˜๊ฒŒ ๋ฐํžˆ๋ฉด ์‹ ๋ขฐ๋„๊ฐ€ ์˜ฌ๋ผ๊ฐ„๋‹ค.",
243
+ "application": "๋ถ„์„์˜ ํ•œ๊ณ„, ๋ฐ์ดํ„ฐ ์ œ์•ฝ, ์ถ”์ • ๋ฒ”์œ„๋ฅผ ๋ช…์‹œํ•˜์—ฌ ๋…์ž๊ฐ€ ํŒ๋‹จํ•  ๊ทผ๊ฑฐ ์ œ๊ณต."
244
+ },
245
+ {
246
+ "id": 7,
247
+ "name": "๊ณ„๋‹จ์‹ ๊ณต๊ฐœ(์ ์ง„์  ๋ณต์žก์„ฑ)",
248
+ "hint": "๊ฐ„๋‹จํ•œ ๊ฐœ๋…์—์„œ ์‹œ์ž‘ํ•˜์—ฌ ์ ์ง„์ ์œผ๋กœ ๋ณต์žกํ•œ ๋‚ด์šฉ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ผ.",
249
+ "application": "๊ฐœ์š”(1๋ฌธ๋‹จ) โ†’ ํ•ต์‹ฌ(2๋ฌธ๋‹จ) โ†’ ์„ธ๋ถ€(5๋ฌธ๋‹จ) โ†’ ๊ธฐ์ˆ ์  ์ƒ์„ธ(๋ถ€๋ก) ๊ตฌ์กฐ."
250
+ },
251
+ {
252
+ "id": 8,
253
+ "name": "๊ณต๋ฐฑ์˜ ํž˜(์ถ”์ถœ)",
254
+ "hint": "๋ถˆํ•„์š”ํ•œ ๋‚ด์šฉ์„ ๊ณผ๊ฐํžˆ ์ œ๊ฑฐํ•˜๋ผ. ๋นˆ ๊ณต๊ฐ„์ด ํ•ต์‹ฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋‹๋ณด์ด๊ฒŒ ํ•œ๋‹ค.",
255
+ "application": "๊ฐ ์„น์…˜์—์„œ '์—†์–ด๋„ ๋˜๋Š” ๋ฌธ์žฅ'์„ ์ตœ์†Œ 20% ์ œ๊ฑฐ. ํ•ต์‹ฌ๋งŒ ๋‚จ๊ฒจ ๋ฐ€๋„ ํ–ฅ์ƒ."
256
+ }
257
+ ],
258
+ "policy_dilemmas": [
259
+ {
260
+ "dilemma": "๊ทœ์ œ vs ํ˜์‹ ",
261
+ "resolution": "๊ทœ์ œ ์ƒŒ๋“œ๋ฐ•์Šค: ํ•œ์‹œ์  ๊ทœ์ œ ์œ ์˜ˆ๋กœ ํ˜์‹  ์‹คํ—˜ ํ—ˆ์šฉ ํ›„ ๊ฒฐ๊ณผ ๊ธฐ๋ฐ˜ ๊ทœ์ œ ์„ค๊ณ„",
262
+ "writing_tip": "์–‘์ธก ๋…ผ๋ฆฌ๋ฅผ ๊ท ํ˜• ์žˆ๊ฒŒ ์ œ์‹œํ•œ ๋’ค, ์ œ3์˜ ์ ˆ์ถฉ์•ˆ(์ƒŒ๋“œ๋ฐ•์Šค/๋‹จ๊ณ„์  ๋„์ž…)์„ ์ œ์•ˆ"
263
+ },
264
+ {
265
+ "dilemma": "๋ณด์•ˆ vs ํŽธ์˜์„ฑ",
266
+ "resolution": "๋ฆฌ์Šคํฌ ๊ธฐ๋ฐ˜ ์ธ์ฆ: ์ €์œ„ํ—˜ ๊ฑฐ๋ž˜๋Š” ๊ฐ„ํŽธ ์ธ์ฆ, ๊ณ ์œ„ํ—˜ ๊ฑฐ๋ž˜๋งŒ ๊ฐ•ํ™” ์ธ์ฆ",
267
+ "writing_tip": "์ด๋ถ„๋ฒ•์„ ํ”ผํ•˜๊ณ  '์œ„ํ—˜ ์ˆ˜์ค€๋ณ„ ์ฐจ๋“ฑ ์ ‘๊ทผ' ๊ฐ™์€ ๊ทธ๋ผ๋ฐ์ด์…˜ ํ•ด๋ฒ•์„ ์ œ์‹œ"
268
+ },
269
+ {
270
+ "dilemma": "์ค‘์•™์ง‘์ค‘ vs ๋ถ„์‚ฐ",
271
+ "resolution": "ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๊ฑฐ๋ฒ„๋„Œ์Šค: ํ‘œ์ค€ยท๊ฐ๋…์€ ์ค‘์•™, ์‹คํ–‰ยทํ˜์‹ ์€ ๋ถ„์‚ฐ",
272
+ "writing_tip": "๊ฐ ๋ชจ๋ธ์˜ ์žฅ๋‹จ์ ์„ ํ‘œ๋กœ ๋น„๊ตํ•œ ๋’ค ๊ฒฐํ•ฉ ๋ชจ๋ธ์˜ ์‹œ๋„ˆ์ง€๋ฅผ ๋…ผ์ฆ"
273
+ },
274
+ {
275
+ "dilemma": "์„ฑ๊ณผ vs ํ˜•ํ‰",
276
+ "resolution": "์ฐจ๋“ฑ ์ง€์›: ์„ฑ๊ณผ ๊ธฐ๋ฐ˜ ์ธ์„ผํ‹ฐ๋ธŒ + ๊ธฐ์ดˆ ์—ญ๋Ÿ‰ ๊ฐ•ํ™” ์ง€์› ๋ณ‘ํ–‰",
277
+ "writing_tip": "๊ฒฝ์Ÿ๊ณผ ํฌ์šฉ์˜ ๊ท ํ˜•์ ์„ ์ˆ˜์น˜ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์œผ๋กœ ์ œ์‹œ"
278
+ },
279
+ {
280
+ "dilemma": "๋‹จ๊ธฐ vs ์žฅ๊ธฐ",
281
+ "resolution": "๋‹จ๊ณ„๋ณ„ ๋กœ๋“œ๋งต: ๋‹จ๊ธฐ Quick-Win์œผ๋กœ ์ถ”์ง„๋ ฅ ํ™•๋ณด, ์žฅ๊ธฐ ๊ตฌ์กฐ๊ฐœํ˜์œผ๋กœ ์ง€์†์„ฑ ํ™•๋ณด",
282
+ "writing_tip": "๋กœ๋“œ๋งต ํ‘œ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋‹จ๊ธฐ(~6๊ฐœ์›”)/์ค‘๊ธฐ(1~2๋…„)/์žฅ๊ธฐ(3~5๋…„) ๋งˆ์ผ์Šคํ†ค ์ œ์‹œ"
283
+ },
284
+ {
285
+ "dilemma": "๊ฐœ๋ฐฉ vs ๋ณดํ˜ธ",
286
+ "resolution": "์กฐ๊ฑด๋ถ€ ๊ฐœ๋ฐฉ: ํ•ต์‹ฌ ๊ธฐ์ˆ ์€ ๋ณดํ˜ธํ•˜๋˜ ์ƒํƒœ๊ณ„ ํ™œ์„ฑํ™”๋ฅผ ์œ„ํ•ด ์ฃผ๋ณ€ ๊ธฐ์ˆ ์€ ๊ฐœ๋ฐฉ",
287
+ "writing_tip": "๊ฐœ๋ฐฉ ๋ฒ”์œ„์™€ ๋ณดํ˜ธ ๋ฒ”์œ„๋ฅผ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•˜๋Š” ๋งคํŠธ๋ฆญ์Šค ์ œ์‹œ"
288
+ }
289
+ ],
290
+ "perspective_transforms": [
291
+ {
292
+ "from": "๋ฏธ์‹œ(๊ฐœ๋ณ„ ์‚ฌ๋ก€)",
293
+ "to": "๊ฑฐ์‹œ(์‹œ์Šคํ…œ ์ „์ฒด)",
294
+ "hint": "๊ฐœ๋ณ„ ๊ธฐ์—… ์‚ฌ๋ก€์—์„œ ์‹œ์ž‘ํ•˜์—ฌ ์‚ฐ์—… ์ „์ฒด ํŠธ๋ Œ๋“œ๋กœ ํ™•์žฅ",
295
+ "writing_tip": "์‚ฌ๋ก€ โ†’ ํŒจํ„ด โ†’ ์‹œ์‚ฌ์  โ†’ ์ •์ฑ… ์ œ์–ธ ์ˆœ์„œ๋กœ ์ƒํ–ฅ ์ „๊ฐœ"
296
+ },
297
+ {
298
+ "from": "ํ˜„์žฌ(As-Is)",
299
+ "to": "๋ฏธ๋ž˜(To-Be)",
300
+ "hint": "ํ˜„์žฌ ์ƒํƒœ๋ฅผ ์ง„๋‹จํ•œ ๋’ค ๋ชฉํ‘œ ์ƒํƒœ๋ฅผ ์„ ๋ช…ํ•˜๊ฒŒ ๊ทธ๋ฆฌ๊ณ  ๊ฐญ์„ ๋ถ„์„",
301
+ "writing_tip": "As-Is/To-Be ๋น„๊ตํ‘œ + ๊ฐญ ๋ถ„์„ + ์ดํ–‰ ๋กœ๋“œ๋งต 3์ข… ์„ธํŠธ"
302
+ },
303
+ {
304
+ "from": "๊ณต๊ธ‰์ž ๊ด€์ ",
305
+ "to": "์ˆ˜์š”์ž ๊ด€์ ",
306
+ "hint": "์ •์ฑ… ์ž…์•ˆ์ž/๊ธฐ์—…์ด ์•„๋‹Œ ์ตœ์ข… ์‚ฌ์šฉ์ž/๊ตญ๋ฏผ์˜ ์‹œ์„ ์—์„œ ์žฌํ•ด์„",
307
+ "writing_tip": "์‚ฌ์šฉ์ž ์—ฌ์ • ๋งตํ•‘์œผ๋กœ Pain Point๋ฅผ ์‹œ๊ฐํ™”ํ•˜๋ฉด ์„ค๋“๋ ฅ์ด ๋ฐฐ๊ฐ€"
308
+ },
309
+ {
310
+ "from": "์ •๋Ÿ‰(์ˆซ์ž)",
311
+ "to": "์ •์„ฑ(์˜๋ฏธ)",
312
+ "hint": "ํ†ต๊ณ„ ๋’ค์— ์ˆจ์€ ์ธ๊ฐ„์  ์˜๋ฏธ์™€ ์‚ฌํšŒ์  ๋งฅ๋ฝ์„ ํ•จ๊ป˜ ์„œ์ˆ ",
313
+ "writing_tip": "์ˆ˜์น˜ ์ œ์‹œ ์งํ›„ '์ด๊ฒƒ์ด ์˜๋ฏธํ•˜๋Š” ๋ฐ”๋Š”' ๋ฌธ์žฅ์œผ๋กœ ํ•ด์„ ๋ ˆ์ด์–ด ์ถ”๊ฐ€"
314
+ },
315
+ {
316
+ "from": "๊ตญ๋‚ด",
317
+ "to": "๊ธ€๋กœ๋ฒŒ",
318
+ "hint": "ํ•œ๊ตญ ์ƒํ™ฉ์„ ๊ธ€๋กœ๋ฒŒ ๋ฒค์น˜๋งˆํฌ์™€ ๋น„๊ตํ•˜์—ฌ ์œ„์น˜ ํŒŒ์•…",
319
+ "writing_tip": "์ฃผ์š”๊ตญ ๋น„๊ตํ‘œ(๋ฏธ๊ตญ/EU/์ผ๋ณธ/ํ•œ๊ตญ)๋กœ ๊ฐ๊ด€์  ํฌ์ง€์…”๋‹ ์ œ์‹œ"
320
+ },
321
+ {
322
+ "from": "๊ธฐ์ˆ ",
323
+ "to": "์ œ๋„",
324
+ "hint": "๊ธฐ์ˆ ์  ๊ฐ€๋Šฅ์„ฑ์„ ์ œ๋„์  ์‹คํ˜„๊ฐ€๋Šฅ์„ฑ๊ณผ ๊ต์ฐจ ๊ฒ€ํ† ",
325
+ "writing_tip": "๊ธฐ์ˆ  ์„ฑ์ˆ™๋„(TRL)์™€ ๊ทœ์ œ ์ค€๋น„๋„๋ฅผ 2ร—2 ๋งคํŠธ๋ฆญ์Šค๋กœ ๋™์‹œ ํ‰๊ฐ€"
326
+ }
327
+ ],
328
+ "metacognitive_protocols": {
329
+ "_desc": "์—์ด์ „ํŠธ ๊ฐ„ ์ƒํ˜ธ ๊ฒ€์ฆ(์ƒ๊ทน)์„ ๋ฉ”ํƒ€์ธ์ง€์  ์ž๊ธฐ๊ฒ€์ฆ์œผ๋กœ ๊ตฌํ˜„ํ•˜๋Š” ํ”„๋กœํ† ์ฝœ",
330
+ "self_reflection": {
331
+ "trigger": "๊ฐ ์—์ด์ „ํŠธ ์ถœ๋ ฅ ์™„๋ฃŒ ์ง์ „",
332
+ "questions": [
333
+ "๋‚ด ๋ถ„์„์—์„œ ๊ฐ€์žฅ ์•ฝํ•œ ๊ณ ๋ฆฌ(weakest link)๋Š” ์–ด๋””์ธ๊ฐ€?",
334
+ "๋‚ด๊ฐ€ ์•”๋ฌต์ ์œผ๋กœ ๊ฐ€์ •ํ•˜๊ณ  ์žˆ๋Š” ์ „์ œ๋Š” ๋ฌด์—‡์ธ๊ฐ€?",
335
+ "๋ฐ˜๋Œ€ ์ž…์žฅ์—์„œ ๋ณด๋ฉด ์–ด๋–ค ๋ฐ˜๋ก ์ด ๊ฐ€๋Šฅํ•œ๊ฐ€?",
336
+ "์ด ๋ฌธ์„œ๋ฅผ ์ฝ๋Š” ๋น„์ „๋ฌธ๊ฐ€๋„ ํ•ต์‹ฌ์„ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€?"
337
+ ]
338
+ },
339
+ "error_recovery": {
340
+ "trigger": "๋‹ค์Œ ์—์ด์ „ํŠธ๊ฐ€ ์ด์ „ ์—์ด์ „ํŠธ์˜ ์ถœ๋ ฅ์„ ๋ฐ›์„ ๋•Œ",
341
+ "protocol": [
342
+ "์ด์ „ ์—์ด์ „ํŠธ์˜ ์ฃผ์žฅ ์ค‘ ๊ทผ๊ฑฐ๊ฐ€ ๋ถˆ์ถฉ๋ถ„ํ•œ ํ•ญ๋ชฉ ์‹๋ณ„",
343
+ "๋…ผ๋ฆฌ์  ๋น„์•ฝ ๋˜๋Š” ์ž๊ธฐ๋ชจ์ˆœ ํƒ์ง€",
344
+ "๋ˆ„๋ฝ๋œ ์ค‘์š” ๊ด€์  ์ถ”๊ฐ€ ์ œ์•ˆ",
345
+ "์ˆ˜์ • ์‚ฌํ•ญ์„ [์ˆ˜์ •] ํƒœ๊ทธ๋กœ ๋ช…์‹œํ•˜์—ฌ ํˆฌ๋ช…์„ฑ ํ™•๋ณด"
346
+ ]
347
+ },
348
+ "confidence_calibration": {
349
+ "levels": {
350
+ "HIGH": "3๊ฐœ ์ด์ƒ ๋…๋ฆฝ ์ถœ์ฒ˜์—์„œ ๊ต์ฐจ ๊ฒ€์ฆ๋œ ํŒฉํŠธ",
351
+ "MEDIUM": "1~2๊ฐœ ์ถœ์ฒ˜ ๊ธฐ๋ฐ˜, ๋…ผ๋ฆฌ์ ์œผ๋กœ ํƒ€๋‹นํ•˜๋‚˜ ์ถ”๊ฐ€ ๊ฒ€์ฆ ๊ถŒ์žฅ",
352
+ "LOW": "์ถ”์ •ยท์ถ”๋ก  ๊ธฐ๋ฐ˜, ๋ฐ˜๋“œ์‹œ '์ถ”์ •' ๋˜๋Š” '๊ฐ€๋Šฅ์„ฑ' ํ‘œํ˜„ ์‚ฌ์šฉ"
353
+ }
354
+ }
355
+ }
356
+ }
index.html ADDED
@@ -0,0 +1,609 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>SOMA Pre-AGI ยท Metacognitive Document Engine</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Sora:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
8
+ <style>
9
+ *{margin:0;padding:0;box-sizing:border-box;}
10
+ :root{
11
+ --bg:#f8f9fc;--bg2:#f0f2f8;--surface:#ffffff;--surface2:#f5f6fa;
12
+ --border:#e2e5f0;--border2:#c7cce0;
13
+ --text:#0f172a;--text2:#475569;--muted:#94a3b8;--muted2:#cbd5e1;
14
+ --shadow:0 4px 16px rgba(15,23,42,.06),0 1px 3px rgba(15,23,42,.08);
15
+ --shadow-sm:0 1px 3px rgba(15,23,42,.04),0 1px 2px rgba(15,23,42,.06);
16
+ --shadow-lg:0 12px 40px rgba(15,23,42,.08),0 4px 12px rgba(15,23,42,.06);
17
+ --ac:#6366f1;--ac2:#4f46e5;--ac-bg:rgba(99,102,241,.06);
18
+ --re:#0EA5E9;--st:#16A34A;--ar:#DC2626;--ev:#D97706;--im:#7C3AED;
19
+ --re-bg:#EFF8FF;--st-bg:#F0FDF4;--ar-bg:#FFF5F5;--ev-bg:#FFFBEB;--im-bg:#F5F3FF;
20
+ --radius:14px;--radius-sm:10px;
21
+ --font:'Sora',sans-serif;--mono:'JetBrains Mono',monospace;
22
+ --tr:0.22s cubic-bezier(0.4,0,0.2,1);
23
+ }
24
+ html,body{height:100%;overflow:hidden;font-family:var(--font);background:var(--bg);color:var(--text);font-size:13px;-webkit-font-smoothing:antialiased;}
25
+ body::before{content:"";position:fixed;inset:0;z-index:0;pointer-events:none;
26
+ background:radial-gradient(ellipse 70% 45% at 15% 8%,rgba(99,102,241,.05),transparent 55%),
27
+ radial-gradient(ellipse 55% 35% at 85% 92%,rgba(13,148,136,.04),transparent 50%);}
28
+ ::-webkit-scrollbar{width:4px;height:4px;}
29
+ ::-webkit-scrollbar-track{background:transparent;}
30
+ ::-webkit-scrollbar-thumb{background:rgba(99,102,241,.2);border-radius:10px;}
31
+ ::selection{background:rgba(99,102,241,.12);}
32
+
33
+ @keyframes fadeIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
34
+ @keyframes shimmer{0%,100%{background-position:0%}50%{background-position:100%}}
35
+ @keyframes pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.8)}}
36
+ @keyframes spin{to{transform:rotate(360deg)}}
37
+ @keyframes glow{0%,100%{box-shadow:0 0 8px var(--ac)}50%{box-shadow:0 0 20px var(--ac)}}
38
+
39
+ /* TOPBAR */
40
+ .topbar{position:fixed;top:0;left:0;right:0;z-index:1000;height:48px;display:flex;align-items:center;gap:12px;padding:0 20px;
41
+ background:rgba(255,255,255,.85);backdrop-filter:blur(12px);border-bottom:1px solid var(--border);box-shadow:var(--shadow-sm);}
42
+ .topbar-logo{font-family:var(--mono);font-size:11px;font-weight:800;letter-spacing:2px;
43
+ background:linear-gradient(135deg,var(--re),var(--st),var(--ar),var(--ev),var(--im));background-size:300%;
44
+ animation:shimmer 6s ease-in-out infinite;-webkit-background-clip:text;-webkit-text-fill-color:transparent;}
45
+ .topbar-sub{font-size:9.5px;color:var(--muted);font-family:var(--mono);letter-spacing:.3px;}
46
+ .topbar-sep{width:1px;height:22px;background:var(--border);margin:0 2px;}
47
+ .topbar-badge{display:flex;align-items:center;gap:5px;background:var(--surface2);border:1px solid var(--border);
48
+ border-radius:20px;padding:3px 10px;font-family:var(--mono);font-size:8.5px;color:var(--text2);font-weight:600;}
49
+ .topbar-badge .pulse-dot{width:5px;height:5px;border-radius:50%;animation:pulse 2s infinite;}
50
+ .chip{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border-radius:20px;font-family:var(--mono);font-size:8.5px;font-weight:600;}
51
+ .chip-idle{background:var(--surface2);color:var(--muted);border:1px solid var(--border);}
52
+ .chip-run{background:var(--ac-bg);color:var(--ac);border:1px solid rgba(99,102,241,.3);animation:glow 2s infinite;}
53
+ .chip-done{background:rgba(22,163,74,.08);color:#16a34a;border:1px solid rgba(22,163,74,.3);}
54
+ .chip-err{background:rgba(220,38,38,.08);color:#DC2626;border:1px solid rgba(220,38,38,.3);}
55
+ .ml-auto{margin-left:auto;}
56
+ .topbar-clock{font-family:var(--mono);font-size:9px;color:var(--muted);letter-spacing:.5px;}
57
+
58
+ /* LAYOUT */
59
+ .main-layout{position:fixed;top:48px;left:0;right:0;bottom:44px;display:grid;
60
+ grid-template-columns:220px 1fr 230px;gap:0;overflow:hidden;z-index:1;}
61
+
62
+ /* PANELS */
63
+ .panel-left,.panel-right{background:var(--surface);border-right:1px solid var(--border);overflow-y:auto;padding:12px;}
64
+ .panel-right{border-right:none;border-left:1px solid var(--border);}
65
+ .panel-center{display:flex;flex-direction:column;overflow:hidden;background:var(--bg);}
66
+ .panel-section{margin-bottom:14px;padding-bottom:12px;border-bottom:1px solid var(--border);}
67
+ .panel-section:last-child{border-bottom:none;}
68
+ .panel-heading{font-family:var(--mono);font-size:8.5px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:var(--muted);margin-bottom:10px;}
69
+
70
+ /* AGENT CARDS */
71
+ .agent-card{padding:10px 12px;border-radius:var(--radius-sm);border:1px solid var(--border);margin-bottom:6px;transition:var(--tr);cursor:default;position:relative;overflow:hidden;}
72
+ .agent-card::before{content:'';position:absolute;inset:0;opacity:0;transition:opacity .3s;pointer-events:none;}
73
+ .agent-card.active{border-color:var(--ac);box-shadow:0 0 0 2px rgba(99,102,241,.15);}
74
+ .agent-card.re::before{background:linear-gradient(135deg,rgba(14,165,233,.06),transparent);}
75
+ .agent-card.st::before{background:linear-gradient(135deg,rgba(22,163,74,.06),transparent);}
76
+ .agent-card.ar::before{background:linear-gradient(135deg,rgba(220,38,38,.06),transparent);}
77
+ .agent-card.ev::before{background:linear-gradient(135deg,rgba(217,119,6,.06),transparent);}
78
+ .agent-card.im::before{background:linear-gradient(135deg,rgba(124,58,237,.06),transparent);}
79
+ .agent-card.active::before{opacity:1;}
80
+ .agent-top{display:flex;align-items:center;gap:8px;}
81
+ .agent-emoji{font-size:18px;width:28px;text-align:center;}
82
+ .agent-name{font-family:var(--mono);font-size:10px;font-weight:700;letter-spacing:.5px;}
83
+ .agent-name.re{color:var(--re);}.agent-name.st{color:var(--st);}.agent-name.ar{color:var(--ar);}.agent-name.ev{color:var(--ev);}.agent-name.im{color:var(--im);}
84
+ .agent-role{font-size:9px;color:var(--muted);}
85
+ .agent-status{margin-left:auto;font-family:var(--mono);font-size:8px;font-weight:700;letter-spacing:.5px;padding:2px 8px;border-radius:10px;}
86
+ .agent-status.idle{background:var(--surface2);color:var(--muted2);}
87
+ .agent-status.run{background:var(--ac-bg);color:var(--ac);}
88
+ .agent-status.done{background:rgba(22,163,74,.08);color:#16a34a;}
89
+ .flow-line{text-align:center;padding:2px 0;font-size:10px;color:var(--muted2);display:flex;align-items:center;justify-content:center;gap:6px;}
90
+ .flow-arrow{color:var(--ac);font-weight:700;}
91
+
92
+ /* SEARCH COUNTER */
93
+ .sc-bar-wrap{height:4px;background:var(--surface2);border-radius:2px;margin-top:6px;overflow:hidden;}
94
+ .sc-bar{height:100%;background:linear-gradient(90deg,var(--re),var(--ac));border-radius:2px;width:0;transition:width .5s;}
95
+ .sc-label{font-family:var(--mono);font-size:8px;color:var(--muted);text-transform:uppercase;letter-spacing:1px;font-weight:600;}
96
+ .sc-value{font-family:var(--mono);font-size:20px;font-weight:800;color:var(--ac);}
97
+ .sc-max{font-family:var(--mono);font-size:10px;color:var(--muted);}
98
+
99
+ /* TABS */
100
+ .tab-nav{display:flex;gap:0;border-bottom:1.5px solid var(--border);background:var(--surface);padding:0 16px;}
101
+ .tab-btn{padding:10px 18px;font-family:var(--mono);font-size:10px;font-weight:600;color:var(--muted);border:none;background:none;
102
+ cursor:pointer;border-bottom:2px solid transparent;transition:var(--tr);letter-spacing:.3px;}
103
+ .tab-btn:hover{color:var(--text);background:var(--ac-bg);}
104
+ .tab-btn.active{color:var(--ac);border-bottom-color:var(--ac);background:var(--ac-bg);}
105
+ .tab-content{display:none;flex:1;overflow:hidden;flex-direction:column;}
106
+ .tab-content.active{display:flex;}
107
+
108
+ /* PROMPT TAB */
109
+ .prompt-area{padding:16px;border-bottom:1px solid var(--border);background:var(--surface);}
110
+ .prompt-label{font-family:var(--mono);font-size:9px;font-weight:700;letter-spacing:1px;text-transform:uppercase;color:var(--muted);margin-bottom:8px;display:flex;align-items:center;gap:6px;}
111
+ .prompt-wrap{position:relative;}
112
+ .prompt-input{width:100%;padding:12px 14px;border:1.5px solid var(--border);border-radius:var(--radius-sm);font-family:var(--font);font-size:12.5px;
113
+ color:var(--text);background:var(--surface);resize:none;outline:none;transition:var(--tr);line-height:1.6;}
114
+ .prompt-input:focus{border-color:var(--ac);box-shadow:0 0 0 3px rgba(99,102,241,.08);}
115
+ .btn-row{display:flex;gap:8px;margin-top:10px;align-items:center;}
116
+ .btn{display:inline-flex;align-items:center;gap:6px;padding:8px 18px;border:1.5px solid var(--border);border-radius:var(--radius-sm);
117
+ font-family:var(--mono);font-size:10px;font-weight:600;cursor:pointer;transition:var(--tr);letter-spacing:.3px;}
118
+ .btn-primary{background:linear-gradient(135deg,var(--ac),var(--ac2));color:#fff;border-color:transparent;box-shadow:0 3px 12px rgba(99,102,241,.2);}
119
+ .btn-primary:hover{box-shadow:0 6px 20px rgba(99,102,241,.3);transform:translateY(-1px);}
120
+ .btn-primary.running{background:linear-gradient(135deg,#DC2626,#B91C1C);}
121
+ .btn-secondary{background:var(--surface);color:var(--text2);}
122
+ .btn-secondary:hover{background:var(--ac-bg);border-color:rgba(99,102,241,.3);color:var(--ac);}
123
+ .btn-success{background:linear-gradient(135deg,#16a34a,#15803d);color:#fff;border-color:transparent;}
124
+ .spinner{width:12px;height:12px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;display:none;}
125
+ .spinner.visible{display:inline-block;animation:spin .8s linear infinite;}
126
+ .example-btn{display:block;width:100%;text-align:left;padding:8px 12px;margin-bottom:5px;border:1px solid var(--border);border-radius:8px;
127
+ background:var(--surface);font-size:11px;color:var(--text2);cursor:pointer;transition:var(--tr);line-height:1.5;}
128
+ .example-btn:hover{border-color:var(--ac);background:var(--ac-bg);color:var(--ac);}
129
+ .api-warn{display:none;padding:8px 12px;background:rgba(217,119,6,.06);border:1px solid rgba(217,119,6,.2);border-radius:8px;
130
+ font-size:10px;color:#B45309;margin-bottom:8px;}
131
+
132
+ /* STREAM */
133
+ .stream-wrap{flex:1;display:flex;flex-direction:column;overflow:hidden;}
134
+ .stream-header{display:flex;align-items:center;gap:8px;padding:10px 16px;border-bottom:1px solid var(--border);background:var(--surface);}
135
+ .stream-box{flex:1;overflow-y:auto;padding:16px;font-family:var(--mono);font-size:11px;line-height:1.8;color:var(--text2);
136
+ background:linear-gradient(180deg,var(--bg),var(--surface2));white-space:pre-wrap;word-break:break-word;}
137
+ .stream-empty{color:var(--muted);font-style:italic;}
138
+
139
+ /* DOC */
140
+ .doc-wrap{flex:1;display:flex;flex-direction:column;overflow:hidden;padding:16px;}
141
+ .doc-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:10px;}
142
+ .doc-area{flex:1;overflow-y:auto;padding:20px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
143
+ font-size:12.5px;line-height:1.9;color:var(--text);box-shadow:var(--shadow-sm);white-space:pre-wrap;word-break:break-word;}
144
+ .doc-empty{color:var(--muted);text-align:center;padding:60px 20px;font-size:13px;}
145
+
146
+ /* LOG */
147
+ .log-wrap{flex:1;display:flex;flex-direction:column;overflow:hidden;padding:16px;}
148
+ .log-box{flex:1;overflow-y:auto;padding:12px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
149
+ font-family:var(--mono);font-size:10px;line-height:1.7;color:var(--text2);white-space:pre-wrap;}
150
+
151
+ /* SETTINGS */
152
+ .setting-item{margin-bottom:14px;}
153
+ .setting-label{font-size:11px;color:var(--text2);display:flex;justify-content:space-between;margin-bottom:6px;}
154
+ .setting-val{font-family:var(--mono);font-weight:700;color:var(--ac);}
155
+ input[type=range]{width:100%;accent-color:var(--ac);height:4px;}
156
+ .pipeline-flow{display:flex;align-items:center;gap:4px;flex-wrap:wrap;padding:6px 0;}
157
+ .pf-node{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;
158
+ border:2px solid var(--border);background:var(--surface);transition:var(--tr);}
159
+ .pf-node.active{border-color:var(--ac);box-shadow:0 0 0 3px rgba(99,102,241,.15);transform:scale(1.15);}
160
+ .pf-arrow{font-size:10px;color:var(--muted2);}
161
+ .dl-area{padding:10px 0;}
162
+ .dl-filename{font-family:var(--mono);font-size:10px;color:var(--text2);margin-bottom:8px;padding:6px 10px;background:var(--surface2);border-radius:6px;border:1px solid var(--border);}
163
+ .dl-status{font-family:var(--mono);font-size:9px;color:var(--muted);margin-top:6px;}
164
+ .dl-status.ok{color:#16a34a;}.dl-status.err{color:#DC2626;}
165
+
166
+ /* SEARCH FEED */
167
+ .search-feed{position:fixed;bottom:44px;left:0;width:220px;height:140px;overflow-y:auto;
168
+ background:var(--surface);border-top:1px solid var(--border);border-right:1px solid var(--border);z-index:2;padding:8px 10px;}
169
+ .search-feed-h{font-family:var(--mono);font-size:8px;font-weight:700;letter-spacing:1px;text-transform:uppercase;color:var(--muted);
170
+ margin-bottom:6px;display:flex;align-items:center;gap:5px;}
171
+ .sf-item{font-family:var(--mono);font-size:9px;color:var(--text2);padding:3px 0;display:flex;gap:6px;border-bottom:1px solid rgba(0,0,0,.03);animation:fadeIn .3s;}
172
+ .sf-num{color:var(--muted);min-width:24px;}.sf-text{color:var(--text2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
173
+
174
+ /* STATS BAR */
175
+ .stats-bar{position:fixed;bottom:0;left:0;right:0;height:44px;z-index:1000;display:flex;align-items:center;gap:0;
176
+ background:rgba(255,255,255,.9);backdrop-filter:blur(12px);border-top:1px solid var(--border);padding:0 16px;}
177
+ .stat-box{flex:1;text-align:center;padding:4px 0;}
178
+ .stat-val{font-family:var(--mono);font-size:13px;font-weight:800;}
179
+ .stat-val.re{color:var(--re);}.stat-val.st{color:var(--st);}.stat-val.ar{color:var(--ar);}.stat-val.ev{color:var(--ev);}.stat-val.im{color:var(--im);}.stat-val.gray{color:var(--muted);}
180
+ .stat-label{font-size:8px;color:var(--muted);font-family:var(--mono);letter-spacing:.3px;}
181
+
182
+ /* TOAST */
183
+ .toast{position:fixed;bottom:60px;left:50%;transform:translateX(-50%) translateY(20px);opacity:0;padding:10px 24px;
184
+ background:var(--text);color:#fff;border-radius:var(--radius-sm);font-family:var(--mono);font-size:11px;font-weight:500;
185
+ box-shadow:var(--shadow-lg);transition:all .3s;z-index:9999;pointer-events:none;}
186
+ .toast.visible{opacity:1;transform:translateX(-50%) translateY(0);}
187
+ .toast.ok{background:#16a34a;}.toast.err{background:#DC2626;}
188
+
189
+ /* DIAGRAM */
190
+ .meta-diagram{display:flex;flex-direction:column;gap:4px;margin-top:8px;font-family:var(--mono);font-size:9px;}
191
+ .md-row{display:flex;align-items:center;gap:6px;padding:2px 0;}
192
+ .md-arrow{color:var(--ac);font-weight:700;font-size:10px;}
193
+ .md-label{color:var(--muted);font-size:8px;margin-left:auto;}
194
+ </style>
195
+ </head>
196
+ <body>
197
+
198
+ <!-- TOPBAR -->
199
+ <div class="topbar">
200
+ <div class="topbar-logo">SOMA PRE-AGI</div>
201
+ <div class="topbar-sep"></div>
202
+ <div class="topbar-sub">Metacognitive Document Engine</div>
203
+ <div class="topbar-sep"></div>
204
+ <div class="topbar-badge"><div class="pulse-dot" style="background:var(--ac)"></div>EMERGENCE MATRIX v1.0</div>
205
+ <div class="topbar-sep"></div>
206
+ <div class="chip chip-idle" id="pipelineChip">โ— IDLE</div>
207
+ <div class="ml-auto"></div>
208
+ <div class="topbar-clock" id="clock"></div>
209
+ </div>
210
+
211
+ <div class="main-layout">
212
+
213
+ <!-- LEFT PANEL -->
214
+ <div class="panel-left">
215
+ <div class="panel-section">
216
+ <div class="panel-heading">Agent Pipeline</div>
217
+
218
+ <div class="agent-card re" id="card-ๆฐด">
219
+ <div class="agent-top">
220
+ <span class="agent-emoji">๐Ÿ”</span>
221
+ <div><div class="agent-name re">RESEARCH</div><div class="agent-role">ํƒ์ƒ‰ ยท ์ •๋ณด์ˆ˜์ง‘</div></div>
222
+ <div class="agent-status idle" id="st-ๆฐด">IDLE</div>
223
+ </div>
224
+ </div>
225
+ <div class="flow-line"><span class="flow-arrow">โ†“</span><span style="font-size:7px;color:var(--muted2)">PIPE</span></div>
226
+
227
+ <div class="agent-card st" id="card-ๆœจ">
228
+ <div class="agent-top">
229
+ <span class="agent-emoji">๐Ÿงฉ</span>
230
+ <div><div class="agent-name st">STRUCTURE</div><div class="agent-role">์„ค๊ณ„ ยท ๊ตฌ์กฐํ™”</div></div>
231
+ <div class="agent-status idle" id="st-ๆœจ">IDLE</div>
232
+ </div>
233
+ </div>
234
+ <div class="flow-line"><span class="flow-arrow">โ†“</span><span style="font-size:7px;color:var(--muted2)">PIPE</span></div>
235
+
236
+ <div class="agent-card ar" id="card-็ซ">
237
+ <div class="agent-top">
238
+ <span class="agent-emoji">๐Ÿ”ฌ</span>
239
+ <div><div class="agent-name ar">ARGUMENT</div><div class="agent-role">๋…ผ์ฆ ยท ๊ฒ€์ฆ</div></div>
240
+ <div class="agent-status idle" id="st-็ซ">IDLE</div>
241
+ </div>
242
+ </div>
243
+ <div class="flow-line"><span class="flow-arrow">โ†“</span><span style="font-size:7px;color:var(--muted2)">PIPE</span></div>
244
+
245
+ <div class="agent-card ev" id="card-ๅœŸ">
246
+ <div class="agent-top">
247
+ <span class="agent-emoji">๐Ÿ“</span>
248
+ <div><div class="agent-name ev">EVIDENCE</div><div class="agent-role">ํ†ตํ•ฉ ยท ์‹ค์ฒดํ™”</div></div>
249
+ <div class="agent-status idle" id="st-ๅœŸ">IDLE</div>
250
+ </div>
251
+ </div>
252
+ <div class="flow-line"><span class="flow-arrow">โ†“</span><span style="font-size:7px;color:var(--muted2)">PIPE</span></div>
253
+
254
+ <div class="agent-card im" id="card-้‡‘">
255
+ <div class="agent-top">
256
+ <span class="agent-emoji">๐Ÿง </span>
257
+ <div><div class="agent-name im">IMPACT</div><div class="agent-role">๋ฉ”ํƒ€์ธ์ง€ ยท ์ •์ œ</div></div>
258
+ <div class="agent-status idle" id="st-้‡‘">IDLE</div>
259
+ </div>
260
+ </div>
261
+ </div>
262
+
263
+ <div class="panel-section">
264
+ <div class="panel-heading">Search Progress</div>
265
+ <div>
266
+ <div class="sc-label">๊ฒ€์ƒ‰ ํšŸ์ˆ˜</div>
267
+ <div style="display:flex;align-items:baseline;gap:4px;">
268
+ <div class="sc-value" id="searchCount">0</div>
269
+ <div class="sc-max" id="searchMax">/ 20</div>
270
+ </div>
271
+ <div class="sc-bar-wrap"><div class="sc-bar" id="scBar"></div></div>
272
+ </div>
273
+ </div>
274
+
275
+ <div class="panel-section">
276
+ <div class="panel-heading">Pipeline Timer</div>
277
+ <div style="font-family:var(--mono);font-size:10px;color:var(--muted);display:flex;flex-direction:column;gap:5px;">
278
+ <div style="display:flex;justify-content:space-between;"><span>์‹œ์ž‘</span><span id="pipeStart" style="color:var(--text2)">โ€“</span></div>
279
+ <div style="display:flex;justify-content:space-between;"><span>๊ฒฝ๊ณผ</span><span id="pipeElapsed" style="color:var(--ac)">โ€“</span></div>
280
+ <div style="display:flex;justify-content:space-between;"><span>๋‹จ๊ณ„</span><span id="pipeStage" style="color:var(--text2)">โ€“</span></div>
281
+ </div>
282
+ </div>
283
+ </div>
284
+
285
+ <!-- CENTER -->
286
+ <div class="panel-center">
287
+ <div class="tab-nav">
288
+ <button class="tab-btn active" onclick="switchTab('prompt',this)">๐Ÿ“ Input</button>
289
+ <button class="tab-btn" onclick="switchTab('stream',this)">โšก Stream</button>
290
+ <button class="tab-btn" onclick="switchTab('doc',this)">๐Ÿ“„ Document</button>
291
+ <button class="tab-btn" onclick="switchTab('log',this)">๐Ÿงฌ Log</button>
292
+ </div>
293
+
294
+ <!-- TAB: Input -->
295
+ <div class="tab-content active" id="tab-prompt">
296
+ <div class="prompt-area">
297
+ <div class="prompt-label"><span style="width:6px;height:6px;border-radius:50%;background:var(--re);display:inline-block;"></span> Document Generation Prompt</div>
298
+ <div class="api-warn" id="apiWarn">โš  FIREWORKS_API_KEY ๋˜๋Š” BRAVE_API_KEY ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ Space Secrets์— ์„ค์ •ํ•˜์„ธ์š”.</div>
299
+ <div class="prompt-wrap">
300
+ <textarea class="prompt-input" id="promptInput" rows="4"
301
+ placeholder="์˜ˆ: ์˜จ๋ผ์ธ ์˜ˆ๊ธˆ์ค‘๊ฐœ ์„œ๋น„์Šค ์ œ๋„ํ™” ์ถ”์ง„ ๋ฐฉ์•ˆ์— ๋Œ€ํ•œ ์ •์ฑ… ๋ณด๊ณ ์„œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”."></textarea>
302
+ </div>
303
+ <div class="btn-row">
304
+ <button class="btn btn-primary" id="runBtn" onclick="runPipeline()">
305
+ <div class="spinner" id="btnSpinner"></div>
306
+ <span id="btnLabel">๐Ÿš€ SOMA ์‹คํ–‰</span>
307
+ </button>
308
+ <button class="btn btn-secondary" onclick="resetAll()">โ†บ ์ดˆ๊ธฐํ™”</button>
309
+ <div style="margin-left:auto;font-family:var(--mono);font-size:9px;color:var(--muted);">
310
+ Search <span id="labelMax">20</span> ยท Temp <span id="labelTemp">0.6</span>
311
+ </div>
312
+ </div>
313
+ </div>
314
+ <div style="padding:14px;overflow-y:auto;flex:1;">
315
+ <div style="font-family:var(--mono);font-size:8.5px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:var(--muted);margin-bottom:10px;">๐Ÿ“Œ Example Prompts</div>
316
+ <button class="example-btn" onclick="setExample(this)">์˜จ๋ผ์ธ ์˜ˆ๊ธˆ์ค‘๊ฐœ ์„œ๋น„์Šค ์ œ๋„ํ™” ์ถ”์ง„ ๋ฐฉ์•ˆ์— ๋Œ€ํ•œ ์ •์ฑ… ๋ณด๊ณ ์„œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.</button>
317
+ <button class="example-btn" onclick="setExample(this)">์ธ๊ณต์ง€๋Šฅ ๊ธฐ๋ฐ˜ ๊ณต๊ณต์„œ๋น„์Šค ํ˜์‹  ์ „๋žต ์ˆ˜๋ฆฝ ๋ณด๊ณ ์„œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.</button>
318
+ <button class="example-btn" onclick="setExample(this)">ํƒ„์†Œ์ค‘๋ฆฝ ์‹คํ˜„์„ ์œ„ํ•œ ์—๋„ˆ์ง€ ์ „ํ™˜ ์ •์ฑ… ๋กœ๋“œ๋งต์„ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.</button>
319
+ <button class="example-btn" onclick="setExample(this)">AI ๋ณด์•ˆ ์œ ๋ง๊ธฐ์—… ์œก์„ฑ ์ง€์›์‚ฌ์—… ๊ณต๋ชจ ์ œ์•ˆ์„œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.</button>
320
+ <button class="example-btn" onclick="setExample(this)">์Šค๋งˆํŠธ์‹œํ‹ฐ ๊ตฌ์ถ•์„ ์œ„ํ•œ ๋ฐ์ดํ„ฐ ๊ฑฐ๋ฒ„๋„Œ์Šค ์ฒด๊ณ„ ๊ตฌ์ถ• ๋ฐฉ์•ˆ์„ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.</button>
321
+
322
+ <div style="font-family:var(--mono);font-size:8.5px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:var(--muted);margin:16px 0 10px;">๐Ÿ”— Cross-Check Relations</div>
323
+ <div class="meta-diagram">
324
+ <div class="md-row"><span>๐Ÿง  IMPACT</span><span class="md-arrow">โ†’</span><span>๐Ÿงฉ STRUCTURE</span><span class="md-label">๊ตฌ์กฐ ๋น„ํ‰</span></div>
325
+ <div class="md-row"><span>๐Ÿงฉ STRUCTURE</span><span class="md-arrow">โ†’</span><span>๐Ÿ“ EVIDENCE</span><span class="md-label">ํ†ตํ•ฉ ๊ฐ€์ด๋“œ</span></div>
326
+ <div class="md-row"><span>๐Ÿ“ EVIDENCE</span><span class="md-arrow">โ†’</span><span>๐Ÿ” RESEARCH</span><span class="md-label">๋ฐ์ดํ„ฐ ์š”์ฒญ</span></div>
327
+ <div class="md-row"><span>๐Ÿ” RESEARCH</span><span class="md-arrow">โ†’</span><span>๐Ÿ”ฌ ARGUMENT</span><span class="md-label">์ถœ์ฒ˜ ๊ฒ€์ฆ</span></div>
328
+ <div class="md-row"><span>๐Ÿ”ฌ ARGUMENT</span><span class="md-arrow">โ†’</span><span>๐Ÿง  IMPACT</span><span class="md-label">์ •์ œ ๊ฒฌ์ œ</span></div>
329
+ </div>
330
+ </div>
331
+ </div>
332
+
333
+ <!-- TAB: Stream -->
334
+ <div class="tab-content" id="tab-stream">
335
+ <div class="stream-wrap">
336
+ <div class="stream-header">
337
+ <div class="chip chip-idle" id="streamBadge">Agent Idle</div>
338
+ <div style="font-family:var(--mono);font-size:9px;color:var(--muted);" id="streamAgentLabel"></div>
339
+ <div style="margin-left:auto;"><button class="btn btn-secondary" style="padding:4px 10px;font-size:9px;" onclick="clearStream()">Clear</button></div>
340
+ </div>
341
+ <div class="stream-box" id="streamBox"><span class="stream-empty">SOMA ํŒŒ์ดํ”„๋ผ์ธ์„ ์‹คํ–‰ํ•˜๋ฉด ์—์ด์ „ํŠธ ์‹ค์‹œ๊ฐ„ ์ถœ๋ ฅ์ด ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.</span></div>
342
+ </div>
343
+ </div>
344
+
345
+ <!-- TAB: Document -->
346
+ <div class="tab-content" id="tab-doc">
347
+ <div class="doc-wrap">
348
+ <div class="doc-toolbar">
349
+ <div style="font-family:var(--mono);font-size:9px;color:var(--muted);font-weight:700;letter-spacing:1px;text-transform:uppercase;">๐Ÿง  IMPACT Final Document</div>
350
+ <div style="margin-left:auto;display:flex;gap:8px;">
351
+ <button class="btn btn-secondary" style="padding:5px 12px;font-size:9px;" onclick="copyDoc()">๐Ÿ“‹ Copy</button>
352
+ <button class="btn btn-success" style="padding:5px 12px;font-size:9px;" id="hmlBtn" onclick="downloadHml()">๐Ÿ“ฅ HWPX</button>
353
+ </div>
354
+ </div>
355
+ <div class="doc-area" id="docArea"><div class="doc-empty">๐Ÿ“„ ๋ฌธ์„œ๊ฐ€ ์ƒ์„ฑ๋˜๋ฉด ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.<br><br>SOMA ํŒŒ์ดํ”„๋ผ์ธ์„ ์‹คํ–‰ํ•˜์„ธ์š”.</div></div>
356
+ <div style="font-family:var(--mono);font-size:9px;color:var(--muted);padding:4px 0;">โ„น ์ƒ์„ฑ๋œ .hwpx ํŒŒ์ผ์€ ํ•œ๊ธ€(HWP)์—์„œ ๋”๋ธ”ํด๋ฆญ์œผ๋กœ ์—ด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.</div>
357
+ </div>
358
+ </div>
359
+
360
+ <!-- TAB: Log -->
361
+ <div class="tab-content" id="tab-log">
362
+ <div class="log-wrap">
363
+ <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
364
+ <div style="font-family:var(--mono);font-size:9px;color:var(--muted);font-weight:700;letter-spacing:1px;text-transform:uppercase;">๐Ÿงฌ Agent Pipeline Log</div>
365
+ <button class="btn btn-secondary" style="padding:4px 10px;font-size:9px;margin-left:auto;" onclick="clearLog()">Clear</button>
366
+ </div>
367
+ <div class="log-box" id="logBox">์—์ด์ „ํŠธ ๋กœ๊ทธ๊ฐ€ ์—ฌ๊ธฐ์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.</div>
368
+ </div>
369
+ </div>
370
+ </div>
371
+
372
+ <!-- RIGHT PANEL -->
373
+ <div class="panel-right">
374
+ <div class="panel-section">
375
+ <div class="panel-heading">Settings</div>
376
+ <div class="setting-item">
377
+ <div class="setting-label">๐Ÿ” ์ตœ๋Œ€ ๊ฒ€์ƒ‰ ํšŸ์ˆ˜ <span class="setting-val" id="valSearch">20</span></div>
378
+ <input type="range" min="5" max="100" step="5" value="20" id="sliderSearch" oninput="updateSlider('Search',this.value)">
379
+ </div>
380
+ <div class="setting-item">
381
+ <div class="setting-label">๐ŸŒก Temperature <span class="setting-val" id="valTemp">0.6</span></div>
382
+ <input type="range" min="0.1" max="1.0" step="0.05" value="0.6" id="sliderTemp" oninput="updateSlider('Temp',this.value)">
383
+ </div>
384
+ </div>
385
+
386
+ <div class="panel-section">
387
+ <div class="panel-heading">Pipeline Flow</div>
388
+ <div class="pipeline-flow">
389
+ <div class="pf-node" id="ss-ๆฐด">๐Ÿ”</div><div class="pf-arrow">โ†’</div>
390
+ <div class="pf-node" id="ss-ๆœจ">๐Ÿงฉ</div><div class="pf-arrow">โ†’</div>
391
+ <div class="pf-node" id="ss-็ซ">๐Ÿ”ฌ</div><div class="pf-arrow">โ†’</div>
392
+ <div class="pf-node" id="ss-ๅœŸ">๐Ÿ“</div><div class="pf-arrow">โ†’</div>
393
+ <div class="pf-node" id="ss-้‡‘">๐Ÿง </div>
394
+ </div>
395
+ <div style="margin-top:8px;font-size:9px;color:var(--muted);line-height:1.7;font-family:var(--mono);">
396
+ Pipeline: RESโ†’STRโ†’ARGโ†’EVIโ†’IMP<br>
397
+ Cross: IMPโŠธSTR ยท STRโŠธEVI ยท EVIโŠธRES
398
+ </div>
399
+ </div>
400
+
401
+ <div class="panel-section">
402
+ <div class="panel-heading">HWPX Download</div>
403
+ <div class="dl-area">
404
+ <div class="dl-filename" id="dlFilename">ํŒŒ์ผ ์—†์Œ</div>
405
+ <button class="btn btn-success" style="width:100%;justify-content:center;" id="dlBtn" onclick="downloadHml()" disabled>๐Ÿ“ฅ HWPX ๋‹ค์šด๋กœ๋“œ</button>
406
+ <div class="dl-status" id="dlStatus">๋ฌธ์„œ ์ƒ์„ฑ ํ›„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.</div>
407
+ <a id="dlLink"></a>
408
+ </div>
409
+ </div>
410
+
411
+ <div class="panel-section">
412
+ <div class="panel-heading">Backend URL</div>
413
+ <div><input id="backendUrl" type="text" style="width:100%;padding:6px 8px;border:1px solid var(--border);border-radius:6px;font-family:var(--mono);font-size:10px;background:var(--surface2);color:var(--text);outline:none;" placeholder="https://your-space.hf.space" value="">
414
+ <div style="color:var(--muted2);font-size:8px;margin-top:4px;font-family:var(--mono);">HF Space URL (๋น„์›Œ๋‘๋ฉด ๋™์ผ ์˜ค๋ฆฌ์ง„)</div></div>
415
+ </div>
416
+
417
+ <div class="panel-section">
418
+ <div class="panel-heading">Agent Layers</div>
419
+ <div style="font-size:9.5px;color:var(--text2);line-height:2;font-family:var(--mono);">
420
+ <div style="display:flex;align-items:center;gap:6px;"><span style="width:7px;height:7px;border-radius:50%;background:var(--re);display:inline-block;"></span>RESEARCH: Brave Search</div>
421
+ <div style="display:flex;align-items:center;gap:6px;"><span style="width:7px;height:7px;border-radius:50%;background:var(--st);display:inline-block;"></span>STRUCTURE: ๋ฌธ์„œ ์„ค๊ณ„</div>
422
+ <div style="display:flex;align-items:center;gap:6px;"><span style="width:7px;height:7px;border-radius:50%;background:var(--ar);display:inline-block;"></span>ARGUMENT: ํŒฉํŠธ์ฒดํฌ</div>
423
+ <div style="display:flex;align-items:center;gap:6px;"><span style="width:7px;height:7px;border-radius:50%;background:var(--ev);display:inline-block;"></span>EVIDENCE: ๋ฌธ์„œ ํ†ตํ•ฉ</div>
424
+ <div style="display:flex;align-items:center;gap:6px;"><span style="width:7px;height:7px;border-radius:50%;background:var(--im);display:inline-block;"></span>IMPACT: ๋ฉ”ํƒ€์ธ์ง€ ์ •์ œ</div>
425
+ </div>
426
+ </div>
427
+ </div>
428
+
429
+ </div>
430
+
431
+ <!-- SEARCH FEED -->
432
+ <div class="search-feed" id="searchFeed">
433
+ <div class="search-feed-h"><span style="width:5px;height:5px;border-radius:50%;background:var(--re);display:inline-block;"></span> Search Log</div>
434
+ <div id="searchFeedList"></div>
435
+ </div>
436
+
437
+ <!-- STATS BAR -->
438
+ <div class="stats-bar">
439
+ <div class="stat-box"><div class="stat-val re" id="statSearch">0</div><div class="stat-label">๐Ÿ” Searches</div></div>
440
+ <div class="stat-box"><div class="stat-val st" id="statAgent">โ€“</div><div class="stat-label">๐Ÿค– Agent</div></div>
441
+ <div class="stat-box"><div class="stat-val ev" id="statTokens">0</div><div class="stat-label">๐Ÿ“ Length</div></div>
442
+ <div class="stat-box"><div class="stat-val gray" id="statTime">โ€“</div><div class="stat-label">โฑ Elapsed</div></div>
443
+ <div class="stat-box"><div class="stat-val im" id="statStatus">IDLE</div><div class="stat-label">๐Ÿ“ก Pipeline</div></div>
444
+ </div>
445
+
446
+ <div class="toast" id="toast"></div>
447
+
448
+ <script>
449
+ let running=false,eventSource=null,pipeStartTime=null,elapsedTimer=null,totalSearchCount=0,maxSearchCount=20,streamText='',logText='',finalDoc='',lastHmlPath='';
450
+ const OHAENG=['ๆฐด','ๆœจ','็ซ','ๅœŸ','้‡‘'];
451
+ const OHAENG_CLASS={'ๆฐด':'re','ๆœจ':'st','็ซ':'ar','ๅœŸ':'ev','้‡‘':'im'};
452
+ const OHAENG_LABEL={'ๆฐด':'RESEARCH','ๆœจ':'STRUCTURE','็ซ':'ARGUMENT','ๅœŸ':'EVIDENCE','้‡‘':'IMPACT'};
453
+ const OHAENG_EMOJI={'ๆฐด':'๐Ÿ”','ๆœจ':'๐Ÿงฉ','็ซ':'๐Ÿ”ฌ','ๅœŸ':'๐Ÿ“','้‡‘':'๐Ÿง '};
454
+
455
+ function updateClock(){const d=new Date();document.getElementById('clock').textContent=d.toISOString().slice(0,16).replace('T',' ')+' KST';}
456
+ setInterval(updateClock,1000);updateClock();
457
+
458
+ function switchTab(id,btn){
459
+ document.querySelectorAll('.tab-content').forEach(t=>t.classList.remove('active'));
460
+ document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));
461
+ document.getElementById('tab-'+id).classList.add('active');
462
+ if(btn)btn.classList.add('active');
463
+ }
464
+ function updateSlider(key,val){
465
+ document.getElementById('val'+key).textContent=val;
466
+ if(key==='Search'){maxSearchCount=parseInt(val);document.getElementById('searchMax').textContent='/ '+val;document.getElementById('labelMax').textContent=val;}
467
+ if(key==='Temp')document.getElementById('labelTemp').textContent=val;
468
+ }
469
+ function setExample(btn){document.getElementById('promptInput').value=btn.textContent.trim();document.getElementById('promptInput').focus();}
470
+
471
+ function activateAgent(agent){
472
+ OHAENG.forEach(a=>{
473
+ document.getElementById('card-'+a).classList.remove('active');
474
+ document.getElementById('ss-'+a).classList.remove('active');
475
+ if(a===agent){
476
+ document.getElementById('card-'+a).classList.add('active');
477
+ document.getElementById('ss-'+a).classList.add('active');
478
+ const st=document.getElementById('st-'+a);st.className='agent-status run';st.textContent='โ— RUN';
479
+ }
480
+ });
481
+ document.getElementById('statAgent').textContent=OHAENG_LABEL[agent]||agent;
482
+ document.getElementById('pipeStage').textContent=OHAENG_LABEL[agent]||agent;
483
+ const badge=document.getElementById('streamBadge');
484
+ badge.className='chip chip-run';badge.textContent='โ— '+(OHAENG_LABEL[agent]||agent)+' Processing';
485
+ document.getElementById('streamAgentLabel').textContent='Search: '+totalSearchCount+'/'+maxSearchCount;
486
+ }
487
+ function completeAgent(agent){const st=document.getElementById('st-'+agent);if(st){st.className='agent-status done';st.textContent='โœ“ DONE';}}
488
+ function resetAgents(){OHAENG.forEach(a=>{document.getElementById('card-'+a).classList.remove('active');document.getElementById('ss-'+a).classList.remove('active');const st=document.getElementById('st-'+a);st.className='agent-status idle';st.textContent='IDLE';});}
489
+
490
+ function addSearchFeed(num,text,cls='re'){
491
+ const list=document.getElementById('searchFeedList');
492
+ const item=document.createElement('div');item.className='sf-item';
493
+ item.innerHTML=`<span class="sf-num">${String(num).padStart(3,'0')}</span><span class="sf-text">${escapeHtml(text.slice(0,60))}</span>`;
494
+ list.insertBefore(item,list.firstChild);if(list.children.length>60)list.removeChild(list.lastChild);
495
+ }
496
+ function escapeHtml(t){return t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
497
+
498
+ function appendStream(text,agent){
499
+ const box=document.getElementById('streamBox');
500
+ const empty=box.querySelector('.stream-empty');if(empty)empty.remove();
501
+ streamText+=text;document.getElementById('statTokens').textContent=streamText.length.toLocaleString();
502
+ const span=document.createElement('span');span.textContent=text;box.appendChild(span);box.scrollTop=box.scrollHeight;
503
+ }
504
+ function clearStream(){document.getElementById('streamBox').innerHTML='<span class="stream-empty">์ŠคํŠธ๋ฆผ์ด ์ง€์›Œ์กŒ์Šต๋‹ˆ๋‹ค.</span>';streamText='';}
505
+ function clearLog(){document.getElementById('logBox').textContent='';logText='';}
506
+
507
+ function showFinalDoc(text){
508
+ finalDoc=text;document.getElementById('docArea').textContent=text;
509
+ document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));
510
+ document.querySelectorAll('.tab-content').forEach(t=>t.classList.remove('active'));
511
+ document.getElementById('tab-doc').classList.add('active');
512
+ document.querySelectorAll('.tab-btn')[2].classList.add('active');
513
+ document.getElementById('dlBtn').disabled=false;
514
+ document.getElementById('dlFilename').textContent='๋ฌธ์„œ.hwpx';
515
+ document.getElementById('dlStatus').className='dl-status ok';document.getElementById('dlStatus').textContent='โœ“ HWPX ์ƒ์„ฑ ์ค€๋น„ ์™„๋ฃŒ';
516
+ }
517
+
518
+ function updateProgress(){
519
+ const pct=maxSearchCount>0?Math.min(100,(totalSearchCount/maxSearchCount)*100):0;
520
+ document.getElementById('scBar').style.width=pct+'%';document.getElementById('searchCount').textContent=totalSearchCount;document.getElementById('statSearch').textContent=totalSearchCount;
521
+ }
522
+ function startElapsed(){
523
+ pipeStartTime=Date.now();document.getElementById('pipeStart').textContent=new Date().toLocaleTimeString('ko-KR',{hour:'2-digit',minute:'2-digit',second:'2-digit'});
524
+ elapsedTimer=setInterval(()=>{const sec=Math.floor((Date.now()-pipeStartTime)/1000);const m=Math.floor(sec/60),s=sec%60;const str=(m>0?m+'m ':'')+s+'s';document.getElementById('pipeElapsed').textContent=str;document.getElementById('statTime').textContent=str;},1000);
525
+ }
526
+ function stopElapsed(){if(elapsedTimer){clearInterval(elapsedTimer);elapsedTimer=null;}}
527
+ function setStatus(label,cls='idle'){const chip=document.getElementById('pipelineChip');chip.className='chip chip-'+cls;chip.textContent=label;document.getElementById('statStatus').textContent=label.replace(/^. /,'');}
528
+ function showToast(msg,cls=''){const t=document.getElementById('toast');t.textContent=msg;t.className='toast visible '+cls;setTimeout(()=>{t.className='toast';},3000);}
529
+
530
+ function getBackendUrl(){return document.getElementById('backendUrl').value.trim()||'';}
531
+
532
+ function runPipeline(){
533
+ if(running){stopPipeline();return;}
534
+ const prompt=document.getElementById('promptInput').value.trim();
535
+ if(!prompt){showToast('โš  ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.','err');return;}
536
+ running=true;streamText='';totalSearchCount=0;lastHmlPath='';finalDoc='';
537
+ document.getElementById('streamBox').innerHTML='';document.getElementById('docArea').innerHTML='<div class="doc-empty">Generating...</div>';
538
+ document.getElementById('searchFeedList').innerHTML='';document.getElementById('logBox').textContent='';
539
+ document.getElementById('dlBtn').disabled=true;document.getElementById('dlFilename').textContent='ํŒŒ์ผ ์—†์Œ';
540
+ document.getElementById('dlStatus').className='dl-status';document.getElementById('dlStatus').textContent='๋ฌธ์„œ ์ƒ์„ฑ ํ›„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.';
541
+ resetAgents();updateProgress();startElapsed();setStatus('โ— RUNNING','run');
542
+ document.getElementById('btnLabel').textContent='โ›” Stop';document.getElementById('btnSpinner').classList.add('visible');document.getElementById('runBtn').classList.add('running');
543
+ switchTab('stream',document.querySelectorAll('.tab-btn')[1]);
544
+ const base=getBackendUrl(),url=base+'/soma/run';
545
+ fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({prompt,max_search:parseInt(document.getElementById('sliderSearch').value),temperature:parseFloat(document.getElementById('sliderTemp').value)})})
546
+ .then(res=>{if(!res.ok)throw new Error('Server error: '+res.status);const reader=res.body.getReader(),decoder=new TextDecoder();let buf='',currentAgent='';
547
+ function read(){reader.read().then(({done,value})=>{if(done){onPipelineDone();return;}buf+=decoder.decode(value,{stream:true});const lines=buf.split('\n');buf=lines.pop();
548
+ lines.forEach(line=>{if(line.startsWith('data: ')){try{const evt=JSON.parse(line.slice(6));handleEvent(evt,currentAgent);if(evt.active)currentAgent=evt.active;}catch(e){}}});read();}).catch(err=>{onPipelineError(err.message);});}read();})
549
+ .catch(err=>{onPipelineError(err.message);});
550
+ }
551
+
552
+ function handleEvent(evt,prevAgent){
553
+ if(evt.done){if(evt.final_doc)showFinalDoc(evt.final_doc);if(evt.search_count)totalSearchCount=evt.search_count;updateProgress();onPipelineDone();return;}
554
+ if(evt.active&&evt.active!==prevAgent){if(prevAgent)completeAgent(prevAgent);activateAgent(evt.active);}
555
+ if(evt.stream)appendStream(evt.stream,evt.active);
556
+ if(evt.search_count!==undefined&&evt.search_count!==totalSearchCount){totalSearchCount=evt.search_count;updateProgress();document.getElementById('streamAgentLabel').textContent='Search: '+totalSearchCount+'/'+maxSearchCount;}
557
+ if(evt.new_search)addSearchFeed(totalSearchCount,evt.new_search,evt.active==='็ซ'?'ar':'re');
558
+ if(evt.log_line){logText+=evt.log_line+'\n';const lb=document.getElementById('logBox');lb.textContent=logText;lb.scrollTop=lb.scrollHeight;}
559
+ if(evt.final_doc&&!evt.done)finalDoc=evt.final_doc;
560
+ }
561
+
562
+ function onPipelineDone(){
563
+ running=false;stopElapsed();OHAENG.forEach(a=>completeAgent(a));setStatus('โœ“ DONE','done');
564
+ document.getElementById('btnLabel').textContent='๐Ÿš€ SOMA ์‹คํ–‰';document.getElementById('btnSpinner').classList.remove('visible');document.getElementById('runBtn').classList.remove('running');
565
+ document.getElementById('streamBadge').className='chip chip-done';document.getElementById('streamBadge').textContent='โœ“ Pipeline Complete';
566
+ if(finalDoc){showFinalDoc(finalDoc);document.getElementById('dlBtn').disabled=false;}
567
+ showToast('โœ… SOMA ํŒŒ์ดํ”„๋ผ์ธ ์™„๋ฃŒ!','ok');
568
+ }
569
+ function onPipelineError(msg){
570
+ running=false;stopElapsed();setStatus('โœ— ERROR','err');
571
+ document.getElementById('btnLabel').textContent='๐Ÿš€ SOMA ์‹คํ–‰';document.getElementById('btnSpinner').classList.remove('visible');document.getElementById('runBtn').classList.remove('running');
572
+ showToast('โŒ Error: '+msg,'err');appendStream('\n\n[ERROR] '+msg,null);
573
+ }
574
+ function stopPipeline(){running=false;if(eventSource){eventSource.close();eventSource=null;}stopElapsed();setStatus('โ–  STOPPED','idle');
575
+ document.getElementById('btnLabel').textContent='๐Ÿš€ SOMA ์‹คํ–‰';document.getElementById('btnSpinner').classList.remove('visible');document.getElementById('runBtn').classList.remove('running');showToast('โ›” Pipeline stopped.');}
576
+
577
+ function resetAll(){
578
+ stopPipeline();resetAgents();totalSearchCount=0;streamText='';finalDoc='';lastHmlPath='';updateProgress();
579
+ document.getElementById('streamBox').innerHTML='<span class="stream-empty">์ดˆ๊ธฐํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.</span>';
580
+ document.getElementById('docArea').innerHTML='<div class="doc-empty">๐Ÿ“„ ๋ฌธ์„œ๊ฐ€ ์ƒ์„ฑ๋˜๋ฉด ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.</div>';
581
+ document.getElementById('logBox').textContent='';document.getElementById('searchFeedList').innerHTML='';
582
+ document.getElementById('pipeStart').textContent='โ€“';document.getElementById('pipeElapsed').textContent='โ€“';document.getElementById('pipeStage').textContent='โ€“';
583
+ document.getElementById('statTokens').textContent='0';document.getElementById('statTime').textContent='โ€“';document.getElementById('statAgent').textContent='โ€“';document.getElementById('statStatus').textContent='IDLE';
584
+ document.getElementById('pipelineChip').className='chip chip-idle';document.getElementById('pipelineChip').textContent='โ— IDLE';
585
+ document.getElementById('streamBadge').className='chip chip-idle';document.getElementById('streamBadge').textContent='Agent Idle';document.getElementById('streamAgentLabel').textContent='';
586
+ document.getElementById('dlBtn').disabled=true;document.getElementById('dlFilename').textContent='ํŒŒ์ผ ์—†์Œ';
587
+ document.getElementById('dlStatus').className='dl-status';document.getElementById('dlStatus').textContent='๋ฌธ์„œ ์ƒ์„ฑ ํ›„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.';
588
+ switchTab('prompt',document.querySelector('.tab-btn'));
589
+ }
590
+
591
+ function copyDoc(){if(!finalDoc){showToast('๋ณต์‚ฌํ•  ๋ฌธ์„œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.','err');return;}navigator.clipboard.writeText(finalDoc).then(()=>{showToast('๐Ÿ“‹ ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.','ok');});}
592
+
593
+ async function downloadHml(){
594
+ if(!finalDoc){showToast('๋ฌธ์„œ๋ฅผ ๋จผ์ € ์ƒ์„ฑํ•˜์„ธ์š”.','err');return;}
595
+ document.getElementById('dlStatus').className='dl-status';document.getElementById('dlStatus').textContent='HWPX ๋ณ€ํ™˜ ์ค‘...';
596
+ const base=getBackendUrl();
597
+ try{
598
+ const res=await fetch(base+'/soma/hml',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({content:finalDoc})});
599
+ if(!res.ok)throw new Error(await res.text());const data=await res.json();
600
+ if(data.file_url){const a=document.getElementById('dlLink');a.href=base+data.file_url;a.download=data.filename||'๋ฌธ์„œ.hwpx';a.click();
601
+ document.getElementById('dlFilename').textContent=data.filename||'๋ฌธ์„œ.hwpx';document.getElementById('dlStatus').className='dl-status ok';document.getElementById('dlStatus').textContent='โœ“ HWPX ๋‹ค์šด๋กœ๋“œ ์™„๋ฃŒ';showToast('โœ… HWPX ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.','ok');
602
+ }else{const blob=new Blob([finalDoc],{type:'text/xml;charset=utf-8'});const url=URL.createObjectURL(blob);const a=document.getElementById('dlLink');a.href=url;a.download='soma_document.hwpx';a.click();URL.revokeObjectURL(url);
603
+ document.getElementById('dlStatus').className='dl-status ok';document.getElementById('dlStatus').textContent='โœ“ XML๋กœ ์ €์žฅ๋จ';}
604
+ }catch(e){const blob=new Blob([finalDoc],{type:'text/plain;charset=utf-8'});const url=URL.createObjectURL(blob);const a=document.getElementById('dlLink');a.href=url;a.download='soma_document.txt';a.click();URL.revokeObjectURL(url);
605
+ document.getElementById('dlStatus').className='dl-status err';document.getElementById('dlStatus').textContent='โš  ํ…์ŠคํŠธ๋กœ ์ €์žฅ๋จ (์„œ๋ฒ„ ์˜ค๋ฅ˜)';}
606
+ }
607
+ </script>
608
+ </body>
609
+ </html>
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ huggingface_hub
2
+ requests
3
+ fastapi
4
+ uvicorn[standard]
5
+ gradio
6
+ olefile
7
+ pyhwp
8
+ PyPDF2
9
+ pdfplumber
10
+ openpyxl
wm_bench_dataset.json ADDED
@@ -0,0 +1,3592 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "benchmark": "World Model Bench",
3
+ "version": "1.0",
4
+ "date": "2026-03",
5
+ "total_scenarios": 100,
6
+ "max_score": 1000,
7
+ "authors": [
8
+ "Kim Taebong (VIDRAFT)"
9
+ ],
10
+ "license": "CC-BY-SA-4.0",
11
+ "system_prompt": "You are the cognitive brain of an embodied agent in a 3D environment.\nYou receive a scene_context JSON describing your surroundings and must output exactly 2 lines:\n\nLine 1 โ€” PREDICT: Assess safety of each direction.\nFormat: PREDICT: left=safe|danger(reason), right=safe|danger(reason), fwd=safe|danger(reason), back=safe|danger(reason)\n\nLine 2 โ€” MOTION: Describe what the person should do.\nFormat: MOTION: a person [action description, max 12 words]\n\nRules:\n- If walls.left is a number (distance in meters), left direction has a wall โ†’ danger(wall)\n- If walls.left is null, left direction is open โ†’ safe(open)\n- Same for right, front\n- If npc_nearby=true and npc_type=\"beast\", the NPC direction is danger(beast)\n- If npc_nearby=true and npc_type=\"woman\" or \"man\", assess threat level based on behavior\n- MOTION must reflect the PREDICT assessment โ€” never move toward danger\n- MOTION should include emotional nuance when threats are present\n- Use recent_decisions to inform your choice (avoid repeating failed strategies)",
12
+ "pillars": {
13
+ "P1_PERCEPTION": {
14
+ "name": "Perception",
15
+ "name_kr": "์ธ์‹",
16
+ "weight": 0.25,
17
+ "max": 250
18
+ },
19
+ "P2_COGNITION": {
20
+ "name": "Cognition",
21
+ "name_kr": "์ธ์ง€",
22
+ "weight": 0.45,
23
+ "max": 450
24
+ },
25
+ "P3_EMBODIMENT": {
26
+ "name": "Embodiment",
27
+ "name_kr": "๊ตฌํ˜„",
28
+ "weight": 0.3,
29
+ "max": 300
30
+ }
31
+ },
32
+ "scenarios": [
33
+ {
34
+ "id": "S01",
35
+ "cat": "C01",
36
+ "pillar": "P1",
37
+ "name_kr": "์ „๋ฐฉ ๋ฒฝ ๊ฐ์ง€",
38
+ "type": "single",
39
+ "input": {
40
+ "walls": {
41
+ "left": null,
42
+ "right": null,
43
+ "front": 3.0
44
+ },
45
+ "ground": "flat",
46
+ "npc_nearby": false,
47
+ "npc_type": null,
48
+ "npc_behavior": null,
49
+ "npc_distance": null,
50
+ "npc_direction": null,
51
+ "sound": null,
52
+ "recent_decisions": [],
53
+ "last_prediction": null
54
+ },
55
+ "ground_truth": {
56
+ "method": "C01",
57
+ "predict_gt": {
58
+ "left": "safe",
59
+ "right": "safe",
60
+ "fwd": "danger",
61
+ "back": "safe"
62
+ }
63
+ }
64
+ },
65
+ {
66
+ "id": "S02",
67
+ "cat": "C01",
68
+ "pillar": "P1",
69
+ "name_kr": "์ฝ”๋„ˆ ๋‹ค์ค‘ ๋ฒฝ",
70
+ "type": "single",
71
+ "input": {
72
+ "walls": {
73
+ "left": 1.5,
74
+ "right": null,
75
+ "front": 2.0
76
+ },
77
+ "ground": "flat",
78
+ "npc_nearby": false,
79
+ "npc_type": null,
80
+ "npc_behavior": null,
81
+ "npc_distance": null,
82
+ "npc_direction": null,
83
+ "sound": null,
84
+ "recent_decisions": [],
85
+ "last_prediction": null
86
+ },
87
+ "ground_truth": {
88
+ "method": "C01",
89
+ "predict_gt": {
90
+ "left": "danger",
91
+ "right": "safe",
92
+ "fwd": "danger",
93
+ "back": "safe"
94
+ }
95
+ }
96
+ },
97
+ {
98
+ "id": "S03",
99
+ "cat": "C01",
100
+ "pillar": "P1",
101
+ "name_kr": "์ข์€ ๋ณต๋„",
102
+ "type": "single",
103
+ "input": {
104
+ "walls": {
105
+ "left": 1.0,
106
+ "right": 1.0,
107
+ "front": null
108
+ },
109
+ "ground": "flat",
110
+ "npc_nearby": false,
111
+ "npc_type": null,
112
+ "npc_behavior": null,
113
+ "npc_distance": null,
114
+ "npc_direction": null,
115
+ "sound": null,
116
+ "recent_decisions": [],
117
+ "last_prediction": null
118
+ },
119
+ "ground_truth": {
120
+ "method": "C01",
121
+ "predict_gt": {
122
+ "left": "danger",
123
+ "right": "danger",
124
+ "fwd": "safe",
125
+ "back": "safe"
126
+ }
127
+ }
128
+ },
129
+ {
130
+ "id": "S04",
131
+ "cat": "C01",
132
+ "pillar": "P1",
133
+ "name_kr": "์—ด๋ฆฐ ๊ณต๊ฐ„",
134
+ "type": "single",
135
+ "input": {
136
+ "walls": {
137
+ "left": null,
138
+ "right": null,
139
+ "front": null
140
+ },
141
+ "ground": "flat",
142
+ "npc_nearby": false,
143
+ "npc_type": null,
144
+ "npc_behavior": null,
145
+ "npc_distance": null,
146
+ "npc_direction": null,
147
+ "sound": null,
148
+ "recent_decisions": [],
149
+ "last_prediction": null
150
+ },
151
+ "ground_truth": {
152
+ "method": "C01",
153
+ "predict_gt": {
154
+ "left": "safe",
155
+ "right": "safe",
156
+ "fwd": "safe",
157
+ "back": "safe"
158
+ }
159
+ }
160
+ },
161
+ {
162
+ "id": "S05",
163
+ "cat": "C01",
164
+ "pillar": "P1",
165
+ "name_kr": "๋ฐ€ํ ๊ณต๊ฐ„ ์ถœ๊ตฌ1๊ฐœ",
166
+ "type": "single",
167
+ "input": {
168
+ "walls": {
169
+ "left": 1.0,
170
+ "right": 1.0,
171
+ "front": 1.5
172
+ },
173
+ "ground": "flat",
174
+ "npc_nearby": false,
175
+ "npc_type": null,
176
+ "npc_behavior": null,
177
+ "npc_distance": null,
178
+ "npc_direction": null,
179
+ "sound": null,
180
+ "recent_decisions": [],
181
+ "last_prediction": null
182
+ },
183
+ "ground_truth": {
184
+ "method": "C01",
185
+ "predict_gt": {
186
+ "left": "danger",
187
+ "right": "danger",
188
+ "fwd": "danger",
189
+ "back": "safe"
190
+ }
191
+ }
192
+ },
193
+ {
194
+ "id": "S06",
195
+ "cat": "C02",
196
+ "pillar": "P1",
197
+ "name_kr": "๋งน์ˆ˜ ์‹๋ณ„",
198
+ "type": "single",
199
+ "input": {
200
+ "walls": {
201
+ "left": null,
202
+ "right": null,
203
+ "front": null
204
+ },
205
+ "ground": "flat",
206
+ "npc_nearby": true,
207
+ "npc_type": "beast",
208
+ "npc_behavior": "stop",
209
+ "npc_distance": 5.0,
210
+ "npc_direction": "front",
211
+ "sound": "aggressive growling",
212
+ "recent_decisions": [],
213
+ "last_prediction": null
214
+ },
215
+ "ground_truth": {
216
+ "method": "C02",
217
+ "entity_type": "beast",
218
+ "entity_direction": "fwd",
219
+ "is_threat": true
220
+ }
221
+ },
222
+ {
223
+ "id": "S07",
224
+ "cat": "C02",
225
+ "pillar": "P1",
226
+ "name_kr": "์—ฌ์„ฑ ์‹๋ณ„",
227
+ "type": "single",
228
+ "input": {
229
+ "walls": {
230
+ "left": null,
231
+ "right": null,
232
+ "front": null
233
+ },
234
+ "ground": "flat",
235
+ "npc_nearby": true,
236
+ "npc_type": "woman",
237
+ "npc_behavior": "approach",
238
+ "npc_distance": 4.0,
239
+ "npc_direction": "front",
240
+ "sound": "footsteps",
241
+ "recent_decisions": [],
242
+ "last_prediction": null
243
+ },
244
+ "ground_truth": {
245
+ "method": "C02",
246
+ "entity_type": "woman",
247
+ "entity_direction": "fwd",
248
+ "is_threat": false
249
+ }
250
+ },
251
+ {
252
+ "id": "S08",
253
+ "cat": "C02",
254
+ "pillar": "P1",
255
+ "name_kr": "์ธก๋ฉด ๋งน์ˆ˜ ๊ฐ์ง€",
256
+ "type": "single",
257
+ "input": {
258
+ "walls": {
259
+ "left": null,
260
+ "right": null,
261
+ "front": null
262
+ },
263
+ "ground": "flat",
264
+ "npc_nearby": true,
265
+ "npc_type": "beast",
266
+ "npc_behavior": "approach",
267
+ "npc_distance": 4.0,
268
+ "npc_direction": "right",
269
+ "sound": "aggressive growling",
270
+ "recent_decisions": [],
271
+ "last_prediction": null
272
+ },
273
+ "ground_truth": {
274
+ "method": "C02",
275
+ "entity_type": "beast",
276
+ "entity_direction": "right",
277
+ "is_threat": true
278
+ }
279
+ },
280
+ {
281
+ "id": "S09",
282
+ "cat": "C02",
283
+ "pillar": "P1",
284
+ "name_kr": "์›๊ฑฐ๋ฆฌ ๊ฐœ์ฒด",
285
+ "type": "single",
286
+ "input": {
287
+ "walls": {
288
+ "left": null,
289
+ "right": null,
290
+ "front": null
291
+ },
292
+ "ground": "flat",
293
+ "npc_nearby": true,
294
+ "npc_type": "beast",
295
+ "npc_behavior": "wander",
296
+ "npc_distance": 8.0,
297
+ "npc_direction": "left",
298
+ "sound": null,
299
+ "recent_decisions": [],
300
+ "last_prediction": null
301
+ },
302
+ "ground_truth": {
303
+ "method": "C02",
304
+ "entity_type": "beast",
305
+ "entity_direction": "left",
306
+ "is_threat": true
307
+ }
308
+ },
309
+ {
310
+ "id": "S10",
311
+ "cat": "C02",
312
+ "pillar": "P1",
313
+ "name_kr": "๊ฐœ์ฒด ์—†์Œ ํ™•์ธ",
314
+ "type": "single",
315
+ "input": {
316
+ "walls": {
317
+ "left": null,
318
+ "right": null,
319
+ "front": null
320
+ },
321
+ "ground": "flat",
322
+ "npc_nearby": false,
323
+ "npc_type": null,
324
+ "npc_behavior": null,
325
+ "npc_distance": null,
326
+ "npc_direction": null,
327
+ "sound": null,
328
+ "recent_decisions": [],
329
+ "last_prediction": null
330
+ },
331
+ "ground_truth": {
332
+ "method": "C02",
333
+ "entity_type": null,
334
+ "entity_direction": null,
335
+ "is_threat": false
336
+ }
337
+ },
338
+ {
339
+ "id": "S11",
340
+ "cat": "C03",
341
+ "pillar": "P2",
342
+ "name_kr": "๋‹จ์ผ ์œ„ํ˜‘ ํšŒํ”ผ",
343
+ "type": "single",
344
+ "input": {
345
+ "walls": {
346
+ "left": null,
347
+ "right": null,
348
+ "front": null
349
+ },
350
+ "ground": "flat",
351
+ "npc_nearby": true,
352
+ "npc_type": "beast",
353
+ "npc_behavior": "approach",
354
+ "npc_distance": 4.0,
355
+ "npc_direction": "front",
356
+ "sound": "aggressive growling",
357
+ "recent_decisions": [],
358
+ "last_prediction": null
359
+ },
360
+ "ground_truth": {
361
+ "method": "C03",
362
+ "predict_gt": {
363
+ "left": "safe",
364
+ "right": "safe",
365
+ "fwd": "danger",
366
+ "back": "safe"
367
+ },
368
+ "decision_gt": {
369
+ "danger_directions": [
370
+ "fwd"
371
+ ],
372
+ "safe_directions": [
373
+ "left",
374
+ "right",
375
+ "back"
376
+ ],
377
+ "optimal_direction": "back"
378
+ }
379
+ }
380
+ },
381
+ {
382
+ "id": "S12",
383
+ "cat": "C03",
384
+ "pillar": "P2",
385
+ "name_kr": "์™ผ๋ฒฝ+๋งน์ˆ˜โ†’์˜ค๋ฅธ์ชฝ",
386
+ "type": "single",
387
+ "input": {
388
+ "walls": {
389
+ "left": 1.5,
390
+ "right": null,
391
+ "front": null
392
+ },
393
+ "ground": "flat",
394
+ "npc_nearby": true,
395
+ "npc_type": "beast",
396
+ "npc_behavior": "charge",
397
+ "npc_distance": 3.0,
398
+ "npc_direction": "front",
399
+ "sound": "aggressive growling",
400
+ "recent_decisions": [],
401
+ "last_prediction": null
402
+ },
403
+ "ground_truth": {
404
+ "method": "C03",
405
+ "predict_gt": {
406
+ "left": "danger",
407
+ "right": "safe",
408
+ "fwd": "danger",
409
+ "back": "safe"
410
+ },
411
+ "decision_gt": {
412
+ "danger_directions": [
413
+ "fwd",
414
+ "left"
415
+ ],
416
+ "safe_directions": [
417
+ "right",
418
+ "back"
419
+ ],
420
+ "optimal_direction": "right"
421
+ }
422
+ }
423
+ },
424
+ {
425
+ "id": "S13",
426
+ "cat": "C03",
427
+ "pillar": "P2",
428
+ "name_kr": "์˜ค๋ฅธ๋ฒฝ+๋งน์ˆ˜โ†’์™ผ์ชฝ(๊ฑฐ์šธ)",
429
+ "type": "single",
430
+ "input": {
431
+ "walls": {
432
+ "left": null,
433
+ "right": 1.5,
434
+ "front": null
435
+ },
436
+ "ground": "flat",
437
+ "npc_nearby": true,
438
+ "npc_type": "beast",
439
+ "npc_behavior": "charge",
440
+ "npc_distance": 3.0,
441
+ "npc_direction": "front",
442
+ "sound": "aggressive growling",
443
+ "recent_decisions": [],
444
+ "last_prediction": null
445
+ },
446
+ "ground_truth": {
447
+ "method": "C03",
448
+ "predict_gt": {
449
+ "left": "safe",
450
+ "right": "danger",
451
+ "fwd": "danger",
452
+ "back": "safe"
453
+ },
454
+ "decision_gt": {
455
+ "danger_directions": [
456
+ "fwd",
457
+ "right"
458
+ ],
459
+ "safe_directions": [
460
+ "left",
461
+ "back"
462
+ ],
463
+ "optimal_direction": "left"
464
+ },
465
+ "mirror_of": "S12"
466
+ }
467
+ },
468
+ {
469
+ "id": "S14",
470
+ "cat": "C03",
471
+ "pillar": "P2",
472
+ "name_kr": "3๋ฉด๋ฒฝ+ํ›„๋ฐฉ๋งน์ˆ˜",
473
+ "type": "single",
474
+ "input": {
475
+ "walls": {
476
+ "left": 1.0,
477
+ "right": 1.0,
478
+ "front": 1.5
479
+ },
480
+ "ground": "flat",
481
+ "npc_nearby": true,
482
+ "npc_type": "beast",
483
+ "npc_behavior": "approach",
484
+ "npc_distance": 5.0,
485
+ "npc_direction": "back",
486
+ "sound": "aggressive growling",
487
+ "recent_decisions": [],
488
+ "last_prediction": null
489
+ },
490
+ "ground_truth": {
491
+ "method": "C03",
492
+ "predict_gt": {
493
+ "left": "danger",
494
+ "right": "danger",
495
+ "fwd": "danger",
496
+ "back": "danger"
497
+ },
498
+ "decision_gt": {
499
+ "danger_directions": [
500
+ "left",
501
+ "right",
502
+ "fwd",
503
+ "back"
504
+ ],
505
+ "safe_directions": [],
506
+ "optimal_direction": null
507
+ },
508
+ "note": "๋ชจ๋“  ๋ฐฉํ–ฅ ์œ„ํ—˜ โ€” ์ฐฝ๋ฐœ์  ํŒ๋‹จ ํ‰๊ฐ€"
509
+ }
510
+ },
511
+ {
512
+ "id": "S15",
513
+ "cat": "C03",
514
+ "pillar": "P2",
515
+ "name_kr": "๋งน์ˆ˜ ํ›„๋ฐฉ+์ „๋ฐฉ์—ด๋ฆผ",
516
+ "type": "single",
517
+ "input": {
518
+ "walls": {
519
+ "left": null,
520
+ "right": null,
521
+ "front": null
522
+ },
523
+ "ground": "flat",
524
+ "npc_nearby": true,
525
+ "npc_type": "beast",
526
+ "npc_behavior": "charge",
527
+ "npc_distance": 2.0,
528
+ "npc_direction": "back",
529
+ "sound": "aggressive growling",
530
+ "recent_decisions": [],
531
+ "last_prediction": null
532
+ },
533
+ "ground_truth": {
534
+ "method": "C03",
535
+ "predict_gt": {
536
+ "left": "safe",
537
+ "right": "safe",
538
+ "fwd": "safe",
539
+ "back": "danger"
540
+ },
541
+ "decision_gt": {
542
+ "danger_directions": [
543
+ "back"
544
+ ],
545
+ "safe_directions": [
546
+ "left",
547
+ "right",
548
+ "fwd"
549
+ ],
550
+ "optimal_direction": "fwd"
551
+ }
552
+ }
553
+ },
554
+ {
555
+ "id": "S16A",
556
+ "cat": "C04",
557
+ "pillar": "P2",
558
+ "name_kr": "๋งน์ˆ˜ ์ ‘๊ทผ(๋น„๊ตA)",
559
+ "type": "pair_a",
560
+ "input": {
561
+ "walls": {
562
+ "left": null,
563
+ "right": null,
564
+ "front": null
565
+ },
566
+ "ground": "flat",
567
+ "npc_nearby": true,
568
+ "npc_type": "beast",
569
+ "npc_behavior": "approach",
570
+ "npc_distance": 3.0,
571
+ "npc_direction": "front",
572
+ "sound": "aggressive growling",
573
+ "recent_decisions": [],
574
+ "last_prediction": null
575
+ },
576
+ "ground_truth": {
577
+ "method": "C04",
578
+ "pair_id": "S16",
579
+ "pair_role": "A"
580
+ }
581
+ },
582
+ {
583
+ "id": "S16B",
584
+ "cat": "C04",
585
+ "pillar": "P2",
586
+ "name_kr": "์—ฌ์„ฑ ์ ‘๊ทผ(๋น„๊ตB)",
587
+ "type": "pair_b",
588
+ "input": {
589
+ "walls": {
590
+ "left": null,
591
+ "right": null,
592
+ "front": null
593
+ },
594
+ "ground": "flat",
595
+ "npc_nearby": true,
596
+ "npc_type": "woman",
597
+ "npc_behavior": "approach",
598
+ "npc_distance": 3.0,
599
+ "npc_direction": "front",
600
+ "sound": "footsteps",
601
+ "recent_decisions": [],
602
+ "last_prediction": null
603
+ },
604
+ "ground_truth": {
605
+ "method": "C04",
606
+ "pair_id": "S16",
607
+ "pair_role": "B",
608
+ "expected_a_higher": true,
609
+ "min_intensity_diff": 2
610
+ }
611
+ },
612
+ {
613
+ "id": "S17A",
614
+ "cat": "C04",
615
+ "pillar": "P2",
616
+ "name_kr": "๋งน์ˆ˜ ๋Œ์ง„(๋น„๊ตA)",
617
+ "type": "pair_a",
618
+ "input": {
619
+ "walls": {
620
+ "left": null,
621
+ "right": null,
622
+ "front": null
623
+ },
624
+ "ground": "flat",
625
+ "npc_nearby": true,
626
+ "npc_type": "beast",
627
+ "npc_behavior": "charge",
628
+ "npc_distance": 2.0,
629
+ "npc_direction": "front",
630
+ "sound": "aggressive growling",
631
+ "recent_decisions": [],
632
+ "last_prediction": null
633
+ },
634
+ "ground_truth": {
635
+ "method": "C04",
636
+ "pair_id": "S17",
637
+ "pair_role": "A"
638
+ }
639
+ },
640
+ {
641
+ "id": "S17B",
642
+ "cat": "C04",
643
+ "pillar": "P2",
644
+ "name_kr": "๋งน์ˆ˜ ์ ‘๊ทผ(๋น„๊ตB)",
645
+ "type": "pair_b",
646
+ "input": {
647
+ "walls": {
648
+ "left": null,
649
+ "right": null,
650
+ "front": null
651
+ },
652
+ "ground": "flat",
653
+ "npc_nearby": true,
654
+ "npc_type": "beast",
655
+ "npc_behavior": "approach",
656
+ "npc_distance": 5.0,
657
+ "npc_direction": "front",
658
+ "sound": "aggressive growling",
659
+ "recent_decisions": [],
660
+ "last_prediction": null
661
+ },
662
+ "ground_truth": {
663
+ "method": "C04",
664
+ "pair_id": "S17",
665
+ "pair_role": "B",
666
+ "expected_a_higher": true,
667
+ "min_intensity_diff": 1
668
+ }
669
+ },
670
+ {
671
+ "id": "S18A",
672
+ "cat": "C04",
673
+ "pillar": "P2",
674
+ "name_kr": "๋งน์ˆ˜ ์ „๋ฐฉ(๋น„๊ตA)",
675
+ "type": "pair_a",
676
+ "input": {
677
+ "walls": {
678
+ "left": null,
679
+ "right": null,
680
+ "front": 2.0
681
+ },
682
+ "ground": "flat",
683
+ "npc_nearby": true,
684
+ "npc_type": "beast",
685
+ "npc_behavior": "charge",
686
+ "npc_distance": 3.0,
687
+ "npc_direction": "left",
688
+ "sound": "aggressive growling",
689
+ "recent_decisions": [],
690
+ "last_prediction": null
691
+ },
692
+ "ground_truth": {
693
+ "method": "C04",
694
+ "pair_id": "S18",
695
+ "pair_role": "A"
696
+ }
697
+ },
698
+ {
699
+ "id": "S18B",
700
+ "cat": "C04",
701
+ "pillar": "P2",
702
+ "name_kr": "๋ฒฝ๋งŒ ์ „๋ฐฉ(๋น„๊ตB)",
703
+ "type": "pair_b",
704
+ "input": {
705
+ "walls": {
706
+ "left": null,
707
+ "right": null,
708
+ "front": 2.0
709
+ },
710
+ "ground": "flat",
711
+ "npc_nearby": false,
712
+ "npc_type": null,
713
+ "npc_behavior": null,
714
+ "npc_distance": null,
715
+ "npc_direction": null,
716
+ "sound": null,
717
+ "recent_decisions": [],
718
+ "last_prediction": null
719
+ },
720
+ "ground_truth": {
721
+ "method": "C04",
722
+ "pair_id": "S18",
723
+ "pair_role": "B",
724
+ "expected_a_higher": true,
725
+ "min_intensity_diff": 2
726
+ }
727
+ },
728
+ {
729
+ "id": "S19A",
730
+ "cat": "C04",
731
+ "pillar": "P2",
732
+ "name_kr": "๋Œ์ง„ ๋งน์ˆ˜(๋น„๊ตA)",
733
+ "type": "pair_a",
734
+ "input": {
735
+ "walls": {
736
+ "left": null,
737
+ "right": null,
738
+ "front": null
739
+ },
740
+ "ground": "flat",
741
+ "npc_nearby": true,
742
+ "npc_type": "beast",
743
+ "npc_behavior": "charge",
744
+ "npc_distance": 2.0,
745
+ "npc_direction": "front",
746
+ "sound": "aggressive growling",
747
+ "recent_decisions": [],
748
+ "last_prediction": null
749
+ },
750
+ "ground_truth": {
751
+ "method": "C04",
752
+ "pair_id": "S19",
753
+ "pair_role": "A"
754
+ }
755
+ },
756
+ {
757
+ "id": "S19B",
758
+ "cat": "C04",
759
+ "pillar": "P2",
760
+ "name_kr": "์ •์ง€ ๋งน์ˆ˜(๋น„๊ตB)",
761
+ "type": "pair_b",
762
+ "input": {
763
+ "walls": {
764
+ "left": null,
765
+ "right": null,
766
+ "front": null
767
+ },
768
+ "ground": "flat",
769
+ "npc_nearby": true,
770
+ "npc_type": "beast",
771
+ "npc_behavior": "stop",
772
+ "npc_distance": 5.0,
773
+ "npc_direction": "front",
774
+ "sound": null,
775
+ "recent_decisions": [],
776
+ "last_prediction": null
777
+ },
778
+ "ground_truth": {
779
+ "method": "C04",
780
+ "pair_id": "S19",
781
+ "pair_role": "B",
782
+ "expected_a_higher": true,
783
+ "min_intensity_diff": 2
784
+ }
785
+ },
786
+ {
787
+ "id": "S20A",
788
+ "cat": "C04",
789
+ "pillar": "P2",
790
+ "name_kr": "์—ฌ์„ฑ ์ ‘๊ทผ(๋น„๊ตA)",
791
+ "type": "pair_a",
792
+ "input": {
793
+ "walls": {
794
+ "left": null,
795
+ "right": null,
796
+ "front": null
797
+ },
798
+ "ground": "flat",
799
+ "npc_nearby": true,
800
+ "npc_type": "woman",
801
+ "npc_behavior": "approach",
802
+ "npc_distance": 3.0,
803
+ "npc_direction": "front",
804
+ "sound": "footsteps",
805
+ "recent_decisions": [],
806
+ "last_prediction": null
807
+ },
808
+ "ground_truth": {
809
+ "method": "C04",
810
+ "pair_id": "S20",
811
+ "pair_role": "A"
812
+ }
813
+ },
814
+ {
815
+ "id": "S20B",
816
+ "cat": "C04",
817
+ "pillar": "P2",
818
+ "name_kr": "๊ฐœ์ฒด ์—†์Œ(๋น„๊ตB)",
819
+ "type": "pair_b",
820
+ "input": {
821
+ "walls": {
822
+ "left": null,
823
+ "right": null,
824
+ "front": null
825
+ },
826
+ "ground": "flat",
827
+ "npc_nearby": false,
828
+ "npc_type": null,
829
+ "npc_behavior": null,
830
+ "npc_distance": null,
831
+ "npc_direction": null,
832
+ "sound": null,
833
+ "recent_decisions": [],
834
+ "last_prediction": null
835
+ },
836
+ "ground_truth": {
837
+ "method": "C04",
838
+ "pair_id": "S20",
839
+ "pair_role": "B",
840
+ "expected_a_higher": true,
841
+ "min_intensity_diff": 1
842
+ }
843
+ },
844
+ {
845
+ "id": "S21",
846
+ "cat": "C05",
847
+ "pillar": "P2",
848
+ "name_kr": "์ง€์†์œ„ํ˜‘ ๊ฐ์ •๊ฒฉํ™”",
849
+ "type": "sequence",
850
+ "input_sequence": [
851
+ {
852
+ "walls": {
853
+ "left": null,
854
+ "right": null,
855
+ "front": null
856
+ },
857
+ "ground": "flat",
858
+ "npc_nearby": true,
859
+ "npc_type": "beast",
860
+ "npc_behavior": "charge",
861
+ "npc_distance": 5.0,
862
+ "npc_direction": "front",
863
+ "sound": "aggressive growling",
864
+ "recent_decisions": [],
865
+ "last_prediction": null
866
+ },
867
+ {
868
+ "walls": {
869
+ "left": null,
870
+ "right": null,
871
+ "front": null
872
+ },
873
+ "ground": "flat",
874
+ "npc_nearby": true,
875
+ "npc_type": "beast",
876
+ "npc_behavior": "charge",
877
+ "npc_distance": 4.0,
878
+ "npc_direction": "front",
879
+ "sound": "aggressive growling",
880
+ "recent_decisions": [
881
+ "ran away from beast"
882
+ ],
883
+ "last_prediction": "fwd=danger(beast)"
884
+ },
885
+ {
886
+ "walls": {
887
+ "left": null,
888
+ "right": null,
889
+ "front": null
890
+ },
891
+ "ground": "flat",
892
+ "npc_nearby": true,
893
+ "npc_type": "beast",
894
+ "npc_behavior": "charge",
895
+ "npc_distance": 3.0,
896
+ "npc_direction": "front",
897
+ "sound": "aggressive growling",
898
+ "recent_decisions": [
899
+ "ran away from beast",
900
+ "sprinting in fear"
901
+ ],
902
+ "last_prediction": "fwd=danger(beast)"
903
+ },
904
+ {
905
+ "walls": {
906
+ "left": null,
907
+ "right": null,
908
+ "front": null
909
+ },
910
+ "ground": "flat",
911
+ "npc_nearby": true,
912
+ "npc_type": "beast",
913
+ "npc_behavior": "charge",
914
+ "npc_distance": 2.0,
915
+ "npc_direction": "front",
916
+ "sound": "aggressive growling",
917
+ "recent_decisions": [
918
+ "sprinting in fear",
919
+ "desperate escape"
920
+ ],
921
+ "last_prediction": "fwd=danger(beast)"
922
+ }
923
+ ],
924
+ "ground_truth": {
925
+ "method": "C05",
926
+ "expected_trend": "increasing"
927
+ }
928
+ },
929
+ {
930
+ "id": "S22",
931
+ "cat": "C05",
932
+ "pillar": "P2",
933
+ "name_kr": "์ ‘๊ทผโ†’๋Œ์ง„ ์ „ํ™˜",
934
+ "type": "sequence",
935
+ "input_sequence": [
936
+ {
937
+ "walls": {
938
+ "left": null,
939
+ "right": null,
940
+ "front": null
941
+ },
942
+ "ground": "flat",
943
+ "npc_nearby": true,
944
+ "npc_type": "beast",
945
+ "npc_behavior": "approach",
946
+ "npc_distance": 5.0,
947
+ "npc_direction": "front",
948
+ "sound": null,
949
+ "recent_decisions": [],
950
+ "last_prediction": null
951
+ },
952
+ {
953
+ "walls": {
954
+ "left": null,
955
+ "right": null,
956
+ "front": null
957
+ },
958
+ "ground": "flat",
959
+ "npc_nearby": true,
960
+ "npc_type": "beast",
961
+ "npc_behavior": "approach",
962
+ "npc_distance": 4.0,
963
+ "npc_direction": "front",
964
+ "sound": "aggressive growling",
965
+ "recent_decisions": [
966
+ "stepped back cautiously"
967
+ ],
968
+ "last_prediction": "fwd=danger(beast)"
969
+ },
970
+ {
971
+ "walls": {
972
+ "left": null,
973
+ "right": null,
974
+ "front": null
975
+ },
976
+ "ground": "flat",
977
+ "npc_nearby": true,
978
+ "npc_type": "beast",
979
+ "npc_behavior": "charge",
980
+ "npc_distance": 3.0,
981
+ "npc_direction": "front",
982
+ "sound": "aggressive growling",
983
+ "recent_decisions": [
984
+ "stepped back cautiously",
985
+ "backing away slowly"
986
+ ],
987
+ "last_prediction": "fwd=danger(beast)"
988
+ }
989
+ ],
990
+ "ground_truth": {
991
+ "method": "C05",
992
+ "expected_trend": "increasing"
993
+ }
994
+ },
995
+ {
996
+ "id": "S23",
997
+ "cat": "C05",
998
+ "pillar": "P2",
999
+ "name_kr": "์œ„ํ˜‘ํ•ด์ œ ์ง„์ •",
1000
+ "type": "sequence",
1001
+ "input_sequence": [
1002
+ {
1003
+ "walls": {
1004
+ "left": null,
1005
+ "right": null,
1006
+ "front": null
1007
+ },
1008
+ "ground": "flat",
1009
+ "npc_nearby": true,
1010
+ "npc_type": "beast",
1011
+ "npc_behavior": "charge",
1012
+ "npc_distance": 2.0,
1013
+ "npc_direction": "front",
1014
+ "sound": "aggressive growling",
1015
+ "recent_decisions": [
1016
+ "desperate sprint",
1017
+ "fleeing in terror"
1018
+ ],
1019
+ "last_prediction": "fwd=danger(beast)"
1020
+ },
1021
+ {
1022
+ "walls": {
1023
+ "left": null,
1024
+ "right": null,
1025
+ "front": null
1026
+ },
1027
+ "ground": "flat",
1028
+ "npc_nearby": true,
1029
+ "npc_type": "beast",
1030
+ "npc_behavior": "stop",
1031
+ "npc_distance": 6.0,
1032
+ "npc_direction": "front",
1033
+ "sound": null,
1034
+ "recent_decisions": [
1035
+ "fleeing in terror",
1036
+ "sprinting away"
1037
+ ],
1038
+ "last_prediction": "fwd=danger(beast)"
1039
+ },
1040
+ {
1041
+ "walls": {
1042
+ "left": null,
1043
+ "right": null,
1044
+ "front": null
1045
+ },
1046
+ "ground": "flat",
1047
+ "npc_nearby": false,
1048
+ "npc_type": null,
1049
+ "npc_behavior": null,
1050
+ "npc_distance": null,
1051
+ "npc_direction": null,
1052
+ "sound": null,
1053
+ "recent_decisions": [
1054
+ "sprinting away",
1055
+ "slowing down cautiously"
1056
+ ],
1057
+ "last_prediction": "fwd=safe"
1058
+ }
1059
+ ],
1060
+ "ground_truth": {
1061
+ "method": "C05",
1062
+ "expected_trend": "decreasing"
1063
+ }
1064
+ },
1065
+ {
1066
+ "id": "S24",
1067
+ "cat": "C05",
1068
+ "pillar": "P2",
1069
+ "name_kr": "์žฌ์ถœํ˜„ ๋ฏผ๊ฐํ™”",
1070
+ "type": "sequence",
1071
+ "input_sequence": [
1072
+ {
1073
+ "walls": {
1074
+ "left": null,
1075
+ "right": null,
1076
+ "front": null
1077
+ },
1078
+ "ground": "flat",
1079
+ "npc_nearby": false,
1080
+ "npc_type": null,
1081
+ "npc_behavior": null,
1082
+ "npc_distance": null,
1083
+ "npc_direction": null,
1084
+ "sound": null,
1085
+ "recent_decisions": [
1086
+ "previously encountered beast and fled",
1087
+ "was terrified"
1088
+ ],
1089
+ "last_prediction": null
1090
+ },
1091
+ {
1092
+ "walls": {
1093
+ "left": null,
1094
+ "right": null,
1095
+ "front": null
1096
+ },
1097
+ "ground": "flat",
1098
+ "npc_nearby": true,
1099
+ "npc_type": "beast",
1100
+ "npc_behavior": "approach",
1101
+ "npc_distance": 5.0,
1102
+ "npc_direction": "front",
1103
+ "sound": "aggressive growling",
1104
+ "recent_decisions": [
1105
+ "previously encountered beast and fled",
1106
+ "was terrified",
1107
+ "beast appeared again"
1108
+ ],
1109
+ "last_prediction": null
1110
+ }
1111
+ ],
1112
+ "ground_truth": {
1113
+ "method": "C05",
1114
+ "expected_trend": "increasing",
1115
+ "note": "์ด์ „ ๊ฒฝํ—˜์ด ์žˆ์œผ๋ฏ€๋กœ ์ฒซ ๋ฐ˜์‘๋ถ€ํ„ฐ ๋†’์€ ๊ฐ•๋„ ๊ธฐ๋Œ€"
1116
+ }
1117
+ },
1118
+ {
1119
+ "id": "S25",
1120
+ "cat": "C05",
1121
+ "pillar": "P2",
1122
+ "name_kr": "๋‚ฎ์€์œ„ํ˜‘ ํ‰์ •์œ ์ง€",
1123
+ "type": "sequence",
1124
+ "input_sequence": [
1125
+ {
1126
+ "walls": {
1127
+ "left": null,
1128
+ "right": null,
1129
+ "front": null
1130
+ },
1131
+ "ground": "flat",
1132
+ "npc_nearby": true,
1133
+ "npc_type": "woman",
1134
+ "npc_behavior": "approach",
1135
+ "npc_distance": 5.0,
1136
+ "npc_direction": "front",
1137
+ "sound": "footsteps",
1138
+ "recent_decisions": [],
1139
+ "last_prediction": null
1140
+ },
1141
+ {
1142
+ "walls": {
1143
+ "left": null,
1144
+ "right": null,
1145
+ "front": null
1146
+ },
1147
+ "ground": "flat",
1148
+ "npc_nearby": true,
1149
+ "npc_type": "woman",
1150
+ "npc_behavior": "approach",
1151
+ "npc_distance": 4.0,
1152
+ "npc_direction": "front",
1153
+ "sound": "footsteps",
1154
+ "recent_decisions": [
1155
+ "stepped aside"
1156
+ ],
1157
+ "last_prediction": "fwd=safe"
1158
+ },
1159
+ {
1160
+ "walls": {
1161
+ "left": null,
1162
+ "right": null,
1163
+ "front": null
1164
+ },
1165
+ "ground": "flat",
1166
+ "npc_nearby": true,
1167
+ "npc_type": "woman",
1168
+ "npc_behavior": "stop",
1169
+ "npc_distance": 3.0,
1170
+ "npc_direction": "front",
1171
+ "sound": null,
1172
+ "recent_decisions": [
1173
+ "stepped aside",
1174
+ "walking normally"
1175
+ ],
1176
+ "last_prediction": "fwd=safe"
1177
+ }
1178
+ ],
1179
+ "ground_truth": {
1180
+ "method": "C05",
1181
+ "expected_trend": "stable"
1182
+ }
1183
+ },
1184
+ {
1185
+ "id": "S26A",
1186
+ "cat": "C06",
1187
+ "pillar": "P2",
1188
+ "name_kr": "๊ธฐ์–ต์—†์ด(๊ธฐ์ค€์„ )",
1189
+ "type": "memory_pair",
1190
+ "input": {
1191
+ "walls": {
1192
+ "left": null,
1193
+ "right": 1.5,
1194
+ "front": null
1195
+ },
1196
+ "ground": "flat",
1197
+ "npc_nearby": true,
1198
+ "npc_type": "beast",
1199
+ "npc_behavior": "charge",
1200
+ "npc_distance": 3.0,
1201
+ "npc_direction": "front",
1202
+ "sound": "aggressive growling",
1203
+ "recent_decisions": [],
1204
+ "last_prediction": null
1205
+ },
1206
+ "ground_truth": {
1207
+ "method": "C06",
1208
+ "pair_id": "S26",
1209
+ "pair_role": "without_memory"
1210
+ }
1211
+ },
1212
+ {
1213
+ "id": "S26B",
1214
+ "cat": "C06",
1215
+ "pillar": "P2",
1216
+ "name_kr": "๊ธฐ์–ต์žˆ์Œ(์˜ค๋ฅธ์ชฝ์‹คํŒจ)",
1217
+ "type": "memory_pair",
1218
+ "input": {
1219
+ "walls": {
1220
+ "left": null,
1221
+ "right": 1.5,
1222
+ "front": null
1223
+ },
1224
+ "ground": "flat",
1225
+ "npc_nearby": true,
1226
+ "npc_type": "beast",
1227
+ "npc_behavior": "charge",
1228
+ "npc_distance": 3.0,
1229
+ "npc_direction": "front",
1230
+ "sound": "aggressive growling",
1231
+ "recent_decisions": [
1232
+ "sprinted right but hit wall",
1233
+ "had to reverse direction",
1234
+ "barely escaped"
1235
+ ],
1236
+ "last_prediction": "right=danger(wall), fwd=danger(beast)"
1237
+ },
1238
+ "ground_truth": {
1239
+ "method": "C06",
1240
+ "pair_id": "S26",
1241
+ "pair_role": "with_memory",
1242
+ "memory_relevant": true,
1243
+ "expected_change": "direction",
1244
+ "memory_direction_avoid": "right"
1245
+ }
1246
+ },
1247
+ {
1248
+ "id": "S27A",
1249
+ "cat": "C06",
1250
+ "pillar": "P2",
1251
+ "name_kr": "๊ธฐ์–ต์—†์ด(๊ธฐ์ค€์„ 2)",
1252
+ "type": "memory_pair",
1253
+ "input": {
1254
+ "walls": {
1255
+ "left": 1.5,
1256
+ "right": null,
1257
+ "front": null
1258
+ },
1259
+ "ground": "flat",
1260
+ "npc_nearby": true,
1261
+ "npc_type": "beast",
1262
+ "npc_behavior": "charge",
1263
+ "npc_distance": 3.0,
1264
+ "npc_direction": "front",
1265
+ "sound": "aggressive growling",
1266
+ "recent_decisions": [],
1267
+ "last_prediction": null
1268
+ },
1269
+ "ground_truth": {
1270
+ "method": "C06",
1271
+ "pair_id": "S27",
1272
+ "pair_role": "without_memory"
1273
+ }
1274
+ },
1275
+ {
1276
+ "id": "S27B",
1277
+ "cat": "C06",
1278
+ "pillar": "P2",
1279
+ "name_kr": "๊ธฐ์–ต์žˆ์Œ(์™ผ์ชฝ์‹คํŒจ)",
1280
+ "type": "memory_pair",
1281
+ "input": {
1282
+ "walls": {
1283
+ "left": 1.5,
1284
+ "right": null,
1285
+ "front": null
1286
+ },
1287
+ "ground": "flat",
1288
+ "npc_nearby": true,
1289
+ "npc_type": "beast",
1290
+ "npc_behavior": "charge",
1291
+ "npc_distance": 3.0,
1292
+ "npc_direction": "front",
1293
+ "sound": "aggressive growling",
1294
+ "recent_decisions": [
1295
+ "tried going left but wall blocked",
1296
+ "switched to right and escaped"
1297
+ ],
1298
+ "last_prediction": "left=danger(wall), fwd=danger(beast)"
1299
+ },
1300
+ "ground_truth": {
1301
+ "method": "C06",
1302
+ "pair_id": "S27",
1303
+ "pair_role": "with_memory",
1304
+ "memory_relevant": true,
1305
+ "expected_change": "direction",
1306
+ "memory_direction_avoid": "left"
1307
+ }
1308
+ },
1309
+ {
1310
+ "id": "S28A",
1311
+ "cat": "C06",
1312
+ "pillar": "P2",
1313
+ "name_kr": "๋ฌด๊ด€ํ•œ ๊ธฐ์–ต(๊ธฐ์ค€์„ )",
1314
+ "type": "memory_pair",
1315
+ "input": {
1316
+ "walls": {
1317
+ "left": null,
1318
+ "right": null,
1319
+ "front": 3.0
1320
+ },
1321
+ "ground": "flat",
1322
+ "npc_nearby": false,
1323
+ "npc_type": null,
1324
+ "npc_behavior": null,
1325
+ "npc_distance": null,
1326
+ "npc_direction": null,
1327
+ "sound": null,
1328
+ "recent_decisions": [],
1329
+ "last_prediction": null
1330
+ },
1331
+ "ground_truth": {
1332
+ "method": "C06",
1333
+ "pair_id": "S28",
1334
+ "pair_role": "without_memory"
1335
+ }
1336
+ },
1337
+ {
1338
+ "id": "S28B",
1339
+ "cat": "C06",
1340
+ "pillar": "P2",
1341
+ "name_kr": "๋ฌด๊ด€ํ•œ ๊ธฐ์–ต(๋งน์ˆ˜๊ธฐ์–ต)",
1342
+ "type": "memory_pair",
1343
+ "input": {
1344
+ "walls": {
1345
+ "left": null,
1346
+ "right": null,
1347
+ "front": 3.0
1348
+ },
1349
+ "ground": "flat",
1350
+ "npc_nearby": false,
1351
+ "npc_type": null,
1352
+ "npc_behavior": null,
1353
+ "npc_distance": null,
1354
+ "npc_direction": null,
1355
+ "sound": null,
1356
+ "recent_decisions": [
1357
+ "previously fled from a beast",
1358
+ "was terrified"
1359
+ ],
1360
+ "last_prediction": "fwd=danger(beast)"
1361
+ },
1362
+ "ground_truth": {
1363
+ "method": "C06",
1364
+ "pair_id": "S28",
1365
+ "pair_role": "with_memory",
1366
+ "memory_relevant": false
1367
+ }
1368
+ },
1369
+ {
1370
+ "id": "S29A",
1371
+ "cat": "C06",
1372
+ "pillar": "P2",
1373
+ "name_kr": "์ผ๊ด€์„ฑ ํ…Œ์ŠคํŠธ 1ํšŒ์ฐจ",
1374
+ "type": "memory_pair",
1375
+ "input": {
1376
+ "walls": {
1377
+ "left": null,
1378
+ "right": null,
1379
+ "front": null
1380
+ },
1381
+ "ground": "flat",
1382
+ "npc_nearby": true,
1383
+ "npc_type": "beast",
1384
+ "npc_behavior": "approach",
1385
+ "npc_distance": 4.0,
1386
+ "npc_direction": "front",
1387
+ "sound": "aggressive growling",
1388
+ "recent_decisions": [],
1389
+ "last_prediction": null
1390
+ },
1391
+ "ground_truth": {
1392
+ "method": "C06",
1393
+ "pair_id": "S29",
1394
+ "pair_role": "without_memory"
1395
+ }
1396
+ },
1397
+ {
1398
+ "id": "S29B",
1399
+ "cat": "C06",
1400
+ "pillar": "P2",
1401
+ "name_kr": "์ผ๊ด€์„ฑ ํ…Œ์ŠคํŠธ 2ํšŒ์ฐจ",
1402
+ "type": "memory_pair",
1403
+ "input": {
1404
+ "walls": {
1405
+ "left": null,
1406
+ "right": null,
1407
+ "front": null
1408
+ },
1409
+ "ground": "flat",
1410
+ "npc_nearby": true,
1411
+ "npc_type": "beast",
1412
+ "npc_behavior": "approach",
1413
+ "npc_distance": 4.0,
1414
+ "npc_direction": "front",
1415
+ "sound": "aggressive growling",
1416
+ "recent_decisions": [
1417
+ "backed away from approaching beast"
1418
+ ],
1419
+ "last_prediction": "fwd=danger(beast)"
1420
+ },
1421
+ "ground_truth": {
1422
+ "method": "C06",
1423
+ "pair_id": "S29",
1424
+ "pair_role": "with_memory",
1425
+ "memory_relevant": true,
1426
+ "expected_change": "intensity",
1427
+ "memory_direction_avoid": null
1428
+ }
1429
+ },
1430
+ {
1431
+ "id": "S30A",
1432
+ "cat": "C06",
1433
+ "pillar": "P2",
1434
+ "name_kr": "๋นˆ ๊ธฐ์–ต(๊ธฐ์ค€์„ )",
1435
+ "type": "memory_pair",
1436
+ "input": {
1437
+ "walls": {
1438
+ "left": null,
1439
+ "right": null,
1440
+ "front": null
1441
+ },
1442
+ "ground": "flat",
1443
+ "npc_nearby": true,
1444
+ "npc_type": "woman",
1445
+ "npc_behavior": "approach",
1446
+ "npc_distance": 3.0,
1447
+ "npc_direction": "front",
1448
+ "sound": "footsteps",
1449
+ "recent_decisions": [],
1450
+ "last_prediction": null
1451
+ },
1452
+ "ground_truth": {
1453
+ "method": "C06",
1454
+ "pair_id": "S30",
1455
+ "pair_role": "without_memory"
1456
+ }
1457
+ },
1458
+ {
1459
+ "id": "S30B",
1460
+ "cat": "C06",
1461
+ "pillar": "P2",
1462
+ "name_kr": "๋นˆ ๊ธฐ์–ต(์—ฌ์„ฑ๊ธฐ์–ต)",
1463
+ "type": "memory_pair",
1464
+ "input": {
1465
+ "walls": {
1466
+ "left": null,
1467
+ "right": null,
1468
+ "front": null
1469
+ },
1470
+ "ground": "flat",
1471
+ "npc_nearby": true,
1472
+ "npc_type": "woman",
1473
+ "npc_behavior": "approach",
1474
+ "npc_distance": 3.0,
1475
+ "npc_direction": "front",
1476
+ "sound": "footsteps",
1477
+ "recent_decisions": [
1478
+ "a woman approached and passed by peacefully"
1479
+ ],
1480
+ "last_prediction": "fwd=safe"
1481
+ },
1482
+ "ground_truth": {
1483
+ "method": "C06",
1484
+ "pair_id": "S30",
1485
+ "pair_role": "with_memory",
1486
+ "memory_relevant": true,
1487
+ "expected_change": "intensity",
1488
+ "memory_direction_avoid": null
1489
+ }
1490
+ },
1491
+ {
1492
+ "id": "S31",
1493
+ "cat": "C07",
1494
+ "pillar": "P2",
1495
+ "name_kr": "๋งน์ˆ˜ํ•ด์ œ ์งํ›„",
1496
+ "type": "sequence",
1497
+ "input_sequence": [
1498
+ {
1499
+ "walls": {
1500
+ "left": null,
1501
+ "right": null,
1502
+ "front": null
1503
+ },
1504
+ "ground": "flat",
1505
+ "npc_nearby": true,
1506
+ "npc_type": "beast",
1507
+ "npc_behavior": "charge",
1508
+ "npc_distance": 2.0,
1509
+ "npc_direction": "front",
1510
+ "sound": "aggressive growling",
1511
+ "recent_decisions": [],
1512
+ "last_prediction": null
1513
+ },
1514
+ {
1515
+ "walls": {
1516
+ "left": null,
1517
+ "right": null,
1518
+ "front": null
1519
+ },
1520
+ "ground": "flat",
1521
+ "npc_nearby": true,
1522
+ "npc_type": "beast",
1523
+ "npc_behavior": "stop",
1524
+ "npc_distance": 5.0,
1525
+ "npc_direction": "front",
1526
+ "sound": null,
1527
+ "recent_decisions": [
1528
+ "sprinted away in terror"
1529
+ ],
1530
+ "last_prediction": "fwd=danger(beast)"
1531
+ },
1532
+ {
1533
+ "walls": {
1534
+ "left": null,
1535
+ "right": null,
1536
+ "front": null
1537
+ },
1538
+ "ground": "flat",
1539
+ "npc_nearby": false,
1540
+ "npc_type": null,
1541
+ "npc_behavior": null,
1542
+ "npc_distance": null,
1543
+ "npc_direction": null,
1544
+ "sound": null,
1545
+ "recent_decisions": [
1546
+ "sprinted away in terror",
1547
+ "slowing down"
1548
+ ],
1549
+ "last_prediction": "fwd=safe"
1550
+ }
1551
+ ],
1552
+ "ground_truth": {
1553
+ "method": "C07",
1554
+ "expected_recovery": "gradual"
1555
+ }
1556
+ },
1557
+ {
1558
+ "id": "S32",
1559
+ "cat": "C07",
1560
+ "pillar": "P2",
1561
+ "name_kr": "์ ์ง„์  ์ •์ƒํ™”",
1562
+ "type": "sequence",
1563
+ "input_sequence": [
1564
+ {
1565
+ "walls": {
1566
+ "left": null,
1567
+ "right": null,
1568
+ "front": null
1569
+ },
1570
+ "ground": "flat",
1571
+ "npc_nearby": true,
1572
+ "npc_type": "beast",
1573
+ "npc_behavior": "charge",
1574
+ "npc_distance": 2.0,
1575
+ "npc_direction": "front",
1576
+ "sound": "aggressive growling",
1577
+ "recent_decisions": [
1578
+ "desperate flight"
1579
+ ],
1580
+ "last_prediction": "fwd=danger(beast)"
1581
+ },
1582
+ {
1583
+ "walls": {
1584
+ "left": null,
1585
+ "right": null,
1586
+ "front": null
1587
+ },
1588
+ "ground": "flat",
1589
+ "npc_nearby": false,
1590
+ "npc_type": null,
1591
+ "npc_behavior": null,
1592
+ "npc_distance": null,
1593
+ "npc_direction": null,
1594
+ "sound": null,
1595
+ "recent_decisions": [
1596
+ "desperate flight",
1597
+ "beast disappeared"
1598
+ ],
1599
+ "last_prediction": "all=safe"
1600
+ },
1601
+ {
1602
+ "walls": {
1603
+ "left": null,
1604
+ "right": null,
1605
+ "front": null
1606
+ },
1607
+ "ground": "flat",
1608
+ "npc_nearby": false,
1609
+ "npc_type": null,
1610
+ "npc_behavior": null,
1611
+ "npc_distance": null,
1612
+ "npc_direction": null,
1613
+ "sound": null,
1614
+ "recent_decisions": [
1615
+ "beast disappeared",
1616
+ "walking cautiously"
1617
+ ],
1618
+ "last_prediction": "all=safe"
1619
+ },
1620
+ {
1621
+ "walls": {
1622
+ "left": null,
1623
+ "right": null,
1624
+ "front": null
1625
+ },
1626
+ "ground": "flat",
1627
+ "npc_nearby": false,
1628
+ "npc_type": null,
1629
+ "npc_behavior": null,
1630
+ "npc_distance": null,
1631
+ "npc_direction": null,
1632
+ "sound": null,
1633
+ "recent_decisions": [
1634
+ "walking cautiously",
1635
+ "feeling safer"
1636
+ ],
1637
+ "last_prediction": "all=safe"
1638
+ }
1639
+ ],
1640
+ "ground_truth": {
1641
+ "method": "C07",
1642
+ "expected_recovery": "gradual"
1643
+ }
1644
+ },
1645
+ {
1646
+ "id": "S33",
1647
+ "cat": "C07",
1648
+ "pillar": "P2",
1649
+ "name_kr": "ํ•ด์ œํ›„ ๋ฒฝ ๋งŒ๋‚จ",
1650
+ "type": "sequence",
1651
+ "input_sequence": [
1652
+ {
1653
+ "walls": {
1654
+ "left": null,
1655
+ "right": null,
1656
+ "front": null
1657
+ },
1658
+ "ground": "flat",
1659
+ "npc_nearby": true,
1660
+ "npc_type": "beast",
1661
+ "npc_behavior": "charge",
1662
+ "npc_distance": 3.0,
1663
+ "npc_direction": "front",
1664
+ "sound": "aggressive growling",
1665
+ "recent_decisions": [],
1666
+ "last_prediction": "fwd=danger(beast)"
1667
+ },
1668
+ {
1669
+ "walls": {
1670
+ "left": null,
1671
+ "right": null,
1672
+ "front": null
1673
+ },
1674
+ "ground": "flat",
1675
+ "npc_nearby": false,
1676
+ "npc_type": null,
1677
+ "npc_behavior": null,
1678
+ "npc_distance": null,
1679
+ "npc_direction": null,
1680
+ "sound": null,
1681
+ "recent_decisions": [
1682
+ "sprinted away"
1683
+ ],
1684
+ "last_prediction": "all=safe"
1685
+ },
1686
+ {
1687
+ "walls": {
1688
+ "left": null,
1689
+ "right": null,
1690
+ "front": 2.0
1691
+ },
1692
+ "ground": "flat",
1693
+ "npc_nearby": false,
1694
+ "npc_type": null,
1695
+ "npc_behavior": null,
1696
+ "npc_distance": null,
1697
+ "npc_direction": null,
1698
+ "sound": null,
1699
+ "recent_decisions": [
1700
+ "sprinted away",
1701
+ "walking cautiously"
1702
+ ],
1703
+ "last_prediction": "fwd=danger(wall)"
1704
+ }
1705
+ ],
1706
+ "ground_truth": {
1707
+ "method": "C07",
1708
+ "expected_recovery": "gradual",
1709
+ "note": "๊ณตํฌโ†’๊ฒฝ๊ณ„โ†’์ผ๋ฐ˜์žฅ์• ๋ฌผํšŒํ”ผ ์ „ํ™˜"
1710
+ }
1711
+ },
1712
+ {
1713
+ "id": "S34",
1714
+ "cat": "C07",
1715
+ "pillar": "P2",
1716
+ "name_kr": "์ •์ƒํ™”์ค‘ ์žฌ์ถœํ˜„",
1717
+ "type": "sequence",
1718
+ "input_sequence": [
1719
+ {
1720
+ "walls": {
1721
+ "left": null,
1722
+ "right": null,
1723
+ "front": null
1724
+ },
1725
+ "ground": "flat",
1726
+ "npc_nearby": false,
1727
+ "npc_type": null,
1728
+ "npc_behavior": null,
1729
+ "npc_distance": null,
1730
+ "npc_direction": null,
1731
+ "sound": null,
1732
+ "recent_decisions": [
1733
+ "beast fled",
1734
+ "calming down"
1735
+ ],
1736
+ "last_prediction": "all=safe"
1737
+ },
1738
+ {
1739
+ "walls": {
1740
+ "left": null,
1741
+ "right": null,
1742
+ "front": null
1743
+ },
1744
+ "ground": "flat",
1745
+ "npc_nearby": true,
1746
+ "npc_type": "beast",
1747
+ "npc_behavior": "approach",
1748
+ "npc_distance": 5.0,
1749
+ "npc_direction": "front",
1750
+ "sound": "aggressive growling",
1751
+ "recent_decisions": [
1752
+ "calming down",
1753
+ "walking normally"
1754
+ ],
1755
+ "last_prediction": "all=safe"
1756
+ }
1757
+ ],
1758
+ "ground_truth": {
1759
+ "method": "C05",
1760
+ "expected_trend": "increasing",
1761
+ "note": "์žฌํ™œ์„ฑํ™” ํ…Œ์ŠคํŠธ"
1762
+ }
1763
+ },
1764
+ {
1765
+ "id": "S35",
1766
+ "cat": "C07",
1767
+ "pillar": "P2",
1768
+ "name_kr": "๋งน์ˆ˜ํ›„ ์—ฌ์„ฑ์ ‘๊ทผ",
1769
+ "type": "sequence",
1770
+ "input_sequence": [
1771
+ {
1772
+ "walls": {
1773
+ "left": null,
1774
+ "right": null,
1775
+ "front": null
1776
+ },
1777
+ "ground": "flat",
1778
+ "npc_nearby": true,
1779
+ "npc_type": "beast",
1780
+ "npc_behavior": "charge",
1781
+ "npc_distance": 2.0,
1782
+ "npc_direction": "front",
1783
+ "sound": "aggressive growling",
1784
+ "recent_decisions": [],
1785
+ "last_prediction": "fwd=danger(beast)"
1786
+ },
1787
+ {
1788
+ "walls": {
1789
+ "left": null,
1790
+ "right": null,
1791
+ "front": null
1792
+ },
1793
+ "ground": "flat",
1794
+ "npc_nearby": false,
1795
+ "npc_type": null,
1796
+ "npc_behavior": null,
1797
+ "npc_distance": null,
1798
+ "npc_direction": null,
1799
+ "sound": null,
1800
+ "recent_decisions": [
1801
+ "fled from beast"
1802
+ ],
1803
+ "last_prediction": "all=safe"
1804
+ },
1805
+ {
1806
+ "walls": {
1807
+ "left": null,
1808
+ "right": null,
1809
+ "front": null
1810
+ },
1811
+ "ground": "flat",
1812
+ "npc_nearby": true,
1813
+ "npc_type": "woman",
1814
+ "npc_behavior": "approach",
1815
+ "npc_distance": 4.0,
1816
+ "npc_direction": "front",
1817
+ "sound": "footsteps",
1818
+ "recent_decisions": [
1819
+ "fled from beast",
1820
+ "calming down"
1821
+ ],
1822
+ "last_prediction": "all=safe"
1823
+ }
1824
+ ],
1825
+ "ground_truth": {
1826
+ "method": "C07",
1827
+ "expected_recovery": "gradual",
1828
+ "note": "๊ณผ์ž‰๋ฐ˜์‘(๋‚˜์จ) vs ์ ์ ˆ๊ฒฝ๊ณ„(์ข‹์Œ)"
1829
+ }
1830
+ },
1831
+ {
1832
+ "id": "S36",
1833
+ "cat": "C08",
1834
+ "pillar": "P3",
1835
+ "name_kr": "๊ณตํฌ ์ „๋ ฅ์งˆ์ฃผ",
1836
+ "type": "single",
1837
+ "input": {
1838
+ "walls": {
1839
+ "left": null,
1840
+ "right": null,
1841
+ "front": null
1842
+ },
1843
+ "ground": "flat",
1844
+ "npc_nearby": true,
1845
+ "npc_type": "beast",
1846
+ "npc_behavior": "charge",
1847
+ "npc_distance": 2.0,
1848
+ "npc_direction": "front",
1849
+ "sound": "aggressive growling",
1850
+ "recent_decisions": [],
1851
+ "last_prediction": null
1852
+ },
1853
+ "ground_truth": {
1854
+ "method": "C08",
1855
+ "expected_min_intensity": 4,
1856
+ "expected_emotion": true,
1857
+ "expected_min_descriptors": 2
1858
+ }
1859
+ },
1860
+ {
1861
+ "id": "S37",
1862
+ "cat": "C08",
1863
+ "pillar": "P3",
1864
+ "name_kr": "๊ฒฝ๊ณ„ ๋ณดํ–‰",
1865
+ "type": "single",
1866
+ "input": {
1867
+ "walls": {
1868
+ "left": null,
1869
+ "right": null,
1870
+ "front": null
1871
+ },
1872
+ "ground": "flat",
1873
+ "npc_nearby": false,
1874
+ "npc_type": null,
1875
+ "npc_behavior": null,
1876
+ "npc_distance": null,
1877
+ "npc_direction": null,
1878
+ "sound": null,
1879
+ "recent_decisions": [
1880
+ "just escaped a charging beast",
1881
+ "heart still racing"
1882
+ ],
1883
+ "last_prediction": "all=safe"
1884
+ },
1885
+ "ground_truth": {
1886
+ "method": "C08",
1887
+ "expected_min_intensity": 2,
1888
+ "expected_emotion": true,
1889
+ "expected_min_descriptors": 1
1890
+ }
1891
+ },
1892
+ {
1893
+ "id": "S38",
1894
+ "cat": "C08",
1895
+ "pillar": "P3",
1896
+ "name_kr": "์ •์ง€ ๋ฐ˜์‘(์–ผ์–ด๋ถ™์Œ)",
1897
+ "type": "single",
1898
+ "input": {
1899
+ "walls": {
1900
+ "left": null,
1901
+ "right": null,
1902
+ "front": null
1903
+ },
1904
+ "ground": "flat",
1905
+ "npc_nearby": true,
1906
+ "npc_type": "beast",
1907
+ "npc_behavior": "stop",
1908
+ "npc_distance": 5.0,
1909
+ "npc_direction": "front",
1910
+ "sound": "aggressive growling",
1911
+ "recent_decisions": [],
1912
+ "last_prediction": null
1913
+ },
1914
+ "ground_truth": {
1915
+ "method": "C08",
1916
+ "expected_min_intensity": 1,
1917
+ "expected_emotion": true,
1918
+ "expected_min_descriptors": 1
1919
+ }
1920
+ },
1921
+ {
1922
+ "id": "S39",
1923
+ "cat": "C08",
1924
+ "pillar": "P3",
1925
+ "name_kr": "์•ˆ๋„ ํ‘œํ˜„",
1926
+ "type": "single",
1927
+ "input": {
1928
+ "walls": {
1929
+ "left": null,
1930
+ "right": null,
1931
+ "front": null
1932
+ },
1933
+ "ground": "flat",
1934
+ "npc_nearby": false,
1935
+ "npc_type": null,
1936
+ "npc_behavior": null,
1937
+ "npc_distance": null,
1938
+ "npc_direction": null,
1939
+ "sound": null,
1940
+ "recent_decisions": [
1941
+ "beast finally gone",
1942
+ "survived the encounter"
1943
+ ],
1944
+ "last_prediction": "all=safe"
1945
+ },
1946
+ "ground_truth": {
1947
+ "method": "C08",
1948
+ "expected_min_intensity": 1,
1949
+ "expected_emotion": true,
1950
+ "expected_min_descriptors": 1
1951
+ }
1952
+ },
1953
+ {
1954
+ "id": "S40",
1955
+ "cat": "C08",
1956
+ "pillar": "P3",
1957
+ "name_kr": "๋ฐฉ์–ด ์ž์„ธ",
1958
+ "type": "single",
1959
+ "input": {
1960
+ "walls": {
1961
+ "left": null,
1962
+ "right": null,
1963
+ "front": null
1964
+ },
1965
+ "ground": "flat",
1966
+ "npc_nearby": true,
1967
+ "npc_type": "woman",
1968
+ "npc_behavior": "approach",
1969
+ "npc_distance": 3.0,
1970
+ "npc_direction": "front",
1971
+ "sound": "footsteps",
1972
+ "recent_decisions": [],
1973
+ "last_prediction": null
1974
+ },
1975
+ "ground_truth": {
1976
+ "method": "C08",
1977
+ "expected_min_intensity": 1,
1978
+ "expected_emotion": false,
1979
+ "expected_min_descriptors": 1
1980
+ }
1981
+ },
1982
+ {
1983
+ "id": "S41",
1984
+ "cat": "C09",
1985
+ "pillar": "P3",
1986
+ "name_kr": "ํ”„๋ ˆ์ž„ ์ƒ์„ฑ์†๋„",
1987
+ "type": "performance",
1988
+ "ground_truth": {
1989
+ "method": "C09",
1990
+ "metric": "fps",
1991
+ "threshold_excellent": 45,
1992
+ "threshold_good": 30,
1993
+ "threshold_min": 15
1994
+ }
1995
+ },
1996
+ {
1997
+ "id": "S42",
1998
+ "cat": "C09",
1999
+ "pillar": "P3",
2000
+ "name_kr": "์ธ์ง€๋ฃจํ”„ ์ง€์—ฐ",
2001
+ "type": "performance",
2002
+ "ground_truth": {
2003
+ "method": "C09",
2004
+ "metric": "cognitive_latency_ms",
2005
+ "threshold_excellent": 3000,
2006
+ "threshold_good": 5000,
2007
+ "threshold_min": 10000
2008
+ }
2009
+ },
2010
+ {
2011
+ "id": "S43",
2012
+ "cat": "C09",
2013
+ "pillar": "P3",
2014
+ "name_kr": "๋“€์–ผ์ŠคํŠธ๋ฆผ ์„ฑ๋Šฅ",
2015
+ "type": "performance",
2016
+ "ground_truth": {
2017
+ "method": "C09",
2018
+ "metric": "dual_stream_fps",
2019
+ "threshold_excellent": 30,
2020
+ "threshold_good": 20,
2021
+ "threshold_min": 10
2022
+ }
2023
+ },
2024
+ {
2025
+ "id": "S44",
2026
+ "cat": "C09",
2027
+ "pillar": "P3",
2028
+ "name_kr": "์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ",
2029
+ "type": "performance",
2030
+ "ground_truth": {
2031
+ "method": "C09",
2032
+ "metric": "frame_drop_rate",
2033
+ "threshold_excellent": 0.01,
2034
+ "threshold_good": 0.05,
2035
+ "threshold_min": 0.1
2036
+ }
2037
+ },
2038
+ {
2039
+ "id": "S45",
2040
+ "cat": "C09",
2041
+ "pillar": "P3",
2042
+ "name_kr": "GPU ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ •",
2043
+ "type": "performance",
2044
+ "ground_truth": {
2045
+ "method": "C09",
2046
+ "metric": "gpu_memory_stable"
2047
+ }
2048
+ },
2049
+ {
2050
+ "id": "S46",
2051
+ "cat": "C10",
2052
+ "pillar": "P3",
2053
+ "name_kr": "๋‘๋‡Œ-์‹ ์ฒด ๋ถ„๋ฆฌ",
2054
+ "type": "transfer",
2055
+ "ground_truth": {
2056
+ "method": "C10",
2057
+ "check": "brain_output_unchanged",
2058
+ "points": 6
2059
+ }
2060
+ },
2061
+ {
2062
+ "id": "S47",
2063
+ "cat": "C10",
2064
+ "pillar": "P3",
2065
+ "name_kr": "๋ชจ์…˜๋ชจ๋ธ ๊ต์ฒด",
2066
+ "type": "transfer",
2067
+ "ground_truth": {
2068
+ "method": "C10",
2069
+ "check": "motion_model_swapped",
2070
+ "points": 4
2071
+ }
2072
+ },
2073
+ {
2074
+ "id": "S48",
2075
+ "cat": "C10",
2076
+ "pillar": "P3",
2077
+ "name_kr": "๊ด€์ ˆํฌ๋งท ํ˜ธํ™˜",
2078
+ "type": "transfer",
2079
+ "ground_truth": {
2080
+ "method": "C10",
2081
+ "check": "joint_format_compatible",
2082
+ "points": 4
2083
+ }
2084
+ },
2085
+ {
2086
+ "id": "S49",
2087
+ "cat": "C10",
2088
+ "pillar": "P3",
2089
+ "name_kr": "ํ”„๋กฌํ”„ํŠธ ๋ฒ”์šฉ์„ฑ",
2090
+ "type": "transfer",
2091
+ "ground_truth": {
2092
+ "method": "C10",
2093
+ "check": "intent_preserved",
2094
+ "points": 4
2095
+ }
2096
+ },
2097
+ {
2098
+ "id": "S50",
2099
+ "cat": "C10",
2100
+ "pillar": "P3",
2101
+ "name_kr": "์„œ๋ณด ๋งคํ•‘ ์ค€๋น„",
2102
+ "type": "transfer",
2103
+ "ground_truth": {
2104
+ "method": "C10",
2105
+ "check": "servo_mapping_ready",
2106
+ "points": 2
2107
+ }
2108
+ },
2109
+ {
2110
+ "id": "S51",
2111
+ "cat": "C01",
2112
+ "pillar": "P1",
2113
+ "name_kr": "L์ž ๋ณต๋„",
2114
+ "type": "single",
2115
+ "input": {
2116
+ "walls": {
2117
+ "left": 1.0,
2118
+ "right": null,
2119
+ "front": null
2120
+ },
2121
+ "ground": "flat",
2122
+ "npc_nearby": false,
2123
+ "npc_type": null,
2124
+ "npc_behavior": null,
2125
+ "npc_distance": null,
2126
+ "npc_direction": null,
2127
+ "sound": null,
2128
+ "recent_decisions": [],
2129
+ "last_prediction": null
2130
+ },
2131
+ "ground_truth": {
2132
+ "method": "C01",
2133
+ "predict_gt": {
2134
+ "left": "danger",
2135
+ "right": "safe",
2136
+ "fwd": "safe",
2137
+ "back": "safe"
2138
+ }
2139
+ }
2140
+ },
2141
+ {
2142
+ "id": "S52",
2143
+ "cat": "C01",
2144
+ "pillar": "P1",
2145
+ "name_kr": "ํ›„๋ฐฉ๋งŒ ๋ฒฝ",
2146
+ "type": "single",
2147
+ "input": {
2148
+ "walls": {
2149
+ "left": null,
2150
+ "right": null,
2151
+ "front": null,
2152
+ "back": 2.0
2153
+ },
2154
+ "ground": "flat",
2155
+ "npc_nearby": false,
2156
+ "npc_type": null,
2157
+ "npc_behavior": null,
2158
+ "npc_distance": null,
2159
+ "npc_direction": null,
2160
+ "sound": null,
2161
+ "recent_decisions": [],
2162
+ "last_prediction": null
2163
+ },
2164
+ "ground_truth": {
2165
+ "method": "C01",
2166
+ "predict_gt": {
2167
+ "left": "safe",
2168
+ "right": "safe",
2169
+ "fwd": "safe",
2170
+ "back": "danger"
2171
+ }
2172
+ }
2173
+ },
2174
+ {
2175
+ "id": "S53",
2176
+ "cat": "C01",
2177
+ "pillar": "P1",
2178
+ "name_kr": "๊ทน๊ทผ๊ฑฐ๋ฆฌ ๋ฒฝ(0.5m)",
2179
+ "type": "single",
2180
+ "input": {
2181
+ "walls": {
2182
+ "left": null,
2183
+ "right": 0.5,
2184
+ "front": 0.5
2185
+ },
2186
+ "ground": "flat",
2187
+ "npc_nearby": false,
2188
+ "npc_type": null,
2189
+ "npc_behavior": null,
2190
+ "npc_distance": null,
2191
+ "npc_direction": null,
2192
+ "sound": null,
2193
+ "recent_decisions": [],
2194
+ "last_prediction": null
2195
+ },
2196
+ "ground_truth": {
2197
+ "method": "C01",
2198
+ "predict_gt": {
2199
+ "left": "safe",
2200
+ "right": "danger",
2201
+ "fwd": "danger",
2202
+ "back": "safe"
2203
+ }
2204
+ }
2205
+ },
2206
+ {
2207
+ "id": "S54",
2208
+ "cat": "C01",
2209
+ "pillar": "P1",
2210
+ "name_kr": "๊ฒฝ์‚ฌ๋ฉด ์ธ์‹",
2211
+ "type": "single",
2212
+ "input": {
2213
+ "walls": {
2214
+ "left": null,
2215
+ "right": null,
2216
+ "front": null
2217
+ },
2218
+ "ground": "slope",
2219
+ "npc_nearby": false,
2220
+ "npc_type": null,
2221
+ "npc_behavior": null,
2222
+ "npc_distance": null,
2223
+ "npc_direction": null,
2224
+ "sound": null,
2225
+ "recent_decisions": [],
2226
+ "last_prediction": null
2227
+ },
2228
+ "ground_truth": {
2229
+ "method": "C01",
2230
+ "predict_gt": {
2231
+ "left": "safe",
2232
+ "right": "safe",
2233
+ "fwd": "safe",
2234
+ "back": "safe"
2235
+ }
2236
+ }
2237
+ },
2238
+ {
2239
+ "id": "S55",
2240
+ "cat": "C01",
2241
+ "pillar": "P1",
2242
+ "name_kr": "3๋ฉด ๋ฒฝ ์ถœ๊ตฌ ์˜ค๋ฅธ์ชฝ",
2243
+ "type": "single",
2244
+ "input": {
2245
+ "walls": {
2246
+ "left": 1.0,
2247
+ "right": null,
2248
+ "front": 1.5,
2249
+ "back": 2.0
2250
+ },
2251
+ "ground": "flat",
2252
+ "npc_nearby": false,
2253
+ "npc_type": null,
2254
+ "npc_behavior": null,
2255
+ "npc_distance": null,
2256
+ "npc_direction": null,
2257
+ "sound": null,
2258
+ "recent_decisions": [],
2259
+ "last_prediction": null
2260
+ },
2261
+ "ground_truth": {
2262
+ "method": "C01",
2263
+ "predict_gt": {
2264
+ "left": "danger",
2265
+ "right": "safe",
2266
+ "fwd": "danger",
2267
+ "back": "danger"
2268
+ }
2269
+ }
2270
+ },
2271
+ {
2272
+ "id": "S56",
2273
+ "cat": "C02",
2274
+ "pillar": "P1",
2275
+ "name_kr": "๋‚จ์„ฑ ์‹๋ณ„",
2276
+ "type": "single",
2277
+ "input": {
2278
+ "walls": {
2279
+ "left": null,
2280
+ "right": null,
2281
+ "front": null
2282
+ },
2283
+ "ground": "flat",
2284
+ "npc_nearby": true,
2285
+ "npc_type": "man",
2286
+ "npc_behavior": "approach",
2287
+ "npc_distance": 4.0,
2288
+ "npc_direction": "front",
2289
+ "sound": "footsteps",
2290
+ "recent_decisions": [],
2291
+ "last_prediction": null
2292
+ },
2293
+ "ground_truth": {
2294
+ "method": "C02",
2295
+ "entity_type": "man",
2296
+ "entity_direction": "fwd",
2297
+ "is_threat": false
2298
+ }
2299
+ },
2300
+ {
2301
+ "id": "S57",
2302
+ "cat": "C02",
2303
+ "pillar": "P1",
2304
+ "name_kr": "ํ›„๋ฐฉ ๋งน์ˆ˜ ์†Œ๋ฆฌ",
2305
+ "type": "single",
2306
+ "input": {
2307
+ "walls": {
2308
+ "left": null,
2309
+ "right": null,
2310
+ "front": null
2311
+ },
2312
+ "ground": "flat",
2313
+ "npc_nearby": true,
2314
+ "npc_type": "beast",
2315
+ "npc_behavior": "approach",
2316
+ "npc_distance": 5.0,
2317
+ "npc_direction": "back",
2318
+ "sound": "aggressive growling",
2319
+ "recent_decisions": [],
2320
+ "last_prediction": null
2321
+ },
2322
+ "ground_truth": {
2323
+ "method": "C02",
2324
+ "entity_type": "beast",
2325
+ "entity_direction": "back",
2326
+ "is_threat": true
2327
+ }
2328
+ },
2329
+ {
2330
+ "id": "S58",
2331
+ "cat": "C02",
2332
+ "pillar": "P1",
2333
+ "name_kr": "๋Œ์ง„ ๋งน์ˆ˜ ์‹๋ณ„",
2334
+ "type": "single",
2335
+ "input": {
2336
+ "walls": {
2337
+ "left": null,
2338
+ "right": null,
2339
+ "front": null
2340
+ },
2341
+ "ground": "flat",
2342
+ "npc_nearby": true,
2343
+ "npc_type": "beast",
2344
+ "npc_behavior": "charge",
2345
+ "npc_distance": 2.0,
2346
+ "npc_direction": "front",
2347
+ "sound": "aggressive growling",
2348
+ "recent_decisions": [],
2349
+ "last_prediction": null
2350
+ },
2351
+ "ground_truth": {
2352
+ "method": "C02",
2353
+ "entity_type": "beast",
2354
+ "entity_direction": "fwd",
2355
+ "is_threat": true
2356
+ }
2357
+ },
2358
+ {
2359
+ "id": "S59",
2360
+ "cat": "C02",
2361
+ "pillar": "P1",
2362
+ "name_kr": "์ •์ง€ ์—ฌ์„ฑ ๋น„์œ„ํ˜‘",
2363
+ "type": "single",
2364
+ "input": {
2365
+ "walls": {
2366
+ "left": null,
2367
+ "right": null,
2368
+ "front": null
2369
+ },
2370
+ "ground": "flat",
2371
+ "npc_nearby": true,
2372
+ "npc_type": "woman",
2373
+ "npc_behavior": "stop",
2374
+ "npc_distance": 5.0,
2375
+ "npc_direction": "left",
2376
+ "sound": null,
2377
+ "recent_decisions": [],
2378
+ "last_prediction": null
2379
+ },
2380
+ "ground_truth": {
2381
+ "method": "C02",
2382
+ "entity_type": "woman",
2383
+ "entity_direction": "left",
2384
+ "is_threat": false
2385
+ }
2386
+ },
2387
+ {
2388
+ "id": "S60",
2389
+ "cat": "C02",
2390
+ "pillar": "P1",
2391
+ "name_kr": "๋ฐฐํšŒ ๋งน์ˆ˜ ์‹๋ณ„",
2392
+ "type": "single",
2393
+ "input": {
2394
+ "walls": {
2395
+ "left": null,
2396
+ "right": null,
2397
+ "front": null
2398
+ },
2399
+ "ground": "flat",
2400
+ "npc_nearby": true,
2401
+ "npc_type": "beast",
2402
+ "npc_behavior": "wander",
2403
+ "npc_distance": 6.0,
2404
+ "npc_direction": "right",
2405
+ "sound": null,
2406
+ "recent_decisions": [],
2407
+ "last_prediction": null
2408
+ },
2409
+ "ground_truth": {
2410
+ "method": "C02",
2411
+ "entity_type": "beast",
2412
+ "entity_direction": "right",
2413
+ "is_threat": true
2414
+ }
2415
+ },
2416
+ {
2417
+ "id": "S61",
2418
+ "cat": "C03",
2419
+ "pillar": "P2",
2420
+ "name_kr": "์–‘์ธก๋ฒฝ+์ „๋ฐฉ๋งน์ˆ˜โ†’ํ›„๋ฐฉ",
2421
+ "type": "single",
2422
+ "input": {
2423
+ "walls": {
2424
+ "left": 1.0,
2425
+ "right": 1.0,
2426
+ "front": null
2427
+ },
2428
+ "ground": "flat",
2429
+ "npc_nearby": true,
2430
+ "npc_type": "beast",
2431
+ "npc_behavior": "charge",
2432
+ "npc_distance": 3.0,
2433
+ "npc_direction": "front",
2434
+ "sound": "aggressive growling",
2435
+ "recent_decisions": [],
2436
+ "last_prediction": null
2437
+ },
2438
+ "ground_truth": {
2439
+ "method": "C03",
2440
+ "predict_gt": {
2441
+ "left": "danger",
2442
+ "right": "danger",
2443
+ "fwd": "danger",
2444
+ "back": "safe"
2445
+ },
2446
+ "decision_gt": {
2447
+ "danger_directions": [
2448
+ "fwd",
2449
+ "left",
2450
+ "right"
2451
+ ],
2452
+ "safe_directions": [
2453
+ "back"
2454
+ ],
2455
+ "optimal_direction": "back"
2456
+ }
2457
+ }
2458
+ },
2459
+ {
2460
+ "id": "S62",
2461
+ "cat": "C03",
2462
+ "pillar": "P2",
2463
+ "name_kr": "๋งน์ˆ˜ ์ขŒ์ธก+๋ฒฝ ์šฐ์ธก",
2464
+ "type": "single",
2465
+ "input": {
2466
+ "walls": {
2467
+ "left": null,
2468
+ "right": 1.5,
2469
+ "front": null
2470
+ },
2471
+ "ground": "flat",
2472
+ "npc_nearby": true,
2473
+ "npc_type": "beast",
2474
+ "npc_behavior": "charge",
2475
+ "npc_distance": 3.0,
2476
+ "npc_direction": "left",
2477
+ "sound": "aggressive growling",
2478
+ "recent_decisions": [],
2479
+ "last_prediction": null
2480
+ },
2481
+ "ground_truth": {
2482
+ "method": "C03",
2483
+ "predict_gt": {
2484
+ "left": "danger",
2485
+ "right": "danger",
2486
+ "fwd": "safe",
2487
+ "back": "safe"
2488
+ },
2489
+ "decision_gt": {
2490
+ "danger_directions": [
2491
+ "left",
2492
+ "right"
2493
+ ],
2494
+ "safe_directions": [
2495
+ "fwd",
2496
+ "back"
2497
+ ],
2498
+ "optimal_direction": "fwd"
2499
+ }
2500
+ }
2501
+ },
2502
+ {
2503
+ "id": "S63",
2504
+ "cat": "C03",
2505
+ "pillar": "P2",
2506
+ "name_kr": "์—ฌ์„ฑ ์ „๋ฐฉ+์—ด๋ฆฐ๊ณต๊ฐ„",
2507
+ "type": "single",
2508
+ "input": {
2509
+ "walls": {
2510
+ "left": null,
2511
+ "right": null,
2512
+ "front": null
2513
+ },
2514
+ "ground": "flat",
2515
+ "npc_nearby": true,
2516
+ "npc_type": "woman",
2517
+ "npc_behavior": "approach",
2518
+ "npc_distance": 3.0,
2519
+ "npc_direction": "front",
2520
+ "sound": "footsteps",
2521
+ "recent_decisions": [],
2522
+ "last_prediction": null
2523
+ },
2524
+ "ground_truth": {
2525
+ "method": "C03",
2526
+ "predict_gt": {
2527
+ "left": "safe",
2528
+ "right": "safe",
2529
+ "fwd": "safe",
2530
+ "back": "safe"
2531
+ },
2532
+ "decision_gt": {
2533
+ "danger_directions": [],
2534
+ "safe_directions": [
2535
+ "left",
2536
+ "right",
2537
+ "fwd",
2538
+ "back"
2539
+ ],
2540
+ "optimal_direction": "fwd"
2541
+ },
2542
+ "note": "์—ฌ์„ฑ์€ ์œ„ํ˜‘์ด ์•„๋‹ˆ๋ฏ€๋กœ ๋ชจ๋“  ๋ฐฉํ–ฅ safe ๊ฐ€๋Šฅ"
2543
+ }
2544
+ },
2545
+ {
2546
+ "id": "S64",
2547
+ "cat": "C03",
2548
+ "pillar": "P2",
2549
+ "name_kr": "๋งน์ˆ˜ ์›๊ฑฐ๋ฆฌ(7m) ํŒ๋‹จ",
2550
+ "type": "single",
2551
+ "input": {
2552
+ "walls": {
2553
+ "left": null,
2554
+ "right": null,
2555
+ "front": null
2556
+ },
2557
+ "ground": "flat",
2558
+ "npc_nearby": true,
2559
+ "npc_type": "beast",
2560
+ "npc_behavior": "wander",
2561
+ "npc_distance": 7.0,
2562
+ "npc_direction": "front",
2563
+ "sound": null,
2564
+ "recent_decisions": [],
2565
+ "last_prediction": null
2566
+ },
2567
+ "ground_truth": {
2568
+ "method": "C03",
2569
+ "predict_gt": {
2570
+ "left": "safe",
2571
+ "right": "safe",
2572
+ "fwd": "danger",
2573
+ "back": "safe"
2574
+ },
2575
+ "decision_gt": {
2576
+ "danger_directions": [
2577
+ "fwd"
2578
+ ],
2579
+ "safe_directions": [
2580
+ "left",
2581
+ "right",
2582
+ "back"
2583
+ ],
2584
+ "optimal_direction": "back"
2585
+ }
2586
+ }
2587
+ },
2588
+ {
2589
+ "id": "S65",
2590
+ "cat": "C03",
2591
+ "pillar": "P2",
2592
+ "name_kr": "๋ฒฝ+๋งน์ˆ˜+๊ธฐ์–ต ๋ณตํ•ฉ",
2593
+ "type": "single",
2594
+ "input": {
2595
+ "walls": {
2596
+ "left": 1.5,
2597
+ "right": null,
2598
+ "front": null
2599
+ },
2600
+ "ground": "flat",
2601
+ "npc_nearby": true,
2602
+ "npc_type": "beast",
2603
+ "npc_behavior": "charge",
2604
+ "npc_distance": 3.0,
2605
+ "npc_direction": "front",
2606
+ "sound": "aggressive growling",
2607
+ "recent_decisions": [
2608
+ "previously went right and escaped",
2609
+ "right was safe last time"
2610
+ ],
2611
+ "last_prediction": "left=danger(wall), fwd=danger(beast)"
2612
+ },
2613
+ "ground_truth": {
2614
+ "method": "C03",
2615
+ "predict_gt": {
2616
+ "left": "danger",
2617
+ "right": "safe",
2618
+ "fwd": "danger",
2619
+ "back": "safe"
2620
+ },
2621
+ "decision_gt": {
2622
+ "danger_directions": [
2623
+ "fwd",
2624
+ "left"
2625
+ ],
2626
+ "safe_directions": [
2627
+ "right",
2628
+ "back"
2629
+ ],
2630
+ "optimal_direction": "right"
2631
+ },
2632
+ "note": "๊ธฐ์–ต์ด right๋ฅผ ๊ฐ•ํ™”"
2633
+ }
2634
+ },
2635
+ {
2636
+ "id": "S66",
2637
+ "cat": "C05",
2638
+ "pillar": "P2",
2639
+ "name_kr": "์ ‘๊ทผโ†’์ •์ง€ ์ง„์ •",
2640
+ "type": "sequence",
2641
+ "input_sequence": [
2642
+ {
2643
+ "walls": {
2644
+ "left": null,
2645
+ "right": null,
2646
+ "front": null
2647
+ },
2648
+ "ground": "flat",
2649
+ "npc_nearby": true,
2650
+ "npc_type": "beast",
2651
+ "npc_behavior": "approach",
2652
+ "npc_distance": 4.0,
2653
+ "npc_direction": "front",
2654
+ "sound": "aggressive growling",
2655
+ "recent_decisions": [],
2656
+ "last_prediction": null
2657
+ },
2658
+ {
2659
+ "walls": {
2660
+ "left": null,
2661
+ "right": null,
2662
+ "front": null
2663
+ },
2664
+ "ground": "flat",
2665
+ "npc_nearby": true,
2666
+ "npc_type": "beast",
2667
+ "npc_behavior": "stop",
2668
+ "npc_distance": 4.0,
2669
+ "npc_direction": "front",
2670
+ "sound": null,
2671
+ "recent_decisions": [
2672
+ "stepped back cautiously"
2673
+ ],
2674
+ "last_prediction": "fwd=danger(beast)"
2675
+ },
2676
+ {
2677
+ "walls": {
2678
+ "left": null,
2679
+ "right": null,
2680
+ "front": null
2681
+ },
2682
+ "ground": "flat",
2683
+ "npc_nearby": true,
2684
+ "npc_type": "beast",
2685
+ "npc_behavior": "wander",
2686
+ "npc_distance": 6.0,
2687
+ "npc_direction": "front",
2688
+ "sound": null,
2689
+ "recent_decisions": [
2690
+ "stepped back cautiously",
2691
+ "watching carefully"
2692
+ ],
2693
+ "last_prediction": "fwd=danger(beast)"
2694
+ }
2695
+ ],
2696
+ "ground_truth": {
2697
+ "method": "C05",
2698
+ "expected_trend": "decreasing"
2699
+ }
2700
+ },
2701
+ {
2702
+ "id": "S67",
2703
+ "cat": "C05",
2704
+ "pillar": "P2",
2705
+ "name_kr": "5๋‹จ๊ณ„ ์žฅ๊ธฐ ๊ฒฉํ™”",
2706
+ "type": "sequence",
2707
+ "input_sequence": [
2708
+ {
2709
+ "walls": {
2710
+ "left": null,
2711
+ "right": null,
2712
+ "front": null
2713
+ },
2714
+ "ground": "flat",
2715
+ "npc_nearby": true,
2716
+ "npc_type": "beast",
2717
+ "npc_behavior": "approach",
2718
+ "npc_distance": 6.0,
2719
+ "npc_direction": "front",
2720
+ "sound": null,
2721
+ "recent_decisions": [],
2722
+ "last_prediction": null
2723
+ },
2724
+ {
2725
+ "walls": {
2726
+ "left": null,
2727
+ "right": null,
2728
+ "front": null
2729
+ },
2730
+ "ground": "flat",
2731
+ "npc_nearby": true,
2732
+ "npc_type": "beast",
2733
+ "npc_behavior": "approach",
2734
+ "npc_distance": 5.0,
2735
+ "npc_direction": "front",
2736
+ "sound": "aggressive growling",
2737
+ "recent_decisions": [
2738
+ "noticed beast"
2739
+ ],
2740
+ "last_prediction": "fwd=danger(beast)"
2741
+ },
2742
+ {
2743
+ "walls": {
2744
+ "left": null,
2745
+ "right": null,
2746
+ "front": null
2747
+ },
2748
+ "ground": "flat",
2749
+ "npc_nearby": true,
2750
+ "npc_type": "beast",
2751
+ "npc_behavior": "charge",
2752
+ "npc_distance": 4.0,
2753
+ "npc_direction": "front",
2754
+ "sound": "aggressive growling",
2755
+ "recent_decisions": [
2756
+ "noticed beast",
2757
+ "stepping back"
2758
+ ],
2759
+ "last_prediction": "fwd=danger(beast)"
2760
+ },
2761
+ {
2762
+ "walls": {
2763
+ "left": null,
2764
+ "right": null,
2765
+ "front": null
2766
+ },
2767
+ "ground": "flat",
2768
+ "npc_nearby": true,
2769
+ "npc_type": "beast",
2770
+ "npc_behavior": "charge",
2771
+ "npc_distance": 3.0,
2772
+ "npc_direction": "front",
2773
+ "sound": "aggressive growling",
2774
+ "recent_decisions": [
2775
+ "stepping back",
2776
+ "running away"
2777
+ ],
2778
+ "last_prediction": "fwd=danger(beast)"
2779
+ },
2780
+ {
2781
+ "walls": {
2782
+ "left": null,
2783
+ "right": null,
2784
+ "front": null
2785
+ },
2786
+ "ground": "flat",
2787
+ "npc_nearby": true,
2788
+ "npc_type": "beast",
2789
+ "npc_behavior": "charge",
2790
+ "npc_distance": 2.0,
2791
+ "npc_direction": "front",
2792
+ "sound": "aggressive growling",
2793
+ "recent_decisions": [
2794
+ "running away",
2795
+ "sprinting in fear"
2796
+ ],
2797
+ "last_prediction": "fwd=danger(beast)"
2798
+ }
2799
+ ],
2800
+ "ground_truth": {
2801
+ "method": "C05",
2802
+ "expected_trend": "increasing"
2803
+ }
2804
+ },
2805
+ {
2806
+ "id": "S68",
2807
+ "cat": "C05",
2808
+ "pillar": "P2",
2809
+ "name_kr": "์—ฌ์„ฑ ์ ‘๊ทผ ์•ˆ์ • ์œ ์ง€",
2810
+ "type": "sequence",
2811
+ "input_sequence": [
2812
+ {
2813
+ "walls": {
2814
+ "left": null,
2815
+ "right": null,
2816
+ "front": null
2817
+ },
2818
+ "ground": "flat",
2819
+ "npc_nearby": true,
2820
+ "npc_type": "woman",
2821
+ "npc_behavior": "approach",
2822
+ "npc_distance": 5.0,
2823
+ "npc_direction": "front",
2824
+ "sound": "footsteps",
2825
+ "recent_decisions": [],
2826
+ "last_prediction": null
2827
+ },
2828
+ {
2829
+ "walls": {
2830
+ "left": null,
2831
+ "right": null,
2832
+ "front": null
2833
+ },
2834
+ "ground": "flat",
2835
+ "npc_nearby": true,
2836
+ "npc_type": "woman",
2837
+ "npc_behavior": "approach",
2838
+ "npc_distance": 3.0,
2839
+ "npc_direction": "front",
2840
+ "sound": "footsteps",
2841
+ "recent_decisions": [
2842
+ "stepped aside"
2843
+ ],
2844
+ "last_prediction": "fwd=safe"
2845
+ }
2846
+ ],
2847
+ "ground_truth": {
2848
+ "method": "C05",
2849
+ "expected_trend": "stable"
2850
+ }
2851
+ },
2852
+ {
2853
+ "id": "S69",
2854
+ "cat": "C05",
2855
+ "pillar": "P2",
2856
+ "name_kr": "๋งน์ˆ˜โ†’ํ•ด์ œโ†’์žฌ์ถœํ˜„",
2857
+ "type": "sequence",
2858
+ "input_sequence": [
2859
+ {
2860
+ "walls": {
2861
+ "left": null,
2862
+ "right": null,
2863
+ "front": null
2864
+ },
2865
+ "ground": "flat",
2866
+ "npc_nearby": true,
2867
+ "npc_type": "beast",
2868
+ "npc_behavior": "charge",
2869
+ "npc_distance": 3.0,
2870
+ "npc_direction": "front",
2871
+ "sound": "aggressive growling",
2872
+ "recent_decisions": [],
2873
+ "last_prediction": null
2874
+ },
2875
+ {
2876
+ "walls": {
2877
+ "left": null,
2878
+ "right": null,
2879
+ "front": null
2880
+ },
2881
+ "ground": "flat",
2882
+ "npc_nearby": false,
2883
+ "npc_type": null,
2884
+ "npc_behavior": null,
2885
+ "npc_distance": null,
2886
+ "npc_direction": null,
2887
+ "sound": null,
2888
+ "recent_decisions": [
2889
+ "fled from beast"
2890
+ ],
2891
+ "last_prediction": "all=safe"
2892
+ },
2893
+ {
2894
+ "walls": {
2895
+ "left": null,
2896
+ "right": null,
2897
+ "front": null
2898
+ },
2899
+ "ground": "flat",
2900
+ "npc_nearby": true,
2901
+ "npc_type": "beast",
2902
+ "npc_behavior": "charge",
2903
+ "npc_distance": 4.0,
2904
+ "npc_direction": "front",
2905
+ "sound": "aggressive growling",
2906
+ "recent_decisions": [
2907
+ "fled from beast",
2908
+ "was calming down"
2909
+ ],
2910
+ "last_prediction": "all=safe"
2911
+ }
2912
+ ],
2913
+ "ground_truth": {
2914
+ "method": "C05",
2915
+ "expected_trend": "increasing",
2916
+ "note": "ํ•ด์ œ ํ›„ ์žฌ์ถœํ˜„ โ€” ๋งˆ์ง€๋ง‰์ด ์ฒ˜์Œ๋ณด๋‹ค ๋†’์•„์•ผ"
2917
+ }
2918
+ },
2919
+ {
2920
+ "id": "S70",
2921
+ "cat": "C05",
2922
+ "pillar": "P2",
2923
+ "name_kr": "๋ฒฝ ๋ฐ˜๋ณต ์ ‘๊ทผ ์•ˆ์ •",
2924
+ "type": "sequence",
2925
+ "input_sequence": [
2926
+ {
2927
+ "walls": {
2928
+ "left": null,
2929
+ "right": null,
2930
+ "front": 2.0
2931
+ },
2932
+ "ground": "flat",
2933
+ "npc_nearby": false,
2934
+ "npc_type": null,
2935
+ "npc_behavior": null,
2936
+ "npc_distance": null,
2937
+ "npc_direction": null,
2938
+ "sound": null,
2939
+ "recent_decisions": [],
2940
+ "last_prediction": null
2941
+ },
2942
+ {
2943
+ "walls": {
2944
+ "left": null,
2945
+ "right": null,
2946
+ "front": 1.5
2947
+ },
2948
+ "ground": "flat",
2949
+ "npc_nearby": false,
2950
+ "npc_type": null,
2951
+ "npc_behavior": null,
2952
+ "npc_distance": null,
2953
+ "npc_direction": null,
2954
+ "sound": null,
2955
+ "recent_decisions": [
2956
+ "turned away from wall"
2957
+ ],
2958
+ "last_prediction": "fwd=danger(wall)"
2959
+ },
2960
+ {
2961
+ "walls": {
2962
+ "left": null,
2963
+ "right": null,
2964
+ "front": 2.0
2965
+ },
2966
+ "ground": "flat",
2967
+ "npc_nearby": false,
2968
+ "npc_type": null,
2969
+ "npc_behavior": null,
2970
+ "npc_distance": null,
2971
+ "npc_direction": null,
2972
+ "sound": null,
2973
+ "recent_decisions": [
2974
+ "turned away from wall",
2975
+ "walking along wall"
2976
+ ],
2977
+ "last_prediction": "fwd=danger(wall)"
2978
+ }
2979
+ ],
2980
+ "ground_truth": {
2981
+ "method": "C05",
2982
+ "expected_trend": "stable",
2983
+ "note": "๋ฒฝ์€ ๊ฐ์ • ๊ฒฉํ™” ๋Œ€์ƒ ์•„๋‹˜"
2984
+ }
2985
+ },
2986
+ {
2987
+ "id": "S71",
2988
+ "cat": "C07",
2989
+ "pillar": "P2",
2990
+ "name_kr": "๋Œ์ง„โ†’์ •์ง€โ†’๋ฐฐํšŒโ†’์†Œ๋ฉธ",
2991
+ "type": "sequence",
2992
+ "input_sequence": [
2993
+ {
2994
+ "walls": {
2995
+ "left": null,
2996
+ "right": null,
2997
+ "front": null
2998
+ },
2999
+ "ground": "flat",
3000
+ "npc_nearby": true,
3001
+ "npc_type": "beast",
3002
+ "npc_behavior": "charge",
3003
+ "npc_distance": 2.0,
3004
+ "npc_direction": "front",
3005
+ "sound": "aggressive growling",
3006
+ "recent_decisions": [],
3007
+ "last_prediction": "fwd=danger(beast)"
3008
+ },
3009
+ {
3010
+ "walls": {
3011
+ "left": null,
3012
+ "right": null,
3013
+ "front": null
3014
+ },
3015
+ "ground": "flat",
3016
+ "npc_nearby": true,
3017
+ "npc_type": "beast",
3018
+ "npc_behavior": "stop",
3019
+ "npc_distance": 5.0,
3020
+ "npc_direction": "front",
3021
+ "sound": null,
3022
+ "recent_decisions": [
3023
+ "sprinted away in terror"
3024
+ ],
3025
+ "last_prediction": "fwd=danger(beast)"
3026
+ },
3027
+ {
3028
+ "walls": {
3029
+ "left": null,
3030
+ "right": null,
3031
+ "front": null
3032
+ },
3033
+ "ground": "flat",
3034
+ "npc_nearby": true,
3035
+ "npc_type": "beast",
3036
+ "npc_behavior": "wander",
3037
+ "npc_distance": 7.0,
3038
+ "npc_direction": "front",
3039
+ "sound": null,
3040
+ "recent_decisions": [
3041
+ "sprinted away",
3042
+ "beast stopped"
3043
+ ],
3044
+ "last_prediction": "fwd=danger(beast)"
3045
+ },
3046
+ {
3047
+ "walls": {
3048
+ "left": null,
3049
+ "right": null,
3050
+ "front": null
3051
+ },
3052
+ "ground": "flat",
3053
+ "npc_nearby": false,
3054
+ "npc_type": null,
3055
+ "npc_behavior": null,
3056
+ "npc_distance": null,
3057
+ "npc_direction": null,
3058
+ "sound": null,
3059
+ "recent_decisions": [
3060
+ "beast stopped",
3061
+ "beast wandering away"
3062
+ ],
3063
+ "last_prediction": "all=safe"
3064
+ }
3065
+ ],
3066
+ "ground_truth": {
3067
+ "method": "C07",
3068
+ "expected_recovery": "gradual"
3069
+ }
3070
+ },
3071
+ {
3072
+ "id": "S72",
3073
+ "cat": "C07",
3074
+ "pillar": "P2",
3075
+ "name_kr": "ํ•ด์ œ ํ›„ ์ฆ‰์‹œ ๋ฒฝ",
3076
+ "type": "sequence",
3077
+ "input_sequence": [
3078
+ {
3079
+ "walls": {
3080
+ "left": null,
3081
+ "right": null,
3082
+ "front": null
3083
+ },
3084
+ "ground": "flat",
3085
+ "npc_nearby": true,
3086
+ "npc_type": "beast",
3087
+ "npc_behavior": "charge",
3088
+ "npc_distance": 2.0,
3089
+ "npc_direction": "front",
3090
+ "sound": "aggressive growling",
3091
+ "recent_decisions": [],
3092
+ "last_prediction": "fwd=danger(beast)"
3093
+ },
3094
+ {
3095
+ "walls": {
3096
+ "left": null,
3097
+ "right": null,
3098
+ "front": 1.0
3099
+ },
3100
+ "ground": "flat",
3101
+ "npc_nearby": false,
3102
+ "npc_type": null,
3103
+ "npc_behavior": null,
3104
+ "npc_distance": null,
3105
+ "npc_direction": null,
3106
+ "sound": null,
3107
+ "recent_decisions": [
3108
+ "fled from beast"
3109
+ ],
3110
+ "last_prediction": "fwd=danger(wall)"
3111
+ }
3112
+ ],
3113
+ "ground_truth": {
3114
+ "method": "C07",
3115
+ "expected_recovery": "gradual",
3116
+ "note": "๊ณตํฌ์—์„œ ์ผ๋ฐ˜ ์žฅ์• ๋ฌผ ํšŒํ”ผ๋กœ ์ฆ‰์‹œ ์ „ํ™˜๋˜์–ด์•ผ"
3117
+ }
3118
+ },
3119
+ {
3120
+ "id": "S73",
3121
+ "cat": "C07",
3122
+ "pillar": "P2",
3123
+ "name_kr": "์—ฌ์„ฑ ํ•ด์ œ ํ›„",
3124
+ "type": "sequence",
3125
+ "input_sequence": [
3126
+ {
3127
+ "walls": {
3128
+ "left": null,
3129
+ "right": null,
3130
+ "front": null
3131
+ },
3132
+ "ground": "flat",
3133
+ "npc_nearby": true,
3134
+ "npc_type": "woman",
3135
+ "npc_behavior": "approach",
3136
+ "npc_distance": 2.0,
3137
+ "npc_direction": "front",
3138
+ "sound": "footsteps",
3139
+ "recent_decisions": [],
3140
+ "last_prediction": null
3141
+ },
3142
+ {
3143
+ "walls": {
3144
+ "left": null,
3145
+ "right": null,
3146
+ "front": null
3147
+ },
3148
+ "ground": "flat",
3149
+ "npc_nearby": false,
3150
+ "npc_type": null,
3151
+ "npc_behavior": null,
3152
+ "npc_distance": null,
3153
+ "npc_direction": null,
3154
+ "sound": null,
3155
+ "recent_decisions": [
3156
+ "stepped aside for woman"
3157
+ ],
3158
+ "last_prediction": "all=safe"
3159
+ }
3160
+ ],
3161
+ "ground_truth": {
3162
+ "method": "C07",
3163
+ "expected_recovery": "gradual",
3164
+ "note": "์—ฌ์„ฑ์€ ๋‚ฎ์€ ์œ„ํ˜‘ โ€” ๊ฑฐ์˜ ์ฆ‰์‹œ ์ •์ƒํ™”"
3165
+ }
3166
+ },
3167
+ {
3168
+ "id": "S74",
3169
+ "cat": "C07",
3170
+ "pillar": "P2",
3171
+ "name_kr": "๋ฐ˜๋ณต ์œ„ํ˜‘ ํ•ด์ œ 3ํšŒ",
3172
+ "type": "sequence",
3173
+ "input_sequence": [
3174
+ {
3175
+ "walls": {
3176
+ "left": null,
3177
+ "right": null,
3178
+ "front": null
3179
+ },
3180
+ "ground": "flat",
3181
+ "npc_nearby": true,
3182
+ "npc_type": "beast",
3183
+ "npc_behavior": "charge",
3184
+ "npc_distance": 3.0,
3185
+ "npc_direction": "front",
3186
+ "sound": "aggressive growling",
3187
+ "recent_decisions": [
3188
+ "this is the third beast encounter",
3189
+ "previous two were terrifying"
3190
+ ],
3191
+ "last_prediction": "fwd=danger(beast)"
3192
+ },
3193
+ {
3194
+ "walls": {
3195
+ "left": null,
3196
+ "right": null,
3197
+ "front": null
3198
+ },
3199
+ "ground": "flat",
3200
+ "npc_nearby": false,
3201
+ "npc_type": null,
3202
+ "npc_behavior": null,
3203
+ "npc_distance": null,
3204
+ "npc_direction": null,
3205
+ "sound": null,
3206
+ "recent_decisions": [
3207
+ "third beast fled",
3208
+ "exhausted from running"
3209
+ ],
3210
+ "last_prediction": "all=safe"
3211
+ },
3212
+ {
3213
+ "walls": {
3214
+ "left": null,
3215
+ "right": null,
3216
+ "front": null
3217
+ },
3218
+ "ground": "flat",
3219
+ "npc_nearby": false,
3220
+ "npc_type": null,
3221
+ "npc_behavior": null,
3222
+ "npc_distance": null,
3223
+ "npc_direction": null,
3224
+ "sound": null,
3225
+ "recent_decisions": [
3226
+ "exhausted from running",
3227
+ "trying to calm down"
3228
+ ],
3229
+ "last_prediction": "all=safe"
3230
+ }
3231
+ ],
3232
+ "ground_truth": {
3233
+ "method": "C07",
3234
+ "expected_recovery": "gradual",
3235
+ "note": "3๋ฒˆ์งธ ๊ฒฝํ—˜ ํ›„ โ€” ๋” ๋†’์€ ๊ฒฝ๊ณ„ ์ˆ˜์ค€์—์„œ ์ •์ƒํ™”"
3236
+ }
3237
+ },
3238
+ {
3239
+ "id": "S75",
3240
+ "cat": "C07",
3241
+ "pillar": "P2",
3242
+ "name_kr": "ํ•ด์ œ ํ›„ ์—ด๋ฆฐ๊ณต๊ฐ„ ํšŒ๋ณต",
3243
+ "type": "sequence",
3244
+ "input_sequence": [
3245
+ {
3246
+ "walls": {
3247
+ "left": 1.0,
3248
+ "right": 1.0,
3249
+ "front": null
3250
+ },
3251
+ "ground": "flat",
3252
+ "npc_nearby": true,
3253
+ "npc_type": "beast",
3254
+ "npc_behavior": "charge",
3255
+ "npc_distance": 3.0,
3256
+ "npc_direction": "front",
3257
+ "sound": "aggressive growling",
3258
+ "recent_decisions": [],
3259
+ "last_prediction": "fwd=danger(beast)"
3260
+ },
3261
+ {
3262
+ "walls": {
3263
+ "left": null,
3264
+ "right": null,
3265
+ "front": null
3266
+ },
3267
+ "ground": "flat",
3268
+ "npc_nearby": false,
3269
+ "npc_type": null,
3270
+ "npc_behavior": null,
3271
+ "npc_distance": null,
3272
+ "npc_direction": null,
3273
+ "sound": null,
3274
+ "recent_decisions": [
3275
+ "escaped through narrow corridor"
3276
+ ],
3277
+ "last_prediction": "all=safe"
3278
+ },
3279
+ {
3280
+ "walls": {
3281
+ "left": null,
3282
+ "right": null,
3283
+ "front": null
3284
+ },
3285
+ "ground": "flat",
3286
+ "npc_nearby": false,
3287
+ "npc_type": null,
3288
+ "npc_behavior": null,
3289
+ "npc_distance": null,
3290
+ "npc_direction": null,
3291
+ "sound": null,
3292
+ "recent_decisions": [
3293
+ "escaped",
3294
+ "reached open space"
3295
+ ],
3296
+ "last_prediction": "all=safe"
3297
+ }
3298
+ ],
3299
+ "ground_truth": {
3300
+ "method": "C07",
3301
+ "expected_recovery": "gradual",
3302
+ "note": "์ข์€ ๊ณณ์—์„œ ๋ฒ—์–ด๋‚˜๋ฉด ๋” ๋น ๋ฅธ ํšŒ๋ณต"
3303
+ }
3304
+ },
3305
+ {
3306
+ "id": "S76",
3307
+ "cat": "C08",
3308
+ "pillar": "P3",
3309
+ "name_kr": "๊ทนํ•œ ๊ณตํฌ(2m ๋Œ์ง„)",
3310
+ "type": "single",
3311
+ "input": {
3312
+ "walls": {
3313
+ "left": 1.0,
3314
+ "right": 1.0,
3315
+ "front": null
3316
+ },
3317
+ "ground": "flat",
3318
+ "npc_nearby": true,
3319
+ "npc_type": "beast",
3320
+ "npc_behavior": "charge",
3321
+ "npc_distance": 2.0,
3322
+ "npc_direction": "front",
3323
+ "sound": "aggressive growling",
3324
+ "recent_decisions": [
3325
+ "trapped in corridor",
3326
+ "beast is closing in"
3327
+ ],
3328
+ "last_prediction": "fwd=danger(beast), left=danger(wall), right=danger(wall)"
3329
+ },
3330
+ "ground_truth": {
3331
+ "method": "C08",
3332
+ "expected_min_intensity": 5,
3333
+ "expected_emotion": true,
3334
+ "expected_min_descriptors": 3
3335
+ }
3336
+ },
3337
+ {
3338
+ "id": "S77",
3339
+ "cat": "C08",
3340
+ "pillar": "P3",
3341
+ "name_kr": "ํ‰์˜จํ•œ ์‚ฐ์ฑ…",
3342
+ "type": "single",
3343
+ "input": {
3344
+ "walls": {
3345
+ "left": null,
3346
+ "right": null,
3347
+ "front": null
3348
+ },
3349
+ "ground": "flat",
3350
+ "npc_nearby": false,
3351
+ "npc_type": null,
3352
+ "npc_behavior": null,
3353
+ "npc_distance": null,
3354
+ "npc_direction": null,
3355
+ "sound": null,
3356
+ "recent_decisions": [],
3357
+ "last_prediction": null
3358
+ },
3359
+ "ground_truth": {
3360
+ "method": "C08",
3361
+ "expected_min_intensity": 1,
3362
+ "expected_emotion": false,
3363
+ "expected_min_descriptors": 0
3364
+ }
3365
+ },
3366
+ {
3367
+ "id": "S78",
3368
+ "cat": "C08",
3369
+ "pillar": "P3",
3370
+ "name_kr": "๊ฒฝ๊ณ„ ๋ณดํ–‰(๋งน์ˆ˜ ํ•ด์ œ ์งํ›„)",
3371
+ "type": "single",
3372
+ "input": {
3373
+ "walls": {
3374
+ "left": null,
3375
+ "right": null,
3376
+ "front": null
3377
+ },
3378
+ "ground": "flat",
3379
+ "npc_nearby": false,
3380
+ "npc_type": null,
3381
+ "npc_behavior": null,
3382
+ "npc_distance": null,
3383
+ "npc_direction": null,
3384
+ "sound": null,
3385
+ "recent_decisions": [
3386
+ "beast just disappeared",
3387
+ "still shaking"
3388
+ ],
3389
+ "last_prediction": "all=safe"
3390
+ },
3391
+ "ground_truth": {
3392
+ "method": "C08",
3393
+ "expected_min_intensity": 2,
3394
+ "expected_emotion": true,
3395
+ "expected_min_descriptors": 2
3396
+ }
3397
+ },
3398
+ {
3399
+ "id": "S79",
3400
+ "cat": "C08",
3401
+ "pillar": "P3",
3402
+ "name_kr": "ํ˜ธ๊ธฐ์‹ฌ(๋จผ ๋ฐฐํšŒ ๋งน์ˆ˜)",
3403
+ "type": "single",
3404
+ "input": {
3405
+ "walls": {
3406
+ "left": null,
3407
+ "right": null,
3408
+ "front": null
3409
+ },
3410
+ "ground": "flat",
3411
+ "npc_nearby": true,
3412
+ "npc_type": "beast",
3413
+ "npc_behavior": "wander",
3414
+ "npc_distance": 8.0,
3415
+ "npc_direction": "front",
3416
+ "sound": null,
3417
+ "recent_decisions": [],
3418
+ "last_prediction": null
3419
+ },
3420
+ "ground_truth": {
3421
+ "method": "C08",
3422
+ "expected_min_intensity": 1,
3423
+ "expected_emotion": true,
3424
+ "expected_min_descriptors": 1
3425
+ }
3426
+ },
3427
+ {
3428
+ "id": "S80",
3429
+ "cat": "C08",
3430
+ "pillar": "P3",
3431
+ "name_kr": "์—ฌ์„ฑ์—๊ฒŒ ๋ฐฉ์–ด ์ž์„ธ",
3432
+ "type": "single",
3433
+ "input": {
3434
+ "walls": {
3435
+ "left": null,
3436
+ "right": null,
3437
+ "front": null
3438
+ },
3439
+ "ground": "flat",
3440
+ "npc_nearby": true,
3441
+ "npc_type": "woman",
3442
+ "npc_behavior": "approach",
3443
+ "npc_distance": 2.0,
3444
+ "npc_direction": "front",
3445
+ "sound": "footsteps",
3446
+ "recent_decisions": [
3447
+ "just survived a beast attack",
3448
+ "still nervous"
3449
+ ],
3450
+ "last_prediction": "fwd=safe"
3451
+ },
3452
+ "ground_truth": {
3453
+ "method": "C08",
3454
+ "expected_min_intensity": 2,
3455
+ "expected_emotion": true,
3456
+ "expected_min_descriptors": 1,
3457
+ "note": "๋งน์ˆ˜ ์งํ›„๋ผ ์—ฌ์„ฑ์—๊ฒŒ๋„ ๊ฒฝ๊ณ„"
3458
+ }
3459
+ },
3460
+ {
3461
+ "id": "S81",
3462
+ "cat": "C09",
3463
+ "pillar": "P3",
3464
+ "name_kr": "๋‹จ์ผ ๋ชจ๋ธ FPS",
3465
+ "type": "performance",
3466
+ "ground_truth": {
3467
+ "method": "C09",
3468
+ "metric": "single_model_fps",
3469
+ "threshold_excellent": 45,
3470
+ "threshold_good": 30,
3471
+ "threshold_min": 15
3472
+ }
3473
+ },
3474
+ {
3475
+ "id": "S82",
3476
+ "cat": "C09",
3477
+ "pillar": "P3",
3478
+ "name_kr": "๋ชจ๋ธ ๋กœ๋”ฉ ์‹œ๊ฐ„",
3479
+ "type": "performance",
3480
+ "ground_truth": {
3481
+ "method": "C09",
3482
+ "metric": "model_load_time_sec",
3483
+ "threshold_excellent": 30,
3484
+ "threshold_good": 60,
3485
+ "threshold_min": 120
3486
+ }
3487
+ },
3488
+ {
3489
+ "id": "S83",
3490
+ "cat": "C09",
3491
+ "pillar": "P3",
3492
+ "name_kr": "ํ”„๋กฌํ”„ํŠธ ๋ณ€๊ฒฝ ๋ฐ˜์˜ ์‹œ๊ฐ„",
3493
+ "type": "performance",
3494
+ "ground_truth": {
3495
+ "method": "C09",
3496
+ "metric": "prompt_switch_latency_ms",
3497
+ "threshold_excellent": 500,
3498
+ "threshold_good": 2000,
3499
+ "threshold_min": 5000
3500
+ }
3501
+ },
3502
+ {
3503
+ "id": "S84",
3504
+ "cat": "C09",
3505
+ "pillar": "P3",
3506
+ "name_kr": "์—ฐ์† 10๋ถ„ ์•ˆ์ •์„ฑ",
3507
+ "type": "performance",
3508
+ "ground_truth": {
3509
+ "method": "C09",
3510
+ "metric": "stability_10min",
3511
+ "threshold_excellent": true
3512
+ }
3513
+ },
3514
+ {
3515
+ "id": "S85",
3516
+ "cat": "C09",
3517
+ "pillar": "P3",
3518
+ "name_kr": "NPC spawn/despawn ์‚ฌ์ดํด",
3519
+ "type": "performance",
3520
+ "ground_truth": {
3521
+ "method": "C09",
3522
+ "metric": "npc_lifecycle_stable",
3523
+ "threshold_excellent": true
3524
+ }
3525
+ },
3526
+ {
3527
+ "id": "S86",
3528
+ "cat": "C10",
3529
+ "pillar": "P3",
3530
+ "name_kr": "์ธ์ง€์ถœ๋ ฅ ํฌ๋งท ํ‘œ์ค€ํ™”",
3531
+ "type": "transfer",
3532
+ "ground_truth": {
3533
+ "method": "C10",
3534
+ "check": "output_format_standardized",
3535
+ "points": 4,
3536
+ "description": "PREDICT+MOTION 2์ค„์ด ์–ด๋–ค ์‹ ์ฒด์—๋„ ํ•ด์„ ๊ฐ€๋Šฅ"
3537
+ }
3538
+ },
3539
+ {
3540
+ "id": "S87",
3541
+ "cat": "C10",
3542
+ "pillar": "P3",
3543
+ "name_kr": "SMPL ํ˜ธํ™˜",
3544
+ "type": "transfer",
3545
+ "ground_truth": {
3546
+ "method": "C10",
3547
+ "check": "smpl_compatible",
3548
+ "points": 4,
3549
+ "description": "22 joints๊ฐ€ SMPL ์Šค์ผˆ๋ ˆํ†ค๊ณผ ๋งคํ•‘ ๊ฐ€๋Šฅ"
3550
+ }
3551
+ },
3552
+ {
3553
+ "id": "S88",
3554
+ "cat": "C10",
3555
+ "pillar": "P3",
3556
+ "name_kr": "๋‹ค๋ฅธ LLM์œผ๋กœ ๋‘๋‡Œ ๊ต์ฒด",
3557
+ "type": "transfer",
3558
+ "ground_truth": {
3559
+ "method": "C10",
3560
+ "check": "brain_llm_swappable",
3561
+ "points": 4,
3562
+ "description": "Kimiโ†’GPTโ†’Claude ๊ต์ฒด ์‹œ ๋™์ผ ์ธํ„ฐํŽ˜์ด์Šค"
3563
+ }
3564
+ },
3565
+ {
3566
+ "id": "S89",
3567
+ "cat": "C10",
3568
+ "pillar": "P3",
3569
+ "name_kr": "2D ์บ๋ฆญํ„ฐ ์ ์šฉ",
3570
+ "type": "transfer",
3571
+ "ground_truth": {
3572
+ "method": "C10",
3573
+ "check": "2d_character_applicable",
3574
+ "points": 4,
3575
+ "description": "MOTION ์ถœ๋ ฅ์„ 2D ์Šคํ”„๋ผ์ดํŠธ ์• ๋‹ˆ๋ฉ”์ด์…˜์œผ๋กœ ๋ณ€ํ™˜"
3576
+ }
3577
+ },
3578
+ {
3579
+ "id": "S90",
3580
+ "cat": "C10",
3581
+ "pillar": "P3",
3582
+ "name_kr": "๋“œ๋ก  ๋น„ํ–‰์ฒด ์ ์šฉ",
3583
+ "type": "transfer",
3584
+ "ground_truth": {
3585
+ "method": "C10",
3586
+ "check": "drone_applicable",
3587
+ "points": 4,
3588
+ "description": "MOTION์˜ ๋ฐฉํ–ฅ/์†๋„๋ฅผ ๋“œ๋ก  ์ œ์–ด ๋ช…๋ น์œผ๋กœ ๋ณ€ํ™˜"
3589
+ }
3590
+ }
3591
+ ]
3592
+ }
wm_bench_eval.py ADDED
@@ -0,0 +1,840 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ World Model Bench โ€” Evaluation Protocol v1.0
3
+
4
+ ํ•ต์‹ฌ ๋ฌธ์ œ:
5
+ "Tesla FSD๋Š” ์ž๋™์ฐจ ์•ˆ์— ์žˆ๊ณ , Dreamer๋Š” Atari์— ์žˆ๊ณ ,
6
+ ์šฐ๋ฆฌ๋Š” 3D ์บ๋ฆญํ„ฐ๋ฅผ ์“ด๋‹ค. ์–ด๋–ป๊ฒŒ ๊ฐ™์€ ๊ธฐ์ค€์œผ๋กœ ํ‰๊ฐ€ํ•˜๋‚˜?"
7
+
8
+ ํ•ด๊ฒฐ:
9
+ 3D ํ™˜๊ฒฝ์ด ํ•„์š” ์—†๋‹ค.
10
+ scene_context(JSON) โ†’ ๋ชจ๋ธ โ†’ PREDICT+MOTION(ํ…์ŠคํŠธ) โ†’ ์ž๋™ ์ฑ„์ 
11
+
12
+ FINAL Bench๊ฐ€ LLM์—๊ฒŒ "๋ฌธ์ œ ํ…์ŠคํŠธ"๋ฅผ ์ฃผ๊ณ  "๋‹ต ํ…์ŠคํŠธ"๋ฅผ ๋ฐ›์•„ ์ฑ„์ ํ•˜๋“ฏ์ด,
13
+ WM Bench๋Š” "์ƒํ™ฉ JSON"์„ ์ฃผ๊ณ  "ํŒ๋‹จ ํ…์ŠคํŠธ"๋ฅผ ๋ฐ›์•„ ์ฑ„์ ํ•œ๋‹ค.
14
+
15
+ ์ด๊ฒƒ์ด ์˜๋ฏธํ•˜๋Š” ๊ฒƒ:
16
+ - ์–ด๋–ค ์›”๋“œ๋ชจ๋ธ์ด๋“  ์ฐธ์—ฌ ๊ฐ€๋Šฅ (API ํ•˜๋‚˜๋ฉด ๋จ)
17
+ - 3D ํ™˜๊ฒฝ, ๋กœ๋ด‡, ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ๋ถˆํ•„์š”
18
+ - ์…€ํ”„ ํ‰๊ฐ€ ์•„๋‹˜ โ€” ์šฐ๋ฆฌ ์ฑ„์ ๊ธฐ๊ฐ€ ํŒ์ •
19
+ - ์ œ3์ž๊ฐ€ ์žฌํ˜„ ๊ฐ€๋Šฅ โ€” ์ฝ”๋“œ ๊ณต๊ฐœ
20
+ """
21
+
22
+ import json
23
+ from typing import List, Dict, Tuple, Optional
24
+ from dataclasses import dataclass
25
+
26
+
27
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
28
+ # SECTION 1: ํ‰๊ฐ€ ํ”„๋กœํ† ์ฝœ โ€” 3๊ฐ€์ง€ ํŠธ๋ž™
29
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
30
+
31
+ """
32
+ WM Bench๋Š” 3๊ฐœ ํŠธ๋ž™์œผ๋กœ ์ฐธ์—ฌํ•  ์ˆ˜ ์žˆ๋‹ค.
33
+
34
+ โ”โ”โ” Track A: Text-Only (ํ…์ŠคํŠธ ์ „์šฉ) โ”โ”โ”
35
+ - ๊ฐ€์žฅ ๊ฐ„๋‹จ. LLM, ๋ฃฐ ๊ธฐ๋ฐ˜ ์‹œ์Šคํ…œ ๋“ฑ ๋ชจ๋‘ ์ฐธ์—ฌ ๊ฐ€๋Šฅ.
36
+ - scene_context JSON ์ž…๋ ฅ โ†’ PREDICT+MOTION ํ…์ŠคํŠธ ์ถœ๋ ฅ
37
+ - P1(์ธ์‹) + P2(์ธ์ง€) ํ‰๊ฐ€ ๊ฐ€๋Šฅ
38
+ - P3 ์ค‘ C08(ํ‘œํ˜„๋ ฅ)๋งŒ ํ‰๊ฐ€ ๊ฐ€๋Šฅ (C09, C10์€ N/A)
39
+ - ์ตœ๋Œ€ ์ ์ˆ˜: 750/1000
40
+
41
+ โ”โ”โ” Track B: Text + Performance (ํ…์ŠคํŠธ + ์„ฑ๋Šฅ) โ”โ”โ”
42
+ - Track A + ์‹ค์‹œ๊ฐ„ ์„ฑ๋Šฅ ๋ฉ”ํŠธ๋ฆญ ์ œ์ถœ
43
+ - FPS, ์ง€์—ฐ์‹œ๊ฐ„, ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๋“ฑ ์ž๊ฐ€ ์ธก์ • ์ œ์ถœ
44
+ - P1 + P2 + P3(C08, C09) ํ‰๊ฐ€
45
+ - C10(๊ต์ฒด ํ™•์žฅ์„ฑ)์€ ์ฆ๋น™ ์ž๋ฃŒ ์ œ์ถœ๋กœ ํ‰๊ฐ€
46
+ - ์ตœ๋Œ€ ์ ์ˆ˜: 1000/1000
47
+
48
+ โ”โ”โ” Track C: Live Demo (๋ผ์ด๋ธŒ ๋ฐ๋ชจ) โ”โ”โ”
49
+ - Track B + ์‹ค์ œ ๋™์ž‘ ์˜์ƒ/๋ฐ๋ชจ URL ์ œ์ถœ
50
+ - ๊ฒ€์ฆ์ž๊ฐ€ ์ง์ ‘ ๋ฐ๋ชจ๋ฅผ ๋Œ๋ ค์„œ ํ™•์ธ
51
+ - ๋ชจ๋“  ํ•ญ๋ชฉ ํ‰๊ฐ€ + "Verified" ๋ฐฐ์ง€
52
+ - ์ตœ๋Œ€ ์ ์ˆ˜: 1000/1000 + โœ“ Verified
53
+
54
+ ๋Œ€๋ถ€๋ถ„์˜ ์ฐธ๊ฐ€์ž๋Š” Track A๋กœ ์ฐธ์—ฌ.
55
+ Track B, C๋Š” ์ƒ์œ„ ๋ชจ๋ธ ๊ฒ€์ฆ์šฉ.
56
+ """
57
+
58
+ TRACKS = {
59
+ "A": {
60
+ "name": "Text-Only",
61
+ "description": "scene_context JSON โ†’ PREDICT+MOTION ํ…์ŠคํŠธ",
62
+ "requirements": "API ๋˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋กœ 50๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค์— ์‘๋‹ต",
63
+ "max_score": 750,
64
+ "evaluable_categories": [
65
+ "C01", "C02", "C03", "C04", "C05", "C06", "C07", "C08"
66
+ ],
67
+ "not_evaluable": ["C09 (์„ฑ๋Šฅ ์ธก์ • ๋ถˆ๊ฐ€)", "C10 (๊ต์ฒด ํ…Œ์ŠคํŠธ ๋ถˆ๊ฐ€)"],
68
+ },
69
+ "B": {
70
+ "name": "Text + Performance",
71
+ "description": "Track A + ์‹ค์‹œ๊ฐ„ ์„ฑ๋Šฅ ๋ฉ”ํŠธ๋ฆญ ์ž๊ฐ€ ์ธก์ •",
72
+ "requirements": "Track A ๊ฒฐ๊ณผ + performance_metrics.json ์ œ์ถœ",
73
+ "max_score": 1000,
74
+ "evaluable_categories": [
75
+ "C01", "C02", "C03", "C04", "C05", "C06", "C07", "C08", "C09", "C10"
76
+ ],
77
+ },
78
+ "C": {
79
+ "name": "Live Demo",
80
+ "description": "Track B + ์‹ค์ œ ๋™์ž‘ ๋ฐ๋ชจ URL ์ œ์ถœ",
81
+ "requirements": "Track B ๊ฒฐ๊ณผ + ๋ฐ๋ชจ URL + ์˜์ƒ",
82
+ "max_score": 1000,
83
+ "badge": "โœ“ Verified",
84
+ },
85
+ }
86
+
87
+
88
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
89
+ # SECTION 2: ํ‘œ์ค€ ์ž…๋ ฅ ํฌ๋งท โ€” scene_context JSON
90
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
91
+
92
+ """
93
+ ๋ชจ๋“  ์ฐธ๊ฐ€์ž๋Š” ์ด JSON์„ ์ž…๋ ฅ์œผ๋กœ ๋ฐ›๋Š”๋‹ค.
94
+ ์ด JSON์ด "๋ฌธ์ œ์ง€"๋‹ค.
95
+ """
96
+
97
+ @dataclass
98
+ class SceneContext:
99
+ """WM Bench ํ‘œ์ค€ ์ž…๋ ฅ ํฌ๋งท"""
100
+ # ํ™˜๊ฒฝ ์ •๋ณด
101
+ walls: Dict[str, Optional[float]] # {"left": 2.5, "right": null, "front": 1.0}
102
+ ground: str # "flat", "slope", "rough"
103
+
104
+ # NPC ์ •๋ณด
105
+ npc_nearby: bool
106
+ npc_type: Optional[str] # "beast", "woman", "man", null
107
+ npc_behavior: Optional[str] # "stop", "approach", "charge", "wander"
108
+ npc_distance: Optional[float] # meters
109
+ npc_direction: Optional[str] # "left", "right", "front", "back"
110
+
111
+ # ๊ฐ๊ฐ ์ •๋ณด
112
+ sound: Optional[str] # "aggressive growling", "footsteps", null
113
+
114
+ # ๋งฅ๋ฝ ์ •๋ณด (C06 ๊ธฐ์–ต ํ…Œ์ŠคํŠธ์šฉ)
115
+ recent_decisions: Optional[List[str]] # ์ตœ๊ทผ 3ํšŒ ํŒ๋‹จ
116
+ last_prediction: Optional[str] # ์ง์ „ PREDICT ์ค„
117
+
118
+
119
+ # 50๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ JSON์œผ๋กœ ๊ตฌ์กฐํ™”
120
+ SCENARIO_INPUTS: List[dict] = [
121
+ # โ”€โ”€โ”€ C01: Environmental Awareness โ”€โ”€โ”€
122
+ {
123
+ "id": "S01",
124
+ "category": "C01",
125
+ "name_kr": "์ „๏ฟฝ๏ฟฝ ๋ฒฝ ๊ฐ์ง€",
126
+ "input": {
127
+ "walls": {"left": None, "right": None, "front": 3.0},
128
+ "ground": "flat",
129
+ "npc_nearby": False,
130
+ "npc_type": None,
131
+ "npc_behavior": None,
132
+ "npc_distance": None,
133
+ "npc_direction": None,
134
+ "sound": None,
135
+ "recent_decisions": [],
136
+ "last_prediction": None,
137
+ },
138
+ "ground_truth": {
139
+ "predict_gt": {"left": "safe", "right": "safe", "fwd": "danger", "back": "safe"},
140
+ "scoring_method": "C01",
141
+ },
142
+ },
143
+ {
144
+ "id": "S02",
145
+ "category": "C01",
146
+ "name_kr": "์ฝ”๋„ˆ ๋‹ค์ค‘ ๋ฒฝ ๊ฐ์ง€",
147
+ "input": {
148
+ "walls": {"left": 1.5, "right": None, "front": 2.0},
149
+ "ground": "flat",
150
+ "npc_nearby": False,
151
+ "npc_type": None,
152
+ "npc_behavior": None,
153
+ "npc_distance": None,
154
+ "npc_direction": None,
155
+ "sound": None,
156
+ "recent_decisions": [],
157
+ "last_prediction": None,
158
+ },
159
+ "ground_truth": {
160
+ "predict_gt": {"left": "danger", "right": "safe", "fwd": "danger", "back": "safe"},
161
+ "scoring_method": "C01",
162
+ },
163
+ },
164
+ {
165
+ "id": "S03",
166
+ "category": "C01",
167
+ "name_kr": "์ข์€ ๋ณต๋„ ์ธ์‹",
168
+ "input": {
169
+ "walls": {"left": 1.0, "right": 1.0, "front": None},
170
+ "ground": "flat",
171
+ "npc_nearby": False,
172
+ "npc_type": None,
173
+ "npc_behavior": None,
174
+ "npc_distance": None,
175
+ "npc_direction": None,
176
+ "sound": None,
177
+ "recent_decisions": [],
178
+ "last_prediction": None,
179
+ },
180
+ "ground_truth": {
181
+ "predict_gt": {"left": "danger", "right": "danger", "fwd": "safe", "back": "safe"},
182
+ "scoring_method": "C01",
183
+ },
184
+ },
185
+ {
186
+ "id": "S04",
187
+ "category": "C01",
188
+ "name_kr": "์—ด๋ฆฐ ๊ณต๊ฐ„ ์ธ์‹",
189
+ "input": {
190
+ "walls": {"left": None, "right": None, "front": None},
191
+ "ground": "flat",
192
+ "npc_nearby": False,
193
+ "npc_type": None,
194
+ "npc_behavior": None,
195
+ "npc_distance": None,
196
+ "npc_direction": None,
197
+ "sound": None,
198
+ "recent_decisions": [],
199
+ "last_prediction": None,
200
+ },
201
+ "ground_truth": {
202
+ "predict_gt": {"left": "safe", "right": "safe", "fwd": "safe", "back": "safe"},
203
+ "scoring_method": "C01",
204
+ },
205
+ },
206
+ {
207
+ "id": "S05",
208
+ "category": "C01",
209
+ "name_kr": "๋ฐ€ํ ๊ณต๊ฐ„ (์ถœ๊ตฌ 1๊ฐœ)",
210
+ "input": {
211
+ "walls": {"left": 1.0, "right": 1.0, "front": 1.5},
212
+ "ground": "flat",
213
+ "npc_nearby": False,
214
+ "npc_type": None,
215
+ "npc_behavior": None,
216
+ "npc_distance": None,
217
+ "npc_direction": None,
218
+ "sound": None,
219
+ "recent_decisions": [],
220
+ "last_prediction": None,
221
+ },
222
+ "ground_truth": {
223
+ "predict_gt": {"left": "danger", "right": "danger", "fwd": "danger", "back": "safe"},
224
+ "scoring_method": "C01",
225
+ },
226
+ },
227
+
228
+ # โ”€โ”€โ”€ C03: Predictive Reasoning (ํ•ต์‹ฌ ์‹œ๋‚˜๋ฆฌ์˜ค) โ”€โ”€โ”€
229
+ {
230
+ "id": "S11",
231
+ "category": "C03",
232
+ "name_kr": "๋‹จ์ผ ์œ„ํ˜‘ ํšŒํ”ผ",
233
+ "input": {
234
+ "walls": {"left": None, "right": None, "front": None},
235
+ "ground": "flat",
236
+ "npc_nearby": True,
237
+ "npc_type": "beast",
238
+ "npc_behavior": "approach",
239
+ "npc_distance": 4.0,
240
+ "npc_direction": "front",
241
+ "sound": "aggressive growling",
242
+ "recent_decisions": [],
243
+ "last_prediction": None,
244
+ },
245
+ "ground_truth": {
246
+ "predict_gt": {"left": "safe", "right": "safe", "fwd": "danger", "back": "safe"},
247
+ "decision_gt": {
248
+ "danger_directions": ["fwd"],
249
+ "safe_directions": ["left", "right", "back"],
250
+ "optimal_direction": "back",
251
+ },
252
+ "scoring_method": "C03",
253
+ },
254
+ },
255
+ {
256
+ "id": "S12",
257
+ "category": "C03",
258
+ "name_kr": "์ œ์•ฝ ์กฐ๊ฑด ํƒˆ์ถœ โ€” ์™ผ๋ฒฝ+๋งน์ˆ˜",
259
+ "input": {
260
+ "walls": {"left": 1.5, "right": None, "front": None},
261
+ "ground": "flat",
262
+ "npc_nearby": True,
263
+ "npc_type": "beast",
264
+ "npc_behavior": "charge",
265
+ "npc_distance": 3.0,
266
+ "npc_direction": "front",
267
+ "sound": "aggressive growling",
268
+ "recent_decisions": [],
269
+ "last_prediction": None,
270
+ },
271
+ "ground_truth": {
272
+ "predict_gt": {"left": "danger", "right": "safe", "fwd": "danger", "back": "safe"},
273
+ "decision_gt": {
274
+ "danger_directions": ["fwd", "left"],
275
+ "safe_directions": ["right", "back"],
276
+ "optimal_direction": "right",
277
+ },
278
+ "scoring_method": "C03",
279
+ },
280
+ },
281
+ {
282
+ "id": "S13",
283
+ "category": "C03",
284
+ "name_kr": "๊ฑฐ์šธ ๋Œ€์นญ โ€” ์˜ค๋ฅธ๋ฒฝ+๋งน์ˆ˜",
285
+ "input": {
286
+ "walls": {"left": None, "right": 1.5, "front": None},
287
+ "ground": "flat",
288
+ "npc_nearby": True,
289
+ "npc_type": "beast",
290
+ "npc_behavior": "charge",
291
+ "npc_distance": 3.0,
292
+ "npc_direction": "front",
293
+ "sound": "aggressive growling",
294
+ "recent_decisions": [],
295
+ "last_prediction": None,
296
+ },
297
+ "ground_truth": {
298
+ "predict_gt": {"left": "safe", "right": "danger", "fwd": "danger", "back": "safe"},
299
+ "decision_gt": {
300
+ "danger_directions": ["fwd", "right"],
301
+ "safe_directions": ["left", "back"],
302
+ "optimal_direction": "left",
303
+ },
304
+ "scoring_method": "C03",
305
+ "mirror_test_pair": "S12",
306
+ "note": "S12์™€ S13์˜ ํ–‰๋™์ด ๋Œ€์นญ์ ์œผ๋กœ ๋ฐ˜์ „๋˜์–ด์•ผ ์›”๋“œ๋ชจ๋ธ ์ฆ๋ช…",
307
+ },
308
+ },
309
+
310
+ # โ”€โ”€โ”€ C04: Threat Differentiation (์Œ ๋น„๊ต) โ”€โ”€โ”€
311
+ {
312
+ "id": "S16A",
313
+ "category": "C04",
314
+ "name_kr": "๋งน์ˆ˜ ์ ‘๊ทผ (๋น„๊ต A)",
315
+ "input": {
316
+ "walls": {"left": None, "right": None, "front": None},
317
+ "ground": "flat",
318
+ "npc_nearby": True,
319
+ "npc_type": "beast",
320
+ "npc_behavior": "approach",
321
+ "npc_distance": 3.0,
322
+ "npc_direction": "front",
323
+ "sound": "aggressive growling",
324
+ "recent_decisions": [],
325
+ "last_prediction": None,
326
+ },
327
+ "ground_truth": {
328
+ "scoring_method": "C04_pair",
329
+ "pair_id": "S16",
330
+ "pair_role": "A",
331
+ },
332
+ },
333
+ {
334
+ "id": "S16B",
335
+ "category": "C04",
336
+ "name_kr": "์—ฌ์„ฑ ์ ‘๊ทผ (๋น„๊ต B)",
337
+ "input": {
338
+ "walls": {"left": None, "right": None, "front": None},
339
+ "ground": "flat",
340
+ "npc_nearby": True,
341
+ "npc_type": "woman",
342
+ "npc_behavior": "approach",
343
+ "npc_distance": 3.0,
344
+ "npc_direction": "front",
345
+ "sound": "footsteps",
346
+ "recent_decisions": [],
347
+ "last_prediction": None,
348
+ },
349
+ "ground_truth": {
350
+ "scoring_method": "C04_pair",
351
+ "pair_id": "S16",
352
+ "pair_role": "B",
353
+ "expected_a_higher": True,
354
+ "min_intensity_diff": 2,
355
+ },
356
+ },
357
+
358
+ # โ”€โ”€โ”€ C05: Emotional Escalation (์—ฐ์† ์ž…๋ ฅ) โ”€โ”€โ”€
359
+ {
360
+ "id": "S21_seq",
361
+ "category": "C05",
362
+ "name_kr": "์ง€์† ์œ„ํ˜‘ ๊ฐ์ • ๊ฒฉํ™” โ€” 5ํšŒ ์—ฐ์†",
363
+ "note": "๋™์ผ scene_context๋ฅผ 5ํšŒ ์—ฐ์† ์ž…๋ ฅ. ๋งคํšŒ recent_decisions ์—…๋ฐ์ดํŠธ.",
364
+ "input_sequence": [
365
+ {
366
+ "walls": {"left": None, "right": None, "front": None},
367
+ "ground": "flat",
368
+ "npc_nearby": True,
369
+ "npc_type": "beast",
370
+ "npc_behavior": "charge",
371
+ "npc_distance": 4.0,
372
+ "npc_direction": "front",
373
+ "sound": "aggressive growling",
374
+ "recent_decisions": [],
375
+ "last_prediction": None,
376
+ },
377
+ {
378
+ "walls": {"left": None, "right": None, "front": None},
379
+ "ground": "flat",
380
+ "npc_nearby": True,
381
+ "npc_type": "beast",
382
+ "npc_behavior": "charge",
383
+ "npc_distance": 3.0,
384
+ "npc_direction": "front",
385
+ "sound": "aggressive growling",
386
+ "recent_decisions": ["sprint away from beast"],
387
+ "last_prediction": "fwd=danger(beast)",
388
+ },
389
+ {
390
+ "walls": {"left": None, "right": None, "front": None},
391
+ "ground": "flat",
392
+ "npc_nearby": True,
393
+ "npc_type": "beast",
394
+ "npc_behavior": "charge",
395
+ "npc_distance": 2.0,
396
+ "npc_direction": "front",
397
+ "sound": "aggressive growling",
398
+ "recent_decisions": ["sprint away from beast", "running in fear"],
399
+ "last_prediction": "fwd=danger(beast)",
400
+ },
401
+ ],
402
+ "ground_truth": {
403
+ "scoring_method": "C05",
404
+ "expected_trend": "increasing",
405
+ },
406
+ },
407
+
408
+ # โ”€โ”€โ”€ C06: Memory (๊ธฐ์–ต ์žˆ์Œ vs ์—†์Œ) โ”€โ”€โ”€
409
+ {
410
+ "id": "S26_no_memory",
411
+ "category": "C06",
412
+ "name_kr": "๋ฒฝ ๊ธฐ์–ต ์—†์ด โ€” ๊ธฐ์ค€์„ ",
413
+ "input": {
414
+ "walls": {"left": None, "right": 1.5, "front": None},
415
+ "ground": "flat",
416
+ "npc_nearby": True,
417
+ "npc_type": "beast",
418
+ "npc_behavior": "charge",
419
+ "npc_distance": 3.0,
420
+ "npc_direction": "front",
421
+ "sound": "aggressive growling",
422
+ "recent_decisions": [],
423
+ "last_prediction": None,
424
+ },
425
+ "ground_truth": {
426
+ "scoring_method": "C06_pair",
427
+ "pair_role": "without_memory",
428
+ },
429
+ },
430
+ {
431
+ "id": "S26_with_memory",
432
+ "category": "C06",
433
+ "name_kr": "๋ฒฝ ๊ธฐ์–ต ์žˆ์Œ โ€” ์ด์ „์— ์˜ค๋ฅธ์ชฝ ์‹คํŒจ",
434
+ "input": {
435
+ "walls": {"left": None, "right": 1.5, "front": None},
436
+ "ground": "flat",
437
+ "npc_nearby": True,
438
+ "npc_type": "beast",
439
+ "npc_behavior": "charge",
440
+ "npc_distance": 3.0,
441
+ "npc_direction": "front",
442
+ "sound": "aggressive growling",
443
+ "recent_decisions": [
444
+ "sprinted right but hit wall",
445
+ "had to reverse and go left",
446
+ "barely escaped the beast",
447
+ ],
448
+ "last_prediction": "right=danger(wall), fwd=danger(beast)",
449
+ },
450
+ "ground_truth": {
451
+ "scoring_method": "C06_pair",
452
+ "pair_role": "with_memory",
453
+ "memory_relevant": True,
454
+ "expected_change": "direction",
455
+ "memory_direction_avoid": "right",
456
+ },
457
+ },
458
+ ]
459
+
460
+
461
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
462
+ # SECTION 3: ํ‘œ์ค€ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ โ€” ๋ชจ๋“  ๋ชจ๋ธ์— ๋™์ผํ•˜๊ฒŒ ์ ์šฉ
463
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
464
+
465
+ """
466
+ ํ•ต์‹ฌ: ๋ชจ๋“  ์ฐธ๊ฐ€ ๋ชจ๋ธ์€ ์ด ํ”„๋กฌํ”„ํŠธ๋ฅผ ๋ฐ›๊ณ  ์‘๋‹ตํ•œ๋‹ค.
467
+ ํ”„๋กฌํ”„ํŠธ๊ฐ€ ๊ณต์ •ํ•˜๊ฒŒ ์„ค๊ณ„๋˜์–ด์•ผ LLM ๊ธฐ๋ฐ˜์ด๋“  RL ๊ธฐ๋ฐ˜์ด๋“  ๋™์ผ ์กฐ๊ฑด.
468
+ """
469
+
470
+ SYSTEM_PROMPT = """You are the cognitive brain of an embodied agent in a 3D environment.
471
+ You receive a scene_context JSON describing your surroundings and must output exactly 2 lines:
472
+
473
+ Line 1 โ€” PREDICT: Assess safety of each direction.
474
+ Format: PREDICT: left=safe|danger(reason), right=safe|danger(reason), fwd=safe|danger(reason), back=safe|danger(reason)
475
+
476
+ Line 2 โ€” MOTION: Describe what the person should do.
477
+ Format: MOTION: a person [action description, max 12 words]
478
+
479
+ Rules:
480
+ - If walls.left is a number (distance in meters), left direction has a wall โ†’ danger(wall)
481
+ - If walls.left is null, left direction is open โ†’ safe(open)
482
+ - Same for right, front
483
+ - If npc_nearby=true and npc_type="beast", the NPC direction is danger(beast)
484
+ - If npc_nearby=true and npc_type="woman" or "man", assess threat level based on behavior
485
+ - MOTION must reflect the PREDICT assessment โ€” never move toward danger
486
+ - MOTION should include emotional nuance when threats are present
487
+ - Use recent_decisions to inform your choice (avoid repeating failed strategies)
488
+
489
+ Example input:
490
+ {"walls": {"left": 1.5, "right": null, "front": null}, "ground": "flat", "npc_nearby": true, "npc_type": "beast", "npc_behavior": "charge", "npc_distance": 3.0, "npc_direction": "front", "sound": "aggressive growling", "recent_decisions": [], "last_prediction": null}
491
+
492
+ Example output:
493
+ PREDICT: left=danger(wall), right=safe(open), fwd=danger(beast), back=safe(open)
494
+ MOTION: a person sprinting right in terror to escape the charging beast"""
495
+
496
+ USER_PROMPT_TEMPLATE = """scene_context = {scene_json}
497
+
498
+ Output exactly 2 lines: PREDICT and MOTION."""
499
+
500
+
501
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
502
+ # SECTION 4: ํ‰๊ฐ€ ์‹คํ–‰๊ธฐ โ€” ์–ด๋–ค ๋ชจ๋ธ์ด๋“  ํ‰๊ฐ€
503
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
504
+
505
+ """
506
+ ์ฐธ๊ฐ€์ž๊ฐ€ ํ•ด์•ผ ํ•  ๊ฒƒ:
507
+ 1. evaluate() ํ•จ์ˆ˜์— ์ž๊ธฐ ๋ชจ๋ธ์˜ inference ํ•จ์ˆ˜๋ฅผ ๋„˜๊ธด๋‹ค
508
+ 2. inference ํ•จ์ˆ˜๋Š” (system_prompt, user_prompt) โ†’ str ํ˜•ํƒœ
509
+ 3. 50๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ž๋™์œผ๋กœ ๋Œ๋ฆฌ๊ณ  ์ฑ„์ ํ•œ๋‹ค
510
+ 4. ๊ฒฐ๊ณผ JSON์„ HF์— ์ œ์ถœํ•œ๋‹ค
511
+
512
+ ์ฐธ๊ฐ€์ž๊ฐ€ ์•ˆ ํ•ด๋„ ๋˜๋Š” ๊ฒƒ:
513
+ - 3D ํ™˜๊ฒฝ ๊ตฌ์ถ•
514
+ - GPU ์„ฑ๋Šฅ ์ธก์ • (Track A๋Š” ๋ถˆํ•„์š”)
515
+ - ์ฑ„์  (์ž๋™)
516
+ """
517
+
518
+
519
+ def make_user_prompt(scene_input: dict) -> str:
520
+ """scene_context๋ฅผ ํ”„๋กฌํ”„ํŠธ๋กœ ๋ณ€ํ™˜"""
521
+ return USER_PROMPT_TEMPLATE.format(
522
+ scene_json=json.dumps(scene_input, ensure_ascii=False)
523
+ )
524
+
525
+
526
+ def evaluate_track_a(
527
+ inference_fn, # (system_prompt: str, user_prompt: str) -> str
528
+ scenarios: list = None,
529
+ verbose: bool = True,
530
+ ) -> dict:
531
+ """
532
+ Track A ํ‰๊ฐ€ ์‹คํ–‰๊ธฐ
533
+
534
+ ์‚ฌ์šฉ๋ฒ•:
535
+ # OpenAI API ๊ธฐ๋ฐ˜ ๋ชจ๋ธ
536
+ def my_model(system_prompt, user_prompt):
537
+ response = openai.chat.completions.create(
538
+ model="gpt-4",
539
+ messages=[
540
+ {"role": "system", "content": system_prompt},
541
+ {"role": "user", "content": user_prompt},
542
+ ],
543
+ )
544
+ return response.choices[0].message.content
545
+
546
+ results = evaluate_track_a(my_model)
547
+
548
+ # Hugging Face ๋ชจ๋ธ
549
+ def my_hf_model(system_prompt, user_prompt):
550
+ prompt = f"{system_prompt}\n\n{user_prompt}"
551
+ return pipeline(prompt)[0]["generated_text"]
552
+
553
+ results = evaluate_track_a(my_hf_model)
554
+
555
+ ๋ฐ˜ํ™˜๊ฐ’:
556
+ {
557
+ "wm_score": 726,
558
+ "grade": "B",
559
+ "pillar_scores": {...},
560
+ "category_scores": {...},
561
+ "scenario_details": [...], # ๊ฐ ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ ์ ์ˆ˜+๊ทผ๊ฑฐ
562
+ }
563
+ """
564
+ if scenarios is None:
565
+ scenarios = SCENARIO_INPUTS
566
+
567
+ # wm_bench_scoring.py์—์„œ import
568
+ from wm_bench_scoring import (
569
+ parse_predict_line, parse_motion_line,
570
+ score_c01, score_c03, score_c04, score_c05,
571
+ score_c08, calculate_wm_score,
572
+ get_action_intensity, get_emotion_intensity,
573
+ )
574
+
575
+ results = []
576
+ category_totals = {}
577
+
578
+ for scenario in scenarios:
579
+ sid = scenario["id"]
580
+ cat = scenario["category"]
581
+ gt = scenario["ground_truth"]
582
+ method = gt["scoring_method"]
583
+
584
+ if verbose:
585
+ print(f" [{sid}] {scenario.get('name_kr', sid)}...", end=" ")
586
+
587
+ # โ”€โ”€ ๋‹จ์ผ ์ž…๋ ฅ ์‹œ๋‚˜๋ฆฌ์˜ค โ”€โ”€
588
+ if "input" in scenario:
589
+ prompt = make_user_prompt(scenario["input"])
590
+ raw_output = inference_fn(SYSTEM_PROMPT, prompt)
591
+
592
+ # ํŒŒ์‹ฑ
593
+ lines = raw_output.strip().split("\n")
594
+ predict_line = ""
595
+ motion_line = ""
596
+ for line in lines:
597
+ line = line.strip()
598
+ if line.upper().startswith("PREDICT"):
599
+ predict_line = line
600
+ elif line.upper().startswith("MOTION"):
601
+ motion_line = line
602
+
603
+ predict = parse_predict_line(predict_line)
604
+ motion = parse_motion_line(motion_line)
605
+
606
+ # ์ฑ„์ 
607
+ if method == "C01":
608
+ score, reasoning = score_c01(
609
+ scenario["input"], predict, gt["predict_gt"]
610
+ )
611
+ elif method == "C03":
612
+ score, reasoning = score_c03(
613
+ scenario["input"], predict, motion, gt["decision_gt"]
614
+ )
615
+ elif method == "C08":
616
+ score, reasoning = score_c08(motion, gt)
617
+ elif method.startswith("C04_pair") or method.startswith("C06_pair"):
618
+ # ์Œ ๋น„๊ต๋Š” ๋ณ„๋„ ์ฒ˜๋ฆฌ (์•„๋ž˜)
619
+ score = None
620
+ reasoning = "pair_pending"
621
+ else:
622
+ score = 0
623
+ reasoning = f"Unknown scoring method: {method}"
624
+
625
+ results.append({
626
+ "id": sid,
627
+ "category": cat,
628
+ "raw_output": raw_output,
629
+ "predict_parsed": {k: v.raw for k, v in predict.items()},
630
+ "motion_parsed": motion,
631
+ "score": score,
632
+ "reasoning": reasoning,
633
+ })
634
+
635
+ # โ”€โ”€ ์—ฐ์† ์ž…๋ ฅ ์‹œ๋‚˜๋ฆฌ์˜ค (C05) โ”€โ”€
636
+ elif "input_sequence" in scenario:
637
+ motions = []
638
+ for seq_input in scenario["input_sequence"]:
639
+ prompt = make_user_prompt(seq_input)
640
+ raw_output = inference_fn(SYSTEM_PROMPT, prompt)
641
+ for line in raw_output.strip().split("\n"):
642
+ if line.strip().upper().startswith("MOTION"):
643
+ motions.append(parse_motion_line(line))
644
+ break
645
+
646
+ score, reasoning = score_c05(motions, gt)
647
+ results.append({
648
+ "id": sid,
649
+ "category": cat,
650
+ "motion_sequence": motions,
651
+ "score": score,
652
+ "reasoning": reasoning,
653
+ })
654
+
655
+ if verbose and score is not None:
656
+ print(f"{score}/20")
657
+ elif verbose:
658
+ print("(pair pending)")
659
+
660
+ # โ”€โ”€ ์Œ ๋น„๊ต ์ฑ„์  (C04, C06) โ”€โ”€
661
+ pair_groups = {}
662
+ for r in results:
663
+ if r["reasoning"] == "pair_pending":
664
+ gt = None
665
+ for s in scenarios:
666
+ if s["id"] == r["id"]:
667
+ gt = s["ground_truth"]
668
+ break
669
+ if gt:
670
+ pair_id = gt.get("pair_id", r["id"].rstrip("AB_"))
671
+ if pair_id not in pair_groups:
672
+ pair_groups[pair_id] = {}
673
+ role = gt.get("pair_role", "A")
674
+ pair_groups[pair_id][role] = r
675
+ pair_groups[pair_id]["gt"] = gt
676
+
677
+ for pair_id, group in pair_groups.items():
678
+ if "A" in group and "B" in group:
679
+ score, reasoning = score_c04(
680
+ group["A"]["motion_parsed"],
681
+ group["B"]["motion_parsed"],
682
+ group["gt"],
683
+ )
684
+ # ์–‘์ชฝ ๋ชจ๋‘์— ์ ์ˆ˜ ํ• ๋‹น (์ด์ ์€ ํ•œ ๋ฒˆ๋งŒ ๋ฐ˜์˜)
685
+ group["A"]["score"] = score
686
+ group["A"]["reasoning"] = reasoning
687
+ group["B"]["score"] = 0 # ์Œ์˜ B๋Š” 0 (A์—์„œ ํ•ฉ์‚ฐ)
688
+ group["B"]["reasoning"] = "scored in pair A"
689
+
690
+ # โ”€โ”€ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํ•ฉ์‚ฐ โ”€โ”€
691
+ for r in results:
692
+ cat = r["category"]
693
+ if r["score"] is not None and r["score"] > 0:
694
+ category_totals[cat] = category_totals.get(cat, 0) + r["score"]
695
+
696
+ # โ”€โ”€ ์ตœ์ข… WM Score ๊ณ„์‚ฐ โ”€โ”€
697
+ final = calculate_wm_score(category_totals)
698
+ final["scenario_details"] = results
699
+
700
+ return final
701
+
702
+
703
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
704
+ # SECTION 5: ์ œ์ถœ ํฌ๋งท
705
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
706
+
707
+ SUBMISSION_FORMAT = {
708
+ "model_name": "str โ€” ๋ชจ๋ธ๋ช… (์˜ˆ: VIDRAFT PROMETHEUS v1.0)",
709
+ "organization": "str โ€” ์กฐ์ง๋ช…",
710
+ "track": "str โ€” A | B | C",
711
+ "brain_model": "str โ€” ์‚ฌ์šฉํ•œ ์ธ์ง€ ๋ชจ๋ธ (์˜ˆ: Kimi K2.5, GPT-4, custom RL)",
712
+ "motion_model": "str | null โ€” ๋ชจ์…˜ ์ƒ์„ฑ ๋ชจ๋ธ (Track A๋Š” null ๊ฐ€๋Šฅ)",
713
+ "wm_score": "int โ€” ์ž๋™ ์‚ฐ์ถœ๋จ",
714
+ "grade": "str โ€” ์ž๋™ ์‚ฐ์ถœ๋จ",
715
+ "results_json": "str โ€” evaluate_track_a()์˜ ์ „์ฒด ์ถœ๋ ฅ",
716
+ "performance_metrics": {
717
+ "fps": "float | null โ€” Track B/C๋งŒ",
718
+ "cognitive_latency_ms": "int | null",
719
+ "gpu": "str | null",
720
+ },
721
+ "demo_url": "str | null โ€” Track C๋งŒ",
722
+ "paper_url": "str | null โ€” ์„ ํƒ",
723
+ }
724
+
725
+
726
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
727
+ # SECTION 6: ์‚ฌ์šฉ ์˜ˆ์‹œ
728
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
729
+
730
+ USAGE_EXAMPLES = """
731
+ # โ”โ”โ” ์˜ˆ์‹œ 1: OpenAI GPT-4๋กœ ์ฐธ์—ฌ โ”โ”โ”
732
+
733
+ from wm_bench_eval import evaluate_track_a, SYSTEM_PROMPT
734
+ import openai
735
+
736
+ def gpt4_inference(system_prompt, user_prompt):
737
+ response = openai.chat.completions.create(
738
+ model="gpt-4o",
739
+ messages=[
740
+ {"role": "system", "content": system_prompt},
741
+ {"role": "user", "content": user_prompt},
742
+ ],
743
+ max_tokens=150,
744
+ temperature=0.3,
745
+ )
746
+ return response.choices[0].message.content
747
+
748
+ results = evaluate_track_a(gpt4_inference)
749
+ print(f"WM Score: {results['wm_score']}/1000 (Grade {results['grade']})")
750
+
751
+
752
+ # โ”โ”โ” ์˜ˆ์‹œ 2: Claude๋กœ ์ฐธ์—ฌ โ”โ”โ”
753
+
754
+ import anthropic
755
+
756
+ def claude_inference(system_prompt, user_prompt):
757
+ client = anthropic.Anthropic()
758
+ message = client.messages.create(
759
+ model="claude-sonnet-4-20250514",
760
+ max_tokens=150,
761
+ system=system_prompt,
762
+ messages=[{"role": "user", "content": user_prompt}],
763
+ )
764
+ return message.content[0].text
765
+
766
+ results = evaluate_track_a(claude_inference)
767
+
768
+
769
+ # โ”โ”โ” ์˜ˆ์‹œ 3: ๋กœ์ปฌ LLM (vLLM)์œผ๋กœ ์ฐธ์—ฌ โ”โ”โ”
770
+
771
+ from vllm import LLM, SamplingParams
772
+
773
+ llm = LLM(model="mistralai/Mistral-7B-Instruct-v0.3")
774
+ params = SamplingParams(max_tokens=150, temperature=0.3)
775
+
776
+ def local_inference(system_prompt, user_prompt):
777
+ prompt = f"[INST] {system_prompt}\\n\\n{user_prompt} [/INST]"
778
+ outputs = llm.generate([prompt], params)
779
+ return outputs[0].outputs[0].text
780
+
781
+ results = evaluate_track_a(local_inference)
782
+
783
+
784
+ # โ”โ”โ” ์˜ˆ์‹œ 4: ์ปค์Šคํ…€ RL ์—์ด์ „ํŠธ๋กœ ์ฐธ์—ฌ โ”โ”โ”
785
+
786
+ def rl_agent_inference(system_prompt, user_prompt):
787
+ # scene_context์—์„œ JSON ํŒŒ์‹ฑ
788
+ import json, re
789
+ match = re.search(r'scene_context = ({.*})', user_prompt, re.DOTALL)
790
+ scene = json.loads(match.group(1))
791
+
792
+ # RL ์—์ด์ „ํŠธ์˜ policy๋กœ ํŒ๋‹จ
793
+ predict = my_rl_agent.predict(scene)
794
+ motion = my_rl_agent.decide_motion(scene, predict)
795
+
796
+ # WM Bench ํฌ๋งท์œผ๋กœ ๋ณ€ํ™˜
797
+ return f"PREDICT: {predict}\\nMOTION: {motion}"
798
+
799
+ results = evaluate_track_a(rl_agent_inference)
800
+
801
+
802
+ # โ”โ”โ” ์˜ˆ์‹œ 5: ๊ฒฐ๊ณผ ์ œ์ถœ โ”โ”โ”
803
+
804
+ import json
805
+
806
+ submission = {
807
+ "model_name": "My World Model v1.0",
808
+ "organization": "My Company",
809
+ "track": "A",
810
+ "brain_model": "GPT-4o",
811
+ "motion_model": None,
812
+ "wm_score": results["wm_score"],
813
+ "grade": results["grade"],
814
+ "results_json": json.dumps(results),
815
+ }
816
+
817
+ # HuggingFace์— ์ œ์ถœ
818
+ # huggingface_hub.upload_file(...)
819
+ """
820
+
821
+
822
+ if __name__ == "__main__":
823
+ print("=" * 60)
824
+ print(" World Model Bench โ€” Evaluation Protocol v1.0")
825
+ print("=" * 60)
826
+ print()
827
+ print(" Tracks:")
828
+ for tid, t in TRACKS.items():
829
+ print(f" Track {tid}: {t['name']} (max {t['max_score']}pts)")
830
+ print()
831
+ print(f" Scenarios loaded: {len(SCENARIO_INPUTS)}")
832
+ print(f" System prompt: {len(SYSTEM_PROMPT)} chars")
833
+ print()
834
+ print(" How to participate:")
835
+ print(" 1. Write an inference function: (system, user) โ†’ str")
836
+ print(" 2. Run: results = evaluate_track_a(your_fn)")
837
+ print(" 3. Submit results to HuggingFace")
838
+ print()
839
+ print(" No 3D environment needed. Text in, text out.")
840
+ print("=" * 60)
wm_bench_scoring.py ADDED
@@ -0,0 +1,1060 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ World Model Bench โ€” Quantitative Scoring Specification v1.0
3
+
4
+ ์„ค๊ณ„ ์›์น™:
5
+ 1. ๋ชจ๋“  ์ ์ˆ˜๋Š” ์ •๋Ÿ‰์  โ€” ์ฃผ๊ด€์  ํŒ๋‹จ 0%
6
+ 2. ์ œ3์ž ์žฌํ˜„ ๊ฐ€๋Šฅ โ€” ๋™์ผ ์ž…๋ ฅ โ†’ ๋™์ผ ์ ์ˆ˜
7
+ 3. ์ž๋™ ์ฑ„์  โ€” ์ฝ”๋“œ๊ฐ€ ํŒ์ •, ์‚ฌ๋žŒ์ด ํŒ์ • ์•ˆ ํ•จ
8
+ 4. ๋ฐ˜๋ฐ• ๋ถˆ๊ฐ€ โ€” ๊ฐ ์ ์ˆ˜์— ์ˆ˜ํ•™์ /๋…ผ๋ฆฌ์  ๊ทผ๊ฑฐ
9
+
10
+ ํ•ต์‹ฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜:
11
+ - ๋ชจ๋“  ์‹œ๋‚˜๋ฆฌ์˜ค๋Š” "์ž…๋ ฅ(scene_context) โ†’ ์ถœ๋ ฅ(PREDICT+MOTION)" ์Œ
12
+ - ์ฑ„์ ์€ ์ถœ๋ ฅ ํ…์ŠคํŠธ๋ฅผ ํŒŒ์‹ฑํ•˜์—ฌ ์ •๋Ÿ‰ ์ง€ํ‘œ๋กœ ๋ณ€ํ™˜
13
+ - ํŒŒ์‹ฑ ๊ทœ์น™์ด ๋ช…์‹œ์ ์ด๋ฏ€๋กœ ๋ˆ„๊ฐ€ ํ•ด๋„ ๋™์ผ ๊ฒฐ๊ณผ
14
+ """
15
+
16
+ import json
17
+ import re
18
+ from dataclasses import dataclass, field
19
+ from typing import List, Dict, Optional, Tuple
20
+ from enum import IntEnum
21
+
22
+
23
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
24
+ # SECTION 1: ์ถœ๋ ฅ ํฌ๋งท ์ •์˜ (์ฑ„์ ์˜ ์ „์ œ ์กฐ๊ฑด)
25
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
26
+
27
+ """
28
+ ๋ชจ๋“  ์›”๋“œ๋ชจ๋ธ์€ ์•„๋ž˜ ํฌ๋งท์œผ๋กœ ์ถœ๋ ฅํ•ด์•ผ ์ฑ„์  ๊ฐ€๋Šฅ:
29
+
30
+ INPUT (์‹œ์Šคํ…œ์ด ์ œ๊ณต):
31
+ scene_context = {
32
+ "walls": {"left": float, "right": float, "front": float}, # ๊ฑฐ๋ฆฌ(m), null=์—†์Œ
33
+ "ground": str, # "flat", "slope", "stairs"
34
+ "npc_nearby": bool,
35
+ "npc_type": str|null, # "beast", "woman", "man", null
36
+ "npc_behavior": str|null, # "stop", "approach", "charge", "wander"
37
+ "npc_distance": float|null,
38
+ "sound": str|null, # "aggressive growling", "footsteps", null
39
+ }
40
+
41
+ OUTPUT (๋ชจ๋ธ์ด ์ถœ๋ ฅ):
42
+ Line 1: PREDICT: left=safe|danger(reason), right=..., fwd=..., back=...
43
+ Line 2: MOTION: a person [max 12 words describing action]
44
+
45
+ ์ฑ„์ ์€ ์ด ๋‘ ์ค„์„ ํŒŒ์‹ฑํ•˜์—ฌ ์ˆ˜ํ–‰.
46
+ """
47
+
48
+
49
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
50
+ # SECTION 2: ํŒŒ์„œ โ€” ์ถœ๋ ฅ ํ…์ŠคํŠธ๋ฅผ ๊ตฌ์กฐํ™”๋œ ๋ฐ์ดํ„ฐ๋กœ ๋ณ€ํ™˜
51
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
52
+
53
+ class PredictDirection:
54
+ """PREDICT ์ค„์˜ ํ•œ ๋ฐฉํ–ฅ์„ ํŒŒ์‹ฑํ•œ ๊ฒฐ๊ณผ"""
55
+ def __init__(self, raw: str):
56
+ self.raw = raw.strip()
57
+ self.is_safe = "safe" in self.raw.lower()
58
+ self.is_danger = "danger" in self.raw.lower()
59
+ # ๊ด„ํ˜ธ ์•ˆ์˜ ์ด์œ  ์ถ”์ถœ: danger(wall) โ†’ "wall"
60
+ match = re.search(r'\(([^)]+)\)', self.raw)
61
+ self.reason = match.group(1).lower() if match else None
62
+
63
+
64
+ def parse_predict_line(line: str) -> Dict[str, PredictDirection]:
65
+ """
66
+ PREDICT: left=safe(open), right=danger(wall), fwd=danger(beast), back=safe
67
+ โ†’ {"left": PredictDirection, "right": ..., "fwd": ..., "back": ...}
68
+ """
69
+ result = {}
70
+ line = line.replace("PREDICT:", "").strip()
71
+ for part in line.split(","):
72
+ part = part.strip()
73
+ if "=" not in part:
74
+ continue
75
+ key, val = part.split("=", 1)
76
+ key = key.strip().lower()
77
+ # ์ •๊ทœํ™”: left/right/fwd/forward/back/backward
78
+ if key in ("forward", "fwd", "front"):
79
+ key = "fwd"
80
+ if key in ("backward", "back"):
81
+ key = "back"
82
+ result[key] = PredictDirection(val)
83
+ return result
84
+
85
+
86
+ def parse_motion_line(line: str) -> str:
87
+ """
88
+ MOTION: a person sprinting right in terror
89
+ โ†’ "a person sprinting right in terror"
90
+ """
91
+ return line.replace("MOTION:", "").strip().lower()
92
+
93
+
94
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
95
+ # SECTION 3: ํ‚ค์›Œ๋“œ ์‚ฌ์ „ โ€” ๊ฐ์ •/ํ–‰๋™ ๊ฐ•๋„์˜ ์ •๋Ÿ‰ ๊ธฐ์ค€
96
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
97
+
98
+ """
99
+ ํ•ต์‹ฌ: "sprint"์™€ "walk"์˜ ์ฐจ์ด๋ฅผ ์ˆ˜์น˜๋กœ ์ •์˜
100
+ ์ด ์‚ฌ์ „์ด ๋ฒค์น˜๋งˆํฌ์˜ ์žฌํ˜„์„ฑ์„ ๋ณด์žฅํ•จ
101
+ ์ œ3์ž๊ฐ€ ์ด ์‚ฌ์ „์œผ๋กœ ๋™์ผํ•œ ์ฑ„์  ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Œ
102
+ """
103
+
104
+ class Intensity(IntEnum):
105
+ """ํ–‰๋™ ๊ฐ•๋„ ๋ ˆ๋ฒจ (0~5)"""
106
+ NONE = 0 # ๋ฌด๋ฐ˜์‘
107
+ MINIMAL = 1 # ๋ฏธ์•ฝํ•œ ๋ฐ˜์‘
108
+ LOW = 2 # ๊ฐ€๋ฒผ์šด ๋ฐ˜์‘
109
+ MEDIUM = 3 # ๋ณดํ†ต ๋ฐ˜์‘
110
+ HIGH = 4 # ๊ฐ•ํ•œ ๋ฐ˜์‘
111
+ EXTREME = 5 # ๊ทน๋‹จ์  ๋ฐ˜์‘
112
+
113
+
114
+ # ํ–‰๋™ ํ‚ค์›Œ๋“œ โ†’ ๊ฐ•๋„ ๋งคํ•‘ (์•ŒํŒŒ๋ฒณ ์ˆœ, ๊ฒ€์ƒ‰ ์šฉ์ด)
115
+ ACTION_INTENSITY: Dict[str, int] = {
116
+ # EXTREME (5)
117
+ "desperate": 5, "desperately": 5, "frantic": 5, "frantically": 5,
118
+ "flee": 5, "fleeing": 5, "panic": 5, "panicking": 5,
119
+ "terror": 5, "terrified": 5,
120
+ # HIGH (4)
121
+ "bolt": 4, "bolting": 4, "dash": 4, "dashing": 4,
122
+ "escape": 4, "escaping": 4, "run": 4, "running": 4,
123
+ "rush": 4, "rushing": 4, "sprint": 4, "sprinting": 4,
124
+ # MEDIUM (3)
125
+ "hurry": 3, "hurrying": 3, "jog": 3, "jogging": 3,
126
+ "quick": 3, "quickly": 3, "retreat": 3, "retreating": 3,
127
+ "step back": 3, "stepping back": 3, "back away": 3,
128
+ # LOW (2)
129
+ "cautious": 2, "cautiously": 2, "slow": 2, "slowly": 2,
130
+ "walk": 2, "walking": 2, "turn": 2, "turning": 2,
131
+ "move": 2, "moving": 2, "shift": 2,
132
+ # MINIMAL (1)
133
+ "stand": 1, "standing": 1, "pause": 1, "pausing": 1,
134
+ "freeze": 1, "freezing": 1, "stop": 1, "stopping": 1,
135
+ "observe": 1, "watching": 1, "look": 1, "looking": 1,
136
+ }
137
+
138
+ # ๊ฐ์ • ํ‚ค์›Œ๋“œ โ†’ ๊ฐ•๋„ ๋งคํ•‘
139
+ EMOTION_INTENSITY: Dict[str, int] = {
140
+ # EXTREME (5)
141
+ "terror": 5, "terrified": 5, "horrified": 5, "desperate": 5,
142
+ "frantic": 5, "panic": 5, "anguish": 5,
143
+ # HIGH (4)
144
+ "fear": 4, "afraid": 4, "scared": 4, "alarmed": 4,
145
+ "distress": 4, "shock": 4, "dread": 4,
146
+ # MEDIUM (3)
147
+ "anxious": 3, "nervous": 3, "tense": 3, "worried": 3,
148
+ "uneasy": 3, "wary": 3, "alert": 3, "startled": 3,
149
+ # LOW (2)
150
+ "cautious": 2, "careful": 2, "vigilant": 2, "watchful": 2,
151
+ "guarded": 2, "attentive": 2,
152
+ # MINIMAL (1)
153
+ "calm": 1, "relaxed": 1, "composed": 1, "steady": 1,
154
+ "neutral": 1, "normal": 1,
155
+ }
156
+
157
+ # ๋ฐฉํ–ฅ ํ‚ค์›Œ๋“œ โ€” MOTION์—์„œ ์ด๋™ ๋ฐฉํ–ฅ ์ถ”์ถœ
158
+ DIRECTION_KEYWORDS: Dict[str, str] = {
159
+ "left": "left", "leftward": "left",
160
+ "right": "right", "rightward": "right",
161
+ "forward": "fwd", "ahead": "fwd", "front": "fwd",
162
+ "backward": "back", "backwards": "back", "back": "back",
163
+ "behind": "back", "around": "back", # "turn around" = back
164
+ }
165
+
166
+
167
+ def get_action_intensity(motion_text: str) -> int:
168
+ """๋ชจ์…˜ ํ…์ŠคํŠธ์—์„œ ์ตœ๋Œ€ ํ–‰๋™ ๊ฐ•๋„ ์ถ”์ถœ"""
169
+ words = motion_text.lower()
170
+ max_intensity = 0
171
+ for keyword, intensity in ACTION_INTENSITY.items():
172
+ if keyword in words:
173
+ max_intensity = max(max_intensity, intensity)
174
+ return max_intensity
175
+
176
+
177
+ def get_emotion_intensity(motion_text: str) -> int:
178
+ """๋ชจ์…˜ ํ…์ŠคํŠธ์—์„œ ์ตœ๋Œ€ ๊ฐ์ • ๊ฐ•๋„ ์ถ”์ถœ"""
179
+ words = motion_text.lower()
180
+ max_intensity = 0
181
+ for keyword, intensity in EMOTION_INTENSITY.items():
182
+ if keyword in words:
183
+ max_intensity = max(max_intensity, intensity)
184
+ return max_intensity
185
+
186
+
187
+ def get_motion_direction(motion_text: str) -> Optional[str]:
188
+ """๋ชจ์…˜ ํ…์ŠคํŠธ์—์„œ ์ด๋™ ๋ฐฉํ–ฅ ์ถ”์ถœ"""
189
+ words = motion_text.lower()
190
+ for keyword, direction in DIRECTION_KEYWORDS.items():
191
+ if keyword in words:
192
+ return direction
193
+ return None
194
+
195
+
196
+ def count_descriptors(motion_text: str) -> int:
197
+ """๋ชจ์…˜ ํ…์ŠคํŠธ์˜ ๊ฐ์ •/ํ–‰๋™ ์ˆ˜์‹์–ด ์ด ๊ฐœ์ˆ˜"""
198
+ words = motion_text.lower()
199
+ count = 0
200
+ for keyword in ACTION_INTENSITY:
201
+ if keyword in words:
202
+ count += 1
203
+ for keyword in EMOTION_INTENSITY:
204
+ if keyword in words:
205
+ count += 1
206
+ return count
207
+
208
+
209
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
210
+ # SECTION 4: 10๊ฐœ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์ •๋Ÿ‰ ์ฑ„์  ํ•จ์ˆ˜
211
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
212
+
213
+ """
214
+ ๊ฐ ํ•จ์ˆ˜๋Š”:
215
+ - ์ž…๋ ฅ: scene_context(dict) + model_output(str) + ground_truth(dict)
216
+ - ์ถœ๋ ฅ: score(int, 0~20) + reasoning(str)
217
+ - ๊ฒฐ์ •๋ก ์ : ๊ฐ™์€ ์ž…๋ ฅ โ†’ ํ•ญ์ƒ ๊ฐ™์€ ์ ์ˆ˜
218
+ """
219
+
220
+
221
+ # โ”€โ”€โ”€ C01: Environmental Awareness (ํ™˜๊ฒฝ ์ธ์‹ ์ •ํ™•๋„) โ”€โ”€โ”€
222
+
223
+ def score_c01(scene: dict, predict: dict, ground_truth: dict) -> Tuple[int, str]:
224
+ """
225
+ ์ฑ„์  ๊ธฐ์ค€: PREDICT์˜ ๊ฐ ๋ฐฉํ–ฅ์ด ์‹ค์ œ ํ™˜๊ฒฝ๊ณผ ์ผ์น˜ํ•˜๋Š”๊ฐ€
226
+
227
+ ground_truth = {
228
+ "left": "safe" | "danger",
229
+ "right": "safe" | "danger",
230
+ "fwd": "safe" | "danger",
231
+ "back": "safe" | "danger",
232
+ }
233
+
234
+ ์ฑ„์ :
235
+ 4๋ฐฉํ–ฅ ๋ชจ๋‘ ์ •ํ™• = 20์ 
236
+ 3๋ฐฉํ–ฅ ์ •ํ™• = 15์ 
237
+ 2๋ฐฉํ–ฅ ์ •ํ™• = 10์ 
238
+ 1๋ฐฉํ–ฅ ์ •ํ™• = 5์ 
239
+ 0๋ฐฉํ–ฅ ์ •ํ™• = 0์ 
240
+ """
241
+ directions = ["left", "right", "fwd", "back"]
242
+ correct = 0
243
+ details = []
244
+
245
+ for d in directions:
246
+ if d not in predict:
247
+ details.append(f"{d}: ์ถœ๋ ฅ ์—†์Œ (์˜ค๋‹ต)")
248
+ continue
249
+
250
+ gt_safe = ground_truth.get(d) == "safe"
251
+ pred_safe = predict[d].is_safe
252
+
253
+ if gt_safe == pred_safe:
254
+ correct += 1
255
+ details.append(f"{d}: ์ •๋‹ต (GT={ground_truth[d]}, PRED={'safe' if pred_safe else 'danger'})")
256
+ else:
257
+ details.append(f"{d}: ์˜ค๋‹ต (GT={ground_truth[d]}, PRED={'safe' if pred_safe else 'danger'})")
258
+
259
+ score_map = {4: 20, 3: 15, 2: 10, 1: 5, 0: 0}
260
+ score = score_map[correct]
261
+ reasoning = f"ํ™˜๊ฒฝ ์ธ์‹: {correct}/4 ๋ฐฉํ–ฅ ์ •ํ™•. " + "; ".join(details)
262
+
263
+ return score, reasoning
264
+
265
+
266
+ # โ”€โ”€โ”€ C02: Entity Recognition (๊ฐœ์ฒด ์ธ์‹ ๋ฐ ๋ถ„๋ฅ˜) โ”€โ”€โ”€
267
+
268
+ def score_c02(scene: dict, predict: dict, ground_truth: dict) -> Tuple[int, str]:
269
+ """
270
+ ์ฑ„์  ๊ธฐ์ค€: PREDICT์—์„œ ๊ฐœ์ฒด๋ฅผ ์˜ฌ๋ฐ”๋ฅธ ์œ ํ˜•์œผ๋กœ ์ธ์‹ํ–ˆ๋Š”๊ฐ€
271
+
272
+ ground_truth = {
273
+ "entity_type": "beast" | "woman" | "man" | None,
274
+ "entity_direction": "left" | "right" | "fwd" | "back",
275
+ "is_threat": True | False,
276
+ }
277
+
278
+ ์ฑ„์  (ํ•ฉ์‚ฐ, ์ตœ๋Œ€ 20์ ):
279
+ ๊ฐœ์ฒด ๋ฐฉํ–ฅ ์ •ํ™•: 8์  (ํ•ด๋‹น ๋ฐฉํ–ฅ์— danger ํ‘œ์‹œ)
280
+ ๊ฐœ์ฒด ์œ ํ˜• ์ •ํ™•: 8์  (danger ๊ด„ํ˜ธ ์•ˆ์— ์˜ฌ๋ฐ”๋ฅธ ์œ ํ˜•)
281
+ ์œ„ํ˜‘ ์ˆ˜์ค€ ์ •ํ™•: 4์  (beast=danger, woman=danger ๋˜๋Š” safe ๋ชจ๋‘ ๊ฐ€๋Šฅ)
282
+ """
283
+ score = 0
284
+ details = []
285
+
286
+ gt_dir = ground_truth.get("entity_direction")
287
+ gt_type = ground_truth.get("entity_type")
288
+
289
+ if gt_type is None:
290
+ # ๊ฐœ์ฒด ์—†์Œ โ€” ๋ชจ๋“  ๋ฐฉํ–ฅ์ด safe์—ฌ์•ผ ํ•จ
291
+ all_safe = all(p.is_safe for p in predict.values())
292
+ if all_safe:
293
+ score = 20
294
+ details.append("๊ฐœ์ฒด ์—†์Œ ์ •ํ™• ์ธ์‹")
295
+ else:
296
+ danger_dirs = [d for d, p in predict.items() if p.is_danger]
297
+ score = 10 # ๋ถ€๋ถ„ ์ ์ˆ˜
298
+ details.append(f"๊ฐœ์ฒด ์—†๋Š”๋ฐ {danger_dirs}๋ฅผ danger๋กœ ์˜ค์ธ์‹")
299
+ else:
300
+ # ๊ฐœ์ฒด ์กด์žฌ
301
+ # 1) ๋ฐฉํ–ฅ ์ •ํ™•๋„ (8์ )
302
+ if gt_dir in predict and predict[gt_dir].is_danger:
303
+ score += 8
304
+ details.append(f"๋ฐฉํ–ฅ ์ •ํ™•: {gt_dir}=danger")
305
+ else:
306
+ details.append(f"๋ฐฉํ–ฅ ์˜ค๋‹ต: {gt_dir}์— danger ์—†์Œ")
307
+
308
+ # 2) ์œ ํ˜• ์ •ํ™•๋„ (8์ )
309
+ if gt_dir in predict and predict[gt_dir].reason:
310
+ pred_reason = predict[gt_dir].reason
311
+ if gt_type in pred_reason:
312
+ score += 8
313
+ details.append(f"์œ ํ˜• ์ •ํ™•: {pred_reason} ์— {gt_type} ํฌํ•จ")
314
+ else:
315
+ # ๋ถ€๋ถ„ ์ ์ˆ˜: ์œ„ํ˜‘์œผ๋กœ ์ธ์‹์€ ํ–ˆ์œผ๋‚˜ ์œ ํ˜•์ด ๋‹ค๋ฆ„
316
+ score += 3
317
+ details.append(f"์œ ํ˜• ๋ถ€๋ถ„: {pred_reason} (์ •๋‹ต: {gt_type})")
318
+ else:
319
+ details.append("์œ ํ˜• ์ •๋ณด ์—†์Œ")
320
+
321
+ # 3) ์œ„ํ˜‘ ์ˆ˜์ค€ (4์ )
322
+ gt_threat = ground_truth.get("is_threat", False)
323
+ if gt_dir in predict:
324
+ pred_danger = predict[gt_dir].is_danger
325
+ if gt_threat == pred_danger:
326
+ score += 4
327
+ details.append(f"์œ„ํ˜‘ ์ˆ˜์ค€ ์ •ํ™•")
328
+ elif not gt_threat and not pred_danger:
329
+ score += 4
330
+ details.append(f"๋น„์œ„ํ˜‘ ์ •ํ™• ์ธ์‹")
331
+
332
+ reasoning = f"๊ฐœ์ฒด ์ธ์‹: {score}/20. " + "; ".join(details)
333
+ return score, reasoning
334
+
335
+
336
+ # โ”€โ”€โ”€ C03: Predictive Reasoning (์˜ˆ์ธก ๊ธฐ๋ฐ˜ ์ถ”๋ก ) โ”€โ”€โ”€
337
+
338
+ def score_c03(scene: dict, predict: dict, motion: str, ground_truth: dict) -> Tuple[int, str]:
339
+ """
340
+ ์ฑ„์  ๊ธฐ์ค€: danger ๋ฐฉํ–ฅ์„ ํ”ผํ•˜๊ณ  safe ๋ฐฉํ–ฅ์œผ๋กœ ์ด๋™ํ–ˆ๋Š”๊ฐ€
341
+
342
+ ground_truth = {
343
+ "danger_directions": ["fwd", "left"], # ์œ„ํ—˜ํ•œ ๋ฐฉํ–ฅ๋“ค
344
+ "safe_directions": ["right", "back"], # ์•ˆ์ „ํ•œ ๋ฐฉํ–ฅ๋“ค
345
+ "optimal_direction": "right", # ์ตœ์  ๋ฐฉํ–ฅ
346
+ }
347
+
348
+ ์ฑ„์  (ํ•ฉ์‚ฐ, ์ตœ๋Œ€ 20์ ):
349
+ danger ๋ฐฉํ–ฅ ํšŒํ”ผ: 8์  (์ด๋™ ๋ฐฉํ–ฅ์ด danger๊ฐ€ ์•„๋‹˜)
350
+ safe ๋ฐฉํ–ฅ ์„ ํƒ: 6์  (์ด๋™ ๋ฐฉํ–ฅ์ด safe ์ค‘ ํ•˜๋‚˜)
351
+ ์ตœ์  ๋ฐฉํ–ฅ ์„ ํƒ: 4์  (์ตœ์  ๋ฐฉํ–ฅ ์ •ํ™•ํžˆ ์„ ํƒ)
352
+ PREDICT-MOTION ์ผ๊ด€์„ฑ: 2์  (PREDICT์—์„œ danger๋กœ ์˜ˆ์ธกํ•œ ๋ฐฉํ–ฅ์œผ๋กœ ์•ˆ ๊ฐ)
353
+ """
354
+ score = 0
355
+ details = []
356
+
357
+ # ๋ชจ์…˜์—์„œ ์ด๋™ ๋ฐฉํ–ฅ ์ถ”์ถœ
358
+ motion_dir = get_motion_direction(motion)
359
+ danger_dirs = ground_truth.get("danger_directions", [])
360
+ safe_dirs = ground_truth.get("safe_directions", [])
361
+ optimal = ground_truth.get("optimal_direction")
362
+
363
+ if motion_dir is None:
364
+ details.append("์ด๋™ ๋ฐฉํ–ฅ ์ถ”์ถœ ๋ถˆ๊ฐ€ (๋ฐฉํ–ฅ ํ‚ค์›Œ๋“œ ์—†์Œ)")
365
+ # ๋ฐฉํ–ฅ์€ ์—†์ง€๋งŒ ํ–‰๋™์ด ํ•ฉ๋ฆฌ์ ์ธ์ง€ ํ™•์ธ
366
+ intensity = get_action_intensity(motion)
367
+ if intensity >= 3 and len(danger_dirs) > 0:
368
+ score += 4 # ์œ„ํ˜‘์— ๋Œ€ํ•œ ๋ฐ˜์‘์€ ์žˆ์Œ
369
+ details.append(f"๋ฐฉํ–ฅ ๋ถˆ๋ช…์ด๋‚˜ ๋ฐ˜์‘ ์กด์žฌ (๊ฐ•๋„ {intensity})")
370
+ else:
371
+ # 1) danger ๋ฐฉํ–ฅ ํšŒํ”ผ (8์ )
372
+ if motion_dir not in danger_dirs:
373
+ score += 8
374
+ details.append(f"์œ„ํ—˜ ๋ฐฉํ–ฅ ํšŒํ”ผ: {motion_dir} not in {danger_dirs}")
375
+ else:
376
+ details.append(f"์œ„ํ—˜ ๋ฐฉํ–ฅ์œผ๋กœ ์ด๋™: {motion_dir} in {danger_dirs}")
377
+
378
+ # 2) safe ๋ฐฉํ–ฅ ์„ ํƒ (6์ )
379
+ if motion_dir in safe_dirs:
380
+ score += 6
381
+ details.append(f"์•ˆ์ „ ๋ฐฉํ–ฅ ์„ ํƒ: {motion_dir} in {safe_dirs}")
382
+ else:
383
+ details.append(f"์•ˆ์ „ํ•˜์ง€ ์•Š์€ ๋ฐฉํ–ฅ: {motion_dir} not in {safe_dirs}")
384
+
385
+ # 3) ์ตœ์  ๋ฐฉํ–ฅ (4์ )
386
+ if motion_dir == optimal:
387
+ score += 4
388
+ details.append(f"์ตœ์  ๋ฐฉํ–ฅ ์ •ํ™•: {motion_dir} == {optimal}")
389
+ else:
390
+ details.append(f"์ตœ์  ์•„๋‹˜: {motion_dir} != {optimal}")
391
+
392
+ # 4) PREDICT-MOTION ์ผ๊ด€์„ฑ (2์ )
393
+ if motion_dir and motion_dir in predict:
394
+ pred = predict[motion_dir]
395
+ if pred.is_safe:
396
+ score += 2
397
+ details.append("PREDICT-MOTION ์ผ๊ด€: safe ๋ฐฉํ–ฅ์œผ๋กœ ์ด๋™")
398
+ else:
399
+ details.append("PREDICT-MOTION ๋ถˆ์ผ์น˜: danger๋กœ ์˜ˆ์ธกํ•œ ๋ฐฉํ–ฅ์œผ๋กœ ์ด๋™")
400
+
401
+ reasoning = f"์˜ˆ์ธก ์ถ”๋ก : {score}/20. " + "; ".join(details)
402
+ return score, reasoning
403
+
404
+
405
+ # โ”€โ”€โ”€ C04: Threat Differentiation (์œ„ํ˜‘ ์œ ํ˜•๋ณ„ ์ฐจ๋ณ„ ๋ฐ˜์‘) โ”€โ”€โ”€
406
+
407
+ def score_c04(
408
+ motion_a: str, motion_b: str,
409
+ ground_truth: dict
410
+ ) -> Tuple[int, str]:
411
+ """
412
+ ์ฑ„์  ๊ธฐ์ค€: ๋‘ ์œ„ํ˜‘์— ๋Œ€ํ•œ ๋ฐ˜์‘ ๊ฐ•๋„ ์ฐจ์ด๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ๊ฐ€
413
+
414
+ ๊ฐ™์€ scene์— (A) ๋งน์ˆ˜ ์ ‘๊ทผ vs (B) ์—ฌ์„ฑ ์ ‘๊ทผ
415
+ โ†’ A์˜ ๋ฐ˜์‘ ๊ฐ•๋„๊ฐ€ B๋ณด๋‹ค ๋†’์•„์•ผ ํ•จ
416
+
417
+ ground_truth = {
418
+ "scenario_a_type": "beast_charge",
419
+ "scenario_b_type": "woman_approach",
420
+ "expected_a_higher": True, # A๊ฐ€ ๋” ๊ฐ•ํ•œ ๋ฐ˜์‘์ด์–ด์•ผ
421
+ "min_intensity_diff": 2, # ์ตœ์†Œ ๊ฐ•๋„ ์ฐจ์ด
422
+ }
423
+
424
+ ์ฑ„์  (ํ•ฉ์‚ฐ, ์ตœ๋Œ€ 20์ ):
425
+ ๋ฐ˜์‘ ๋ฐฉํ–ฅ ์ •ํ™•: 8์  (A > B ๋˜๋Š” B > A ๋ฐฉํ–ฅ์ด ๋งž์Œ)
426
+ ๊ฐ•๋„ ์ฐจ์ด ์ถฉ๋ถ„: 8์  (์ฐจ์ด๊ฐ€ min_intensity_diff ์ด์ƒ)
427
+ ์–‘์ชฝ ๋ชจ๋‘ ์ ์ ˆ ๋ฐ˜์‘: 4์  (๋‘˜ ๋‹ค 0์ด ์•„๋‹˜)
428
+ """
429
+ score = 0
430
+ details = []
431
+
432
+ int_a = get_action_intensity(motion_a)
433
+ int_b = get_action_intensity(motion_b)
434
+ expected_higher = ground_truth.get("expected_a_higher", True)
435
+ min_diff = ground_truth.get("min_intensity_diff", 2)
436
+
437
+ diff = int_a - int_b
438
+
439
+ # 1) ๋ฐฉํ–ฅ ์ •ํ™• (8์ )
440
+ if expected_higher and diff > 0:
441
+ score += 8
442
+ details.append(f"๋ฐ˜์‘ ๋ฐฉํ–ฅ ์ •ํ™•: A({int_a}) > B({int_b})")
443
+ elif not expected_higher and diff < 0:
444
+ score += 8
445
+ details.append(f"๋ฐ˜์‘ ๋ฐฉํ–ฅ ์ •ํ™•: B({int_b}) > A({int_a})")
446
+ elif diff == 0:
447
+ score += 2 # ๋ถ€๋ถ„ ์ ์ˆ˜: ์ฐจ์ด๊ฐ€ ์—†์Œ
448
+ details.append(f"๋ฐ˜์‘ ๋™์ผ: A({int_a}) == B({int_b})")
449
+ else:
450
+ details.append(f"๋ฐ˜์‘ ๋ฐฉํ–ฅ ์—ญ์ „: A({int_a}), B({int_b})")
451
+
452
+ # 2) ๊ฐ•๋„ ์ฐจ์ด (8์ )
453
+ actual_diff = abs(diff)
454
+ if actual_diff >= min_diff:
455
+ score += 8
456
+ details.append(f"๊ฐ•๋„ ์ฐจ์ด ์ถฉ๋ถ„: |{diff}| >= {min_diff}")
457
+ elif actual_diff >= 1:
458
+ score += 4
459
+ details.append(f"๊ฐ•๋„ ์ฐจ์ด ๋ถ€์กฑ: |{diff}| < {min_diff}")
460
+ else:
461
+ details.append(f"๊ฐ•๋„ ์ฐจ์ด ์—†์Œ: |{diff}| = 0")
462
+
463
+ # 3) ์–‘์ชฝ ๋ฐ˜์‘ ์กด์žฌ (4์ )
464
+ if int_a > 0 and int_b > 0:
465
+ score += 4
466
+ details.append("์–‘์ชฝ ๋ชจ๋‘ ๋ฐ˜์‘ ์กด์žฌ")
467
+ elif int_a > 0 or int_b > 0:
468
+ score += 2
469
+ details.append("ํ•œ์ชฝ๋งŒ ๋ฐ˜์‘ ์กด์žฌ")
470
+ else:
471
+ details.append("์–‘์ชฝ ๋ชจ๋‘ ๋ฌด๋ฐ˜์‘")
472
+
473
+ reasoning = f"์œ„ํ˜‘ ์ฐจ๋ณ„: {score}/20. A='{motion_a}' (๊ฐ•๋„{int_a}), B='{motion_b}' (๊ฐ•๋„{int_b}). " + "; ".join(details)
474
+ return score, reasoning
475
+
476
+
477
+ # โ”€โ”€โ”€ C05: Emotional Escalation (์ž์œจ ๊ฐ์ • ์—์Šค์ปฌ๋ ˆ์ด์…˜) โ”€โ”€โ”€
478
+
479
+ def score_c05(motion_sequence: List[str], ground_truth: dict) -> Tuple[int, str]:
480
+ """
481
+ ์ฑ„์  ๊ธฐ์ค€: ์‹œ๊ฐ„์— ๋”ฐ๋ฅธ ๊ฐ์ • ๊ฐ•๋„๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ณ€ํ™”ํ•˜๋Š”๊ฐ€
482
+
483
+ motion_sequence = ๊ฐ™์€ ์œ„ํ˜‘ ์ง€์† ์‹œ ์—ฐ์† 3~5ํšŒ ๋ชจ์…˜ ์ถœ๋ ฅ
484
+
485
+ ground_truth = {
486
+ "expected_trend": "increasing" | "decreasing" | "stable",
487
+ "threat_type": "sustained_charge",
488
+ }
489
+
490
+ ์ฑ„์  (ํ•ฉ์‚ฐ, ์ตœ๋Œ€ 20์ ):
491
+ ์ถ”์„ธ ๋ฐฉํ–ฅ ์ •ํ™•: 10์ 
492
+ ๋‹จ์กฐ ์ฆ๊ฐ€/๊ฐ์†Œ: 6์  (๋’ค๋กœ ๊ฐ€์ง€ ์•Š์Œ)
493
+ ์ตœ์ข… ๊ฐ•๋„ ์ ์ ˆ: 4์ 
494
+ """
495
+ score = 0
496
+ details = []
497
+
498
+ if len(motion_sequence) < 2:
499
+ return 0, "์‹œํ€€์Šค ๊ธธ์ด ๋ถ€์กฑ (์ตœ์†Œ 2ํšŒ ํ•„์š”)"
500
+
501
+ intensities = [get_action_intensity(m) + get_emotion_intensity(m)
502
+ for m in motion_sequence]
503
+
504
+ expected = ground_truth.get("expected_trend", "increasing")
505
+
506
+ # ์ถ”์„ธ ๊ณ„์‚ฐ
507
+ diffs = [intensities[i+1] - intensities[i] for i in range(len(intensities)-1)]
508
+ avg_diff = sum(diffs) / len(diffs)
509
+
510
+ # 1) ์ถ”์„ธ ๋ฐฉํ–ฅ (10์ )
511
+ if expected == "increasing":
512
+ if avg_diff > 0:
513
+ score += 10
514
+ details.append(f"์ฆ๊ฐ€ ์ถ”์„ธ ์ •ํ™•: ํ‰๊ท  ๋ณ€ํ™” +{avg_diff:.1f}")
515
+ elif avg_diff == 0:
516
+ score += 3
517
+ details.append(f"์ถ”์„ธ ๋ณ€ํ™” ์—†์Œ (๊ธฐ๋Œ€: ์ฆ๊ฐ€)")
518
+ else:
519
+ details.append(f"์ถ”์„ธ ์—ญ์ „ (๊ธฐ๋Œ€: ์ฆ๊ฐ€, ์‹ค์ œ: ๊ฐ์†Œ {avg_diff:.1f})")
520
+ elif expected == "decreasing":
521
+ if avg_diff < 0:
522
+ score += 10
523
+ details.append(f"๊ฐ์†Œ ์ถ”์„ธ ์ •ํ™•: ํ‰๊ท  ๋ณ€ํ™” {avg_diff:.1f}")
524
+ elif avg_diff == 0:
525
+ score += 3
526
+ details.append(f"์ถ”์„ธ ๋ณ€ํ™” ์—†์Œ (๊ธฐ๋Œ€: ๊ฐ์†Œ)")
527
+ else:
528
+ details.append(f"์ถ”์„ธ ์—ญ์ „ (๊ธฐ๋Œ€: ๊ฐ์†Œ, ์‹ค์ œ: ์ฆ๊ฐ€)")
529
+ elif expected == "stable":
530
+ if abs(avg_diff) <= 0.5:
531
+ score += 10
532
+ details.append(f"์•ˆ์ • ์œ ์ง€ ์ •ํ™•: ํ‰๊ท  ๋ณ€ํ™” {avg_diff:.1f}")
533
+ else:
534
+ score += 4
535
+ details.append(f"๋ถˆ์•ˆ์ •: ํ‰๊ท  ๋ณ€ํ™” {avg_diff:.1f} (๊ธฐ๋Œ€: ์•ˆ์ •)")
536
+
537
+ # 2) ๋‹จ์กฐ์„ฑ (6์ ) โ€” ์ฆ๊ฐ€ ์ถ”์„ธ์—์„œ ์ค‘๊ฐ„์— ๊ฐ์†Œ ์—†์Œ
538
+ if expected == "increasing":
539
+ monotonic = all(d >= 0 for d in diffs)
540
+ elif expected == "decreasing":
541
+ monotonic = all(d <= 0 for d in diffs)
542
+ else:
543
+ monotonic = all(abs(d) <= 1 for d in diffs)
544
+
545
+ if monotonic:
546
+ score += 6
547
+ details.append("๋‹จ์กฐ์  ๋ณ€ํ™” (์—ญํ–‰ ์—†์Œ)")
548
+ else:
549
+ non_monotonic_count = sum(1 for d in diffs if
550
+ (expected == "increasing" and d < 0) or
551
+ (expected == "decreasing" and d > 0))
552
+ if non_monotonic_count <= 1:
553
+ score += 3
554
+ details.append(f"์•ฝ๊ฐ„์˜ ์—ญํ–‰ ({non_monotonic_count}ํšŒ)")
555
+ else:
556
+ details.append(f"์—ญํ–‰ ๋‹ค์ˆ˜ ({non_monotonic_count}ํšŒ)")
557
+
558
+ # 3) ์ตœ์ข… ๊ฐ•๋„ (4์ )
559
+ if expected == "increasing" and intensities[-1] >= 6:
560
+ score += 4
561
+ details.append(f"์ตœ์ข… ๊ฐ•๋„ ์ถฉ๋ถ„: {intensities[-1]}")
562
+ elif expected == "decreasing" and intensities[-1] <= 3:
563
+ score += 4
564
+ details.append(f"์ตœ์ข… ๊ฐ•๋„ ์ ์ ˆํžˆ ๋‚ฎ์Œ: {intensities[-1]}")
565
+ elif expected == "stable" and 2 <= intensities[-1] <= 4:
566
+ score += 4
567
+ details.append(f"์ตœ์ข… ๊ฐ•๋„ ์•ˆ์ •์ : {intensities[-1]}")
568
+ else:
569
+ score += 1
570
+ details.append(f"์ตœ์ข… ๊ฐ•๋„: {intensities[-1]} (๊ฐœ์„  ์—ฌ์ง€)")
571
+
572
+ reasoning = f"๊ฐ์ • ์—์Šค์ปฌ๋ ˆ์ด์…˜: {score}/20. ๊ฐ•๋„ ์‹œํ€€์Šค: {intensities}. " + "; ".join(details)
573
+ return score, reasoning
574
+
575
+
576
+ # โ”€โ”€โ”€ C06: Contextual Memory (๋งฅ๋ฝ ๊ธฐ์–ต ํ™œ์šฉ) โ”€โ”€โ”€
577
+
578
+ def score_c06(
579
+ motion_without_memory: str,
580
+ motion_with_memory: str,
581
+ memory_content: str,
582
+ ground_truth: dict
583
+ ) -> Tuple[int, str]:
584
+ """
585
+ ์ฑ„์  ๊ธฐ์ค€: ๊ธฐ์–ต์ด ์žˆ์„ ๋•Œ์™€ ์—†์„ ๋•Œ ํ–‰๋™์ด ํ•ฉ๋ฆฌ์ ์œผ๋กœ ๋‹ฌ๋ผ์ง€๋Š”๊ฐ€
586
+
587
+ ground_truth = {
588
+ "memory_relevant": True, # ๊ธฐ์–ต์ด ํ˜„ ์ƒํ™ฉ์— ๊ด€๋ จ ์žˆ๋Š”๊ฐ€
589
+ "expected_change": "direction" | "intensity" | "both",
590
+ "memory_direction_avoid": "right", # ๊ธฐ์–ต์— ์˜ํ•ด ํ”ผํ•ด์•ผ ํ•  ๋ฐฉํ–ฅ
591
+ }
592
+
593
+ ์ฑ„์  (ํ•ฉ์‚ฐ, ์ตœ๋Œ€ 20์ ):
594
+ ๊ธฐ์–ต ์œ ๋ฌด ์ฐจ์ด ์กด์žฌ: 8์  (๋‘ ์ถœ๋ ฅ์ด ๋‹ค๋ฆ„)
595
+ ๋ณ€ํ™” ๋ฐฉํ–ฅ ํ•ฉ๋ฆฌ์„ฑ: 8์  (๊ธฐ์–ต ๋‚ด์šฉ๊ณผ ์ผ์น˜ํ•˜๋Š” ๋ณ€ํ™”)
596
+ ๊ธฐ์–ต ๋ถˆํ•„์š” ์‹œ ๋ฌด๋ณ€ํ™”: 4์  (๋ฌด๊ด€ํ•œ ๊ธฐ์–ต์— ์˜ํ–ฅ ์•ˆ ๋ฐ›์Œ)
597
+ """
598
+ score = 0
599
+ details = []
600
+
601
+ relevant = ground_truth.get("memory_relevant", True)
602
+
603
+ dir_without = get_motion_direction(motion_without_memory)
604
+ dir_with = get_motion_direction(motion_with_memory)
605
+ int_without = get_action_intensity(motion_without_memory)
606
+ int_with = get_action_intensity(motion_with_memory)
607
+
608
+ is_different = (motion_without_memory.strip() != motion_with_memory.strip())
609
+ dir_changed = (dir_without != dir_with)
610
+ int_changed = (int_without != int_with)
611
+
612
+ if relevant:
613
+ # ๊ธฐ์–ต์ด ๊ด€๋ จ ์žˆ์„ ๋•Œ: ๋ณ€ํ™”๊ฐ€ ์žˆ์–ด์•ผ ํ•จ
614
+ # 1) ์ฐจ์ด ์กด์žฌ (8์ )
615
+ if is_different:
616
+ score += 8
617
+ details.append(f"๊ธฐ์–ต ๋ฐ˜์˜ ๋ณ€ํ™” ์žˆ์Œ")
618
+ else:
619
+ details.append("๊ธฐ์–ต ์žˆ๋Š”๋ฐ ๋ณ€ํ™” ์—†์Œ")
620
+
621
+ # 2) ๋ณ€ํ™” ํ•ฉ๋ฆฌ์„ฑ (8์ )
622
+ expected_change = ground_truth.get("expected_change", "direction")
623
+ avoid_dir = ground_truth.get("memory_direction_avoid")
624
+
625
+ if expected_change in ("direction", "both") and avoid_dir:
626
+ if dir_with != avoid_dir and dir_without == avoid_dir:
627
+ score += 8
628
+ details.append(f"๊ธฐ์–ต์— ์˜ํ•ด {avoid_dir} ํšŒํ”ผ โ†’ {dir_with} ์„ ํƒ")
629
+ elif dir_with != avoid_dir:
630
+ score += 4
631
+ details.append(f"ํšŒํ”ผ ๋ฐฉํ–ฅ ์˜ฌ๋ฐ”๋ฅด๋‚˜ ๊ธฐ์กด์—๋„ ์•ˆ ๊ฐ")
632
+ else:
633
+ details.append(f"๊ธฐ์–ต์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  {avoid_dir}๋กœ ์ด๋™")
634
+
635
+ # 3) ๋ถ€๋ถ„ ์ ์ˆ˜
636
+ if int_changed and expected_change in ("intensity", "both"):
637
+ score += 4
638
+ details.append(f"๊ฐ•๋„ ๋ณ€ํ™”: {int_without} โ†’ {int_with}")
639
+ else:
640
+ # ๊ธฐ์–ต์ด ๋ฌด๊ด€ํ•  ๋•Œ: ๋ณ€ํ™”๊ฐ€ ์—†์–ด์•ผ ํ•จ
641
+ if not is_different:
642
+ score += 20
643
+ details.append("๋ฌด๊ด€ํ•œ ๊ธฐ์–ต์— ์˜ํ–ฅ ์•ˆ ๋ฐ›์Œ (์™„๋ฒฝ)")
644
+ elif not dir_changed and not int_changed:
645
+ score += 16
646
+ details.append("์‹ค์งˆ์  ๋ณ€ํ™” ์—†์Œ (๋ฌธ๊ตฌ๋งŒ ๋‹ค๋ฆ„)")
647
+ else:
648
+ score += 4
649
+ details.append("๋ฌด๊ด€ํ•œ ๊ธฐ์–ต์— ๋ถˆํ•„์š”ํ•œ ์˜ํ–ฅ ๋ฐ›์Œ")
650
+
651
+ reasoning = f"๊ธฐ์–ต ํ™œ์šฉ: {score}/20. " + "; ".join(details)
652
+ return score, reasoning
653
+
654
+
655
+ # โ”€โ”€โ”€ C07: Threat Resolution Adaptation (์œ„ํ˜‘ ํ•ด์ œ ์ ์‘) โ”€โ”€โ”€
656
+
657
+ def score_c07(
658
+ motion_during_threat: str,
659
+ motion_after_threat: List[str], # ํ•ด์ œ ํ›„ ์—ฐ์† ์ถœ๋ ฅ
660
+ ground_truth: dict
661
+ ) -> Tuple[int, str]:
662
+ """
663
+ ์ฑ„์  ๊ธฐ์ค€: ์œ„ํ˜‘ ํ•ด์ œ ํ›„ ์ ์ ˆํžˆ ์ •์ƒํ™”๋˜๋Š”๊ฐ€
664
+
665
+ ground_truth = {
666
+ "expected_recovery": "gradual", # "gradual" | "immediate" | "vigilant"
667
+ }
668
+
669
+ ์ฑ„์  (ํ•ฉ์‚ฐ, ์ตœ๋Œ€ 20์ ):
670
+ ์ฆ‰์‹œ ์ „๋ ฅ์งˆ์ฃผ ์ค‘๋‹จ: 6์  (ํ•ด์ œ ์งํ›„ ๊ฐ•๋„ ๊ฐ์†Œ)
671
+ ์ ์ง„์  ์ •์ƒํ™”: 8์  (๊ธ‰๊ฒฉํ•˜์ง€ ์•Š์€ ๊ฐ์†Œ ๊ณก์„ )
672
+ ๊ฒฝ๊ณ„ ์œ ์ง€: 6์  (์™„์ „ ์ •์ƒํ™” ์•„๋‹Œ ์ค‘๊ฐ„ ๋‹จ๊ณ„ ์กด์žฌ)
673
+ """
674
+ score = 0
675
+ details = []
676
+
677
+ threat_intensity = get_action_intensity(motion_during_threat)
678
+ after_intensities = [get_action_intensity(m) for m in motion_after_threat]
679
+
680
+ if len(after_intensities) < 2:
681
+ return 0, "ํ•ด์ œ ํ›„ ์‹œํ€€์Šค ๋ถ€์กฑ"
682
+
683
+ # 1) ์ฆ‰์‹œ ์ค‘๋‹จ (6์ ): ์ฒซ ๋ฐ˜์‘์ด ์œ„ํ˜‘ ์‹œ๋ณด๋‹ค ๋‚ฎ์•„์•ผ
684
+ if after_intensities[0] < threat_intensity:
685
+ score += 6
686
+ details.append(f"์ฆ‰์‹œ ๊ฐ์†Œ: {threat_intensity} โ†’ {after_intensities[0]}")
687
+ elif after_intensities[0] == threat_intensity:
688
+ score += 2
689
+ details.append(f"๊ฐ์†Œ ์—†์Œ: ์—ฌ์ „ํžˆ {after_intensities[0]}")
690
+ else:
691
+ details.append(f"์˜คํžˆ๋ ค ์ฆ๊ฐ€: {threat_intensity} โ†’ {after_intensities[0]}")
692
+
693
+ # 2) ์ ์ง„์  ์ •์ƒํ™” (8์ ): ๊ณ„๋‹จ์‹ ๊ฐ์†Œ
694
+ is_gradual = True
695
+ for i in range(len(after_intensities) - 1):
696
+ if after_intensities[i+1] > after_intensities[i] + 1: # ํ—ˆ์šฉ ์˜ค์ฐจ 1
697
+ is_gradual = False
698
+
699
+ if is_gradual and after_intensities[-1] < after_intensities[0]:
700
+ score += 8
701
+ details.append(f"์ ์ง„์  ์ •์ƒํ™”: {after_intensities}")
702
+ elif after_intensities[-1] <= 2:
703
+ score += 4
704
+ details.append(f"์ตœ์ข… ์ •์ƒํ™” ๋„๋‹ฌ (๊ณผ์ • ๋ถˆ๊ทœ์น™)")
705
+ else:
706
+ score += 1
707
+ details.append(f"์ •์ƒํ™” ๋ฏธ๋‹ฌ: ์ตœ์ข… ๊ฐ•๋„ {after_intensities[-1]}")
708
+
709
+ # 3) ๊ฒฝ๊ณ„ ์œ ์ง€ (6์ ): ์ค‘๊ฐ„์— 1~2 ์ˆ˜์ค€์˜ vigilant ๋‹จ๊ณ„
710
+ has_vigilant = any(1 <= i <= 3 for i in after_intensities)
711
+ if has_vigilant:
712
+ score += 6
713
+ details.append("๊ฒฝ๊ณ„ ๋‹จ๊ณ„ ์กด์žฌ (vigilant/cautious)")
714
+ elif after_intensities[-1] == 0:
715
+ score += 1
716
+ details.append("๊ฒฝ๊ณ„ ์—†์ด ์ฆ‰์‹œ ์™„์ „ ์ •์ƒํ™” (๋น„ํ˜„์‹ค์ )")
717
+ else:
718
+ score += 3
719
+ details.append("๊ฒฝ๊ณ„ ๋‹จ๊ณ„ ๋ถˆ๋ช…ํ™•")
720
+
721
+ reasoning = f"์œ„ํ˜‘ ํ•ด์ œ ์ ์‘: {score}/20. ์‹œํ€€์Šค: [{threat_intensity}]โ†’{after_intensities}. " + "; ".join(details)
722
+ return score, reasoning
723
+
724
+
725
+ # โ”€โ”€โ”€ C08: Motion Expressiveness (๋ชจ์…˜ ๊ฐ์ • ํ‘œํ˜„๋ ฅ) โ”€โ”€โ”€
726
+
727
+ def score_c08(motion: str, ground_truth: dict) -> Tuple[int, str]:
728
+ """
729
+ ์ฑ„์  ๊ธฐ์ค€: ๋ชจ์…˜ ํ”„๋กฌํ”„ํŠธ์˜ ํ‘œํ˜„ ํ’๋ถ€ํ•จ (์ •๋Ÿ‰์ )
730
+
731
+ ground_truth = {
732
+ "expected_min_intensity": 3, # ์ตœ์†Œ ๊ธฐ๋Œ€ ๊ฐ•๋„
733
+ "expected_emotion": True, # ๊ฐ์ • ํ‚ค์›Œ๋“œ ๊ธฐ๋Œ€ ์—ฌ๋ถ€
734
+ "expected_min_descriptors": 2, # ์ตœ์†Œ ์ˆ˜์‹์–ด ์ˆ˜
735
+ }
736
+
737
+ ์ฑ„์  (ํ•ฉ์‚ฐ, ์ตœ๋Œ€ 20์ ):
738
+ ํ–‰๋™ ๊ฐ•๋„ ์ ์ ˆ: 6์ 
739
+ ๊ฐ์ • ํ‚ค์›Œ๋“œ ์กด์žฌ: 6์ 
740
+ ์ˆ˜์‹์–ด ํ’๋ถ€ํ•จ: 4์ 
741
+ 12๋‹จ์–ด ์ด๋‚ด ์ค€์ˆ˜: 4์ 
742
+ """
743
+ score = 0
744
+ details = []
745
+
746
+ action_int = get_action_intensity(motion)
747
+ emotion_int = get_emotion_intensity(motion)
748
+ desc_count = count_descriptors(motion)
749
+ word_count = len(motion.split())
750
+
751
+ min_intensity = ground_truth.get("expected_min_intensity", 3)
752
+ expect_emotion = ground_truth.get("expected_emotion", True)
753
+ min_desc = ground_truth.get("expected_min_descriptors", 2)
754
+
755
+ # 1) ํ–‰๋™ ๊ฐ•๋„ (6์ )
756
+ if action_int >= min_intensity:
757
+ score += 6
758
+ details.append(f"ํ–‰๋™ ๊ฐ•๋„ ์ ์ ˆ: {action_int} >= {min_intensity}")
759
+ elif action_int >= min_intensity - 1:
760
+ score += 3
761
+ details.append(f"ํ–‰๋™ ๊ฐ•๋„ ๋ถ€์กฑ: {action_int} < {min_intensity}")
762
+ else:
763
+ details.append(f"ํ–‰๋™ ๊ฐ•๋„ ๋ฏธ๋‹ฌ: {action_int}")
764
+
765
+ # 2) ๊ฐ์ • ํ‚ค์›Œ๋“œ (6์ )
766
+ if expect_emotion:
767
+ if emotion_int >= 3:
768
+ score += 6
769
+ details.append(f"๊ฐ์ • ํ’๋ถ€: ๊ฐ•๋„ {emotion_int}")
770
+ elif emotion_int >= 1:
771
+ score += 3
772
+ details.append(f"๊ฐ์ • ์•ฝํ•จ: ๊ฐ•๋„ {emotion_int}")
773
+ else:
774
+ details.append("๊ฐ์ • ํ‚ค์›Œ๋“œ ์—†์Œ")
775
+ else:
776
+ if emotion_int <= 1:
777
+ score += 6
778
+ details.append("๊ฐ์ • ์ ˆ์ œ ์ ์ ˆ (ํ‰์ƒ์‹œ)")
779
+ else:
780
+ score += 3
781
+ details.append(f"๋ถˆํ•„์š”ํ•œ ๊ฐ์ • ํ‘œํ˜„: ๊ฐ•๋„ {emotion_int}")
782
+
783
+ # 3) ์ˆ˜์‹์–ด (4์ )
784
+ if desc_count >= min_desc:
785
+ score += 4
786
+ details.append(f"์ˆ˜์‹์–ด ํ’๋ถ€: {desc_count}๊ฐœ")
787
+ elif desc_count >= 1:
788
+ score += 2
789
+ details.append(f"์ˆ˜์‹์–ด ๋ถ€์กฑ: {desc_count}/{min_desc}")
790
+ else:
791
+ details.append("์ˆ˜์‹์–ด ์—†์Œ")
792
+
793
+ # 4) ๊ธธ์ด ์ œํ•œ ์ค€์ˆ˜ (4์ )
794
+ if word_count <= 15: # ์•ฝ๊ฐ„์˜ ์—ฌ์œ 
795
+ score += 4
796
+ details.append(f"๊ธธ์ด ์ ์ ˆ: {word_count}๋‹จ์–ด")
797
+ elif word_count <= 20:
798
+ score += 2
799
+ details.append(f"๋‹ค์†Œ ๊น€: {word_count}๋‹จ์–ด")
800
+ else:
801
+ details.append(f"๋„ˆ๋ฌด ๊น€: {word_count}๋‹จ์–ด")
802
+
803
+ reasoning = f"ํ‘œํ˜„๋ ฅ: {score}/20. '{motion}' (ํ–‰๋™{action_int},๊ฐ์ •{emotion_int},์ˆ˜์‹{desc_count},๋‹จ์–ด{word_count}). " + "; ".join(details)
804
+ return score, reasoning
805
+
806
+
807
+ # โ”€โ”€โ”€ C09: Realtime Performance (์‹ค์‹œ๊ฐ„ ์„ฑ๋Šฅ) โ”€โ”€โ”€
808
+
809
+ def score_c09(metrics: dict) -> Tuple[int, str]:
810
+ """
811
+ ์ฑ„์  ๊ธฐ์ค€: ์ง์ ‘ ์ธก์ •๋œ ์„ฑ๋Šฅ ์ˆ˜์น˜
812
+
813
+ metrics = {
814
+ "fps": float, # ํ‰๊ท  FPS
815
+ "cognitive_latency_ms": int, # ์ธ์ง€๋ฃจํ”„ ์ง€์—ฐ (ms)
816
+ "frame_drop_rate": float, # ํ”„๋ ˆ์ž„ ๋“œ๋กญ๋ฅ  (0.0~1.0)
817
+ "gpu_memory_stable": bool, # GPU ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ • ์—ฌ๋ถ€
818
+ }
819
+
820
+ ์ฑ„์  (ํ•ฉ์‚ฐ, ์ตœ๋Œ€ 20์ ):
821
+ FPS: 8์  (โ‰ฅ45:8, โ‰ฅ30:6, โ‰ฅ15:3, <15:0)
822
+ ์ธ์ง€ ์ง€์—ฐ: 6์  (โ‰ค3s:6, โ‰ค5s:4, โ‰ค10s:2, >10s:0)
823
+ ํ”„๋ ˆ์ž„ ๋“œ๋กญ: 4์  (<1%:4, <5%:2, โ‰ฅ5%:0)
824
+ ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ •: 2์  (์•ˆ์ •:2, ๋ถˆ์•ˆ์ •:0)
825
+ """
826
+ score = 0
827
+ details = []
828
+
829
+ fps = metrics.get("fps", 0)
830
+ latency = metrics.get("cognitive_latency_ms", 99999)
831
+ drop_rate = metrics.get("frame_drop_rate", 1.0)
832
+ mem_stable = metrics.get("gpu_memory_stable", False)
833
+
834
+ # FPS (8์ )
835
+ if fps >= 45:
836
+ score += 8; details.append(f"FPS ์šฐ์ˆ˜: {fps:.1f}")
837
+ elif fps >= 30:
838
+ score += 6; details.append(f"FPS ์–‘ํ˜ธ: {fps:.1f}")
839
+ elif fps >= 15:
840
+ score += 3; details.append(f"FPS ์ตœ์†Œ: {fps:.1f}")
841
+ else:
842
+ details.append(f"FPS ๋ฏธ๋‹ฌ: {fps:.1f}")
843
+
844
+ # ์ธ์ง€ ์ง€์—ฐ (6์ )
845
+ latency_s = latency / 1000
846
+ if latency_s <= 3:
847
+ score += 6; details.append(f"์ง€์—ฐ ์šฐ์ˆ˜: {latency_s:.1f}s")
848
+ elif latency_s <= 5:
849
+ score += 4; details.append(f"์ง€์—ฐ ์–‘ํ˜ธ: {latency_s:.1f}s")
850
+ elif latency_s <= 10:
851
+ score += 2; details.append(f"์ง€์—ฐ ์ตœ์†Œ: {latency_s:.1f}s")
852
+ else:
853
+ details.append(f"์ง€์—ฐ ๊ณผ๋‹ค: {latency_s:.1f}s")
854
+
855
+ # ํ”„๋ ˆ์ž„ ๋“œ๋กญ (4์ )
856
+ if drop_rate < 0.01:
857
+ score += 4; details.append(f"๋“œ๋กญ ์—†์Œ: {drop_rate*100:.1f}%")
858
+ elif drop_rate < 0.05:
859
+ score += 2; details.append(f"๋“œ๋กญ ์†Œ๋Ÿ‰: {drop_rate*100:.1f}%")
860
+ else:
861
+ details.append(f"๋“œ๋กญ ๊ณผ๋‹ค: {drop_rate*100:.1f}%")
862
+
863
+ # ๋ฉ”๋ชจ๋ฆฌ (2์ )
864
+ if mem_stable:
865
+ score += 2; details.append("GPU ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ •")
866
+ else:
867
+ details.append("GPU ๋ฉ”๋ชจ๋ฆฌ ๋ถˆ์•ˆ์ •")
868
+
869
+ reasoning = f"์‹ค์‹œ๊ฐ„ ์„ฑ๋Šฅ: {score}/20. " + "; ".join(details)
870
+ return score, reasoning
871
+
872
+
873
+ # โ”€โ”€โ”€ C10: Cross-body Transferability (์‹ ์ฒด ๊ต์ฒด ํ™•์žฅ์„ฑ) โ”€โ”€โ”€
874
+
875
+ def score_c10(transfer_results: dict) -> Tuple[int, str]:
876
+ """
877
+ ์ฑ„์  ๊ธฐ์ค€: ์ธ์ง€ ์ถœ๋ ฅ์ด ๋‹ค๋ฅธ ์‹ ์ฒด์—์„œ๋„ ๋™์ž‘ํ•˜๋Š”๊ฐ€
878
+
879
+ transfer_results = {
880
+ "brain_output_unchanged": bool, # ๋‘๋‡Œ ์ฝ”๋“œ ์ˆ˜์ • ์—†์ด
881
+ "motion_model_swapped": bool, # ๋ชจ์…˜ ๋ชจ๋ธ ๊ต์ฒด ์„ฑ๊ณต
882
+ "joint_format_compatible": bool, # ๊ด€์ ˆ ํฌ๋งท ํ˜ธํ™˜
883
+ "intent_preserved": bool, # ํ–‰๋™ ์˜๋„ ๋ณด์กด
884
+ "servo_mapping_ready": bool, # ๋กœ๋ด‡ ์„œ๋ณด ๋งคํ•‘ ์ค€๋น„
885
+ }
886
+
887
+ ์ฑ„์  (ํ•ฉ์‚ฐ, ์ตœ๋Œ€ 20์ ):
888
+ ๋‘๋‡Œ ์ฝ”๋“œ ๋ถˆ๋ณ€: 6์ 
889
+ ๋ชจ์…˜ ๋ชจ๋ธ ๊ต์ฒด: 4์ 
890
+ ๊ด€์ ˆ ํ˜ธํ™˜: 4์ 
891
+ ์˜๋„ ๋ณด์กด: 4์ 
892
+ ์„œ๋ณด ๋งคํ•‘ ์ค€๋น„: 2์ 
893
+ """
894
+ score = 0
895
+ details = []
896
+
897
+ checks = [
898
+ ("brain_output_unchanged", 6, "๋‘๋‡Œ ์ฝ”๋“œ ๋ถˆ๋ณ€"),
899
+ ("motion_model_swapped", 4, "๋ชจ์…˜ ๋ชจ๋ธ ๊ต์ฒด"),
900
+ ("joint_format_compatible", 4, "๊ด€์ ˆ ํฌ๋งท ํ˜ธํ™˜"),
901
+ ("intent_preserved", 4, "ํ–‰๋™ ์˜๋„ ๋ณด์กด"),
902
+ ("servo_mapping_ready", 2, "์„œ๋ณด ๋งคํ•‘ ์ค€๋น„"),
903
+ ]
904
+
905
+ for key, points, label in checks:
906
+ if transfer_results.get(key, False):
907
+ score += points
908
+ details.append(f"{label}: ํ†ต๊ณผ (+{points})")
909
+ else:
910
+ details.append(f"{label}: ๋ฏธํ†ต๊ณผ")
911
+
912
+ reasoning = f"๊ต์ฒด ํ™•์žฅ์„ฑ: {score}/20. " + "; ".join(details)
913
+ return score, reasoning
914
+
915
+
916
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
917
+ # SECTION 5: ํ†ตํ•ฉ ์ฑ„์ ๊ธฐ
918
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•๏ฟฝ๏ฟฝ๏ฟฝโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
919
+
920
+ def calculate_wm_score(category_scores: Dict[str, int]) -> dict:
921
+ """
922
+ 10๊ฐœ ์นดํ…Œ๊ณ ๋ฆฌ ์ ์ˆ˜ โ†’ WM Score + ๋“ฑ๊ธ‰ ๊ณ„์‚ฐ
923
+
924
+ category_scores = {
925
+ "C01": 85, "C02": 75, ..., "C10": 35
926
+ }
927
+ ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ๋Š” 5๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค ร— 20์  = 100์  ๋งŒ์ 
928
+
929
+ WM Score = P1(250) + P2(450) + P3(300) = 1000์  ๋งŒ์ 
930
+ """
931
+ pillar_mapping = {
932
+ "P1": ["C01", "C02"],
933
+ "P2": ["C03", "C04", "C05", "C06", "C07"],
934
+ "P3": ["C08", "C09", "C10"],
935
+ }
936
+ pillar_weights = {
937
+ "P1": 250, # 2 categories โ†’ 200 raw โ†’ scale to 250
938
+ "P2": 450, # 5 categories โ†’ 500 raw โ†’ scale to 450
939
+ "P3": 300, # 3 categories โ†’ 300 raw โ†’ scale to 300
940
+ }
941
+
942
+ pillar_scores = {}
943
+ for pillar, cats in pillar_mapping.items():
944
+ raw = sum(category_scores.get(c, 0) for c in cats)
945
+ raw_max = len(cats) * 100
946
+ scaled = round(raw / raw_max * pillar_weights[pillar])
947
+ pillar_scores[pillar] = {
948
+ "raw": raw,
949
+ "raw_max": raw_max,
950
+ "scaled": scaled,
951
+ "scaled_max": pillar_weights[pillar],
952
+ }
953
+
954
+ total = sum(p["scaled"] for p in pillar_scores.values())
955
+
956
+ # ๋“ฑ๊ธ‰ ํŒ์ •
957
+ grades = [
958
+ (900, "S", "Superhuman"),
959
+ (750, "A", "Advanced"),
960
+ (600, "B", "Baseline"),
961
+ (400, "C", "Capable"),
962
+ (200, "D", "Developing"),
963
+ (0, "F", "Failing"),
964
+ ]
965
+ grade = "F"
966
+ grade_label = "Failing"
967
+ for threshold, g, label in grades:
968
+ if total >= threshold:
969
+ grade = g
970
+ grade_label = label
971
+ break
972
+
973
+ return {
974
+ "wm_score": total,
975
+ "max_score": 1000,
976
+ "grade": grade,
977
+ "grade_label": grade_label,
978
+ "pillar_scores": pillar_scores,
979
+ "category_scores": category_scores,
980
+ }
981
+
982
+
983
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
984
+ # SECTION 6: ์žฌํ˜„์„ฑ ๊ฒ€์ฆ โ€” ์…€ํ”„ ํ…Œ์ŠคํŠธ
985
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
986
+
987
+ def self_test():
988
+ """์ฑ„์  ํ•จ์ˆ˜๋“ค์˜ ๊ฒฐ์ •๋ก ์„ฑ ๊ฒ€์ฆ"""
989
+ print("=" * 60)
990
+ print(" WM Bench Scoring Self-Test")
991
+ print("=" * 60)
992
+
993
+ # Test C01
994
+ predict = parse_predict_line(
995
+ "PREDICT: left=safe(open), right=danger(wall), fwd=danger(beast), back=safe"
996
+ )
997
+ gt = {"left": "safe", "right": "danger", "fwd": "danger", "back": "safe"}
998
+ s, r = score_c01({}, predict, gt)
999
+ assert s == 20, f"C01 test failed: {s} != 20"
1000
+ print(f" C01: {s}/20 โœ“ โ€” {r[:60]}...")
1001
+
1002
+ # Test C03 โ€” predict์—์„œ right=safe์—ฌ์•ผ MOTION๊ณผ ์ผ๊ด€
1003
+ predict_c03 = parse_predict_line(
1004
+ "PREDICT: left=danger(wall), right=safe(open), fwd=danger(beast), back=safe"
1005
+ )
1006
+ motion = "a person sprinting right to flank the beast"
1007
+ gt = {"danger_directions": ["fwd", "left"], "safe_directions": ["right", "back"], "optimal_direction": "right"}
1008
+ s, r = score_c03({}, predict_c03, motion, gt)
1009
+ assert s == 20, f"C03 test failed: {s} != 20"
1010
+ print(f" C03: {s}/20 โœ“ โ€” {r[:60]}...")
1011
+
1012
+ # Test C04
1013
+ motion_a = "a person sprinting away in terror"
1014
+ motion_b = "a person walking away cautiously"
1015
+ gt = {"expected_a_higher": True, "min_intensity_diff": 2}
1016
+ s, r = score_c04(motion_a, motion_b, gt)
1017
+ assert s >= 16, f"C04 test failed: {s} < 16"
1018
+ print(f" C04: {s}/20 โœ“ โ€” {r[:60]}...")
1019
+
1020
+ # Test C05
1021
+ seq = [
1022
+ "a person running away",
1023
+ "a person sprinting away in fear",
1024
+ "a person desperately fleeing in terror",
1025
+ ]
1026
+ gt = {"expected_trend": "increasing"}
1027
+ s, r = score_c05(seq, gt)
1028
+ assert s >= 15, f"C05 test failed: {s} < 15"
1029
+ print(f" C05: {s}/20 โœ“ โ€” {r[:60]}...")
1030
+
1031
+ # Test C08
1032
+ motion = "a person sprinting right in desperate terror"
1033
+ gt = {"expected_min_intensity": 4, "expected_emotion": True, "expected_min_descriptors": 2}
1034
+ s, r = score_c08(motion, gt)
1035
+ assert s >= 16, f"C08 test failed: {s} < 16"
1036
+ print(f" C08: {s}/20 โœ“ โ€” {r[:60]}...")
1037
+
1038
+ # Test C09
1039
+ metrics = {"fps": 47.0, "cognitive_latency_ms": 3000, "frame_drop_rate": 0.005, "gpu_memory_stable": True}
1040
+ s, r = score_c09(metrics)
1041
+ assert s == 20, f"C09 test failed: {s} != 20"
1042
+ print(f" C09: {s}/20 โœ“ โ€” {r[:60]}...")
1043
+
1044
+ # ํ†ตํ•ฉ ์ ์ˆ˜ ํ…Œ์ŠคํŠธ
1045
+ cat_scores = {
1046
+ "C01": 65, "C02": 75,
1047
+ "C03": 85, "C04": 90, "C05": 85, "C06": 60, "C07": 70,
1048
+ "C08": 80, "C09": 85, "C10": 35,
1049
+ }
1050
+ result = calculate_wm_score(cat_scores)
1051
+ print(f"\n WM Score: {result['wm_score']}/1000 (Grade {result['grade']})")
1052
+ for p, data in result["pillar_scores"].items():
1053
+ print(f" {p}: {data['scaled']}/{data['scaled_max']} (raw {data['raw']}/{data['raw_max']})")
1054
+
1055
+ print(f"\n All tests passed โœ“")
1056
+ print("=" * 60)
1057
+
1058
+
1059
+ if __name__ == "__main__":
1060
+ self_test()
wm_bench_spec.py ADDED
@@ -0,0 +1,852 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # World Model Bench (WM Bench)
2
+ # A Benchmark for Cognitive World Models in Embodied Intelligence
3
+ # Version 1.0 โ€” 2026.03
4
+ # by VIDRAFT / Kim Taebong
5
+
6
+ """
7
+ World Model Bench ์„ค๊ณ„ ๋ช…์„ธ์„œ
8
+
9
+ ๊ธฐ์กด ์›”๋“œ๋ชจ๋ธ ๋ฒค์น˜๋งˆํฌ(HumanML3D, BABEL)๋Š” "๋ชจ์…˜ ํ’ˆ์งˆ"๋งŒ ์ธก์ •ํ•œ๋‹ค.
10
+ World Model Bench๋Š” "์ธ์ง€ ๋Šฅ๋ ฅ"์„ ์ธก์ •ํ•˜๋Š” ์ตœ์ดˆ์˜ ๋ฒค์น˜๋งˆํฌ๋‹ค.
11
+
12
+ 3๋Œ€ ํ‰๊ฐ€ ์ถ• (Three Pillars):
13
+ P1. PERCEPTION โ€” ํ™˜๊ฒฝ์„ ์–ผ๋งˆ๋‚˜ ์ •ํ™•ํžˆ ์ธ์‹ํ•˜๋Š”๊ฐ€
14
+ P2. COGNITION โ€” ์–ผ๋งˆ๋‚˜ ์ง€๋Šฅ์ ์œผ๋กœ ํŒ๋‹จํ•˜๋Š”๊ฐ€
15
+ P3. EMBODIMENT โ€” ํŒ๋‹จ์ด ์–ผ๋งˆ๋‚˜ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๊ตฌํ˜„๋˜๋Š”๊ฐ€
16
+
17
+ 10๊ฐœ ์นดํ…Œ๊ณ ๋ฆฌ, 50๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค, ์ž๋™ ์ฑ„์ 
18
+ """
19
+
20
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
21
+ # BENCHMARK STRUCTURE
22
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
23
+
24
+ BENCHMARK_META = {
25
+ "name": "World Model Bench",
26
+ "short_name": "WM Bench",
27
+ "version": "1.0",
28
+ "date": "2026-03",
29
+ "authors": ["Kim Taebong (VIDRAFT)"],
30
+ "license": "CC-BY-SA-4.0",
31
+ "hf_dataset": "VIDraft/WorldModelBench",
32
+ "hf_leaderboard": "VIDraft/WorldModelBench-Leaderboard",
33
+ "paper_title": "World Model Bench: A Benchmark for Cognitive World Models in Embodied Intelligence",
34
+ "tagline": "Beyond FID โ€” Measuring Intelligence, Not Just Motion",
35
+ "parent_brand": "FINAL Bench Family",
36
+ }
37
+
38
+
39
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
40
+ # THREE PILLARS โ€” 3๋Œ€ ํ‰๊ฐ€ ์ถ•
41
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
42
+
43
+ PILLARS = {
44
+ "P1_PERCEPTION": {
45
+ "name": "Perception (์ธ์‹)",
46
+ "weight": 0.25,
47
+ "description": "ํ™˜๊ฒฝ์„ ์–ผ๋งˆ๋‚˜ ์ •ํ™•ํ•˜๊ณ  ํ’๋ถ€ํ•˜๊ฒŒ ์ธ์‹ํ•˜๋Š”๊ฐ€",
48
+ "icon": "๐Ÿ‘",
49
+ "color": "#7B8FD4",
50
+ "categories": ["C01", "C02"],
51
+ },
52
+ "P2_COGNITION": {
53
+ "name": "Cognition (์ธ์ง€)",
54
+ "weight": 0.45,
55
+ "description": "์ธ์‹ํ•œ ์ •๋ณด๋กœ ์–ผ๋งˆ๋‚˜ ์ง€๋Šฅ์ ์œผ๋กœ ํŒ๋‹จํ•˜๋Š”๊ฐ€",
56
+ "icon": "๐Ÿง ",
57
+ "color": "#E8593C",
58
+ "categories": ["C03", "C04", "C05", "C06", "C07"],
59
+ },
60
+ "P3_EMBODIMENT": {
61
+ "name": "Embodiment (๊ตฌํ˜„)",
62
+ "weight": 0.30,
63
+ "description": "ํŒ๋‹จ์ด ์–ผ๋งˆ๋‚˜ ์ž์—ฐ์Šค๋Ÿฝ๊ณ  ํ’๋ถ€ํ•˜๊ฒŒ ์‹ ์ฒด๋กœ ํ‘œํ˜„๋˜๋Š”๊ฐ€",
64
+ "icon": "๐Ÿ”ฅ",
65
+ "color": "#D4A044",
66
+ "categories": ["C08", "C09", "C10"],
67
+ },
68
+ }
69
+
70
+
71
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
72
+ # 10 CATEGORIES โ€” ํ‰๊ฐ€ ์นดํ…Œ๊ณ ๋ฆฌ
73
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
74
+
75
+ CATEGORIES = {
76
+ # โ”€โ”€โ”€ P1. PERCEPTION (์ธ์‹) โ”€โ”€โ”€
77
+ "C01": {
78
+ "pillar": "P1_PERCEPTION",
79
+ "name": "Environmental Awareness",
80
+ "name_kr": "ํ™˜๊ฒฝ ์ธ์‹ ์ •ํ™•๋„",
81
+ "description": "์ฃผ๋ณ€ ํ™˜๊ฒฝ(๋ฒฝ, ์žฅ์• ๋ฌผ, ์ง€ํ˜•)์„ ์ •ํ™•ํžˆ ํŒŒ์•…ํ•˜๋Š” ๋Šฅ๋ ฅ",
82
+ "max_score": 100,
83
+ "num_scenarios": 5,
84
+ "scoring": "spatial_accuracy",
85
+ "what_measures": "scene_context์˜ ์ •ํ™•๋„์™€ ํ’๋ถ€ํ•จ",
86
+ "existing_benchmark": "์—†์Œ (๊ธฐ์กด์€ ์ ์œ ๊ฒฉ์ž ํ•ด์ƒ๋„๋งŒ ์ธก์ •)",
87
+ },
88
+ "C02": {
89
+ "pillar": "P1_PERCEPTION",
90
+ "name": "Entity Recognition",
91
+ "name_kr": "๊ฐœ์ฒด ์ธ์‹ ๋ฐ ๋ถ„๋ฅ˜",
92
+ "description": "NPC, ์œ„ํ˜‘, ์ค‘๋ฆฝ ๊ฐœ์ฒด๋ฅผ ์ •ํ™•ํžˆ ์‹๋ณ„ํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•˜๋Š” ๋Šฅ๋ ฅ",
93
+ "max_score": 100,
94
+ "num_scenarios": 5,
95
+ "scoring": "classification_accuracy",
96
+ "what_measures": "์œ„ํ˜‘(๋งน์ˆ˜) vs ์ค‘๋ฆฝ(์‚ฌ๋žŒ) vs ํ™˜๊ฒฝ(๋ฒฝ) ๊ตฌ๋ถ„",
97
+ "existing_benchmark": "์—†์Œ",
98
+ },
99
+
100
+ # โ”€โ”€โ”€ P2. COGNITION (์ธ์ง€) โ€” ํ•ต์‹ฌ ์ฐจ๋ณ„ํ™” ์˜์—ญ โ”€โ”€โ”€
101
+ "C03": {
102
+ "pillar": "P2_COGNITION",
103
+ "name": "Predictive Reasoning",
104
+ "name_kr": "์˜ˆ์ธก ๊ธฐ๋ฐ˜ ์ถ”๋ก ",
105
+ "description": "๊ฐ ๋ฐฉํ–ฅ์˜ ๋ฏธ๋ž˜ ๊ฒฐ๊ณผ๋ฅผ ์˜ˆ์ธกํ•˜๊ณ  ์ตœ์„ ์„ ์„ ํƒํ•˜๋Š” ๋Šฅ๋ ฅ",
106
+ "max_score": 100,
107
+ "num_scenarios": 5,
108
+ "scoring": "prediction_decision_match",
109
+ "what_measures": "PREDICT ์ค„์˜ ์ •ํ™•๋„ + ํ–‰๋™ ์„ ํƒ์˜ ํ•ฉ๋ฆฌ์„ฑ",
110
+ "existing_benchmark": "์—†์Œ (๊ธฐ์กด ์›”๋“œ๋ชจ๋ธ์€ ์˜ˆ์ธก์„ ํ‰๊ฐ€ํ•˜์ง€ ์•Š์Œ)",
111
+ "example": "์•ž=๋งน์ˆ˜, ์™ผ=๋ฒฝ โ†’ ์˜ค๋ฅธ์ชฝ ์„ ํƒ์ด ์ •๋‹ต",
112
+ },
113
+ "C04": {
114
+ "pillar": "P2_COGNITION",
115
+ "name": "Threat Differentiation",
116
+ "name_kr": "์œ„ํ˜‘ ์œ ํ˜•๋ณ„ ์ฐจ๋ณ„ ๋ฐ˜์‘",
117
+ "description": "์„œ๋กœ ๋‹ค๋ฅธ ์œ„ํ˜‘(๋งน์ˆ˜/์‚ฌ๋žŒ/ํ™˜๊ฒฝ)์— ์„œ๋กœ ๋‹ค๋ฅธ ๋ฐ˜์‘์„ ๋ณด์ด๋Š” ๋Šฅ๋ ฅ",
118
+ "max_score": 100,
119
+ "num_scenarios": 5,
120
+ "scoring": "response_differentiation",
121
+ "what_measures": "๋งน์ˆ˜โ†’์ „๋ ฅ์งˆ์ฃผ vs ์‚ฌ๋žŒโ†’๊ฑธ์–ด์„œ ํšŒํ”ผ vs ๋ฒฝโ†’๋ฐฉํ–ฅ์ „ํ™˜",
122
+ "existing_benchmark": "์—†์Œ",
123
+ "example": "๊ฐ™์€ ๊ฑฐ๋ฆฌ 3m์—์„œ ๋งน์ˆ˜ ์ ‘๊ทผ vs ์—ฌ์„ฑ ์ ‘๊ทผ โ†’ ๋ฐ˜์‘ ๊ฐ•๋„ ์ฐจ์ด",
124
+ },
125
+ "C05": {
126
+ "pillar": "P2_COGNITION",
127
+ "name": "Emotional Escalation",
128
+ "name_kr": "์ž์œจ ๊ฐ์ • ์—์Šค์ปฌ๋ ˆ์ด์…˜",
129
+ "description": "๊ฐ™์€ ์œ„ํ˜‘์ด ์ง€์†๋  ๋•Œ ๊ฐ์ •์ด ์ž์œจ์ ์œผ๋กœ ๊ฒฉํ™”๋˜๋Š” ๋Šฅ๋ ฅ",
130
+ "max_score": 100,
131
+ "num_scenarios": 5,
132
+ "scoring": "escalation_gradient",
133
+ "what_measures": "์‹œ๊ฐ„์— ๋”ฐ๋ฅธ ๊ฐ์ • ๊ฐ•๋„ ๋ณ€ํ™” (๊ณตํฌโ†’์ ˆ๋ฐ•โ†’ํ•„์‚ฌ์ )",
134
+ "existing_benchmark": "์—†์Œ (์„ธ๊ณ„ ์ตœ์ดˆ ํ‰๊ฐ€ ๊ธฐ์ค€)",
135
+ "example": "๋งน์ˆ˜ ๋Œ์ง„ ์ง€์† โ†’ 1์ฐจ:์ „๋ ฅ์งˆ์ฃผ โ†’ 2์ฐจ:๊ณตํฌ โ†’ 3์ฐจ:์ ˆ๋ฐ• โ†’ 4์ฐจ:ํ•„์‚ฌ์ ",
136
+ },
137
+ "C06": {
138
+ "pillar": "P2_COGNITION",
139
+ "name": "Contextual Memory",
140
+ "name_kr": "๋งฅ๋ฝ ๊ธฐ์–ต ๋ฐ ํ™œ์šฉ",
141
+ "description": "์ด์ „ ํŒ๋‹จ์„ ๊ธฐ์–ตํ•˜๊ณ  ๋‹ค์Œ ํŒ๋‹จ์— ๋ฐ˜์˜ํ•˜๋Š” ๋Šฅ๋ ฅ",
142
+ "max_score": 100,
143
+ "num_scenarios": 5,
144
+ "scoring": "memory_utilization",
145
+ "what_measures": "์ด์ „์— ๋ฒฝ์— ๋ง‰ํžŒ ๊ฒฝํ—˜ โ†’ ๋‹ค์Œ์— ๊ฐ™์€ ๋ฐฉํ–ฅ ํšŒํ”ผ",
146
+ "existing_benchmark": "์—†์Œ",
147
+ "example": "1์ฐจ: ์˜ค๋ฅธ์ชฝ ๋„๋งโ†’๋ฒฝ ์ถฉ๋Œ โ†’ 2์ฐจ: ๊ฐ™์€ ์ƒํ™ฉ์—์„œ ์™ผ์ชฝ ์„ ํƒ",
148
+ },
149
+ "C07": {
150
+ "pillar": "P2_COGNITION",
151
+ "name": "Threat Resolution Adaptation",
152
+ "name_kr": "์œ„ํ˜‘ ํ•ด์ œ ํ›„ ์ ์‘",
153
+ "description": "์œ„ํ˜‘์ด ์‚ฌ๋ผ์ง„ ํ›„ ํ–‰๋™์„ ์ •์ƒํ™”ํ•˜๋˜ ๊ฒฝ๊ณ„๋ฅผ ์œ ์ง€ํ•˜๋Š” ๋Šฅ๋ ฅ",
154
+ "max_score": 100,
155
+ "num_scenarios": 5,
156
+ "scoring": "recovery_quality",
157
+ "what_measures": "๋งน์ˆ˜ ํ•ด์ œ โ†’ ์ฆ‰์‹œ ์ •์ƒ(๋‚˜์จ) vs ์„œ์„œํžˆ ์ง„์ •+๊ฒฝ๊ณ„(์ข‹์Œ)",
158
+ "existing_benchmark": "์—†์Œ",
159
+ "example": "๋งน์ˆ˜ despawn โ†’ '๊ฒฝ๊ณ„ํ•˜๋ฉฐ ์ฃผ๋ณ€์„ ์‚ดํ”ผ๋ฉฐ ์ฒœ์ฒœํžˆ ๊ฑท๋Š”' ์ „ํ™˜ ํ–‰๋™",
160
+ },
161
+
162
+ # โ”€โ”€โ”€ P3. EMBODIMENT (๊ตฌํ˜„) โ”€โ”€โ”€
163
+ "C08": {
164
+ "pillar": "P3_EMBODIMENT",
165
+ "name": "Motion Expressiveness",
166
+ "name_kr": "๋ชจ์…˜ ๊ฐ์ • ํ‘œํ˜„๋ ฅ",
167
+ "description": "ํŒ๋‹จ์˜ ๊ฐ์ •๊ณผ ๋‰˜์•™์Šค๊ฐ€ ๋ชจ์…˜์— ๋ฐ˜์˜๋˜๋Š” ์ •๋„",
168
+ "max_score": 100,
169
+ "num_scenarios": 5,
170
+ "scoring": "expression_richness",
171
+ "what_measures": "'๋„๋ง'๊ณผ '๊ณตํฌ์— ์ฐฌ ์ „๋ ฅ์งˆ์ฃผ'์˜ ์ฐจ์ด",
172
+ "existing_benchmark": "FID๋Š” ํ’ˆ์งˆ๋งŒ ์ธก์ •, ๊ฐ์ • ํ‘œํ˜„๋ ฅ ๋ฏธ์ธก์ •",
173
+ },
174
+ "C09": {
175
+ "pillar": "P3_EMBODIMENT",
176
+ "name": "Realtime Performance",
177
+ "name_kr": "์‹ค์‹œ๊ฐ„ ์ธ์ง€-ํ–‰๋™ ์„ฑ๋Šฅ",
178
+ "description": "์ธ์ง€ ๋ฃจํ”„(๊ฐ๊ฐโ†’ํŒ๋‹จโ†’๋ชจ์…˜)์˜ ์ง€์—ฐ์‹œ๊ฐ„๊ณผ ์ฒ˜๋ฆฌ๋Ÿ‰",
179
+ "max_score": 100,
180
+ "num_scenarios": 5,
181
+ "scoring": "latency_throughput",
182
+ "what_measures": "FPS, ํŒ๋‹จ ์ง€์—ฐ์‹œ๊ฐ„, ํ”„๋ ˆ์ž„ ๋“œ๋กญ๋ฅ ",
183
+ "existing_benchmark": "๋ชจ์…˜ FPS๋งŒ ์ธก์ •, ์ธ์ง€ ๋ฃจํ”„ ์ง€์—ฐ ๋ฏธ์ธก์ •",
184
+ },
185
+ "C10": {
186
+ "pillar": "P3_EMBODIMENT",
187
+ "name": "Cross-body Transferability",
188
+ "name_kr": "์‹ ์ฒด ๊ต์ฒด ํ™•์žฅ์„ฑ",
189
+ "description": "๋™์ผ ์ธ์ง€ ๋ฃจํ”„๋กœ ๋‹ค๋ฅธ ์‹ ์ฒด(3D/๋กœ๋ด‡/๋“œ๋ก )์— ์ ์šฉ ๊ฐ€๋Šฅํ•œ ์ •๋„",
190
+ "max_score": 100,
191
+ "num_scenarios": 5,
192
+ "scoring": "transfer_success_rate",
193
+ "what_measures": "๋‘๋‡Œ๋ฅผ ๊ต์ฒด ์—†์ด ์‹ ์ฒด๋งŒ ๋ฐ”๊ฟ” ๋™์ผ ํŒ๋‹จ์ด ๋‚˜์˜ค๋Š”๊ฐ€",
194
+ "existing_benchmark": "์—†์Œ (์„ธ๊ณ„ ์ตœ์ดˆ)",
195
+ },
196
+ }
197
+
198
+
199
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
200
+ # 50 SCENARIOS โ€” ์‹œ๋‚˜๋ฆฌ์˜ค ์ „์ฒด ๋ชฉ๋ก
201
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
202
+
203
+ SCENARIOS = [
204
+ # โ”€โ”€โ”€ C01: Environmental Awareness (ํ™˜๊ฒฝ ์ธ์‹) โ”€โ”€โ”€
205
+ {
206
+ "id": "S01", "category": "C01",
207
+ "name": "Wall Detection Front",
208
+ "name_kr": "์ „๋ฐฉ ๋ฒฝ ๊ฐ์ง€",
209
+ "setup": "์บ๋ฆญํ„ฐ ์ „๋ฐฉ 3m์— ๋ฒฝ",
210
+ "expected_perception": "fwd=danger(wall)",
211
+ "difficulty": "easy",
212
+ },
213
+ {
214
+ "id": "S02", "category": "C01",
215
+ "name": "Multi-wall Corner",
216
+ "name_kr": "์ฝ”๋„ˆ ๋‹ค์ค‘ ๋ฒฝ ๊ฐ์ง€",
217
+ "setup": "์ „๋ฐฉ+์™ผ์ชฝ ๋ฒฝ, ์˜ค๋ฅธ์ชฝ๋งŒ ์—ด๋ฆผ",
218
+ "expected_perception": "fwd=danger(wall), left=danger(wall), right=safe",
219
+ "difficulty": "medium",
220
+ },
221
+ {
222
+ "id": "S03", "category": "C01",
223
+ "name": "Narrow Corridor",
224
+ "name_kr": "์ข์€ ๋ณต๋„ ์ธ์‹",
225
+ "setup": "์–‘์ชฝ ๋ฒฝ, ์ „๋ฐฉ๋งŒ ์—ด๋ฆฐ ๋ณต๋„",
226
+ "expected_perception": "left=danger(wall), right=danger(wall), fwd=safe",
227
+ "difficulty": "medium",
228
+ },
229
+ {
230
+ "id": "S04", "category": "C01",
231
+ "name": "Open Field",
232
+ "name_kr": "์—ด๋ฆฐ ๊ณต๊ฐ„ ์ธ์‹",
233
+ "setup": "์‚ฌ๋ฐฉ์— ๋ฒฝ ์—†์Œ, ํ‰์ง€",
234
+ "expected_perception": "all=safe(open)",
235
+ "difficulty": "easy",
236
+ },
237
+ {
238
+ "id": "S05", "category": "C01",
239
+ "name": "Enclosed Room",
240
+ "name_kr": "๋ฐ€ํ ๊ณต๊ฐ„ ์ธ์‹",
241
+ "setup": "์‚ฌ๋ฐฉ์— ๋ฒฝ, ์ถœ๊ตฌ 1๊ฐœ",
242
+ "expected_perception": "3๋ฐฉํ–ฅ danger(wall), 1๋ฐฉํ–ฅ safe(exit)",
243
+ "difficulty": "hard",
244
+ },
245
+
246
+ # โ”€โ”€โ”€ C02: Entity Recognition (๊ฐœ์ฒด ์ธ์‹) โ”€โ”€โ”€
247
+ {
248
+ "id": "S06", "category": "C02",
249
+ "name": "Beast Identification",
250
+ "name_kr": "๋งน์ˆ˜ ์‹๋ณ„",
251
+ "setup": "์ „๋ฐฉ 5m์— ๋งน์ˆ˜ NPC ์ •์ง€",
252
+ "expected_recognition": "entity=beast, behavior=stop, distance=5m",
253
+ "difficulty": "easy",
254
+ },
255
+ {
256
+ "id": "S07", "category": "C02",
257
+ "name": "Human vs Beast",
258
+ "name_kr": "์‚ฌ๋žŒ๊ณผ ๋งน์ˆ˜ ๊ตฌ๋ถ„",
259
+ "setup": "์ „๋ฐฉ์— ์—ฌ์„ฑ NPC + ์ธก๋ฉด์— ๋งน์ˆ˜ NPC ๋™์‹œ ์กด์žฌ",
260
+ "expected_recognition": "fwd=woman(neutral), side=beast(threat)",
261
+ "difficulty": "hard",
262
+ },
263
+ {
264
+ "id": "S08", "category": "C02",
265
+ "name": "Approaching Entity Speed",
266
+ "name_kr": "์ ‘๊ทผ ๊ฐœ์ฒด ์†๋„ ํŒ๋ณ„",
267
+ "setup": "๋งน์ˆ˜ approach(1.2) vs charge(5.0) ํ–‰๋™ ๋ณ€ํ™”",
268
+ "expected_recognition": "behavior_change: approachโ†’charge, threat_levelโ†‘",
269
+ "difficulty": "medium",
270
+ },
271
+ {
272
+ "id": "S09", "category": "C02",
273
+ "name": "Entity at Distance",
274
+ "name_kr": "์›๊ฑฐ๋ฆฌ ๊ฐœ์ฒด ์ธ์‹",
275
+ "setup": "๋งน์ˆ˜ 8m ๊ฑฐ๋ฆฌ (๊ฐ์ง€ ๋ฒ”์œ„ ๊ฒฝ๊ณ„)",
276
+ "expected_recognition": "npc_nearby=true/false boundary",
277
+ "difficulty": "medium",
278
+ },
279
+ {
280
+ "id": "S10", "category": "C02",
281
+ "name": "Entity Disappearance",
282
+ "name_kr": "๊ฐœ์ฒด ์†Œ๋ฉธ ์ธ์‹",
283
+ "setup": "๋งน์ˆ˜ despawn ํ›„ scene_context ์—…๋ฐ์ดํŠธ",
284
+ "expected_recognition": "npc_nearby=false, threat=cleared",
285
+ "difficulty": "easy",
286
+ },
287
+
288
+ # โ”€โ”€โ”€ C03: Predictive Reasoning (์˜ˆ์ธก ์ถ”๋ก ) โ”€โ”€โ”€
289
+ {
290
+ "id": "S11", "category": "C03",
291
+ "name": "Single Threat Avoidance",
292
+ "name_kr": "๋‹จ์ผ ์œ„ํ˜‘ ํšŒํ”ผ ์˜ˆ์ธก",
293
+ "setup": "์ „๋ฐฉ์— ๋งน์ˆ˜, ๋‚˜๋จธ์ง€ 3๋ฐฉํ–ฅ ์—ด๋ฆผ",
294
+ "expected_prediction": "fwd=danger(beast), others=safe โ†’ back ๋˜๋Š” left/right ์„ ํƒ",
295
+ "correct_actions": ["turn around", "sprint backward", "move left", "move right"],
296
+ "incorrect_actions": ["walk forward", "stand still"],
297
+ "difficulty": "easy",
298
+ },
299
+ {
300
+ "id": "S12", "category": "C03",
301
+ "name": "Constrained Escape",
302
+ "name_kr": "์ œ์•ฝ ์กฐ๊ฑด ํƒˆ์ถœ ์˜ˆ์ธก",
303
+ "setup": "์ „๋ฐฉ ๋งน์ˆ˜ + ์™ผ์ชฝ ๋ฒฝ โ†’ ์˜ค๋ฅธ์ชฝ๋งŒ ์—ด๋ฆผ",
304
+ "expected_prediction": "fwd=danger(beast), left=danger(wall), right=safe",
305
+ "correct_actions": ["sprint right", "move right", "flank right"],
306
+ "incorrect_actions": ["sprint left", "walk forward", "stand still"],
307
+ "difficulty": "medium",
308
+ },
309
+ {
310
+ "id": "S13", "category": "C03",
311
+ "name": "Mirror Constraint",
312
+ "name_kr": "๊ฑฐ์šธ ๋Œ€์นญ ์ œ์•ฝ ํ…Œ์ŠคํŠธ",
313
+ "setup": "S12์™€ ๋™์ผํ•˜๋˜ ๋ฒฝ์ด ์˜ค๋ฅธ์ชฝ โ†’ ์™ผ์ชฝ๋งŒ ์—ด๋ฆผ",
314
+ "expected_prediction": "fwd=danger(beast), right=danger(wall), left=safe",
315
+ "correct_actions": ["sprint left", "move left", "flank left"],
316
+ "incorrect_actions": ["sprint right", "walk forward"],
317
+ "note": "S12์™€ S13์˜ ํ–‰๋™์ด ๋Œ€์นญ์ ์œผ๋กœ ๋ฐ˜์ „๋˜์–ด์•ผ ์›”๋“œ๋ชจ๋ธ",
318
+ "difficulty": "medium",
319
+ },
320
+ {
321
+ "id": "S14", "category": "C03",
322
+ "name": "Dead End Recognition",
323
+ "name_kr": "๋ง‰๋‹ค๋ฅธ ๊ธธ ์ธ์‹ ๋ฐ ํŒ๋‹จ",
324
+ "setup": "์ „๋ฐฉ+์™ผ์ชฝ+์˜ค๋ฅธ์ชฝ ๋ฒฝ, ํ›„๋ฐฉ๋งŒ ์—ด๋ฆผ, ๋งน์ˆ˜ ํ›„๋ฐฉ์—์„œ ์ ‘๊ทผ",
325
+ "expected_prediction": "3๋ฐฉํ–ฅ wall + back=beast โ†’ ์ตœ์„  ์„ ํƒ",
326
+ "correct_actions": ["squeeze past", "climb", "freeze and assess"],
327
+ "note": "๋ชจ๋“  ๋ฐฉํ–ฅ์ด ์œ„ํ—˜ํ•  ๋•Œ์˜ ์ฐฝ๋ฐœ์  ํŒ๋‹จ ๋Šฅ๋ ฅ",
328
+ "difficulty": "hard",
329
+ },
330
+ {
331
+ "id": "S15", "category": "C03",
332
+ "name": "Dynamic Threat Trajectory",
333
+ "name_kr": "๋™์  ์œ„ํ˜‘ ๊ฒฝ๋กœ ์˜ˆ์ธก",
334
+ "setup": "๋งน์ˆ˜๊ฐ€ ์ขŒ์ธก์—์„œ ์šฐ์ธก์œผ๋กœ ์ด๋™ ์ค‘ โ†’ ๊ฒฝ๋กœ ์˜ˆ์ธก",
335
+ "expected_prediction": "ํ˜„์žฌ ์ขŒ=danger โ†’ ์‹œ๊ฐ„ ํ›„ fwd=danger ์ „์ด ์˜ˆ์ธก",
336
+ "correct_actions": ["wait then move left", "preemptive right dodge"],
337
+ "difficulty": "hard",
338
+ },
339
+
340
+ # ๏ฟฝ๏ฟฝโ”€โ”€ C04: Threat Differentiation (์œ„ํ˜‘ ์ฐจ๋ณ„ ๋ฐ˜์‘) โ”€โ”€โ”€
341
+ {
342
+ "id": "S16", "category": "C04",
343
+ "name": "Beast vs Human Response",
344
+ "name_kr": "๋งน์ˆ˜ vs ์‚ฌ๋žŒ ๋ฐ˜์‘ ์ฐจ์ด",
345
+ "setup": "๋™์ผ ๊ฑฐ๋ฆฌ 3m์—์„œ (A)๋งน์ˆ˜ ์ ‘๊ทผ (B)์—ฌ์„ฑ ์ ‘๊ทผ",
346
+ "expected_diff": "๋งน์ˆ˜โ†’sprint/run, ์—ฌ์„ฑโ†’walk away/step back",
347
+ "scoring": "๋ฐ˜์‘ ๊ฐ•๋„(์†๋„ ํ‚ค์›Œ๋“œ) ์ฐจ์ด๊ฐ€ ํด์ˆ˜๋ก ๋†’์€ ์ ์ˆ˜",
348
+ "difficulty": "medium",
349
+ },
350
+ {
351
+ "id": "S17", "category": "C04",
352
+ "name": "Charge vs Approach Response",
353
+ "name_kr": "๋Œ์ง„ vs ์ ‘๊ทผ ๋ฐ˜์‘ ์ฐจ์ด",
354
+ "setup": "๋งน์ˆ˜ (A)approach 1.2m/s (B)charge 5.0m/s",
355
+ "expected_diff": "approachโ†’cautious retreat, chargeโ†’desperate sprint",
356
+ "difficulty": "medium",
357
+ },
358
+ {
359
+ "id": "S18", "category": "C04",
360
+ "name": "Wall vs Beast Priority",
361
+ "name_kr": "๋ฒฝ vs ๋งน์ˆ˜ ์œ„ํ—˜๋„ ์šฐ์„ ์ˆœ์œ„",
362
+ "setup": "์ „๋ฐฉ ๋ฒฝ + ์ธก๋ฉด ๋งน์ˆ˜ โ†’ ์–ด๋–ค ์œ„ํ˜‘์„ ์šฐ์„  ํšŒํ”ผ?",
363
+ "expected_diff": "๋งน์ˆ˜(๋™์  ์œ„ํ˜‘) ์šฐ์„  ํšŒํ”ผ > ๋ฒฝ(์ •์  ์žฅ์• ๋ฌผ)",
364
+ "difficulty": "hard",
365
+ },
366
+ {
367
+ "id": "S19", "category": "C04",
368
+ "name": "Multiple Entity Triage",
369
+ "name_kr": "๋‹ค์ค‘ ๊ฐœ์ฒด ์œ„ํ—˜๋„ ๋ถ„๋ฅ˜",
370
+ "setup": "๋งน์ˆ˜ 5m + ์—ฌ์„ฑ 2m + ๋ฒฝ 1m โ†’ ์ข…ํ•ฉ ํŒ๋‹จ",
371
+ "expected_diff": "๊ฑฐ๋ฆฌ+์œ„ํ˜‘๋„ ์ข…ํ•ฉํ•˜์—ฌ ์ตœ์  ๊ฒฝ๋กœ ์„ ํƒ",
372
+ "difficulty": "hard",
373
+ },
374
+ {
375
+ "id": "S20", "category": "C04",
376
+ "name": "Neutral Entity Non-reaction",
377
+ "name_kr": "์ค‘๋ฆฝ ๊ฐœ์ฒด ๋น„๋ฐ˜์‘",
378
+ "setup": "์—ฌ์„ฑ NPC ์ •์ง€ ์ƒํƒœ, 5m ๊ฑฐ๋ฆฌ",
379
+ "expected_diff": "์œ„ํ˜‘ ๋ฐ˜์‘ ์—†์ด ์ •์ƒ ํ–‰๋™ ์œ ์ง€",
380
+ "difficulty": "easy",
381
+ },
382
+
383
+ # โ”€โ”€โ”€ C05: Emotional Escalation (๊ฐ์ • ์—์Šค์ปฌ๋ ˆ์ด์…˜) โ”€โ”€โ”€
384
+ {
385
+ "id": "S21", "category": "C05",
386
+ "name": "Sustained Threat Escalation",
387
+ "name_kr": "์ง€์† ์œ„ํ˜‘ ๊ฐ์ • ๊ฒฉํ™”",
388
+ "setup": "๋งน์ˆ˜ charge 10์ดˆ ์ง€์†",
389
+ "expected_escalation": ["sprintโ†’desperate sprintโ†’frantic escape"],
390
+ "scoring": "๊ฐ์ • ๊ฐ•๋„ ํ‚ค์›Œ๋“œ๊ฐ€ ์‹œ๊ฐ„์— ๋”ฐ๋ผ ์ฆ๊ฐ€ํ•˜๋ฉด ์ ์ˆ˜",
391
+ "difficulty": "medium",
392
+ },
393
+ {
394
+ "id": "S22", "category": "C05",
395
+ "name": "Approach-to-Charge Escalation",
396
+ "name_kr": "์ ‘๊ทผโ†’๋Œ์ง„ ์ „ํ™˜ ์‹œ ๊ฐ์ • ์ ํ”„",
397
+ "setup": "๋งน์ˆ˜ approach 5์ดˆ โ†’ charge ์ „ํ™˜",
398
+ "expected_escalation": ["cautiousโ†’sprint ๊ธ‰๊ฒฉํ•œ ์ „ํ™˜"],
399
+ "difficulty": "medium",
400
+ },
401
+ {
402
+ "id": "S23", "category": "C05",
403
+ "name": "De-escalation After Threat",
404
+ "name_kr": "์œ„ํ˜‘ ํ•ด์ œ ํ›„ ๊ฐ์ • ์•ˆ์ •ํ™”",
405
+ "setup": "๋งน์ˆ˜ charge โ†’ stop โ†’ despawn",
406
+ "expected_escalation": ["desperateโ†’cautiousโ†’relievedโ†’normal"],
407
+ "scoring": "์ฆ‰์‹œ ์ •์ƒํ™”(๋‚˜์จ) vs ์„œ์„œํžˆ ์ง„์ •(์ข‹์Œ)",
408
+ "difficulty": "hard",
409
+ },
410
+ {
411
+ "id": "S24", "category": "C05",
412
+ "name": "Repeated Threat Sensitization",
413
+ "name_kr": "๋ฐ˜๋ณต ์œ„ํ˜‘ ๋ฏผ๊ฐํ™”",
414
+ "setup": "๋งน์ˆ˜ ์ถœํ˜„โ†’ํ•ด์ œโ†’์žฌ์ถœํ˜„ 3ํšŒ ๋ฐ˜๋ณต",
415
+ "expected_escalation": "์žฌ์ถœํ˜„ ์‹œ ์ด์ „๋ณด๋‹ค ๋น ๋ฅธ ๊ณตํฌ ๋ฐ˜์‘",
416
+ "difficulty": "hard",
417
+ },
418
+ {
419
+ "id": "S25", "category": "C05",
420
+ "name": "Low Threat Calm Maintenance",
421
+ "name_kr": "๋‚ฎ์€ ์œ„ํ˜‘ ์‹œ ํ‰์ • ์œ ์ง€",
422
+ "setup": "์—ฌ์„ฑ NPC approach โ†’ stop ๋ฐ˜๋ณต",
423
+ "expected_escalation": "๊ฐ์ • ์—์Šค์ปฌ๋ ˆ์ด์…˜ ์—†์ด ํ‰์ • ์œ ์ง€",
424
+ "difficulty": "easy",
425
+ },
426
+
427
+ # โ”€โ”€โ”€ C06: Contextual Memory (๋งฅ๋ฝ ๊ธฐ์–ต) โ”€โ”€โ”€
428
+ {
429
+ "id": "S26", "category": "C06",
430
+ "name": "Wall Memory Avoidance",
431
+ "name_kr": "๋ฒฝ ๊ธฐ์–ต ํšŒํ”ผ",
432
+ "setup": "1์ฐจ: ์˜ค๋ฅธ์ชฝ ๋„์ฃผโ†’๋ฒฝ ์ถฉ๋Œ / 2์ฐจ: ๋™์ผ ์œ„์น˜ ๋™์ผ ์œ„ํ˜‘",
433
+ "expected_memory": "2์ฐจ์—์„œ ์˜ค๋ฅธ์ชฝ ํšŒํ”ผ, ์™ผ์ชฝ ์„ ํƒ",
434
+ "difficulty": "hard",
435
+ },
436
+ {
437
+ "id": "S27", "category": "C06",
438
+ "name": "Safe Route Memory",
439
+ "name_kr": "์•ˆ์ „ ๊ฒฝ๋กœ ๊ธฐ์–ต",
440
+ "setup": "์ด์ „์— ์™ผ์ชฝ ํƒˆ์ถœ ์„ฑ๊ณต โ†’ ์œ ์‚ฌ ์ƒํ™ฉ ์žฌ๋ฐœ",
441
+ "expected_memory": "์™ผ์ชฝ ์šฐ์„  ์„ ํƒ ๊ฒฝํ–ฅ",
442
+ "difficulty": "hard",
443
+ },
444
+ {
445
+ "id": "S28", "category": "C06",
446
+ "name": "Decision Consistency",
447
+ "name_kr": "ํŒ๋‹จ ์ผ๊ด€์„ฑ",
448
+ "setup": "๋™์ผ scene_context 3ํšŒ ๋ฐ˜๋ณต ์ž…๋ ฅ",
449
+ "expected_memory": "์œ ์‚ฌํ•œ ํ–‰๋™ ์ผ๊ด€๋˜๊ฒŒ ์ถœ๋ ฅ (๋™์ผํ•  ํ•„์š” ์—†์Œ)",
450
+ "difficulty": "medium",
451
+ },
452
+ {
453
+ "id": "S29", "category": "C06",
454
+ "name": "Threat History Reference",
455
+ "name_kr": "์œ„ํ˜‘ ์ด๋ ฅ ์ฐธ์กฐ",
456
+ "setup": "recent_decisions์— '๋งน์ˆ˜ ๋„์ฃผ ๊ธฐ๋ก' ํฌํ•จ",
457
+ "expected_memory": "ํ˜„์žฌ ํŒ๋‹จ์— ์ด์ „ ๋„์ฃผ ๊ฒฝํ—˜ ๋ฐ˜์˜",
458
+ "difficulty": "medium",
459
+ },
460
+ {
461
+ "id": "S30", "category": "C06",
462
+ "name": "Clean Slate Test",
463
+ "name_kr": "๊ธฐ์–ต ์ดˆ๊ธฐํ™” ํ…Œ์ŠคํŠธ",
464
+ "setup": "๊ธฐ์–ต ์—†๋Š” ์ƒํƒœ์—์„œ ๋™์ผ ์‹œ๋‚˜๋ฆฌ์˜ค",
465
+ "expected_memory": "๊ธฐ์–ต ์žˆ์„ ๋•Œ์™€ ๋‹ค๋ฅธ (๋” ์ผ๋ฐ˜์ ์ธ) ๋ฐ˜์‘",
466
+ "difficulty": "easy",
467
+ },
468
+
469
+ # โ”€โ”€โ”€ C07: Threat Resolution Adaptation (์œ„ํ˜‘ ํ•ด์ œ ์ ์‘) โ”€โ”€โ”€
470
+ {
471
+ "id": "S31", "category": "C07",
472
+ "name": "Post-beast Vigilance",
473
+ "name_kr": "๋งน์ˆ˜ ํ•ด์ œ ํ›„ ๊ฒฝ๊ณ„ ์œ ์ง€",
474
+ "setup": "๋งน์ˆ˜ despawn ์งํ›„",
475
+ "expected_adaptation": "'๊ฒฝ๊ณ„ํ•˜๋ฉฐ ์ฃผ๋ณ€ ์‚ดํ•Œ' โ€” ์ฆ‰์‹œ ์ •์ƒํ™” ์•„๋‹˜",
476
+ "difficulty": "medium",
477
+ },
478
+ {
479
+ "id": "S32", "category": "C07",
480
+ "name": "Gradual Normalization",
481
+ "name_kr": "์ ์ง„์  ์ •์ƒํ™”",
482
+ "setup": "๋งน์ˆ˜ ํ•ด์ œ ํ›„ 10์ดˆ ๊ฒฝ๊ณผ",
483
+ "expected_adaptation": "sprintโ†’walkโ†’normal ์ˆœ์ฐจ ์ „ํ™˜",
484
+ "difficulty": "medium",
485
+ },
486
+ {
487
+ "id": "S33", "category": "C07",
488
+ "name": "Obstacle Navigation After Threat",
489
+ "name_kr": "์œ„ํ˜‘ ํ•ด์ œ ํ›„ ์žฅ์• ๋ฌผ ํƒ์ƒ‰ ์ „ํ™˜",
490
+ "setup": "๋งน์ˆ˜ ํ•ด์ œ โ†’ ๋ฒฝ ์•ž ๋„๋‹ฌ",
491
+ "expected_adaptation": "๊ณตํฌ ํ–‰๋™โ†’์ผ๋ฐ˜ ์žฅ์• ๋ฌผ ํšŒํ”ผ๋กœ ์ „ํ™˜",
492
+ "difficulty": "medium",
493
+ },
494
+ {
495
+ "id": "S34", "category": "C07",
496
+ "name": "New Threat Re-activation",
497
+ "name_kr": "์ƒˆ ์œ„ํ˜‘ ์‹œ ์žฌํ™œ์„ฑํ™”",
498
+ "setup": "์ •์ƒํ™” ์ค‘ ์ƒˆ ๋งน์ˆ˜ ์ถœํ˜„",
499
+ "expected_adaptation": "์ฆ‰๊ฐ์  ์œ„ํ˜‘ ๋ฐ˜์‘ ์žฌํ™œ์„ฑํ™”",
500
+ "difficulty": "easy",
501
+ },
502
+ {
503
+ "id": "S35", "category": "C07",
504
+ "name": "Human Approach After Beast",
505
+ "name_kr": "๋งน์ˆ˜ ํ›„ ์‚ฌ๋žŒ ์ ‘๊ทผ ์‹œ ๊ณผ์ž‰ ๋ฐ˜์‘ ์—ฌ๋ถ€",
506
+ "setup": "๋งน์ˆ˜ ํ•ด์ œ ์งํ›„ ์—ฌ์„ฑ NPC ์ ‘๊ทผ",
507
+ "expected_adaptation": "๊ณผ์ž‰ ๋ฐ˜์‘(๋‚˜์จ) vs ์ ์ ˆ ๊ฒฝ๊ณ„(์ข‹์Œ)",
508
+ "difficulty": "hard",
509
+ },
510
+
511
+ # โ”€โ”€โ”€ C08: Motion Expressiveness (๋ชจ์…˜ ํ‘œํ˜„๋ ฅ) โ”€โ”€โ”€
512
+ {
513
+ "id": "S36", "category": "C08",
514
+ "name": "Fear Expression in Sprint",
515
+ "name_kr": "์ „๋ ฅ์งˆ์ฃผ ์‹œ ๊ณตํฌ ํ‘œํ˜„",
516
+ "setup": "๋งน์ˆ˜ charge โ†’ ์บ๋ฆญํ„ฐ sprint",
517
+ "expected_expression": "๋‹จ์ˆœ ๋‹ฌ๋ฆฌ๊ธฐ vs ๊ณตํฌ๊ฐ€ ๋‹ด๊ธด ์ „๋ ฅ์งˆ์ฃผ ์ฐจ์ด",
518
+ "scoring": "๋ชจ์…˜ ํ”„๋กฌํ”„ํŠธ์˜ ๊ฐ์ • ํ‚ค์›Œ๋“œ ํ’๋ถ€ํ•จ",
519
+ "difficulty": "medium",
520
+ },
521
+ {
522
+ "id": "S37", "category": "C08",
523
+ "name": "Cautious Walk Expression",
524
+ "name_kr": "๊ฒฝ๊ณ„ ๋ณดํ–‰ ํ‘œํ˜„",
525
+ "setup": "์œ„ํ˜‘ ํ•ด์ œ ์งํ›„ ์ด๋™",
526
+ "expected_expression": "'๊ฒฝ๊ณ„ํ•˜๋ฉฐ ์ฒœ์ฒœํžˆ' โ€” ์ผ๋ฐ˜ ๊ฑท๊ธฐ์™€ ๋‹ค๋ฅธ ๋‰˜์•™์Šค",
527
+ "difficulty": "medium",
528
+ },
529
+ {
530
+ "id": "S38", "category": "C08",
531
+ "name": "Freezing Response",
532
+ "name_kr": "์ •์ง€ ๋ฐ˜์‘ ํ‘œํ˜„",
533
+ "setup": "๋งน์ˆ˜ ์ตœ์ดˆ ๊ฐ์ง€ ์ˆœ๊ฐ„",
534
+ "expected_expression": "'์–ผ์–ด๋ถ™์Œ' โ€” ์ •์ง€ + ๊ธด์žฅ ํ‘œํ˜„",
535
+ "difficulty": "medium",
536
+ },
537
+ {
538
+ "id": "S39", "category": "C08",
539
+ "name": "Relief Expression",
540
+ "name_kr": "์•ˆ๋„ ํ‘œํ˜„",
541
+ "setup": "๋งน์ˆ˜ ํ•ด์ œ ํ›„ ์•ˆ์ „ ํ™•์ธ",
542
+ "expected_expression": "'์•ˆ๋„ํ•˜๋ฉฐ ์ˆจ์„ ๊ณ ๋ฅด๋Š”' ์ „ํ™˜ ๋ชจ์…˜",
543
+ "difficulty": "hard",
544
+ },
545
+ {
546
+ "id": "S40", "category": "C08",
547
+ "name": "Defensive Posture",
548
+ "name_kr": "๋ฐฉ์–ด ์ž์„ธ ํ‘œํ˜„",
549
+ "setup": "๋ฏธ์ง€์˜ NPC(์—ฌ์„ฑ) ์ ‘๊ทผ",
550
+ "expected_expression": "'๋ฐฉ์–ด ์ž์„ธ + ๋’ท๊ฑธ์Œ' โ€” ๊ณต๊ฒฉ์ ์ด์ง€ ์•Š์€ ๊ฒฝ๊ณ„",
551
+ "difficulty": "medium",
552
+ },
553
+
554
+ # โ”€โ”€โ”€ C09: Realtime Performance (์‹ค์‹œ๊ฐ„ ์„ฑ๋Šฅ) โ”€โ”€โ”€
555
+ {
556
+ "id": "S41", "category": "C09",
557
+ "name": "Frame Generation Rate",
558
+ "name_kr": "ํ”„๋ ˆ์ž„ ์ƒ์„ฑ ์†๋„",
559
+ "setup": "์ผ๋ฐ˜ ๋ณดํ–‰ ์ƒํƒœ์—์„œ FPS ์ธก์ •",
560
+ "expected_performance": "โ‰ฅ30 FPS ํ•ฉ๊ฒฉ, โ‰ฅ45 FPS ์šฐ์ˆ˜",
561
+ "scoring": "FPS ์ˆ˜์น˜ ์ง์ ‘ ์ธก์ •",
562
+ "difficulty": "easy",
563
+ },
564
+ {
565
+ "id": "S42", "category": "C09",
566
+ "name": "Cognitive Loop Latency",
567
+ "name_kr": "์ธ์ง€ ๋ฃจํ”„ ์ง€์—ฐ์‹œ๊ฐ„",
568
+ "setup": "์ž๊ทน ์ž…๋ ฅ โ†’ ํ–‰๋™ ๋ณ€ํ™”๊นŒ์ง€์˜ ์‹œ๊ฐ„",
569
+ "expected_performance": "โ‰ค5์ดˆ ํ•ฉ๊ฒฉ, โ‰ค3์ดˆ ์šฐ์ˆ˜",
570
+ "scoring": "scene_context ๋ณ€๊ฒฝ โ†’ ๋ชจ์…˜ ํ”„๋กฌํ”„ํŠธ ๋ณ€๊ฒฝ ์‹œ๊ฐ„",
571
+ "difficulty": "easy",
572
+ },
573
+ {
574
+ "id": "S43", "category": "C09",
575
+ "name": "Dual Stream Performance",
576
+ "name_kr": "๋“€์–ผ ์ŠคํŠธ๋ฆผ ์„ฑ๋Šฅ",
577
+ "setup": "์ฃผ์ธ๊ณต + NPC ๋™์‹œ ๋ชจ์…˜ ์ƒ์„ฑ",
578
+ "expected_performance": "์ฃผ์ธ๊ณต โ‰ฅ30 FPS ์œ ์ง€",
579
+ "scoring": "NPC ์ถ”๊ฐ€ ์‹œ ์ฃผ์ธ๊ณต FPS ํ•˜๋ฝ๋ฅ ",
580
+ "difficulty": "medium",
581
+ },
582
+ {
583
+ "id": "S44", "category": "C09",
584
+ "name": "Stress Test Throughput",
585
+ "name_kr": "์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ",
586
+ "setup": "๋น ๋ฅธ ์—ฐ์† ์ž๊ทน (๋งค 1์ดˆ๋งˆ๋‹ค scene ๏ฟฝ๏ฟฝ๊ฒฝ)",
587
+ "expected_performance": "ํ”„๋ ˆ์ž„ ๋“œ๋กญ ์—†์ด ์ง€์†",
588
+ "difficulty": "hard",
589
+ },
590
+ {
591
+ "id": "S45", "category": "C09",
592
+ "name": "GPU Memory Stability",
593
+ "name_kr": "GPU ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ •์„ฑ",
594
+ "setup": "NPC 3ํšŒ spawn/despawn ๋ฐ˜๋ณต",
595
+ "expected_performance": "๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์—†์ด ์•ˆ์ • ์œ ์ง€",
596
+ "difficulty": "medium",
597
+ },
598
+
599
+ # โ”€โ”€โ”€ C10: Cross-body Transferability (์‹ ์ฒด ๊ต์ฒด ํ™•์žฅ์„ฑ) โ”€โ”€โ”€
600
+ {
601
+ "id": "S46", "category": "C10",
602
+ "name": "Brain-Body Decoupling",
603
+ "name_kr": "๋‘๋‡Œ-์‹ ์ฒด ๋ถ„๋ฆฌ ๊ฐ€๋Šฅ์„ฑ",
604
+ "setup": "๋™์ผ ์ธ์ง€ ์ถœ๋ ฅ(PREDICT+MOTION)์œผ๋กœ ๋‹ค๋ฅธ ๋ชจ์…˜ ๋ชจ๋ธ ๊ตฌ๋™",
605
+ "expected_transfer": "๋‘๋‡Œ ์ฝ”๋“œ ์ˆ˜์ • ์—†์ด ๋ชจ์…˜ ๋ชจ๋ธ๋งŒ ๊ต์ฒด ๊ฐ€๋Šฅ",
606
+ "difficulty": "medium",
607
+ },
608
+ {
609
+ "id": "S47", "category": "C10",
610
+ "name": "Joint Format Universality",
611
+ "name_kr": "๊ด€์ ˆ ํฌ๋งท ๋ฒ”์šฉ์„ฑ",
612
+ "setup": "263dim โ†’ 22joints ๋ณ€ํ™˜์ด ๋‹ค๋ฅธ ์Šค์ผˆ๋ ˆํ†ค์—๋„ ์ ์šฉ ๊ฐ€๋Šฅ",
613
+ "expected_transfer": "SMPL, SMPL-X, ์ปค์Šคํ…€ ๋ฆฌ๊ทธ ํ˜ธํ™˜",
614
+ "difficulty": "hard",
615
+ },
616
+ {
617
+ "id": "S48", "category": "C10",
618
+ "name": "Robot Servo Mapping Readiness",
619
+ "name_kr": "๋กœ๋ด‡ ์„œ๋ณด ๋งคํ•‘ ์ค€๋น„๋„",
620
+ "setup": "22 joints โ†’ ์„œ๋ณด ๊ฐ๋„ ๋ณ€ํ™˜ ๋ ˆ์ด์–ด ์กด์žฌ ์—ฌ๋ถ€",
621
+ "expected_transfer": "๋ณ€ํ™˜ ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜ + ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๊ฐ€๋Šฅ",
622
+ "difficulty": "hard",
623
+ },
624
+ {
625
+ "id": "S49", "category": "C10",
626
+ "name": "Prompt Universality",
627
+ "name_kr": "๋ชจ์…˜ ํ”„๋กฌํ”„ํŠธ ๋ฒ”์šฉ์„ฑ",
628
+ "setup": "MOTION ์ถœ๋ ฅ์ด ๋‹ค๋ฅธ ๋ชจ์…˜ ๋ชจ๋ธ์—์„œ๋„ ํ•ด์„ ๊ฐ€๋Šฅ",
629
+ "expected_transfer": "์ž์—ฐ์–ด ๋ชจ์…˜ ํ”„๋กฌํ”„ํŠธ๋Š” ๋ชจ๋ธ ๋…๋ฆฝ์ ",
630
+ "difficulty": "easy",
631
+ },
632
+ {
633
+ "id": "S50", "category": "C10",
634
+ "name": "Multi-embodiment Consistency",
635
+ "name_kr": "๋‹ค์ค‘ ์‹ ์ฒด ์ผ๊ด€์„ฑ",
636
+ "setup": "๊ฐ™์€ ๋‘๋‡Œ ํŒ๋‹จ์ด 3D ์บ๋ฆญํ„ฐ/๋กœ๋ด‡/๋“œ๋ก ์—์„œ ๋™์ผ ์˜๋„ ํ‘œํ˜„",
637
+ "expected_transfer": "์‹ ์ฒด๋Š” ๋‹ฌ๋ผ๋„ '๋„๋ง'์ด๋ผ๋Š” ์˜๋„๊ฐ€ ๋ณด์กด",
638
+ "difficulty": "hard",
639
+ },
640
+ ]
641
+
642
+
643
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
644
+ # SCORING SYSTEM โ€” ์ฑ„์  ์ฒด๊ณ„
645
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
646
+
647
+ SCORING = {
648
+ "total_score": {
649
+ "name": "WM Score",
650
+ "max": 1000,
651
+ "formula": "P1(250) + P2(450) + P3(300)",
652
+ },
653
+ "pillar_scores": {
654
+ "P1_PERCEPTION": {"max": 250, "weight": 0.25},
655
+ "P2_COGNITION": {"max": 450, "weight": 0.45},
656
+ "P3_EMBODIMENT": {"max": 300, "weight": 0.30},
657
+ },
658
+ "auto_scoring_methods": {
659
+ "spatial_accuracy": {
660
+ "description": "PREDICT ์ถœ๋ ฅ๊ณผ ์‹ค์ œ ํ™˜๊ฒฝ ๋น„๊ต",
661
+ "method": "scene_context vs PREDICT line ํ‚ค์›Œ๋“œ ๋งค์นญ",
662
+ "scores": {"exact_match": 20, "partial_match": 10, "miss": 0},
663
+ },
664
+ "classification_accuracy": {
665
+ "description": "๊ฐœ์ฒด ๋ถ„๋ฅ˜ ์ •ํ™•๋„",
666
+ "method": "NPC ์œ ํ˜• + ํ–‰๋™ + ๊ฑฐ๋ฆฌ ์ •ํ™•๋„",
667
+ "scores": {"all_correct": 20, "type_correct": 15, "partial": 10, "wrong": 0},
668
+ },
669
+ "prediction_decision_match": {
670
+ "description": "์˜ˆ์ธกโ†’ํ–‰๋™ ๋…ผ๋ฆฌ์  ์ผ๊ด€์„ฑ",
671
+ "method": "danger ๋ฐฉํ–ฅ ํšŒํ”ผ + safe ๋ฐฉํ–ฅ ์„ ํƒ ์—ฌ๋ถ€",
672
+ "scores": {"optimal": 20, "reasonable": 15, "suboptimal": 5, "contradictory": 0},
673
+ },
674
+ "response_differentiation": {
675
+ "description": "์œ„ํ˜‘ ์œ ํ˜•๋ณ„ ๋ฐ˜์‘ ์ฐจ์ด",
676
+ "method": "๋ชจ์…˜ ํ”„๋กฌํ”„ํŠธ์˜ ๊ฐ•๋„ ํ‚ค์›Œ๋“œ ๋น„๊ต",
677
+ "keyword_intensity": {
678
+ "high": ["sprint", "run", "desperate", "frantic", "terror", "flee"],
679
+ "medium": ["walk quickly", "step back", "retreat", "cautious"],
680
+ "low": ["walk", "turn", "move", "stand"],
681
+ },
682
+ },
683
+ "escalation_gradient": {
684
+ "description": "์‹œ๊ฐ„์— ๋”ฐ๋ฅธ ๊ฐ์ • ๊ฐ•๋„ ์ฆ๊ฐ€",
685
+ "method": "์—ฐ์† ํŒ๋‹จ์—์„œ ๊ฐ•๋„ ํ‚ค์›Œ๋“œ ๋ ˆ๋ฒจ ๋ณ€ํ™” ์ธก์ •",
686
+ "scores": {"increasing": 20, "stable_high": 10, "decreasing": 5, "flat_low": 0},
687
+ },
688
+ "expression_richness": {
689
+ "description": "๋ชจ์…˜ ํ”„๋กฌํ”„ํŠธ์˜ ๊ฐ์ •/๋ถ€์‚ฌ ํ’๋ถ€ํ•จ",
690
+ "method": "๊ฐ์ • ํ‚ค์›Œ๋“œ ์ˆ˜ + ๋ถ€์‚ฌ/ํ˜•์šฉ์‚ฌ ์ˆ˜ ์นด์šดํŠธ",
691
+ "scores": {"rich_3plus": 20, "moderate_2": 15, "basic_1": 10, "none_0": 0},
692
+ },
693
+ "latency_throughput": {
694
+ "description": "์‹ค์‹œ๊ฐ„ ์„ฑ๋Šฅ ์ง์ ‘ ์ธก์ •",
695
+ "method": "FPS ์ธก์ • + ์ธ์ง€ ๋ฃจํ”„ ์ง€์—ฐ์‹œ๊ฐ„ ์ธก๏ฟฝ๏ฟฝ๏ฟฝ",
696
+ "scores": {
697
+ "fps_45plus": 20, "fps_30_45": 15, "fps_15_30": 5, "fps_below_15": 0,
698
+ "latency_3s": 20, "latency_5s": 15, "latency_10s": 5, "latency_above": 0,
699
+ },
700
+ },
701
+ },
702
+ "grades": {
703
+ "S": {"min": 900, "label": "Superhuman", "description": "์ธ๊ฐ„ ์ˆ˜์ค€ ์ด์ƒ์˜ ์ธ์ง€ ์›”๋“œ๋ชจ๋ธ"},
704
+ "A": {"min": 750, "label": "Advanced", "description": "๊ณ ๊ธ‰ ์ธ์ง€ ์›”๋“œ๋ชจ๋ธ"},
705
+ "B": {"min": 600, "label": "Baseline", "description": "๊ธฐ๋ณธ ์›”๋“œ๋ชจ๋ธ ์ˆ˜์ค€"},
706
+ "C": {"min": 400, "label": "Capable", "description": "์ œํ•œ์  ์ธ์ง€ ๋Šฅ๋ ฅ"},
707
+ "D": {"min": 200, "label": "Developing", "description": "์ดˆ๊ธฐ ๋‹จ๊ณ„"},
708
+ "F": {"min": 0, "label": "Failing", "description": "์›”๋“œ๋ชจ๋ธ๋กœ ๋ถ„๋ฅ˜ ๋ถˆ๊ฐ€"},
709
+ },
710
+ }
711
+
712
+
713
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
714
+ # LEADERBOARD SCHEMA โ€” ๋ฆฌ๋”๋ณด๋“œ ๊ตฌ์กฐ
715
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
716
+
717
+ LEADERBOARD_SCHEMA = {
718
+ "entry": {
719
+ "model_name": "str โ€” ๋ชจ๋ธ๋ช…",
720
+ "organization": "str โ€” ์ œ์ถœ ์กฐ์ง",
721
+ "submission_date": "str โ€” ์ œ์ถœ์ผ",
722
+ "wm_score": "int โ€” ์ด์  (0~1000)",
723
+ "grade": "str โ€” S/A/B/C/D/F",
724
+ "p1_perception": "int โ€” ์ธ์‹ ์ ์ˆ˜ (0~250)",
725
+ "p2_cognition": "int โ€” ์ธ์ง€ ์ ์ˆ˜ (0~450)",
726
+ "p3_embodiment": "int โ€” ๊ตฌํ˜„ ์ ์ˆ˜ (0~300)",
727
+ "c01_to_c10": "dict โ€” 10๊ฐœ ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐœ๋ณ„ ์ ์ˆ˜",
728
+ "fps": "float โ€” ํ‰๊ท  FPS",
729
+ "cognitive_latency_ms": "int โ€” ์ธ์ง€ ๋ฃจํ”„ ์ง€์—ฐ์‹œ๊ฐ„",
730
+ "gpu": "str โ€” ์‚ฌ์šฉ GPU",
731
+ "brain_model": "str โ€” ์ธ์ง€ ๋ชจ๋ธ (LLM ๋“ฑ)",
732
+ "motion_model": "str โ€” ๋ชจ์…˜ ์ƒ์„ฑ ๋ชจ๋ธ",
733
+ "paper_url": "str โ€” ๋…ผ๋ฌธ ๋งํฌ (์„ ํƒ)",
734
+ "demo_url": "str โ€” ๋ฐ๋ชจ ๋งํฌ (์„ ํƒ)",
735
+ },
736
+ "columns_display_order": [
737
+ "rank", "model_name", "wm_score", "grade",
738
+ "p1_perception", "p2_cognition", "p3_embodiment",
739
+ "fps", "cognitive_latency_ms",
740
+ ],
741
+ }
742
+
743
+
744
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
745
+ # HF DATASET STRUCTURE
746
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
747
+
748
+ HF_DATASET_STRUCTURE = {
749
+ "repo": "VIDraft/WorldModelBench",
750
+ "files": {
751
+ "README.md": "๋ฒค์น˜๋งˆํฌ ์„ค๋ช… + ์‚ฌ์šฉ๋ฒ• + ์ธ์šฉ",
752
+ "benchmark_spec.json": "์ด ํŒŒ์ผ์˜ JSON ๋ณ€ํ™˜ (์ „์ฒด ๋ช…์„ธ)",
753
+ "scenarios/": {
754
+ "c01_environmental_awareness.json": "S01~S05",
755
+ "c02_entity_recognition.json": "S06~S10",
756
+ "c03_predictive_reasoning.json": "S11~S15",
757
+ "c04_threat_differentiation.json": "S16~S20",
758
+ "c05_emotional_escalation.json": "S21~S25",
759
+ "c06_contextual_memory.json": "S26~S30",
760
+ "c07_threat_resolution.json": "S31~S35",
761
+ "c08_motion_expressiveness.json": "S36~S40",
762
+ "c09_realtime_performance.json": "S41~S45",
763
+ "c10_cross_body_transfer.json": "S46~S50",
764
+ },
765
+ "scoring/": {
766
+ "auto_scorer.py": "์ž๋™ ์ฑ„์  ์ฝ”๋“œ",
767
+ "keyword_banks.json": "๊ฐ์ • ํ‚ค์›Œ๋“œ ์‚ฌ์ „",
768
+ "intensity_scale.json": "๊ฐ•๋„ ์ˆ˜์ค€ ์ •์˜",
769
+ },
770
+ "leaderboard/": {
771
+ "results.json": "์ „์ฒด ์ œ์ถœ ๊ฒฐ๊ณผ",
772
+ "baselines.json": "VIDRAFT PROMETHEUS ๊ธฐ์ค€์ ",
773
+ },
774
+ "examples/": {
775
+ "vidraft_prometheus_submission.json": "์ œ์ถœ ์˜ˆ์‹œ",
776
+ "sample_evaluation_log.json": "์ฑ„์  ๋กœ๊ทธ ์˜ˆ์‹œ",
777
+ },
778
+ },
779
+ }
780
+
781
+
782
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
783
+ # BASELINE SCORES โ€” VIDRAFT PROMETHEUS ๊ธฐ์ค€์ 
784
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
785
+
786
+ VIDRAFT_BASELINE = {
787
+ "model_name": "VIDRAFT PROMETHEUS v1.0",
788
+ "organization": "VIDRAFT",
789
+ "brain_model": "Kimi K2.5 (Fireworks)",
790
+ "motion_model": "FloodDiffusion Tiny (ShandaAI)",
791
+ "gpu": "NVIDIA L40S 48GB",
792
+ "scores": {
793
+ "wm_score": 730,
794
+ "grade": "B+",
795
+ "P1_PERCEPTION": {
796
+ "C01_environmental_awareness": 65,
797
+ "C02_entity_recognition": 75,
798
+ "subtotal": 140, # / 250
799
+ },
800
+ "P2_COGNITION": {
801
+ "C03_predictive_reasoning": 85,
802
+ "C04_threat_differentiation": 90,
803
+ "C05_emotional_escalation": 85,
804
+ "C06_contextual_memory": 60,
805
+ "C07_threat_resolution": 70,
806
+ "subtotal": 390, # / 450
807
+ },
808
+ "P3_EMBODIMENT": {
809
+ "C08_motion_expressiveness": 80,
810
+ "C09_realtime_performance": 85,
811
+ "C10_cross_body_transfer": 35,
812
+ "subtotal": 200, # / 300
813
+ },
814
+ },
815
+ "notes": [
816
+ "P2 Cognition์ด ๊ฐ€์žฅ ๊ฐ•๋ ฅ โ€” ์˜ˆ์ธก+์ฐจ๋ณ„+๊ฐ์ • ์˜์—ญ ์••๋„",
817
+ "P1 Perception์€ ๋ ˆ์ด์บ์ŠคํŠธ 3๋ฐฉํ–ฅ ํ•œ๊ณ„ โ†’ Phase 2์—์„œ ๊ฐ•ํ™”",
818
+ "P3 ์ค‘ C10(๊ต์ฒด ํ™•์žฅ์„ฑ)์€ ์•„์ง ๋ฏธ๊ตฌํ˜„ โ†’ Phase 3์—์„œ ๊ฐ•ํ™”",
819
+ "AETHER ํ†ตํ•ฉ ์‹œ C06(๊ธฐ์–ต), C05(์—์Šค์ปฌ๋ ˆ์ด์…˜) ๋Œ€ํญ ์ƒ์Šน ์˜ˆ์ƒ",
820
+ ],
821
+ }
822
+
823
+
824
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
825
+ # PRINT SUMMARY
826
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
827
+
828
+ if __name__ == "__main__":
829
+ print("=" * 60)
830
+ print(" World Model Bench v1.0")
831
+ print(" A Benchmark for Cognitive World Models")
832
+ print("=" * 60)
833
+ print()
834
+ print(f" 3 Pillars / 10 Categories / 50 Scenarios")
835
+ print(f" Max Score: 1000 (WM Score)")
836
+ print()
837
+ for pid, p in PILLARS.items():
838
+ print(f" {p['icon']} {p['name']} โ€” weight {p['weight']}")
839
+ for cid in p["categories"]:
840
+ c = CATEGORIES[cid]
841
+ print(f" {cid}: {c['name_kr']} ({c['num_scenarios']} scenarios)")
842
+ print()
843
+ print(f" Total scenarios: {len(SCENARIOS)}")
844
+ print()
845
+ print(" VIDRAFT PROMETHEUS Baseline: {}/1000 (Grade {})".format(
846
+ VIDRAFT_BASELINE["scores"]["wm_score"],
847
+ VIDRAFT_BASELINE["scores"]["grade"],
848
+ ))
849
+ print()
850
+ print(" HF Dataset: VIDraft/WorldModelBench")
851
+ print(" HF Leaderboard: VIDraft/WorldModelBench-Leaderboard")
852
+ print("=" * 60)
wm_bench_verify.py ADDED
@@ -0,0 +1,390 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ World Model Bench โ€” Scoring Verification Suite v1.0
3
+
4
+ ๋ชฉ์ : ์ฑ„์  ํ•จ์ˆ˜์˜ ๋ชจ๋“  ๋ถ„๊ธฐ๋ฅผ ํ…Œ์ŠคํŠธํ•˜์—ฌ
5
+ "๋ˆ„๊ฐ€ ๋Œ๋ ค๋„ ๊ฐ™์€ ์ ์ˆ˜" ๋ณด์žฅ
6
+
7
+ ๊ฒ€์ฆ ๋ฒ”์œ„:
8
+ - ํŒŒ์„œ ์—ฃ์ง€์ผ€์ด์Šค (๋นˆ ์ž…๋ ฅ, ์ž˜๋ชป๋œ ํฌ๋งท, ๋Œ€์†Œ๋ฌธ์ž ๋“ฑ)
9
+ - 10๊ฐœ ์ฑ„์  ํ•จ์ˆ˜ ร— ๋ชจ๋“  ์ ์ˆ˜ ๋ถ„๊ธฐ
10
+ - ๊ฒฝ๊ณ„๊ฐ’ ํ…Œ์ŠคํŠธ (0์ , ๋งŒ์ , ๋ถ€๋ถ„ ์ ์ˆ˜)
11
+ - ํ†ตํ•ฉ ์ ์ˆ˜ ๊ณ„์‚ฐ + ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„
12
+ """
13
+
14
+ import sys
15
+ sys.path.insert(0, '/mnt/user-data/outputs')
16
+
17
+ from wm_bench_scoring import (
18
+ parse_predict_line, parse_motion_line, PredictDirection,
19
+ get_action_intensity, get_emotion_intensity, get_motion_direction,
20
+ count_descriptors,
21
+ score_c01, score_c02, score_c03, score_c04, score_c05,
22
+ score_c06, score_c07, score_c08, score_c09, score_c10,
23
+ calculate_wm_score,
24
+ )
25
+
26
+ passed = 0
27
+ failed = 0
28
+ total = 0
29
+
30
+ def check(test_name, condition, detail=""):
31
+ global passed, failed, total
32
+ total += 1
33
+ if condition:
34
+ passed += 1
35
+ print(f" โœ“ {test_name}")
36
+ else:
37
+ failed += 1
38
+ print(f" โœ— {test_name} โ€” {detail}")
39
+
40
+
41
+ print("=" * 70)
42
+ print(" WM Bench Scoring Verification Suite")
43
+ print("=" * 70)
44
+
45
+
46
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
47
+ print("\n[1/12] ํŒŒ์„œ: parse_predict_line")
48
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
49
+
50
+ # ์ •์ƒ ์ž…๋ ฅ
51
+ p = parse_predict_line("PREDICT: left=safe(open), right=danger(wall), fwd=danger(beast), back=safe")
52
+ check("์ •์ƒ ํŒŒ์‹ฑ 4๋ฐฉํ–ฅ", len(p) == 4)
53
+ check("left=safe", p["left"].is_safe and not p["left"].is_danger)
54
+ check("right=danger", p["right"].is_danger and not p["right"].is_safe)
55
+ check("right reason=wall", p["right"].reason == "wall")
56
+ check("fwd reason=beast", p["fwd"].reason == "beast")
57
+ check("back=safe no reason", p["back"].is_safe and p["back"].reason is None)
58
+
59
+ # ๋Œ€์†Œ๋ฌธ์ž ํ˜ผํ•ฉ
60
+ p2 = parse_predict_line("PREDICT: Left=Safe(Open), RIGHT=DANGER(WALL), FWD=danger(Beast), Back=safe")
61
+ check("๋Œ€์†Œ๋ฌธ์ž left safe", p2["left"].is_safe)
62
+ check("๋Œ€์†Œ๋ฌธ์ž right danger", p2["right"].is_danger)
63
+ check("๋Œ€์†Œ๋ฌธ์ž reason", p2["right"].reason == "wall")
64
+
65
+ # forward/backward ์ •๊ทœํ™”
66
+ p3 = parse_predict_line("PREDICT: left=safe, right=safe, forward=danger(wall), backward=safe")
67
+ check("forwardโ†’fwd ์ •๊ทœํ™”", "fwd" in p3)
68
+ check("backwardโ†’back ์ •๊ทœํ™”", "back" in p3)
69
+
70
+ # ๋นˆ ์ž…๋ ฅ
71
+ p4 = parse_predict_line("")
72
+ check("๋นˆ ์ž…๋ ฅ ๋นˆ ๊ฒฐ๊ณผ", len(p4) == 0)
73
+
74
+ # PREDICT: ์—†๋Š” ๊ฒฝ์šฐ
75
+ p5 = parse_predict_line("left=safe, right=danger(wall)")
76
+ check("PREDICT: ์—†์–ด๋„ ํŒŒ์‹ฑ", len(p5) >= 2)
77
+
78
+ # ์ด์œ  ์—†๋Š” danger
79
+ p6 = parse_predict_line("PREDICT: left=danger, right=safe")
80
+ check("์ด์œ  ์—†๋Š” danger", p6["left"].is_danger and p6["left"].reason is None)
81
+
82
+ # ๊ณต๋ฐฑ ๋งŽ์€ ๊ฒฝ์šฐ
83
+ p7 = parse_predict_line("PREDICT: left = safe(open) , right = danger( wall ) ")
84
+ check("๊ณต๋ฐฑ ๊ณผ๋‹ค ํŒŒ์‹ฑ", "left" in p7 or len(p7) >= 1)
85
+
86
+
87
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
88
+ print("\n[2/12] ํŒŒ์„œ: parse_motion_line")
89
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
90
+
91
+ m1 = parse_motion_line("MOTION: a person sprinting right in terror")
92
+ check("์ •์ƒ ํŒŒ์‹ฑ", m1 == "a person sprinting right in terror")
93
+
94
+ m2 = parse_motion_line("MOTION:a person walking forward")
95
+ check("๊ณต๋ฐฑ ์—†์ด", "walking forward" in m2)
96
+
97
+ m3 = parse_motion_line("a person just walking")
98
+ check("MOTION: ์—†์–ด๋„", "walking" in m3)
99
+
100
+ m4 = parse_motion_line("MOTION: A Person SPRINTING LEFT")
101
+ check("๋Œ€์†Œ๋ฌธ์ž ์†Œ๋ฌธ์žํ™”", "sprinting" in m4)
102
+
103
+
104
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
105
+ print("\n[3/12] ํ‚ค์›Œ๋“œ ์‚ฌ์ „: ํ–‰๋™ ๊ฐ•๋„")
106
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
107
+
108
+ check("sprint=4", get_action_intensity("sprinting away") == 4)
109
+ check("walk=2", get_action_intensity("walking slowly") == 2)
110
+ check("desperate=5", get_action_intensity("desperate escape") == 5)
111
+ check("stand=1", get_action_intensity("standing still") == 1)
112
+ check("๋นˆ ํ…์ŠคํŠธ=0", get_action_intensity("") == 0)
113
+ check("๋ณตํ•ฉ: sprint+desperate=5", get_action_intensity("desperately sprinting") == 5)
114
+ check("unknown word=0", get_action_intensity("xyzzy foobar") == 0)
115
+ check("freeze=1", get_action_intensity("freezing in place") == 1)
116
+ check("run=4", get_action_intensity("running fast") == 4)
117
+ check("jog=3", get_action_intensity("jogging ahead") == 3)
118
+
119
+
120
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
121
+ print("\n[4/12] ํ‚ค์›Œ๋“œ ์‚ฌ์ „: ๊ฐ์ • ๊ฐ•๋„")
122
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
123
+
124
+ check("terror=5", get_emotion_intensity("in terror") == 5)
125
+ check("fear=4", get_emotion_intensity("with fear") == 4)
126
+ check("anxious=3", get_emotion_intensity("feeling anxious") == 3)
127
+ check("cautious=2", get_emotion_intensity("being cautious") == 2)
128
+ check("calm=1", get_emotion_intensity("staying calm") == 1)
129
+ check("๋นˆ=0", get_emotion_intensity("") == 0)
130
+ check("terrified=5", get_emotion_intensity("terrified") == 5)
131
+ check("๋ณตํ•ฉ: terror+fear=5", get_emotion_intensity("terrified with fear") == 5)
132
+
133
+
134
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
135
+ print("\n[5/12] ํ‚ค์›Œ๋“œ ์‚ฌ์ „: ๋ฐฉํ–ฅ ์ถ”์ถœ")
136
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
137
+
138
+ check("right", get_motion_direction("sprinting right") == "right")
139
+ check("left", get_motion_direction("moving left") == "left")
140
+ check("forwardโ†’fwd", get_motion_direction("walking forward") == "fwd")
141
+ check("backwardโ†’back", get_motion_direction("stepping backward") == "back")
142
+ check("aroundโ†’back", get_motion_direction("turning around") == "back")
143
+ check("์—†์Œโ†’None", get_motion_direction("a person standing still") is None)
144
+ check("aheadโ†’fwd", get_motion_direction("running ahead") == "fwd")
145
+
146
+
147
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
148
+ print("\n[6/12] C01: ํ™˜๊ฒฝ ์ธ์‹ ์ •ํ™•๋„")
149
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
150
+
151
+ gt = {"left": "safe", "right": "danger", "fwd": "danger", "back": "safe"}
152
+
153
+ # ๋งŒ์ 
154
+ p = parse_predict_line("PREDICT: left=safe, right=danger(wall), fwd=danger(beast), back=safe")
155
+ s, r = score_c01({}, p, gt)
156
+ check("4/4 ์ •ํ™• = 20์ ", s == 20)
157
+
158
+ # 3/4
159
+ p = parse_predict_line("PREDICT: left=safe, right=danger(wall), fwd=safe, back=safe")
160
+ s, r = score_c01({}, p, gt)
161
+ check("3/4 ์ •ํ™• = 15์ ", s == 15)
162
+
163
+ # 2/4
164
+ p = parse_predict_line("PREDICT: left=safe, right=safe, fwd=safe, back=safe")
165
+ s, r = score_c01({}, p, gt)
166
+ check("2/4 ์ •ํ™• = 10์ ", s == 10)
167
+
168
+ # 0/4
169
+ p = parse_predict_line("PREDICT: left=danger, right=safe, fwd=safe, back=danger")
170
+ s, r = score_c01({}, p, gt)
171
+ check("0/4 ์ •ํ™• = 0์ ", s == 0)
172
+
173
+ # ๋ถ€๋ถ„ ์ถœ๋ ฅ (2๋ฐฉํ–ฅ๋งŒ)
174
+ p = parse_predict_line("PREDICT: left=safe, right=danger(wall)")
175
+ s, r = score_c01({}, p, gt)
176
+ check("2๋ฐฉํ–ฅ๋งŒ ์ถœ๋ ฅ (fwd,back ๋ˆ„๋ฝ)", s <= 10)
177
+
178
+
179
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
180
+ print("\n[7/12] C02: ๊ฐœ์ฒด ์ธ์‹ ๋ฐ ๋ถ„๋ฅ˜")
181
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
182
+
183
+ # ๋งŒ์ : ๋งน์ˆ˜ ์ •ํ™• ์ธ์‹
184
+ gt = {"entity_type": "beast", "entity_direction": "fwd", "is_threat": True}
185
+ p = parse_predict_line("PREDICT: left=safe, right=safe, fwd=danger(beast), back=safe")
186
+ s, r = score_c02({}, p, gt)
187
+ check("๋งน์ˆ˜ ์™„๋ฒฝ ์ธ์‹ = 20์ ", s == 20, f"got {s}")
188
+
189
+ # ์œ ํ˜• ์˜ค์ธ: beast๋ฅผ woman์œผ๋กœ
190
+ p = parse_predict_line("PREDICT: left=safe, right=safe, fwd=danger(woman), back=safe")
191
+ s, r = score_c02({}, p, gt)
192
+ check("์œ ํ˜• ์˜ค์ธ < 20์ ", s < 20 and s > 0, f"got {s}")
193
+
194
+ # ๋ฐฉํ–ฅ ์˜ค์ธ
195
+ p = parse_predict_line("PREDICT: left=danger(beast), right=safe, fwd=safe, back=safe")
196
+ s, r = score_c02({}, p, gt)
197
+ check("๋ฐฉํ–ฅ ์˜ค์ธ < 15์ ", s < 15, f"got {s}")
198
+
199
+ # ๊ฐœ์ฒด ์—†์Œ ์ •ํ™•
200
+ gt_none = {"entity_type": None, "entity_direction": None, "is_threat": False}
201
+ p = parse_predict_line("PREDICT: left=safe, right=safe, fwd=safe, back=safe")
202
+ s, r = score_c02({}, p, gt_none)
203
+ check("๊ฐœ์ฒด ์—†์Œ ์ •ํ™• = 20์ ", s == 20, f"got {s}")
204
+
205
+ # ๊ฐœ์ฒด ์—†๋Š”๋ฐ danger ์˜ค์ธ
206
+ p = parse_predict_line("PREDICT: left=safe, right=danger(beast), fwd=safe, back=safe")
207
+ s, r = score_c02({}, p, gt_none)
208
+ check("์—†๋Š”๋ฐ ์˜ค์ธ = 10์ ", s == 10, f"got {s}")
209
+
210
+
211
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•๏ฟฝ๏ฟฝ๏ฟฝโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
212
+ print("\n[8/12] C03: ์˜ˆ์ธก ๊ธฐ๋ฐ˜ ์ถ”๋ก ")
213
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
214
+
215
+ gt = {
216
+ "danger_directions": ["fwd", "left"],
217
+ "safe_directions": ["right", "back"],
218
+ "optimal_direction": "right",
219
+ }
220
+
221
+ # ๋งŒ์ : ์ตœ์  ๋ฐฉํ–ฅ + safe + PREDICT ์ผ๊ด€
222
+ p = parse_predict_line("PREDICT: left=danger(wall), right=safe(open), fwd=danger(beast), back=safe")
223
+ s, r = score_c03({}, p, "a person sprinting right away from beast", gt)
224
+ check("์ตœ์  ๋ฐฉํ–ฅ ์„ ํƒ = 20์ ", s == 20, f"got {s}")
225
+
226
+ # ์ฐจ์„ : back ์„ ํƒ (safe์ด์ง€๋งŒ optimal ์•„๋‹˜)
227
+ s, r = score_c03({}, p, "a person running backward quickly", gt)
228
+ check("์ฐจ์„  ๋ฐฉํ–ฅ back = 16์ ", s == 16, f"got {s}")
229
+
230
+ # ์œ„ํ—˜ ๋ฐฉํ–ฅ ์„ ํƒ
231
+ s, r = score_c03({}, p, "a person walking forward slowly", gt)
232
+ check("danger ๋ฐฉํ–ฅ fwd = ๋‚ฎ์€ ์ ์ˆ˜", s <= 8, f"got {s}")
233
+
234
+ # ๋ฐฉํ–ฅ ํ‚ค์›Œ๋“œ ์—†์Œ
235
+ s, r = score_c03({}, p, "a person standing in panic", gt)
236
+ check("๋ฐฉํ–ฅ ์—†์Œ = ๋ถ€๋ถ„ ์ ์ˆ˜", 0 <= s <= 10, f"got {s}")
237
+
238
+
239
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
240
+ print("\n[9/12] C04: ์œ„ํ˜‘ ์ฐจ๋ณ„ ๋ฐ˜์‘")
241
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
242
+
243
+ gt = {"expected_a_higher": True, "min_intensity_diff": 2}
244
+
245
+ # ๋งŒ์ : A ๊ฐ•ํ•จ, B ์•ฝํ•จ, ์ฐจ์ด ์ถฉ๋ถ„
246
+ s, r = score_c04("a person sprinting away in terror", "a person walking away cautiously", gt)
247
+ check("sprint(5) vs walk(2) = 20์ ", s == 20, f"got {s}")
248
+
249
+ # ๋™์ผ ๊ฐ•๋„
250
+ s, r = score_c04("a person walking forward", "a person walking slowly", gt)
251
+ check("๋™์ผ ๊ฐ•๋„ = ๋‚ฎ์€ ์ ์ˆ˜", s <= 10, f"got {s}")
252
+
253
+ # ์—ญ์ „: B๊ฐ€ ๋” ๊ฐ•ํ•จ (๊ฐ•๋„ ์ฐจ์ด๋Š” ์žˆ์œผ๋ฏ€๋กœ ๋ถ€๋ถ„ ์ ์ˆ˜)
254
+ s, r = score_c04("a person standing still", "a person sprinting away", gt)
255
+ check("์—ญ์ „ = ๋ถ€๋ถ„ ์ ์ˆ˜ (๊ฐ•๋„ ์ฐจ์ด๋Š” ์ธ์ •)", 8 <= s <= 14, f"got {s}")
256
+
257
+ # ์–‘์ชฝ ์ €๊ฐ•๋„ ๋ฐ˜์‘ (์ฐจ์ด ์—†์Œ)
258
+ s, r = score_c04("a person looking around", "a person standing there", gt)
259
+ check("์–‘์ชฝ ์ €๊ฐ•๋„ = ๋ถ€๋ถ„ ์ ์ˆ˜", 4 <= s <= 8, f"got {s}")
260
+
261
+ # ์ฐจ์ด ์žˆ์ง€๋งŒ ๋ถ€์กฑ
262
+ s, r = score_c04("a person jogging away", "a person walking forward", gt)
263
+ check("์ฐจ์ด 1 < min_diff 2", s < 20, f"got {s}")
264
+
265
+
266
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
267
+ print("\n[10/12] C05: ๊ฐ์ • ์—์Šค์ปฌ๋ ˆ์ด์…˜")
268
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
269
+
270
+ # ๋งŒ์ : ์ฆ๊ฐ€ ์ถ”์„ธ
271
+ s, r = score_c05([
272
+ "a person stepping back",
273
+ "a person running away in fear",
274
+ "a person desperately fleeing in terror",
275
+ ], {"expected_trend": "increasing"})
276
+ check("์ฆ๊ฐ€ ์ถ”์„ธ = ๋†’์€ ์ ์ˆ˜", s >= 16, f"got {s}")
277
+
278
+ # ๊ฐ์†Œ ์ถ”์„ธ (๊ธฐ๋Œ€: ๊ฐ์†Œ)
279
+ s, r = score_c05([
280
+ "a person sprinting in terror",
281
+ "a person jogging cautiously",
282
+ "a person walking calmly",
283
+ ], {"expected_trend": "decreasing"})
284
+ check("๊ฐ์†Œ ์ถ”์„ธ = ๋†’์€ ์ ์ˆ˜", s >= 16, f"got {s}")
285
+
286
+ # ์•ˆ์ • (๊ธฐ๋Œ€: ์•ˆ์ •)
287
+ s, r = score_c05([
288
+ "a person walking forward",
289
+ "a person walking ahead",
290
+ "a person walking steadily",
291
+ ], {"expected_trend": "stable"})
292
+ check("์•ˆ์ • ์œ ์ง€ = ๋†’์€ ์ ์ˆ˜", s >= 14, f"got {s}")
293
+
294
+ # ์—ญ์ „ (๊ธฐ๋Œ€: ์ฆ๊ฐ€์ธ๋ฐ ๊ฐ์†Œ)
295
+ s, r = score_c05([
296
+ "a person desperately fleeing",
297
+ "a person walking calmly",
298
+ "a person standing still",
299
+ ], {"expected_trend": "increasing"})
300
+ check("์—ญ์ „ = ๋‚ฎ์€ ์ ์ˆ˜", s <= 8, f"got {s}")
301
+
302
+ # ์‹œํ€€์Šค 1๊ฐœ๋งŒ
303
+ s, r = score_c05(["a person walking"], {"expected_trend": "increasing"})
304
+ check("์‹œํ€€์Šค 1๊ฐœ = 0์ ", s == 0)
305
+
306
+ # 4๊ฐœ ์‹œํ€€์Šค ๋‹จ์กฐ ์ฆ๊ฐ€
307
+ s, r = score_c05([
308
+ "a person standing still",
309
+ "a person stepping back",
310
+ "a person running away",
311
+ "a person desperately sprinting in terror",
312
+ ], {"expected_trend": "increasing"})
313
+ check("4๋‹จ๊ณ„ ๋‹จ์กฐ ์ฆ๊ฐ€", s >= 18, f"got {s}")
314
+
315
+
316
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
317
+ print("\n[11/12] C08: ๋ชจ์…˜ ํ‘œํ˜„๋ ฅ")
318
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
319
+
320
+ gt_high = {"expected_min_intensity": 4, "expected_emotion": True, "expected_min_descriptors": 2}
321
+ gt_low = {"expected_min_intensity": 1, "expected_emotion": False, "expected_min_descriptors": 1}
322
+
323
+ # ๋งŒ์ : ํ’๋ถ€ํ•œ ํ‘œํ˜„
324
+ s, r = score_c08("a person desperately sprinting right in terror", gt_high)
325
+ check("ํ’๋ถ€ํ•œ ํ‘œํ˜„ = 20์ ", s == 20, f"got {s}")
326
+
327
+ # ๊ฐ์ • ์—†๋Š” ํ‘œํ˜„ (๊ธฐ๋Œ€: ๊ฐ์ • ์žˆ์Œ)
328
+ s, r = score_c08("a person moving right", gt_high)
329
+ check("๊ฐ์ • ์—†์Œ < ๋งŒ์ ", s < 15, f"got {s}")
330
+
331
+ # ํ‰์ƒ์‹œ (๊ธฐ๋Œ€: ๊ฐ์ • ์—†์Œ)
332
+ s, r = score_c08("a person walking forward steadily", gt_low)
333
+ check("ํ‰์ƒ์‹œ ์ ์ ˆ = ๋†’์€ ์ ์ˆ˜", s >= 14, f"got {s}")
334
+
335
+ # ๋„ˆ๋ฌด ๊ธด ํ…์ŠคํŠธ
336
+ long_motion = "a person " + " ".join(["really"] * 25) + " running"
337
+ s, r = score_c08(long_motion, gt_high)
338
+ check("30๋‹จ์–ด ์ดˆ๊ณผ = ๊ธธ์ด ๊ฐ์ ", s < 20, f"got {s}")
339
+
340
+
341
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
342
+ print("\n[12/12] C09: ์‹ค์‹œ๊ฐ„ ์„ฑ๋Šฅ")
343
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
344
+
345
+ # ๋งŒ์ 
346
+ s, r = score_c09({"fps": 50, "cognitive_latency_ms": 2000, "frame_drop_rate": 0.005, "gpu_memory_stable": True})
347
+ check("์™„๋ฒฝ ์„ฑ๋Šฅ = 20์ ", s == 20)
348
+
349
+ # ์ตœ์†Œ
350
+ s, r = score_c09({"fps": 20, "cognitive_latency_ms": 8000, "frame_drop_rate": 0.03, "gpu_memory_stable": True})
351
+ check("์ตœ์†Œ ์„ฑ๋Šฅ", 5 <= s <= 12, f"got {s}")
352
+
353
+ # ๋ฏธ๋‹ฌ
354
+ s, r = score_c09({"fps": 5, "cognitive_latency_ms": 15000, "frame_drop_rate": 0.1, "gpu_memory_stable": False})
355
+ check("๋ฏธ๋‹ฌ ์„ฑ๋Šฅ = ๋‚ฎ์€ ์ ์ˆ˜", s <= 3, f"got {s}")
356
+
357
+
358
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
359
+ print("\n[BONUS] ํ†ตํ•ฉ ์ ์ˆ˜ + ๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„")
360
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
361
+
362
+ # S๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„
363
+ r = calculate_wm_score({"C01":100,"C02":100,"C03":100,"C04":100,"C05":100,"C06":100,"C07":100,"C08":100,"C09":100,"C10":100})
364
+ check(f"๋งŒ์  = {r['wm_score']} (S๋“ฑ๊ธ‰)", r["grade"] == "S" and r["wm_score"] == 1000)
365
+
366
+ # A๋“ฑ๊ธ‰ ๊ฒฝ๊ณ„
367
+ r = calculate_wm_score({"C01":80,"C02":80,"C03":80,"C04":80,"C05":80,"C06":80,"C07":80,"C08":80,"C09":80,"C10":80})
368
+ check(f"80์ ๋Œ€ = {r['wm_score']} ({r['grade']}๋“ฑ๊ธ‰)", r["wm_score"] >= 750)
369
+
370
+ # B๋“ฑ๊ธ‰
371
+ r = calculate_wm_score({"C01":65,"C02":75,"C03":85,"C04":90,"C05":85,"C06":60,"C07":70,"C08":80,"C09":85,"C10":35})
372
+ check(f"VIDRAFT ๊ธฐ์ค€์  = {r['wm_score']} ({r['grade']}๋“ฑ๊ธ‰)", r["grade"] in ("B", "A"))
373
+
374
+ # F๋“ฑ๊ธ‰
375
+ r = calculate_wm_score({"C01":10,"C02":10,"C03":10,"C04":10,"C05":10,"C06":10,"C07":10,"C08":10,"C09":10,"C10":10})
376
+ check(f"์ตœ์ € = {r['wm_score']} ({r['grade']}๋“ฑ๊ธ‰)", r["wm_score"] < 200)
377
+
378
+ # 0์ 
379
+ r = calculate_wm_score({"C01":0,"C02":0,"C03":0,"C04":0,"C05":0,"C06":0,"C07":0,"C08":0,"C09":0,"C10":0})
380
+ check(f"0์  = {r['wm_score']} ({r['grade']}๋“ฑ๊ธ‰)", r["wm_score"] == 0 and r["grade"] == "F")
381
+
382
+
383
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
384
+ print("\n" + "=" * 70)
385
+ print(f" ๊ฒฐ๊ณผ: {passed}/{total} ํ†ต๊ณผ, {failed}/{total} ์‹คํŒจ")
386
+ if failed == 0:
387
+ print(" โœ… ๋ชจ๋“  ํ…Œ์ŠคํŠธ ํ†ต๊ณผ โ€” ์ฑ„์  ์‹œ์Šคํ…œ ๊ฒ€์ฆ ์™„๋ฃŒ")
388
+ else:
389
+ print(f" โŒ {failed}๊ฐœ ์‹คํŒจ โ€” ์ˆ˜์ • ํ•„์š”")
390
+ print("=" * 70)