diff --git a/Assets/Editor/DeveloperMode/TestSaveSystemTests.cs b/Assets/Editor/DeveloperMode/TestSaveSystemTests.cs index dc1206359..57537d9c9 100644 --- a/Assets/Editor/DeveloperMode/TestSaveSystemTests.cs +++ b/Assets/Editor/DeveloperMode/TestSaveSystemTests.cs @@ -287,6 +287,109 @@ namespace AibisDream.DeveloperMode.Editor.Tests Assert.That(exit.AnchorSpec.StartDialogueOnRestore, Is.False); } + [TestCase(false, "DetourNode", true)] + [TestCase(false, "NoSaveNode", false)] + [TestCase(true, "AnotherNode", true)] + public void ValidateDialogueTiming_NonExitRequestIgnoresLaterDialogueState( + bool explicitCommand, + string currentNodeName, + bool isDialogueRunning) + { + var request = explicitCommand + ? SaveRequest.Explicit("SourceNode", "ProjectA", "ChapterA", 3, 7) + : SaveRequest.NodeEnter("SourceNode", "ProjectA", "ChapterA", 3, 7); + + var valid = SaveRestoreOrchestrator.ValidateDialogueTiming( + request, + currentDialogueRunId: 99, + currentFlowRevision: 101, + currentNodeName: currentNodeName, + isDialogueRunning: isDialogueRunning, + out var reason); + + Assert.That(valid, Is.True, reason); + Assert.That(request.AnchorSpec.NodeName, Is.EqualTo("SourceNode")); + } + + [Test] + public void ValidateDialogueTiming_ConsecutiveNodeEnterRequestsRemainEligible() + { + var first = SaveRequest.NodeEnter("FirstNode", "ProjectA", "ChapterA", 3, 7); + var second = SaveRequest.NodeEnter("SecondNode", "ProjectA", "ChapterA", 3, 8); + + var firstValid = SaveRestoreOrchestrator.ValidateDialogueTiming( + first, + currentDialogueRunId: 3, + currentFlowRevision: 8, + currentNodeName: "SecondNode", + isDialogueRunning: true, + out var firstReason); + var secondValid = SaveRestoreOrchestrator.ValidateDialogueTiming( + second, + currentDialogueRunId: 3, + currentFlowRevision: 8, + currentNodeName: "SecondNode", + isDialogueRunning: true, + out var secondReason); + + Assert.That(firstValid, Is.True, firstReason); + Assert.That(secondValid, Is.True, secondReason); + Assert.That(first.AnchorSpec.NodeName, Is.EqualTo("FirstNode")); + Assert.That(second.AnchorSpec.NodeName, Is.EqualTo("SecondNode")); + } + + [Test] + public void ValidateDialogueTiming_DialogueExitAcceptsStableCompletedFlow() + { + var request = SaveRequest.DialogueExit( + "InteractionNode", + "ProjectA", + "ChapterA", + 3, + 9); + + var valid = SaveRestoreOrchestrator.ValidateDialogueTiming( + request, + currentDialogueRunId: 3, + currentFlowRevision: 9, + currentNodeName: null, + isDialogueRunning: false, + out var reason); + + Assert.That(valid, Is.True, reason); + } + + [TestCase(4, 9, null, false, "dialogue run changed")] + [TestCase(3, 10, null, false, "flow revision changed")] + [TestCase(3, 9, "DetourNode", false, "current Yarn node")] + [TestCase(3, 9, "NoSaveNode", false, "current Yarn node")] + [TestCase(3, 9, "JumpTarget", true, "still running")] + public void ValidateDialogueTiming_DialogueExitRejectsChangedOrRunningFlow( + long currentDialogueRunId, + long currentFlowRevision, + string currentNodeName, + bool isDialogueRunning, + string expectedReason) + { + var request = SaveRequest.DialogueExit( + "InteractionNode", + "ProjectA", + "ChapterA", + 3, + 9); + + var valid = SaveRestoreOrchestrator.ValidateDialogueTiming( + request, + currentDialogueRunId, + currentFlowRevision, + currentNodeName, + isDialogueRunning, + out var reason); + + Assert.That(valid, Is.False); + Assert.That(reason, Does.Contain(expectedReason)); + } + [Test] public void InteractionLock_ComposesWithDialogueLock() { diff --git a/Assets/Font/Assets/ChillBitmap_16px SDF.asset b/Assets/Font/Assets/ChillBitmap_16px SDF.asset index 96c5ebe5e..bd600ebdb 100644 --- a/Assets/Font/Assets/ChillBitmap_16px SDF.asset +++ b/Assets/Font/Assets/ChillBitmap_16px SDF.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe2a40b3de3d36e7f9d10a5bc7d494f3b5b6250447ba19843b9d0f722579c43a -size 18724862 +oid sha256:dba84383774998b357669a0616d6df58ffaf7d8d8548374b4478645372762472 +size 18727475 diff --git a/Assets/Font/字符统计.txt b/Assets/Font/字符统计.txt index 0af1befac..89a7e7843 100644 --- a/Assets/Font/字符统计.txt +++ b/Assets/Font/字符统计.txt @@ -89,123 +89,123 @@ rstuvwxyz{|}~̀́̂̃̄̅̆ 挨挪挫振挺挽捂捅捆捉捋捌捍捎捏捐捕捜捞损 捡换捣捧据捶捷捺捻掀掂授掉掌掏掐排掘掠探 接控推掩措掰掷掺揉揍描提插換握揣揩揪揭援 -揺揽搀搁搂搅損搏搓搔搜搞搬搭携摄摆摇摊摔 -摘摧摩摸摹撇撑撒撕撞撤撩撬播撮撰撵撸撼擂 -擅操擎擒擦攀攒攥支收改攻放政故效敌敏救敗 -教敛敞敢散敦敬数敲整敵敷文斋斌斑斗料斜斟 -斤斥斧斩断斯新方施旁旅旋族旗无既日旦旧旨 -早旬旭旱时旷旺昂昆昌明昏易昔星映春昧昨昭 -是昼显時晃晋晌晒晓晕晚晦晨晩普景晰晴晶智 -晾暂暇暑暖暗暮暴曙曝曰曲更書曹曼曾替最月 -有朋服朔朗望朝期朦木未末本术朱朴朵机朽杀 -杂权杆杉李杏材村杖杜束杠条来杨杭杯杰東松 -板极构枉析枕林枚果枝枠枢枣枪枫枯架柄柏某 -柑柒染柔柜柠查柬柱柳柴査柿栅标栈栋栏树栓 -栖栗校株样核根格栽桂桃框案桌桐桑桔档桥桦 -桨桩桶梁梅梆梗梢梦梧梨梭梯械梳检棉棋棍棒 -棕棘棚棠森棱棵棺椅植椎椒検椭椰椿楚業楷楼 -楽概榄榆榔榕榜榨榴構槐様槛槽標樟模権横樱 -橄橘橙機橡橱檀檐檬欠次欢欣欧欲欺款歇歉歌 -止正此步武歧歩歪歹死歼殃殉殊残殖殴段殷殺 -殿毁毅母毎每毒比毕毙毛毡毫毯氏民氓气気氛 -氢氧氨氮氯水永汁求汇汉汐汗汚汛汝汞江池污 -汤汪汰汹決汽沁沃沈沉沐沙沛沟没沢沥沦沧沪 -沫沮河沸油治沼沽沾沿況泄泉泊泌法泛泞泡波 -泣泥注泪泰泳泵泻泼泽洁洋洒洗洛洞津洩洪洲 -活洼洽派流浅浆浇浊测济浏浑浓浙浦浩浪浮浴 -海浸涂消涉涌涕涛涝涡涣涤润涧涨涩涮涯液涵 -淀淆淋淌淑淘淡淤淫淮深淳混淹添清済渉渊渐 -渔渗渝渠渡渣渤温港渲渴游渺湃湖湘湾湿溃溅 -溉源準溜溢溪溯溶溺滇滋滑滔滚滞满滤滥滨滩 -滴漂漆漏漓演漠漫漱漾潇潘潜潤潭潮澄澈澎澜 -澡澳激濒瀑灌火灭灯灰灵灶灸灼灾灿炉炊炎炒 -炕炫炬炭炮炸点炼烁烂烈烘烙烛烟烤烦烧烫热 -烹焉焊焕焚無焦焰然焼煌煎煙煞煤照煩煮煽熄 -熊熏熔熙熟熬熱燃燕燥爆爪爬爱爵父爷爸爹爽 -片版牌牙牛牡牢牧物牲牵特牺犀犁犬犯状犹狂 -狈狐狗狞狠狡独狭狮狰狱狸狼猎猖猛猜猩猪猫 -猬献猴猾猿玄率玉王玖玛玩玫环现玲玷玻珊珍 -珠班現球琅理琉琐琢琳琴琼瑙瑞瑟瑰璃璧環瓜 -瓢瓣瓤瓦瓶瓷甘甚甜生産甥用甩甫田由甲申电 -男甸町画畅界畏畔留畜略番異畴畸疆疏疑疗疙 -疚疟疤疫疮疯疲疹疼疾病症痊痒痕痘痛痢痪痰 -痴痹瘟瘤瘦瘩瘪瘫瘸瘾療癌癣癸発登白百皂的 -皆皇皓皖皮皱皺皿盆盈益盏盐监盒盔盖盗盘盛 -盟目盯盲直相盹盼盾省眉看真眠眨眩眯眶眷眺 -眼着睁睐睛睡督睦睫睬睹瞄瞅瞌瞎瞒瞥瞧瞩瞪 -瞬瞭瞳瞻矗矛矢矣知矩矫短矮石矾矿码砂砌砍 -研砕砖砚砰破砸砾础硅硕硝硫硬确碌碍碎碑碗 -碘碟碧碰碱碳確碾磁磅磊磕磨磷礁示礼社祀祈 -祖祝神祟祠祥票祭祷祸禀禁禄禅福禹离禽禾秀 -私秃秆秉秋种科秒秘租秤秦秧秩积称秸移秽稀 -程稍税稚稠稳稻稼稽稿穆積穏穗穴究穷空穿突 -窃窄窍窑窒窓窖窗窘窜窝窟窣窥窸窿立竖站竞 -竟章竣童竭端竹竿笋笑笔笙笛符笨第笺笼筆等 -筋筏筐筑筒答策筛筝筷筹签简箕算管箩箫箭箱 -節範篇篓篡篮篱篷簇簡簧簸簿籍米类籽粉粋粒 -粗粘粟粤粥粪粮粱粹精糊糕糖糙糟糠糯系紊純 -素索紧紫累細終組経結絮絵絶継続綺緑緒線練 -縛縮績繁繋繰纠红纤约级纪纫纬纯纱纲纳纵纷 -纸纹纺纽线练组绅细织终绊绍绎经绑绒结绕绘 -给绚络绝绞统绢绣继绩绪续绰绳维绵绷绸综绽 -绿缀缅缆缉缎缓缔缕编缘缚缝缠缤缩缭缰缴缶 -缸缺罐网罕罗罚罢罩罪置署羊美羔羚羞羡群羹 -羽翁翅翔翘翠翩翰翻翼耀老考者而耍耐耕耗耘 -耙耦耳耷耸耻耽耿聂聆聊聋职联聘聚聞聪肃肆 -肇肉肋肌肖肘肚肛肝肠股肢肤肥肩肪肮肯育肴 -肺肾肿胀胁胃胆背胎胖胚胜胞胡胧胰胳胶胸能 -脂脆脉脊脏脐脑脓脖脚脯脱脳脸脾腊腋腐腔腕 -腥腮腰腹腺腻腾腿膀膊膏膛膜膝膨臀臂臊臣自 -臭至致臼舀舅舆興舌舍舎舒舔舞舟航般舰舱舵 -舶船艇艘良艰色艳艺艾节芋芒芙芜芝芥芦芬芭 -芯花芳芸芹芽苇苍苏苑苔苗苛苞苟若苦英苹茁 -茂范茄茅茉茎茧茫茬茵茶茸荆草荐荒荔荡荣荤 -荧荫药荷莉莫莱莲获莹莺莽菇菊菌菜菠菩菱菲 -萄萌萍萎萝萤营萧萨落葉著葛葡董葫葬葱葵蒂 -蒋蒙蒜蒲蒸蓄蓉蓝蓬蔑蔓蔗蔚蔡蔬蔵蔼蔽蕉蕊 -蕴蕾薄薇薛薪薬薯藉藏藐藕藤藻蘑虎虏虐虑虚 -虫虹虽虾蚀蚁蚂蚊蚌蚓蚕蚣蚤蚪蚯蛀蛇蛋蛙蛛 -蛤蛮蛾蜀蜂蜈蜒蜓蜕蜗蜘蜜蜡蜻蝇蝉蝌蝎蝗蝙 -蝠蝴蝶螃融螺蟀蟆蟋蟹蠕蠢血衅行衍術衔街衙 -衝衡衣补表衫衬衰衷袁袄袋袍袖袜被袭袱裁裂 -装裏裕裙補裤裳裸裹褂褄複褐褒褥褪襟西要覆 -見規視覗覚覧観见观规觅视览觉角解触言計訓 -記訝設許診証評試詰話該詳誉誊認誓語説読誰 -調請論謝識警譬議计订认讥讨让训议讯记讲讳 -讶许讹论讼讽设访诀证评识诈诉诊词译试诗诚 -话诞诡询该详诫诬语误诱诲说诵请诸诺读诽课 -谁调谅谆谈谊谋谍谎谐谓谚谜谢谣谤谦谨谬谭 -谱谴谷豁豆豊豌豚象豪豫豹豺貌負費貼資質贅 -贝贞负贡财责贤败账货质贩贪贫贬购贮贯贰贱 -贴贵贷贸费贺贻贼贾贿赁赂赃资赋赌赎赏赐赔 -赖赘赚赛赞赠赡赢赣赤赦赫走赴赵赶起趁超越 -趋趟趣足趴趾跃跋跌跑跛距跟跡跤跨跪路跳践 -跷跺踊踏踝踢踩踪踮踱蹂蹄蹈蹋蹦蹬蹭蹲躁躏 -身躬躯躲躺車転軽載輪轄车轧轨轩转轮软轰轱 -轴轻载轿较辅辆辈辉辐辑输辖辗辘辙辛辜辞辟 -辣辨辩辫辰辱边辺辻込辽达辿迁迂迄迅过迈迎 -运近返还这进远违连迟迢迪迫迭述迷迹追退送 -适逃逆选逊透逐递途逗通逛逝逞速造逢連逮逸 -逻逼逾遂遅遇運遍過遏道違遗遠遣遥遭遮遵選 -避邀邑邓那邦邪邮邻郁郊郎郑部郭都鄂鄙酉酌 -配酒酗酝酣酥酪酬酱酵酷酸酿醇醉醋醒采释里 -重野量金針鉢鉴鋼錆録鏡针钉钓钙钝钞钟钠钢 -钥钦钧钩钮钱钳钻钾铁铃铅铆铐铛铜铝铬铭铲 -银铸铺链销锁锄锅锈锋锌锐错锚锡锣锤锥锦键 -锯锰锹锻镀镇镍镐镑镜镰镶長长門閉開間関閲 -闇门闪闭问闯闰闲间闷闸闹闺闻闽阀阁阅阈阎 -阐阔队阱防阳阴阵阶阻阿附际陆陈陋陌降限陕 -陡院除陥陨险陪陵陶陷陽隅隆隋階随隐隔隘隙 -障隠隧隶难雀雁雄雅集雇雌雏雕離難雨雪雲雳 -零雷雹雾需霄震霉霍霎霓霜霞霧露霸霹青靖静 -非靠靡面革靴靶鞋鞍鞠鞭韦韧韩韭音韵響頃項 -頑頭頼題額顔願類页顶顷项顺须顽顾顿颁颂预 -颅领颇颈颊频颓颖颗题颜额颠颤風风飕飘飛飞 -食飲餐館饥饭饮饰饱饲饵饶饺饼饿馁馅馆馈馋 -馍馏馒首香馨駄駆騒马驮驯驰驱驳驴驶驹驻驼 -驾骂骄骆骇验骏骑骗骚骡骤骨髓高鬓鬼魁魂魄 -魅魏魔魚鮮鱼鲁鲍鲜鲤鲨鲫鲸鳄鳍鳖鳞鳥鴉鸚 -鸟鸡鸣鸥鸦鸭鸯鸳鸵鸽鸿鹃鹅鹉鹊鹏鹤鹦鹰鹿 -麗麦麻黄黎黑黒黔默黙黯鼎鼓鼠鼻齐齿龄龙龟 -갚계귨꾣뇤덅됬뚳램묈뷴뼭섉솳숅쉪싧썮썹쐣 -펽풆퓳퓵!(),:;?� +揺揽搀搁搂搅損搏搐搓搔搜搞搬搭携摄摆摇摊 +摔摘摧摩摸摹撇撑撒撕撞撤撩撬播撮撰撵撸撼 +擂擅操擎擒擦攀攒攥支收改攻放政故效敌敏救 +敗教敛敞敢散敦敬数敲整敵敷文斋斌斑斗料斜 +斟斤斥斧斩断斯新方施旁旅旋族旗无既日旦旧 +旨早旬旭旱时旷旺昂昆昌明昏易昔星映春昧昨 +昭是昼显時晃晋晌晒晓晕晚晦晨晩普景晰晴晶 +智晾暂暇暑暖暗暮暴曙曝曰曲更書曹曼曾替最 +月有朋服朔朗望朝期朦木未末本术朱朴朵机朽 +杀杂权杆杉李杏材村杖杜束杠条来杨杭杯杰東 +松板极构枉析枕林枚果枝枠枢枣枪枫枯架柄柏 +某柑柒染柔柜柠查柬柱柳柴査柿栅标栈栋栏树 +栓栖栗校株样核根格栽桂桃框案桌桐桑桔档桥 +桦桨桩桶梁梅梆梗梢梦梧梨梭梯械梳检棉棋棍 +棒棕棘棚棠森棱棵棺椅植椎椒検椭椰椿楚業楷 +楼楽概榄榆榔榕榜榨榴構槐様槛槽標樟模権横 +樱橄橘橙機橡橱檀檐檬欠次欢欣欧欲欺款歇歉 +歌止正此步武歧歩歪歹死歼殃殉殊残殖殴段殷 +殺殿毁毅母毎每毒比毕毙毛毡毫毯氏民氓气気 +氛氢氧氨氮氯水永汁求汇汉汐汗汚汛汝汞江池 +污汤汪汰汹決汽沁沃沈沉沌沐沙沛沟没沢沥沦 +沧沪沫沮河沸油治沼沽沾沿況泄泉泊泌法泛泞 +泡波泣泥注泪泰泳泵泻泼泽洁洋洒洗洛洞津洩 +洪洲活洼洽派流浅浆浇浊测济浏浑浓浙浦浩浪 +浮浴海浸涂消涉涌涕涛涝涡涣涤润涧涨涩涮涯 +液涵淀淆淋淌淑淘淡淤淫淮深淳混淹添清済渉 +渊渐渔渗渝渠渡渣渤温港渲渴游渺湃湖湘湾湿 +溃溅溉源準溜溢溪溯溶溺滇滋滑滔滚滞满滤滥 +滨滩滴漂漆漏漓演漠漫漱漾潇潘潜潤潭潮澄澈 +澎澜澡澳激濒瀑灌火灭灯灰灵灶灸灼灾灿炉炊 +炎炒炕炫炬炭炮炸点炼烁烂烈烘烙烛烟烤烦烧 +烫热烹焉焊焕焚無焦焰然焼煌煎煙煞煤照煩煮 +煽熄熊熏熔熙熟熬熱燃燕燥爆爪爬爱爵父爷爸 +爹爽片版牌牙牛牡牢牧物牲牵特牺犀犁犬犯状 +犹狂狈狐狗狞狠狡独狭狮狰狱狸狼猎猖猛猜猩 +猪猫猬献猴猾猿玄率玉王玖玛玩玫环现玲玷玻 +珊珍珠班現球琅理琉琐琢琳琴琼瑙瑞瑟瑰璃璧 +環瓜瓢瓣瓤瓦瓶瓷甘甚甜生産甥用甩甫田由甲 +申电男甸町画畅界畏畔留畜略番異畴畸疆疏疑 +疗疙疚疟疤疫疮疯疲疹疼疾病症痊痒痕痘痛痢 +痪痰痴痹瘟瘤瘦瘩瘪瘫瘸瘾療癌癣癸発登白百 +皂的皆皇皓皖皮皱皺皿盆盈益盏盐监盒盔盖盗 +盘盛盟目盯盲直相盹盼盾省眉看真眠眨眩眯眶 +眷眺眼着睁睐睛睡督睦睫睬睹瞄瞅瞌瞎瞒瞥瞧 +瞩瞪瞬瞭瞳瞻矗矛矢矣知矩矫短矮石矾矿码砂 +砌砍研砕砖砚砰破砸砾础硅硕硝硫硬确碌碍碎 +碑碗碘碟碧碰碱碳確碾磁磅磊磕磨磷礁示礼社 +祀祈祖祝神祟祠祥票祭祷祸禀禁禄禅福禹离禽 +禾秀私秃秆秉秋种科秒秘租秤秦秧秩积称秸移 +秽稀程稍税稚稠稳稻稼稽稿穆積穏穗穴究穷空 +穿突窃窄窍窑窒窓窖窗窘窜窝窟窣窥窸窿立竖 +站竞竟章竣童竭端竹竿笋笑笔笙笛符笨第笺笼 +筆等筋筏筐筑筒答策筛筝筷筹签简箕算管箩箫 +箭箱節範篇篓篡篮篱篷簇簡簧簸簿籍米类籽粉 +粋粒粗粘粟粤粥粪粮粱粹精糊糕糖糙糟糠糯系 +紊純素索紧紫累細終組経結絮絵絶継続綺緑緒 +線練縛縮績繁繋繰纠红纤约级纪纫纬纯纱纲纳 +纵纷纸纹纺纽线练组绅细织终绊绍绎经绑绒结 +绕绘给绚络绝绞统绢绣继绩绪续绰绳维绵绷绸 +综绽绿缀缅缆缉缎缓缔缕编缘缚缝缠缤缩缭缰 +缴缶缸缺罐网罕罗罚罢罩罪置署羊美羔羚羞羡 +群羹羽翁翅翔翘翠翩翰翻翼耀老考者而耍耐耕 +耗耘耙耦耳耷耸耻耽耿聂聆聊聋职联聘聚聞聪 +肃肆肇肉肋肌肖肘肚肛肝肠股肢肤肥肩肪肮肯 +育肴肺肾肿胀胁胃胆背胎胖胚胜胞胡胧胰胳胶 +胸能脂脆脉脊脏脐脑脓脖脚脯脱脳脸脾腊腋腐 +腔腕腥腮腰腹腺腻腾腿膀膊膏膛膜膝膨臀臂臊 +臣自臭至致臼舀舅舆興舌舍舎舒舔舞舟航般舰 +舱舵舶船艇艘良艰色艳艺艾节芋芒芙芜芝芥芦 +芬芭芯花芳芸芹芽苇苍苏苑苔苗苛苞苟若苦英 +苹茁茂范茄茅茉茎茧茫茬茵茶茸荆草荐荒荔荡 +荣荤荧荫药荷莉莫莱莲获莹莺莽菇菊菌菜菠菩 +菱菲萄萌萍萎萝萤营萧萨落葉著葛葡董葫葬葱 +葵蒂蒋蒙蒜蒲蒸蓄蓉蓝蓬蔑蔓蔗蔚蔡蔬蔵蔼蔽 +蕉蕊蕴蕾薄薇薛薪薬薯藉藏藐藕藤藻蘑虎虏虐 +虑虚虫虹虽虾蚀蚁蚂蚊蚌蚓蚕蚣蚤蚪蚯蛀蛇蛋 +蛙蛛蛤蛮蛾蜀蜂蜈蜒蜓蜕蜗蜘蜜蜡蜻蝇蝉蝌蝎 +蝗蝙蝠蝴蝶螃融螺蟀蟆蟋蟹蠕蠢血衅行衍術衔 +街衙衝衡衣补表衫衬衰衷袁袄袋袍袖袜被袭袱 +裁裂装裏裕裙補裤裳裸裹褂褄複褐褒褥褪襟西 +要覆見規視覗覚覧観见观规觅视览觉角解触言 +計訓記訝設許診証評試詰話該詳誉誊認誓語説 +読誰調請論謝識警譬議计订认讥讨让训议讯记 +讲讳讶许讹论讼讽设访诀证评识诈诉诊词译试 +诗诚话诞诡询该详诫诬语误诱诲说诵请诸诺读 +诽课谁调谅谆谈谊谋谍谎谐谓谚谜谢谣谤谦谨 +谬谭谱谴谷豁豆豊豌豚象豪豫豹豺貌負費貼資 +質贅贝贞负贡财责贤败账货质贩贪贫贬购贮贯 +贰贱贴贵贷贸费贺贻贼贾贿赁赂赃资赋赌赎赏 +赐赔赖赘赚赛赞赠赡赢赣赤赦赫走赴赵赶起趁 +超越趋趟趣足趴趾跃跋跌跑跛距跟跡跤跨跪路 +跳践跷跺踊踏踝踢踩踪踮踱蹂蹄蹈蹋蹦蹬蹭蹲 +躁躏身躬躯躲躺車転軽載輪轄车轧轨轩转轮软 +轰轱轴轻载轿较辅辆辈辉辐辑输辖辗辘辙辛辜 +辞辟辣辨辩辫辰辱边辺辻込辽达辿迁迂迄迅过 +迈迎运近返还这进远违连迟迢迪迫迭述迷迹追 +退送适逃逆选逊透逐递途逗通逛逝逞速造逢連 +逮逸逻逼逾遂遅遇運遍過遏道違遗遠遣遥遭遮 +遵選避邀邑邓那邦邪邮邻郁郊郎郑部郭都鄂鄙 +酉酌配酒酗酝酣酥酪酬酱酵酷酸酿醇醉醋醒采 +释里重野量金針鉢鉴鋼錆録鏡针钉钓钙钝钞钟 +钠钢钥钦钧钩钮钱钳钻钾铁铃铅铆铐铛铜铝铬 +铭铲银铸铺链销锁锄锅锈锋锌锐错锚锡锣锤锥 +锦键锯锰锹锻镀镇镍镐镑镜镰镶長长門閉開間 +関閲闇门闪闭问闯闰闲间闷闸闹闺闻闽阀阁阅 +阈阎阐阔队阱防阳阴阵阶阻阿附际陆陈陋陌降 +限陕陡院除陥陨险陪陵陶陷陽隅隆隋階随隐隔 +隘隙障隠隧隶难雀雁雄雅集雇雌雏雕離難雨雪 +雲雳零雷雹雾需霄震霉霍霎霓霜霞霧露霸霹青 +靖静非靠靡面革靴靶鞋鞍鞠鞭韦韧韩韭音韵響 +頃項頑頭頼題額顔願類页顶顷项顺须顽顾顿颁 +颂预颅领颇颈颊频颓颖颗题颜额颠颤風风飕飘 +飛飞食飲餐館饥饭饮饰饱饲饵饶饺饼饿馁馅馆 +馈馋馍馏馒首香馨駄駆騒马驮驯驰驱驳驴驶驹 +驻驼驾骂骄骆骇验骏骑骗骚骡骤骨髓高鬓鬼魁 +魂魄魅魏魔魚鮮鱼鲁鲍鲜鲤鲨鲫鲸鳄鳍鳖鳞鳥 +鴉鸚鸟鸡鸣鸥鸦鸭鸯鸳鸵鸽鸿鹃鹅鹉鹊鹏鹤鹦 +鹰鹿麗麦麻黄黎黑黒黔默黙黯鼎鼓鼠鼻齐齿龄 +龙龟갚계귨꾣뇤덅됬뚳램묈뷴뼭섉솳숅쉪싧썮 +썹쐣펽풆퓳퓵!(),:;?� diff --git a/Assets/Scripts/SaveSystem/README.md b/Assets/Scripts/SaveSystem/README.md index b8bae1063..badbc5601 100644 --- a/Assets/Scripts/SaveSystem/README.md +++ b/Assets/Scripts/SaveSystem/README.md @@ -49,6 +49,8 @@ tags: content save_on_exit - `interaction` / `save_on_exit` 进入时不存档;对应节点完成后若直接结束本轮 Dialogue,则自动保存 state-only 档。 - 若执行路径继续 `jump` / `detour` 到其他 Yarn 节点,退出档会取消并记录告警。 +- run / flow / current-node 稳定性只约束 `DialogueExit`:任何后续 Yarn 节点(包括 `detour`、`no_save`)都会取消节点末尾存档。 +- 普通 `NodeEnter` 和兼容命令 `<>` 不受后续节点流转影响;它们始终使用请求触发时冻结的 node / YarnProject / TalkScene 生成 anchor,并继续进入串行写盘队列。 - 放在目标节点**末尾**:所有状态命令(`switch_fix_system_to`、`hide_dialog`、`play_timeline` 等)执行完毕之后,`<>` / `<>` 之前。 - `anchor.nodeName` 始终保留来源节点;`anchor.startDialogueOnRestore=false`,读档时只加载 YarnProject,不启动节点。 - 退出档在 `DialogEnd` 同步初始化后等待一帧捕获;这一帧由 scoped interaction lock 保持输入锁定,捕获进入写盘队列后再交出控制权。 @@ -153,6 +155,7 @@ Postflight 校验 - **核心层**(`scene`、`anchor`、`yarnVariables`)由 `SnapshotRestore` 框架直管,不通过 Provider 注册。 - **表现层**(`sections` 内各子系统)通过 `ISnapshotProvider` 扩展;`RestoreOrder` 表示同 Phase 内的建议顺序或软依赖。 - **逐项还原(D1)**指各子系统各自写回状态,**不是** Provider 之间逐步 `yield return`。 +- Postflight 只核对 Scene、TalkSceneSO、YarnProject 与必要 Provider readiness;不要求 RestartNode 仍停在 anchor,也不校验 StateOnly 结束后的 DialogueRunner 状态。 Provider 使用 `ISyncSnapshotProvider` 或 `IAsyncSnapshotProvider` 显式声明同步/异步恢复;异步 Provider 必须把等待过程返回给编排层,禁止内部 fire-and-forget。 diff --git a/Assets/Scripts/SaveSystem/SaveRestoreOrchestrator.cs b/Assets/Scripts/SaveSystem/SaveRestoreOrchestrator.cs index f6bc4ac4d..a0a0929db 100644 --- a/Assets/Scripts/SaveSystem/SaveRestoreOrchestrator.cs +++ b/Assets/Scripts/SaveSystem/SaveRestoreOrchestrator.cs @@ -422,18 +422,6 @@ namespace AibisDream.SaveSystem return false; } - if (dialog.DialogueRunId != request.DialogueRunId) - { - reason = $"dialogue run changed ({request.DialogueRunId} -> {dialog.DialogueRunId})"; - return false; - } - - if (dialog.DialogueFlowVersion != request.FlowRevision) - { - reason = $"flow revision changed ({request.FlowRevision} -> {dialog.DialogueFlowVersion})"; - return false; - } - if (!string.Equals(runner.YarnProject?.name, request.YarnProjectId, StringComparison.Ordinal)) { reason = $"YarnProject changed to {runner.YarnProject?.name ?? "(none)"}"; @@ -454,33 +442,51 @@ namespace AibisDream.SaveSystem } var (currentNodeName, _) = dialog.GetCurrentNodeContext(); - if (request.Trigger == SaveTrigger.DialogueExit) - { - if (runner.IsDialogueRunning) - { - reason = "DialogueRunner is still running"; - return false; - } + return ValidateDialogueTiming( + request, + dialog.DialogueRunId, + dialog.DialogueFlowVersion, + currentNodeName, + runner.IsDialogueRunning, + out reason); + } - if (!string.IsNullOrEmpty(currentNodeName)) - { - reason = $"a current Yarn node is still present: {currentNodeName}"; - return false; - } + internal static bool ValidateDialogueTiming( + SaveRequest request, + long currentDialogueRunId, + long currentFlowRevision, + string currentNodeName, + bool isDialogueRunning, + out string reason) + { + if (request.Trigger != SaveTrigger.DialogueExit) + { + reason = null; + return true; } - else - { - if (!runner.IsDialogueRunning) - { - reason = "DialogueRunner stopped before capture"; - return false; - } - if (!string.Equals(currentNodeName, request.SourceNodeName, StringComparison.Ordinal)) - { - reason = $"current node changed to {currentNodeName ?? "(none)"}"; - return false; - } + if (currentDialogueRunId != request.DialogueRunId) + { + reason = $"dialogue run changed ({request.DialogueRunId} -> {currentDialogueRunId})"; + return false; + } + + if (currentFlowRevision != request.FlowRevision) + { + reason = $"flow revision changed ({request.FlowRevision} -> {currentFlowRevision})"; + return false; + } + + if (isDialogueRunning) + { + reason = "DialogueRunner is still running"; + return false; + } + + if (!string.IsNullOrEmpty(currentNodeName)) + { + reason = $"a current Yarn node is still present: {currentNodeName}"; + return false; } reason = null; @@ -775,23 +781,6 @@ namespace AibisDream.SaveSystem var runner = DialogController.Instance?.DialogueRunner; if (!string.Equals(runner?.YarnProject?.name, snapshot.anchor.yarnProjectId, StringComparison.Ordinal)) context.Error($"YarnProject mismatch: {runner?.YarnProject?.name ?? "none"}."); - if (snapshot.anchor.startDialogueOnRestore) - { - var (currentNodeName, _) = DialogController.Instance != null - ? DialogController.Instance.GetCurrentNodeContext() - : (null, null); - if (runner == null || !runner.IsDialogueRunning) - context.Error($"Yarn node did not start: {snapshot.anchor.nodeName}."); - else if (!string.Equals(currentNodeName, snapshot.anchor.nodeName, StringComparison.Ordinal)) - context.Error( - $"Yarn node mismatch: expected {snapshot.anchor.nodeName}, " + - $"actual {currentNodeName ?? "none"}."); - } - else if (runner != null && runner.IsDialogueRunning) - { - context.Error( - $"StateOnly restore unexpectedly started Yarn node {snapshot.anchor.nodeName}."); - } if (snapshot.sections != null && snapshot.sections.ContainsKey(SnapshotProviderIds.Fix) diff --git a/Assets/Yarn/Refactor/FP_Day2_sleep/FP_Day2_sleep_new.yarn b/Assets/Yarn/Refactor/FP_Day2_sleep/FP_Day2_sleep_new.yarn index 13fcdd5cc..c6e45332c 100644 --- a/Assets/Yarn/Refactor/FP_Day2_sleep/FP_Day2_sleep_new.yarn +++ b/Assets/Yarn/Refactor/FP_Day2_sleep/FP_Day2_sleep_new.yarn @@ -65,11 +65,11 @@ tags: hub <> <> -->放线 #line:08a1756 +->放线 #line:08a1756 #notrack <> -->收线<1>> #line:0b475d7 +->收线<1>> #line:0b475d7 #notrack <> -->保持<> #line:0930771 +->保持<> #line:0930771 #notrack <> <> @@ -388,11 +388,11 @@ tags: hub <> <> -->放线 #line:08c3eee +->放线 #line:08c3eee #notrack <> -->收线 #line:08e99b8 +->收线 #line:08e99b8 #notrack <> -->保持 #line:0e846d1 +->保持 #line:0e846d1 #notrack <> <> diff --git a/Docs/存档系统设计方案.md b/Docs/存档系统设计方案.md index b57332788..2e4591619 100644 --- a/Docs/存档系统设计方案.md +++ b/Docs/存档系统设计方案.md @@ -234,6 +234,8 @@ P7 横切 `interaction` / `save_on_exit` 登记退出 checkpoint,节点完成并直接结束本轮 Dialogue 后写 state-only 档。 - `anchor.nodeName` 对所有档位都保存触发节点;`anchor.startDialogueOnRestore` 独立决定读档时是否启动 Yarn。 - 退出 checkpoint 在触发时冻结 node / YarnProject / TalkScene / Dialogue run;`DialogEnd` 初始化期间使用 scoped interaction lock,下一帧捕获完成后才开放玩法输入。 +- Dialogue run / flow revision / current node / runner 停止状态的稳定性检查只用于 `DialogueExit`。退出节点后若开始任何新 Yarn 节点(含 detour、`no_save`、jump 目标或新 Dialogue),取消该节点末尾存档。 +- `NodeEnter` 与 `ExplicitCommand` 不因下一帧发生节点流转而取消;它们使用触发时冻结的 node / YarnProject / TalkScene 生成 anchor。连续产生的有效请求全部进入串行队列,后产生的 checkpoint 最终覆盖自动档。 - **Fresh 进入 vs detour 续跑**:`SavePointEvaluator` 按 `yarnProjectId` 维护 `InProgressNodes`;节点尚未 `onNodeComplete` 时再次 `onNodeStart`(Yarn detour 返回父节点)→ `DetourResume`,跳过自动存。`OnNodeComplete` 移出集合;切换 YarnProject / `StopDialog` 重置;读档 anchor 重进仅 `OnRestoreEnterNode` 标记,不写盘。 - **tag 规则**:`hub` / `linear` / `content` 默认进入时保存;`interaction` 默认 Dialogue 退出时保存; `save_on_exit` 可覆盖其他一级类型;`no_save` 禁止保存,并与上述退出语义冲突。