chore: add docker deployment support

This commit is contained in:
jerryW123
2026-06-23 14:12:30 +08:00
parent 5b4c973812
commit f51f64f3bd
10 changed files with 449 additions and 0 deletions
@@ -0,0 +1,258 @@
from __future__ import annotations
import html
import zipfile
from pathlib import Path
OUT = Path("RPGAgent组会展示文档.docx")
def esc(text: str) -> str:
return html.escape(text, quote=False)
def paragraph(text: str = "", style: str | None = None, bold: bool = False) -> str:
p_style = f'<w:pPr><w:pStyle w:val="{style}"/></w:pPr>' if style else ""
b = "<w:b/>" if bold else ""
lines = text.split("\n") or [""]
runs = []
for idx, line in enumerate(lines):
if idx:
runs.append("<w:r><w:br/></w:r>")
preserve = ' xml:space="preserve"' if line.startswith(" ") or line.endswith(" ") else ""
runs.append(f"<w:r><w:rPr>{b}</w:rPr><w:t{preserve}>{esc(line)}</w:t></w:r>")
return f"<w:p>{p_style}{''.join(runs)}</w:p>"
def bullet(text: str) -> str:
return paragraph("" + text)
def heading(text: str, level: int) -> str:
return paragraph(text, f"Heading{level}")
def figure_note(text: str) -> str:
return paragraph("【建议插图】" + text, "FigureNote", bold=True)
def page_break() -> str:
return '<w:p><w:r><w:br w:type="page"/></w:r></w:p>'
sections: list[str] = []
sections.append(paragraph("RPGAgent 论文组会展示文档", "Title"))
sections.append(paragraph("论文:RPGAgent: Driving Coherent Story-to-Play Generation with an LLM-Based Multi-Agent System", "Subtitle"))
sections.append(paragraph("用途:组会汇报讲解稿与展示材料整理"))
sections.append(paragraph("建议时长:15-20 分钟"))
sections.append(paragraph("核心观点:这篇论文真正关注的不是让大模型生成更多游戏内容,而是让故事、场景、玩法和代码这些异质内容在同一条创作流水线中保持一致,并且能被设计者继续编辑。", bold=True))
sections.append(page_break())
sections.append(heading("一、汇报开场:这篇论文在研究什么", 1))
sections.append(paragraph("大家好,今天我汇报的是 CHI 2026 的论文《RPGAgent: Driving Coherent Story-to-Play Generation with an LLM-Based Multi-Agent System》。这篇论文属于 AI 辅助创作、游戏设计工具和多智能体系统的交叉方向。"))
sections.append(paragraph("如果用一句话概括,它提出了一个叫 RPGAgent 的系统:用户输入一段简短的 RPG 故事大纲,系统通过多个 LLM agent 和程序化内容生成技术,把这个大纲逐步转成一个可在 Unity 中运行和编辑的 2D RPG 游戏原型。"))
sections.append(paragraph("这篇论文的关键词是 coherence,也就是一致性或连贯性。这里的一致性不是说故事写得通顺,而是指故事、地图、角色、道具、交互机制和脚本之间能够互相对应。比如故事里提到主角去森林寻找古代护符,那么生成结果中就应该有森林区域、有护符、有寻找或收集机制,并且这个机制能够推动剧情继续发展。"))
sections.append(figure_note("开场处适合放论文 Figure 1。Figure 1 是系统总览图,能直观说明用户输入故事和设计备注后,RPGAgent 如何通过 Narrative Designer、Scene Designer、Mechanics Designer、Code Generator 生成文本资产、视觉元素和脚本。"))
sections.append(paragraph("建议讲法:先不要急着讲技术细节,可以先强调游戏原型生成的难点在于“多种内容之间要对得上”。这样后面讲多智能体分工时,听众会更容易理解作者为什么这样设计。"))
sections.append(heading("二、研究背景:为什么单个 LLM 不够", 1))
sections.append(paragraph("近年来,大语言模型已经可以用于很多创作任务,例如写剧情、生成对白、辅助编程、生成关卡设定等。这给游戏设计带来了一个很自然的想法:能不能让 AI 帮助新手快速做出一个可玩的游戏原型?"))
sections.append(paragraph("但是游戏设计并不是单一文本生成任务。一个游戏原型至少包含四类互相关联的内容:叙事内容、视觉空间、玩法规则和技术实现。这四类内容之间存在强依赖关系。故事决定场景需要出现什么;场景决定玩家能在哪里行动;玩法机制决定玩家如何与角色和道具交互;代码则决定这些机制能不能真正运行。"))
sections.append(paragraph("传统的 GPT 辅助流程通常是碎片化的。用户可能先让 GPT 写故事,再让它写对话,然后手动复制到 Unity,再让它生成 C# 脚本,最后自己解决资源、对象引用和编译错误。这个过程中,GPT 生成的内容看起来都不错,但放在一起往往会出问题。"))
sections.append(paragraph("论文把这种问题称为 coherence gap。单个 LLM 在一个上下文中同时处理故事、地图、机制和代码时,容易出现上下文漂移。比如它可能在故事中生成一个道具,但场景里没有这个道具;或者它写出一个交互逻辑,但 Unity 中没有对应对象;再或者它为了叙事效果生成了很复杂的玩法,但现有系统根本不支持。"))
sections.append(paragraph("因此,这篇论文的出发点是:游戏原型生成不能只依赖更强的文本生成能力,还需要一个能够协调不同创作模块的系统架构。"))
sections.append(heading("三、研究目标与问题定义", 1))
sections.append(paragraph("作者的目标是支持 less-experienced 或 novice game creators,也就是经验较少的游戏创作者。他们不一定熟悉 Unity,也不一定能自己完成完整代码实现,但他们可能有故事想法,希望快速看到一个可以交互的原型。"))
sections.append(paragraph("论文围绕三个研究问题展开:"))
sections.append(bullet("RQ1RPGAgent 的 story-to-play 流水线如何影响用户感知到的可用性和原型制作效率?"))
sections.append(bullet("RQ2:用户在使用 RPGAgent 时如何体验创作过程,尤其是想法生成、创意探索和创作控制?"))
sections.append(bullet("RQ3:用户如何感知、交互和控制这个系统?在自动化和设计者主体性之间会出现什么张力?"))
sections.append(paragraph("这里可以看到,作者并不只是评价系统生成的游戏质量,而是把它放在 creativity support tools 的语境中,关注它是否能帮助设计者探索想法、减少工具切换、保留创作控制。"))
sections.append(heading("四、理论基础:Elemental Tetrad 如何变成系统架构", 1))
sections.append(paragraph("这篇论文使用 Jesse Schell 的 Elemental Tetrad 作为理论基础。这个框架认为,一个游戏体验由四个基本元素共同构成:Story、Aesthetics、Mechanics 和 Technology。"))
sections.append(bullet("Story 指故事、角色、对白和世界观。"))
sections.append(bullet("Aesthetics 指视觉风格、场景氛围和玩家感知到的环境。"))
sections.append(bullet("Mechanics 指规则、玩法系统和玩家行动方式。"))
sections.append(bullet("Technology 指支撑游戏运行的引擎、平台和代码实现。"))
sections.append(paragraph("RPGAgent 的关键设计在于,它不是简单借用这个理论做背景介绍,而是把 Elemental Tetrad 直接转化成了多智能体架构。Narrative Designer Agent 对应 StoryScene Designer Agent 对应 AestheticsMechanics Designer Agent 对应 MechanicsCode Generator Agent 对应 Technology。"))
sections.append(figure_note("这里建议放论文 Figure 2。Figure 2 展示了 RPGAgent 的多智能体框架如何映射到 Elemental Tetrad,并且区分了更可见的设计 agent 和更后台化的技术 agent。"))
sections.append(paragraph("这个设计有两个好处。第一,它让系统分工更清晰,每个 agent 只处理一个相对明确的任务。第二,它让不同游戏元素之间的关系可以通过结构化接口来传递,而不是完全依赖自然语言上下文。"))
sections.append(heading("五、系统总体流程:从 Story 到 Play", 1))
sections.append(paragraph("RPGAgent 的整体流程可以理解为三阶段 pipeline:叙事生成、场景生成、机制实现。每一阶段都会接收上一阶段的结构化结果,并输出下一阶段可以使用的数据。"))
sections.append(figure_note("这里建议放论文 Figure 3。Figure 3 是最重要的系统流程图,展示了 Narrative Generation、Scene Generation 和 Game Mechanism Generation 三个阶段,以及故事大纲如何逐步转为 Unity 资产和脚本。"))
sections.append(heading("5.1 第一阶段:Narrative Generation", 2))
sections.append(paragraph("第一阶段由 Narrative Designer Agent 负责。用户输入的通常是一段比较短的故事大纲,也可以附加一些设计备注,例如希望是幻想风格、山地场景、对话不要太长等。"))
sections.append(paragraph("Narrative Agent 会先扩展出完整叙事,包括世界设定、角色档案和主线剧情。随后,它会把主线剧情拆成一系列 narrative steps。每个 step 都是从玩家视角出发的一个小目标,例如“离开村庄”“进入森林”“寻找失踪角色”“获得关键道具”等。"))
sections.append(paragraph("这个阶段最关键的不是生成漂亮的故事,而是生成结构化字段。论文给出的示例包括 Step Order、Title、Location、Objective、Key Characters、Main Dialogues 和 Key Items。也就是说,故事被转化成了后续场景和机制可以读取的数据结构。"))
sections.append(paragraph("可以这样讲:Narrative Agent 相当于把模糊的创意整理成一个可执行的设计蓝图。它既保留叙事意义,也为下游模块提供地点、角色、物品和目标这些硬信息。"))
sections.append(heading("5.2 第二阶段:Scene Generation", 2))
sections.append(paragraph("第二阶段由 Scene Designer Agent 负责,它把每个 narrative step 转换为空间场景。这里作者没有完全依赖 LLM 直接生成地图,而是结合了程序化内容生成,也就是 PCG。"))
sections.append(paragraph("场景生成分为三个步骤。第一步是基础地形生成。系统用 Perlin noise 生成水域、平原、山地、悬崖等地形层,并由 agent 根据叙事语义调节参数。例如 remote archipelago 会对应更高的 Island Factorvast plain 会降低山地阈值。"))
sections.append(paragraph("第二步是区域划分和场景元素放置。系统使用 Voronoi 图把地图划分成多个区域,并通过 Lloyd relaxation、空间约束和边界扰动提升自然度和可导航性。之后,系统根据 location type 和配置模板放置树木、建筑、道路、障碍等元素。"))
sections.append(paragraph("第三步是语义资产分配。系统会根据 narrative step 中的角色、道具和目标,把关键 NPC、任务物品或可交互对象放到有意义的位置。为了完成这个匹配,系统维护了 AssetIndex,记录可用资源的名称、类型、别名和描述。"))
sections.append(figure_note("这里建议放论文 Figure 4 或 Figure 6。Figure 4 适合讲场景生成流程,Figure 6 适合展示不同语义和 PCG 参数如何生成草地、沙漠、雪地等不同场景效果。"))
sections.append(heading("5.3 第三阶段:Mechanic Implementation", 2))
sections.append(paragraph("第三阶段由 Mechanics Designer Agent 和 Code Generator Agent 共同完成。Mechanics Agent 会读取叙事步骤和场景结构,判断每个角色、物体和区域应该承担什么交互功能。"))
sections.append(paragraph("例如,一个 NPC 可能负责提供任务和对白;一个敌人可能触发战斗;一个古代护符可能是可收集物品,并在后续用于打开隐藏神庙。Mechanics Agent 会把这些内容写成 JSON 形式的交互规格,其中包括 Function、Interaction Mode、Outcome 和 System Relationships。"))
sections.append(paragraph("之后,Code Generator Agent 会把这些机制规格转成 Unity C# 脚本。这里非常重要的一点是,它不是自由生成任意代码,而是从预设模板库中选择模板,然后让 LLM 填入占位符。模板包括玩家、NPC、敌人和物品等基础类型。"))
sections.append(paragraph("这种方式看上去限制了创造性,但它显著提升了可靠性。作者在实验观察中提到,如果允许 LLM 自由修改代码,几乎总是会因为缺失引用或结构不一致导致编译错误;而模板填空可以保证基本语法和结构正确。"))
sections.append(figure_note("这里建议放论文 Figure 7。Figure 7 展示了 Mechanics Designer 如何把叙事和场景转成 mechanic JSON,再由 Code Generator 映射到模板、填实体引用、添加逻辑块并附加到 Unity prefab。"))
sections.append(heading("六、系统实现:Unity、Python 与结构化校验", 1))
sections.append(paragraph("从实现层面看,RPGAgent 采用 Unity 6 前端加 Python 后端的混合架构。Unity 负责可视化编辑、用户输入、场景展示和游戏运行;Python 后端使用 Flask 和 Autogen 管理多个 agent,并调用 GPT-4o。"))
sections.append(paragraph("前后端通过 JSON 异步通信。用户在 Unity 中输入故事和参数后,Unity 会把数据序列化为 JSON 发送到 Python 后端。后端创建对应 agent,调用 LLM,拿到结构化结果后再返回 Unity 进行可视化和实例化。"))
sections.append(paragraph("作者还对 Narrative Agent 和 Scene Agent 做了小规模 SFT,训练数据量是 80 条人工构造样本。这里的 SFT 主要是为了让输出更符合复杂 schema 和 Unity 逻辑要求,而不是为了让模型掌握新知识。"))
sections.append(paragraph("系统还设置了多层校验机制。例如 JSON Schema 用于检查层级结构,语义检查用于确认参数范围、资源引用和对象依赖是否合理。代码生成后还会检查实体名、item ID、参数类型和依赖关系。如果发现错误,系统会把结构化错误信息反馈给 Code Generator Agent,让它重新生成。"))
sections.append(figure_note("这里建议放论文 Figure 8 和 Figure 9。Figure 8 用于讲 Unity-Python-LLM 通信架构,Figure 9 用于展示 Unity 编辑器中的 Narrative、Scene、Mechanics 三个标签页。"))
sections.append(heading("七、用户研究设计", 1))
sections.append(paragraph("为了评估系统,作者进行了一个包含 18 名参与者的用户研究。参与者年龄在 20 到 29 岁之间,包含艺术、建筑、设计、计算机等背景。多数人熟悉 LLM,但对 Unity 和游戏开发工具的熟悉程度有限,这与论文面向 novice game creators 的定位一致。"))
sections.append(paragraph("实验采用 within-subject crossover 设计,也就是每个参与者都会体验 RPGAgent 和 baseline 两种条件。为了避免顺序影响,9 人先用 RPGAgent9 人先用 baseline。"))
sections.append(paragraph("baseline 是 Unity Tilemap 手工制作流程,允许参与者使用 GPT-4o 辅助写故事或代码,但所有场景搭建、内容整合和实现都必须手动完成。这个 baseline 模拟的是现实中常见的“GPT 辅助但没有深度集成”的创作流程。"))
sections.append(paragraph("实验任务是制作一个虚构森林村庄中的短互动场景。参与者可以自由设定故事大纲,但都使用相同的素材库和 Unity Tilemap。每个工具先培训 5 分钟,正式任务每个条件 20 分钟。"))
sections.append(paragraph("数据收集包括问卷、访谈、think-aloud、屏幕录制、现场观察和系统日志。量化指标主要是 UEQ 和 CSI。UEQ 用于衡量用户体验,包括吸引力、清晰度、效率、可靠性、刺激性和新颖性;CSI 用于衡量创造力支持,包括探索、表达、沉浸、愉悦和投入回报。"))
sections.append(heading("八、量化结果:效率、探索和愉悦明显提升", 1))
sections.append(paragraph("实验结果显示,RPGAgent 在整体用户体验和整体创造力支持上都显著优于 baseline。"))
sections.append(paragraph("UEQ 方面,RPGAgent 的 overall score 是 5.047baseline 是 3.836p = 0.00004。具体维度上,Attractiveness、Perspicuity、Efficiency、Stimulation 和 Novelty 都显著更高。尤其是 Efficiency 从 baseline 的 3.278 提升到 5.236,说明 integrated workflow 对原型效率帮助很大。"))
sections.append(paragraph("但是 Dependability 没有显著提升。RPGAgent 的 Dependability 是 4.292baseline 是 4.514p = 0.125。这说明系统虽然更快、更有启发性,但用户并不一定觉得它更可靠。作者认为这可能和程序化生成的随机性、细粒度控制不足和资源匹配问题有关。"))
sections.append(paragraph("CSI 方面,RPGAgent 的 overall score 是 5.233baseline 是 4.406p = 0.00019。其中 Exploration 和 Enjoyment 显著提升,说明系统更能激发用户探索,也让创作过程更愉快。"))
sections.append(paragraph("不过 Expressiveness、Immersion 和 Effort/Reward 没有显著差异。这一点很值得注意:RPGAgent 更像是降低起步门槛、加速探索和提供灵感,但它不一定让用户拥有更强的表达能力。"))
sections.append(figure_note("这里建议放论文 Table 1。Table 1 是汇报结果时最重要的表格,建议重点标出 Efficiency、Novelty、Overall UEQ、Exploration、Enjoyment 和 Overall CSI,同时提醒 Dependability 不显著。"))
sections.append(heading("九、质性发现:系统为什么有用,也为什么受限", 1))
sections.append(heading("9.1 创意脚手架", 2))
sections.append(paragraph("访谈中,参与者普遍认为 RPGAgent 能提供一个有效的创意起点。它不一定直接完成最终游戏,但能从一个简单 prompt 生成故事、多步剧情、场景布局和可交互逻辑,让用户很快摆脱空白页面。"))
sections.append(paragraph("有参与者把它形容为团队里的 junior designer。这个说法很贴切:它不是替代设计师,而是先给出一个初稿,让设计师在此基础上修改和拓展。"))
sections.append(heading("9.2 创意边界和同质化", 2))
sections.append(paragraph("系统也暴露出明显边界。Narrative Agent 经常把非典型 prompt 重新整理成传统 RPG 任务结构。例如用户输入“猴子找香蕉”,系统会把它改写成典型寻宝任务,把猴子映射为主角,把香蕉映射为关键物品。"))
sections.append(paragraph("这说明系统的结构化流程有双刃剑效应。它让内容更连贯、更容易执行,但也可能压缩原本更开放、更奇特的创意,把它们拉回熟悉的 RPG 模板。"))
sections.append(figure_note("这里可以放论文 Figure 14。Figure 14 展示了因为资源库没有金色香蕉和猴子素材,系统用 generic placeholder 和人类角色替代,从而体现资源库边界如何影响创意表达。"))
sections.append(heading("9.3 语义理解与空间控制之间的落差", 2))
sections.append(paragraph("用户反馈中还有一个典型问题:系统能理解语义,但不一定能做好空间布局。比如它知道“战士到达村庄”这个剧情含义,但可能把战士放在村庄边缘,而不是更合理的广场或入口。"))
sections.append(paragraph("这时用户会从 co-creator 转变成 director,手动调整角色位置、NPC 数量或道具摆放。多数用户并不把这种手动修正视为失败,而是认为这是自动生成和人工控制之间可以接受的协作方式。"))
sections.append(heading("9.4 自动化和设计者主体性的张力", 2))
sections.append(paragraph("参与者对交互方式有两类偏好。一类用户喜欢分阶段检查和修改,因为这样可以确保结果符合自己的想法。另一类用户更喜欢快速自动生成,通过改变随机种子探索不同结果。"))
sections.append(paragraph("这说明 RPGAgent 这类系统未来可能需要同时支持两种模式:一种是高控制的 iterative mode,适合有明确想法或需要精修的用户;另一种是高速度的 one-shot mode,适合早期发散探索。"))
sections.append(heading("9.5 新手和专家的差异", 2))
sections.append(paragraph("专家用户更愿意使用参数调节,因为他们理解 PCG 参数和 Unity 结构,能把 agent 输出当作可塑的初稿。新手则容易被参数数量吓到,担心调坏后无法恢复。"))
sections.append(paragraph("因此,作者提出未来应该加入更好的安全实验机制,例如 reset、undo、参数解释、预设模式和渐进式暴露。这个观察也说明:给用户更多控制并不必然提升体验,控制必须和用户能力匹配。"))
sections.append(heading("十、论文贡献", 1))
sections.append(paragraph("这篇论文的贡献可以总结为三点。"))
sections.append(bullet("第一,提出了 RPGAgent,一个面向 2D RPG 原型设计的 LLM 多智能体系统,能够把故事大纲转成 Unity 中可运行和可编辑的游戏原型。"))
sections.append(bullet("第二,把 Elemental Tetrad 从游戏设计理论转化为具体系统架构,用四类 agent 对应故事、美学、机制和技术,从结构上解决跨内容一致性问题。"))
sections.append(bullet("第三,通过用户研究证明,相比普通 GPT 辅助的手工 Unity 流程,集成式多智能体工作流能显著提升效率、探索性和创作愉悦感。"))
sections.append(heading("十一、我的评价与批判", 1))
sections.append(paragraph("我认为这篇论文最有价值的地方,不在于它单纯使用了多智能体,而在于它展示了一种比较现实的 AI 创作系统设计原则:让 LLM 负责语义理解和创意扩展,让 PCG 负责空间变化,让模板和校验机制负责工程可靠性,让人类设计者保留中间干预权。"))
sections.append(paragraph("也就是说,它不是追求完全自动生成游戏,而是把 AI 放在一个受约束、可检查、可编辑的创作流程中。这一点对于游戏、交互设计、数字内容生成等领域都有借鉴意义。"))
sections.append(paragraph("不过这篇论文也有几个局限。第一,它采用 narrative-first 流程,适合故事驱动 RPG,但不一定适合解谜、策略或动作等机制优先的游戏。第二,代码生成依赖模板,可靠性提高了,但玩法表达空间有限。第三,baseline 没有包含单 agent pipeline 或没有阶段可视化的多 agent 消融实验,因此还不能完全证明效果提升一定来自多智能体分解本身。第四,实验只有 18 人且是短时间任务,距离真实生产环境还有差距。"))
sections.append(paragraph("我会把这篇论文看作一个比较成熟的 creativity support tool 原型,而不是一个真正自动化游戏开发系统。它更适合早期概念探索和原型搭建,而不是完整游戏生产。"))
sections.append(heading("十二、可用于组会讨论的问题", 1))
sections.append(bullet("RPGAgent 的提升主要来自多智能体,还是来自 Unity 内部集成和结构化中间表示?"))
sections.append(bullet("模板化代码生成是否会过度限制玩法创新?如果放开代码生成,又如何保证可靠性?"))
sections.append(bullet("系统为了 coherence 使用了强约束,但这些约束会不会导致创意同质化?"))
sections.append(bullet("对新手来说,AI 自动生成游戏原型是在帮助学习,还是让他们跳过了理解游戏设计原理的过程?"))
sections.append(bullet("如果把这个框架迁移到非 RPG 游戏,比如解谜或策略游戏,agent 的顺序和职责应该如何改变?"))
sections.append(heading("十三、汇报结尾", 1))
sections.append(paragraph("最后总结一下。这篇论文提出了 RPGAgent,一个将 LLM 多智能体、PCG 和 Unity 编辑器结合起来的 story-to-play 系统。它从用户的故事大纲出发,通过 Narrative、Scene、Mechanics 和 Code 四类 agent,生成可玩的 RPG 原型。"))
sections.append(paragraph("它的核心贡献不是让 AI 生成更多内容,而是通过结构化接口和分阶段工作流,让故事、场景、玩法和代码之间保持一致。实验表明,它能够显著提升用户体验、创意探索和创作愉悦感,但在可靠性、表达自由度和细粒度控制方面仍有明显限制。"))
sections.append(paragraph("所以我对这篇论文的最终理解是:RPGAgent 代表了一类新的 AI 辅助创作工具,它不是把设计师替换掉,而是为设计师提供一个可运行、可修改、可继续发展的创意初稿。它真正有价值的地方,是把“生成内容”推进到了“生成能被整合和交互的内容”。"))
document_xml = f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
{''.join(sections)}
<w:sectPr>
<w:pgSz w:w="11906" w:h="16838"/>
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="708" w:footer="708" w:gutter="0"/>
</w:sectPr>
</w:body>
</w:document>
"""
styles_xml = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
<w:name w:val="Normal"/>
<w:pPr><w:spacing w:after="120" w:line="360" w:lineRule="auto"/></w:pPr>
<w:rPr><w:rFonts w:ascii="Microsoft YaHei" w:eastAsia="Microsoft YaHei" w:hAnsi="Microsoft YaHei"/><w:sz w:val="22"/></w:rPr>
</w:style>
<w:style w:type="paragraph" w:styleId="Title">
<w:name w:val="Title"/>
<w:pPr><w:jc w:val="center"/><w:spacing w:after="240"/></w:pPr>
<w:rPr><w:rFonts w:ascii="Microsoft YaHei" w:eastAsia="Microsoft YaHei" w:hAnsi="Microsoft YaHei"/><w:b/><w:sz w:val="36"/></w:rPr>
</w:style>
<w:style w:type="paragraph" w:styleId="Subtitle">
<w:name w:val="Subtitle"/>
<w:pPr><w:jc w:val="center"/><w:spacing w:after="240"/></w:pPr>
<w:rPr><w:rFonts w:ascii="Microsoft YaHei" w:eastAsia="Microsoft YaHei" w:hAnsi="Microsoft YaHei"/><w:sz w:val="22"/><w:color w:val="555555"/></w:rPr>
</w:style>
<w:style w:type="paragraph" w:styleId="Heading1">
<w:name w:val="heading 1"/>
<w:basedOn w:val="Normal"/>
<w:pPr><w:spacing w:before="360" w:after="160"/><w:outlineLvl w:val="0"/></w:pPr>
<w:rPr><w:rFonts w:ascii="Microsoft YaHei" w:eastAsia="Microsoft YaHei" w:hAnsi="Microsoft YaHei"/><w:b/><w:sz w:val="30"/></w:rPr>
</w:style>
<w:style w:type="paragraph" w:styleId="Heading2">
<w:name w:val="heading 2"/>
<w:basedOn w:val="Normal"/>
<w:pPr><w:spacing w:before="220" w:after="120"/><w:outlineLvl w:val="1"/></w:pPr>
<w:rPr><w:rFonts w:ascii="Microsoft YaHei" w:eastAsia="Microsoft YaHei" w:hAnsi="Microsoft YaHei"/><w:b/><w:sz w:val="25"/></w:rPr>
</w:style>
<w:style w:type="paragraph" w:styleId="FigureNote">
<w:name w:val="Figure Note"/>
<w:basedOn w:val="Normal"/>
<w:pPr><w:spacing w:before="120" w:after="160"/><w:ind w:left="360"/></w:pPr>
<w:rPr><w:rFonts w:ascii="Microsoft YaHei" w:eastAsia="Microsoft YaHei" w:hAnsi="Microsoft YaHei"/><w:b/><w:color w:val="1F4E79"/><w:sz w:val="21"/></w:rPr>
</w:style>
</w:styles>
"""
content_types_xml = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
<Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>
</Types>
"""
rels_xml = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>
"""
doc_rels_xml = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
</Relationships>
"""
with zipfile.ZipFile(OUT, "w", compression=zipfile.ZIP_DEFLATED) as zf:
zf.writestr("[Content_Types].xml", content_types_xml)
zf.writestr("_rels/.rels", rels_xml)
zf.writestr("word/document.xml", document_xml)
zf.writestr("word/styles.xml", styles_xml)
zf.writestr("word/_rels/document.xml.rels", doc_rels_xml)
print(OUT.resolve())