Files
AgentBlock/tools/generate_rpgagent_group_meeting_doc.py
T
2026-06-23 14:12:30 +08:00

259 lines
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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())