添加DSL引擎迁移和验证功能
- 新增最终验证脚本和迁移测试脚本,确保DSL引擎的完整性和功能 - 实现故事模块的音频配置、角色定义和情感故事模块 - 迁移现有故事数据到新的DSL格式,支持动态内容和条件导航 - 更新主题和UI组件,确保一致的黑色背景和暗色主题 - 添加音频管理器和性能监控工具,提升游戏体验和调试能力
This commit is contained in:
458
Documentation/mindmap/all_storylines.mmd
Normal file
458
Documentation/mindmap/all_storylines.mmd
Normal file
@@ -0,0 +1,458 @@
|
||||
%%{init: {'flowchart': {'htmlLabels': true}}}%%
|
||||
flowchart TD
|
||||
|
||||
subgraph cluster_main[主线:第一次觉醒]
|
||||
alternate_route["寻找其他路径
|
||||
跳转分支:寻找萨拉博士"]
|
||||
anchor_modification["时间锚的重塑 - 第一阶段"]
|
||||
awakening["起身探索"]
|
||||
cautious_investigation["档案库的秘密调查"]
|
||||
consciousness_integration["意识的完美融合"]
|
||||
contact_eva["联系伊娃
|
||||
跳转分支:伊娃的身份揭示"]
|
||||
cosmic_communication["宇宙信标计划
|
||||
跳转分支:伊娃的身份揭示"]
|
||||
dmitri_confrontation["时间锚实验室的真相
|
||||
跳转分支:伊娃的身份揭示、跳转分支:发现植入物的痕迹"]
|
||||
dmitri_empathy_path["尝试理解德米特里的痛苦,寻求和解"]
|
||||
dmitri_evidence_reveal["证据摊牌"]
|
||||
dmitri_personal_reveal["德米特里的个人动机"]
|
||||
dmitri_truth_confrontation["冲破德米特里的房门"]
|
||||
ending_perfect["完美的新世界
|
||||
跳转分支:伊娃的身份揭示"]
|
||||
eva_assistance_request["请求伊娃的帮助
|
||||
跳转分支:伊娃的身份揭示"]
|
||||
eva_experiment_explanation["时间锚实验的真相
|
||||
跳转分支:伊娃的身份揭示"]
|
||||
eva_plan_consultation["联系伊娃,询问她对计划的看法"]
|
||||
evidence_collection["保留证据,继续调查德米特里"]
|
||||
explosion_evidence_analysis["爆炸证据深度分析"]
|
||||
find_dmitri["寻找德米特里博士"]
|
||||
first_awakening["第一次觉醒"]
|
||||
fragmented_visions["破碎的时间回忆
|
||||
跳转分支:发现植入物的痕迹、跳转分支:第48次循环的完美策略"]
|
||||
investigation["项目月神的四个阶段"]
|
||||
medical_bay_discovery["医疗舱记录搜索
|
||||
跳转分支:寻找萨拉博士"]
|
||||
memory_attempt["回忆尝试"]
|
||||
memory_integration_crisis["记忆整合危机
|
||||
跳转分支:伊娃的身份揭示、跳转分支:第48次循环的完美策略"]
|
||||
mysterious_note["神秘纸条"]
|
||||
observe_medical_bay["观察医疗舱"]
|
||||
observe_scar["观察伤疤
|
||||
跳转分支:发现植入物的痕迹"]
|
||||
other_subjects["医疗舱搜索
|
||||
跳转分支:伊娃的身份揭示、跳转分支:寻找萨拉博士"]
|
||||
plan_vulnerability_analysis["寻找计划的漏洞和弱点"]
|
||||
reactor_confrontation_path["反应堆最终对峙"]
|
||||
reactor_investigation_path["反应堆主动调查
|
||||
跳转分支:第48次循环的完美策略"]
|
||||
reactor_path["前往量子反应堆
|
||||
跳转分支:第48次循环的完美策略"]
|
||||
reactor_sabotage_attempt["反应堆破坏尝试"]
|
||||
repetition_memory_analysis["循环记忆的深度分析"]
|
||||
sabotage_memory_recovery["专注于时间锚摧毁计划的记忆"]
|
||||
sara_consultation["寻找萨拉博士,询问她的立场"]
|
||||
scientist_memory_fragment["回忆科学家的争论"]
|
||||
self_recording["录音设备"]
|
||||
temporal_civilization["时间避难所社会的创建
|
||||
跳转分支:伊娃的身份揭示"]
|
||||
timeline_analysis["时间线深度分析"]
|
||||
transcendent_exploration["探索超越性的存在
|
||||
跳转分支:伊娃的身份揭示、跳转分支:第48次循环的完美策略"]
|
||||
alternate_route -- "通过维护通道秘密潜入反应堆" --> reactor_investigation_path
|
||||
anchor_modification -- "利用时间避难所向宇宙发出信号" --> cosmic_communication
|
||||
anchor_modification -- "在时间避难所中建立新的人类社会" --> temporal_civilization
|
||||
anchor_modification -- "尝试让伊娃融合新现实" --> consciousness_integration
|
||||
anchor_modification -- "探索超越性的存在" --> transcendent_exploration
|
||||
awakening -- "仔细检查自己的伤疤" --> observe_scar
|
||||
awakening -- "播放录音设备" --> self_recording
|
||||
awakening -- "查看床头柜上的纸条" --> mysterious_note
|
||||
cautious_investigation -- "仔细阅读项目计划..." --> investigation
|
||||
consciousness_integration -- "走向完美的新世界" --> ending_perfect
|
||||
contact_eva -- "要求莉莉帮助阻止实验" --> eva_assistance_request
|
||||
contact_eva -- "询问莉莉关于时间锚实验的详情" --> eva_experiment_explanation
|
||||
contact_eva -- "请求莉莉恢复你的完整记忆" --> eva_experiment_explanation
|
||||
dmitri_confrontation -- "询问如何终止实验" --> reactor_investigation_path
|
||||
dmitri_confrontation -- "质问德米特里为什么选择了你" --> dmitri_personal_reveal
|
||||
dmitri_truth_confrontation -- "跟随德米特里进入实验室..." --> dmitri_confrontation
|
||||
eva_assistance_request -- "先收集更多信息再行动" --> cautious_investigation
|
||||
eva_assistance_request -- "立即执行计划,前往反应堆" --> reactor_path
|
||||
eva_experiment_explanation -- "只恢复最关键的记忆" --> memory_integration_crisis
|
||||
eva_experiment_explanation -- "暂时放弃,专注于当前的行动" --> eva_assistance_request
|
||||
eva_experiment_explanation -- "立即开始记忆恢复" --> memory_integration_crisis
|
||||
eva_experiment_explanation -- "询问如何阻止这个实验" --> eva_assistance_request
|
||||
explosion_evidence_analysis -- "带着证据去找德米特里对质" --> dmitri_evidence_reveal
|
||||
explosion_evidence_analysis -- "搜索关于这个机器的更多信息" --> reactor_investigation_path
|
||||
find_dmitri -- "告诉他你发现了录音和纸条" --> dmitri_evidence_reveal
|
||||
find_dmitri -- "离开这里,寻找其他帮助" --> contact_eva
|
||||
find_dmitri -- "要求德米特里开门解释一切" --> dmitri_truth_confrontation
|
||||
first_awakening -- "尝试回忆发生了什么" --> memory_attempt
|
||||
first_awakening -- "立即起身查看情况" --> awakening
|
||||
first_awakening -- "观察周围环境寻找线索" --> observe_medical_bay
|
||||
fragmented_visions -- "寻找录音设备和更多证据" --> self_recording
|
||||
fragmented_visions -- "立即前往反应堆寻找时间锚" --> reactor_investigation_path
|
||||
investigation -- "寻找计划的漏洞和弱点" --> plan_vulnerability_analysis
|
||||
investigation -- "尝试理解德米特里的痛苦,寻求和解" --> dmitri_empathy_path
|
||||
investigation -- "联系伊娃,询问她对计划的看法" --> eva_plan_consultation
|
||||
medical_bay_discovery -- "保留证据,继续调查德米特里" --> evidence_collection
|
||||
memory_attempt -- "专注于爆炸和蓝光的记忆" --> fragmented_visions
|
||||
memory_attempt -- "尝试回忆德米特里和萨拉的对话" --> scientist_memory_fragment
|
||||
memory_attempt -- "探索'再试一次'的记忆含义" --> repetition_memory_analysis
|
||||
memory_integration_crisis -- "是的,我准备好与伊娃一起改变一切" --> anchor_modification
|
||||
memory_integration_crisis -- "让我看看所有的可能性" --> timeline_analysis
|
||||
mysterious_note -- "搜索医疗舱寻找更多线索" --> other_subjects
|
||||
mysterious_note -- "播放录音设备" --> self_recording
|
||||
mysterious_note -- "立即前往反应堆冷却回路" --> reactor_path
|
||||
observe_medical_bay -- "搜索医疗记录寻找答案" --> medical_bay_discovery
|
||||
observe_medical_bay -- "调查墙上的神秘洞和破坏痕迹" --> explosion_evidence_analysis
|
||||
observe_scar -- "播放录音设备,看是否有相关信息" --> self_recording
|
||||
observe_scar -- "查看床头柜上的纸条,寻找解释" --> mysterious_note
|
||||
other_subjects -- "带着证据去找德米特里博士" --> find_dmitri
|
||||
other_subjects -- "联系伊娃/莉莉验证这些信息" --> contact_eva
|
||||
reactor_investigation_path -- "尝试手动关闭时间锚" --> reactor_sabotage_attempt
|
||||
reactor_investigation_path -- "深入研究时间线数据" --> timeline_analysis
|
||||
reactor_investigation_path -- "要求德米特里展示完整的真相" --> reactor_confrontation_path
|
||||
reactor_path -- "使用紧急维护接入点强行进入" --> reactor_confrontation_path
|
||||
reactor_path -- "退出,寻找其他方法或帮助" --> alternate_route
|
||||
scientist_memory_fragment -- "专注于时间锚摧毁计划的记忆" --> sabotage_memory_recovery
|
||||
scientist_memory_fragment -- "回忆关于其他141个实验对象的信息" --> other_subjects
|
||||
scientist_memory_fragment -- "寻找萨拉博士,询问她的立场" --> sara_consultation
|
||||
scientist_memory_fragment -- "立即与德米特里对质关于道德问题" --> dmitri_truth_confrontation
|
||||
self_recording -- "仔细分析录音中的所有信息" --> other_subjects
|
||||
self_recording -- "寻找德米特里博士要求解释" --> find_dmitri
|
||||
self_recording -- "尝试联系伊娃确认信息" --> contact_eva
|
||||
self_recording -- "立即前往反应堆寻找时间锚装置" --> reactor_path
|
||||
end
|
||||
subgraph cluster_branch_1[分支:发现植入物的痕迹]
|
||||
subgraph cluster_branch_1_stage_0[stage 0]
|
||||
body_modification_revelation["发现植入物的痕迹"]
|
||||
end
|
||||
subgraph cluster_branch_1_stage_1[stage 1]
|
||||
eva_identity_revelation["伊娃身份的深度揭示"]
|
||||
implant_activation["尝试激活手臂植入物"]
|
||||
project_luna_investigation["量子植入物的觉醒"]
|
||||
end
|
||||
subgraph cluster_branch_1_stage_2[stage 2]
|
||||
eva_quantum_link["从痛苦中创造意义"]
|
||||
humanity_preservation["关闭植入物,重新变回完全的人类"]
|
||||
reality_stabilization["现实守护者的使命"]
|
||||
temporal_selves_communion["意识接触 - 47个自我"]
|
||||
end
|
||||
subgraph cluster_branch_1_stage_3[stage 3]
|
||||
civilization_renarration["重写文明之歌"]
|
||||
complete_self_world["创造一个每个人都能成为完整自我的世界"]
|
||||
earth_salvation["使用新的理解来真正拯救地球"]
|
||||
eternal_guardians_ending["接受守护者的永恒使命"]
|
||||
guardian_council_ending["建立守护者议会,培养新的守护者"]
|
||||
meaning_academy["建立'意义创造学院',教导这种哲学"]
|
||||
philosophical_programming["将这种思维方式编程到时间锚中"]
|
||||
reality_reprogramming["利用完整的自我重新编程现实"]
|
||||
wholeness_sharing["分享这种完整性给其他人"]
|
||||
wisdom_messenger["返回地球,成为这种智慧的活体传播者"]
|
||||
end
|
||||
subgraph cluster_branch_1_stage_4[stage 4]
|
||||
civilization_symbol["成为新文明模式的活体象征"]
|
||||
interspecies_harmony["创建跨物种的文明交流协议"]
|
||||
narrative_academy["建立'新叙事'培训中心,遍布全宇宙"]
|
||||
end
|
||||
body_modification_revelation -- "尝试激活手臂植入物" --> implant_activation
|
||||
body_modification_revelation -- "探索'月神'项目的含义" --> project_luna_investigation
|
||||
body_modification_revelation -- "立即联系伊娃询问真相" --> eva_identity_revelation
|
||||
civilization_renarration -- "创建跨物种的文明交流协议" --> interspecies_harmony
|
||||
civilization_renarration -- "建立'新叙事'培训中心,遍布全宇宙" --> narrative_academy
|
||||
civilization_renarration -- "成为新文明模式的活体象征" --> civilization_symbol
|
||||
eva_quantum_link -- "创建全新的故事,重新定义人类文明的叙事" --> civilization_renarration
|
||||
eva_quantum_link -- "将这种思维方式编程到时间锚中" --> philosophical_programming
|
||||
eva_quantum_link -- "建立'意义创造学院',教导这种哲学" --> meaning_academy
|
||||
eva_quantum_link -- "返回地球,成为这种智慧的活体传播者" --> wisdom_messenger
|
||||
project_luna_investigation -- "使用新能力直接与伊娃的意识深层连接" --> eva_quantum_link
|
||||
project_luna_investigation -- "关闭植入物,重新变回完全的人类" --> humanity_preservation
|
||||
project_luna_investigation -- "尝试与时间锚中的47个自己沟通" --> temporal_selves_communion
|
||||
project_luna_investigation -- "试图稳定现实裂隙,阻止宇宙崩溃" --> reality_stabilization
|
||||
reality_stabilization -- "建立守护者议会,培养新的守护者" --> guardian_council_ending
|
||||
reality_stabilization -- "接受守护者的永恒使命" --> eternal_guardians_ending
|
||||
temporal_selves_communion -- "使用新的理解来真正拯救地球" --> earth_salvation
|
||||
temporal_selves_communion -- "分享这种完整性给其他人" --> wholeness_sharing
|
||||
temporal_selves_communion -- "创造一个每个人都能成为完整自我的世界" --> complete_self_world
|
||||
temporal_selves_communion -- "利用完整的自我重新编程现实" --> reality_reprogramming
|
||||
end
|
||||
subgraph cluster_branch_2[分支:输入关闭代码]
|
||||
subgraph cluster_branch_2_stage_0[stage 0]
|
||||
emergency_shutdown["输入关闭代码"]
|
||||
end
|
||||
subgraph cluster_branch_2_stage_1[stage 1]
|
||||
shutdown["德米特里的恐慌"]
|
||||
end
|
||||
subgraph cluster_branch_2_stage_2[stage 2]
|
||||
shutdown_final_choice["时空稳定的关键选择"]
|
||||
end
|
||||
subgraph cluster_branch_2_stage_3[stage 3]
|
||||
manual_stabilization["使用植入物手动稳定时空"]
|
||||
reality_preservation["立即停止,保护现实稳定"]
|
||||
timeline_liberation["完成关闭序列,释放所有时间线"]
|
||||
end
|
||||
emergency_shutdown -- "观察时间锚的反应..." --> shutdown
|
||||
shutdown -- "理解后果..." --> shutdown_final_choice
|
||||
shutdown_final_choice -- "使用植入物手动稳定时空" --> manual_stabilization
|
||||
shutdown_final_choice -- "完成关闭序列,释放所有时间线" --> timeline_liberation
|
||||
shutdown_final_choice -- "立即停止,保护现实稳定" --> reality_preservation
|
||||
end
|
||||
subgraph cluster_branch_3[分支:伊娃的身份揭示]
|
||||
subgraph cluster_branch_3_身份揭示主簇[身份揭示主簇]
|
||||
eva_revelation["伊娃的身份揭示
|
||||
跳转分支:情感-记忆"]
|
||||
end
|
||||
subgraph cluster_branch_3_情感-记忆[情感-记忆]
|
||||
emotional_reunion["姐妹重聚"]
|
||||
identity_exploration["身份的探索"]
|
||||
memory_sharing["记忆的分享"]
|
||||
rescue_planning["拯救计划"]
|
||||
emotional_reunion -- "先保护伊娃安全" --> rescue_planning
|
||||
emotional_reunion -- "整合所有循环记忆" --> identity_exploration
|
||||
end
|
||||
subgraph cluster_branch_3_其他[其他]
|
||||
anchor_destruction["破坏时间锚的决定"]
|
||||
anna_grave_investigation["先找到安娜的墓地,了解德米特里的弱点"]
|
||||
comfort_session["安慰的时光"]
|
||||
contact_crew["手动重启尝试"]
|
||||
crew_analysis["人员分析与评估"]
|
||||
crew_confrontation["直面冲突"]
|
||||
crew_confrontation_control_room["控制室对峙"]
|
||||
crew_search["主动接触"]
|
||||
data_extraction["访问机密数据库"]
|
||||
deception_play["策略性欺骗"]
|
||||
denial_path["逃避的代价"]
|
||||
direct_confrontation["直接对峙"]
|
||||
earth_truth["地球的真相"]
|
||||
emergency_shutdown["输入关闭代码"]
|
||||
emotional_breakdown["情感崩溃"]
|
||||
ending_freedom["自由的代价"]
|
||||
ending_guardian["时间的守护者"]
|
||||
ending_heroic["英雄的选择"]
|
||||
escape_attempt["逃离医疗舱"]
|
||||
eternal_loop["永恒的循环"]
|
||||
ethical_discussion["道德抉择的对话"]
|
||||
eva_photo_reaction["继续阅读"]
|
||||
garden_cooperation["秘密花园的发现"]
|
||||
garden_learning["学习生命的艺术"]
|
||||
garden_observation["花园的生命力"]
|
||||
garden_partnership["花园伙伴关系"]
|
||||
gradual_revelation["逐步的启示"]
|
||||
immediate_exploration["紧急离开"]
|
||||
manual_stabilization["使用植入物手动稳定时空"]
|
||||
marcus_strategy["与马库斯联手"]
|
||||
memory_reconstruction["记忆重建"]
|
||||
oxygen_crisis_expanded["氧气危机"]
|
||||
philosophical_discussion["哲学思辨"]
|
||||
reality_preservation["立即停止,保护现实稳定"]
|
||||
restart_cycle_end["重新开始(进入下一次循环)"]
|
||||
sabotage_discussion["破坏计划"]
|
||||
sara_evidence_sharing["联系萨拉博士,告诉她你发现的证据"]
|
||||
shutdown["德米特里的恐慌"]
|
||||
shutdown_final_choice["时空稳定的关键选择"]
|
||||
stealth_medical_search["隐蔽搜索行动"]
|
||||
stealth_observation["隐秘观察"]
|
||||
stealth_observation_detailed["隐秘观察 - 详细版"]
|
||||
system_logs["系统日志分析"]
|
||||
timeline_liberation["完成关闭序列,释放所有时间线"]
|
||||
anchor_destruction -- "开始新的生活" --> ending_freedom
|
||||
comfort_session -- "先确保你的安全" --> gradual_revelation
|
||||
comfort_session -- "立即行动" --> stealth_observation
|
||||
comfort_session -- "通知马库斯" --> marcus_strategy
|
||||
contact_crew -- "尝试绕过安全协议" --> oxygen_crisis_expanded
|
||||
contact_crew -- "检查系统的安全锁定" --> system_logs
|
||||
contact_crew -- "等待萨拉博士到来" --> oxygen_crisis_expanded
|
||||
crew_analysis -- "与马库斯私下讨论安全措施" --> stealth_observation_detailed
|
||||
crew_analysis -- "测试萨拉的忠诚度和决心" --> direct_confrontation
|
||||
crew_analysis -- "深入调查德米特里的真实意图" --> stealth_observation_detailed
|
||||
crew_analysis -- "请求伊娃提供更详细的基地分析" --> data_extraction
|
||||
crew_confrontation -- "支持马库斯,一起对抗德米特里" --> marcus_strategy
|
||||
crew_confrontation -- "要求所有人冷静下来讨论解决方案" --> deception_play
|
||||
crew_confrontation -- "试图说服德米特里停止实验" --> sabotage_discussion
|
||||
crew_search -- "反问她关于基地的异常情况" --> crew_confrontation
|
||||
crew_search -- "告诉她你发现了录音设备" --> stealth_observation
|
||||
crew_search -- "撒谎说什么都不记得" --> deception_play
|
||||
deception_play -- "利用这段时间联系伊娃制定计划" --> stealth_observation
|
||||
deception_play -- "快速离开寻找马库斯的帮助" --> immediate_exploration
|
||||
denial_path -- "重新开始" --> restart_cycle_end
|
||||
earth_truth -- "选择改进时间锚技术,自愿拯救地球" --> ending_heroic
|
||||
earth_truth -- "选择放弃地球,优先考虑人类尊严" --> anchor_destruction
|
||||
emergency_shutdown -- "观察时间锚的反应..." --> shutdown
|
||||
emotional_breakdown -- "试图冷静并重思" --> comfort_session
|
||||
ending_freedom -- "重新开始" --> restart_cycle_end
|
||||
ending_guardian -- "重新开始" --> restart_cycle_end
|
||||
ending_heroic -- "重新开始" --> restart_cycle_end
|
||||
escape_attempt -- "尝试联系外界求救" --> contact_crew
|
||||
escape_attempt -- "躲藏起来,等待合适的时机" --> stealth_medical_search
|
||||
eternal_loop -- "接受永恒的使命" --> ending_guardian
|
||||
ethical_discussion -- "寻求地球与人性的平衡" --> earth_truth
|
||||
ethical_discussion -- "讨论彻底停止后果" --> anchor_destruction
|
||||
ethical_discussion -- "让每个人提出解决方案" --> gradual_revelation
|
||||
garden_cooperation -- "仔细观察花园中的植物细节" --> garden_observation
|
||||
garden_cooperation -- "同意测试记忆恢复程序" --> memory_reconstruction
|
||||
garden_cooperation -- "提议寻找其他解决方案" --> garden_partnership
|
||||
garden_cooperation -- "要求更多时间考虑" --> philosophical_discussion
|
||||
garden_cooperation -- "询问关于地球植物的更多信息" --> earth_truth
|
||||
garden_cooperation -- "询问萨拉是否保留了过去的回忆" --> eva_photo_reaction
|
||||
garden_observation -- "表达对萨拉的感谢和敬意" --> comfort_session
|
||||
garden_observation -- "要求萨拉教你一些园艺技能" --> garden_learning
|
||||
garden_observation -- "询问植物治疗的具体原理" --> garden_partnership
|
||||
gradual_revelation -- "推动记忆恢复研究" --> memory_reconstruction
|
||||
immediate_exploration -- "前往通讯阵列寻求外界帮助" --> escape_attempt
|
||||
immediate_exploration -- "潜入主控制室收集信息" --> system_logs
|
||||
marcus_strategy -- "先确保基地内其他人的安全" --> crew_confrontation
|
||||
marcus_strategy -- "制定详细的行动计划" --> sabotage_discussion
|
||||
oxygen_crisis_expanded -- "尝试手动重启氧气系统" --> contact_crew
|
||||
oxygen_crisis_expanded -- "检查系统日志" --> system_logs
|
||||
oxygen_crisis_expanded -- "联系基地其他人员" --> contact_crew
|
||||
sabotage_discussion -- "威胁报告给外界机构" --> deception_play
|
||||
sabotage_discussion -- "要求德米特里交出所有实验数据" --> crew_confrontation
|
||||
sabotage_discussion -- "销毁记忆植入设备" --> marcus_strategy
|
||||
shutdown -- "理解后果..." --> shutdown_final_choice
|
||||
shutdown_final_choice -- "使用植入物手动稳定时空" --> manual_stabilization
|
||||
shutdown_final_choice -- "完成关闭序列,释放所有时间线" --> timeline_liberation
|
||||
shutdown_final_choice -- "立即停止,保护现实稳定" --> reality_preservation
|
||||
stealth_medical_search -- "先找到安娜的墓地,了解德米特里的弱点" --> anna_grave_investigation
|
||||
stealth_medical_search -- "立即使用关闭代码尝试停止时间锚" --> emergency_shutdown
|
||||
stealth_medical_search -- "联系萨拉博士,告诉她你发现的证据" --> sara_evidence_sharing
|
||||
stealth_observation -- "假装配合,等待机会" --> deception_play
|
||||
stealth_observation -- "现身与萨拉对话" --> crew_search
|
||||
stealth_observation -- "立即离开医疗舱" --> immediate_exploration
|
||||
stealth_observation -- "等他们离开后行动" --> immediate_exploration
|
||||
stealth_observation -- "要求伊娃帮助联系马库斯" --> marcus_strategy
|
||||
stealth_observation_detailed -- "主动现身,与萨拉对话" --> direct_confrontation
|
||||
stealth_observation_detailed -- "尝试创造分散注意力的行动" --> deception_play
|
||||
stealth_observation_detailed -- "尝试联系伊娃寻求帮助" --> stealth_observation
|
||||
stealth_observation_detailed -- "立即现身帮助萨拉" --> crew_confrontation_control_room
|
||||
system_logs -- "寻求其他人的帮助" --> contact_crew
|
||||
system_logs -- "深入分析访问日志" --> oxygen_crisis_expanded
|
||||
system_logs -- "立即尝试修复氧气系统" --> contact_crew
|
||||
end
|
||||
end
|
||||
subgraph cluster_branch_4[分支:寻找萨拉博士]
|
||||
subgraph cluster_branch_4_stage_0[stage 0]
|
||||
find_sara["寻找萨拉博士"]
|
||||
end
|
||||
subgraph cluster_branch_4_stage_1[stage 1]
|
||||
sara_confession["萨拉的坦白 - 第一部分"]
|
||||
end
|
||||
subgraph cluster_branch_4_stage_2[stage 2]
|
||||
dmitri_vulnerability["要求萨拉提供德米特里的弱点信息"]
|
||||
external_contact["请求萨拉帮助联系外界"]
|
||||
sara_alliance["安慰萨拉,询问如何联合阻止德米特里"]
|
||||
end
|
||||
find_sara -- "让萨拉先说她想说的" --> sara_confession
|
||||
find_sara -- "询问自己昏迷的时间" --> sara_confession
|
||||
find_sara -- "质问萨拉为什么看起来内疚" --> sara_confession
|
||||
sara_confession -- "安慰萨拉,询问如何联合阻止德米特里" --> sara_alliance
|
||||
sara_confession -- "要求萨拉提供德米特里的弱点信息" --> dmitri_vulnerability
|
||||
sara_confession -- "请求萨拉帮助联系外界" --> external_contact
|
||||
end
|
||||
subgraph cluster_branch_5[分支:隐藏标记的发现]
|
||||
subgraph cluster_branch_5_stage_0[stage 0]
|
||||
hidden_marks_discovery["隐藏标记的发现"]
|
||||
end
|
||||
subgraph cluster_branch_5_stage_1[stage 1]
|
||||
deep_body_scan["忽略警告,继续自我检查"]
|
||||
implant_ai_communication["尝试与植入AI系统对话"]
|
||||
project_luna_investigation["量子植入物的觉醒"]
|
||||
quantum_interface_activation["立即尝试激活量子接口"]
|
||||
end
|
||||
subgraph cluster_branch_5_stage_2[stage 2]
|
||||
eva_quantum_link["从痛苦中创造意义"]
|
||||
humanity_preservation["关闭植入物,重新变回完全的人类"]
|
||||
reality_stabilization["现实守护者的使命"]
|
||||
temporal_selves_communion["意识接触 - 47个自我"]
|
||||
end
|
||||
subgraph cluster_branch_5_stage_3[stage 3]
|
||||
civilization_renarration["重写文明之歌"]
|
||||
complete_self_world["创造一个每个人都能成为完整自我的世界"]
|
||||
earth_salvation["使用新的理解来真正拯救地球"]
|
||||
eternal_guardians_ending["接受守护者的永恒使命"]
|
||||
guardian_council_ending["建立守护者议会,培养新的守护者"]
|
||||
meaning_academy["建立'意义创造学院',教导这种哲学"]
|
||||
philosophical_programming["将这种思维方式编程到时间锚中"]
|
||||
reality_reprogramming["利用完整的自我重新编程现实"]
|
||||
wholeness_sharing["分享这种完整性给其他人"]
|
||||
wisdom_messenger["返回地球,成为这种智慧的活体传播者"]
|
||||
end
|
||||
subgraph cluster_branch_5_stage_4[stage 4]
|
||||
civilization_symbol["成为新文明模式的活体象征"]
|
||||
interspecies_harmony["创建跨物种的文明交流协议"]
|
||||
narrative_academy["建立'新叙事'培训中心,遍布全宇宙"]
|
||||
end
|
||||
civilization_renarration -- "创建跨物种的文明交流协议" --> interspecies_harmony
|
||||
civilization_renarration -- "建立'新叙事'培训中心,遍布全宇宙" --> narrative_academy
|
||||
civilization_renarration -- "成为新文明模式的活体象征" --> civilization_symbol
|
||||
eva_quantum_link -- "创建全新的故事,重新定义人类文明的叙事" --> civilization_renarration
|
||||
eva_quantum_link -- "将这种思维方式编程到时间锚中" --> philosophical_programming
|
||||
eva_quantum_link -- "建立'意义创造学院',教导这种哲学" --> meaning_academy
|
||||
eva_quantum_link -- "返回地球,成为这种智慧的活体传播者" --> wisdom_messenger
|
||||
hidden_marks_discovery -- "尝试与植入AI系统对话" --> implant_ai_communication
|
||||
hidden_marks_discovery -- "忽略警告,继续自我检查" --> deep_body_scan
|
||||
hidden_marks_discovery -- "搜索关于'月神'项目的信息" --> project_luna_investigation
|
||||
hidden_marks_discovery -- "立即尝试激活量子接口" --> quantum_interface_activation
|
||||
project_luna_investigation -- "使用新能力直接与伊娃的意识深层连接" --> eva_quantum_link
|
||||
project_luna_investigation -- "关闭植入物,重新变回完全的人类" --> humanity_preservation
|
||||
project_luna_investigation -- "尝试与时间锚中的47个自己沟通" --> temporal_selves_communion
|
||||
project_luna_investigation -- "试图稳定现实裂隙,阻止宇宙崩溃" --> reality_stabilization
|
||||
reality_stabilization -- "建立守护者议会,培养新的守护者" --> guardian_council_ending
|
||||
reality_stabilization -- "接受守护者的永恒使命" --> eternal_guardians_ending
|
||||
temporal_selves_communion -- "使用新的理解来真正拯救地球" --> earth_salvation
|
||||
temporal_selves_communion -- "分享这种完整性给其他人" --> wholeness_sharing
|
||||
temporal_selves_communion -- "创造一个每个人都能成为完整自我的世界" --> complete_self_world
|
||||
temporal_selves_communion -- "利用完整的自我重新编程现实" --> reality_reprogramming
|
||||
end
|
||||
subgraph cluster_branch_6[分支:第48次循环的完美策略]
|
||||
subgraph cluster_branch_6_stage_0[stage 0]
|
||||
strategic_planning["第48次循环的完美策略"]
|
||||
end
|
||||
subgraph cluster_branch_6_stage_1[stage 1]
|
||||
complete_destruction_plan["激进重生:彻底摧毁"]
|
||||
empathy_healing_path["选择情感路径 - 通过爱与理解来治愈所有人"]
|
||||
eva_transcendence["伊娃的超越"]
|
||||
suffering_philosophy["共情治愈之路"]
|
||||
end
|
||||
subgraph cluster_branch_6_stage_2[stage 2]
|
||||
collective_redefinition["与所有人一起重新定义时间锚的目的"]
|
||||
consciousness_communication["与时间锚意识沟通"]
|
||||
ethical_discussion["道德抉择的对话"]
|
||||
eva_transcendence_ending["伊娃的最终超越"]
|
||||
individual_path_guidance["帮助每个人找到属于自己的道路"]
|
||||
natural_healing_path["回到地球,专注于自然修复而非技术干预"]
|
||||
tech_ethics_guardian["成立'技术伦理监督委员会',永久监管危险科学"]
|
||||
understanding_future["创建一个新的未来,基于理解而非恐惧"]
|
||||
universal_warning_beacon["将你的故事传播到全宇宙,警告其他文明"]
|
||||
end
|
||||
subgraph cluster_branch_6_stage_3[stage 3]
|
||||
anchor_destruction["破坏时间锚的决定"]
|
||||
gradual_revelation["逐步的启示"]
|
||||
end
|
||||
subgraph cluster_branch_6_stage_4[stage 4]
|
||||
ending_freedom["自由的代价"]
|
||||
memory_reconstruction["记忆重建"]
|
||||
end
|
||||
anchor_destruction -- "开始新的生活" --> ending_freedom
|
||||
complete_destruction_plan -- "回到地球,专注于自然修复而非技术干预" --> natural_healing_path
|
||||
complete_destruction_plan -- "将你的故事传播到全宇宙,警告其他文明" --> universal_warning_beacon
|
||||
complete_destruction_plan -- "成立'技术伦理监督委员会',永久监管危险科学" --> tech_ethics_guardian
|
||||
consciousness_communication -- "决定释放所有被困的意识" --> anchor_destruction
|
||||
consciousness_communication -- "询问伊娃是否有其他选择" --> eva_transcendence
|
||||
ethical_discussion -- "讨论彻底停止后果" --> anchor_destruction
|
||||
ethical_discussion -- "让每个人提出解决方案" --> gradual_revelation
|
||||
eva_transcendence -- "同意让伊娃进行超越转化" --> eva_transcendence_ending
|
||||
eva_transcendence -- "要求更多时间考虑这个选择" --> ethical_discussion
|
||||
eva_transcendence -- "询问是否有让伊娃保持个体性的方法" --> consciousness_communication
|
||||
gradual_revelation -- "推动记忆恢复研究" --> memory_reconstruction
|
||||
strategic_planning -- "暂停,深入思考痛苦与意义的哲学问题" --> suffering_philosophy
|
||||
strategic_planning -- "选择情感路径 - 通过爱与理解来治愈所有人" --> empathy_healing_path
|
||||
strategic_planning -- "选择激进路径 - 彻底摧毁所有实验设施" --> complete_destruction_plan
|
||||
strategic_planning -- "选择超越路径 - 与伊娃一起进化" --> eva_transcendence
|
||||
suffering_philosophy -- "与所有人一起重新定义时间锚的目的" --> collective_redefinition
|
||||
suffering_philosophy -- "创建一个新的未来,基于理解而非恐惧" --> understanding_future
|
||||
suffering_philosophy -- "帮助每个人找到属于自己的道路" --> individual_path_guidance
|
||||
end
|
||||
subgraph cluster_restart[循环重启]
|
||||
restart_cycle_end["重新开始(进入下一次循环)"]
|
||||
end
|
||||
114
Documentation/mindmap/analyze_end_nodes.py
Normal file
114
Documentation/mindmap/analyze_end_nodes.py
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
|
||||
NODE_RE = re.compile(r'^\s*@node\s+(?P<id>[A-Za-z0-9_]+)\s*$')
|
||||
TITLE_RE = re.compile(r'^\s*@title\s+"(?P<title>.*)"\s*$')
|
||||
CHOICE_RE = re.compile(r'^\s*choice_\d+\s*:\s*"(?P<label>.*?)"\s*->\s*(?P<target>[A-Za-z0-9_]+)')
|
||||
|
||||
ENDING_ID_PREFIXES = (
|
||||
'ending_',
|
||||
)
|
||||
ENDING_TITLE_KEYWORDS = (
|
||||
'结局', '最终', '终极', '拯救', '守护', '和谐', '文明', '宽恕', '宇宙', '完美'
|
||||
)
|
||||
|
||||
|
||||
def parse_story(path: str) -> Tuple[Dict[str, str], Dict[str, List[Tuple[str, str]]]]:
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
nodes: Dict[str, str] = {}
|
||||
edges: Dict[str, List[Tuple[str, str]]] = {}
|
||||
|
||||
current_id: str = ''
|
||||
i = 0
|
||||
n = len(lines)
|
||||
while i < n:
|
||||
line = lines[i].rstrip('\n')
|
||||
m_node = NODE_RE.match(line)
|
||||
if m_node:
|
||||
current_id = m_node.group('id')
|
||||
if current_id not in nodes:
|
||||
nodes[current_id] = current_id
|
||||
if current_id not in edges:
|
||||
edges[current_id] = []
|
||||
|
||||
j = i + 1
|
||||
while j < n:
|
||||
l2 = lines[j].rstrip('\n')
|
||||
if NODE_RE.match(l2):
|
||||
break
|
||||
m_title = TITLE_RE.match(l2)
|
||||
if m_title:
|
||||
title = m_title.group('title').strip()
|
||||
if title:
|
||||
nodes[current_id] = title
|
||||
m_choice = CHOICE_RE.match(l2)
|
||||
if m_choice:
|
||||
label = m_choice.group('label').strip()
|
||||
target = m_choice.group('target').strip()
|
||||
edges.setdefault(current_id, []).append((label, target))
|
||||
j += 1
|
||||
|
||||
i = j
|
||||
continue
|
||||
i += 1
|
||||
|
||||
return nodes, edges
|
||||
|
||||
|
||||
def is_ending(node_id: str, title: str) -> bool:
|
||||
if any(node_id.startswith(p) for p in ENDING_ID_PREFIXES):
|
||||
return True
|
||||
return any(k in (title or '') for k in ENDING_TITLE_KEYWORDS)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('--input', required=True)
|
||||
ap.add_argument('--output', required=True)
|
||||
args = ap.parse_args()
|
||||
|
||||
nodes, edges = parse_story(args.input)
|
||||
|
||||
# Compute out-degree
|
||||
outdeg = {nid: len(edges.get(nid, [])) for nid in nodes.keys()}
|
||||
|
||||
leaf_nodes = [nid for nid, deg in outdeg.items() if deg == 0]
|
||||
|
||||
endings = []
|
||||
non_endings = []
|
||||
for nid in sorted(leaf_nodes):
|
||||
title = nodes.get(nid, '')
|
||||
(endings if is_ending(nid, title) else non_endings).append((nid, title))
|
||||
|
||||
os.makedirs(os.path.dirname(args.output), exist_ok=True)
|
||||
with open(args.output, 'w', encoding='utf-8') as f:
|
||||
f.write('# 无后续(无 choices)节点清单\n\n')
|
||||
f.write(f'- 总节点数: {len(nodes)}\n')
|
||||
f.write(f'- 无后续节点数: {len(leaf_nodes)}\n')
|
||||
f.write(f'- 结局型: {len(endings)}\n')
|
||||
f.write(f'- 可能未接续: {len(non_endings)}\n\n')
|
||||
|
||||
f.write('## 结局型(Leaf Endings)\n')
|
||||
for nid, title in endings:
|
||||
f.write(f'- {title} | id: `{nid}`\n')
|
||||
f.write('\n')
|
||||
|
||||
f.write('## 可能未接续(需人工确认是否应有后续)\n')
|
||||
for nid, title in non_endings:
|
||||
f.write(f'- {title} | id: `{nid}`\n')
|
||||
|
||||
print(f"Wrote report to {args.output}. Endings={len(endings)} NonEndings={len(non_endings)}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
536
Documentation/mindmap/generate_mermaid.py
Normal file
536
Documentation/mindmap/generate_mermaid.py
Normal file
@@ -0,0 +1,536 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
from typing import Dict, List, Tuple, Set
|
||||
|
||||
|
||||
NODE_RE = re.compile(r'^\s*@node\s+(?P<id>[A-Za-z0-9_]+)\s*$')
|
||||
TITLE_RE = re.compile(r'^\s*@title\s+"(?P<title>.*)"\s*$')
|
||||
CHOICE_RE = re.compile(
|
||||
r'^\s*choice_\d+\s*:\s*"(?P<label>.*?)"\s*->\s*(?P<target>[A-Za-z0-9_]+)'
|
||||
)
|
||||
|
||||
|
||||
# ---- Multi-page patterns & helpers ----
|
||||
# ID suffix patterns: foo_p2, foo_part2, foo_final
|
||||
SUFFIX_PATTERNS = [
|
||||
re.compile(r'^(?P<base>.+)_p\d+$'),
|
||||
re.compile(r'^(?P<base>.+)_part\d+$'),
|
||||
re.compile(r'^(?P<base>.+)_(?:final)$'),
|
||||
]
|
||||
|
||||
# Title pagination markers like (1/3) or (1/3)
|
||||
TITLE_PAGE_RE = re.compile(r'[(\(]\s*\d+\s*/\s*\d+\s*[)\)]')
|
||||
|
||||
# Edge labels considered as pagination/continuation and can be merged away
|
||||
CONTINUE_LABEL_PREFIXES: Tuple[str, ...] = (
|
||||
'继续阅读', '继续查看', '继续听', '继续'
|
||||
)
|
||||
|
||||
|
||||
def parse_story(path: str) -> Tuple[Dict[str, str], Dict[str, List[Tuple[str, str]]]]:
|
||||
"""
|
||||
Parse .story file and return:
|
||||
- nodes: id -> title
|
||||
- edges: src_id -> list of (label, target_id)
|
||||
"""
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
nodes: Dict[str, str] = {}
|
||||
edges: Dict[str, List[Tuple[str, str]]] = {}
|
||||
|
||||
current_id: str = ''
|
||||
i = 0
|
||||
n = len(lines)
|
||||
while i < n:
|
||||
line = lines[i].rstrip('\n')
|
||||
m_node = NODE_RE.match(line)
|
||||
if m_node:
|
||||
current_id = m_node.group('id')
|
||||
# Initialize node if not exists
|
||||
if current_id not in nodes:
|
||||
nodes[current_id] = current_id
|
||||
if current_id not in edges:
|
||||
edges[current_id] = []
|
||||
|
||||
# Scan until next @node or EOF, collecting title and choices
|
||||
j = i + 1
|
||||
while j < n:
|
||||
l2 = lines[j].rstrip('\n')
|
||||
# Stop when next node begins
|
||||
if NODE_RE.match(l2):
|
||||
break
|
||||
# Title
|
||||
m_title = TITLE_RE.match(l2)
|
||||
if m_title:
|
||||
title = m_title.group('title').strip()
|
||||
if title:
|
||||
nodes[current_id] = title
|
||||
# Choices -> edges
|
||||
m_choice = CHOICE_RE.match(l2)
|
||||
if m_choice:
|
||||
label = m_choice.group('label').strip()
|
||||
target = m_choice.group('target').strip()
|
||||
edges.setdefault(current_id, []).append((label, target))
|
||||
j += 1
|
||||
|
||||
# continue from j (next node or EOF)
|
||||
i = j
|
||||
continue
|
||||
i += 1
|
||||
|
||||
return nodes, edges
|
||||
|
||||
|
||||
def mermaid_escape(text: str) -> str:
|
||||
# Replace double quotes to avoid breaking labels
|
||||
return text.replace('"', "'")
|
||||
|
||||
|
||||
def extract_base_id(node_id: str) -> str:
|
||||
for pat in SUFFIX_PATTERNS:
|
||||
m = pat.match(node_id)
|
||||
if m:
|
||||
return m.group('base')
|
||||
return node_id
|
||||
|
||||
|
||||
class UnionFind:
|
||||
def __init__(self):
|
||||
self.parent: Dict[str, str] = {}
|
||||
|
||||
def find(self, x: str) -> str:
|
||||
if x not in self.parent:
|
||||
self.parent[x] = x
|
||||
if self.parent[x] != x:
|
||||
self.parent[x] = self.find(self.parent[x])
|
||||
return self.parent[x]
|
||||
|
||||
def union(self, a: str, b: str):
|
||||
ra, rb = self.find(a), self.find(b)
|
||||
if ra != rb:
|
||||
self.parent[rb] = ra
|
||||
|
||||
|
||||
def clean_title_for_display(title: str) -> str:
|
||||
# Remove pagination markers like (1/3)
|
||||
return TITLE_PAGE_RE.sub('', title).strip()
|
||||
|
||||
|
||||
def is_continue_label(label: str) -> bool:
|
||||
return any(label.startswith(prefix) for prefix in CONTINUE_LABEL_PREFIXES)
|
||||
|
||||
|
||||
def build_mermaid(nodes: Dict[str, str], edges: Dict[str, List[Tuple[str, str]]], *, root_id: str = 'first_awakening', detach_branches: bool = True) -> str:
|
||||
# Collect all referenced targets to add placeholder nodes if missing
|
||||
referenced = set()
|
||||
for src, lst in edges.items():
|
||||
for _label, tgt in lst:
|
||||
referenced.add(tgt)
|
||||
|
||||
# Add placeholders for undefined targets
|
||||
for tgt in referenced:
|
||||
if tgt not in nodes:
|
||||
nodes[tgt] = f"{tgt}"
|
||||
edges.setdefault(tgt, [])
|
||||
|
||||
# Derive incoming labels for placeholder nodes; use edge label as display title if node has no proper title
|
||||
incoming_labels: Dict[str, List[str]] = {}
|
||||
for src, lst in edges.items():
|
||||
for label, tgt in lst:
|
||||
if label:
|
||||
incoming_labels.setdefault(tgt, []).append(label)
|
||||
for node_id, title in list(nodes.items()):
|
||||
if title.strip() == node_id.strip():
|
||||
labels = incoming_labels.get(node_id, [])
|
||||
if labels:
|
||||
# pick the shortest non-empty label as node title
|
||||
best = sorted((l for l in labels if l.strip()), key=lambda s: len(s))[0]
|
||||
nodes[node_id] = best
|
||||
|
||||
# -------- Merge multi-page nodes and continuation edges --------
|
||||
uf = UnionFind()
|
||||
|
||||
# 1) Merge by suffix patterns (id-based grouping)
|
||||
for node_id in list(nodes.keys()):
|
||||
base = extract_base_id(node_id)
|
||||
if base != node_id:
|
||||
uf.union(base, node_id)
|
||||
|
||||
# 2) Merge by edges labeled as continuation (e.g., 继续阅读)
|
||||
for src, lst in edges.items():
|
||||
for label, tgt in lst:
|
||||
if is_continue_label(label):
|
||||
uf.union(src, tgt)
|
||||
# 3) Also merge if base ids are identical (pagination chain)
|
||||
if extract_base_id(src) == extract_base_id(tgt) and src != tgt:
|
||||
uf.union(src, tgt)
|
||||
|
||||
# Build groups
|
||||
groups: Dict[str, List[str]] = {}
|
||||
for node_id in nodes.keys():
|
||||
rep = uf.find(node_id)
|
||||
groups.setdefault(rep, []).append(node_id)
|
||||
|
||||
# Choose representative id per group (prefer base id of representative)
|
||||
group_id_map: Dict[str, str] = {}
|
||||
for rep, members in groups.items():
|
||||
base_rep = extract_base_id(rep)
|
||||
group_id_map[rep] = base_rep
|
||||
|
||||
# Map every node to its group representative id
|
||||
node_to_group: Dict[str, str] = {}
|
||||
for rep, members in groups.items():
|
||||
gid = group_id_map[rep]
|
||||
for m in members:
|
||||
node_to_group[m] = gid
|
||||
|
||||
# Compute group titles (prefer a member title without pagination marker)
|
||||
group_titles: Dict[str, str] = {}
|
||||
for rep, members in groups.items():
|
||||
gid = group_id_map[rep]
|
||||
chosen = ''
|
||||
# Prefer a member whose title does not contain (x/y)
|
||||
for m in members:
|
||||
t = nodes.get(m, '').strip()
|
||||
if t and not TITLE_PAGE_RE.search(t):
|
||||
chosen = t
|
||||
break
|
||||
if not chosen:
|
||||
# Fallback to any non-empty title
|
||||
for m in members:
|
||||
t = nodes.get(m, '').strip()
|
||||
if t:
|
||||
chosen = t
|
||||
break
|
||||
if not chosen:
|
||||
# Final fallback: use group id
|
||||
chosen = gid
|
||||
group_titles[gid] = clean_title_for_display(chosen)
|
||||
|
||||
# Rebuild edges at group level, dropping pagination edges and self-loops
|
||||
new_edges: Set[Tuple[str, str, str]] = set()
|
||||
for src, lst in edges.items():
|
||||
gsrc = node_to_group[src]
|
||||
for label, tgt in lst:
|
||||
gtgt = node_to_group[tgt]
|
||||
if gsrc == gtgt:
|
||||
continue # internal after merge
|
||||
if is_continue_label(label):
|
||||
continue # drop continuation edges
|
||||
elabel = mermaid_escape(label)
|
||||
new_edges.add((gsrc, elabel, gtgt))
|
||||
|
||||
# -------- Redirect loops back to root → to a dedicated restart node --------
|
||||
RESTART_NODE_ID = 'restart_cycle_end'
|
||||
RESTART_TITLE = '重新开始(进入下一次循环)'
|
||||
redirected_edges: Set[Tuple[str, str, str]] = set()
|
||||
for (s, l, t) in new_edges:
|
||||
if t == root_id:
|
||||
redirected_edges.add((s, '重新开始', RESTART_NODE_ID))
|
||||
else:
|
||||
redirected_edges.add((s, l, t))
|
||||
new_edges = redirected_edges
|
||||
# Register restart node in titles map
|
||||
group_titles[RESTART_NODE_ID] = RESTART_TITLE
|
||||
|
||||
# Build adjacency on merged graph
|
||||
adj: Dict[str, List[Tuple[str, str]]] = {}
|
||||
rev: Dict[str, List[Tuple[str, str]]] = {}
|
||||
indeg: Dict[str, int] = {gid: 0 for gid in group_titles.keys()}
|
||||
outdeg: Dict[str, int] = {gid: 0 for gid in group_titles.keys()}
|
||||
for s, l, t in new_edges:
|
||||
adj.setdefault(s, []).append((l, t))
|
||||
rev.setdefault(t, []).append((l, s))
|
||||
outdeg[s] = outdeg.get(s, 0) + 1
|
||||
indeg[t] = indeg.get(t, 0) + 1
|
||||
|
||||
# Reachable set from root
|
||||
main_nodes: Set[str] = set()
|
||||
if root_id in group_titles:
|
||||
q: List[str] = [root_id]
|
||||
while q:
|
||||
u = q.pop(0)
|
||||
if u in main_nodes:
|
||||
continue
|
||||
main_nodes.add(u)
|
||||
for _l, v in adj.get(u, []):
|
||||
if v not in main_nodes:
|
||||
q.append(v)
|
||||
else:
|
||||
# Fallback: include all nodes if root not found
|
||||
main_nodes = set(group_titles.keys())
|
||||
|
||||
# Compute depth (shortest hops) from root within reachable graph
|
||||
depth: Dict[str, int] = {}
|
||||
if root_id in group_titles:
|
||||
from collections import deque
|
||||
dq = deque()
|
||||
dq.append(root_id)
|
||||
depth[root_id] = 0
|
||||
while dq:
|
||||
u = dq.popleft()
|
||||
du = depth[u]
|
||||
for _l, v in adj.get(u, []):
|
||||
if v not in depth:
|
||||
depth[v] = du + 1
|
||||
dq.append(v)
|
||||
|
||||
# Identify branch roots (heuristic)
|
||||
ENDING_KEYWORDS = (
|
||||
'结局', '最终', '终极', '共存', '宽恕', '拯救', '新文明', '宇宙', '学院', '网络', '守护', '转型', '推广'
|
||||
)
|
||||
def is_branch_title(title: str) -> bool:
|
||||
return any(k in title for k in ENDING_KEYWORDS)
|
||||
|
||||
branch_roots: Set[str] = set()
|
||||
if detach_branches:
|
||||
# Force-split branch roots by id/title keywords
|
||||
FORCED_BRANCH_IDS = {
|
||||
'eva_identity_revelation', 'hidden_marks_discovery', 'find_sara'
|
||||
}
|
||||
FORCED_BRANCH_TITLE_KEYWORDS = (
|
||||
'伊娃的身份揭示', '身份揭示', '植入物', '痕迹', '寻找萨拉', '第48次循环的完美策略'
|
||||
)
|
||||
|
||||
for gid, title in group_titles.items():
|
||||
if gid == root_id:
|
||||
continue
|
||||
# candidate if titled like an ending cluster or low indegree/high outdegree leaf-ish
|
||||
if is_branch_title(title) and gid in main_nodes:
|
||||
branch_roots.add(gid)
|
||||
|
||||
# Also add nodes with id starting patterns often used for endings
|
||||
for gid in list(main_nodes):
|
||||
if gid.startswith('ending_') or gid in (
|
||||
'coexistence_path', 'coexistence_path_p2',
|
||||
'healing_civilization_final', 'harmony_guardians_final',
|
||||
'reality_guardians', 'cosmic_guardians_ending', 'universal_rescue_ending'
|
||||
):
|
||||
branch_roots.add(gid)
|
||||
|
||||
# Add forced branch roots (by id or title keywords), regardless of heuristics
|
||||
for gid, title in group_titles.items():
|
||||
if gid in FORCED_BRANCH_IDS:
|
||||
branch_roots.add(gid)
|
||||
else:
|
||||
if any(kw in title for kw in FORCED_BRANCH_TITLE_KEYWORDS):
|
||||
branch_roots.add(gid)
|
||||
|
||||
# Heuristic: deep and weakly-connected nodes become new branch roots
|
||||
DEPTH_THRESHOLD = 6
|
||||
MAX_EDGES_FROM_MAIN = 1
|
||||
MAX_EDGES_TO_MAIN = 1
|
||||
for gid in list(main_nodes):
|
||||
if gid == root_id:
|
||||
continue
|
||||
d = depth.get(gid, 0)
|
||||
if d < DEPTH_THRESHOLD:
|
||||
continue
|
||||
in_from_main = sum(1 for _l, s in rev.get(gid, []) if s in main_nodes)
|
||||
out_to_main = sum(1 for _l, t in adj.get(gid, []) if t in main_nodes)
|
||||
if in_from_main <= MAX_EDGES_FROM_MAIN and out_to_main <= MAX_EDGES_TO_MAIN:
|
||||
branch_roots.add(gid)
|
||||
|
||||
# Grow branch clusters from roots
|
||||
branch_clusters: List[Tuple[str, Set[str]]] = [] # (root, nodes)
|
||||
used_in_cluster: Set[str] = set()
|
||||
for br in sorted(branch_roots):
|
||||
if br in used_in_cluster:
|
||||
continue
|
||||
cluster: Set[str] = set()
|
||||
q = [br]
|
||||
while q:
|
||||
u = q.pop(0)
|
||||
if u in cluster:
|
||||
continue
|
||||
cluster.add(u)
|
||||
for _l, v in adj.get(u, []):
|
||||
# avoid pulling back too many main nodes: keep expansion to weakly connected area
|
||||
in_from_outside = sum(1 for _l, s in rev.get(v, []) if s not in cluster and s in main_nodes)
|
||||
if v not in cluster and in_from_outside <= (MAX_EDGES_FROM_MAIN + 1):
|
||||
q.append(v)
|
||||
if len(cluster) >= 3:
|
||||
branch_clusters.append((br, cluster))
|
||||
used_in_cluster |= cluster
|
||||
|
||||
# Remove cluster nodes from main_nodes
|
||||
for _br, cset in branch_clusters:
|
||||
main_nodes -= cset
|
||||
|
||||
# Build label augmentation mapping for main nodes that lead to clusters
|
||||
jump_tips: Dict[str, List[str]] = {}
|
||||
for s, l, t in new_edges:
|
||||
# Edge from main to cluster root -> add jump tip
|
||||
for br, cset in branch_clusters:
|
||||
if s in main_nodes and t in cset:
|
||||
tip = f"跳转分支:{group_titles[br]}"
|
||||
jump_tips.setdefault(s, [])
|
||||
if tip not in jump_tips[s]:
|
||||
jump_tips[s].append(tip)
|
||||
|
||||
# Build Mermaid text
|
||||
lines: List[str] = []
|
||||
lines.append("%%{init: {'flowchart': {'htmlLabels': true}}}%%")
|
||||
lines.append('flowchart TD')
|
||||
lines.append('')
|
||||
|
||||
# Main cluster
|
||||
lines.append(' subgraph cluster_main[主线:第一次觉醒]')
|
||||
for gid in sorted(main_nodes):
|
||||
title = group_titles[gid]
|
||||
extra = ''
|
||||
if gid in jump_tips:
|
||||
extra = '\n' + '、'.join(jump_tips[gid])
|
||||
label = mermaid_escape(title + extra)
|
||||
lines.append(f' {gid}["{label}"]')
|
||||
# Main edges
|
||||
for (s, l, t) in sorted(new_edges):
|
||||
if s in main_nodes and t in main_nodes:
|
||||
if l:
|
||||
lines.append(f' {s} -- "{l}" --> {t}')
|
||||
else:
|
||||
lines.append(f' {s} --> {t}')
|
||||
lines.append(' end')
|
||||
|
||||
# Branch clusters with optional stage subgraphs by depth buckets to avoid overcrowding
|
||||
for idx, (br, nodeset) in enumerate(branch_clusters, start=1):
|
||||
title = group_titles[br]
|
||||
lines.append(f' subgraph cluster_branch_{idx}[分支:{mermaid_escape(title)}]')
|
||||
|
||||
# Special handling: 二次拆分“伊娃的身份揭示”大分支
|
||||
identity_ids = { 'eva_identity_revelation', 'eva_revelation' }
|
||||
emo_ids = { 'emotional_reunion', 'memory_sharing', 'identity_exploration', 'rescue_planning' }
|
||||
quantum_ids = { 'consciousness_communication', 'consciousness_integration', 'eva_quantum_link' }
|
||||
|
||||
is_identity_branch = (br in identity_ids) or ('身份' in title and '揭示' in title)
|
||||
|
||||
if is_identity_branch:
|
||||
# Build subsets by id
|
||||
subset_identity = {gid for gid in nodeset if gid in identity_ids}
|
||||
subset_emo = {gid for gid in nodeset if gid in emo_ids}
|
||||
subset_quantum = {gid for gid in nodeset if gid in quantum_ids}
|
||||
subset_other = nodeset - subset_identity - subset_emo - subset_quantum
|
||||
|
||||
# Prepare jump tips within this branch
|
||||
intra_jump_tips: Dict[str, List[str]] = {}
|
||||
def add_tip(src_gid: str, tip: str):
|
||||
intra_jump_tips.setdefault(src_gid, [])
|
||||
if tip not in intra_jump_tips[src_gid]:
|
||||
intra_jump_tips[src_gid].append(tip)
|
||||
|
||||
# Print subgraphs
|
||||
def print_subset(name: str, subset: Set[str]):
|
||||
if not subset:
|
||||
return
|
||||
lines.append(f' subgraph cluster_branch_{idx}_{name}[{name}]')
|
||||
for gid in sorted(subset):
|
||||
extra = ''
|
||||
if gid in intra_jump_tips:
|
||||
extra = '\n' + '、'.join(intra_jump_tips[gid])
|
||||
label = mermaid_escape(group_titles[gid] + extra)
|
||||
lines.append(f' {gid}["{label}"]')
|
||||
# internal edges
|
||||
for (s, l, t) in sorted(new_edges):
|
||||
if s in subset and t in subset:
|
||||
if l:
|
||||
lines.append(f' {s} -- "{l}" --> {t}')
|
||||
else:
|
||||
lines.append(f' {s} --> {t}')
|
||||
lines.append(' end')
|
||||
|
||||
# Collect cross-subset edges to convert into jump tips (从身份主簇指向其他簇)
|
||||
for (s, l, t) in sorted(new_edges):
|
||||
if s in subset_identity and t in subset_emo:
|
||||
add_tip(s, '跳转分支:情感-记忆')
|
||||
if s in subset_identity and t in subset_quantum:
|
||||
add_tip(s, '跳转分支:意识-量子')
|
||||
|
||||
# Render subsets
|
||||
print_subset('身份揭示主簇', subset_identity or {br})
|
||||
print_subset('情感-记忆', subset_emo)
|
||||
print_subset('意识-量子', subset_quantum)
|
||||
if subset_other:
|
||||
print_subset('其他', subset_other)
|
||||
|
||||
# 不渲染跨子簇边,避免拥挤
|
||||
lines.append(' end')
|
||||
continue
|
||||
|
||||
# ---------- 默认:按深度 stage 分桶 ----------
|
||||
# compute local depths from branch root
|
||||
from collections import deque
|
||||
local_depth: Dict[str, int] = {br: 0}
|
||||
dq = deque([br])
|
||||
while dq:
|
||||
u = dq.popleft()
|
||||
du = local_depth[u]
|
||||
for _l, v in adj.get(u, []):
|
||||
if v in nodeset and v not in local_depth:
|
||||
local_depth[v] = du + 1
|
||||
dq.append(v)
|
||||
|
||||
# bucket nodes by depth
|
||||
buckets: Dict[int, List[str]] = {}
|
||||
for n in nodeset:
|
||||
d = local_depth.get(n, 0)
|
||||
buckets.setdefault(d, []).append(n)
|
||||
|
||||
for stage in sorted(buckets.keys()):
|
||||
lines.append(f' subgraph cluster_branch_{idx}_stage_{stage}[stage {stage}]')
|
||||
for gid in sorted(buckets[stage]):
|
||||
label = mermaid_escape(group_titles[gid])
|
||||
lines.append(f' {gid}["{label}"]')
|
||||
# stage internal edges
|
||||
for (s, l, t) in sorted(new_edges):
|
||||
if s in buckets[stage] and t in buckets[stage]:
|
||||
if l:
|
||||
lines.append(f' {s} -- "{l}" --> {t}')
|
||||
else:
|
||||
lines.append(f' {s} --> {t}')
|
||||
lines.append(' end')
|
||||
|
||||
# cross-stage edges within the branch
|
||||
for (s, l, t) in sorted(new_edges):
|
||||
if s in nodeset and t in nodeset and local_depth.get(s, 0) != local_depth.get(t, 0):
|
||||
if l:
|
||||
lines.append(f' {s} -- "{l}" --> {t}')
|
||||
else:
|
||||
lines.append(f' {s} --> {t}')
|
||||
|
||||
lines.append(' end')
|
||||
|
||||
# Restart cluster (sink)
|
||||
lines.append(f' subgraph cluster_restart[循环重启]')
|
||||
lines.append(f' {RESTART_NODE_ID}["{mermaid_escape(RESTART_TITLE)}"]')
|
||||
lines.append(' end')
|
||||
|
||||
lines.append('')
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--input', required=True)
|
||||
parser.add_argument('--output', required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
nodes, edges = parse_story(args.input)
|
||||
# Force root as first_awakening; detach branches by default
|
||||
content = build_mermaid(nodes, edges, root_id='first_awakening', detach_branches=True)
|
||||
|
||||
os.makedirs(os.path.dirname(args.output), exist_ok=True)
|
||||
with open(args.output, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
print(f"Generated Mermaid with {len(nodes)} nodes and {sum(len(v) for v in edges.values())} edges → {args.output}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
31
Documentation/mindmap/leaf_nodes_report.md
Normal file
31
Documentation/mindmap/leaf_nodes_report.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 无后续(无 choices)节点清单
|
||||
|
||||
- 总节点数: 209
|
||||
- 无后续节点数: 21
|
||||
- 结局型: 4
|
||||
- 可能未接续: 17
|
||||
|
||||
## 结局型(Leaf Endings)
|
||||
- 伊娃的最终超越 | id: `eva_transcendence_ending`
|
||||
- 地球的最终拯救(3/3) | id: `healing_civilization_final_p3`
|
||||
- 反应堆最终对峙(4/4) | id: `reactor_confrontation_path_p4`
|
||||
- 和谐守护者的永恒使命(2/2) | id: `universal_rescue_ending_p2`
|
||||
|
||||
## 可能未接续(需人工确认是否应有后续)
|
||||
- 控制室对峙(3/3) | id: `crew_confrontation_control_room_p3`
|
||||
- 意识进化的发现 | id: `data_extraction_final`
|
||||
- 直接对峙(2/2) | id: `direct_confrontation_p2`
|
||||
- 证据摊牌 | id: `dmitri_evidence_reveal`
|
||||
- 德米特里的个人动机(2/2) | id: `dmitri_personal_reveal_p2`
|
||||
- 伊娃身份的深度揭示(3/3) | id: `eva_identity_revelation_p3`
|
||||
- eva_photo_reaction_p3 | id: `eva_photo_reaction_p3`
|
||||
- 学习生命的艺术 | id: `garden_learning`
|
||||
- 花园伙伴关系(2/2) | id: `garden_partnership_p2`
|
||||
- 医疗录音的深层真相(2/2) | id: `medical_recording_revelation_p2`
|
||||
- 记忆重建(3/3) | id: `memory_reconstruction_p3`
|
||||
- 哲学思辨(2/2) | id: `philosophical_discussion_p2`
|
||||
- 量子徽章的深度分析 | id: `quantum_badge_analysis`
|
||||
- 反应堆破坏尝试(续) | id: `reactor_sabotage_attempt_p2`
|
||||
- repetition_memory_analysis_p2 | id: `repetition_memory_analysis_p2`
|
||||
- 系统破坏(2/2) | id: `system_sabotage_p2`
|
||||
- 时间线深度分析(4/4) | id: `timeline_analysis_p4`
|
||||
Reference in New Issue
Block a user