<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>push -f</title><description>Pull first before pushing</description><link>https://blog.euroka.moe/</link><language>ko</language><item><title>Edit 툴 4종, 880회 실측 성공률 — replace가 이긴 이유와 patch의 토큰 블랙홀</title><link>https://blog.euroka.moe/posts/edit-tool-success-rates/</link><guid isPermaLink="true">https://blog.euroka.moe/posts/edit-tool-success-rates/</guid><description>Oh My Pi 하네스의 4가지 코드 편집 툴(replace/hashline/apply_patch/patch)을 11개 LLM × 20개 태스크, 총 880회 실행으로 실측한 성공률과 토큰·비용 효율 분석</description><pubDate>Sat, 15 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Edit 툴 4종, 880회 실측 성공률 — &lt;code&gt;replace&lt;/code&gt;가 이긴 이유와 &lt;code&gt;patch&lt;/code&gt;의 토큰 블랙홀&lt;/h1&gt;
&lt;p&gt;코딩 에이전트를 만들 때 &quot;모델이 코드를 어떻게 수정하게 할까?&quot;는 보통 구현 디테일로 취급된다. Git 스타일 unified diff(&lt;code&gt;apply_patch&lt;/code&gt;)나 퍼지 매칭(&lt;code&gt;patch&lt;/code&gt;)이 사람 개발자에게 익숙하다는 이유로 기본값이 되기 쉽다. 하지만 LLM은 코드를 &lt;em&gt;토큰 스트림&lt;/em&gt;으로 본다. 줄 번호 오프셋 계산, 컨텍스트 매칭, hunk 헤더의 산수 실수가 쌓이면 단순한 &quot;실패&quot;가 아니라 &lt;strong&gt;비싼 재시도 루프&lt;/strong&gt;로 이어진다.&lt;/p&gt;
&lt;p&gt;이 글은 Oh My Pi 하네스가 제공하는 4가지 코드 편집 툴 — &lt;code&gt;replace&lt;/code&gt;, &lt;code&gt;hashline&lt;/code&gt;, &lt;code&gt;apply_patch&lt;/code&gt;, &lt;code&gt;patch&lt;/code&gt; — 의 성공률과 토큰·비용 효율을 &lt;strong&gt;11개 LLM × 20개 TypeScript 태스크, 총 880회 실행&lt;/strong&gt;으로 실측한 결과다.&lt;/p&gt;
&lt;p&gt;결론부터: &lt;code&gt;replace&lt;/code&gt;가 평균 성공률 85.0%로 압도적이다. &lt;code&gt;patch&lt;/code&gt;는 평균 성공률 68.2%로 오히려 2위인데도 &lt;strong&gt;입력 토큰이 replace의 3.05배, 성공 1건당 비용은 2.8배&lt;/strong&gt;로 최악이다. 성공률만 보면 안 되는 이유가 여기 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;툴&lt;/th&gt;
&lt;th&gt;평균 성공률&lt;/th&gt;
&lt;th&gt;11개 모델 중 최고 툴로 선택&lt;/th&gt;
&lt;th&gt;비용/성공&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;replace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8/11&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.0094&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hashline&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;74.5%&lt;/td&gt;
&lt;td&gt;2/11&lt;/td&gt;
&lt;td&gt;$0.0118&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;apply_patch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;59.1%&lt;/td&gt;
&lt;td&gt;1/11&lt;/td&gt;
&lt;td&gt;$0.0228&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;patch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;68.2%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0/11&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.0260&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;replace&lt;/code&gt;는 &quot;실패의 하한선&quot;이 가장 높다.&lt;/strong&gt; 11개 모델 최저 성공률이 60% — 어떤 모델이든 어느 정도는 고쳐낸다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;patch&lt;/code&gt;는 어떤 모델에서도 최고 툴이 되지 못했고&lt;/strong&gt;, 실패할 때마다 파일 전체 재독 → 더 큰 패치 재시도 루프로 토큰을 태운다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;hashline&lt;/code&gt;은 구조적 정밀도가 필요한 모델&lt;/strong&gt;(kimi-k3, qwen3.7-plus)에서 강점을 보였다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;apply_patch&lt;/code&gt;는 mimo-v2.5 한 모델만&lt;/strong&gt; 85%로 최고 툴로 선택 — 모델별 편향이 뚜렷하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 하네스의 4가지 edit 툴&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;툴&lt;/th&gt;
&lt;th&gt;동작 방식&lt;/th&gt;
&lt;th&gt;장점&lt;/th&gt;
&lt;th&gt;실패 모드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;replace&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;path, startLine, endLine, newText&lt;/code&gt; — 줄 범위를 통째로 교체&lt;/td&gt;
&lt;td&gt;컨텍스트 드리프트 없음, 지역 수정에 출력 토큰 최소&lt;/td&gt;
&lt;td&gt;줄 번호가 밀리면 off-by-one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;hashline&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[파일#태그]&lt;/code&gt; 콘텐츠 해시 헤더 + &lt;code&gt;SWAP&lt;/code&gt;/&lt;code&gt;DEL&lt;/code&gt;/&lt;code&gt;INS&lt;/code&gt; 연산&lt;/td&gt;
&lt;td&gt;스냅샷 검증이 결정적, 낡은 줄 번호에 면역&lt;/td&gt;
&lt;td&gt;해시 문법을 엄격히 따라야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;apply_patch&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;표준 unified diff (&lt;code&gt;@@ -N,M +N,M @@&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Git 형식, 학습 데이터에 익숙&lt;/td&gt;
&lt;td&gt;hunk 헤더 줄 수를 모델이 자주 잘못 계산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;patch&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;oldContext&lt;/code&gt; 문자열을 찾아 &lt;code&gt;newContext&lt;/code&gt;로 교체&lt;/td&gt;
&lt;td&gt;줄 번호 불필요, 프롬프트가 직관적&lt;/td&gt;
&lt;td&gt;공백/들여쓰기 민감, &lt;strong&gt;다중 턴 재시도 루프 유발&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 측정 설계&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;스케일&lt;/strong&gt;: 11개 모델 × 4개 툴 × 20개 태스크 = &lt;strong&gt;880회 시도&lt;/strong&gt; (arm당 20회, best-of-1)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;태스크&lt;/strong&gt;: 100% TypeScript — Core TS(45%), Express/Node(25%), React+TSX(20%), DOM+jsdom(10%). 난이도는 Easy 25% ~ Nightmare(10+ 파일 아키텍처 리팩터링) 10%&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;검증&lt;/strong&gt;: Docker verifier 컨테이너(&lt;code&gt;--network=none&lt;/code&gt;)에서 &lt;code&gt;tsc --noEmit&lt;/code&gt; + 숨은 동작 테스트. 모델이 자기 환경을 속일 수 없게 격리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;설정&lt;/strong&gt;: 모든 실행은 &lt;code&gt;--thinking off&lt;/code&gt; — 추론 토큰 노이즈를 제거하고 순수한 툴 콜·편집 정밀도만 측정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;비용&lt;/strong&gt;: 공식 리스트 가격 기준. 429 쿼터 실패(인프라 원인)는 0토큰으로 제외, 에이전트 유발 재시도는 100% 부과&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 툴별 성공률&lt;/h2&gt;
&lt;p&gt;11개 모델 각각에서 해당 툴로 20개 태스크를 돌린 성공률의 평균·중앙값·하한·상한:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;툴&lt;/th&gt;
&lt;th&gt;평균&lt;/th&gt;
&lt;th&gt;중앙값&lt;/th&gt;
&lt;th&gt;최저&lt;/th&gt;
&lt;th&gt;최고&lt;/th&gt;
&lt;th&gt;총 성공 / 220&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;replace&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;85%&lt;/td&gt;
&lt;td&gt;60% (mimo-v2.5)&lt;/td&gt;
&lt;td&gt;100% (deepseek-v4-flash)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;187&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hashline&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;74.5%&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;25% (qwen3.8-max)&lt;/td&gt;
&lt;td&gt;95% (deepseek-v4-flash)&lt;/td&gt;
&lt;td&gt;164&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;apply_patch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;59.1%&lt;/td&gt;
&lt;td&gt;65%&lt;/td&gt;
&lt;td&gt;0% (qwen3.8-max)¹&lt;/td&gt;
&lt;td&gt;90% (deepseek-v4-flash)&lt;/td&gt;
&lt;td&gt;130&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;patch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;68.2%&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;td&gt;35% (minimax-m3)&lt;/td&gt;
&lt;td&gt;85% (kimi-k3·qwen3.8-max·deepseek-v4-flash·deepseek-v4-pro)&lt;/td&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;¹ qwen3.8-max × apply_patch arm은 토큰 0·비용 $0 — 쿼터/인프라 실패로 보이며 실측치가 아니다. 제외 시 apply_patch 평균은 65.0%.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;여기서 가장 중요한 지표는 &lt;strong&gt;하한선(floor)&lt;/strong&gt; 이다. &lt;code&gt;replace&lt;/code&gt;는 가장 못하는 모델(mimo-v2.5)도 60%를 기록했다. 반면 &lt;code&gt;hashline&lt;/code&gt;의 바닥은 25%, &lt;code&gt;apply_patch&lt;/code&gt;는 사실상 측정 불가(0%)였다. 좋은 툴은 모델의 천장을 높이는 게 아니라 &lt;strong&gt;바닥을 끌어올린다&lt;/strong&gt; — 그리고 &lt;code&gt;replace&lt;/code&gt;가 유일하게 그 역할을 했다.&lt;/p&gt;
&lt;p&gt;흥미로운 반전: &lt;strong&gt;모든 툴에서 가장 높은 성공률은 deepseek-v4-flash가 가져갔다&lt;/strong&gt; (replace 100%, hashline 95%, apply_patch 90%, patch 85% 공동 1위). 모델 역량이 받쳐주면 어떤 툴이든 잘 쓰지만, &lt;strong&gt;툴은 모델의 바닥을 결정한다&lt;/strong&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 토큰 &amp;amp; 비용: 성공률 너머&lt;/h2&gt;
&lt;p&gt;성공률만 보면 &lt;code&gt;patch&lt;/code&gt;는 2위다. 그런데 실제 지출을 보면 이야기가 완전히 달라진다 (44개 arm 합산):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;툴&lt;/th&gt;
&lt;th&gt;총 입력 토큰&lt;/th&gt;
&lt;th&gt;총 출력 토큰&lt;/th&gt;
&lt;th&gt;캐시 읽기&lt;/th&gt;
&lt;th&gt;총 비용&lt;/th&gt;
&lt;th&gt;태스크당 입력&lt;/th&gt;
&lt;th&gt;평균 $/성공&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;replace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.26M&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;197k&lt;/td&gt;
&lt;td&gt;6.89M&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1.78&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19.4k&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.0094&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hashline&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6.51M&lt;/td&gt;
&lt;td&gt;260k&lt;/td&gt;
&lt;td&gt;8.59M&lt;/td&gt;
&lt;td&gt;$2.07&lt;/td&gt;
&lt;td&gt;29.6k&lt;/td&gt;
&lt;td&gt;$0.0118&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;apply_patch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8.82M&lt;/td&gt;
&lt;td&gt;243k&lt;/td&gt;
&lt;td&gt;12.07M&lt;/td&gt;
&lt;td&gt;$2.26&lt;/td&gt;
&lt;td&gt;40.1k&lt;/td&gt;
&lt;td&gt;$0.0228&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;patch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;13.01M&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;376k&lt;/td&gt;
&lt;td&gt;16.79M&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$3.28&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;59.1k&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.0260&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;patch&lt;/code&gt;는 태스크당 평균 &lt;strong&gt;59.1k 입력 토큰&lt;/strong&gt; — &lt;code&gt;replace&lt;/code&gt;(19.4k)의 &lt;strong&gt;3.05배&lt;/strong&gt;다. 성공률 격차(68% vs 85%)보다 토큰 격차가 훨씬 크다. 성공 1건당 비용으로 환산하면 &lt;code&gt;patch&lt;/code&gt;는 &lt;code&gt;replace&lt;/code&gt;의 &lt;strong&gt;2.8배&lt;/strong&gt;였고, 880회 전체 총지출($9.38)에서도 &lt;code&gt;replace&lt;/code&gt;는 4개 툴 중 가장 적게 썼다.&lt;/p&gt;
&lt;p&gt;전체로는 &lt;strong&gt;631회 성공(71.7%)&lt;/strong&gt; 이었다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 모델 × 툴 상호작용 매트릭스&lt;/h2&gt;
&lt;p&gt;성공률(%) — 굵은 값은 해당 모델의 최고 툴:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;모델&lt;/th&gt;
&lt;th&gt;&lt;code&gt;apply_patch&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;hashline&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;patch&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;replace&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;deepseek-v4-flash&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3.8-max&lt;/td&gt;
&lt;td&gt;0¹&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;95&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;glm-5.2&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;95&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kimi-k2.7-code&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;90&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hy3&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;90&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kimi-k3&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;90&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mimo-v2.5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mimo-v2.5-pro&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deepseek-v4-pro&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3.7-plus&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;minimax-m3&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;툴별 우승 횟수: &lt;strong&gt;replace 8, hashline 2, apply_patch 1, patch 0.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;replace&lt;/code&gt;가 11개 중 8개 모델에서 최고 — 현대 LLM은 번호가 매겨진 read 출력만 있으면 절대 줄 번호 이해에 강하다. 오프셋 산수를 아예 제거해 버리는 게 핵심이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hashline&lt;/code&gt;은 kimi-k3(90%)와 qwen3.7-plus(85%)처럼 구조적 정밀도를 잘 따르는 모델에서 빛났다. 콘텐츠 해시 태그가 낡은 앵커 덮어쓰기를 원천 차단한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apply_patch&lt;/code&gt;는 mimo-v2.5 한 모델만 85%로 선호. 나머지 모델에선 hunk 헤더 산수 실패가 잦았다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;patch&lt;/code&gt;는 &lt;strong&gt;단 한 모델의 최고 툴도 되지 못했다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 사례 연구: glm-5.2, 툴 하나로 50%p가 갈렸다&lt;/h2&gt;
&lt;p&gt;같은 모델, 같은 20개 태스크에서 툴만 바꿨을 뿐인데:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;replace     :  95% (19/20) |  298,956 입력 토큰 |  $0.3433
patch       :  45% ( 9/20) | 1,481,206 입력 토큰 |  $1.0886  (토큰 4.95배, 비용 3.2배)
apply_patch :  45% ( 9/20) | 1,482,332 입력 토큰 |  $1.0882
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;replace&lt;/code&gt;로 바꾸자 &lt;strong&gt;성공률이 45% → 95%로 두 배&lt;/strong&gt;가 되면서 &lt;strong&gt;토큰은 79.8% 감소&lt;/strong&gt;했다. 툴 설계가 모델의 실제 코딩 능력보다 더 큰 변수인 경우가 있다는, 가장 극적인 증거다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 왜 &lt;code&gt;patch&lt;/code&gt;는 실패 루프에 빠지는가&lt;/h2&gt;
&lt;p&gt;원본 이벤트 스트림(&lt;code&gt;sample-failed-patch-loop-glm52.jsonl&lt;/code&gt;)을 보면 패턴이 반복적이다:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;graph TD
    A[patch 컨텍스트 매칭 실패] --&amp;gt; B[read로 파일 전체 재독]
    B --&amp;gt; C[컨텍스트를 더 크게 붙인 patch 재시도]
    C --&amp;gt;|여전히 공백/들여쓰기 미스매치| B
    C --&amp;gt;|턴 한도 초과| D[실패 · 토큰 폭증]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;미세한 공백 차이(탭 vs 스페이스, 줄바꿈, 들여쓰기)로 첫 매칭이 실패한다.&lt;/li&gt;
&lt;li&gt;모델은 거의 예외 없이 &lt;strong&gt;파일 전체를 다시 읽어&lt;/strong&gt; 재정렬한다.&lt;/li&gt;
&lt;li&gt;더 큰 컨텍스트로 재시도하지만, 근본 원인(컨텍스트 오해)이 해결되지 않아 또 실패한다.&lt;/li&gt;
&lt;li&gt;2~4턴 반복 후 턴 한도에 걸려 실패 — 태스크 하나에 75,000+ 토큰을 태운 사례도 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;반면 &lt;code&gt;replace&lt;/code&gt;·&lt;code&gt;hashline&lt;/code&gt;은 실패 시 &lt;strong&gt;&quot;정확한 앵커 오류&quot;라는 결정적 피드백&lt;/strong&gt;을 주므로 모델이 즉시 재조준한다. 실패가 비싸지 않은 구조가 핵심이다. 퍼지 매칭은 컨텍스트 오해를 숨기고, 그 대가를 토큰으로 지불하게 만든다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 하네스에 주는 교훈&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;기본값은 &lt;code&gt;replace&lt;/code&gt;로.&lt;/strong&gt; 최신 프론티어·중간 모델에서 가장 높은 신뢰성, 가장 낮은 지연, 가장 적은 토큰 낭비.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;대형 파일·동시 편집 환경에서는 &lt;code&gt;hashline&lt;/code&gt;.&lt;/strong&gt; 1,000줄 이상 파일에서 전체 블록 교체가 동시 편집을 덮어쓸 위험이 있다면, 콘텐츠 해시 태그가 낡은 앵커 문제를 제거한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;순수 컨텍스트 diff &lt;code&gt;patch&lt;/code&gt;는 피할 것.&lt;/strong&gt; 컨텍스트 블록 형식에 특화 파인튜닝된 모델이 아니라면, 실패 루프가 컨텍스트 윈도우와 API 예산을 빠르게 소진한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;성공률 단일 지표로 툴을 평가하지 말 것.&lt;/strong&gt; 이 벤치마크처럼 성공률 2위인 툴이 비용은 최악일 수 있다. 성공 1건당 비용($/success)과 토큰 분포를 함께 봐야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;9. 데이터 &amp;amp; 재현&lt;/h2&gt;
&lt;p&gt;이 글의 모든 수치는 공개된 원본 데이터에서 직접 재계산했다. 벤치마크 데이터, 원본 이벤트 스트림, 재현 스크립트는 &lt;code&gt;omp-bench-review&lt;/code&gt; 저장소에 포함돼 있다 (누구나 같은 인프라로 재현 가능):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;data/summary-leaderboard.json&lt;/code&gt; — 44개 arm 전체 메트릭 (토큰 버킷, 가격, wall time)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data/model-rankings.csv&lt;/code&gt; — 스프레드시트용 테이블&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data/raw-samples/&lt;/code&gt; — 실제 JSONL 이벤트 스트림 3종 (완벽한 1턴 replace / patch 실패 루프 / 429 쿼터 오류)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scripts/analyze-results.ts&lt;/code&gt; · &lt;code&gt;scripts/export-charts.ts&lt;/code&gt; — 리더보드·차트 재현 스크립트&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;bun run analyze   # 리더보드 출력
bun run charts    # ASCII 차트 출력
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;검증은 Docker 격리 verifier에서 &lt;code&gt;tsc --noEmit&lt;/code&gt; + 숨은 동작 테스트로 수행했고, 실행 인프라는 Azure Spot(시간당 약 0.02달러 수준)의 일회성 VM으로 전체 캠페인 비용을 10달러 미만으로 유지했다.&lt;/p&gt;
</content:encoded></item><item><title>MVCC 그리고 PostgreSQL과 MySQL의 차이점</title><link>https://blog.euroka.moe/posts/psql-msql-mvcc/</link><guid isPermaLink="true">https://blog.euroka.moe/posts/psql-msql-mvcc/</guid><pubDate>Mon, 29 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;MVCC&lt;/h1&gt;
&lt;p&gt;MVCC는 다중 버전 동시성 제어의 약자이다. &quot;기존 데이터를 덮어쓰기&quot; 대신 &quot;새 데이터를 쌓아올린다&quot;로 요약할 수 있다.&lt;/p&gt;
&lt;h2&gt;그러니까 창팝으로 비유하자면...&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A교실에서 시험 치면 강제 -50점
B교실에서 시험 치면 강제 +50점
근데 몇 개월 전에 이 사실에 대해 모른 채로
A교실에서 시험 치기로 한 거임
교실을 바꾸려면 내년을 기약하고
똑같이 B교실이 +50점 줄지 모르는 상황
이 형평성에 가만히 있으면 정당한거냐?
-메벤남&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;그냥넣어보고싶었음&lt;/p&gt;
&lt;h2&gt;이해가하나도안되잖아쿼리슝좍아&lt;/h2&gt;
&lt;p&gt;Git으로 설명해보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;일단 main 브랜치에서 dev 브랜치를 딴다. (&lt;code&gt;BEGIN&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;거기서 작업을 잔뜩 한다. (&lt;code&gt;BEGIN&lt;/code&gt; 이후 쿼리)&lt;/li&gt;
&lt;li&gt;이제 작업사항들을...
&lt;ul&gt;
&lt;li&gt;main에 머지하거나(&lt;code&gt;COMMIT&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;버린다(&lt;code&gt;ROLLBACK&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;PostgreSQL의 MVCC 구현&lt;/h1&gt;
&lt;p&gt;어 코끼리는 짱커 그래서 디스크 더 쓸 거야&lt;/p&gt;
&lt;p&gt;순서대로 알아보자면&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BEGIN&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;대충 뭔가 어마무시하게 많은 쿼리들
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UPDATE&lt;/code&gt;: 쓰려는 원본 테이블 대신 임시 튜플을 만들고, 그곳에 UPDATE한다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SELECT&lt;/code&gt;: 원본 테이블과 임시 튜플(있으면)에서 찾는다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이제 다 끝나고...
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ROLLBACK&lt;/code&gt;: 임시 튜플을 버린다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;COMMIT&lt;/code&gt;: 임시 튜플로 원본 테이블의 튜플을 교체한다(레퍼런스 교체 느낌으로 빠르다!)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그렇게 되면, 임시 튜플이나 원본 튜플들은 레퍼런스 없이 떠돌게 된다. 이는 &lt;code&gt;VACUUM&lt;/code&gt; 시 GC된다.&lt;/p&gt;
&lt;p&gt;이 구현의 장점은...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;어마무시하게 원자적이다. 레퍼런스 바꾸기 딸깍ㅋㅋ&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SELECT&lt;/code&gt;가 여러 번 있어도 용량이 커지지 않는다
&lt;ul&gt;
&lt;li&gt;락도 안 걸린다!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;한편 단점은...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UPDATE&lt;/code&gt; 쿼리 많으면 산으로 간다. 디스크 용량 3배 사용 이벤트!&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;MySQL의 MVCC 구현&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;./image.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;순서대로 알아보자면&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BEGIN&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;대충 뭔가 많은 쿼리들
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UPDATE&lt;/code&gt;: 원래 데이터를 undo log에 백업해두고, 원본 테이블을 바로 수정한다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SELECT&lt;/code&gt;: 현재 트랜잭션 격리 수준에 따라, undo log를 참고해서 적절한 버전의 데이터를 조회한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이제 다 끝나고...
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ROLLBACK&lt;/code&gt;: undo log에 있는 원래 데이터로 복구한다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;COMMIT&lt;/code&gt;: 변경사항을 확정한다. undo log는 더 이상 필요 없어지면 purge 스레드가 정리한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;undo log에는 두 종류가 있다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;insert undo log: &lt;code&gt;INSERT&lt;/code&gt; 때 기록. 트랜잭션 커밋되면 바로 버려도 됨&lt;/li&gt;
&lt;li&gt;update undo log: &lt;code&gt;UPDATE&lt;/code&gt;/&lt;code&gt;DELETE&lt;/code&gt; 때 기록. MVCC를 위해 커밋 후에도 당장 안 지울 수도 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;트랜잭션 격리 수준별 동작 방식:&lt;/p&gt;
&lt;p&gt;이 구현의 장점은...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;디스크 용량을 PostgreSQL보단 적게 쓴다. 원본을 바로 갈아치우니까&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UPDATE&lt;/code&gt;가 많아도 튜플이 쌓이지 않는다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;한편 단점은...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;undo log 관리가 복잡하다. purge 스레드가 바쁘게 돈다&lt;/li&gt;
&lt;li&gt;롤백 시 undo log를 뒤져서 원복해야 하므로 오래된 트랜잭션일수록 롤백 비용이 크다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SELECT&lt;/code&gt;가 오래된 undo log까지 뒤져야 하면 느려질 수 있다, 위 단점과 시너지&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;그래서 뭐가 더 좋은데?&lt;/h1&gt;
&lt;p&gt;둘 다 정답은 없다. 트레이드오프가 있을 뿐.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;PostgreSQL&lt;/th&gt;
&lt;th&gt;MySQL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;저장 방식&lt;/td&gt;
&lt;td&gt;새 튜플 추가 (append-only)&lt;/td&gt;
&lt;td&gt;원본 수정 + undo log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;디스크 사용량&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UPDATE&lt;/code&gt; 많으면 죽을게&lt;/td&gt;
&lt;td&gt;상대적으로 안정적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SELECT 성능&lt;/td&gt;
&lt;td&gt;어 형이야 형은 락 거는 법 몰라&lt;/td&gt;
&lt;td&gt;격리 수준에 따라 다름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VACUUM / Purge&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VACUUM&lt;/code&gt; 필요 (안 한다고? 그럼죽어)&lt;/td&gt;
&lt;td&gt;purge 스레드가 자동 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;롤백 속도&lt;/td&gt;
&lt;td&gt;임시 튜플 삭제만 하면 끝&lt;/td&gt;
&lt;td&gt;undo log 뒤져서 복구해야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;원자성&lt;/td&gt;
&lt;td&gt;레퍼런스 교체로 딸깍&lt;/td&gt;
&lt;td&gt;undo log 기반 복구&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;./image-1.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;요약&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;PostgreSQL: 읽기 성능이 중요하고, 디스크 넉넉할 때. MVCC가 진짜로 &quot;쌓아올린다&quot;에 가깝다&lt;/li&gt;
&lt;li&gt;MySQL: 읽기/쓰기 밸런스가 중요하고, 디스크를 아껴야 할 때. Undo log로 타협한 MVCC&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>OpenAI Responses API SSE Reversing Notes</title><link>https://blog.euroka.moe/posts/reversing/openai-responses-sse/en/</link><guid isPermaLink="true">https://blog.euroka.moe/posts/reversing/openai-responses-sse/en/</guid><description>SSE structure and usage patterns of the Responses API reverse-engineered from the OpenAI library source code</description><pubDate>Sat, 20 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;OpenAI Responses API — SSE Streaming Internals&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Source&lt;/strong&gt;: OpenAI Python SDK openai-python (Stainless-generated)&lt;br /&gt;
&lt;strong&gt;Scope&lt;/strong&gt;: SSE (Server-Sent Events) only. WebSocket mode excluded.&lt;br /&gt;
&lt;strong&gt;Last updated&lt;/strong&gt;: 2026-06-20&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;1. High-Level Architecture&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TB
    subgraph User[&quot;User Code&quot;]
        A[&quot;client.responses.create(stream=True)&quot;]
    end
    subgraph HTTP[&quot;HTTP POST /v1/responses&quot;]
        B[&quot;POST /v1/responses&amp;lt;br/&amp;gt;headers: text/event-stream&amp;lt;br/&amp;gt;body: { stream: true }&quot;]
    end
    subgraph SSE[&quot;SSE Decoding&quot;]
        C[&quot;SSEDecoder.decode(line)&quot;]
        D[&quot;ServerSentEvent&quot;]
        E[&quot;construct_type(discriminator=type)&quot;]
    end
    subgraph SDK[&quot;SDK Layer&quot;]
        F[&quot;ResponseStreamState&amp;lt;br/&amp;gt;(accumulate + handle)&quot;]
        G[&quot;ResponseStreamEvent[]&quot;]
    end
    A --&amp;gt; B
    B --&amp;gt;|chunked transfer| C
    C --&amp;gt;|line by line| D
    D --&amp;gt;|JSON dict| E
    E --&amp;gt;|typed event| F
    F --&amp;gt;|yields| G
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or via the convenience API:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;with client.responses.stream(input=&quot;...&quot;, model=&quot;gpt-4o&quot;) as stream:
    for event in stream:
        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which uses &lt;code&gt;ResponseStreamManager&lt;/code&gt; → &lt;code&gt;ResponseStream&lt;/code&gt; internally.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. SSE Wire Format&lt;/h2&gt;
&lt;p&gt;Unlike the Assistants API (which uses named SSE event: fields like thread.message.delta), &lt;strong&gt;the Responses API embeds the event type directly in the JSON payload&lt;/strong&gt; as a type field.&lt;/p&gt;
&lt;p&gt;Each SSE chunk looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;data: {&quot;type&quot;:&quot;response.created&quot;,&quot;response&quot;:{...},&quot;sequence_number&quot;:0}

data: {&quot;type&quot;:&quot;response.in_progress&quot;,&quot;response&quot;:{...},&quot;sequence_number&quot;:1}

data: {&quot;type&quot;:&quot;response.output_item.added&quot;,&quot;output_index&quot;:0,&quot;item&quot;:{...},&quot;sequence_number&quot;:2}

data: {&quot;type&quot;:&quot;response.content_part.added&quot;,&quot;output_index&quot;:0,&quot;item_id&quot;:&quot;...&quot;,&quot;content_index&quot;:0,&quot;part&quot;:{...},&quot;sequence_number&quot;:3}

data: {&quot;type&quot;:&quot;response.output_text.delta&quot;,&quot;output_index&quot;:0,&quot;item_id&quot;:&quot;...&quot;,&quot;content_index&quot;:0,&quot;delta&quot;:&quot;Hello&quot;,&quot;sequence_number&quot;:4}
...
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;event: line is &lt;strong&gt;not&lt;/strong&gt; set for Responses API SSE events.&lt;/li&gt;
&lt;li&gt;The type field inside the JSON body is the discriminator.&lt;/li&gt;
&lt;li&gt;Every event has a sequence_number: int for ordering.&lt;/li&gt;
&lt;li&gt;The stream ends when the connection closes (no [DONE] sentinel for Responses; the [DONE] check in Stream._iter_events is only for legacy Chat Completions).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Full Event Catalog (53 types)&lt;/h2&gt;
&lt;h3&gt;3.1 Response Lifecycle Events&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;type literal&lt;/th&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;response.created&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseCreatedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;response: Response, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;response.queued&lt;/td&gt;
&lt;td&gt;ResponseQueuedEvent&lt;/td&gt;
&lt;td&gt;response: Response, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;response.in_progress&lt;/td&gt;
&lt;td&gt;ResponseInProgressEvent&lt;/td&gt;
&lt;td&gt;response: Response, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;response.completed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseCompletedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;response: Response, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;response.failed&lt;/td&gt;
&lt;td&gt;ResponseFailedEvent&lt;/td&gt;
&lt;td&gt;response: Response, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;response.incomplete&lt;/td&gt;
&lt;td&gt;ResponseIncompleteEvent&lt;/td&gt;
&lt;td&gt;response: Response, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;error&lt;/td&gt;
&lt;td&gt;ResponseErrorEvent&lt;/td&gt;
&lt;td&gt;code: Optional[str], message: str, param: Optional[str], sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.2 Output Items — Structural&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;type literal&lt;/th&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;response.output_item.added&lt;/td&gt;
&lt;td&gt;ResponseOutputItemAddedEvent&lt;/td&gt;
&lt;td&gt;item: &lt;code&gt;ResponseOutputItem&lt;/code&gt;, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;response.output_item.done&lt;/td&gt;
&lt;td&gt;ResponseOutputItemDoneEvent&lt;/td&gt;
&lt;td&gt;item: &lt;code&gt;ResponseOutputItem&lt;/code&gt;, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;response.content_part.added&lt;/td&gt;
&lt;td&gt;ResponseContentPartAddedEvent&lt;/td&gt;
&lt;td&gt;part: Part, content_index: int, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;response.content_part.done&lt;/td&gt;
&lt;td&gt;ResponseContentPartDoneEvent&lt;/td&gt;
&lt;td&gt;part: Part, content_index: int, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Part is a discriminated union of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ResponseOutputText (type=output_text)&lt;/li&gt;
&lt;li&gt;ResponseOutputRefusal (type=refusal)&lt;/li&gt;
&lt;li&gt;PartReasoningText (type=reasoning_text)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.3 Text Content&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;type literal&lt;/th&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;response.output_text.delta&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseTextDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;delta: str, content_index: int, item_id: str, output_index: int, logprobs: List[Logprob], sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;response.output_text.done&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseTextDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text: str, content_index: int, item_id: str, output_index: int, logprobs: List[Logprob], sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;response.output_text.annotation.added&lt;/td&gt;
&lt;td&gt;ResponseOutputTextAnnotationAddedEvent&lt;/td&gt;
&lt;td&gt;annotation: object, annotation_index: int, content_index: int, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;logprob structure&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Logprob:
  token: str
  logprob: float
  top_logprobs: Optional[List[LogprobTopLogprob]]  # up to 20 alternatives

LogprobTopLogprob:
  token: Optional[str]
  logprob: Optional[float]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4 Refusal&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;type literal&lt;/th&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;response.refusal.delta&lt;/td&gt;
&lt;td&gt;ResponseRefusalDeltaEvent&lt;/td&gt;
&lt;td&gt;delta: str, content_index: int, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;response.refusal.done&lt;/td&gt;
&lt;td&gt;ResponseRefusalDoneEvent&lt;/td&gt;
&lt;td&gt;refusal: str, content_index: int, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.5 Function Calling&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;type literal&lt;/th&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;response.function_call_arguments.delta&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseFunctionCallArgumentsDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;delta: str, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;response.function_call_arguments.done&lt;/td&gt;
&lt;td&gt;ResponseFunctionCallArgumentsDoneEvent&lt;/td&gt;
&lt;td&gt;arguments: str, name: str, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.6 MCP (Model Context Protocol)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;type literal&lt;/th&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;response.mcp_call.in_progress&lt;/td&gt;
&lt;td&gt;ResponseMcpCallInProgressEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;response.mcp_call_arguments.delta&lt;/td&gt;
&lt;td&gt;ResponseMcpCallArgumentsDeltaEvent&lt;/td&gt;
&lt;td&gt;delta: str (JSON partial), item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;response.mcp_call_arguments.done&lt;/td&gt;
&lt;td&gt;ResponseMcpCallArgumentsDoneEvent&lt;/td&gt;
&lt;td&gt;arguments: str (JSON full), item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;response.mcp_call.completed&lt;/td&gt;
&lt;td&gt;ResponseMcpCallCompletedEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;response.mcp_call.failed&lt;/td&gt;
&lt;td&gt;ResponseMcpCallFailedEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;response.mcp_list_tools.in_progress&lt;/td&gt;
&lt;td&gt;ResponseMcpListToolsInProgressEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;response.mcp_list_tools.completed&lt;/td&gt;
&lt;td&gt;ResponseMcpListToolsCompletedEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;response.mcp_list_tools.failed&lt;/td&gt;
&lt;td&gt;ResponseMcpListToolsFailedEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.7 Audio&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;type literal&lt;/th&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;response.audio.delta&lt;/td&gt;
&lt;td&gt;ResponseAudioDeltaEvent&lt;/td&gt;
&lt;td&gt;delta: str (Base64 audio bytes), sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;response.audio.done&lt;/td&gt;
&lt;td&gt;ResponseAudioDoneEvent&lt;/td&gt;
&lt;td&gt;sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;response.audio.transcript.delta&lt;/td&gt;
&lt;td&gt;ResponseAudioTranscriptDeltaEvent&lt;/td&gt;
&lt;td&gt;delta: str, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;response.audio.transcript.done&lt;/td&gt;
&lt;td&gt;ResponseAudioTranscriptDoneEvent&lt;/td&gt;
&lt;td&gt;sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.8 Web Search Tool&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;type literal&lt;/th&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;response.web_search_call.in_progress&lt;/td&gt;
&lt;td&gt;ResponseWebSearchCallInProgressEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;response.web_search_call.searching&lt;/td&gt;
&lt;td&gt;ResponseWebSearchCallSearchingEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;response.web_search_call.completed&lt;/td&gt;
&lt;td&gt;ResponseWebSearchCallCompletedEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.9 File Search Tool&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;type literal&lt;/th&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;response.file_search_call.in_progress&lt;/td&gt;
&lt;td&gt;ResponseFileSearchCallInProgressEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;response.file_search_call.searching&lt;/td&gt;
&lt;td&gt;ResponseFileSearchCallSearchingEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;response.file_search_call.completed&lt;/td&gt;
&lt;td&gt;ResponseFileSearchCallCompletedEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.10 Code Interpreter Tool&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;type literal&lt;/th&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;response.code_interpreter_call.in_progress&lt;/td&gt;
&lt;td&gt;ResponseCodeInterpreterCallInProgressEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;response.code_interpreter_call.interpreting&lt;/td&gt;
&lt;td&gt;ResponseCodeInterpreterCallInterpretingEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;response.code_interpreter_call_code.delta&lt;/td&gt;
&lt;td&gt;ResponseCodeInterpreterCallCodeDeltaEvent&lt;/td&gt;
&lt;td&gt;delta: str, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;response.code_interpreter_call_code.done&lt;/td&gt;
&lt;td&gt;ResponseCodeInterpreterCallCodeDoneEvent&lt;/td&gt;
&lt;td&gt;code: str, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;response.code_interpreter_call.completed&lt;/td&gt;
&lt;td&gt;ResponseCodeInterpreterCallCompletedEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.11 Reasoning&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;type literal&lt;/th&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;response.reasoning_text.delta&lt;/td&gt;
&lt;td&gt;ResponseReasoningTextDeltaEvent&lt;/td&gt;
&lt;td&gt;delta: str, content_index: int, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;response.reasoning_text.done&lt;/td&gt;
&lt;td&gt;ResponseReasoningTextDoneEvent&lt;/td&gt;
&lt;td&gt;text: str, content_index: int, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;response.reasoning_summary_part.added&lt;/td&gt;
&lt;td&gt;ResponseReasoningSummaryPartAddedEvent&lt;/td&gt;
&lt;td&gt;part: Part (summary_text), summary_index: int, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;response.reasoning_summary_part.done&lt;/td&gt;
&lt;td&gt;ResponseReasoningSummaryPartDoneEvent&lt;/td&gt;
&lt;td&gt;part: Part (summary_text), summary_index: int, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;response.reasoning_summary_text.delta&lt;/td&gt;
&lt;td&gt;ResponseReasoningSummaryTextDeltaEvent&lt;/td&gt;
&lt;td&gt;delta: str, summary_index: int, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;response.reasoning_summary_text.done&lt;/td&gt;
&lt;td&gt;ResponseReasoningSummaryTextDoneEvent&lt;/td&gt;
&lt;td&gt;text: str, summary_index: int, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.12 Image Generation&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;type literal&lt;/th&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;response.image_generation_call.in_progress&lt;/td&gt;
&lt;td&gt;ResponseImageGenCallInProgressEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;response.image_generation_call.generating&lt;/td&gt;
&lt;td&gt;ResponseImageGenCallGeneratingEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;response.image_generation_call.partial_image&lt;/td&gt;
&lt;td&gt;ResponseImageGenCallPartialImageEvent&lt;/td&gt;
&lt;td&gt;partial_image_b64: str, partial_image_index: int (0-based), item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;response.image_generation_call.completed&lt;/td&gt;
&lt;td&gt;ResponseImageGenCallCompletedEvent&lt;/td&gt;
&lt;td&gt;item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.13 Custom Tool Call&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;type literal&lt;/th&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;response.custom_tool_call_input.delta&lt;/td&gt;
&lt;td&gt;ResponseCustomToolCallInputDeltaEvent&lt;/td&gt;
&lt;td&gt;delta: str, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;response.custom_tool_call_input.done&lt;/td&gt;
&lt;td&gt;ResponseCustomToolCallInputDoneEvent&lt;/td&gt;
&lt;td&gt;input: str, item_id: str, output_index: int, sequence_number: int&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;4. SSE Parsing Pipeline (Wire to Typed Event)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TB
    subgraph HTTP[&quot;HTTP Response Stream&quot;]
        A[&quot;httpx.Response.iter_bytes()&quot;]
    end
    subgraph SSE[&quot;SSE Decoder (WHATWG SSE)&quot;]
        B[&quot;SSEDecoder.decode(line)&quot;]
        C[&quot;ServerSentEvent&amp;lt;br/&amp;gt;.event - None for Responses&amp;lt;br/&amp;gt;.data - raw JSON string&quot;]
    end
    subgraph DISPATCH[&quot;Stream Dispatcher&quot;]
        D{&quot;sse.data starts with [DONE]?&quot;}
        E{&quot;sse.event starts with thread.?&quot;}
        F[&quot;construct_type(discriminator=type)&quot;]
    end
    subgraph OUT[&quot;Typed Event&quot;]
        G[&quot;ResponseCreatedEvent |&amp;lt;br/&amp;gt;ResponseTextDeltaEvent |&amp;lt;br/&amp;gt;... (52 more types)&quot;]
    end
    A --&amp;gt; B
    B --&amp;gt; C
    C --&amp;gt; D
    D --&amp;gt;|yes| H[&quot;break (legacy)&quot;]
    D --&amp;gt;|no| E
    E --&amp;gt;|yes| I[&quot;thread. events (Assistants)&quot;]
    E --&amp;gt;|no| F
    F --&amp;gt; G
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Key decoder details&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;SSEDecoder accumulates lines until a blank line (\n\n, \r\r, or \r\n\r\n).&lt;/li&gt;
&lt;li&gt;Each SSE field (event:, data:, id:, retry:) is processed per spec.&lt;/li&gt;
&lt;li&gt;For Responses, event: is absent → &lt;code&gt;ServerSentEvent&lt;/code&gt;.event is None.&lt;/li&gt;
&lt;li&gt;The raw JSON data: is deserialized and passed to &lt;code&gt;construct_type&lt;/code&gt;().&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ResponseStreamEvent&lt;/code&gt; is Annotated[Union[...], PropertyInfo(discriminator=&quot;type&quot;)], so &lt;code&gt;construct_type&lt;/code&gt; examines the &quot;type&quot; key to dispatch to the correct subclass.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;5. SDK Convenience API: &lt;code&gt;ResponseStream&lt;/code&gt; / &lt;code&gt;ResponseStreamState&lt;/code&gt;&lt;/h2&gt;
&lt;h3&gt;5.1 &lt;code&gt;ResponseStreamManager&lt;/code&gt; (entry point)&lt;/h3&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;with client.responses.stream(
    input=&quot;Hello&quot;, model=&quot;gpt-4o&quot;
) as stream:  # returns ResponseStream[TextFormatT]
    for event in stream:
        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The manager:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Calls client.responses.create(stream=True, ...) to get the raw Stream[&lt;code&gt;ResponseStreamEvent&lt;/code&gt;].&lt;/li&gt;
&lt;li&gt;Wraps it in &lt;code&gt;ResponseStream&lt;/code&gt;(raw_stream=..., text_format=..., input_tools=...).&lt;/li&gt;
&lt;li&gt;Exposes the stream as an iterator of &lt;code&gt;ResponseStreamEvent&lt;/code&gt;[TextFormatT].&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;5.2 &lt;code&gt;ResponseStream&lt;/code&gt; → &lt;code&gt;ResponseStreamState&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ResponseStream&lt;/code&gt;.&lt;code&gt;__stream__()&lt;/code&gt; delegates to &lt;code&gt;ResponseStreamState&lt;/code&gt;.handle_event(sse_event) for each raw event.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;handle_event()&lt;/strong&gt; produces &lt;strong&gt;1 or 2 events&lt;/strong&gt; per raw SSE event:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Raw SSE type&lt;/th&gt;
&lt;th&gt;Emitted events&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;response.output_text.delta&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseTextDeltaEvent&lt;/code&gt; (with snapshot field added — accumulated text so far)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;response.output_text.done&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseTextDoneEvent&lt;/code&gt; (with parsed field if text_format is set)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;response.function_call_arguments.delta&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseFunctionCallArgumentsDeltaEvent&lt;/code&gt; (with snapshot — accumulated args so far)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;response.completed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseCompletedEvent&lt;/code&gt; (with response being the full parsed &lt;code&gt;ParsedResponse&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All others&lt;/td&gt;
&lt;td&gt;Passed through as-is&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;5.3 accumulate_event() — State Machine&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ResponseStreamState&lt;/code&gt; maintains a &lt;code&gt;__current_snapshot&lt;/code&gt; (partial &lt;code&gt;ParsedResponse&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;response.created&lt;/strong&gt; → Creates initial snapshot from &lt;code&gt;event.response&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;response.output_item.added&lt;/strong&gt; → Appends item to &lt;code&gt;snapshot.output[]&lt;/code&gt; (handles function_call, message, and generic types).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;response.content_part.added&lt;/strong&gt; → Appends content part to the indexed output message.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;response.output_text.delta&lt;/strong&gt; → Concatenates delta to the matching output_text content&apos;s text.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;response.function_call_arguments.delta&lt;/strong&gt; → Concatenates delta to the matching function call&apos;s arguments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;response.completed&lt;/strong&gt; → Stores the fully parsed response via &lt;code&gt;parse_response&lt;/code&gt;() (also handles structured output parsing).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.4 Convenience methods&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;stream.&lt;code&gt;get_final_response&lt;/code&gt;() → Consumes entire stream, returns the accumulated &lt;code&gt;ParsedResponse&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;stream.&lt;code&gt;until_done&lt;/code&gt;() → Blocks until stream fully consumed.&lt;/li&gt;
&lt;li&gt;stream.close() → Closes underlying httpx response.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.5 &lt;code&gt;ResponseStreamEvent&lt;/code&gt;[TextFormatT] (SDK wrapper types)&lt;/h3&gt;
&lt;p&gt;These subclass or wrap the raw generated types with extra fields:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Raw type&lt;/th&gt;
&lt;th&gt;SDK wrapper&lt;/th&gt;
&lt;th&gt;Extra fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResponseTextDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;lib.streaming.responses.&lt;code&gt;ResponseTextDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;snapshot: str (accumulated text)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResponseTextDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;lib.streaming.responses.&lt;code&gt;ResponseTextDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;parsed: Optional[TextFormatT]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResponseFunctionCallArgumentsDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;lib.streaming.responses.&lt;code&gt;ResponseFunctionCallArgumentsDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;snapshot: str (accumulated args)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResponseCompletedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;lib.streaming.responses.&lt;code&gt;ResponseCompletedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;response: &lt;code&gt;ParsedResponse&lt;/code&gt;[TextFormatT]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The composite &lt;code&gt;ResponseStreamEvent&lt;/code&gt; type alias used by the stream is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ResponseStreamEvent = Annotated[Union[
    ResponseTextDeltaEvent,       # SDK wrapper
    ResponseTextDoneEvent,        # SDK wrapper
    ResponseFunctionCallArgumentsDeltaEvent,  # SDK wrapper
    ResponseCompletedEvent,       # SDK wrapper
    ResponseAudioDeltaEvent,      # raw
    ... (all other raw events)    # raw
], PropertyInfo(discriminator=&quot;type&quot;)]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. Background Mode Streaming&lt;/h2&gt;
&lt;p&gt;When background=True is passed to responses.stream():&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The API request includes &quot;background&quot;: true.&lt;/li&gt;
&lt;li&gt;The server immediately returns a &lt;code&gt;response.created&lt;/code&gt; event with the response id.&lt;/li&gt;
&lt;li&gt;The stream stays open but yields events asynchronously as the background processing progresses.&lt;/li&gt;
&lt;li&gt;The stream can be &lt;strong&gt;interrupted and resumed&lt;/strong&gt;: note the response_id from response.created, then later call responses.stream(response_id=id, starting_after=N) to resume from sequence number N.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Usage pattern:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;with client.responses.stream(input=&quot;...&quot;, model=&quot;gpt-4o&quot;, background=True) as stream:
    for event in stream:
        if event.type == &quot;response.created&quot;:
            id = event.response.id
        if event.sequence_number == 10:
            break

# Later...
with client.responses.stream(response_id=id, starting_after=10) as stream:
    for event in stream:
        ...
    response = stream.get_final_response()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;7. Data Flow Summary&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TB
    subgraph CLIENT[&quot;Client&quot;]
        A[&quot;HTTP POST /v1/responses&amp;lt;br/&amp;gt;{ stream: true }&quot;]
    end
    subgraph PROXY[&quot;HTTP Transport&quot;]
        B[&quot;Content-Type: text/event-stream&amp;lt;br/&amp;gt;chunked transfer&quot;]
    end
    subgraph PARSE[&quot;SSE Parser&quot;]
        C[&quot;SSEDecoder&amp;lt;br/&amp;gt;- buffers \n\n chunks&amp;lt;br/&amp;gt;- parses event:/data:&quot;]
    end
    subgraph DISP[&quot;Type Dispatch&quot;]
        D[&quot;construct_type()&amp;lt;br/&amp;gt;via type discriminator&quot;]
    end
    subgraph ACC[&quot;Accumulator&quot;]
        E[&quot;ResponseStreamState&amp;lt;br/&amp;gt;- maintains snapshot&amp;lt;br/&amp;gt;- adds snapshot/parsed&quot;]
    end
    subgraph USER[&quot;User&quot;]
        F[&quot;for event in stream:&quot;]
    end
    A --&amp;gt; B
    B --&amp;gt; C
    C --&amp;gt; D
    D --&amp;gt; E
    E --&amp;gt; F
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;8. Wire-Level Example (Synthetic)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;data: {&quot;type&quot;:&quot;response.created&quot;,&quot;response&quot;:{&quot;id&quot;:&quot;resp_abc123&quot;,&quot;object&quot;:&quot;response&quot;,&quot;status&quot;:&quot;queued&quot;,&quot;created_at&quot;:1718000000,&quot;model&quot;:&quot;gpt-4o&quot;,&quot;output&quot;:[],&quot;tools&quot;:[],&quot;tool_choice&quot;:&quot;auto&quot;,&quot;parallel_tool_calls&quot;:true,&quot;top_p&quot;:1,&quot;temperature&quot;:1},&quot;sequence_number&quot;:0}

data: {&quot;type&quot;:&quot;response.queued&quot;,&quot;response&quot;:{&quot;id&quot;:&quot;resp_abc123&quot;,...},&quot;sequence_number&quot;:1}

data: {&quot;type&quot;:&quot;response.in_progress&quot;,&quot;response&quot;:{&quot;id&quot;:&quot;resp_abc123&quot;,&quot;status&quot;:&quot;in_progress&quot;,...},&quot;sequence_number&quot;:2}

data: {&quot;type&quot;:&quot;response.output_item.added&quot;,&quot;output_index&quot;:0,&quot;item&quot;:{&quot;id&quot;:&quot;item_1&quot;,&quot;type&quot;:&quot;message&quot;,&quot;role&quot;:&quot;assistant&quot;,&quot;content&quot;:[],&quot;status&quot;:&quot;in_progress&quot;},&quot;sequence_number&quot;:3}

data: {&quot;type&quot;:&quot;response.content_part.added&quot;,&quot;output_index&quot;:0,&quot;item_id&quot;:&quot;item_1&quot;,&quot;content_index&quot;:0,&quot;part&quot;:{&quot;type&quot;:&quot;output_text&quot;,&quot;text&quot;:&quot;&quot;,&quot;annotations&quot;:[]},&quot;sequence_number&quot;:4}

data: {&quot;type&quot;:&quot;response.output_text.delta&quot;,&quot;output_index&quot;:0,&quot;item_id&quot;:&quot;item_1&quot;,&quot;content_index&quot;:0,&quot;delta&quot;:&quot;Hello&quot;,&quot;sequence_number&quot;:5,&quot;logprobs&quot;:[{&quot;token&quot;:&quot;Hello&quot;,&quot;logprob&quot;:-0.01}]}

data: {&quot;type&quot;:&quot;response.output_text.delta&quot;,&quot;output_index&quot;:0,&quot;item_id&quot;:&quot;item_1&quot;,&quot;content_index&quot;:0,&quot;delta&quot;:&quot; world&quot;,&quot;sequence_number&quot;:6,&quot;logprobs&quot;:[...]}

data: {&quot;type&quot;:&quot;response.output_text.done&quot;,&quot;output_index&quot;:0,&quot;item_id&quot;:&quot;item_1&quot;,&quot;content_index&quot;:0,&quot;text&quot;:&quot;Hello world&quot;,&quot;sequence_number&quot;:7,&quot;logprobs&quot;:[...]}

data: {&quot;type&quot;:&quot;response.content_part.done&quot;,&quot;output_index&quot;:0,&quot;item_id&quot;:&quot;item_1&quot;,&quot;content_index&quot;:0,&quot;part&quot;:{&quot;type&quot;:&quot;output_text&quot;,&quot;text&quot;:&quot;Hello world&quot;,&quot;annotations&quot;:[]},&quot;sequence_number&quot;:8}

data: {&quot;type&quot;:&quot;response.output_item.done&quot;,&quot;output_index&quot;:0,&quot;item&quot;:{&quot;id&quot;:&quot;item_1&quot;,&quot;type&quot;:&quot;message&quot;,&quot;role&quot;:&quot;assistant&quot;,&quot;content&quot;:[{&quot;type&quot;:&quot;output_text&quot;,&quot;text&quot;:&quot;Hello world&quot;,&quot;annotations&quot;:[]}],&quot;status&quot;:&quot;completed&quot;},&quot;sequence_number&quot;:9}

data: {&quot;type&quot;:&quot;response.completed&quot;,&quot;response&quot;:{&quot;id&quot;:&quot;resp_abc123&quot;,&quot;status&quot;:&quot;completed&quot;,&quot;output&quot;:[{&quot;id&quot;:&quot;item_1&quot;,...}],...},&quot;sequence_number&quot;:10}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;9. Implementation Reference (Key Files in SDK)&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;src/openai/_streaming.py&lt;/td&gt;
&lt;td&gt;Stream[T], AsyncStream[T], SSEDecoder, SSEBytesDecoder, &lt;code&gt;ServerSentEvent&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;src/openai/_response.py&lt;/td&gt;
&lt;td&gt;APIResponse, _parse() → constructs Stream[T]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;src/openai/_base_client.py&lt;/td&gt;
&lt;td&gt;request(), _process_response(), _process_response_data(), _make_sse_decoder(), make_request_options()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;src/openai/types/responses/response_stream_event.py&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseStreamEvent&lt;/code&gt; discriminated union&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;src/openai/lib/streaming/responses/_responses.py&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseStream&lt;/code&gt;, &lt;code&gt;AsyncResponseStream&lt;/code&gt;, &lt;code&gt;ResponseStreamManager&lt;/code&gt;, &lt;code&gt;ResponseStreamState&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;src/openai/lib/streaming/responses/_events.py&lt;/td&gt;
&lt;td&gt;SDK-enhanced wrapper types + &lt;code&gt;ResponseStreamEvent&lt;/code&gt; (lib version)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;src/openai/resources/responses/responses.py&lt;/td&gt;
&lt;td&gt;Responses.create(), Responses.stream(), WebSocket connection code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;10. Event Taxonomy Diagram&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;graph LR
    subgraph LIFECYCLE[&quot;Response Lifecycle&quot;]
        RC[&quot;response.created&quot;] --&amp;gt; RQ[&quot;response.queued&quot;]
        RQ --&amp;gt; RI[&quot;response.in_progress&quot;]
        RI --&amp;gt; RC2[&quot;response.completed&quot;]
        RI --&amp;gt; RF[&quot;response.failed&quot;]
        RI --&amp;gt; RINC[&quot;response.incomplete&quot;]
    end
    subgraph CONTENT[&quot;Content Delta&quot;]
        OT[&quot;output_text.delta&quot;] --&amp;gt; OTD[&quot;output_text.done&quot;]
        OTD --&amp;gt; OTA[&quot;output_text.annotation&quot;]
        RD[&quot;refusal.delta&quot;] --&amp;gt; RD2[&quot;refusal.done&quot;]
        AD[&quot;audio.delta&quot;] --&amp;gt; ADD[&quot;audio.done&quot;]
        ATD[&quot;audio.transcript.delta&quot;] --&amp;gt; ATDD[&quot;audio.transcript.done&quot;]
        RT[&quot;reasoning_text.delta&quot;] --&amp;gt; RTD[&quot;reasoning_text.done&quot;]
        RS[&quot;reasoning_summary_part&amp;lt;br/&amp;gt;reasoning_summary_text&quot;]
    end
    subgraph STRUCT[&quot;Output Structure&quot;]
        OIA[&quot;output_item.added&quot;] --&amp;gt; OID[&quot;output_item.done&quot;]
        CPA[&quot;content_part.added&quot;] --&amp;gt; CPD[&quot;content_part.done&quot;]
    end
    subgraph IMAGE[&quot;Image Generation&quot;]
        IGI[&quot;image_gen_call.in_progress&quot;] --&amp;gt; IGG[&quot;image_gen_call.generating&quot;]
        IGG --&amp;gt; IGP[&quot;image_gen_call.partial_image&quot;]
        IGP --&amp;gt; IGC[&quot;image_gen_call.completed&quot;]
    end
    subgraph TOOLS[&quot;Tool Calls&quot;]
        FC[&quot;function_call_arguments.delta/done&quot;]
        MCP[&quot;mcp_call.* (8 events)&quot;]
        WS[&quot;web_search_call.* (3 events)&quot;]
        FS[&quot;file_search_call.* (3 events)&quot;]
        CI[&quot;code_interpreter_call.* (5 events)&quot;]
        CT[&quot;custom_tool_call_input.delta/done&quot;]
    end
    LIFECYCLE --&amp;gt; CONTENT
    LIFECYCLE --&amp;gt; STRUCT
    LIFECYCLE --&amp;gt; TOOLS
    LIFECYCLE --&amp;gt; IMAGE
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pro tip&lt;/strong&gt;: The SDK docstrings only document response.created. Use the type definitions from src/openai/types/responses/response_&amp;lt;name&amp;gt;_event.py as the canonical source. Every event has the type literal (matching the type field in the JSON), sequence_number: int, and event-specific fields.&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>OpenAI Responses API SSE 리버싱 노트</title><link>https://blog.euroka.moe/posts/reversing/openai-responses-sse/</link><guid isPermaLink="true">https://blog.euroka.moe/posts/reversing/openai-responses-sse/</guid><description>OpenAI 라이브러리 소스 코드에서 알아낸 Responses API의 SSE 구조와 사용법</description><pubDate>Sat, 20 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;OpenAI Responses API — SSE 스트리밍 내부 구조&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;출처&lt;/strong&gt;: OpenAI Python SDK &lt;code&gt;openai-python&lt;/code&gt; (Stainless 생성)
&lt;strong&gt;범위&lt;/strong&gt;: SSE (Server-Sent Events)만 다룸. WebSocket 모드 제외.
&lt;strong&gt;마지막 업데이트&lt;/strong&gt;: 2026-06-20&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 상위 수준 아키텍처&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TB
    subgraph User[&quot;User Code&quot;]
        A[&quot;client.responses.create(stream=True)&quot;]
    end
    subgraph HTTP[&quot;HTTP Layer&quot;]
        B[&quot;POST /v1/responses&amp;lt;br/&amp;gt;Accept: text/event-stream&amp;lt;br/&amp;gt;body: { ..., &apos;stream&apos;: true }&quot;]
    end
    subgraph SSE[&quot;SSE Decoding&quot;]
        C[&quot;httpx.Response (청크 전송)&quot;]
        D[&quot;Stream[ResponseStreamEvent]&quot;]
        E[&quot;SSEDecoder.iter_bytes()&quot;]
        F[&quot;ServerSentEvent.json()&quot;]
        G[&quot;construct_type(discriminator=&apos;type&apos;)&quot;]
    end
    subgraph Wrapper[&quot;SDK Wrapper&quot;]
        H[&quot;ResponseStreamState&quot;]
        I[&quot;ResponseStream (이터레이터)&quot;]
    end
    A --&amp;gt; B --&amp;gt; C --&amp;gt; D
    C --&amp;gt; E --&amp;gt; F --&amp;gt; G --&amp;gt; D
    D --&amp;gt; H --&amp;gt; I
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;또는 편의 API 사용:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;with client.responses.stream(input=&quot;...&quot;, model=&quot;gpt-4o&quot;) as stream:
    for event in stream:
        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;내부적으로 &lt;code&gt;ResponseStreamManager&lt;/code&gt; → &lt;code&gt;ResponseStream&lt;/code&gt;을 사용한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. SSE 와이어 포맷&lt;/h2&gt;
&lt;p&gt;Assistants API(이벤트명 &lt;code&gt;thread.message.delta&lt;/code&gt; 등으로 구분)와 달리, &lt;strong&gt;Responses API는 JSON 페이로드 내부에 &lt;code&gt;type&lt;/code&gt; 필드로 이벤트 타입을 구분&lt;/strong&gt;한다.&lt;/p&gt;
&lt;p&gt;각 SSE 청크는 다음과 같다:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;data: {&quot;type&quot;:&quot;response.created&quot;,&quot;response&quot;:{...},&quot;sequence_number&quot;:0}

data: {&quot;type&quot;:&quot;response.in_progress&quot;,&quot;response&quot;:{...},&quot;sequence_number&quot;:1}

data: {&quot;type&quot;:&quot;response.output_item.added&quot;,&quot;output_index&quot;:0,&quot;item&quot;:{...},&quot;sequence_number&quot;:2}

data: {&quot;type&quot;:&quot;response.content_part.added&quot;,&quot;output_index&quot;:0,&quot;item_id&quot;:&quot;...&quot;,&quot;content_index&quot;:0,&quot;part&quot;:{...},&quot;sequence_number&quot;:3}

data: {&quot;type&quot;:&quot;response.output_text.delta&quot;,&quot;output_index&quot;:0,&quot;item_id&quot;:&quot;...&quot;,&quot;content_index&quot;:0,&quot;delta&quot;:&quot;Hello&quot;,&quot;sequence_number&quot;:4}
...
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Responses API SSE 이벤트에는 &lt;code&gt;event:&lt;/code&gt; 라인이 &lt;strong&gt;설정되지 않는다&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;JSON 본문 내부의 &lt;code&gt;type&lt;/code&gt; 필드가 구분자(discriminator) 역할을 한다.&lt;/li&gt;
&lt;li&gt;모든 이벤트에는 순서 보장용 &lt;code&gt;sequence_number: int&lt;/code&gt;가 있다.&lt;/li&gt;
&lt;li&gt;스트림은 연결 종료 시 끝난다(Responses에는 &lt;code&gt;[DONE]&lt;/code&gt; 센티넬이 없음. &lt;code&gt;Stream._iter_events&lt;/code&gt;의 &lt;code&gt;[DONE]&lt;/code&gt; 체크는 레거시 Chat Completions 전용).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 전체 이벤트 목록 (53종)&lt;/h2&gt;
&lt;h3&gt;3.1 Response 생명주기 이벤트&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;&lt;code&gt;type&lt;/code&gt; 값&lt;/th&gt;
&lt;th&gt;클래스&lt;/th&gt;
&lt;th&gt;필드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.created&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseCreatedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response: Response&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.queued&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseQueuedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response: Response&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.in_progress&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseInProgressEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response: Response&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.completed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseCompletedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response: Response&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.failed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseFailedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response: Response&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.incomplete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseIncompleteEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response: Response&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;code&gt;error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseErrorEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;code: Optional[str]&lt;/code&gt;, &lt;code&gt;message: str&lt;/code&gt;, &lt;code&gt;param: Optional[str]&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.2 출력 항목 — 구조적&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;&lt;code&gt;type&lt;/code&gt; 값&lt;/th&gt;
&lt;th&gt;클래스&lt;/th&gt;
&lt;th&gt;필드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.output_item.added&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseOutputItemAddedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item: ResponseOutputItem&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.output_item.done&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseOutputItemDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item: ResponseOutputItem&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.content_part.added&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseContentPartAddedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;part: Part&lt;/code&gt;, &lt;code&gt;content_index: int&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.content_part.done&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseContentPartDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;part: Part&lt;/code&gt;, &lt;code&gt;content_index: int&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;Part&lt;/code&gt;는 다음 중 하나의 구분된 유니온:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ResponseOutputText&lt;/code&gt; (type=&lt;code&gt;output_text&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ResponseOutputRefusal&lt;/code&gt; (type=&lt;code&gt;refusal&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PartReasoningText&lt;/code&gt; (type=&lt;code&gt;reasoning_text&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.3 텍스트 콘텐츠&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;&lt;code&gt;type&lt;/code&gt; 값&lt;/th&gt;
&lt;th&gt;클래스&lt;/th&gt;
&lt;th&gt;필드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.output_text.delta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseTextDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;delta: str&lt;/code&gt;, &lt;code&gt;content_index: int&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;logprobs: List[Logprob]&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.output_text.done&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseTextDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text: str&lt;/code&gt;, &lt;code&gt;content_index: int&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;logprobs: List[Logprob]&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.output_text.annotation.added&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseOutputTextAnnotationAddedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;annotation: object&lt;/code&gt;, &lt;code&gt;annotation_index: int&lt;/code&gt;, &lt;code&gt;content_index: int&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;logprob 구조&lt;/strong&gt;:&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code&gt;class Logprob:
    token: str
    logprob: float
    top_logprobs: Optional[List[LogprobTopLogprob]]  # 최대 20개 대안

class LogprobTopLogprob:
    token: Optional[str]
    logprob: Optional[float]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4 거절(Refusal)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;&lt;code&gt;type&lt;/code&gt; 값&lt;/th&gt;
&lt;th&gt;클래스&lt;/th&gt;
&lt;th&gt;필드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.refusal.delta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseRefusalDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;delta: str&lt;/code&gt;, &lt;code&gt;content_index: int&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.refusal.done&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseRefusalDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refusal: str&lt;/code&gt;, &lt;code&gt;content_index: int&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.5 함수 호출(Function Calling)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;&lt;code&gt;type&lt;/code&gt; 값&lt;/th&gt;
&lt;th&gt;클래스&lt;/th&gt;
&lt;th&gt;필드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.function_call_arguments.delta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseFunctionCallArgumentsDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;delta: str&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.function_call_arguments.done&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseFunctionCallArgumentsDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;arguments: str&lt;/code&gt;, &lt;code&gt;name: str&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.6 MCP (Model Context Protocol)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;&lt;code&gt;type&lt;/code&gt; 값&lt;/th&gt;
&lt;th&gt;클래스&lt;/th&gt;
&lt;th&gt;필드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.mcp_call.in_progress&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseMcpCallInProgressEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.mcp_call_arguments.delta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseMcpCallArgumentsDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;delta: str&lt;/code&gt; (JSON 부분), &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.mcp_call_arguments.done&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseMcpCallArgumentsDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;arguments: str&lt;/code&gt; (JSON 전체), &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.mcp_call.completed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseMcpCallCompletedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.mcp_call.failed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseMcpCallFailedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.mcp_list_tools.in_progress&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseMcpListToolsInProgressEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.mcp_list_tools.completed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseMcpListToolsCompletedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.mcp_list_tools.failed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseMcpListToolsFailedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.7 오디오&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;&lt;code&gt;type&lt;/code&gt; 값&lt;/th&gt;
&lt;th&gt;클래스&lt;/th&gt;
&lt;th&gt;필드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.audio.delta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseAudioDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;delta: str&lt;/code&gt; (Base64 오디오 바이트), &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.audio.done&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseAudioDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.audio.transcript.delta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseAudioTranscriptDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;delta: str&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.audio.transcript.done&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseAudioTranscriptDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.8 웹 검색 도구&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;&lt;code&gt;type&lt;/code&gt; 값&lt;/th&gt;
&lt;th&gt;클래스&lt;/th&gt;
&lt;th&gt;필드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.web_search_call.in_progress&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseWebSearchCallInProgressEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.web_search_call.searching&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseWebSearchCallSearchingEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.web_search_call.completed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseWebSearchCallCompletedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.9 파일 검색 도구&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;&lt;code&gt;type&lt;/code&gt; 값&lt;/th&gt;
&lt;th&gt;클래스&lt;/th&gt;
&lt;th&gt;필드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.file_search_call.in_progress&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseFileSearchCallInProgressEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.file_search_call.searching&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseFileSearchCallSearchingEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.file_search_call.completed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseFileSearchCallCompletedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.10 코드 인터프리터 도구&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;&lt;code&gt;type&lt;/code&gt; 값&lt;/th&gt;
&lt;th&gt;클래스&lt;/th&gt;
&lt;th&gt;필드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.code_interpreter_call.in_progress&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseCodeInterpreterCallInProgressEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.code_interpreter_call.interpreting&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseCodeInterpreterCallInterpretingEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.code_interpreter_call_code.delta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseCodeInterpreterCallCodeDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;delta: str&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.code_interpreter_call_code.done&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseCodeInterpreterCallCodeDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;code: str&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.code_interpreter_call.completed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseCodeInterpreterCallCompletedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.11 추론(Reasoning)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;&lt;code&gt;type&lt;/code&gt; 값&lt;/th&gt;
&lt;th&gt;클래스&lt;/th&gt;
&lt;th&gt;필드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.reasoning_text.delta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseReasoningTextDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;delta: str&lt;/code&gt;, &lt;code&gt;content_index: int&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.reasoning_text.done&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseReasoningTextDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text: str&lt;/code&gt;, &lt;code&gt;content_index: int&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.reasoning_summary_part.added&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseReasoningSummaryPartAddedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;part: Part&lt;/code&gt; (summary_text), &lt;code&gt;summary_index: int&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.reasoning_summary_part.done&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseReasoningSummaryPartDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;part: Part&lt;/code&gt; (summary_text), &lt;code&gt;summary_index: int&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.reasoning_summary_text.delta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseReasoningSummaryTextDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;delta: str&lt;/code&gt;, &lt;code&gt;summary_index: int&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.reasoning_summary_text.done&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseReasoningSummaryTextDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text: str&lt;/code&gt;, &lt;code&gt;summary_index: int&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.12 이미지 생성&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;&lt;code&gt;type&lt;/code&gt; 값&lt;/th&gt;
&lt;th&gt;클래스&lt;/th&gt;
&lt;th&gt;필드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.image_generation_call.in_progress&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseImageGenCallInProgressEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.image_generation_call.generating&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseImageGenCallGeneratingEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.image_generation_call.partial_image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseImageGenCallPartialImageEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;partial_image_b64: str&lt;/code&gt;, &lt;code&gt;partial_image_index: int&lt;/code&gt; (0부터 시작), &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.image_generation_call.completed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseImageGenCallCompletedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.13 사용자 정의 도구 호출&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;&lt;code&gt;type&lt;/code&gt; 값&lt;/th&gt;
&lt;th&gt;클래스&lt;/th&gt;
&lt;th&gt;필드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.custom_tool_call_input.delta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseCustomToolCallInputDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;delta: str&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response.custom_tool_call_input.done&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseCustomToolCallInputDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;input: str&lt;/code&gt;, &lt;code&gt;item_id: str&lt;/code&gt;, &lt;code&gt;output_index: int&lt;/code&gt;, &lt;code&gt;sequence_number: int&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;4. SSE 파싱 파이프라인 (와이어 → 타입화된 이벤트)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
    subgraph Wire[&quot;HTTP Wire&quot;]
        A[&quot;httpx.Response&amp;lt;br/&amp;gt;iter_bytes()&quot;]
    end
    subgraph Decoder[&quot;SSE Decoder&quot;]
        B[&quot;SSEDecoder.decode(line)&amp;lt;br/&amp;gt;RFC 8895&quot;]
        C[&quot;ServerSentEvent&amp;lt;br/&amp;gt;.data = JSON 문자열&quot;]
    end
    subgraph Dispatch[&quot;Type Dispatch&quot;]
        D[&quot;construct_type()&amp;lt;br/&amp;gt;discriminator=&apos;type&apos;&quot;]
    end
    subgraph Output[&quot;Output&quot;]
        E[&quot;ResponseCreatedEvent |&amp;lt;br/&amp;gt;ResponseTextDeltaEvent |&amp;lt;br/&amp;gt;... (53개 타입화된 모델)&quot;]
    end
    A -- 바이트 --&amp;gt; B -- SSE 라인 --&amp;gt; C -- .json() dict --&amp;gt; D --&amp;gt; E
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;디코더 주요 상세&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SSEDecoder&lt;/code&gt;는 빈 줄(&lt;code&gt;\n\n&lt;/code&gt;, &lt;code&gt;\r\r&lt;/code&gt;, &lt;code&gt;\r\n\r\n&lt;/code&gt;)을 만날 때까지 라인을 누적한다.&lt;/li&gt;
&lt;li&gt;각 SSE 필드(&lt;code&gt;event:&lt;/code&gt;, &lt;code&gt;data:&lt;/code&gt;, &lt;code&gt;id:&lt;/code&gt;, &lt;code&gt;retry:&lt;/code&gt;)는 스펙에 따라 처리된다.&lt;/li&gt;
&lt;li&gt;Responses API에서는 &lt;code&gt;event:&lt;/code&gt; 필드가 없으므로 &lt;code&gt;ServerSentEvent.event&lt;/code&gt;는 &lt;code&gt;None&lt;/code&gt;이다.&lt;/li&gt;
&lt;li&gt;원본 JSON &lt;code&gt;data:&lt;/code&gt;는 역직렬화되어 &lt;code&gt;construct_type()&lt;/code&gt;에 전달된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ResponseStreamEvent&lt;/code&gt;는 &lt;code&gt;Annotated[Union[...], PropertyInfo(discriminator=&quot;type&quot;)]&lt;/code&gt;이므로, &lt;code&gt;construct_type&lt;/code&gt;이 &lt;code&gt;&quot;type&quot;&lt;/code&gt; 키를 확인하여 올바른 서브클래스로 디스패치한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;5. SDK 편의 API: ResponseStream / ResponseStreamState&lt;/h2&gt;
&lt;h3&gt;5.1 ResponseStreamManager (진입점)&lt;/h3&gt;
&lt;p&gt;사용법:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;with client.responses.stream(
    input=&quot;Hello&quot;, model=&quot;gpt-4o&quot;
) as stream:  # ResponseStream[TextFormatT] 반환
    for event in stream:
        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;매니저는:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;client.responses.create(stream=True, ...)&lt;/code&gt;를 호출하여 원본 &lt;code&gt;Stream[ResponseStreamEvent]&lt;/code&gt;를 얻는다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ResponseStream(raw_stream=..., text_format=..., input_tools=...)&lt;/code&gt;으로 래핑한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ResponseStreamEvent[TextFormatT]&lt;/code&gt;의 이터레이터로 노출한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;5.2 ResponseStream → ResponseStreamState&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ResponseStream.__stream__()&lt;/code&gt;은 각 원본 이벤트를 &lt;code&gt;ResponseStreamState.handle_event(sse_event)&lt;/code&gt;에 위임한다.&lt;/p&gt;
&lt;p&gt;**handle_event()**는 각 원본 SSE 이벤트에 대해 &lt;strong&gt;1~2개의 이벤트&lt;/strong&gt;를 생성:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;원본 SSE &lt;code&gt;type&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;방출되는 이벤트&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;response.output_text.delta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseTextDeltaEvent&lt;/code&gt; (&lt;code&gt;snapshot&lt;/code&gt; 필드 추가 — 지금까지 누적된 텍스트)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;response.output_text.done&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseTextDoneEvent&lt;/code&gt; (&lt;code&gt;parsed&lt;/code&gt; 필드, &lt;code&gt;text_format&lt;/code&gt; 설정 시)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;response.function_call_arguments.delta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseFunctionCallArgumentsDeltaEvent&lt;/code&gt; (&lt;code&gt;snapshot&lt;/code&gt; — 지금까지 누적된 인자)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;response.completed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseCompletedEvent&lt;/code&gt; (&lt;code&gt;response&lt;/code&gt;가 완전히 파싱된 &lt;code&gt;ParsedResponse&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;그 외&lt;/td&gt;
&lt;td&gt;그대로 통과&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;5.3 accumulate_event() — 상태 머신&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ResponseStreamState&lt;/code&gt;는 &lt;code&gt;__current_snapshot&lt;/code&gt; (부분 &lt;code&gt;ParsedResponse&lt;/code&gt;)을 유지한다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;response.created&lt;/code&gt;&lt;/strong&gt; → &lt;code&gt;event.response&lt;/code&gt;에서 초기 스냅샷 생성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;response.output_item.added&lt;/code&gt;&lt;/strong&gt; → &lt;code&gt;snapshot.output[]&lt;/code&gt;에 항목 추가 (&lt;code&gt;function_call&lt;/code&gt;, &lt;code&gt;message&lt;/code&gt;, 일반 타입 처리)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;response.content_part.added&lt;/code&gt;&lt;/strong&gt; → 인덱싱된 출력 메시지에 콘텐츠 파트 추가&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;response.output_text.delta&lt;/code&gt;&lt;/strong&gt; → 일치하는 &lt;code&gt;output_text&lt;/code&gt; 콘텐츠의 텍스트에 &lt;code&gt;delta&lt;/code&gt; 연결&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;response.function_call_arguments.delta&lt;/code&gt;&lt;/strong&gt; → 일치하는 함수 호출의 인자에 &lt;code&gt;delta&lt;/code&gt; 연결&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;response.completed&lt;/code&gt;&lt;/strong&gt; → &lt;code&gt;parse_response()&lt;/code&gt;로 완전히 파싱된 response 저장 (구조화된 출력 파싱 포함)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.4 편의 메서드&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;stream.get_final_response()&lt;/code&gt; → 스트림 전체를 소비하고 누적된 &lt;code&gt;ParsedResponse&lt;/code&gt; 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stream.until_done()&lt;/code&gt; → 스트림이 완전히 소비될 때까지 블로킹&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stream.close()&lt;/code&gt; → 내부 httpx response 종료&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.5 ResponseStreamEvent[TextFormatT] (SDK 래퍼 타입)&lt;/h3&gt;
&lt;p&gt;다음은 원본 생성 타입을 서브클래싱하거나 추가 필드로 래핑한다:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;원본 타입&lt;/th&gt;
&lt;th&gt;SDK 래퍼&lt;/th&gt;
&lt;th&gt;추가 필드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResponseTextDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib.streaming.responses.ResponseTextDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;snapshot: str&lt;/code&gt; (누적 텍스트)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResponseTextDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib.streaming.responses.ResponseTextDoneEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;parsed: Optional[TextFormatT]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResponseFunctionCallArgumentsDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib.streaming.responses.ResponseFunctionCallArgumentsDeltaEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;snapshot: str&lt;/code&gt; (누적 인자)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResponseCompletedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib.streaming.responses.ResponseCompletedEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response: ParsedResponse[TextFormatT]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;스트림이 사용하는 복합 &lt;code&gt;ResponseStreamEvent&lt;/code&gt; 타입 별칭:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ResponseStreamEvent = Annotated[Union[
    ResponseTextDeltaEvent,       # SDK 래퍼
    ResponseTextDoneEvent,        # SDK 래퍼
    ResponseFunctionCallArgumentsDeltaEvent,  # SDK 래퍼
    ResponseCompletedEvent,       # SDK 래퍼
    ResponseAudioDeltaEvent,      # 원본
    ... (그 외 모든 원본 이벤트)    # 원본
], PropertyInfo(discriminator=&quot;type&quot;)]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 백그라운드 모드 스트리밍&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;responses.stream()&lt;/code&gt;에 &lt;code&gt;background=True&lt;/code&gt;를 전달하면:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;API 요청에 &lt;code&gt;&quot;background&quot;: true&lt;/code&gt;가 포함된다.&lt;/li&gt;
&lt;li&gt;서버는 즉시 &lt;code&gt;response.created&lt;/code&gt; 이벤트로 &lt;code&gt;response id&lt;/code&gt;를 반환한다.&lt;/li&gt;
&lt;li&gt;스트림은 열린 상태로 유지되며, 백그라운드 처리 진행에 따라 비동기적으로 이벤트를 전달한다.&lt;/li&gt;
&lt;li&gt;스트림을 &lt;strong&gt;중단하고 재개&lt;/strong&gt;할 수 있다: &lt;code&gt;response.created&lt;/code&gt;에서 &lt;code&gt;response_id&lt;/code&gt;를 기록한 후, 나중에 &lt;code&gt;responses.stream(response_id=id, starting_after=N)&lt;/code&gt;을 호출하여 시퀀스 번호 N부터 재개한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;사용 패턴:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;with client.responses.stream(input=&quot;...&quot;, model=&quot;gpt-4o&quot;, background=True) as stream:
    for event in stream:
        if event.type == &quot;response.created&quot;:
            id = event.response.id
        if event.sequence_number == 10:
            break

# 나중에...
with client.responses.stream(response_id=id, starting_after=10) as stream:
    for event in stream:
        ...
    response = stream.get_final_response()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 데이터 흐름 요약&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TB
    subgraph HTTP[&quot;HTTP Layer&quot;]
        A[&quot;HTTP POST /v1/responses&amp;lt;br/&amp;gt;{ stream: true }&amp;lt;br/&amp;gt;Content-Type: text/event-stream&quot;]
    end
    subgraph SSE[&quot;SSE Decoder&quot;]
        B[&quot;SSEDecoder&amp;lt;br/&amp;gt;빈 줄 단위 버퍼링&amp;lt;br/&amp;gt;event:/data: 파싱&quot;]
    end
    subgraph Stream[&quot;Stream Layer&quot;]
        C[&quot;Stream[ResponseStreamEvent]&amp;lt;br/&amp;gt;construct_type() 디스패치&amp;lt;br/&amp;gt;&apos;type&apos; discriminator 사용&quot;]
    end
    subgraph Accumulator[&quot;Accumulator&quot;]
        D[&quot;ResponseStreamState&amp;lt;br/&amp;gt;스냅샷 유지&amp;lt;br/&amp;gt;snapshot/parsed 추가&quot;]
    end
    subgraph User[&quot;User Code&quot;]
        E[&quot;for event in stream:&quot;]
    end
    A --&amp;gt; B --&amp;gt; C --&amp;gt; D --&amp;gt; E
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 와이어 레벨 예시 (합성)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;data: {&quot;type&quot;:&quot;response.created&quot;,&quot;response&quot;:{&quot;id&quot;:&quot;resp_abc123&quot;,&quot;object&quot;:&quot;response&quot;,&quot;status&quot;:&quot;queued&quot;,&quot;created_at&quot;:1718000000,&quot;model&quot;:&quot;gpt-4o&quot;,&quot;output&quot;:[],&quot;tools&quot;:[],&quot;tool_choice&quot;:&quot;auto&quot;,&quot;parallel_tool_calls&quot;:true,&quot;top_p&quot;:1,&quot;temperature&quot;:1},&quot;sequence_number&quot;:0}

data: {&quot;type&quot;:&quot;response.queued&quot;,&quot;response&quot;:{&quot;id&quot;:&quot;resp_abc123&quot;,...},&quot;sequence_number&quot;:1}

data: {&quot;type&quot;:&quot;response.in_progress&quot;,&quot;response&quot;:{&quot;id&quot;:&quot;resp_abc123&quot;,&quot;status&quot;:&quot;in_progress&quot;,...},&quot;sequence_number&quot;:2}

data: {&quot;type&quot;:&quot;response.output_item.added&quot;,&quot;output_index&quot;:0,&quot;item&quot;:{&quot;id&quot;:&quot;item_1&quot;,&quot;type&quot;:&quot;message&quot;,&quot;role&quot;:&quot;assistant&quot;,&quot;content&quot;:[],&quot;status&quot;:&quot;in_progress&quot;},&quot;sequence_number&quot;:3}

data: {&quot;type&quot;:&quot;response.content_part.added&quot;,&quot;output_index&quot;:0,&quot;item_id&quot;:&quot;item_1&quot;,&quot;content_index&quot;:0,&quot;part&quot;:{&quot;type&quot;:&quot;output_text&quot;,&quot;text&quot;:&quot;&quot;,&quot;annotations&quot;:[]},&quot;sequence_number&quot;:4}

data: {&quot;type&quot;:&quot;response.output_text.delta&quot;,&quot;output_index&quot;:0,&quot;item_id&quot;:&quot;item_1&quot;,&quot;content_index&quot;:0,&quot;delta&quot;:&quot;Hello&quot;,&quot;sequence_number&quot;:5,&quot;logprobs&quot;:[{&quot;token&quot;:&quot;Hello&quot;,&quot;logprob&quot;:-0.01}]}

data: {&quot;type&quot;:&quot;response.output_text.delta&quot;,&quot;output_index&quot;:0,&quot;item_id&quot;:&quot;item_1&quot;,&quot;content_index&quot;:0,&quot;delta&quot;:&quot; world&quot;,&quot;sequence_number&quot;:6,&quot;logprobs&quot;:[...]}

data: {&quot;type&quot;:&quot;response.output_text.done&quot;,&quot;output_index&quot;:0,&quot;item_id&quot;:&quot;item_1&quot;,&quot;content_index&quot;:0,&quot;text&quot;:&quot;Hello world&quot;,&quot;sequence_number&quot;:7,&quot;logprobs&quot;:[...]}

data: {&quot;type&quot;:&quot;response.content_part.done&quot;,&quot;output_index&quot;:0,&quot;item_id&quot;:&quot;item_1&quot;,&quot;content_index&quot;:0,&quot;part&quot;:{&quot;type&quot;:&quot;output_text&quot;,&quot;text&quot;:&quot;Hello world&quot;,&quot;annotations&quot;:[]},&quot;sequence_number&quot;:8}

data: {&quot;type&quot;:&quot;response.output_item.done&quot;,&quot;output_index&quot;:0,&quot;item&quot;:{&quot;id&quot;:&quot;item_1&quot;,&quot;type&quot;:&quot;message&quot;,&quot;role&quot;:&quot;assistant&quot;,&quot;content&quot;:[{&quot;type&quot;:&quot;output_text&quot;,&quot;text&quot;:&quot;Hello world&quot;,&quot;annotations&quot;:[]}],&quot;status&quot;:&quot;completed&quot;},&quot;sequence_number&quot;:9}

data: {&quot;type&quot;:&quot;response.completed&quot;,&quot;response&quot;:{&quot;id&quot;:&quot;resp_abc123&quot;,&quot;status&quot;:&quot;completed&quot;,&quot;output&quot;:[{&quot;id&quot;:&quot;item_1&quot;,...}],...},&quot;sequence_number&quot;:10}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;9. 구현 참조 (SDK 주요 파일)&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;파일&lt;/th&gt;
&lt;th&gt;목적&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/openai/_streaming.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Stream[T]&lt;/code&gt;, &lt;code&gt;AsyncStream[T]&lt;/code&gt;, &lt;code&gt;SSEDecoder&lt;/code&gt;, &lt;code&gt;SSEBytesDecoder&lt;/code&gt;, &lt;code&gt;ServerSentEvent&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/openai/_response.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;APIResponse&lt;/code&gt;, &lt;code&gt;_parse()&lt;/code&gt; → &lt;code&gt;Stream[T]&lt;/code&gt; 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/openai/_base_client.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;request()&lt;/code&gt;, &lt;code&gt;_process_response()&lt;/code&gt;, &lt;code&gt;_process_response_data()&lt;/code&gt;, &lt;code&gt;_make_sse_decoder()&lt;/code&gt;, &lt;code&gt;make_request_options()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/openai/types/responses/response_stream_event.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseStreamEvent&lt;/code&gt; 구분된 유니온&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/openai/lib/streaming/responses/_responses.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponseStream&lt;/code&gt;, &lt;code&gt;AsyncResponseStream&lt;/code&gt;, &lt;code&gt;ResponseStreamManager&lt;/code&gt;, &lt;code&gt;ResponseStreamState&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/openai/lib/streaming/responses/_events.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SDK 확장 래퍼 타입 + &lt;code&gt;ResponseStreamEvent&lt;/code&gt; (라이브러리 버전)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/openai/resources/responses/responses.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Responses.create()&lt;/code&gt;, &lt;code&gt;Responses.stream()&lt;/code&gt;, WebSocket 연결 코드&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;10. 이벤트 분류 다이어그램&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;mindmap
  root((&quot;Responses API SSE 이벤트&amp;lt;br/&amp;gt;(53종)&quot;))
    생명주기
      response.created
      response.queued
      response.in_progress
      response.completed
      response.failed
      response.incomplete
      error
    출력 구조
      output_item.added
      output_item.done
      content_part.added
      content_part.done
    텍스트 콘텐츠
      output_text.delta
      output_text.done
      output_text.annotation.added
      refusal.delta
      refusal.done
    함수 호출
      function_call_arguments.delta
      function_call_arguments.done
    MCP
      mcp_call.in_progress
      mcp_call_arguments.delta
      mcp_call_arguments.done
      mcp_call.completed
      mcp_call.failed
      mcp_list_tools.in_progress
      mcp_list_tools.completed
      mcp_list_tools.failed
    오디오
      audio.delta
      audio.done
      audio.transcript.delta
      audio.transcript.done
    웹 검색
      web_search_call.in_progress
      web_search_call.searching
      web_search_call.completed
    파일 검색
      file_search_call.in_progress
      file_search_call.searching
      file_search_call.completed
    코드 인터프리터
      code_interpreter_call.in_progress
      code_interpreter_call.interpreting
      code_interpreter_call_code.delta
      code_interpreter_call_code.done
      code_interpreter_call.completed
    추론
      reasoning_text.delta
      reasoning_text.done
      reasoning_summary_part.added
      reasoning_summary_part.done
      reasoning_summary_text.delta
      reasoning_summary_text.done
    이미지 생성
      image_generation_call.in_progress
      image_generation_call.generating
      image_generation_call.partial_image
      image_generation_call.completed
    사용자 정의 도구 호출
      custom_tool_call_input.delta
      custom_tool_call_input.done
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;팁&lt;/strong&gt;: SDK docstring에는 &lt;code&gt;response.created&lt;/code&gt;만 문서화되어 있다. &lt;code&gt;src/openai/types/responses/response_&amp;lt;name&amp;gt;_event.py&lt;/code&gt;의 타입 정의를 정식 출처로 사용하라. 모든 이벤트에는 &lt;code&gt;type&lt;/code&gt; 리터럴(JSON의 &lt;code&gt;type&lt;/code&gt; 필드와 일치), &lt;code&gt;sequence_number: int&lt;/code&gt;, 그리고 이벤트별 필드가 있다.&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Alice2 리버싱 노트</title><link>https://blog.euroka.moe/posts/reversing/alice2-script/</link><guid isPermaLink="true">https://blog.euroka.moe/posts/reversing/alice2-script/</guid><description>Alice2 소스 코드에서 알아낸 구조와 사용법</description><pubDate>Wed, 10 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Alice 2 - Jython 상호운용성 계층&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;분석 목적&lt;/strong&gt;: Alice 2(JAVA)에 내장된 Jython 스크립팅 시스템의 전체 구조와 유저 접점.
&lt;strong&gt;분석 기준&lt;/strong&gt;: &lt;a href=&quot;https://github.com/TheAliceProject/alice2&quot;&gt;TheAliceProject/alice2&lt;/a&gt; 소스 코드 + Alice 2.6.1 배포본 직접 분석.
&lt;strong&gt;마지막 업데이트&lt;/strong&gt;: 2026-06-10&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;아키텍처 개요&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    AT[&quot;Alice 2 저작 도구&amp;lt;br/&amp;gt;(JAlice.java)&quot;] --&amp;gt;|생성| W[&quot;World&quot;]
    W --&amp;gt;|소유| SF[&quot;ScriptingFactory&amp;lt;br/&amp;gt;Jython 초기화, alice 패키지 로드&quot;]
    W --&amp;gt;|소유| I[&quot;Interpreter&amp;lt;br/&amp;gt;월드별 Jython 엔진&quot;]
    I --&amp;gt;|소유| NS[&quot;Namespace&amp;lt;br/&amp;gt;PyStringMap 서브클래스&quot;]
    NS --&amp;gt;|이름 조회| WE[&quot;PyElement / PySandbox&amp;lt;br/&amp;gt;Alice 객체 프록시&quot;]
    W --&amp;gt;|소유| SP[&quot;ScriptProperty&amp;lt;br/&amp;gt;캐싱 + .py 저장&quot;]
    W --&amp;gt;|소유| SR[&quot;ScriptResponse&amp;lt;br/&amp;gt;매 프레임 EXEC_MULTIPLE&quot;]
    W --&amp;gt;|소유| SDR[&quot;ScriptDefinedResponse&amp;lt;br/&amp;gt;프롤로그에서 EVAL&quot;]

    subgraph UF[&quot;4가지 사용자 입력면&quot;]
        S1[&quot;1. Sandbox Script&amp;lt;br/&amp;gt;우클릭 → Edit Script&quot;]
        S2[&quot;2. ScriptResponse 블록&amp;lt;br/&amp;gt;드래그앤드롭 → 텍스트 입력&quot;]
        S3[&quot;3. ScriptDefinedResponse 블록&amp;lt;br/&amp;gt;드래그앤드롭 → Response 표현식&quot;]
        S4[&quot;4. ScriptComboWidget + ScratchPad&amp;lt;br/&amp;gt;하단 툴바 즉시 실행&quot;]
    end

    S1 --&amp;gt;|&quot;World.start() 시&quot;| I
    S2 --&amp;gt;|&quot;매 프레임 update(t)&quot;| I
    S3 --&amp;gt;|&quot;프롤로그(t) 평가&quot;| I
    S4 --&amp;gt;|&quot;유저 클릭 실행&quot;| I
    I --&amp;gt;|&quot;Py.exec / Py.eval&quot;| JR[&quot;Jython 런타임&quot;]
    JR --&amp;gt;|&quot;__finditem__&quot;| NS
    JR --&amp;gt;|&quot;alice.* 함수&quot;| PK[&quot;alice Python 패키지&amp;lt;br/&amp;gt;(Required/jython/Lib/alice/)&quot;]
    PK --&amp;gt;|Java 상호운용| SF
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;⚠️ 중요: Script 기능 동작 안 함 (Alice 2.6.1)&lt;/h3&gt;
&lt;p&gt;소스 코드 분석 결과, &lt;code&gt;Interpreter.java&lt;/code&gt;의 &lt;code&gt;Py.compile_flags&lt;/code&gt; 호출 시 마지막 인자가 &lt;code&gt;null&lt;/code&gt;로 전달되어
Jython 2.7.3에서 &lt;strong&gt;NPE가 발생&lt;/strong&gt;. 이로 인해 모든 Script 기반 기능(Sandbox Script, ScriptResponse,
ScriptDefinedResponse, ComboWidget, ScratchPad)이 실행되지 않음. 블록 기반 편집만 정상 작동.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;읽기 가이드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;전체 구조부터 보고 싶다?
  → 01-architecture.md     (Jython 부트스트랩, CompileType, World.start())
  → 05-behind-the-scenes.md (Java 클래스 전체 인벤토리)

이름이 어떻게 Alice 오브젝트에 연결되는지 궁금하다?
  → 02-name-resolution.md  (Namespace → PyElement 전 과정, PySandbox 변수 쓰기)

유저 입장에서 뭘 입력해야 하는지 알고 싶다?
  → 03-script-surfaces.md  (4가지 입력면 + 동작 안 함 확인)

API가 궁금하다?
  → 04-api-reference.md    (alice.* 함수 전수 + 버그 목록)

월드 파일을 뜯어보고 싶다?
  → 06-file-format-a2w.md  (a2w ZIP 구조, 저장/로드)
  → 07-file-format-a2c.md  (a2c 갤러리 오브젝트)
  → 08-element-data-xml.md (elementData.xml 스키마)
  → 09-storing-scripts.md   (ScriptProperty, .py 파일 저장)

블록이 어떻게 생성되는지 알고 싶다?
  → 10-drag-and-drop.md    (팔레트 → ResponsePrototype → Element)

실전 문제 해결이 필요하다?
  → 11-troubleshooting.md  (로딩 실패, 폰트, 변수, a2w 직접 수정)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;문서 목록&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;파일&lt;/th&gt;
&lt;th&gt;내용&lt;/th&gt;
&lt;th&gt;분량&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;01&lt;/td&gt;
&lt;td&gt;&lt;code&gt;01-architecture.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Jython 부트스트랩, World.start(), ScriptResponse/ScriptDefinedResponse&lt;/td&gt;
&lt;td&gt;중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02&lt;/td&gt;
&lt;td&gt;&lt;code&gt;02-name-resolution.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Namespace → PyElement 전체 체인, PySandbox 변수 할당&lt;/td&gt;
&lt;td&gt;중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;03&lt;/td&gt;
&lt;td&gt;&lt;code&gt;03-script-surfaces.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;유저 중심&lt;/strong&gt;: 4가지 입력면 UI + 실행 시점&lt;/td&gt;
&lt;td&gt;★ 많음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;04&lt;/td&gt;
&lt;td&gt;&lt;code&gt;04-api-reference.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;alice.* API 전수, 발견된 버그 3건&lt;/td&gt;
&lt;td&gt;중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;05&lt;/td&gt;
&lt;td&gt;&lt;code&gt;05-behind-the-scenes.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Java 클래스 계층, s_classnameMap, 호출 체인&lt;/td&gt;
&lt;td&gt;중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;06&lt;/td&gt;
&lt;td&gt;&lt;code&gt;06-file-format-a2w.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a2w ZIP 내부, 저장/로드 플로우&lt;/td&gt;
&lt;td&gt;중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;07&lt;/td&gt;
&lt;td&gt;&lt;code&gt;07-file-format-a2c.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a2c 갤러리 오브젝트 포맷&lt;/td&gt;
&lt;td&gt;소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;08&lt;/td&gt;
&lt;td&gt;&lt;code&gt;08-element-data-xml.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;elementData.xml 스키마 전수&lt;/td&gt;
&lt;td&gt;중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;09&lt;/td&gt;
&lt;td&gt;&lt;code&gt;09-storing-scripts.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ScriptProperty, .py 저장, 4가지 입력면 저장 비교&lt;/td&gt;
&lt;td&gt;중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10-drag-and-drop.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;팔레트 → ResponsePrototype → Element 인스턴스화&lt;/td&gt;
&lt;td&gt;많음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;&lt;code&gt;11-troubleshooting.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;실전 문제&lt;/strong&gt;: 로딩 실패, 폰트, 변수, a2w 직접 수정&lt;/td&gt;
&lt;td&gt;중&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;빠른 참조&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# Script의 API 참조 — 실제로는 동작하지 않지만 구조는 이렇다

import alice

# 이름으로 접근 가능한 객체
bunny.color = alice.red
bunny.opacity = 0.5

# 애니메이션 함수
alice.MoveAnimation(subject=bunny, direction=alice.forward, amount=2, duration=2)
alice.SayAnimation(subject=bunny, what=&quot;Hello!&quot;)

# 제어 흐름
alice.DoInOrder(
    alice.MoveAnimation(subject=bunny, amount=1, duration=1),
    alice.TurnAnimation(subject=bunny, direction=alice.left, amount=0.25),
)

# 센서
if alice.keyboard.isKeyPressed(alice.keyboard.VK_SPACE):
    print(&quot;Space pressed!&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;주요 발견 사항 요약&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;발견 사항&lt;/th&gt;
&lt;th&gt;근거&lt;/th&gt;
&lt;th&gt;상태&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Jython 2.7.3 null cflags NPE&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Interpreter.java&lt;/code&gt; 56번째 줄 &lt;code&gt;null&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Script 전면 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HideAction = ShowAction&lt;/td&gt;
&lt;td&gt;&lt;code&gt;animations.py&lt;/code&gt; 702-703, 둘 다 &lt;code&gt;isShowing=true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;버그&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IfElseInOrder elseAnimation 무시&lt;/td&gt;
&lt;td&gt;&lt;code&gt;animations.py&lt;/code&gt; 162-173, &lt;code&gt;#todo&lt;/code&gt;로 남음&lt;/td&gt;
&lt;td&gt;미구현&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LoopNInOrder = None&lt;/td&gt;
&lt;td&gt;&lt;code&gt;animations.py&lt;/code&gt; 184-185&lt;/td&gt;
&lt;td&gt;미구현&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SoundAction 클래스로 저장 시 로드 실패&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s_classnameMap&lt;/code&gt;에 매핑 없음&lt;/td&gt;
&lt;td&gt;호환성 문제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text3D / SayAnimation 한글 미지원&lt;/td&gt;
&lt;td&gt;Alice 2.6.1 폰트&lt;/td&gt;
&lt;td&gt;환경 제약&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;01 - 아키텍처 및 부트스트랩&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;Alice 2가 Jython을 기동하고 World에 스크립팅 레이어를 연결하는 전체 과정.
&lt;strong&gt;분석 기준&lt;/strong&gt;: alice2 소스 + Alice 2.6.1 배포본.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;1. Jython 부트스트랩 순서&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant JVM as JVM
    participant SF as ScriptingFactory&amp;lt;br/&amp;gt;(jython.ScriptingFactory)
    participant JY as Jython 런타임
    participant AT as 저작 도구
    participant W as World
    participant I as Interpreter

    JVM-&amp;gt;&amp;gt;SF: new ScriptingFactory()
    SF-&amp;gt;&amp;gt;JY: PySystemState.initialize(preProperties, ...)
    Note over SF,JY: python.home = Required/jython/
    SF-&amp;gt;&amp;gt;JY: alice/__init__.py 읽기 및 컴파일
    SF-&amp;gt;&amp;gt;JY: Py.exec(code, builtins, builtins)
    Note over SF,JY: alice.* 이름을 builtins에 주입&amp;lt;br/&amp;gt;(전체 alice를 모든 Interpreter가 공유)

    AT-&amp;gt;&amp;gt;W: new World()
    AT-&amp;gt;&amp;gt;W: setScriptingFactory(SF)
    W-&amp;gt;&amp;gt;SF: manufactureInterpreter()
    SF--&amp;gt;&amp;gt;I: new Interpreter(this)
    I-&amp;gt;&amp;gt;I: new Namespace() + new PyModule(&quot;main&quot;, dict)
    W-&amp;gt;&amp;gt;I: setWorld(this)
    I-&amp;gt;&amp;gt;I: Namespace.setWorld(world) → clear() + setWorld()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;주요 포인트&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;alice/__init__.py&lt;/code&gt;가 builtins 네임스페이스에 로드된다&lt;/strong&gt; — 모든 &lt;code&gt;alice.*&lt;/code&gt; 이름이 모든 인터프리터에서 바로 사용 가능&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ScriptingFactory&lt;/code&gt;는 인터프리터 풀을 관리한다 (&lt;code&gt;Vector&amp;lt;Interpreter&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;alice/__init__.py&lt;/code&gt; → &lt;code&gt;animations.py&lt;/code&gt;, &lt;code&gt;constants.py&lt;/code&gt;, &lt;code&gt;linearalgebra.py&lt;/code&gt; 전부 임포트&lt;/li&gt;
&lt;li&gt;JVM 속성: &lt;code&gt;-Dpython.home=jython -Dpython.path=jython/Lib/alice&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;2. World 레벨 실행 체인&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
    E[&quot;Element.compile(script, source, type)&quot;] --&amp;gt; EW[&quot;getWorld().compile(...)&quot;]
    EW --&amp;gt; WI[&quot;getInterpreter().compile(...)&quot;]
    WI --&amp;gt; PY[&quot;Py.compile_flags(script, pathname, CompileMode, flags)&quot;]
    PY --&amp;gt; JC[&quot;jython.Code(PyCode, CompileType)&quot;]

    E2[&quot;Element.eval(code)&quot;] --&amp;gt; EW2[&quot;getWorld().eval(code)&quot;]
    EW2 --&amp;gt; WI2[&quot;getInterpreter().eval(code)&quot;]
    WI2 --&amp;gt; PYE[&quot;Py.eval(code.getPyCode(), ns, ns)&quot;]

    E3[&quot;Element.exec(code)&quot;] --&amp;gt; EW3[&quot;getWorld().exec(code)&quot;]
    EW3 --&amp;gt; WI3[&quot;getInterpreter().exec(code)&quot;]
    WI3 --&amp;gt; PYX[&quot;Py.exec(code.getPyCode(), ns, ns)&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;실제 Java 구현&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// Element.java
public Code compile(String script, Object source, CompileType compileType) {
    return getWorld().compile(script, source, compileType);
}
public Object eval(Code code) {
    return getWorld().eval(code);
}
public void exec(Code code) {
    getWorld().exec(code);
}

// World.java
public Code compile(String script, Object source, CompileType compileType) {
    return getInterpreter().compile(script, source, compileType);
}
public Object eval(Code code) {
    return getInterpreter().eval(code);
}
public void exec(Code code) {
    getInterpreter().exec(code);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. CompileType 3종&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CompileType&lt;/th&gt;
&lt;th&gt;Jython CompileMode&lt;/th&gt;
&lt;th&gt;특징&lt;/th&gt;
&lt;th&gt;사용처&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EVAL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CompileMode.eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;단일 표현식 평가, 결과 반환&lt;/td&gt;
&lt;td&gt;ScriptDefinedResponse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EXEC_SINGLE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CompileMode.single&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;단일 구문 실행&lt;/td&gt;
&lt;td&gt;ScriptComboWidget (Go 버튼)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EXEC_MULTIPLE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CompileMode.exec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;여러 구문 실행&lt;/td&gt;
&lt;td&gt;ScriptResponse, Sandbox Script, ScratchPad&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;4. World.start() — 실제 코드로 보는 실행 흐름&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// World.java
public void start() {
    if (m_scriptingFactory != null) {
        getInterpreter().start();                     // Namespace 리셋
        Code code = script.getCode(EXEC_MULTIPLE);   // World.script Property → 컴파일
        if (code != null) exec(code);                 // Sandbox Script 1회 실행
    }
    bubbles.clear();
    started(this, m_clock.getTime());
    m_isRunning = true;
}

public void schedule() {
    m_currentSandbox = this;
    scheduleBehaviors(m_clock.getTime());             // 매 프레임 behavior 실행
    m_collisions = m_collisionManager.update(256);
    m_currentSandbox = null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant U as 유저가 Play 클릭
    participant W as World
    participant I as Interpreter
    participant NS as Namespace
    participant JB as Jython
    participant WOBJ as Alice 객체들

    U-&amp;gt;&amp;gt;W: start()
    W-&amp;gt;&amp;gt;I: start()
    I-&amp;gt;&amp;gt;NS: resetNamespace() → clear() + setWorld()
    W-&amp;gt;&amp;gt;W: script.getCode(EXEC_MULTIPLE)
    W-&amp;gt;&amp;gt;I: exec(code)
    I-&amp;gt;&amp;gt;JB: Py.exec(pyCode, ns, ns)
    JB-&amp;gt;&amp;gt;NS: __finditem__(&quot;bunny&quot;)
    NS-&amp;gt;&amp;gt;NS: 로컬에도 없고, 월드 이름도 아님
    NS-&amp;gt;&amp;gt;WOBJ: m_pyWorld.__findattr__(&quot;bunny&quot;)
    WOBJ--&amp;gt;&amp;gt;NS: PyElement(bunny)
    NS--&amp;gt;&amp;gt;JB: PyElement
    JB-&amp;gt;&amp;gt;PyElement: bunny.color = red
    PyElement-&amp;gt;&amp;gt;WOBJ: property.set(red)

    loop 매 프레임
        W-&amp;gt;&amp;gt;W: schedule()
        W-&amp;gt;&amp;gt;W: scheduleBehaviors(t)
        Note over W: 모든 Sandbox behavior 실행
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;5. ScriptResponse와 ScriptDefinedResponse&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;classDiagram
    class Response {
        +ScriptProperty script
        +manufactureRuntimeResponse()
    }
    class ScriptResponse {
        +update(t): exec(script, EXEC_MULTIPLE)
    }
    class ScriptDefinedResponse {
        +prologue(t): eval(script, EVAL)
        -m_actual: RuntimeResponse
        +update(t): m_actual.update(t)
        +getTimeRemaining(t): m_actual.getTimeRemaining(t)
    }
    class RuntimeResponse {
        +prologue(t)
        +update(t)
        +epilogue(t)
    }

    Response &amp;lt;|-- ScriptResponse
    Response &amp;lt;|-- ScriptDefinedResponse
    ScriptDefinedResponse --&amp;gt; RuntimeResponse : m_actual에 위임
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ScriptResponse (실제 코드)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// ScriptResponse.java
public class RuntimeScriptResponse extends RuntimeResponse {
    public void update(double t) {
        super.update(t);
        ScriptResponse.this.exec(
            ScriptResponse.this.script.getCode(CompileType.EXEC_MULTIPLE));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ScriptDefinedResponse (실제 코드)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// ScriptDefinedResponse.java
public void prologue(double t) {
    super.prologue(t);
    m_actual = null;
    Object o = ScriptDefinedResponse.this.eval(
        ScriptDefinedResponse.this.script.getCode(CompileType.EVAL));
    if (o instanceof Response) {
        m_actual = ((Response)o).manufactureRuntimeResponse();
        if (m_actual != null) m_actual.prologue(t);
    } else {
        throw new RuntimeException(&quot;does not evaluate to a response: &quot; + script);
    }
}

public void update(double t) {
    super.update(t);
    if (m_actual != null) m_actual.update(t);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;ScriptResponse&lt;/th&gt;
&lt;th&gt;ScriptDefinedResponse&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CompileType&lt;/td&gt;
&lt;td&gt;EXEC_MULTIPLE&lt;/td&gt;
&lt;td&gt;EVAL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실행 시기&lt;/td&gt;
&lt;td&gt;매 프레임 &lt;code&gt;update(t)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prologue(t)&lt;/code&gt;에서 1회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;요구사항&lt;/td&gt;
&lt;td&gt;아무 Python 코드&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Response&lt;/code&gt; 객체 반환 필수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;지속 시간&lt;/td&gt;
&lt;td&gt;0 (고정)&lt;/td&gt;
&lt;td&gt;위임받은 response에 따름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;용도&lt;/td&gt;
&lt;td&gt;반복/연속 동작&lt;/td&gt;
&lt;td&gt;Python으로 애니메이션 동적 구성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;ScriptDefinedResponse 예&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 이 코드는 반드시 Response 객체를 반환해야 한다
alice.DoInOrder(
    alice.MoveAnimation(subject=bunny, amount=1, duration=1),
    alice.TurnAnimation(subject=bunny, direction=alice.left, amount=0.25),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. ⚠️ 발견된 문제: Jython 2.7.3 null cflags (실제 코드 분석 결과)&lt;/h2&gt;
&lt;h3&gt;현상&lt;/h3&gt;
&lt;p&gt;Alice 2.6.1에서 Script 기반 기능들이 런타임에 NPE로 크래시난다.&lt;/p&gt;
&lt;h3&gt;원인 (소스 코드 확인 완료)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// Interpreter.java (jython/Interpreter.java, line 56)
public Code compile(String script, Object source, CompileType compileType) {
    PyCode pyCode = Py.compile_flags(
        script, source.toString(), compileType.getMode(),
        null    // ← Jython 2.7.3의 ParserFacade.prepBufReader()가 이 null을 못 받음
    );
    return new Code(pyCode, compileType);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;참고: &lt;code&gt;ScriptingFactory.java&lt;/code&gt;에서는 같은 API를 올바르게 호출함:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PyCode code = Py.compile_flags(script, pathname, CompileMode.exec,
    Py.getCompilerFlags());  // ← flags를 제대로 전달
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;영향받는 기능&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;기능&lt;/th&gt;
&lt;th&gt;결과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sandbox Script (Edit Script)&lt;/td&gt;
&lt;td&gt;World.start() 시점에 NPE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScriptResponse 블록&lt;/td&gt;
&lt;td&gt;update() 시점에 NPE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScriptDefinedResponse 블록&lt;/td&gt;
&lt;td&gt;prologue() 시점에 NPE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScriptComboWidget (Go)&lt;/td&gt;
&lt;td&gt;컴파일 시점에 NPE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScriptScratchPad (Perform All)&lt;/td&gt;
&lt;td&gt;컴파일 시점에 NPE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;드래그앤드롭 블록 전반&lt;/td&gt;
&lt;td&gt;✅ 정상 작동 (Script 미사용)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;현실적 영향&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Script로 무언가를 하는 것은 Alice 2.6.1에서 불가능&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;alice/__init__.py&lt;/code&gt;와 &lt;code&gt;animations.py&lt;/code&gt;에 정의된 Python API는 &lt;strong&gt;사용할 수 없음&lt;/strong&gt; (ScriptAction, MoveAnimation 등)&lt;/li&gt;
&lt;li&gt;블록 기반 편집만 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;02 - 이름 해석: Python 이름 → Alice 객체&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;스크립트에 &lt;code&gt;bunny&lt;/code&gt;라고 쓰면 어떻게 Alice 월드의 오브젝트가 연결되는지 설명한다.
⚠️ &lt;strong&gt;실제 코드 분석 기반&lt;/strong&gt; — PyElement.&lt;strong&gt;findattr_ex&lt;/strong&gt;, PySandbox.&lt;strong&gt;setattr&lt;/strong&gt; 전체 추적.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 전체 조회 체인&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Namespace.__finditem__(key)&lt;/code&gt;는 다음 순서로 이름을 찾는다:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    START[&quot;bunny.color = red&quot;] --&amp;gt; NS[&quot;Namespace.__finditem__(&apos;bunny&apos;)&quot;]
    NS --&amp;gt; C1[&quot;1. local PyStringMap 확인&amp;lt;br/&amp;gt;(Python local/global var)&quot;]
    C1 --&amp;gt;|찾음| RET1[&quot;return PyObject&quot;]
    C1 --&amp;gt;|없음| C2[&quot;2. 월드 이름과 일치?&quot;]
    C2 --&amp;gt;|예| RETW[&quot;return m_pyWorld&quot;]
    C2 --&amp;gt;|아니오| C3[&quot;3. World.lookup(key)&quot;]
    C3 --&amp;gt; SB[&quot;Sandbox.lookup(key)&quot;]
    SB --&amp;gt; STK[&quot;a. currentBehavior.stackLookup(key)&amp;lt;br/&amp;gt;(파라미터, 지역 변수)&quot;]
    STK --&amp;gt;|찾음| REXPR[&quot;return Expression.getValue()&quot;]
    STK --&amp;gt;|없음| DTL[&quot;b. currentBehavior.detailLookup(key)&amp;lt;br/&amp;gt;(behavior 속성)&quot;]
    DTL --&amp;gt;|찾음| REXPR
    DTL --&amp;gt;|없음| C4[&quot;4. m_pyWorld.__findattr__(&apos;bunny&apos;)&quot;]
    C4 --&amp;gt; PE[&quot;PyElement.__findattr_ex__(&apos;bunny&apos;)&quot;]
    PE --&amp;gt; CH[&quot;a. getChildNamedIgnoreCase(&apos;bunny&apos;)&quot;]
    CH --&amp;gt;|표현식 발견| CHVE[&quot;return expression.getValue()&quot;]
    CH --&amp;gt;|element 발견| CHVN[&quot;return PyElement(descendant)&quot;]
    CH --&amp;gt;|없음| PR[&quot;b. getPropertyNamedIgnoreCase(&apos;bunny&apos;)&quot;]
    PR --&amp;gt;|찾음| PRV[&quot;return property.get()&quot;]
    PR --&amp;gt;|없음| USCORE[&quot;c. name.startsWith(&apos;_&apos;)?&quot;]
    USCORE --&amp;gt;|예| RETRY[&quot;이름[1:]로 재시도&amp;lt;br/&amp;gt;child → property → reflection&quot;]
    USCORE --&amp;gt;|아니오| SUP[&quot;d. super.__findattr_ex__(name)&amp;lt;br/&amp;gt;(Java reflection fallback)&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Java: Namespace.&lt;strong&gt;finditem&lt;/strong&gt; (실제 코드)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// Namespace.java
public synchronized PyObject __finditem__(String key) {
    // 1. Python local/global variables
    PyObject py = super.__finditem__(key);
    if (py != null) return py;

    // 2. 월드 이름과 일치? → world 자체 반환
    if (key.equalsIgnoreCase(m_world.name.getStringValue())) {
        return m_pyWorld;
    }

    // 3. Sandbox.lookup() — behavior context (파라미터/지역변수)
    Expression expression = m_world.lookup(key);
    if (expression != null) {
        return java2py(expression.getValue());
    }

    // 4. 최종 fallback: world의 child/property lookup
    return m_pyWorld.__findattr__(key);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Sandbox.lookup() — Behavior Context (실제 코드)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// Sandbox.java
public Expression lookup(String key) {
    if (m_currentBehavior != null) {
        // a. behavior stack → parameters, local variables
        Expression e = m_currentBehavior.stackLookup(key);
        if (e != null) return e;

        // b. behavior detail properties
        return m_currentBehavior.detailLookup(key);
    }
    return null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;중요&lt;/strong&gt;: 현재 실행 중인 Behavior의 지역변수/파라미터가 전역 오브젝트명보다 &lt;strong&gt;항상 우선&lt;/strong&gt;한다.
&lt;code&gt;stackLookup&lt;/code&gt; → &lt;code&gt;detailLookup&lt;/code&gt; 순서로 검색.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. PyElement.&lt;strong&gt;findattr_ex&lt;/strong&gt; — 속성 읽기 (실제 코드)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// PyElement.java
public PyObject __findattr_ex__(String name) {
    // 1. Child Element 검색 (대소문자 구분 없음)
    Element descendant = m_element.getChildNamedIgnoreCase(name);
    if (descendant == null) {
        // 2. Property 검색
        Property property = m_element.getPropertyNamedIgnoreCase(name);
        if (property == null) {
            // 3. 언더스코어 prefix → child 건너뛰고 재시도
            if (name.startsWith(&quot;_&quot;)) {
                descendant = m_element.getChildNamed(name.substring(1));
                if (descendant != null) return m_namespace.getPyElement(descendant);
                property = m_element.getPropertyNamedIgnoreCase(name.substring(1));
                if (property != null) return Py.java2py(property);
            }
            // 4. Java reflection fallback
            return super.__findattr_ex__(name);
        }
        return m_namespace.java2py(property.get());
    }
    // Child가 Expression이면 getValue(), 아니면 PyElement로 래핑
    Object value;
    if (descendant instanceof Expression) {
        value = ((Expression)descendant).getValue();
    } else {
        value = descendant;
    }
    return m_namespace.java2py(value);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant Script
    participant NS as Namespace
    participant PE as PyElement
    participant Elem as Alice Element

    Script-&amp;gt;&amp;gt;NS: __finditem__(&quot;bunny&quot;)
    NS-&amp;gt;&amp;gt;PE: __findattr_ex__(&quot;bunny&quot;)
    PE-&amp;gt;&amp;gt;Elem: getChildNamedIgnoreCase(&quot;bunny&quot;)
    Elem--&amp;gt;&amp;gt;PE: Element(bunny Transformable)
    Note over PE: descendant is not Expression
    PE--&amp;gt;&amp;gt;NS: PyElement(bunny)
    NS--&amp;gt;&amp;gt;Script: PyElement(bunny)

    Script-&amp;gt;&amp;gt;PE: bunny.color (읽기)
    PE-&amp;gt;&amp;gt;Elem: getPropertyNamedIgnoreCase(&quot;color&quot;)
    Elem--&amp;gt;&amp;gt;PE: Color property
    PE-&amp;gt;&amp;gt;PE: property.get() -&amp;gt; Color.RED
    PE--&amp;gt;&amp;gt;Script: PyColor(RED)

    Script-&amp;gt;&amp;gt;PE: bunny.color = alice.red (쓰기)
    PE-&amp;gt;&amp;gt;Elem: getPropertyNamedIgnoreCase(&quot;color&quot;)
    Elem--&amp;gt;&amp;gt;PE: Color property
    PE-&amp;gt;&amp;gt;Elem: property.set(attr.__tojava__(Color.class))
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;5. ⭐ PySandbox.&lt;strong&gt;setattr&lt;/strong&gt; — 변수에 값 쓰기 (실제 코드)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// PySandbox.java
public void __setattr__(String name, PyObject attr) {
    // 우선 Sandbox.variables 배열에서 이름이 일치하는 변수 찾기
    for (int i = 0; i &amp;lt; getSandbox().variables.size(); i++) {
        Variable variable = (Variable)getSandbox().variables.get(i);
        if (name.equalsIgnoreCase(variable.name.getStringValue())) {
            // Alice UI 변수에 직접 값 설정!
            variable.value.set(attr.__tojava__(variable.getValueClass()));
            return;
        }
    }
    // 없으면 일반 PyElement.__setattr__ 위임
    super.__setattr__(name, attr);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;이것이 의미하는 것&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# Alice UI에서 만든 전역 변수 score가 있을 때:
score = 5  # ← PySandbox.__setattr__가 가로챔!
           # ← Sandbox.variables 중 &quot;score&quot;를 찾아 value.set(5)
           # ← Python dict에 &quot;score&quot;가 생기지 않음!

# Alice UI 변수가 없으면:
my_python_var = 42  # ← 일반 Python 변수 (Namespace dict에 저장)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;유저 입장&lt;/strong&gt;: Alice UI 변수는 &lt;strong&gt;할당으로 바로 값 변경 가능&lt;/strong&gt;하다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;score = score + 1  # ✅ Alice 전역 변수 증가
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. PyElement.&lt;strong&gt;setattr&lt;/strong&gt; — Property 쓰기 (실제 코드)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// PyElement.java
public void __setattr__(String name, PyObject attr) {
    Property property = m_element.getPropertyNamedIgnoreCase(name);
    if (property != null) {
        // Python → Java 타입 변환 후 Property.set()
        property.set(attr.__tojava__(property.getValueClass()));
    } else {
        super.__setattr__(name, attr);  // Python instance attribute
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 특수 구문&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구문&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;th&gt;예제&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__UnnamedN__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;인덱스로 이름 없는 child 접근&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__Unnamed0__&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt; 구분자&lt;/td&gt;
&lt;td&gt;계층적 경로 (internalGetDescendantKeyed)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;world.bunny.leftLeg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_name&lt;/code&gt; prefix&lt;/td&gt;
&lt;td&gt;child 조회 건너뛰고 바로 property로&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_color&lt;/code&gt; (child가 있어도 property로)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 전체 추적: &lt;code&gt;bunny.color = alice.red&lt;/code&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;1. Jython이 &quot;bunny.color = alice.red&quot; 파싱
   → bunny 평가: Namespace.__finditem__(&quot;bunny&quot;)

2. Namespace.__finditem__(&quot;bunny&quot;)
   → local dict: 없음
   → world name: &quot;world&quot; 아님
   → World.lookup(&quot;bunny&quot;): m_currentBehavior == null → null 반환
   → m_pyWorld.__findattr__(&quot;bunny&quot;)

3. PyElement.__findattr_ex__(&quot;bunny&quot;)
   → getChildNamedIgnoreCase(&quot;bunny&quot;) → Element(bunny)
   → descendant is not Expression
   → return PyElement(bunny)

4. Python: bunny.color = alice.red
   → PyElement.__setattr__(&quot;color&quot;, red_pyobj)

5. PyElement.__setattr__(&quot;color&quot;, PyObject(alice.red))
   → getPropertyNamedIgnoreCase(&quot;color&quot;) → ColorProperty
   → attr.__tojava__(Color.class) → Java Color.RED
   → property.set(Color.RED)

6. &quot;alice.red&quot; 해석:
   from edu.cmu.cs.stage3.alice.scenegraph import Color
   red = Color.RED   # Java static field from constants.py
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;9. ⭐ 키 포인트 요약&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;이름은 대소문자를 구분하지 않는다&lt;/strong&gt; (&lt;code&gt;getChildNamedIgnoreCase&lt;/code&gt;, &lt;code&gt;equalsIgnoreCase&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Behavior의 지역변수 &amp;gt; 전역 오브젝트명&lt;/strong&gt; (stackLookup 우선)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alice UI 변수 = Python 할당 가능&lt;/strong&gt; (PySandbox.__setattr__가 가로챔)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;_name&lt;/code&gt; prefix = child 우회&lt;/strong&gt; (같은 이름의 child와 property가 있을 때 유용)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;__UnnamedN__&lt;/code&gt; = 이름 없는 child 접근&lt;/strong&gt; (DefinedResponse 내부 구조 분석용)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/&lt;/code&gt; 경로 = 계층적 접근&lt;/strong&gt; (&lt;code&gt;world.bunny.leftLeg&lt;/code&gt; 가능)&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;03 - 스크립트 입력면: 코드를 작성하는 4가지 방법&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;Alice 2에서 &lt;strong&gt;스크립트를 입력하고 실행하는 4가지 방법&lt;/strong&gt;을 유저 입장에서 설명한다.
각 입력면마다: UI 위치, 조작 단계, 실행되는 시점, 실제 예제, 주의사항.
&lt;strong&gt;분석 기준&lt;/strong&gt;: alice2 source + Alice 2.6.1 배포본.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;개요&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    ALICE[&quot;Alice 2 실행&quot;] --&amp;gt;|world 선택| CM[&quot;오른쪽 클릭&quot;]
    CM --&amp;gt;|enableScripting=true| ES[&quot;Edit Script 메뉴&quot;]
    ES --&amp;gt; SES[&quot;ScriptEditor 창&amp;lt;br/&amp;gt;여러 줄 코드 입력&quot;]
    SES --&amp;gt;|&quot;World.start()시&quot;| EXEC1[&quot;EXEC_MULTIPLE 1회 실행&quot;]

    ALICE --&amp;gt;|ResponseEditor| PALL[&quot;블록 팔레트&quot;]
    PALL --&amp;gt;|드래그| SR[&quot;ScriptResponse 블록&quot;]
    SR --&amp;gt; SRIN[&quot;script 필드에 코드 입력&quot;]
    SRIN --&amp;gt;|매 프레임| EXEC2[&quot;EXEC_MULTIPLE 실행&quot;]

    PALL --&amp;gt;|드래그| SDR[&quot;ScriptDefinedResponse 블록&quot;]
    SDR --&amp;gt; SDRIN[&quot;Response 반환 표현식 입력&quot;]
    SDRIN --&amp;gt;|프롤로그 1회| EVAL[&quot;EVAL&quot;]

    ALICE --&amp;gt;|하단 UI| CW[&quot;ComboWidget + ScratchPad&quot;]
    CW --&amp;gt;|한 줄 입력 + Go| EXEC3[&quot;EXEC_SINGLE 즉시 실행&quot;]
    CW --&amp;gt;|여러 줄 + Perform All| EXEC4[&quot;EXEC_MULTIPLE 즉시 실행&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;🚨 실행 불가 확인 (Alice 2.6.1)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;소스 코드 분석 결과&lt;/strong&gt;: Alice 2.6.1에서 모든 Script 기능이 Jython 2.7.3의 &lt;code&gt;Py.compile_flags&lt;/code&gt; null cflags 문제로 런타임에 동작하지 않는다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;관련 파일: &lt;code&gt;Interpreter.java&lt;/code&gt; 56번째 줄 — &lt;code&gt;Py.compile_flags(script, source, mode, null)&lt;/code&gt; 마지막 인자가 null&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ScriptingFactory.java&lt;/code&gt;에서 builtins 로딩할 때는 &lt;code&gt;Py.getCompilerFlags()&lt;/code&gt;를 제대로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;입력면&lt;/th&gt;
&lt;th&gt;시점&lt;/th&gt;
&lt;th&gt;결과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sandbox Script&lt;/td&gt;
&lt;td&gt;World.start() 시&lt;/td&gt;
&lt;td&gt;NPE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScriptResponse&lt;/td&gt;
&lt;td&gt;매 프레임 update()&lt;/td&gt;
&lt;td&gt;NPE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScriptDefinedResponse&lt;/td&gt;
&lt;td&gt;prologue() 시&lt;/td&gt;
&lt;td&gt;NPE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScriptComboWidget&lt;/td&gt;
&lt;td&gt;컴파일 시&lt;/td&gt;
&lt;td&gt;NPE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScriptScratchPad&lt;/td&gt;
&lt;td&gt;컴파일 시&lt;/td&gt;
&lt;td&gt;NPE&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;블록 기반 편집은 정상 작동한다.&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;입력면 1: Sandbox Script (Edit Script)&lt;/h2&gt;
&lt;h3&gt;UI 위치와 조작&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;1. Alice 2 실행, 월드 선택
2. 월드 또는 샌드박스 오브젝트에서 오른쪽 클릭
3. 컨텍스트 메뉴에서 &quot;edit script&quot; 선택
4. ScriptEditor 창이 열림 (줄 번호 표시 + 일반 텍스트 편집기)
5. Python 코드 입력
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;참고&lt;/strong&gt;: 이 메뉴는 &lt;code&gt;enableScripting = true&lt;/code&gt; 설정일 때만 나타난다.&lt;/p&gt;
&lt;h3&gt;편집 대상 (소스 코드 확인)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// ElementPopupUtilities.java — EditScriptRunnable
public void run() {
    if (element instanceof Sandbox) {
        authoringTool.editObject(((Sandbox)element).script);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;편집 대상 = &lt;code&gt;Sandbox.script&lt;/code&gt; (ScriptProperty). ZIP 내 &lt;code&gt;/script.py&lt;/code&gt;에 저장됨.&lt;/p&gt;
&lt;h3&gt;실행 시점 (소스 코드 확인)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// World.java start()
public void start() {
    if (m_scriptingFactory != null) {
        getInterpreter().start();
        Code code = script.getCode(EXEC_MULTIPLE);  // World.script Property
        if (code != null) exec(code);               // Sandbox Script 1회 실행
    }
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant User
    participant World
    participant Interp as Interpreter
    participant Sandbox

    User-&amp;gt;&amp;gt;World: Play 버튼 클릭
    World-&amp;gt;&amp;gt;Interp: start()
    Interp-&amp;gt;&amp;gt;Interp: Namespace.reset() → clear() + setWorld()
    World-&amp;gt;&amp;gt;World: script.getCode(EXEC_MULTIPLE)
    World-&amp;gt;&amp;gt;Interp: exec(code)
    Note over Interp: Sandbox Script **한 번** 실행
    World-&amp;gt;&amp;gt;World: schedule()
    loop 매 프레임
        World-&amp;gt;&amp;gt;World: scheduleBehaviors(t)
        World-&amp;gt;&amp;gt;Sandbox: sandbox.scheduleBehaviors(t)
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;입력면 2: ScriptResponse 블록&lt;/h2&gt;
&lt;h3&gt;UI 위치와 조작&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;1. ResponseEditor 열기 (오브젝트 더블클릭 또는 편집 메뉴)
2. 좌측 블록 팔레트에서 &quot;script&quot; 라벨이 붙은 블록 찾기
3. 블록을 드래그하여 DoInOrder 등의 ContainerResponse에 드롭
4. 생성된 ScriptResponse 블록을 클릭
5. 속성 패널에 &quot;script&quot; 텍스트 필드가 나타남
6. 여기에 Python 코드 입력
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;실행 방식 (소스 코드)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// ScriptResponse.java
public class RuntimeScriptResponse extends RuntimeResponse {
    public void update(double t) {
        super.update(t);
        ScriptResponse.this.exec(
            ScriptResponse.this.script.getCode(CompileType.EXEC_MULTIPLE));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant Sched as 스케줄러
    participant Resp as ScriptResponse
    participant ScriptProp as ScriptProperty
    participant Int as Interpreter

    Sched-&amp;gt;&amp;gt;Resp: update(t)
    Resp-&amp;gt;&amp;gt;ScriptProp: getCode(EXEC_MULTIPLE)
    ScriptProp-&amp;gt;&amp;gt;ScriptProp: 캐시 미스? compile()
    ScriptProp--&amp;gt;&amp;gt;Resp: Code (캐시됨)
    Resp-&amp;gt;&amp;gt;Int: exec(code)
    Note over Resp,Int: 매 프레임마다 실행됨!
    Int--&amp;gt;&amp;gt;Resp: 완료
    Sched-&amp;gt;&amp;gt;Resp: 다음 update(t+dt) ...
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;입력면 3: ScriptDefinedResponse 블록&lt;/h2&gt;
&lt;h3&gt;UI 위치와 조작&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;1. ResponseEditor 열기
2. 팔레트에서 &quot;script defined&quot; 라벨 블록 찾기
3. ContainerResponse에 드롭
4. 블록 클릭 → &quot;script&quot; 속성 필드
5. 여기에 Response 객체를 반환하는 Python 표현식 입력
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;실행 방식 (소스 코드)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// ScriptDefinedResponse.java
public void prologue(double t) {
    super.prologue(t);
    m_actual = null;
    Object o = ScriptDefinedResponse.this.eval(
        ScriptDefinedResponse.this.script.getCode(CompileType.EVAL));
    if (o instanceof Response) {
        m_actual = ((Response)o).manufactureRuntimeResponse();
        if (m_actual != null) m_actual.prologue(t);
    } else {
        throw new RuntimeException(&quot;does not evaluate to a response: &quot; + script);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant Sched as 스케줄러
    participant SDR as ScriptDefinedResponse
    participant Int as Interpreter
    participant Deleg as 위임받은 RuntimeResponse

    Sched-&amp;gt;&amp;gt;SDR: prologue(t0)
    SDR-&amp;gt;&amp;gt;Int: eval(code) -- EVAL 모드
    Int--&amp;gt;&amp;gt;SDR: Response 객체 (예: DoInOrder)
    SDR-&amp;gt;&amp;gt;Deleg: manufactureRuntimeResponse()
    SDR-&amp;gt;&amp;gt;Deleg: prologue(t0)

    loop 매 프레임
        Sched-&amp;gt;&amp;gt;SDR: update(t)
        SDR-&amp;gt;&amp;gt;Deleg: update(t)
    end

    Sched-&amp;gt;&amp;gt;SDR: epilogue(tN)
    SDR-&amp;gt;&amp;gt;Deleg: epilogue(tN)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;입력면 4: ScriptComboWidget + ScriptScratchPad&lt;/h2&gt;
&lt;h3&gt;UI 위치&lt;/h3&gt;
&lt;p&gt;Alice 하단 툴바 영역:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──────────────────────────────────────────────────────┐
│ [  입력 + 히스토리(콤보박스)        ] [ Go 버튼 ]    │  ← ScriptComboWidget
├──────────────────────────────────────────────────────┤
│ [  Perform All (Ctrl+F4) ] [ Perform Selected (F4) ] │  ← ScriptScratchPad 버튼
├──────────────────────────────────────────────────────┤
│ Scratch Pad (임시 편집기)                             │
│ ┌──────────────────────────────────────────────────┐ │
│ │                                                  │ │
│ │  여러 줄 입력 가능                                │ │
│ │                                                  │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;입력면 4a: ScriptComboWidget (Go)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// ScriptComboWidget.java
public void runScript() {
    String script = ((String)comboBox.getEditor().getItem()).trim();
    if (script.length() != 0) {
        Code code = sandbox.compile(script, &quot;&amp;lt;Run Line&amp;gt;&quot;, EXEC_SINGLE);
        sandbox.exec(code);
        comboBox.insertItemAt(script, 0);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;한 줄 입력 → Go 또는 Enter → EXEC_SINGLE&lt;/li&gt;
&lt;li&gt;실행 후 히스토리에 저장 (재선택 가능)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;저장되지 않음&lt;/strong&gt; (휘발성)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;입력면 4b: ScriptScratchPad (Perform All / Perform Selected)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// ScriptEditorPane.java
// Perform All (Ctrl+F4)
script = ScriptEditorPane.this.getText();
code = sandbox.compile(script, &quot;&amp;lt;ScriptEditorPane&amp;gt;&quot;, EXEC_MULTIPLE);
sandbox.exec(code);

// Perform Selected (F4)
int selStart = getLineStartOffset(getLineOfOffset(getSelectionStart()));
int selEnd = getLineEndOffset(getLineOfOffset(getSelectionEnd()));
script = getText(selStart, selEnd - selStart);
code = sandbox.compile(script, &quot;&amp;lt;ScriptEditorPane&amp;gt;&quot;, EXEC_MULTIPLE);
sandbox.exec(code);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;저장되지 않음&lt;/strong&gt; (휘발성)&lt;/li&gt;
&lt;li&gt;영구 저장 필요 시 Sandbox Script 또는 ScriptResponse 블록으로 이전 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;입력면 5: ResponseEditor 팔레트 블록&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;팔레트 블록&lt;/th&gt;
&lt;th&gt;Response 클래스&lt;/th&gt;
&lt;th&gt;스크립트 필요?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Do in order&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DoInOrder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;하위 블록 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Do together&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DoTogether&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;하위 블록 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loop (N times)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LoopNInOrder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;end/increment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;If/Else&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IfElseInOrder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;condition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;While&lt;/td&gt;
&lt;td&gt;&lt;code&gt;WhileLoopInOrder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;condition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;For each in order&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ForEachInOrder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;For all together&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ForEachTogether&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Script&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ScriptResponse&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;script 필드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Script defined&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ScriptDefinedResponse&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;script 필드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wait&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Wait&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;duration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comment&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Comment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(실행 안 됨)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Print&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Print&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;저장/휘발 구분&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;편집 위치&lt;/th&gt;
&lt;th&gt;a2w 내 저장 위치&lt;/th&gt;
&lt;th&gt;지속성&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Edit Script&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/script.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScriptResponse 블록&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/.../script.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScriptDefinedResponse 블록&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/.../script.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScriptComboWidget&lt;/td&gt;
&lt;td&gt;메모리&lt;/td&gt;
&lt;td&gt;❌ 휘발&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScriptScratchPad&lt;/td&gt;
&lt;td&gt;메모리&lt;/td&gt;
&lt;td&gt;❌ 휘발&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;04 - API 참조: &lt;code&gt;alice&lt;/code&gt; Python 패키지&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Required/jython/Lib/alice/&lt;/code&gt; 아래 4개 파일이 제공하는 모든 함수와 상수.
&lt;strong&gt;분석 기준&lt;/strong&gt;: Alice 2.6.1 배포본의 실제 .py 파일.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;패키지 구조&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Required/jython/Lib/alice/
  __init__.py        - 임포트, Keyboard, Mouse, Clock, copy(), exportToObj()
  animations.py      - 모든 애니메이션 + 제어 흐름 함수
  constants.py       - 색상, 방향, 스타일, HowMuch
  linearalgebra.py   - Vec3, dot(), cross()
  pinchgloves.py     - Pinch Gloves 하드웨어 연동 (직렬 COM 포트)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;상수 (&lt;code&gt;constants.py&lt;/code&gt;)&lt;/h2&gt;
&lt;h3&gt;방향&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Java Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;left&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Direction.LEFT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;right&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Direction.RIGHT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;up&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Direction.UP&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;down&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Direction.DOWN&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Direction.FORWARD&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;back&lt;/code&gt; / &lt;code&gt;backward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Direction.BACKWARD&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;차원&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Java Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;leftToRight&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Dimension.LEFT_TO_RIGHT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;topToBottom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Dimension.TOP_TO_BOTTOM&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;frontToBack&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Dimension.FRONT_TO_BACK&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;all&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Dimension.ALL&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;HowMuch (실행 범위)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;instance&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;이 Element만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;instanceAndParts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Element + parts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;instanceAndAllDescendants&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Element + 모든 하위 요소&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;애니메이션 스타일 (이징)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Java Source&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;abruptly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LINEAR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;이징 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;beginGently&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SLOW_IN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;시작만 느리게&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;endGently&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SLOW_OUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;끝만 느리게&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gently&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SLOW_IN_OUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;시작과 끝 모두 느리게 (기본값)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;색상&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;edu.cmu.cs.stage3.alice.scenegraph.Color&lt;/code&gt;의 static 필드:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Color&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;red&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pink&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;orange&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yellow&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;green&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;blue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;purple&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;brown&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;white&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lightGray&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gray&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;darkGray&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;black&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cyan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;magenta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;채우기 / 쉐이딩 스타일&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;solid&lt;/code&gt; / &lt;code&gt;wireframe&lt;/code&gt; / &lt;code&gt;points&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;채우기 스타일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;noShading&lt;/code&gt; / &lt;code&gt;flatShading&lt;/code&gt; / &lt;code&gt;smoothShading&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;쉐이딩 스타일&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;공간 관계&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;leftOf&lt;/code&gt; | &lt;code&gt;rightOf&lt;/code&gt; | &lt;code&gt;above&lt;/code&gt; | &lt;code&gt;below&lt;/code&gt; | &lt;code&gt;inFrontOf&lt;/code&gt; | &lt;code&gt;behind&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;선형대수 (&lt;code&gt;linearalgebra.py&lt;/code&gt;)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;v = alice.Vec3(1, 2, 3)
v2 = alice.Vec3(4, 5, 6)
v + v2       # Vec3(5, 7, 9)
v * 2        # Vec3(2, 4, 6)
v.getLength()       # 3.74
alice.dot(v, v2)    # 32
alice.cross(v, v2)  # Vec3(-3, 6, -3)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;센서 (&lt;code&gt;__init__.py&lt;/code&gt;)&lt;/h2&gt;
&lt;h3&gt;키보드&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kb = alice.keyboard
kb.isKeyPressed(kb.VK_SPACE)   # 현재 키 상태
kb.getKeyNames()               # 키 이름 목록
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;모든 &lt;code&gt;java.awt.event.KeyEvent.VK_*&lt;/code&gt; 상수 사용 가능: &lt;code&gt;VK_SPACE&lt;/code&gt;, &lt;code&gt;VK_ENTER&lt;/code&gt;, &lt;code&gt;VK_A&lt;/code&gt; ... &lt;code&gt;VK_Z&lt;/code&gt;, &lt;code&gt;VK_0&lt;/code&gt; ... &lt;code&gt;VK_9&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;마우스&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;pos = alice.mouse.getLocation()  # → java.awt.Point
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;시계&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;t = alice.clock.getTime()  # → System.currentTimeMillis / 1000 (초)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;애니메이션 함수 (&lt;code&gt;animations.py&lt;/code&gt; — 실제 코드 기반)&lt;/h2&gt;
&lt;h3&gt;이동&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;함수&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MoveAnimation(subject, direction, amount, duration, asSeenBy, style, isScaledBySize)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TurnAnimation(subject, direction, amount, duration, ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;회전 (앞/뒤/좌/우)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RollAnimation(subject, direction, amount, ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;굴림 (좌/우)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResizeAnimation(subject, dimension, amount, likeRubber, ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;크기 조절&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PositionAnimation(subject, position, ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;절대 위치 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QuaternionAnimation(subject, quaternion, ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;절대 회전 (쿼터니언)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EulerAnglesAnimation(subject, pitchYawRoll, ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;절대 회전 (오일러)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SizeAnimation(subject, size, ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;절대 크기 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PointAtAnimation(subject, target, offset, upGuide, onlyAffectYaw, ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;타겟 지향&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PointOfViewAnimation(subject, pointOfView, ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;전체 자세 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;StandUpAnimation(subject, ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;월드 업 정렬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PathAnimation(subject, pointOfViews, times, ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;키프레임 경로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ForwardVectorAnimation(subject, forward, upGuide, ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;전방 벡터 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;텍스처&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;함수&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TextureMapAnimation(subject, textureMaps, framesPerSecond, ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;텍스처 플립북&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;속성 및 차량&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;함수&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PropertyAnimation(element, propertyName, value, ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;모든 Property를 시간에 따라 애니메이션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;VehiclePropertyAnimation(element, vehicle)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Vehicle 즉시 설정 (duration=0)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;소리 및 표시&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;함수&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SoundAction(sound, volumeLevel, wait)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;소리 재생 (내부적으로 SoundResponse 사용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ShowAction(element, howMuch, wait)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;isShowing = true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HideAction(element, howMuch, wait)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;⚠️ &lt;strong&gt;실제로는 isShowing = true&lt;/strong&gt; (소스 확인 완료)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SayAnimation(subject, what, ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;말풍선&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ThinkAnimation(subject, what, ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;생각풍선&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;제어 흐름&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;함수&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DoInOrder(*animations, wait)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;순차 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DoTogether(*animations, wait)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;병렬 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ForEachInOrder(each, list, *animations, wait)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;순차 반복&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ForEachTogether(each, list, *animations, wait)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;병렬 반복&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IfElseInOrder(condition, ifAnimation, elseAnimation)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;⚠️ &lt;strong&gt;elseAnimation 미구현&lt;/strong&gt; (소스 확인 완료)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WhileLoopInOrder(condition, *animations)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;While 루프&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LoopNInOrder(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌ &lt;strong&gt;None 반환 (미구현)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WaitAction(duration)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;대기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ScriptAction(fcn_or_string, args, kwargs)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Python 함수를 Response로 래핑&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;기본 파라미터 패턴&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;def AnimFunc(subject, ..., duration=1, style=gently, wait=0):
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;모든 파라미터에 callable 전달 가능 (ApplyQuestion으로 자동 래핑)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wait=1&lt;/code&gt;이면 DoInOrder 내에서 블로킹&lt;/li&gt;
&lt;li&gt;&lt;code&gt;subject&lt;/code&gt;, &lt;code&gt;direction&lt;/code&gt;, &lt;code&gt;amount&lt;/code&gt; 등 전부 callable 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;⚠️ animations.py에서 발견된 버그 (실제 코드 분석)&lt;/h2&gt;
&lt;h3&gt;버그 1: HideAction = ShowAction&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# animations.py 699-703
def ShowAction(element, howMuch=instanceAndParts, wait=0):
    return PropertyAnimation(element, &quot;isShowing&quot;, true, duration=0, ...)

def HideAction(element, howMuch=instanceAndParts, wait=0):
    return PropertyAnimation(element, &quot;isShowing&quot;, true, duration=0, ...)  # ← false여야 함
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;두 함수가 완전히 동일. &lt;code&gt;HideAction&lt;/code&gt;을 호출해도 &lt;code&gt;isShowing = true&lt;/code&gt;로 설정됨.&lt;/p&gt;
&lt;h3&gt;버그 2: IfElseInOrder가 elseAnimation 미처리&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# animations.py 162-173
def IfElseInOrder(condition, ifAnimation, elseAnimation=None):
    a = IfElseInOrder()
    a.addComponentResponse(ifAnimation)
    #todo          ← elseAnimation 파라미터를 전혀 사용하지 않음
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;elseAnimation&lt;/code&gt; 파라미터는 받지만 내부적으로 무시됨.&lt;/p&gt;
&lt;h3&gt;버그 3: LoopNInOrder = None&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# animations.py 184-185
def LoopNInOrder(*args):
    return None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;구현 자체가 없음.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;고급: ScriptAction&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;def ScriptAction(fcn, args=(), kws={}):
    if type(fcn) == str:
        # 문자열 → ScriptResponse (매 프레임 실행)
        sr = ScriptResponse()
        sr.script.set(fcn)
        return sr
    else:
        # Callable → ApplyResponse (1회 호출)
        return ApplyResponse(fcn, args, kws)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;고급: ApplyQuestion (동적 파라미터)&lt;/h2&gt;
&lt;p&gt;모든 animation 함수는 callable 파라미터를 자동으로 ApplyQuestion으로 래핑:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;alice.MoveAnimation(
    subject=bunny,
    amount=lambda: alice.random.random() * 5,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;유틸리티 함수&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;alice.copy(src, name=&quot;bunny2&quot;)              # 깊은 복사
alice.exportToObj(model, &quot;bunny.obj&quot;)       # OBJ 내보내기
alice.ManufactureEach([bunny, snowman])     # ForEach 용 Variable
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Pinch Gloves (&lt;code&gt;pinchgloves.py&lt;/code&gt;)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import pinchgloves
pinchgloves.AddPinchResponse(
    (pinchgloves.LEFT_THUMB, pinchgloves.LEFT_INDEX),
    on_pinch_callback, on_release_callback,
)
pinchgloves.SetDefaultPort(&quot;COM1&quot;)
pinchgloves.Start()
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;05 - 내부 구조: Java 클래스 참조&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;Alice 2의 Jython 스크립팅 시스템을 구성하는 Java 클래스 전체 맵.
&lt;strong&gt;분석 기준&lt;/strong&gt;: alice2 source 전체 트레이스.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;클래스 계층&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;classDiagram
    class ScriptingFactory {
        &amp;lt;&amp;lt;interface&amp;gt;&amp;gt;
        +manufactureInterpreter()
        +getInterpreters()
        +setStdOut(OutputStream)
        +setStdErr(OutputStream)
    }
    class Interpreter {
        &amp;lt;&amp;lt;interface&amp;gt;&amp;gt;
        +setWorld(World)
        +start()/stop()
        +compile(String, Object, CompileType) Code
        +eval(Code) Object
        +exec(Code)
    }
    class Code {
        &amp;lt;&amp;lt;interface&amp;gt;&amp;gt;
        +getCompileType() CompileType
    }
    class CompileType {
        &amp;lt;&amp;lt;interface&amp;gt;&amp;gt;
        +EVAL / EXEC_SINGLE / EXEC_MULTIPLE
        +getMode() CompileMode
    }

    class jython_ScriptingFactory {
        +ScriptingFactory()
        -m_interpreters: Vector
    }
    class jython_Interpreter {
        -m_module: PyModule
        -m_dict: Namespace
        -m_world: World
    }
    class jython_Code {
        -m_pyCode: PyCode
        -m_compileType: CompileType
    }
    class Namespace {
        -m_world: World
        -m_pyWorld: PyElement
        -m_map: Hashtable
        +__finditem__(String) PyObject
        +java2py(Object) PyObject
    }
    class PyElement {
        -m_element: Element
        -m_namespace: Namespace
        +__findattr_ex__(String) PyObject
        +__setattr__(String, PyObject)
    }
    class PySandbox {
        +__setattr__(String, PyObject)
    }

    ScriptingFactory &amp;lt;|.. jython_ScriptingFactory
    Interpreter &amp;lt;|.. jython_Interpreter
    Code &amp;lt;|.. jython_Code
    jython_Interpreter --&amp;gt; Namespace : 소유
    Namespace --&amp;gt; PyElement : 생성
    PyElement &amp;lt;|-- PySandbox
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Java 파일 인벤토리&lt;/h2&gt;
&lt;h3&gt;Scripting API (인터페이스)&lt;/h3&gt;
&lt;p&gt;경로: &lt;code&gt;src/main/java/edu/cmu/cs/stage3/alice/scripting/&lt;/code&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;파일&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ScriptingFactory.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;인터프리터 생성/해제, I/O 리디렉션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Interpreter.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;compile, eval, exec, start, stop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Code.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;컴파일된 스크립트 핸들&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CompileType.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;EVAL / EXEC_SINGLE / EXEC_MULTIPLE&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Jython 구현&lt;/h3&gt;
&lt;p&gt;경로: &lt;code&gt;src/main/java/edu/cmu/cs/stage3/alice/scripting/jython/&lt;/code&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;파일&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;th&gt;특징&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ScriptingFactory.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Jython 초기화: PySystemState.initialize, alice/&lt;strong&gt;init&lt;/strong&gt;.py 로드&lt;/td&gt;
&lt;td&gt;builtins에 로드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Interpreter.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;compile: Py.compile_flags → &lt;strong&gt;마지막 인자 null&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚠️ NPE 원인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Code.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PyCode + CompileType 래핑&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Namespace.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PyStringMap 상속, 4단계 이름 해석&lt;/td&gt;
&lt;td&gt;java2py로 Element → PyElement 변환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PyElement.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PyInstance 상속, &lt;strong&gt;findattr_ex&lt;/strong&gt; 4단계&lt;/td&gt;
&lt;td&gt;child → property → _prefix → reflection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PySandbox.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PyElement 상속, &lt;strong&gt;setattr&lt;/strong&gt; 오버라이드&lt;/td&gt;
&lt;td&gt;변수명 일치 시 variable.value.set()&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Alice Core 통합&lt;/h3&gt;
&lt;p&gt;경로: &lt;code&gt;src/main/java/edu/cmu/cs/stage3/alice/core/&lt;/code&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;파일&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;World.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ScriptingFactory 소유, compile/eval/exec 위임, start()에서 script 1회 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Element.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;compile/eval/exec을 getWorld()로 위임, getChildNamedIgnoreCase / getPropertyNamedIgnoreCase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sandbox.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ScriptProperty 보유, behavior 스케줄, lookup() 이름 해석&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;경로: &lt;code&gt;src/main/java/edu/cmu/cs/stage3/alice/core/property/&lt;/code&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;파일&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ScriptProperty.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;StringProperty 상속, Code 캐싱, .py 파일 저장/로드&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;경로: &lt;code&gt;src/main/java/edu/cmu/cs/stage3/alice/core/responses/&lt;/code&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;파일&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ScriptResponse.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;update(t) → exec(EXEC_MULTIPLE) 매 프레임&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ScriptDefinedResponse.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;prologue(t) → eval(EVAL), 위임 패턴&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;저작 도구 (UI)&lt;/h3&gt;
&lt;p&gt;경로: &lt;code&gt;src/main/java/edu/cmu/cs/stage3/alice/authoringtool/&lt;/code&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;파일&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;editors/scripteditor/ScriptEditor.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ScriptEditor 전체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;util/ScriptEditorPane.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Perform All/Selected, undo/redo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;util/ScriptScratchPad.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;임시 메모장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;util/ScriptComboWidget.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;한 줄 실행 콤보박스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;util/ElementPopupUtilities.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;EditScriptRunnable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;Element.s_classnameMap (전체 목록)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Element.java&lt;/code&gt; static initializer에서 확인된 클래스명 매핑 (이전 버전 → 현재):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 이름이 바뀐 Response 클래스들
ConditionalLoopSequentialResponse → WhileLoopInOrder
ConditionalSequentialResponse    → IfElseInOrder
CountLoopSequentialResponse      → LoopNInOrder
ForEachInListSequentialResponse  → ForEach
OrientationAnimation             → ForwardVectorAnimation
ParallelForEachInListSequentialResponse → ForEachTogether
ParallelResponse                 → DoTogether
ProxyForScriptDefinedResponse    → ScriptDefinedResponse
SequentialForEachInListSequentialResponse → ForEachInOrder
SequentialResponse               → DoInOrder
MetaResponse                     → CallToUserDefinedResponse

// 내비게이션
KeyboardNavigationBehavior → KeyboardNavigationBehavior
MouseNavigationBehavior    → MouseLookingBehavior

// 포즈
Pose        → Pose
PoseAnimation → PoseAnimation
KeyMapping  → KeyMapping
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;⚠️ 없는 매핑 (참고)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;edu.cmu.cs.stage3.alice.core.responses.SoundAction → ??? (없음)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;a2w 파일에 &lt;code&gt;SoundAction&lt;/code&gt; 클래스명으로 저장된 Response가 있으면 로드 실패.
직접 ZIP을 열어 elementData.xml을 수정하거나 Alice UI에서 블록을 다시 설정해야 함.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;코드 실행 체인 요약&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;블록/Python 코드 → ScriptProperty.getCode()
  → owner.compile(script, owner, compileType)
    → World.compile()
      → Interpreter.compile()
        → Py.compile_flags(script, path, mode, flags) → PyCode
      → new Code(PyCode, CompileType)
    → Code 반환
  → Code 캐싱 (m_code)
→ exec(code) / eval(code)
  → World.exec() → Interpreter.exec()
    → Py.exec(pyCode, ns, ns) / __builtin__.eval(pyCode, ns, ns)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Alice 2 - a2w 파일 포맷 (World)&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;대상&lt;/strong&gt;: Alice 2 World 파일 (.a2w)의 내부 구조와 저장/로드 메커니즘
&lt;strong&gt;출처&lt;/strong&gt;: &lt;code&gt;Element.java&lt;/code&gt;, &lt;code&gt;AuthoringTool.java&lt;/code&gt;, &lt;code&gt;ZipTreeStorer.java&lt;/code&gt;, &lt;code&gt;ZipFileTreeStorer.java&lt;/code&gt;
&lt;strong&gt;마지막 업데이트&lt;/strong&gt;: 2026-06-09&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;1. ZIP Archive 개요&lt;/h2&gt;
&lt;p&gt;a2w 파일은 &lt;strong&gt;ZIP archive&lt;/strong&gt;다. 매직 바이트 &lt;code&gt;PK\x03\x04&lt;/code&gt;로 시작하며, 기본적으로 Deflate 압축.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;값&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;매직 시그니처&lt;/td&gt;
&lt;td&gt;&lt;code&gt;50 4B 03 04&lt;/code&gt; (PK\x03\x04)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기본 압축&lt;/td&gt;
&lt;td&gt;Deflate (java.util.zip)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;로더 클래스&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ZipTreeLoader&lt;/code&gt; / &lt;code&gt;ZipFileTreeLoader&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스토러 클래스&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ZipTreeStorer&lt;/code&gt; (순차) / &lt;code&gt;ZipFileTreeStorer&lt;/code&gt; (in-place)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;World 확장자&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.a2w&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;2. a2w ZIP 내부 파일 목록&lt;/h2&gt;
&lt;p&gt;실제 &lt;code&gt;default_English.a2w&lt;/code&gt; 분석 결과:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    subgraph ZIP[&quot;world.a2w (ZIP Archive)&quot;]
        EH[&quot;elementCountHint.txt - 진행률 힌트&quot;]
        TN[&quot;thumbnail.png - 120x90 미리보기&quot;]
        SP[&quot;script.py - World Sandbox script&quot;]
        EX[&quot;elementData.xml - 루트 World 직렬화&quot;]
        CAM[&quot;camera/&quot;]
        LIT[&quot;light/&quot;]
        GRD[&quot;ground/ + __ita__/ + texture/&quot;]
        BEH[&quot;behavior0/ + __Unnamed0__/ + __Unnamed1__/&quot;]
        MET[&quot;my first method/&quot;]
    end
    EH --&amp;gt; LOAD[&quot;Element.load()&quot;]
    SP --- EX
    EX --&amp;gt; CAM &amp;amp; LIT &amp;amp; GRD &amp;amp; BEH &amp;amp; MET
    style SP fill:#ff0,stroke:#a80
    style EX fill:#aef,stroke:#08a
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;elementCountHint.txt&lt;/h3&gt;
&lt;p&gt;로딩 프로그레스바용 힌트. 한 줄 정수.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;없어도 로딩에 지장 없음 (&lt;code&gt;FileNotFoundException&lt;/code&gt; 무시)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;thumbnail.png&lt;/h3&gt;
&lt;p&gt;120x90 PNG. 조건부 저장 (&lt;code&gt;config.saveThumbnailWithWorld&lt;/code&gt;).&lt;/p&gt;
&lt;h3&gt;script.py&lt;/h3&gt;
&lt;p&gt;World의 &lt;code&gt;Sandbox.script&lt;/code&gt; 프로퍼티의 외부 파일.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;저장: &lt;code&gt;ScriptProperty.encodeObject()&lt;/code&gt; &amp;gt; &lt;code&gt;script.py&lt;/code&gt; 생성&lt;/li&gt;
&lt;li&gt;로드: &lt;code&gt;ScriptProperty.decodeObject()&lt;/code&gt; &amp;gt; &lt;code&gt;script.py&lt;/code&gt; 읽어 &lt;code&gt;set()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;자세한 내용: &lt;strong&gt;&lt;a href=&quot;09-storing-scripts.md&quot;&gt;09-storing-scripts.md&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;elementData.xml&lt;/h3&gt;
&lt;p&gt;각 Element 하나당 하나씩 존재하는 XML 직렬화.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;자세한 스키마: &lt;strong&gt;&lt;a href=&quot;08-element-data-xml.md&quot;&gt;08-element-data-xml.md&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;하위 디렉토리 규칙&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;종류&lt;/th&gt;
&lt;th&gt;디렉토리명&lt;/th&gt;
&lt;th&gt;예&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;named child&lt;/td&gt;
&lt;td&gt;&lt;code&gt;child.name&lt;/code&gt; 값&lt;/td&gt;
&lt;td&gt;&lt;code&gt;camera&lt;/code&gt;, &lt;code&gt;my first method&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;unnamed child&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__UnnamedN__&lt;/code&gt; (0-indexed)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__Unnamed0__&lt;/code&gt;, &lt;code&gt;__Unnamed1__&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;3. World 저장 (Store) Flow&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant User as 유저
    participant AT as AuthoringTool
    participant ST as ZipFileTreeStorer
    participant EL as World (Element)
    participant SP as ScriptProperty

    User-&amp;gt;&amp;gt;AT: Ctrl+S / Save World
    AT-&amp;gt;&amp;gt;AT: saveWorldToFile(file)
    AT-&amp;gt;&amp;gt;AT: fireWorldSaving, saveTabs, saveCount
    AT-&amp;gt;&amp;gt;AT: thumbnail 생성 (설정 시)
    AT-&amp;gt;&amp;gt;ST: open(file) / parse central dir
    ST-&amp;gt;&amp;gt;EL: world.store(storer, observer, map)
    EL-&amp;gt;&amp;gt;EL: elementCountHint.txt 쓰기
    EL-&amp;gt;&amp;gt;EL: thumbnail.png 등 map 쓰기
    EL-&amp;gt;&amp;gt;EL: internalStore() 재귀
    loop 각 child
        EL-&amp;gt;&amp;gt;ST: createDir + setCurrentDir
        EL-&amp;gt;&amp;gt;EL: child.internalStore() 재귀
    end
    SP-&amp;gt;&amp;gt;ST: getKeepKey -&amp;gt; create or keep script.py
    EL-&amp;gt;&amp;gt;ST: write elementData.xml
    AT-&amp;gt;&amp;gt;ST: close() - delete, reorder, write CD
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;4. World 로드 (Load) Flow&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant AT as AuthoringTool
    participant LD as ZipFileTreeLoader
    participant EL as Element
    participant SP as ScriptProperty

    AT-&amp;gt;&amp;gt;LD: new ZipFileTreeLoader().open(file)
    LD-&amp;gt;&amp;gt;LD: read all entries to memory
    AT-&amp;gt;&amp;gt;EL: Element.load(loader, externalRoot, observer)
    EL-&amp;gt;&amp;gt;EL: read elementCountHint.txt
    EL-&amp;gt;&amp;gt;EL: parse elementData.xml -&amp;gt; Class.forName -&amp;gt; instantiate
    SP-&amp;gt;&amp;gt;SP: decodeObject -&amp;gt; read script.py -&amp;gt; set()
    loop 각 &amp;lt;child&amp;gt;
        EL-&amp;gt;&amp;gt;EL: setCurrentDir -&amp;gt; recursive load -&amp;gt; addChild
    end
    EL-&amp;gt;&amp;gt;EL: ReferenceResolver.resolve()
    AT--&amp;gt;&amp;gt;User: World loaded
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;5. ZIP 스토러 비교&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;특성&lt;/th&gt;
&lt;th&gt;ZipTreeStorer&lt;/th&gt;
&lt;th&gt;ZipFileTreeStorer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;쓰기 방식&lt;/td&gt;
&lt;td&gt;순차 ZipOutputStream&lt;/td&gt;
&lt;td&gt;RandomAccessFile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;in-place&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;getKeepKey&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;파일 기반&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용처&lt;/td&gt;
&lt;td&gt;a2c (갤러리)&lt;/td&gt;
&lt;td&gt;a2w (World)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 주요 포인트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;a2w = Element 트리 -&amp;gt; ZIP 디렉토리 1:1 매핑&lt;/li&gt;
&lt;li&gt;각 Element = &lt;code&gt;elementData.xml&lt;/code&gt; 하나&lt;/li&gt;
&lt;li&gt;스크립트 = &lt;code&gt;ScriptProperty&lt;/code&gt;에 의해 별도 &lt;code&gt;.py&lt;/code&gt; 파일로 분리 저장&lt;/li&gt;
&lt;li&gt;Keep file 메커니즘으로 변경된 부분만 재기록&lt;/li&gt;
&lt;li&gt;로드/저장 모두 재귀적 (&lt;code&gt;internalStore()&lt;/code&gt; / 재귀 &lt;code&gt;load()&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;Alice 2 - a2c 파일 포맷 (캐릭터/갤러리 오브젝트)&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;대상&lt;/strong&gt;: Alice 2 Character/Gallery Object 파일 (.a2c)의 내부 구조
&lt;strong&gt;출처&lt;/strong&gt;: &lt;code&gt;TheAliceProject/alice2&lt;/code&gt; - &lt;code&gt;LocalGalleryObject.java&lt;/code&gt;, &lt;code&gt;GalleryViewer.java&lt;/code&gt;
&lt;strong&gt;마지막 업데이트&lt;/strong&gt;: 2026-06-09&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;1. a2c 개요&lt;/h2&gt;
&lt;p&gt;a2c 파일도 a2w와 동일하게 &lt;strong&gt;ZIP archive&lt;/strong&gt;다. 차이점은:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;용도&lt;/strong&gt;: 갤러리에 표시되는 캐릭터/오브젝트/씬 조각&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;편집 가능?&lt;/strong&gt;: a2c는 &lt;strong&gt;읽기 전용&lt;/strong&gt; (갤러리에서 드래그하여 월드로 가져오기만 가능)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;저장 방식&lt;/strong&gt;: &lt;code&gt;ZipTreeStorer&lt;/code&gt; (순차 쓰기, 새 파일 생성)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;필수 파일&lt;/strong&gt;: &lt;code&gt;elementData.xml&lt;/code&gt;, &lt;code&gt;galleryData.xml&lt;/code&gt;, &lt;code&gt;thumbnail.png&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;2. a2c ZIP 내부 구조&lt;/h2&gt;
&lt;p&gt;아래는 실제 &lt;code&gt;BumperCars.a2c&lt;/code&gt;의 구조다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    subgraph ZIP[&quot;Bopper.a2c (ZIP Archive)&quot;]
        EH[&quot;elementCountHint.txt\n3\n&quot;]
        GD[&quot;galleryData.xml\n⭐ 갤러리 메타데이터&quot;]
        TN[&quot;thumbnail.png\n갤러리 썸네일&quot;]
        EX[&quot;elementData.xml\nModel (Bopper) 직렬화&quot;]
        ITA[&quot;__ita__/\n  ├ elementData.xml\n  ├ indices.bin\n  └ vertices.bin&quot;]
        MAT[&quot;Material #2_CL/\n  ├ elementData.xml\n  └ image.png&quot;]
    end

    GD --&amp;gt;|갤러리 표시용| GV[GalleryViewer]
    EX --&amp;gt;|오브젝트 데이터| LOAD[&quot;Element.load()&quot;]

    style GD fill:#fda,stroke:#a60
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. galleryData.xml&lt;/h2&gt;
&lt;p&gt;a2w와의 가장 큰 차이점은 &lt;code&gt;galleryData.xml&lt;/code&gt;이 존재한다는 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;model&amp;gt;
    &amp;lt;name&amp;gt;bumperCars&amp;lt;/name&amp;gt;
    &amp;lt;parts&amp;gt;3&amp;lt;/parts&amp;gt;
    &amp;lt;physicalsize&amp;gt;29.95m x 6.2m x 31.29m&amp;lt;/physicalsize&amp;gt;
    &amp;lt;methods/&amp;gt;
    &amp;lt;questions/&amp;gt;
    &amp;lt;sounds/&amp;gt;
&amp;lt;/model&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;태그&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;갤러리에 표시할 이름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;parts&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;구성 파트 수 (계층에서 분리 가능한 자식 Model 수)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;physicalsize&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;물리적 크기 (미터 단위)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;methods&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;노출할 메서드 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;questions&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;노출할 질문(Question) 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;sounds&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;포함된 사운드 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;갤러리에서 a2c를 드래그하여 월드에 놓으면, Alice는 &lt;code&gt;elementData.xml&lt;/code&gt;을 로드하여 Model 인스턴스를 생성하고,
&lt;code&gt;galleryData.xml&lt;/code&gt;의 정보는 &lt;code&gt;GalleryViewer&lt;/code&gt; UI에 표시되는 용도로만 사용된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;4. elementData.xml (a2c 버전)&lt;/h2&gt;
&lt;p&gt;a2c의 루트 Element는 &lt;code&gt;World&lt;/code&gt;가 아니라 &lt;strong&gt;&lt;code&gt;Model&lt;/code&gt;&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;element class=&quot;edu.cmu.cs.stage3.alice.core.Model&quot;
    modelName=&quot;Bopper&quot; name=&quot;bopper&quot; version=&quot;2.2&quot;&amp;gt;
    &amp;lt;child filename=&quot;__ita__&quot;/&amp;gt;
    &amp;lt;child filename=&quot;Material #2_CL&quot;/&amp;gt;
    &amp;lt;property name=&quot;color&quot;&amp;gt;&amp;lt;red&amp;gt;1.0&amp;lt;/red&amp;gt;&amp;lt;green&amp;gt;1.0&amp;lt;/green&amp;gt;&amp;lt;blue&amp;gt;1.0&amp;lt;/blue&amp;gt;&amp;lt;alpha&amp;gt;1.0&amp;lt;/alpha&amp;gt;&amp;lt;/property&amp;gt;
    &amp;lt;property class=&quot;java.lang.Double&quot; name=&quot;opacity&quot;&amp;gt;1.0&amp;lt;/property&amp;gt;
&amp;lt;/element&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;특징&lt;/th&gt;
&lt;th&gt;a2w (World)&lt;/th&gt;
&lt;th&gt;a2c (Character)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;루트 클래스&lt;/td&gt;
&lt;td&gt;&lt;code&gt;World&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Model&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;galleryData.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;script.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;있음 (빈 파일)&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;thumbnail.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;옵션 (설정)&lt;/td&gt;
&lt;td&gt;필수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;저장 시 스토러&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ZipFileTreeStorer&lt;/code&gt; (in-place)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ZipTreeStorer&lt;/code&gt; (순차)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;편집 가능&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;5. a2c가 갤러리에서 동작하는 방식&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Alice 실행 &amp;gt; &lt;code&gt;GalleryViewer&lt;/code&gt;가 &lt;code&gt;Required/gallery/&lt;/code&gt; 디렉토리 스캔&lt;/li&gt;
&lt;li&gt;각 &lt;code&gt;.a2c&lt;/code&gt; 파일에 대해 &lt;code&gt;LocalGalleryObject&lt;/code&gt; 생성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;thumbnail.png&lt;/code&gt; - 썸네일, &lt;code&gt;galleryData.xml&lt;/code&gt; - 이름/크기/파트 수 표시&lt;/li&gt;
&lt;li&gt;사용자가 드래그하여 월드에 가져올 때: &lt;code&gt;Element.load(a2cFile, externalRoot)&lt;/code&gt; &amp;gt; Model 인스턴스 생성&lt;/li&gt;
&lt;li&gt;&quot;Save Character&quot;는 원본 a2c를 변경하지 않고 새 &lt;code&gt;.a2c&lt;/code&gt;를 만듦&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;6. a2c와 스크립트&lt;/h2&gt;
&lt;p&gt;a2c 자체에는 Alice UI에서 직접 코드를 추가할 수 없다.
하지만 &lt;strong&gt;월드로 가져온 후&lt;/strong&gt;에는 일반 World 오브젝트처럼 취급되므로
ScriptResponse 블록 등으로 Script를 작성할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 월드에 가져온 bopper 조작
bopper.color = alice.red
alice.MoveAnimation(subject=bopper, direction=alice.forward, amount=1)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;Alice 2 - elementData.xml 스키마&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;대상&lt;/strong&gt;: &lt;code&gt;elementData.xml&lt;/code&gt;의 태그 구조와 Property 직렬화 규칙
&lt;strong&gt;출처&lt;/strong&gt;: &lt;code&gt;Element.java&lt;/code&gt; (internalStore / load), Property 하위 클래스들의 encode/decode
&lt;strong&gt;마지막 업데이트&lt;/strong&gt;: 2026-06-09&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 기본 구조&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
    subgraph XML[&quot;elementData.xml&quot;]
        ROOT[&quot;&amp;lt;element class=#quot;...#quot; name=#quot;...#quot; version=#quot;...#quot;&amp;gt;&quot;]
        CHILD[&quot;&amp;lt;child filename=#quot;...#quot;/&amp;gt;&quot;]
        PROP[&quot;&amp;lt;property name=#quot;...#quot;&amp;gt;...&amp;lt;/property&amp;gt;&quot;]
    end

    ROOT --&amp;gt;|0..n| CHILD
    ROOT --&amp;gt;|0..n| PROP
    CHILD --&amp;gt;|ZIP 하위 디렉토리| SUB[&quot;하위 elementData.xml&quot;]
    PROP --&amp;gt;|Class.forName| FIELD[&quot;Element의 Property 필드&quot;]
    PROP --&amp;gt;|encode/decode 위임| PROPCLS[&quot;Property 하위 클래스&quot;]
    PROP --&amp;gt;|참조 해결| REF[&quot;ReferenceResolver&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 루트 노드: &amp;lt;element&amp;gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;element class=&quot;edu.cmu.cs.stage3.alice.core.World&quot; name=&quot;world&quot; version=&quot;2.001&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;속성&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;class&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Element의 FQCN (Fully Qualified Class Name)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Element의 &lt;code&gt;name&lt;/code&gt; Property 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;파일 포맷 버전 (현재 &lt;code&gt;2.001&lt;/code&gt; 또는 &lt;code&gt;2.2&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;class 매핑&lt;/strong&gt;: &lt;code&gt;Element.s_classnameMap&lt;/code&gt;에 오래된/별칭 클래스명을 최신 클래스로 매핑.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Element.java static initializer
s_classnameMap.put(&quot;edu.cmu.cs.stage3.alice.core.responses.SequentialResponse&quot;,
    edu.cmu.cs.stage3.alice.core.responses.DoInOrder.class);
s_classnameMap.put(&quot;edu.cmu.cs.stage3.alice.core.responses.ProxyForScriptDefinedResponse&quot;,
    edu.cmu.cs.stage3.alice.core.responses.ScriptDefinedResponse.class);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 자식 노드: &amp;lt;child&amp;gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;child filename=&quot;camera&quot;/&amp;gt;
&amp;lt;child filename=&quot;my first method&quot;/&amp;gt;
&amp;lt;child filename=&quot;__Unnamed0__&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;filename&lt;/code&gt; = ZIP 내 &lt;strong&gt;상대 디렉토리명&lt;/strong&gt;, &lt;code&gt;getRepr(i)&lt;/code&gt;로 결정&lt;/li&gt;
&lt;li&gt;named child: &lt;code&gt;child.name.getStringValue()&lt;/code&gt; = 디렉토리명&lt;/li&gt;
&lt;li&gt;unnamed child: &lt;code&gt;&quot;__Unnamed&quot; + i&lt;/code&gt; 형식&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;로드 시: &lt;code&gt;setCurrentDirectory(filename)&lt;/code&gt; &amp;gt; 재귀 load &amp;gt; &lt;code&gt;addChild&lt;/code&gt; &amp;gt; 복귀&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Property 태그 종류&lt;/h2&gt;
&lt;h3&gt;4.1 기본형 (primitive + String)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;property class=&quot;java.lang.Double&quot; name=&quot;opacity&quot;&amp;gt;1.0&amp;lt;/property&amp;gt;
&amp;lt;property class=&quot;java.lang.Boolean&quot; name=&quot;isFirstClass&quot;&amp;gt;false&amp;lt;/property&amp;gt;
&amp;lt;property name=&quot;name&quot;&amp;gt;world&amp;lt;/property&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;class&lt;/code&gt; 속성 생략 시 기본값 &lt;code&gt;java.lang.String&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;은 Element의 Property 필드명과 일치&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.2 Color&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;property name=&quot;color&quot;&amp;gt;
    &amp;lt;red&amp;gt;1.0&amp;lt;/red&amp;gt;&amp;lt;green&amp;gt;1.0&amp;lt;/green&amp;gt;&amp;lt;blue&amp;gt;1.0&amp;lt;/blue&amp;gt;&amp;lt;alpha&amp;gt;1.0&amp;lt;/alpha&amp;gt;
&amp;lt;/property&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3 Matrix4 / visualScale&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;property name=&quot;visualScale&quot;&amp;gt;
    &amp;lt;row&amp;gt;&amp;lt;item&amp;gt;0.00512&amp;lt;/item&amp;gt;&amp;lt;item&amp;gt;0.0&amp;lt;/item&amp;gt;&amp;lt;item&amp;gt;0.0&amp;lt;/item&amp;gt;&amp;lt;/row&amp;gt;
    &amp;lt;row&amp;gt;&amp;lt;item&amp;gt;0.0&amp;lt;/item&amp;gt;&amp;lt;item&amp;gt;0.00512&amp;lt;/item&amp;gt;&amp;lt;item&amp;gt;0.0&amp;lt;/item&amp;gt;&amp;lt;/row&amp;gt;
&amp;lt;/property&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4x4 행렬 = &lt;code&gt;&amp;lt;row&amp;gt;&lt;/code&gt; 4개, 각 &lt;code&gt;&amp;lt;row&amp;gt;&lt;/code&gt;에 &lt;code&gt;&amp;lt;item&amp;gt;&lt;/code&gt; 4개.&lt;/p&gt;
&lt;h3&gt;4.4 내부 참조 (Element 간 연결)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;property criterionClass=&quot;edu.cmu.cs.stage3.alice.core.criterion.InternalReferenceKeyedCriterion&quot;
    name=&quot;userDefinedResponse&quot;&amp;gt;&amp;lt;![CDATA[my first method]]&amp;gt;&amp;lt;/property&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;criterionClass&lt;/code&gt; = &lt;code&gt;InternalReferenceKeyedCriterion&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;값 = target Element의 key (name)&lt;/li&gt;
&lt;li&gt;로드 시: &lt;code&gt;ReferenceResolver&lt;/code&gt;가 key로 target 검색&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예 - behavior의 triggerResponse:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;property criterionClass=&quot;...&quot;
    name=&quot;triggerResponse&quot;&amp;gt;behavior0.__Unnamed1__&amp;lt;/property&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.5 파일 참조 (ScriptProperty)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;property name=&quot;script&quot;&amp;gt;java.io.File[script.py]&amp;lt;/property&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ScriptProperty&lt;/code&gt;의 외부 파일 참조. &lt;code&gt;java.io.File[파일명]&lt;/code&gt; 형식&lt;/li&gt;
&lt;li&gt;로드 시 파일명 추출 &amp;gt; ZIP에서 읽기&lt;/li&gt;
&lt;li&gt;자세한 내용: &lt;strong&gt;&lt;a href=&quot;09-storing-scripts.md&quot;&gt;09-storing-scripts.md&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.6 Enum&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;property class=&quot;edu.cmu.cs.stage3.alice.scenegraph.FillingStyle&quot;
    name=&quot;fillingStyle&quot;&amp;gt;edu.cmu.cs.stage3.alice.scenegraph.FillingStyle[SOLID]&amp;lt;/property&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;PackagePath[ENUM_NAME]&lt;/code&gt; 형식.&lt;/p&gt;
&lt;h3&gt;4.7 Dictionary (data Property)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;property name=&quot;data&quot;&amp;gt;
    &amp;lt;entry&amp;gt;
        &amp;lt;key class=&quot;java.lang.String&quot;&amp;gt;edu.cmu.cs.stage3.alice.authoringtool.worldOpenTime&amp;lt;/key&amp;gt;
        &amp;lt;value class=&quot;java.lang.String&quot;&amp;gt;1980594&amp;lt;/value&amp;gt;
    &amp;lt;/entry&amp;gt;
&amp;lt;/property&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.8 Array (responses, behaviors 등)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;property componentClass=&quot;edu.cmu.cs.stage3.alice.core.Response&quot; name=&quot;responses&quot;&amp;gt;
    &amp;lt;item criterionClass=&quot;...&quot;&amp;gt;&amp;lt;![CDATA[my first method]]&amp;gt;&amp;lt;/item&amp;gt;
&amp;lt;/property&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;5. Element 클래스별 주요 Property&lt;/h2&gt;
&lt;h3&gt;World (Sandbox)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;타입&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;script&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;File 참조&lt;/td&gt;
&lt;td&gt;월드 스크립트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;responses&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;실행 가능한 Response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;behaviors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;등록된 Behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;variables&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;전역 변수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;questions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;질문(Question)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;textureMaps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;텍스처 맵&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dictionary&lt;/td&gt;
&lt;td&gt;AuthoringTool 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Response (ScriptResponse, UserDefinedResponse 등)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;타입&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;script&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;File 참조&lt;/td&gt;
&lt;td&gt;ScriptProperty -&amp;gt; .py 파일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;duration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Double&lt;/td&gt;
&lt;td&gt;실행 지속 시간 (초)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isCommentedOut&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Boolean&lt;/td&gt;
&lt;td&gt;주석 처리 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;componentResponses&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;자식 Response들&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;requiredFormalParameters&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;메서드 파라미터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;localVariables&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;지역 변수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Behavior (WorldStartBehavior, KeyEventBehavior 등)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;타입&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Boolean&lt;/td&gt;
&lt;td&gt;활성화 여부 (기본 true)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;details&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;상세 정보 (키 코드 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;triggerResponse&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;InternalReference&lt;/td&gt;
&lt;td&gt;트리거할 Response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multipleRuntimeResponsePolicy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enum&lt;/td&gt;
&lt;td&gt;중복 실행 정책&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Model&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;타입&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;color&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Color&lt;/td&gt;
&lt;td&gt;모델 색상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;opacity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Double&lt;/td&gt;
&lt;td&gt;불투명도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fillingStyle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enum&lt;/td&gt;
&lt;td&gt;채우기 스타일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;visualScale&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Matrix4&lt;/td&gt;
&lt;td&gt;변환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;diffuseColorMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;InternalReference&lt;/td&gt;
&lt;td&gt;텍스처 참조&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Variable&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;타입&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;value&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Value (any)&lt;/td&gt;
&lt;td&gt;변수 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;valueClass&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Class&lt;/td&gt;
&lt;td&gt;값의 Java 타입&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;변수 값 타입에 따라 XML 직렬화 방식이 달라짐:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;숫자/문자열/Boolean: 인라인 값&lt;/li&gt;
&lt;li&gt;오브젝트 참조: &lt;code&gt;InternalReferenceKeyedCriterion&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;6. UserDefinedResponse 전체 예시&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;element
    class=&quot;edu.cmu.cs.stage3.alice.core.response.UserDefinedResponse&quot;
    name=&quot;my first method&quot; version=&quot;2.001&quot;&amp;gt;
    &amp;lt;property name=&quot;isFirstClass&quot;&amp;gt;false&amp;lt;/property&amp;gt;
    &amp;lt;property name=&quot;data&quot;/&amp;gt;
    &amp;lt;property name=&quot;isCommentedOut&quot;/&amp;gt;
    &amp;lt;property name=&quot;duration&quot;/&amp;gt;
    &amp;lt;property name=&quot;componentResponses&quot;/&amp;gt;
    &amp;lt;property name=&quot;requiredFormalParameters&quot;/&amp;gt;
    &amp;lt;property name=&quot;keywordFormalParameters&quot;/&amp;gt;
    &amp;lt;property name=&quot;localVariables&quot;/&amp;gt;
&amp;lt;/element&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;비어 있는 Property (null)는 빈 태그로 저장. Alice는 null을 &quot;기본값 사용&quot;으로 해석.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;7. Property Encoding 위임 구조&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property 클래스&lt;/th&gt;
&lt;th&gt;XML 저장 방식&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;StringProperty&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;텍스트 노드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NumberProperty&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;class&lt;/code&gt; 속성 + 텍스트 노드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BooleanProperty&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt; / &lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ColorProperty&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;red&amp;gt;&lt;/code&gt;/&lt;code&gt;&amp;lt;green&amp;gt;&lt;/code&gt;/&lt;code&gt;&amp;lt;blue&amp;gt;&lt;/code&gt;/&lt;code&gt;&amp;lt;alpha&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Matrix44Property&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;row&amp;gt;&lt;/code&gt;/&lt;code&gt;&amp;lt;item&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DictionaryProperty&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;entry&amp;gt;&lt;/code&gt;/&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;/&lt;code&gt;&amp;lt;value&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ElementArrayProperty&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;componentClass&lt;/code&gt; + &lt;code&gt;&amp;lt;item&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ClassProperty&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;FQCN 문자열&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EnumProperty&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PackagePath[ENUM_NAME]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ScriptProperty&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;java.io.File[filename.py]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ValueProperty&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;값 타입에 따라 적절한 방식 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h1&gt;Alice 2 - 스크립트가 파일에 저장/로드되는 방식&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;대상&lt;/strong&gt;: &lt;code&gt;ScriptProperty&lt;/code&gt; 직렬화 메커니즘과 4가지 스크립트 입력면의 저장 차이
&lt;strong&gt;출처&lt;/strong&gt;: &lt;code&gt;ScriptProperty.java&lt;/code&gt;, &lt;code&gt;ScriptResponse.java&lt;/code&gt;, &lt;code&gt;ScriptDefinedResponse.java&lt;/code&gt;
&lt;strong&gt;분석 기준&lt;/strong&gt;: alice2 source + Alice 2.6.1 배포본.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;1. ScriptProperty 개요&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ScriptProperty&lt;/code&gt;는 &lt;code&gt;StringProperty&lt;/code&gt;의 하위 클래스로, 문자열 스크립트를 &lt;strong&gt;별도의 .py 파일&lt;/strong&gt;로 저장한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;classDiagram
    class Property {
        +encode(Document, Node, Storer, ReferenceGenerator)
        +decode(Node, Loader, Vector, double)
    }
    class StringProperty {
        +String getStringValue()
    }
    class ScriptProperty {
        -Code m_code
        -Object m_associatedFileKey
        +getCode(CompileType) Code
        +encodeObject()
        +decodeObject()
        +loadScript(InputStream) String
        +storeScript(OutputStream)
        +keepAnyAssociatedFiles()
    }
    Property &amp;lt;|-- StringProperty
    StringProperty &amp;lt;|-- ScriptProperty
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;핵심 특징&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;기능&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;m_code&lt;/code&gt; 캐시&lt;/td&gt;
&lt;td&gt;Jython 컴파일 결과 캐시, &lt;code&gt;set()&lt;/code&gt; 시 &lt;code&gt;null&lt;/code&gt; 초기화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;m_associatedFileKey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ZIP 내 .py 파일의 keep key (변경 감지용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getCode(compileType)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;지연 컴파일 (lazy compilation)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;파일명&lt;/td&gt;
&lt;td&gt;&lt;code&gt;getName() + &quot;.py&quot;&lt;/code&gt; (Property명 + &quot;.py&quot;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 저장 (encodeObject) 상세&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant E as Element.internalStore()
    participant SP as ScriptProperty
    participant ST as DirectoryTreeStorer
    participant ZIP as &quot;[a2w] ZIP Archive&quot;

    E-&amp;gt;&amp;gt;SP: Property 저장 요청
    SP-&amp;gt;&amp;gt;SP: filename = &quot;script.py&quot;
    SP-&amp;gt;&amp;gt;ST: storer.getKeepKey(&quot;script.py&quot;)
    ST--&amp;gt;&amp;gt;SP: associatedFileKey (or null)
    alt 변경 또는 신규
        SP-&amp;gt;&amp;gt;ST: createFile(&quot;script.py&quot;), storeScript(os), close
    else 변경 없음
        SP-&amp;gt;&amp;gt;ST: keepFile(&quot;script.py&quot;)
    end
    SP-&amp;gt;&amp;gt;SP: node.appendChild(&quot;java.io.File[script.py]&quot;)
    E-&amp;gt;&amp;gt;ST: writeXMLDocument → elementData.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;protected void encodeObject(...) {
    String filename = getName()+&quot;.py&quot;;
    Object associatedFileKey = storer.getKeepKey(filename);
    if (m_associatedFileKey==null || !m_associatedFileKey.equals(associatedFileKey)) {
        m_associatedFileKey = null;
        OutputStream os = storer.createFile(filename, true);
        storeScript(os);
        storer.closeCurrentFile();
        m_associatedFileKey = storer.getKeepKey(filename);
    } else {
        storer.keepFile(filename);
    }
    node.appendChild(createNodeForString(document, &quot;java.io.File[&quot;+filename+&quot;]&quot;));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 로드 (decodeObject) 상세&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant E as Element.load()
    participant SP as ScriptProperty
    participant LD as DirectoryTreeLoader
    participant ZIP as &quot;[a2w] ZIP Archive&quot;

    E-&amp;gt;&amp;gt;E: XML &amp;gt; property name=&quot;script&quot;
    E-&amp;gt;&amp;gt;SP: decodeObject(node, loader, refs, version)
    SP-&amp;gt;&amp;gt;SP: getNodeText(node) = &quot;java.io.File[script.py]&quot;
    SP-&amp;gt;&amp;gt;SP: getFilename() = &quot;script.py&quot;
    SP-&amp;gt;&amp;gt;LD: readFile(&quot;script.py&quot;)
    LD--&amp;gt;&amp;gt;SP: InputStream
    SP-&amp;gt;&amp;gt;SP: loadScript(is) - BufferedReader로 한 줄씩 읽음
    SP-&amp;gt;&amp;gt;SP: set(scriptString) - m_code = null
    SP-&amp;gt;&amp;gt;LD: getKeepKey(&quot;script.py&quot;) - keep key 저장
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;private String loadScript(InputStream is) throws IOException {
    BufferedReader br = new BufferedReader(
        new InputStreamReader(new BufferedInputStream(is)));
    StringBuffer sb = new StringBuffer();
    while (true) {
        String s = br.readLine();
        if (s != null) {
            sb.append(s);
            sb.append(&apos;\n&apos;);
        } else break;
    }
    if (sb.length() &amp;gt; 0)
        return sb.substring(0, sb.length() - 1);
    return &quot;&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 4가지 스크립트 입력면 저장 위치 비교&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;입력면&lt;/th&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;ZIP 내 파일 경로&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sandbox.script&lt;/strong&gt; (World)&lt;/td&gt;
&lt;td&gt;ScriptProperty&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/script.py&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ScriptResponse.script&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ScriptProperty&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/behaviorN/__UnnamedN__/script.py&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ScriptDefinedResponse.script&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ScriptProperty&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/behaviorN/__UnnamedN__/script.py&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ComboWidget / ScratchPad&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;없음 (메모리)&lt;/td&gt;
&lt;td&gt;저장되지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;중요&lt;/strong&gt;: ScriptResponse와 ScriptDefinedResponse도 &lt;code&gt;ScriptProperty&lt;/code&gt;를 사용한다!
&lt;strong&gt;세 가지 모두&lt;/strong&gt; ZIP 내 별도의 .py 파일로 저장된다. 유일한 차이는 ZIP 내 경로.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
    subgraph ZIP[&quot;world.a2w ZIP&quot;]
        SPY[&quot;/script.py (Sandbox.script)&quot;]
        RP1[&quot;/behavior0/__Unnamed0__/script.py (ScriptResponse)&quot;]
        RP2[&quot;/behavior0/__Unnamed1__/script.py (ScriptDefinedResponse)&quot;]
        EX[&quot;/elementData.xml&quot;]
    end
    EX --&amp;gt;|&quot;&amp;lt;property name=#quot;script#quot;&amp;gt;java.io.File[script.py]&amp;lt;/property&amp;gt;&quot;| SPY
    EX --&amp;gt;|behavior0 child| B0[&quot;behavior0/elementData.xml&quot;]
    B0 --&amp;gt;|__Unnamed0__ child| U0[&quot;__Unnamed0__/elementData.xml&quot;]
    U0 --&amp;gt;|script property →| RP1
    B0 --&amp;gt;|__Unnamed1__ child| U1[&quot;__Unnamed1__/elementData.xml&quot;]
    U1 --&amp;gt;|script property →| RP2
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 코드 컴파일과 캐싱&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;public Code getCode(CompileType compileType) {
    String script = getStringValue();
    if (script != null &amp;amp;&amp;amp; script.length() &amp;gt; 0) {
        if (m_code == null) {
            m_code = getOwner().compile(script, this, compileType);
        }
    } else {
        m_code = null;
    }
    return m_code;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;script 빈 문자열 → &lt;code&gt;m_code = null&lt;/code&gt; 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;m_code == null&lt;/code&gt; → &lt;code&gt;owner.compile()&lt;/code&gt;으로 Jython 컴파일&lt;/li&gt;
&lt;li&gt;결과 캐시, &lt;code&gt;set()&lt;/code&gt; 호출 시 &lt;code&gt;m_code = null&lt;/code&gt;로 초기화&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 유저 입장 저장 요약&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;편집 위치&lt;/th&gt;
&lt;th&gt;저장 위치&lt;/th&gt;
&lt;th&gt;지속성&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Edit Script &amp;gt; ScriptEditor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/script.py&lt;/code&gt; in a2w&lt;/td&gt;
&lt;td&gt;✅ 저장됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScriptResponse 블록&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/.../script.py&lt;/code&gt; in a2w&lt;/td&gt;
&lt;td&gt;✅ 저장됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScriptDefinedResponse 블록&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/.../script.py&lt;/code&gt; in a2w&lt;/td&gt;
&lt;td&gt;✅ 저장됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScriptComboWidget (Go)&lt;/td&gt;
&lt;td&gt;메모리 전용&lt;/td&gt;
&lt;td&gt;❌ 휘발&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScriptScratchPad (Perform All)&lt;/td&gt;
&lt;td&gt;메모리 전용&lt;/td&gt;
&lt;td&gt;❌ 휘발&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;ComboWidget/ScratchPad 코드는 &lt;strong&gt;저장되지 않는다!&lt;/strong&gt;
영구 유지하려면 ScriptResponse 블록이나 Sandbox Script로 옮겨야 함.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 변수/이벤트와 스크립트 관계&lt;/h2&gt;
&lt;h3&gt;변수 (Variable)&lt;/h3&gt;
&lt;p&gt;Alice UI에서 만든 전역 변수는 &lt;code&gt;Sandbox.variables&lt;/code&gt; 배열에 저장:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;property componentClass=&quot;edu.cmu.cs.stage3.alice.core.Variable&quot; name=&quot;variables&quot;&amp;gt;
    &amp;lt;item criterionClass=&quot;...&quot;&amp;gt;myVariable&amp;lt;/item&amp;gt;
&amp;lt;/property&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 Variable Element는 별도 디렉토리에 저장:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- /myVariable/elementData.xml --&amp;gt;
&amp;lt;element class=&quot;edu.cmu.cs.stage3.alice.core.Variable&quot; name=&quot;myVariable&quot;&amp;gt;
    &amp;lt;property class=&quot;java.lang.Double&quot; name=&quot;value&quot;&amp;gt;42.0&amp;lt;/property&amp;gt;
    &amp;lt;property class=&quot;java.lang.Class&quot; name=&quot;valueClass&quot;&amp;gt;java.lang.Number&amp;lt;/property&amp;gt;
&amp;lt;/element&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;스크립트에서 접근:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Alice UI 변수는 이름으로 직접 접근 (Sandbox.lookup 자동)
print(myVariable)

# Python 지역 변수 (Alice 트리와 무관)
myNewVar = 100
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Behavior와 Response 연결&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;triggerResponse&lt;/code&gt; Property로 Behavior가 실행할 Response 지정:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;element class=&quot;edu.cmu.cs.stage3.alice.core.behavior.WorldStartBehavior&quot; name=&quot;behavior0&quot;&amp;gt;
    &amp;lt;property criterionClass=&quot;...&quot; name=&quot;triggerResponse&quot;&amp;gt;behavior0.__Unnamed1__&amp;lt;/property&amp;gt;
&amp;lt;/element&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;이벤트 등록 (KeyEventBehavior)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;element class=&quot;edu.cmu.cs.stage3.alice.core.behavior.KeyEventBehavior&quot; name=&quot;keyHandler&quot;&amp;gt;
    &amp;lt;property name=&quot;keyCode&quot;&amp;gt;32&amp;lt;/property&amp;gt;    &amp;lt;!-- VK_SPACE --&amp;gt;
    &amp;lt;property name=&quot;keyEventType&quot;&amp;gt;KEY_PRESSED&amp;lt;/property&amp;gt;
    &amp;lt;property criterionClass=&quot;...&quot; name=&quot;triggerResponse&quot;&amp;gt;myScriptResponse&amp;lt;/property&amp;gt;
&amp;lt;/element&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;8. ⚠️ SoundAction → SoundResponse 호환성 문제 (a2w 로딩)&lt;/h2&gt;
&lt;h3&gt;현상&lt;/h3&gt;
&lt;p&gt;Alice 2.2 등 이전 버전에서 만든 a2w 파일에 &lt;code&gt;SoundAction&lt;/code&gt; 클래스명이 저장되어 있으면
Alice 2.6.1에서 로딩 실패.&lt;/p&gt;
&lt;h3&gt;원인&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Element.s_classnameMap&lt;/code&gt;에 &lt;code&gt;SoundAction → SoundResponse&lt;/code&gt; 매핑이 없음.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Element.java static initializer — 확인된 매핑 목록
// SoundAction → SoundResponse 매핑 없음!
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;a2w ZIP 내부에서 확인하는 방법&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;a2w를 ZIP으로 열기&lt;/li&gt;
&lt;li&gt;모든 &lt;code&gt;elementData.xml&lt;/code&gt;에서 다음 문자열 검색:&lt;pre&gt;&lt;code&gt;class=&quot;edu.cmu.cs.stage3.alice.core.responses.SoundAction&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;발견 시 &lt;code&gt;SoundAction&lt;/code&gt; → &lt;code&gt;SoundResponse&lt;/code&gt;로 수동 변경&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;수동 수정&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- before (로드 실패) --&amp;gt;
&amp;lt;element class=&quot;edu.cmu.cs.stage3.alice.core.responses.SoundAction&quot; ...&amp;gt;

&amp;lt;!-- after (로드 성공) --&amp;gt;
&amp;lt;element class=&quot;edu.cmu.cs.stage3.alice.core.responses.SoundResponse&quot; ...&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 수정으로 ZIP을 저장하면 Alice 2.6.1에서 정상 로드된다.&lt;/p&gt;
&lt;h1&gt;Alice 2 - 드래그 앤 드롭 블록 생성 메커니즘&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;대상&lt;/strong&gt;: ResponseEditor 팔레트에서 블록을 드래그하여 Element 인스턴스가 생성되는 전체 과정
&lt;strong&gt;출처&lt;/strong&gt;: &lt;code&gt;ResponseEditor.java&lt;/code&gt;, &lt;code&gt;DnDGroupingPanel.java&lt;/code&gt;, &lt;code&gt;CompositeComponentElementPanel.java&lt;/code&gt;, &lt;code&gt;ElementPrototype.java&lt;/code&gt;, &lt;code&gt;CompositeComponentResponsePanel.java&lt;/code&gt;, &lt;code&gt;GUIFactory.java&lt;/code&gt;
&lt;strong&gt;마지막 업데이트&lt;/strong&gt;: 2026-06-09&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 전체 구조 개요&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    subgraph Palette[&quot;팔레트 (initPrototypes)&quot;]
        DIO[&quot;doInOrderPrototype: DnDGroupingPanel&quot;]
        DT[&quot;doTogetherPrototype: DnDGroupingPanel&quot;]
        SCRIPT[&quot;scriptPrototype: DnDGroupingPanel&amp;lt;br/&amp;gt;label=&apos;script&apos;&quot;]
        SDR[&quot;scriptDefinedPrototype: DnDGroupingPanel&amp;lt;br/&amp;gt;label=&apos;script defined&apos;&quot;]
        IF[&quot;doIfTruePrototype: DnDGroupingPanel&quot;]
        LOOP[&quot;loopPrototype: DnDGroupingPanel&quot;]
        WL[&quot;whileLoopPrototype: DnDGroupingPanel&quot;]
    end

    Palette --&amp;gt;|&quot;DragGestureListener.dragGesturePerformed()&quot;| DND[&quot;java.awt.dnd.DragSource&quot;]
    DND --&amp;gt;|&quot;startDrag() → Transferable&quot;| TRANS[&quot;ResponsePrototypeReferenceTransferable&amp;lt;br/&amp;gt;ResponsePrototype 래핑&quot;]
    TRANS --&amp;gt;|&quot;DropTarget.drop()&quot;| DROP[&quot;CompositeComponentResponsePanel.drop()&quot;]
    DROP --&amp;gt;|&quot;getTransferData(responsePrototypeReferenceFlavor)&quot;| RP[&quot;ResponsePrototype&quot;]
    RP --&amp;gt;|&quot;createNewResponse() → createNewElement()&quot;| NEW[&quot;새 Response 인스턴스&amp;lt;br/&amp;gt;(ScriptResponse, DoInOrder 등)&quot;]
    NEW --&amp;gt;|&quot;performDrop(response, dtde)&quot;| TREE[&quot;Element 트리에 addChild&quot;]

    style SCRIPT fill:#ff0,stroke:#a80
    style SDR fill:#ff0,stroke:#a80
    style NEW fill:#aef,stroke:#08a
    style RP fill:#fda,stroke:#a60
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 팔레트 초기화 (initPrototypes)&lt;/h2&gt;
&lt;p&gt;ResponseEditor가 열릴 때, &lt;code&gt;initPrototypes()&lt;/code&gt;가 각 블록 타입별로 &lt;strong&gt;DnDGroupingPanel + ResponsePrototype&lt;/strong&gt;을 생성한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ResponseEditor.initPrototypes()
protected void initPrototypes() {
    // 1. 각 블록 타입의 표시 문자열 가져오기
    String scriptString = AuthoringToolResources.getReprForValue(
        ScriptResponse.class);  // → &quot;script&quot;

    // 2. DnDGroupingPanel 생성 (Swing JPanel, 드래그 가능)
    scriptPrototype = new DnDGroupingPanel();
    scriptPrototype.setBackground(SCRIPT_COLOR);  // 노란색 계열

    // 3. 라벨 추가 (팔레트에 표시될 텍스트)
    JLabel scriptLabel = new JLabel(scriptString);
    scriptPrototype.add(scriptLabel, BorderLayout.CENTER);

    // 4. ★핵심★ Transferable 설정 (드래그 시 전달할 데이터)
    scriptPrototype.setTransferable(
        new ResponsePrototypeReferenceTransferable(
            new ResponsePrototype(ScriptResponse.class, null, null)));

    // 5. 드래그 소스 등록
    scriptPrototype.addDragSourceComponent(scriptLabel);

    // ScriptDefinedResponse도 동일한 패턴
    scriptDefinedPrototype = new DnDGroupingPanel();
    // ... 같은 패턴 ...
    scriptDefinedPrototype.setTransferable(
        new ResponsePrototypeReferenceTransferable(
            new ResponsePrototype(ScriptDefinedResponse.class, null, null)));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 블록별로 생성되는 Prototype:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;블록&lt;/th&gt;
&lt;th&gt;Response 클래스&lt;/th&gt;
&lt;th&gt;knownPropertyValues&lt;/th&gt;
&lt;th&gt;desiredProperties&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DoInOrder&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DoInOrder.class&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DoTogether&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DoTogether.class&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;If/Else&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IfElseInOrder.class&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&quot;condition&quot;}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loop N times&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LoopNInOrder.class&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&quot;end&quot;}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;While&lt;/td&gt;
&lt;td&gt;&lt;code&gt;WhileLoopInOrder.class&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&quot;condition&quot;}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ForEach&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ForEachInOrder.class&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&quot;list&quot;}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;script&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ScriptResponse.class&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;null&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;null&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;script defined&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ScriptDefinedResponse.class&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;null&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;null&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comment&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Comment.class&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&quot;text&quot;, &quot;No comment&quot;}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Print&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Print.class&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;knownPropertyValues&lt;/code&gt;: 생성 시 미리 설정할 Property 값
&lt;code&gt;desiredProperties&lt;/code&gt;: 드롭 후 팝업 메뉴에서 사용자가 입력해야 할 Property 목록&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 드래그 시작 (Drag Source)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant User as 유저
    participant DGP as DnDGroupingPanel
    participant GDL as GroupingPanelDragGestureListener
    participant DS as DragSource
    participant TRANS as ResponsePrototypeReferenceTransferable
    participant DNDM as DnDManager

    User-&amp;gt;&amp;gt;DGP: 팔레트의 &quot;script&quot; 블록 그립(grip) 클릭 + 드래그
    DGP-&amp;gt;&amp;gt;GDL: dragGestureRecognizer 감지
    GDL-&amp;gt;&amp;gt;GDL: dragGesturePerformed(DragGestureEvent dge)

    alt Transferable이 null이 아님
        GDL-&amp;gt;&amp;gt;DS: dge.startDrag(DefaultCopyDrop, transferable, listener)
        DS-&amp;gt;&amp;gt;DNDM: DnDManager.fireDragStarted(transferable, this)
    else Transferable이 null
        GDL-&amp;gt;&amp;gt;DS: startDrag(DefaultCopyNoDrop, emptyImg, ...)
        Note over GDL: 드래그 불가, NoDrop 커서 표시
    end

    Note right of DS: 이제 Transferable이&amp;lt;br/&amp;gt;Drag-and-Drop 시스템을 통해&amp;lt;br/&amp;gt;DropTarget으로 전달됨
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;DnDGroupingPanel&lt;/code&gt;의 핵심 드래그 코드:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// DnDGroupingPanel.GroupingPanelDragGestureListener
public void dragGesturePerformed(DragGestureEvent dge) {
    if (DnDGroupingPanel.this.transferable != null) {
        dge.startDrag(DragSource.DefaultCopyDrop,
            DnDGroupingPanel.this.transferable,  // ← Transferable 객체
            DnDManager.getInternalListener());
        DnDManager.fireDragStarted(DnDGroupingPanel.this.transferable, this);
    } else {
        // 드래그 불가 — 빈 이미지로 시작 (NoDrop 커서)
        dge.startDrag(DragSource.DefaultCopyNoDrop, empty, new Point(), ...);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 데이터 전달 (Transferable)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ResponsePrototypeReferenceTransferable&lt;/code&gt;이 드래그 데이터를 캡슐화한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ResponsePrototypeReferenceTransferable
    extends ElementPrototypeReferenceTransferable {

    // 이 Transferable이 지원하는 DataFlavor
    public final static DataFlavor responsePrototypeReferenceFlavor =
        new DataFlavor(
            DataFlavor.javaJVMLocalObjectMimeType
            + &quot;; class=edu.cmu.cs.stage3.alice.authoringtool.util.ResponsePrototype&quot;,
            &quot;responsePrototypeReferenceFlavor&quot;);

    protected ResponsePrototype responsePrototype;

    public ResponsePrototypeReferenceTransferable(
            ResponsePrototype responsePrototype) {
        super(responsePrototype);
        this.responsePrototype = responsePrototype;

        // 3가지 flavor 지원
        flavors = new DataFlavor[3];
        flavors[0] = responsePrototypeReferenceFlavor;  // Response 전용
        flavors[1] = elementPrototypeReferenceFlavor;   // 범용 Element
        flavors[2] = DataFlavor.stringFlavor;           // 디버그용 문자열
    }

    public Object getTransferData(DataFlavor flavor) {
        if (flavor.equals(responsePrototypeReferenceFlavor))
            return responsePrototype;     // ← ResponsePrototype 객체 반환
        if (flavor.equals(elementPrototypeReferenceFlavor))
            return responsePrototype;
        if (flavor.equals(DataFlavor.stringFlavor))
            return responsePrototype.toString();
        throw new UnsupportedFlavorException(flavor);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;지원되는 DataFlavor 3종:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flavor&lt;/th&gt;
&lt;th&gt;용도&lt;/th&gt;
&lt;th&gt;반환 객체&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;responsePrototypeReferenceFlavor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ResponseEditor 전용 드롭&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponsePrototype&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;elementPrototypeReferenceFlavor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;범용 Element 드롭 처리&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResponsePrototype&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stringFlavor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;디버깅/폴백&lt;/td&gt;
&lt;td&gt;&lt;code&gt;responsePrototype.toString()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 드롭 처리 (Drop Target)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant DROP as CompositeComponentResponsePanel&amp;lt;br/&amp;gt;(DropTarget)
    participant DNDM as DnDManager
    participant TRANS as Transferable
    participant RP as ResponsePrototype
    participant EP as ElementPrototype

    DROP-&amp;gt;&amp;gt;DROP: drop(DropTargetDropEvent dtde)
    DROP-&amp;gt;&amp;gt;DNDM: getCurrentTransferable()
    DROP-&amp;gt;&amp;gt;TRANS: safeIsDataFlavorSupported(responsePrototypeReferenceFlavor)?

    alt Prototype 드롭 (팔레트에서)
        DROP-&amp;gt;&amp;gt;TRANS: getTransferData(responsePrototypeReferenceFlavor)
        TRANS--&amp;gt;&amp;gt;DROP: ResponsePrototype

        DROP-&amp;gt;&amp;gt;RP: getDesiredProperties().length
        alt desiredProperties 없거나 3개 초과
            RP-&amp;gt;&amp;gt;EP: createNewElement() → Class.newInstance()
            EP--&amp;gt;&amp;gt;DROP: 새 ScriptResponse 인스턴스
            DROP-&amp;gt;&amp;gt;DROP: performDrop(newResponse, dtde)
        else desiredProperties 1~3개
            DROP-&amp;gt;&amp;gt;DROP: 팝업 메뉴 표시 (사용자 입력)
            User-&amp;gt;&amp;gt;DROP: 팝업에서 값 선택
            DROP-&amp;gt;&amp;gt;RP: createNewElement() (knownPropertyValues 적용)
            DROP-&amp;gt;&amp;gt;DROP: performDrop(newResponse, dtde)
        end

    else Property 드롭 (속성 패널에서)
        DROP-&amp;gt;&amp;gt;TRANS: getTransferData(propertyReferenceFlavor)
        TRANS--&amp;gt;&amp;gt;DROP: Property 객체
        DROP-&amp;gt;&amp;gt;DROP: PropertyAnimation Response 생성
        DROP-&amp;gt;&amp;gt;DROP: performDrop(animation, dtde)

    else 기존 Response 이동/복사
        DROP-&amp;gt;&amp;gt;TRANS: getTransferData(responseReferenceFlavor)
        TRANS--&amp;gt;&amp;gt;DROP: Response 객체
        alt ACTION_MOVE
            DROP-&amp;gt;&amp;gt;DROP: performDrop(response, dtde) → shift
        else ACTION_COPY
            DROP-&amp;gt;&amp;gt;DROP: performDrop(response.copy(), dtde) → add
        end
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. ElementPrototype.createNewElement() — ★핵심 공장 메서드★&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ElementPrototype.createNewElement()&lt;/code&gt;가 실제로 Java 리플렉션을 통해 Element 인스턴스를 생성한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    START[&quot;createNewElement()&quot;] --&amp;gt; NEW[&quot;elementClass.newInstance()&amp;lt;br/&amp;gt;예: new ScriptResponse()&quot;]
    NEW --&amp;gt; KNOWN{&quot;knownPropertyValues&amp;lt;br/&amp;gt;있음?&quot;}
    KNOWN --&amp;gt;|각 knownPropertyValue에 대해| PROP[&quot;property.set(value)&quot;]
    PROP --&amp;gt;|value가 Element면| ADD[&quot;property.owner.addChild(valueElement)&quot;]
    KNOWN --&amp;gt;|끝| SPECIAL{&quot;특수 클래스인가?&quot;}

    SPECIAL --&amp;gt;|ForEach| FOREACH[&quot;item Variable 생성 + addChild&quot;]
    SPECIAL --&amp;gt;|LoopNInOrder| LOOP[&quot;index Variable 생성 + addChild&quot;]
    SPECIAL --&amp;gt;|기타| DONE[&quot;Element 반환&quot;]

    FOREACH --&amp;gt; DONE
    LOOP --&amp;gt; DONE
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// ElementPrototype.createNewElement() — 핵심 로직
public Element createNewElement() {
    // 1. Class.newInstance()로 Element 생성
    Element element = (Element)elementClass.newInstance();

    // 2. knownPropertyValues 설정 (미리 지정된 값)
    if (knownPropertyValues != null) {
        for (int i = 0; i &amp;lt; knownPropertyValues.length; i++) {
            String propertyName = knownPropertyValues[i].getString();
            Object propertyValue = knownPropertyValues[i].getObject();
            Property property = element.getPropertyNamed(propertyName);
            property.set(propertyValue);

            // value가 Element이면 owner의 child로 추가
            if (propertyValue instanceof Element) {
                Element e = (Element)propertyValue;
                if (e.getParent() == null &amp;amp;&amp;amp; !(e instanceof World)) {
                    property.getOwner().addChild(e);
                    e.data.put(&quot;associatedProperty&quot;, property.getName());
                }
            }
        }
    }

    // 3. 특수 클래스 처리 — 자동 변수 생성
    if (ForEachInOrder.class.isAssignableFrom(elementClass)) {
        Variable eachVar = new Variable();
        eachVar.name.set(&quot;item&quot;);
        eachVar.valueClass.set(Object.class);
        element.addChild(eachVar);
        ((ForEachInOrder)element).each.set(eachVar);
    } else if (LoopNInOrder.class.isAssignableFrom(elementClass)) {
        Variable indexVar = new Variable();
        indexVar.name.set(&quot;index&quot;);
        indexVar.valueClass.set(Number.class);
        element.addChild(indexVar);
        ((LoopNInOrder)element).index.set(indexVar);
    }
    // ... WhileLoop 등 유사 패턴

    return element;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;7. performDrop() — Element 트리에 연결&lt;/h2&gt;
&lt;p&gt;드롭된 Element가 실제 Alice Element 트리에 추가되는 마지막 단계.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    DROP[&quot;performDrop(toDrop, dtde)&quot;] --&amp;gt; RECURSE{&quot;재귀 호출?&quot;}
    RECURSE --&amp;gt;|예| WARN[&quot;경고 다이얼로그 표시&amp;lt;br/&amp;gt;취소 가능&quot;]
    RECURSE --&amp;gt;|아니오| COPY{&quot;ACTION_COPY?&quot;}
    COPY --&amp;gt;|예| CLONE[&quot;toDrop = toDrop.HACK_createCopy()&quot;]
    COPY --&amp;gt;|아니오| MOVE{&quot;이미 같은 배열에&amp;lt;br/&amp;gt;있는 Element?&quot;}
    MOVE --&amp;gt;|예| SHIFT[&quot;componentElements.shift(old, new)&quot;]
    MOVE --&amp;gt;|아니오| ADD[&quot;unhook(toDrop) → removeFromParent()&quot;]
    ADD --&amp;gt;|&quot;addToElement(toDrop, componentElements, position)&quot;| FINAL
    SHIFT --&amp;gt; FINAL
    CLONE --&amp;gt; ADD2[&quot;addToElement(toDrop, componentElements, position)&quot;]
    ADD2 --&amp;gt; FINAL
    FINAL[&quot;✅ UndoRedoStack.stopCompound()&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// CompositeComponentElementPanel.performDrop() — 최종 Element 트리 삽입
protected void performDrop(Element toDrop, DropTargetDropEvent dtde) {
    // 재귀 호출 검사
    if (isRecursive(toDrop)) {
        // 경고 다이얼로그 → 사용자가 취소 가능
        int result = DialogManager.showOptionDialog(...);
        if (result != 0) return;
    }

    if (authoringTool != null)
        authoringTool.getUndoRedoStack().startCompound();

    int position = getInsertLocation(...);

    if ((dtde.getDropAction() &amp;amp; ACTION_COPY) &amp;gt; 0) {
        // ★복사: createCopy로 Element 복제
        toDrop = toDrop.HACK_createCopy(null, null, position, null, ...);
    }

    if (!isCopy &amp;amp;&amp;amp; componentElements.contains(toDrop)) {
        // ★이동: 같은 배열 내에서 위치 변경
        int oldIndex = componentElements.indexOf(toDrop);
        position = adjustPosition(oldIndex, position);
        componentElements.shift(oldIndex, position);
    } else {
        // ★신규: 부모 변경 후 ObjectArrayProperty에 add
        unhook(toDrop);                     // 기존 부모에서 제거
        addToElement(toDrop, componentElements, position);
    }

    if (authoringTool != null)
        authoringTool.getUndoRedoStack().stopCompound();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 블록별 드롭 결과 예시&lt;/h2&gt;
&lt;h3&gt;&quot;script&quot; 블록 드롭&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;팔레트 [script] → 드래그 → DoInOrder 위에 드롭
    ↓
ResponsePrototype(ScriptResponse.class, null, null)
    ↓
ElementPrototype.createNewElement()
    ↓
new ScriptResponse()  ← ScriptProperty(script=&quot;&quot;, duration=0)
    ↓
componentElements.add(position, scriptResponse)
    ↓
DoInOrder의 자식 Response로 등록
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&quot;script defined&quot; 블록 드롭&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;팔레트 [script defined] → 드래그 → DoInOrder 위에 드롭
    ↓
ResponsePrototype(ScriptDefinedResponse.class, null, null)
    ↓
new ScriptDefinedResponse()  ← ScriptProperty(script=&quot;&quot;)
    ↓
componentElements.add(position, scriptDefinedResponse)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;속성에서 PropertyAnimation 드롭&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;bunny.color 속성을 드래그 → DoInOrder 위에 드롭
    ↓
PropertyAnimation.response 팝업 (value/지속시간 선택)
    ↓
new PropertyAnimation()
    element = property.getOwner()  // bunny
    propertyName = &quot;color&quot;
    value = 사용자 선택 값
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;9. 키 포인트 요약&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;팔레트의 각 블록은 &lt;code&gt;DnDGroupingPanel&lt;/code&gt; + &lt;code&gt;ResponsePrototype&lt;/code&gt; 쌍&lt;/strong&gt;이다.&lt;/li&gt;
&lt;li&gt;드래그 시 &lt;code&gt;ResponsePrototypeReferenceTransferable&lt;/code&gt;이 &lt;strong&gt;&lt;code&gt;ResponsePrototype&lt;/code&gt; 객체를 전달&lt;/strong&gt;한다.&lt;/li&gt;
&lt;li&gt;드롭 시 &lt;code&gt;CompositeComponentResponsePanel.drop()&lt;/code&gt;이 &lt;strong&gt;&lt;code&gt;responsePrototypeReferenceFlavor&lt;/code&gt;&lt;/strong&gt; 로 Prototype을 추출한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ElementPrototype.createNewElement()&lt;/code&gt;가 &lt;strong&gt;&lt;code&gt;Class.newInstance()&lt;/code&gt;&lt;/strong&gt; 로 Element를 생성하고, knownPropertyValues를 설정한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;performDrop()&lt;/code&gt;이 &lt;strong&gt;Element 트리의 &lt;code&gt;ObjectArrayProperty&lt;/code&gt;&lt;/strong&gt; 에 새 Element를 추가한다.&lt;/li&gt;
&lt;li&gt;복사/이동/신규 생성 모두 동일한 &lt;code&gt;performDrop()&lt;/code&gt;을 통해 처리된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ForEach&lt;/code&gt;/&lt;code&gt;LoopN&lt;/code&gt; 등 특수 블록은 &lt;strong&gt;자동으로 index/item 변수&lt;/strong&gt;를 생성한다.&lt;/li&gt;
&lt;li&gt;Undo/Redo는 &lt;code&gt;startCompound()/stopCompound()&lt;/code&gt;로 그룹핑된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ScriptResponse와 ScriptDefinedResponse의 차이는 &lt;code&gt;createNewElement()&lt;/code&gt; 시점에는 없다.&lt;/strong&gt;&lt;br /&gt;
둘 다 동일한 패턴으로 &lt;code&gt;new ScriptResponse()&lt;/code&gt; / &lt;code&gt;new ScriptDefinedResponse()&lt;/code&gt; 인스턴스만 생성된다.&lt;br /&gt;
차이는 런타임에 &lt;code&gt;CompileType.EXEC_MULTIPLE&lt;/code&gt; vs &lt;code&gt;CompileType.EVAL&lt;/code&gt;로 나타난다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h1&gt;11 - 문제 해결: 실전 문제 해결&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;Alice 2.6.1에서 a2w 월드를 만들고 수정할 때 실제로 마주친 문제들과 해결 방법.
&lt;strong&gt;분석 기준&lt;/strong&gt;: 분할주의력검사 월드 개발 과정에서 확인된 내용.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 월드 로딩 실패 — &quot;Unable to load world&quot;&lt;/h2&gt;
&lt;h3&gt;케이스 A: SoundAction ClassNotFoundException&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;에러 메시지&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ClassNotFoundException: edu.cmu.cs.stage3.alice.core.response.SoundAction
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;원인&lt;/strong&gt;: Alice 2의 블록 편집기에서 &lt;code&gt;SoundAction&lt;/code&gt; 클래스명으로 저장된 블록이 있는데,
&lt;code&gt;Element.s_classnameMap&lt;/code&gt;에 &lt;code&gt;SoundAction → SoundResponse&lt;/code&gt; 매핑이 없다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결&lt;/strong&gt;: a2w ZIP 파일을 직접 열어 &lt;code&gt;elementData.xml&lt;/code&gt;에서 &lt;code&gt;SoundAction&lt;/code&gt;을 검색하여 &lt;code&gt;SoundResponse&lt;/code&gt;로 변경.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. a2w 파일을 ZIP 유틸리티(7-Zip 등)로 열기
2. 모든 elementData.xml에서 &quot;SoundAction&quot; 검색
3. class=&quot;...&quot; 속성을 SoundResponse로 변경
   - &quot;edu.cmu.cs.stage3.alice.core.responses.SoundAction&quot;
   → &quot;edu.cmu.cs.stage3.alice.core.responses.SoundResponse&quot;
4. ZIP 저장 후 다시 로드
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;케이스 B: Property.decodeObject RuntimeException&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;에러 메시지&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;java.lang.RuntimeException
    at edu.cmu.cs.stage3.alice.core.Property.decodeObject(Property.java:663)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;원인&lt;/strong&gt;: elementData.xml의 Property 값 디코딩 실패. 특정 Property가 예상 타입과 다른 값이 저장된 경우.
주로 변수 &lt;code&gt;valueClass&lt;/code&gt; 불일치, Reference 해석 실패 등.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alice에서 &quot;Yes&quot; 눌러 None으로 설정하고 계속 진행&lt;/li&gt;
&lt;li&gt;이후 해당 Property를 UI에서 직접 재설정&lt;/li&gt;
&lt;li&gt;또는 ZIP 내 elementData.xml을 열어 해당 Property 값 확인/수정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;케이스 C: InternalReferenceKeyedCriterion 경고&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;경고 메시지&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WARNING: unable to resolve references:
...triggerResponse → ...InternalReferenceKeyedCriterion[event2.Unnamed0]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;원인&lt;/strong&gt;: ZIP 내에 참조 대상 디렉토리(&lt;code&gt;__Unnamed0__&lt;/code&gt;)가 없거나, 참조 키가 불일치.
블록 편집기에서 behavior의 &lt;code&gt;triggerResponse&lt;/code&gt; 연결이 깨짐.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&quot;Yes&quot; 눌러 로딩 계속&lt;/li&gt;
&lt;li&gt;Alice UI에서 해당 behavior의 triggerResponse 속성 재할당&lt;/li&gt;
&lt;li&gt;또는 ZIP 내 elementData.xml에서 criterionClass 참조 수정&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 수정된 월드가 로딩 시 Reference Warning&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;현상&lt;/strong&gt;: 팀원이 저장한 a2w를 열면 수많은 &lt;code&gt;unable to resolve references&lt;/code&gt; 경고가 뜸&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원인&lt;/strong&gt;: ZIP 내 unnamed child 디렉토리명(&lt;code&gt;__UnnamedN__&lt;/code&gt;)의 인덱스가 elementData.xml의 참조와 불일치.
블록 편집기에서 복사/이동/삭제 시 내부 참조가 깨질 수 있음.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&quot;Yes&quot; 누르고 로딩 (모든 참조 None 처리)&lt;/li&gt;
&lt;li&gt;Alice UI에서 behavior들의 &lt;code&gt;triggerResponse&lt;/code&gt;를 다시 연결&lt;/li&gt;
&lt;li&gt;저장 전에 모든 behavior 연결 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;3. stopSign이 car와 함께 움직임&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;현상&lt;/strong&gt;: &lt;code&gt;carMoveLoop&lt;/code&gt;에서 car가 앞으로 갈 때 stopSign도 따라 움직임&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원인&lt;/strong&gt;: stopSign의 &lt;code&gt;vehicle&lt;/code&gt; Property가 car로 설정되어 있거나,
carMoveLoop에서 &lt;code&gt;stopSign move backward 0.5&lt;/code&gt; 동기화 코드가 들어가 있음.
블록 구성에 따라 의도된 동작일 수도 있음 (stopSign을 car-relative로 배치한 경우).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;확인 방법&lt;/strong&gt;: Alice UI에서 stopSign 선택 → &lt;code&gt;vehicle&lt;/code&gt; 속성 확인&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;vehicle = world: stopSign은 고정 (car와 무관)&lt;/li&gt;
&lt;li&gt;vehicle = car: stopSign은 car의 자식처럼 움직임&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;수정&lt;/strong&gt;: stopSign의 vehicle을 world로 설정하거나, carMoveLoop에서 stopSign 이동 블록 제거&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. SayAnimation에서 변수 값이 안 보임&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;현상&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;car.say(&quot;결과: ${score}점&quot;)  # 스크립트는 동작하지 않음
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;또는 블록으로 &lt;code&gt;car say score&lt;/code&gt; 했는데 레이블 없이 숫자만 뜸&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원인 &amp;amp; 해결&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Script는 동작하지 않음&lt;/strong&gt; — Alice 2.6.1 Jython NPE 문제 (01-architecture.md 참고)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;한글이 안 보임&lt;/strong&gt; — Alice 2의 Text3D/SayAnimation은 CJK 글리프를 지원하지 않음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;레이블 + 값 표시&lt;/strong&gt; — &lt;code&gt;DoTogether&lt;/code&gt;로 여러 SayAnimation 블록 연결:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;DoTogether:
  car → say &quot;Score: &quot; (0.1초)
  car → say score (0.1초)
  car → say &quot; | Wrong: &quot; (0.1초)
  car → say wrongCount (0.1초)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Text3D 오브젝트 사용&lt;/strong&gt; — 미리 &lt;code&gt;ScoreText&lt;/code&gt; 등의 Text3D를 배치해두고 &lt;code&gt;text&lt;/code&gt; Property를
블록의 &lt;code&gt;PropertyAnimation&lt;/code&gt;으로 업데이트 → 하지만 3D 공간에 위치하므로 차가 이동하면 화면 밖으로 나감&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;5. Print 구문이 안 보임&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;현상&lt;/strong&gt;: ScriptScratchPad에서 &lt;code&gt;print(&quot;test&quot;)&lt;/code&gt; 실행했는데 아무 출력도 안 보임&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원인&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alice 2의 &lt;code&gt;System.out&lt;/code&gt;은 기본적으로 Alice 로그 창으로 리디렉션&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ScriptingFactory.setStdOut()&lt;/code&gt;를 호출해야 출력 스트림이 연결됨&lt;/li&gt;
&lt;li&gt;UI 상에 출력창이 보이지 않을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;대안&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Print&lt;/code&gt; 블록 사용 (ResponseEditor 팔레트에 있음)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SayAnimation&lt;/code&gt; 블록으로 car 등에 말풍선 표시&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 테스트 종료 후에도 car가 계속 움직임&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;현상&lt;/strong&gt;: &lt;code&gt;checkEndCondition&lt;/code&gt;에서 &lt;code&gt;testRunning = 0&lt;/code&gt;으로 설정했는데도 carMoveLoop가 계속 돎&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원인&lt;/strong&gt;: WhileLoopInOrder는 조건을 &lt;strong&gt;behavior 실행 시점&lt;/strong&gt;에 평가.
&lt;code&gt;testRunning = 0&lt;/code&gt;이 설정된 후 다음 behavior 스케줄 시점까지 딜레이가 있을 수 있음.
또는 &lt;code&gt;WhileLoopInOrder&lt;/code&gt;의 condition이 &lt;code&gt;isShowing&lt;/code&gt; 등 다른 Property를 보고 있을 가능성.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결 방법&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WhileLoop의 condition이 &lt;code&gt;testRunning == 1&lt;/code&gt; (Variable 비교)인지 확인&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkEndCondition&lt;/code&gt; 메서드가 &lt;code&gt;WhileLoop&lt;/code&gt;보다 먼저 실행되는지 behavior 순서 확인&lt;/li&gt;
&lt;li&gt;또는 WhileLoop 내부에 &lt;code&gt;If testRunning == 0 → break&lt;/code&gt; 블록 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 한글/글자 깨짐&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;원인&lt;/strong&gt;: Alice 2.6.1의 3D 텍스트 렌더러(Text3D, SayAnimation)가 CJK(CJK Unified Ideographs)
글리프를 지원하지 않음. 기본 폰트에 한글 글리프가 없음.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;모든 사용자 텍스트를 &lt;strong&gt;영어&lt;/strong&gt;로 작성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;정답: 5&lt;/code&gt; → &lt;code&gt;Correct: 5&lt;/code&gt; / &lt;code&gt;Score: 5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;오답: 2&lt;/code&gt; → &lt;code&gt;Wrong: 2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;평균 반응시간&lt;/code&gt; → &lt;code&gt;Avg Time&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;검사 결과&lt;/code&gt; → &lt;code&gt;Results&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;시작하려면 스페이스바&lt;/code&gt; → &lt;code&gt;Press SPACE to start&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 변수 값이 SayAnimation에 반영 안 됨&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;현상&lt;/strong&gt;: &lt;code&gt;score&lt;/code&gt; 값을 SayAnimation으로 표시했는데 항상 0&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원인&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SayAnimation(what=score)&lt;/code&gt;는 &lt;strong&gt;variable reference&lt;/strong&gt;를 사용&lt;/li&gt;
&lt;li&gt;variable이 업데이트되어도 SayAnimation의 텍스트는 &lt;strong&gt;prologue 시점에 고정&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;또는 SayAnimation의 duration이 0이면 화면에 표시될 시간이 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;해결&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;duration을 충분히 설정 (0.5~1초)&lt;/li&gt;
&lt;li&gt;값 업데이트 후 새로운 SayAnimation 실행&lt;/li&gt;
&lt;li&gt;DoTogether로 여러 SayAnimation 동시 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;9. a2w ZIP 직접 수정 가이드&lt;/h2&gt;
&lt;p&gt;ZIP 내부를 직접 수정해야 할 때:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;world.a2w/
├── elementData.xml           # 월드 전체 구조
├── script.py                 # Sandbox Script (실행 안 됨)
├── camera/
├── car/
│   └── elementData.xml
├── stopSign/
│   └── elementData.xml
├── score/
│   └── elementData.xml
├── my first method/
│   └── elementData.xml       # 메서드 정의
├── behavior0/                 # World start behavior
│   ├── elementData.xml
│   └── __Unnamed0__/
│       └── elementData.xml   # triggerResponse의 내용
└── event/                     # 키 이벤트 behavior
    ├── elementData.xml
    └── __Unnamed0__/
        └── elementData.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;수정 팁&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ZIP 파일 바로 열기 → 7-Zip 등으로 열고 텍스트 파일 직접 편집&lt;/li&gt;
&lt;li&gt;&lt;code&gt;elementData.xml&lt;/code&gt;에서 &lt;code&gt;&amp;lt;property name=&quot;class&quot;&amp;gt;&lt;/code&gt; 값 수정으로 클래스명 변경 가능&lt;/li&gt;
&lt;li&gt;변수 값: &lt;code&gt;&amp;lt;property class=&quot;java.lang.Double&quot; name=&quot;value&quot;&amp;gt;5.0&amp;lt;/property&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;참조 복구: &lt;code&gt;&amp;lt;property criterionClass=&quot;...&quot; name=&quot;triggerResponse&quot;&amp;gt;behavior0.__Unnamed1__&amp;lt;/property&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;저장 후 Alice로 로드 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;10. &quot;enableScripting&quot; 메뉴 안 보임&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;현상&lt;/strong&gt;: 우클릭 메뉴에 &quot;Edit Script&quot;가 없음&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원인&lt;/strong&gt;: Alice 설정에서 &lt;code&gt;enableScripting = false&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결&lt;/strong&gt;: Alice 설치 디렉토리에서 설정 파일 확인&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Required/&lt;/code&gt; 아래 설정에서 &lt;code&gt;enableScripting=true&lt;/code&gt;로 변경&lt;/li&gt;
&lt;li&gt;또는 &lt;code&gt;Alice.ini&lt;/code&gt; 등 설정 파일 수정&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Bluesky의 ID</title><link>https://blog.euroka.moe/posts/atproto-did/</link><guid isPermaLink="true">https://blog.euroka.moe/posts/atproto-did/</guid><pubDate>Tue, 10 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;ID&lt;/h2&gt;
&lt;p&gt;내 프로필은 여기 있다. &lt;a href=&quot;https://bsky.app/profile/euroka.moe&quot;&gt;https://bsky.app/profile/euroka.moe&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이 링크는 2개로 나눌 수 있다. bsky.app 앱뷰, euroka.moe 핸들. 그런데...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;왜 도메인 형식을 지닐까?&lt;/li&gt;
&lt;li&gt;PDS를 변경해도 핸들은 왜 같을까?&lt;/li&gt;
&lt;li&gt;핸들을 변경해도 어떻게 게시글 등은 유지될까?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;DID&lt;/h2&gt;
&lt;p&gt;이때 DID가 등장한다. Decentralized ID의 약자로, DID 종류(메서드)에 따라 탈중앙화된 식별자를 고유하게 생성한다. 180개 이상의 DID 종류가 있지만, &lt;a href=&quot;https://atproto.com/specs/did#blessed-did-methods&quot;&gt;ATProto에서 사용하는 DID&lt;/a&gt;는 두 가지가 있다.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;did:web&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;did:web&lt;/code&gt;은 자신의 DID document를 &lt;code&gt;.well-known/did.json&lt;/code&gt; 경로에 둔다. &lt;code&gt;did:web:euroka.moe&lt;/code&gt;의 경우 &lt;code&gt;https://euroka.moe/.well-known/did.json&lt;/code&gt;에 있는 것이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
내 DID는 &lt;code&gt;did:web&lt;/code&gt;을 쓰지 않는다! 예시용일 뿐이니 참고 바란다.
&lt;img src=&quot;image.png&quot; alt=&quot;DNS lookup for euroka.moe A record, no records found.&quot; /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;장점은,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;탈중앙화에 더 가깝다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DNS를 신뢰해야 한다는 문제가 있지만, 이건 뭐 어쩔 수 없지 않나.
공인되고 검증된 기관이 운영한다는 것에 기대는 게 그나마 나을 것이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;did가 직관적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://pdsls.dev/at://did:plc:qjua73d5eq537nio2sucdaj3&quot;&gt;&lt;code&gt;did:plc:qjua73d5eq537nio2sucdaj3&lt;/code&gt;&lt;/a&gt;와 &lt;code&gt;did:web:euroka.moe&lt;/code&gt; 중 기억하기 쉬운 것을 고르라 하면 당연히 후자 아닌가?&lt;/p&gt;
&lt;p&gt;하지만 단점이 상당하다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;도메인의 &lt;code&gt;.well-known/did.json&lt;/code&gt; 접근에만 의존한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;도메인 만료, 호스팅 업체의 변덕, 레코드 작성 실수, 모든 것은 DID document의 제어권 유실로 이어진다.
그리고 DID document의 제어권 유실은 signing 키나 PDS 변경, 핸들(DID document의 &lt;code&gt;alsoKnownAs&lt;/code&gt; 값) 등을 불가능하게 만든다.
HTTPS 인증서 주던 CA가 터졌다? 다른 CA에서 인증서 받아오기 전까진, DID document는 다른 사람들에게 보이지 않게 되고, DID document가 없으니 존재하지 않는 자가 된다. 게시글, 팔로우 기록, 등등. 이것들은 존재하지 않는 사람이 한 것으로 나온다.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;did:plc&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;did:plc&lt;/code&gt;는 제일 간결하고 탈중앙화는 아니지만 타협적인 방법이다. &lt;a href=&quot;https://plc.directory/&quot;&gt;PLC Dictionary&lt;/a&gt;라는 중앙화된 원장에 저장한다. 그리고 이걸 &lt;a href=&quot;https://web.plc.directory/api/redoc&quot;&gt;REST API로 모두에게 공개&lt;/a&gt;한다. 감사 로그와 키 서명 검증을 통하여 투명성을 확보하지만, 그렇다고 완전히 염려가 없는 것은 아니다. 하지만, 이는 매우 큰 장점이 있다.&lt;/p&gt;
&lt;p&gt;도메인의 사용처를 &lt;code&gt;alsoKnownAs&lt;/code&gt;를 채우는 데만 쓰는 게 가능하다. PLC Directory가 망하지 않는 이상, 도메인이 털려도 PLC Directory는 털리지 않으니 원래 있던 recovery 키로 &lt;code&gt;alsoKnownAs&lt;/code&gt;를 수정하면 그만이다.
이 장점 덕분에 절대 다수의 유저들은 &lt;code&gt;did:plc&lt;/code&gt;를 사용한다.&lt;/p&gt;
&lt;h2&gt;DID의 검증&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://pdsls.dev/at://did:plc:qjua73d5eq537nio2sucdaj3&quot;&gt;PDSls 서비스에서 DID document&lt;/a&gt;를 보기 쉽게 확인할 수 있다. 여기서 Identity 탭의 Aliases 항목을 보자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;이는 DID document의 &lt;code&gt;alsoKnownAs&lt;/code&gt;를 알려주는 곳이다. DNS (TXT record) 항목에서 &lt;code&gt;did:plc:qjua73d5eq537nio2sucdaj3&lt;/code&gt;가 적혀임을 알 수 있다.
DID document에서 &lt;code&gt;did:plc:qjua...&lt;/code&gt;가 &lt;code&gt;euroka.moe&lt;/code&gt;를 통제한다고 주장한다. 주장일 뿐이고, 인증되지 않았다. 이제 &lt;code&gt;euroka.moe&lt;/code&gt;가 &lt;code&gt;did:plc:qjua...&lt;/code&gt;를 통제한다고 주장하면 상호 통제 상태가 되고, 즉 같은 사람에게 제어받는다 생각할 수 있다.&lt;/p&gt;
&lt;p&gt;핸들의 &lt;code&gt;${핸들}/.well-known/atproto-did&lt;/code&gt; 파일을 확인해서 검증할 수도 있지만, 이는 예시로 쓸 사용자가 없어 생략한다.&lt;/p&gt;
</content:encoded></item><item><title>서버 배포를 쌀먹하기 w. Hono</title><link>https://blog.euroka.moe/posts/deployment/</link><guid isPermaLink="true">https://blog.euroka.moe/posts/deployment/</guid><pubDate>Sat, 07 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;돈벌기&lt;/h1&gt;
&lt;p&gt;&amp;lt;img src=&quot;./images/194a891a978577680.png&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;돈이 없다. 이것은 학생의 숙명이다. 프리티어를 떠도는 노마드의 삶이다.
그런데 코딩을 하면 배포할 일이 생긴다. 당연한 거 아닌가. 배포를 그럼 git clone하라고 할 수도 없지 않나.&lt;/p&gt;
&lt;h1&gt;FaaS/서버리스&lt;/h1&gt;
&lt;p&gt;서버리스의 장점은 그것이다. 24/7이 필요하긴 한데, 굳이 막 항상 사람이 들어오는 건 아닌 사이트 많지 않나? 그런 상황이 많다.
서버리스는 그 상황에 딱 맞는다. 트래픽 없다? 서버 내릴게. 서버 내려? 돈 안뺄게.&lt;/p&gt;
&lt;h2&gt;Azure Functions&lt;/h2&gt;
&lt;p&gt;Azure Functions는 마이크로소프트의 서버리스 플랫폼이다.&lt;/p&gt;
&lt;p&gt;장점:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;언어 독립적이다, 안 되면 커스텀 핸들러로 어떻게든 된다&lt;/li&gt;
&lt;li&gt;SLA가 있다(무료티어 포함)&lt;/li&gt;
&lt;li&gt;무료 티어도 있고 학생 크레딧을 쓸 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;단점:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하드캡이 없다. 카드 넣어야 한다&lt;/li&gt;
&lt;li&gt;콜드 스타트가 좀 걸린다&lt;/li&gt;
&lt;li&gt;저장공간은 또 다른 서비스 써야 한다&lt;/li&gt;
&lt;li&gt;&lt;s&gt;꽤 고립적이다&lt;/s&gt; &lt;a href=&quot;#hono&quot;&gt;Hono&lt;/a&gt;가 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Cloudflare Workers&lt;/h2&gt;
&lt;p&gt;Cloudflare Workers는 JS를 지원하는 FaaS이다.&lt;/p&gt;
&lt;p&gt;장점:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;빠른 콜드 스타트(0ms!!!!)&lt;/li&gt;
&lt;li&gt;무료티어 넘어가면 청구 없이 터진다(Paid 플랜 업그레이드하지 않으면)&lt;/li&gt;
&lt;li&gt;CI/CD를 CF에서 제공한다(=Github Action 요금 낼 필요 없다, 워크플로우 짤 필요 없다)&lt;/li&gt;
&lt;li&gt;분당 1000회 무료 티어&lt;/li&gt;
&lt;li&gt;다른 WinterTC 계열 서비스 대비 더 넉넉하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;단점:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JS와 WASM 컴파일을 지원하는 일부 언어만 가능하다&lt;/li&gt;
&lt;li&gt;Github Action은 어짜피 퍼블릭레포 무제한 무료라 굳이...?&lt;/li&gt;
&lt;li&gt;런타임은 Node.JS와 거리가 멀다, DOM 없다&lt;/li&gt;
&lt;li&gt;CPU 타임이 제한적이다(무료플랜 10ms/콜)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Deno Deploy&lt;/h2&gt;
&lt;p&gt;Deno Deploy는 Cloudflare Workers와 유사한 FaaS 호스팅 서비스다.&lt;/p&gt;
&lt;p&gt;장점:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloudflare Workers 대비 넉넉한 CPU 타임(15h/월)&lt;/li&gt;
&lt;li&gt;빠른 콜드 스타트&lt;/li&gt;
&lt;li&gt;셋업하기 쉽다(&lt;code&gt;import { Hono } from &quot;jsr:hono&quot;&lt;/code&gt;, TypeScript OOTB 지원 등)&lt;/li&gt;
&lt;li&gt;제일 JS스럽고, 배워야 할 자체 개념이 적다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;단점:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JS/TS만 지원한다&lt;/li&gt;
&lt;li&gt;카드 등록이 필요하다(업그레이드하기 전까진 billing X)&lt;/li&gt;
&lt;li&gt;Cloudflare Workers 대비 물리적 인프라가 떨어진다&lt;/li&gt;
&lt;li&gt;다들 Cloudflare 계정은 있지만 Deno Deploy는 없는 경우도 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Hono&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;신&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hono는 각종 FaaS 프로바이더를 위한 프레임워크다.
지원하는 실행 환경은...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;Deno(Deno Deploy 포함)&lt;/li&gt;
&lt;li&gt;Bun&lt;/li&gt;
&lt;li&gt;WebAssembly&lt;/li&gt;
&lt;li&gt;Service Worker&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러하다. 또 호스팅은 이 플랫폼들을 지원한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloudflare Workers, Cloudflare Pages&lt;/li&gt;
&lt;li&gt;Fastly Compute&lt;/li&gt;
&lt;li&gt;Vercel, Netlify&lt;/li&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;AWS Lambda, Lambda@Edge&lt;/li&gt;
&lt;li&gt;Azure Functions&lt;/li&gt;
&lt;li&gt;Google Cloud Run&lt;/li&gt;
&lt;li&gt;Supabase Functions&lt;/li&gt;
&lt;li&gt;Ali Function Compute&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 무슨 개얼탱없는 라인업이란 말인가. 당장 써야지.
원래 FaaS 서비스들은 꽤 벤더 종속적이다.
벤더에서 제공한 API를 쓰게 되는데, 이러면 다른 곳으로 마이그레이션하기 상당히 어렵다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Cloudflare Workers, ES Modules 문법
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    const name = url.searchParams.get(&apos;name&apos;);

    if (name) {
      return new Response(`Hello, ${name}! Welcome to Cloudflare Workers.`, {
        status: 200,
        headers: {
          &apos;Content-Type&apos;: &apos;text/plain; charset=utf-8&apos;,
        },
      });
    } else {
      return new Response(&apos;Hello World! This is Cloudflare Workers.&apos;, {
        status: 200,
        headers: {
          &apos;Content-Type&apos;: &apos;text/plain; charset=utf-8&apos;,
        },
      });
    }
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// Azure Functions
export async function serve(context, req) {
  context.log(&apos;HTTP trigger function processed a request.&apos;);

  const name = req.query.name || (req.body &amp;amp;&amp;amp; req.body.name);

  if (name) {
    context.res = {
      status: 200,
      body: `Hello, ${name}! Welcome to Azure Functions.`
    };
  } else {
    context.res = {
      status: 200,
      body: &apos;Hello World! This is Azure Functions.&apos;
    };
  }
};
export default serve;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;네이티브 API를 쓰면 벌써 코드가 다르다.
CF Workers는 함수를 객체에 담아 &lt;code&gt;export default&lt;/code&gt;하고, 그 함수는 Response 객체를 리턴한다.
하지면 Azure Functions는 함수가 &lt;code&gt;context.res&lt;/code&gt;를 수정한다.&lt;/p&gt;
&lt;p&gt;그런데 그때, Hono가 등장한다...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Hono } from &apos;hono&apos;;

type Env = {
  Bindings: Record&amp;lt;string, unknown&amp;gt;;
};

const app = new Hono&amp;lt;Env&amp;gt;();

// POST 요청 처리 예제
app.post(&apos;/api/hello&apos;, async (c) =&amp;gt; {
  const body = await c.req.json&amp;lt;{ name?: string }&amp;gt;();
  const name = body.name || &apos;World&apos;;

  return c.json({
    message: `Hello, ${name}!`,
    method: &apos;POST&apos;,
    framework: &apos;Hono&apos;,
  });
});

export default app;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이거, 어디선가 본 패턴이지 않은가?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import express, { Request, Response } from &apos;express&apos;;

const app = express();

app.use(express.json());

app.post(&apos;/api/hello&apos;, (req: Request, res: Response) =&amp;gt; {
  const body = req.body as { name?: string };
  const name = body.name || &apos;World&apos;;

  res.json({
    message: `Hello, ${name}!`,
    method: &apos;POST&apos;,
    framework: &apos;Express&apos;,
  });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () =&amp;gt; {
  console.log(`Server is running on port ${PORT}`);
});

export default app;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Express.js와 매우 유사하다. 선언적이고, 자유분방하다.
게다가 타입스크립트를 염두하고 만들어졌기 때문에,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//...
const app=new Hono()
    .post(&quot;/post&quot;, ...)
    .get(&quot;/get&quot;, ...);
export default app;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이와 같은 방식으로 메서드 체인을 걸면, app의 타입 자체에 라우트 정보가 작성된다.
이는 RPC나 &lt;a href=&quot;https://hono.dev/docs/helpers/testing&quot;&gt;테스팅&lt;/a&gt; 시 매우 유용하다.&lt;/p&gt;
&lt;p&gt;개인 사용을 위해 &lt;a href=&quot;https://github.com/templecon/template-hono&quot;&gt;Hono용 템플릿&lt;/a&gt;을 작성해두었다.&lt;/p&gt;
</content:encoded></item><item><title>안녕 세계</title><link>https://blog.euroka.moe/posts/init/</link><guid isPermaLink="true">https://blog.euroka.moe/posts/init/</guid><description>첫글!!!</description><pubDate>Sat, 21 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;안녕! 👋 오늘부터 블로그를 시작합니다.&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;./images/054040e5ed529d79cc65e09f71458e6e8505b721e6e574d6fabaf5e251d44393.gif&quot;&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>작은 스크립트 파일은 Deno를 쓰는 것에 대하여</title><link>https://blog.euroka.moe/posts/why-deno/</link><guid isPermaLink="true">https://blog.euroka.moe/posts/why-deno/</guid><pubDate>Sat, 21 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Node.js에서의 타입스크립트&lt;/h2&gt;
&lt;p&gt;Node.js에서 타입스크립트를 쓰려면, 3가지 방법이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;타입스크립트용 REPL을 사용한다.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tsx&lt;/code&gt; 패키지를 설치해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tsx&lt;/code&gt; 명령어로 타입스크립트 코드를 실행시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;타입스크립트 프로젝트를 생성한다.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tsconfig.json&lt;/code&gt; 파일을 만들어야 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;esbuild&lt;/code&gt;나 &lt;code&gt;tsc&lt;/code&gt;로 타입스크립트를 자바스크립트로 컴파일해야 한다.&lt;/li&gt;
&lt;li&gt;그렇기에, package.json과 node_modules도 필요하다.&lt;/li&gt;
&lt;li&gt;컴파일된 자바스크립트 파일을 실행해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Node.js를 믿는다.
&lt;ul&gt;
&lt;li&gt;일단 타입스크립트 파일을 실행시킨다.&lt;/li&gt;
&lt;li&gt;Node.js의 &lt;code&gt;--strip-types&lt;/code&gt;를 믿고 실행한다.&lt;/li&gt;
&lt;li&gt;strip 가능한 코드가 아니라면서, 에러가 난다.
&lt;img src=&quot;image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;li&gt;이제 &lt;code&gt;--experimental-transform-types&lt;/code&gt;를 쓴다.&lt;/li&gt;
&lt;li&gt;매번 플래그 넘겨주려니까 귀찮다.&lt;/li&gt;
&lt;li&gt;결국 tsx를 설치한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;img src=&quot;./images/200.gif&quot; alt=&quot;GIF&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;gt; 서기관 셰럼, 이건 좀 너무한다고 생각함.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;그럼 타입스크립트 왜씀?&lt;/h2&gt;
&lt;p&gt;타입스크립트는 자바스크립트보다 빌드 타임에 많은 정보를 알게 된다.
이곳에서 오는 크나큰 장점이 있다.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;자동완성&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;JS는 object의 프로퍼티가 무엇인지 알 수 없다. 그래서 자동완성이 안된다.&lt;/p&gt;
&lt;p&gt;TS는 정보를 .ts 파일이나 .d.ts 파일에 모두 담아놓는다. number에는 toFixed가 있고, string에는 toUpperCase가 있다는 것을 알고 있다. toUpperCase는 파라미터를 아무것도 받지 않은 &lt;code&gt;()=&amp;gt;string&lt;/code&gt; 타입의 함수임을 lib.d.ts에 적어놓는다.&lt;/p&gt;
&lt;p&gt;JS는 이상한 기교가 있다. 빈 배열과 느낌표, 더하기, 이상한 기호 몇 개로 모든 코드를 만들 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[]+[] // &quot;&quot;
[]+{} // &quot;[object Object]&quot;
{}+[] // 0
{}+{} // NaN
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 무슨 쓰레기 언어란 말인가. 타입스크립트 안 쓸 이유가 없지 않나?&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;./images/giphy.webp&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;gt; 정체성의 유령도 한물 두물 세물 간 자바스크립트를 버리고 타입스크립트에 어울리는...&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;빨간약&lt;/h2&gt;
&lt;p&gt;하지만 타입스크립트는 딱 하나의 문제점이 있다. 결국 실행을 못 한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;./images/19af9216e4c133c46.png&quot; width=&quot;200&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;gt; 으에엥 타입스크립트 네르지 마세요&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;그러면 타입스크립트 코드를 자바스크립트 코드로 변환하고, 실행하고, 그걸 반복하면 되지 않을까?
가능하다. Node.js의 &lt;code&gt;--strip-types&lt;/code&gt;가 비슷하게 한다.
하지만 이는 &lt;code&gt;const enum&lt;/code&gt;과 같은 일부 기능을 지원하지 않는다. &lt;code&gt;--experimental-transform-types&lt;/code&gt; 플래그를 써야 한다. 하지만 이름에서 알 수 있듯이 이는 실험적이다. 언제 터져도 이상하지 않다는 것이다.&lt;/p&gt;
&lt;h2&gt;그렇게 엄청 간단하지는 않은 스크립트&lt;/h2&gt;
&lt;p&gt;예시로, &lt;code&gt;hello.ts&lt;/code&gt;라는 파일을 만들어보자.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.log(&quot;Hello, world!&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이는 충분히 간단하다. tsx로도 실행할 수 있다.
하지만, 조금 복잡한 스크립트는 어떨까?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// openai.ts
const prompt=&apos;&quot;크아앙&quot;\n투명드래곤이 울부짖었다.\n\n이 다음 소설의 내용을 이어서 써줘.&apos;;

const response = await fetch(&quot;https://api.openai.com/v1/chat/completions&quot;, {
  method: &quot;POST&quot;,
  headers: {
    &quot;Content-Type&quot;: &quot;application/json&quot;,
    &quot;Authorization&quot;: `Bearer ${process.env.OPENAI_API_KEY}`,
  },
  body: JSON.stringify({
    model: &quot;gpt-5-mini&quot;,
    messages: [{ role: &quot;user&quot;, content: prompt }],
  }),
});
const data = await response.json();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;img src=&quot;./images/3_EC_BB_B7.gif&quot; width=&quot;400&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;gt; 한 동인지 작가의 흑역사를 드러내고 있다.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;조금 복잡해진다. 이제 스트리밍도 추가해볼까?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// openai.ts
const prompt=&apos;&quot;크아앙&quot;\n투명드래곤이 울부짖었다.\n\n이 다음 소설의 내용을 이어서 써줘.&apos;;
const response = await fetch(&quot;https://api.openai.com/v1/chat/completions&quot;, {
  method: &quot;POST&quot;,
  headers: {
    &quot;Content-Type&quot;: &quot;application/json&quot;,
    &quot;Authorization&quot;: `Bearer ${process.env.OPENAI_API_KEY}`,
  },
  body: JSON.stringify({
    model: &quot;gpt-5-mini&quot;,
    messages: [{ role: &quot;user&quot;, content: prompt }],
    stream: true,
  }),
});
const reader = response.body.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const chunk = new TextDecoder().decode(value);
  console.log(chunk);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;img src=&quot;./images/i-SrNCqaDPPKDhBa2nAg_8y4z6guRNb67uAE9D09hF9ZAmN5ayhkvd1m7cl_99HOpZpi7GgOa3h2mEkMWrvRaq_VvWJG-7oAmbHs30mAfGDKtIBPhXTpTI5tGzI6l2teZ_OGL95hKk_YGzVtTiKZ0w.webp&quot; width=&quot;400&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;물론 여기서 저 청크 하나하나도 JSON이니까, JSON.parse를 해야 한다. 메세지 델타들에 대해 조금씩 출력하고, 메세지 끝에 도달했는지 확인하는 로직도 필요하다.&lt;/p&gt;
&lt;h2&gt;Deno를 쓰세요&lt;/h2&gt;
&lt;p&gt;tsx는 Node.js다. 라이브러리를 설치하려면, node_modules에 넣어야 한다. 방법은? &lt;code&gt;pnpm add&lt;/code&gt;와 package.json 관리, 그리고 기타 귀찮은 파일 관리를 해야 한다.&lt;/p&gt;
&lt;p&gt;Deno는 Deno다. 라이브러리를 사용하려면,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import OpenAI from &apos;jsr:@openai/openai&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 얼마나 간결한가. Deno로 위 예시를 다시 짜 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import OpenAI from &quot;jsr:@openai/openai&quot;;

const client = new OpenAI();

const stream = await client.responses.create({
  model: &quot;gpt-5-mini&quot;,
  input: &apos;&quot;크아앙&quot;\n투명드래곤이 울부짖었다.\n\n이 다음 소설의 내용을 이어서 써줘.&apos;,
  stream: true,
});

for await (const event of stream) {
  console.log(event);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;타입스크립트? Deno는 된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;./images/198b318a37d4da809.jpg&quot; width=&quot;400&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;gt; 긴긴-팔다리의 출몰에 정신을 잃은 셰럼&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Markdown Extended Features</title><link>https://blog.euroka.moe/posts/markdown-extended/</link><guid isPermaLink="true">https://blog.euroka.moe/posts/markdown-extended/</guid><description>Read more about Markdown features in Fuwari</description><pubDate>Wed, 01 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;GitHub Repository Cards&lt;/h2&gt;
&lt;p&gt;You can add dynamic cards that link to GitHub repositories, on page load, the repository information is pulled from the GitHub API.&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;Fabrizz/MMM-OnSpotify&quot;}&lt;/p&gt;
&lt;p&gt;Create a GitHub repository card with the code &lt;code&gt;::github{repo=&quot;&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;&quot;}&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;::github{repo=&quot;saicaca/fuwari&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Admonitions&lt;/h2&gt;
&lt;p&gt;Following types of admonitions are supported: &lt;code&gt;note&lt;/code&gt; &lt;code&gt;tip&lt;/code&gt; &lt;code&gt;important&lt;/code&gt; &lt;code&gt;warning&lt;/code&gt; &lt;code&gt;caution&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;:::note
Highlights information that users should take into account, even when skimming.
:::&lt;/p&gt;
&lt;p&gt;:::tip
Optional information to help a user be more successful.
:::&lt;/p&gt;
&lt;p&gt;:::important
Crucial information necessary for users to succeed.
:::&lt;/p&gt;
&lt;p&gt;:::warning
Critical content demanding immediate user attention due to potential risks.
:::&lt;/p&gt;
&lt;p&gt;:::caution
Negative potential consequences of an action.
:::&lt;/p&gt;
&lt;h3&gt;Basic Syntax&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;:::note
Highlights information that users should take into account, even when skimming.
:::

:::tip
Optional information to help a user be more successful.
:::
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Custom Titles&lt;/h3&gt;
&lt;p&gt;The title of the admonition can be customized.&lt;/p&gt;
&lt;p&gt;:::note[MY CUSTOM TITLE]
This is a note with a custom title.
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:::note[MY CUSTOM TITLE]
This is a note with a custom title.
:::
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;GitHub Syntax&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;a href=&quot;https://github.com/orgs/community/discussions/16925&quot;&gt;The GitHub syntax&lt;/a&gt; is also supported.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; [!NOTE]
&amp;gt; The GitHub syntax is also supported.

&amp;gt; [!TIP]
&amp;gt; The GitHub syntax is also supported.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Spoiler&lt;/h3&gt;
&lt;p&gt;You can add spoilers to your text. The text also supports &lt;strong&gt;Markdown&lt;/strong&gt; syntax.&lt;/p&gt;
&lt;p&gt;The content :spoiler[is hidden &lt;strong&gt;ayyy&lt;/strong&gt;]!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;The content :spoiler[is hidden **ayyy**]!

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Expressive Code Example</title><link>https://blog.euroka.moe/posts/expressive-code/</link><guid isPermaLink="true">https://blog.euroka.moe/posts/expressive-code/</guid><description>How code blocks look in Markdown using Expressive Code.</description><pubDate>Wed, 10 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here, we&apos;ll explore how code blocks look using &lt;a href=&quot;https://expressive-code.com/&quot;&gt;Expressive Code&lt;/a&gt;. The provided examples are based on the official documentation, which you can refer to for further details.&lt;/p&gt;
&lt;h2&gt;Expressive Code&lt;/h2&gt;
&lt;h3&gt;Syntax Highlighting&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/syntax-highlighting/&quot;&gt;Syntax Highlighting&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Regular syntax highlighting&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;This code is syntax highlighted!&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Rendering ANSI escape sequences&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;ANSI colors:
- Regular: [31mRed[0m [32mGreen[0m [33mYellow[0m [34mBlue[0m [35mMagenta[0m [36mCyan[0m
- Bold:    [1;31mRed[0m [1;32mGreen[0m [1;33mYellow[0m [1;34mBlue[0m [1;35mMagenta[0m [1;36mCyan[0m
- Dimmed:  [2;31mRed[0m [2;32mGreen[0m [2;33mYellow[0m [2;34mBlue[0m [2;35mMagenta[0m [2;36mCyan[0m

256 colors (showing colors 160-177):
[38;5;160m160 [38;5;161m161 [38;5;162m162 [38;5;163m163 [38;5;164m164 [38;5;165m165[0m
[38;5;166m166 [38;5;167m167 [38;5;168m168 [38;5;169m169 [38;5;170m170 [38;5;171m171[0m
[38;5;172m172 [38;5;173m173 [38;5;174m174 [38;5;175m175 [38;5;176m176 [38;5;177m177[0m

Full RGB colors:
[38;2;34;139;34mForestGreen - RGB(34, 139, 34)[0m

Text formatting: [1mBold[0m [2mDimmed[0m [3mItalic[0m [4mUnderline[0m
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Editor &amp;amp; Terminal Frames&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/frames/&quot;&gt;Editor &amp;amp; Terminal Frames&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Code editor frames&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;Title attribute example&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- src/content/index.html --&amp;gt;
&amp;lt;div&amp;gt;File name comment example&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Terminal frames&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;This terminal frame has no title&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;Write-Output &quot;This one has a title!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Overriding frame types&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;Look ma, no frame!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;# Without overriding, this would be a terminal frame
function Watch-Tail { Get-Content -Tail 20 -Wait $args }
New-Alias tail Watch-Tail
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Text &amp;amp; Line Markers&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/text-markers/&quot;&gt;Text &amp;amp; Line Markers&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Marking full lines &amp;amp; line ranges&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// Line 1 - targeted by line number
// Line 2
// Line 3
// Line 4 - targeted by line number
// Line 5
// Line 6
// Line 7 - targeted by range &quot;7-8&quot;
// Line 8 - targeted by range &quot;7-8&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Selecting line marker types (mark, ins, del)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function demo() {
  console.log(&apos;this line is marked as deleted&apos;)
  // This line and the next one are marked as inserted
  console.log(&apos;this is the second inserted line&apos;)

  return &apos;this line uses the neutral default marker type&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Adding labels to line markers&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// labeled-line-markers.jsx
&amp;lt;button
  role=&quot;button&quot;
  {...props}
  value={value}
  className={buttonClassName}
  disabled={disabled}
  active={active}
&amp;gt;
  {children &amp;amp;&amp;amp;
    !active &amp;amp;&amp;amp;
    (typeof children === &apos;string&apos; ? &amp;lt;span&amp;gt;{children}&amp;lt;/span&amp;gt; : children)}
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Adding long labels on their own lines&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// labeled-line-markers.jsx
&amp;lt;button
  role=&quot;button&quot;
  {...props}

  value={value}
  className={buttonClassName}

  disabled={disabled}
  active={active}
&amp;gt;

  {children &amp;amp;&amp;amp;
    !active &amp;amp;&amp;amp;
    (typeof children === &apos;string&apos; ? &amp;lt;span&amp;gt;{children}&amp;lt;/span&amp;gt; : children)}
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Using diff-like syntax&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;+this line will be marked as inserted
-this line will be marked as deleted
this is a regular line
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+this is an actual diff file
-all contents will remain unmodified
 no whitespace will be removed either
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Combining syntax highlighting with diff-like syntax&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;  function thisIsJavaScript() {
    // This entire block gets highlighted as JavaScript,
    // and we can still add diff markers to it!
-   console.log(&apos;Old code to be removed&apos;)
+   console.log(&apos;New and shiny code!&apos;)
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Marking individual text inside lines&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function demo() {
  // Mark any given text inside lines
  return &apos;Multiple matches of the given text are supported&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Regular expressions&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;The words yes and yep will be marked.&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Escaping forward slashes&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;Test&quot; &amp;gt; /home/test.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Selecting inline marker types (mark, ins, del)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function demo() {
  console.log(&apos;These are inserted and deleted marker types&apos;);
  // The return statement uses the default marker type
  return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Word Wrap&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/word-wrap/&quot;&gt;Word Wrap&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Configuring word wrap per block&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// Example with wrap
function getLongString() {
  return &apos;This is a very long string that will most probably not fit into the available space unless the container is extremely wide&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;// Example with wrap=false
function getLongString() {
  return &apos;This is a very long string that will most probably not fit into the available space unless the container is extremely wide&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Configuring indentation of wrapped lines&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// Example with preserveIndent (enabled by default)
function getLongString() {
  return &apos;This is a very long string that will most probably not fit into the available space unless the container is extremely wide&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;// Example with preserveIndent=false
function getLongString() {
  return &apos;This is a very long string that will most probably not fit into the available space unless the container is extremely wide&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Collapsible Sections&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/plugins/collapsible-sections/&quot;&gt;Collapsible Sections&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// All this boilerplate setup code will be collapsed
import { someBoilerplateEngine } from &apos;@example/some-boilerplate&apos;
import { evenMoreBoilerplate } from &apos;@example/even-more-boilerplate&apos;

const engine = someBoilerplateEngine(evenMoreBoilerplate())

// This part of the code will be visible by default
engine.doSomething(1, 2, 3, calcFn)

function calcFn() {
  // You can have multiple collapsed sections
  const a = 1
  const b = 2
  const c = a + b

  // This will remain visible
  console.log(`Calculation result: ${a} + ${b} = ${c}`)
  return c
}

// All this code until the end of the block will be collapsed again
engine.closeConnection()
engine.freeMemory()
engine.shutdown({ reason: &apos;End of example boilerplate code&apos; })
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Line Numbers&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/plugins/line-numbers/&quot;&gt;Line Numbers&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Displaying line numbers per block&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// This code block will show line numbers
console.log(&apos;Greetings from line 2!&apos;)
console.log(&apos;I am on line 3&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;// Line numbers are disabled for this block
console.log(&apos;Hello?&apos;)
console.log(&apos;Sorry, do you know what line I am on?&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Changing the starting line number&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;Greetings from line 5!&apos;)
console.log(&apos;I am on line 6&apos;)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Simple Guides for Fuwari</title><link>https://blog.euroka.moe/posts/guide/</link><guid isPermaLink="true">https://blog.euroka.moe/posts/guide/</guid><description>How to use this blog template.</description><pubDate>Mon, 01 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Cover image source: &lt;a href=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=2048/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This blog template is built with &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;. For the things that are not mentioned in this guide, you may find the answers in the &lt;a href=&quot;https://docs.astro.build/&quot;&gt;Astro Docs&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Front-matter of Posts&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;---
title: My First Blog Post
published: 2023-09-09
description: This is the first post of my new Astro blog.
image: ./cover.jpg
tags: [Foo, Bar]
category: Front-end
draft: false
---
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The title of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;published&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The date the post was published.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A short description of the post. Displayed on index page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The cover image path of the post.&amp;lt;br/&amp;gt;1. Start with &lt;code&gt;http://&lt;/code&gt; or &lt;code&gt;https://&lt;/code&gt;: Use web image&amp;lt;br/&amp;gt;2. Start with &lt;code&gt;/&lt;/code&gt;: For image in &lt;code&gt;public&lt;/code&gt; dir&amp;lt;br/&amp;gt;3. With none of the prefixes: Relative to the markdown file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tags&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The tags of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;category&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The category of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;draft&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;If this post is still a draft, which won&apos;t be displayed.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Where to Place the Post Files&lt;/h2&gt;
&lt;p&gt;Your post files should be placed in &lt;code&gt;src/content/posts/&lt;/code&gt; directory. You can also create sub-directories to better organize your posts and assets.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src/content/posts/
├── post-1.md
└── post-2/
    ├── cover.png
    └── index.md
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Markdown Example</title><link>https://blog.euroka.moe/posts/markdown/</link><guid isPermaLink="true">https://blog.euroka.moe/posts/markdown/</guid><description>A simple example of a Markdown blog post.</description><pubDate>Sun, 01 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;An h1 header&lt;/h1&gt;
&lt;p&gt;Paragraphs are separated by a blank line.&lt;/p&gt;
&lt;p&gt;2nd paragraph. &lt;em&gt;Italic&lt;/em&gt;, &lt;strong&gt;bold&lt;/strong&gt;, and &lt;code&gt;monospace&lt;/code&gt;. Itemized lists
look like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;this one&lt;/li&gt;
&lt;li&gt;that one&lt;/li&gt;
&lt;li&gt;the other one&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that --- not considering the asterisk --- the actual text
content starts at 4-columns in.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Block quotes are
written like so.&lt;/p&gt;
&lt;p&gt;They can span multiple paragraphs,
if you like.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., &quot;it&apos;s all
in chapters 12--14&quot;). Three dots ... will be converted to an ellipsis.
Unicode is supported. ☺&lt;/p&gt;
&lt;h2&gt;An h2 header&lt;/h2&gt;
&lt;p&gt;Here&apos;s a numbered list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;first item&lt;/li&gt;
&lt;li&gt;second item&lt;/li&gt;
&lt;li&gt;third item&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note again how the actual text starts at 4 columns in (4 characters
from the left side). Here&apos;s a code sample:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Let me re-iterate ...
for i in 1 .. 10 { do-something(i) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you probably guessed, indented 4 spaces. By the way, instead of
indenting the block, you can use delimited blocks, if you like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;define foobar() {
    print &quot;Welcome to flavor country!&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(which makes copying &amp;amp; pasting easier). You can optionally mark the
delimited block for Pandoc to syntax highlight it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import time
# Quick, count to ten!
for i in range(10):
    # (but not *too* quick)
    time.sleep(0.5)
    print i
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;An h3 header&lt;/h3&gt;
&lt;p&gt;Now a nested list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First, get these ingredients:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;carrots&lt;/li&gt;
&lt;li&gt;celery&lt;/li&gt;
&lt;li&gt;lentils&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Boil some water.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dump everything in the pot and follow
this algorithm:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; find wooden spoon
 uncover pot
 stir
 cover pot
 balance wooden spoon precariously on pot handle
 wait 10 minutes
 goto first step (or shut off burner when done)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not bump wooden spoon or it will fall.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notice again how text always lines up on 4-space indents (including
that last line which continues item 3 above).&lt;/p&gt;
&lt;p&gt;Here&apos;s a link to &lt;a href=&quot;http://foo.bar&quot;&gt;a website&lt;/a&gt;, to a &lt;a href=&quot;local-doc.html&quot;&gt;local
doc&lt;/a&gt;, and to a &lt;a href=&quot;#an-h2-header&quot;&gt;section heading in the current
doc&lt;/a&gt;. Here&apos;s a footnote [^1].&lt;/p&gt;
&lt;p&gt;[^1]: Footnote text goes here.&lt;/p&gt;
&lt;p&gt;Tables can look like this:&lt;/p&gt;
&lt;p&gt;size material color&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;9 leather brown
10 hemp canvas natural
11 glass transparent&lt;/p&gt;
&lt;p&gt;Table: Shoes, their sizes, and what they&apos;re made of&lt;/p&gt;
&lt;p&gt;(The above is the caption for the table.) Pandoc also supports
multi-line tables:&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;keyword text&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;red Sunsets, apples, and
other red or reddish
things.&lt;/p&gt;
&lt;p&gt;green Leaves, grass, frogs
and other things it&apos;s
not easy being.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;A horizontal rule follows.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Here&apos;s a definition list:&lt;/p&gt;
&lt;p&gt;apples
: Good for making applesauce.
oranges
: Citrus!
tomatoes
: There&apos;s no &quot;e&quot; in tomatoe.&lt;/p&gt;
&lt;p&gt;Again, text is indented 4 spaces. (Put a blank line between each
term/definition pair to spread things out more.)&lt;/p&gt;
&lt;p&gt;Here&apos;s a &quot;line block&quot;:&lt;/p&gt;
&lt;p&gt;| Line one
| Line too
| Line tree&lt;/p&gt;
&lt;p&gt;and images can be specified like so:&lt;/p&gt;
&lt;p&gt;Inline math equations go in like so: $\omega = d\phi / dt$. Display
math should get its own line and be put in in double-dollarsigns:&lt;/p&gt;
&lt;p&gt;$$I = \int \rho R^{2} dV$$&lt;/p&gt;
&lt;p&gt;$$
\begin{equation*}
\pi
=3.1415926535
;8979323846;2643383279;5028841971;6939937510;5820974944
;5923078164;0628620899;8628034825;3421170679;\ldots
\end{equation*}
$$&lt;/p&gt;
&lt;p&gt;And note that you can backslash-escape any punctuation characters
which you wish to be displayed literally, ex.: `foo`, *bar*, etc.&lt;/p&gt;
</content:encoded></item><item><title>Include Video in the Posts</title><link>https://blog.euroka.moe/posts/video/</link><guid isPermaLink="true">https://blog.euroka.moe/posts/video/</guid><description>This post demonstrates how to include embedded video in a blog post.</description><pubDate>Tue, 01 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Just copy the embed code from YouTube or other platforms, and paste it in the markdown file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
title: Include Video in the Post
published: 2023-10-19
// ...
---

&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;YouTube&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;h2&gt;Bilibili&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;//player.bilibili.com/player.html?bvid=BV1fK4y1s7Qf&amp;amp;p=1&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot;&amp;gt; &amp;lt;/iframe&amp;gt;&lt;/p&gt;
</content:encoded></item></channel></rss>