Files
ft/CodexOutput/upserver/P2AlphaNullCompatibility/101两类历史空态最小兼容方案-待评审.md

18 KiB
Raw Blame History

101两类历史空态最小兼容方案(修订1,待差异补审)

2026-09-28,主会话。前置:P2-α-A关闭裁决。本单元简称P2-α-B,仅代表两类空态兼容,不等于整体迁移政策或阶段4B。

最新状态:原方案已审并完成B1裁决及B2离线红测,生产代码尚未修改;发现原M15解析顺序不可达,修订1待差异补审后恢复实施。 已改两个离线测试文件,旧生产代码下77组中3组预期失败,证据见B2-red-result.json。原受审方案副本保存在B2-original-reviewed-plan.md,原审查与清单不重写。保留用户“方案完整评审后编码”的要求。

1. 问题、目标与取舍

已有本地PF101共55键,被当前原生产Decode拒绝的两项是:M15 FishingChallengeData的JSON null;M22 GeneralEventPackData.selections的JSON null。旧业务能产生这两种值,详见已复核语义盘点。PF102共45键已Valid,且KV102已有后续真实游戏进度。

目标:只修两条加载表示差异,让解码与物化保留旧空态;其余字段、严格格式、集合隔离和事务准入不变。保留所有Raw键和值,不替用户改档。101能否整体Valid,以原样本和冻结配置实测为准;不为追求全绿不断扩大修复。

路线 取舍 决定
精确保留两类null表示 改4个已有生产文件;保留存在性、其他字段及列表深复制 推荐,作为本方案唯一申请范围
删除挑战键、礼包null规范成[]或重建礼包 表面改动少,丢原表示或覆盖已有礼包字段;存档写入范围更大 不采用
通用可空Schema/迁移框架或补全所有活动入口 扩大所有模块接受集合和生命周期改动,无法由两个样本验证 不采用

不需要新增架构、服务、状态机、配置开关或持久化版本。M15为避免把“键存在但null”冒充“键缺失”,只给现有内部DTO补一个只读标记;不引入通用三态框架。

2. 输入与开发边界

  • 原样本、cfgData/Tables及关键源码由本目录design-review-hashes.json冻结;原A单元证据保持原文。PF101/102不再联网拉取,KV102仅用既有末次备份作历史证据。
  • 生产白名单4个已有文件:Client/Assets/Scripts/Services/PlayerData/PlayerKVSaveSchema.cs、同目录PlayerKVDecodedSave.cs、Client/Assets/Scripts/DataCenter/FishingChallengeManager.cs、同目录BargainPackData.cs。只改下文指明的Decode/DTO/Replace;不改活动入口、计分、奖励、购买、保存或Reset逻辑。
  • 测试白名单3个已有文件:CodexOutput/upserver/KVGameLoadTests/ActivitySchemaTests.cs、同目录D3SchemaTests.cs、Client/Assets/Scripts/Services/PlayerData/PlayerKVEditorValidationHost.cs(仅UNITY_EDITOR固定验证;包含Bargain专用比较器的null分支)。无新测试工程/新反射入口/新诊断类;不改csproj、asmdef或生成Tables。
  • KVGameLoadValidation.cs原固定case入口、KVGameplayLoopSetup.InspectPFBackup、unity-source-inspection.cs.txt及其他测试文件只复用。设计/结果/源码修改前快照放本目录;共享入口/状态/TODO/日志逐检查点更新。
  • 任何白名单扩展或第三种拒档原因需要停在具体差异并记录,不得自动放宽。暂不打包,无Play、许可、登录、远端读写或存档导入。地址固定http://s101.local:18080/,只用Unity CLI Loop。

3. M15:缺键、JSON null、合法对象三者分开

3.1 表示合同

输入 IsPresent 新IsExplicitNull 受控Reset→Replace后的业务对象
缺FishingChallengeData键 false false null;保留原Missing合同
键值为严格JSON null,允许JSON自身空白 true true null,未激活
完整合法JSON对象 true false 原全部字段物化和列表深复制
C# null值、空字符串/空白、字符串"null"、数字、布尔、数组、坏JSON 无成功DTO 无 Malformed,不能调用Apply

修订1: 原共用Json在PlayerKVSaveSchema:163拒绝根级Null,故原方案“先Json再判断Null”不可达。在现有errors.Capture(15,0)内,先仅识别text != null && text.Trim(' ', '\t', '\r', '\n') == "null";命中时调用已有StrictJsonSyntax.Validate(text)验证完整原文,再返回ExplicitNull工厂。其余输入沿用原Object(Json(text))及全部字段验证。四种trim字符恰为JSON空白,不使用宽松任意Unicode空白,不去掉注释或尾随内容。C# null不命中,仍由Json报Malformed。Raw不使用trim后的字符串。

共用Json/Object/IntegerList/StrictJsonSyntax实现均不修改,没有第二套解析器、包装拼接JSON或其他模块接受范围变化。StrictJsonSyntax本来支持null字面量及全量耗尽检查;局部分支只处理这一终结符,不触碰对象结构。对象内部任一原必填字段缺失/null仍失败。根null与嵌套null有区别:M22根为对象、selections嵌套null原Json可解析,故M22方案无需调整。

PlayerKVChallengeState增加只读IsExplicitNull;既有构造末尾增加可选bool explicitNull=false以保留旧调用,赋值并禁止explicitNull && !present。新增ExplicitNull()工厂生成present=true、explicitNull=true、其余默认值及非null空只读列表。Missing保持present=false、explicitNull=false。对象构造仍默认false。此DTO为内部内存表示,不序列化到服务器。

FishingChallengeManager.ReplaceFromKVDecoded仅把原!state.IsPresent条件扩为“缺失或显式null”,两者赋业务对象null;合法对象初始化器不改。Reset继续由现有受控加载事务先调用,不在Decode或Replace中启动活动、清理全局状态或写档。仍拒绝state本身为null。

Raw字典由原PlayerKVDecodedSave复制:显式null保留键及原字符串,缺键仍缺键;不能以DTO旗标重建Raw。现有DoSave对业务null序列化为JSON null的行为保持;本轮验证其序列化含义而不调用网络保存。

3.2 调用前置、P3-1及限制

142处逐引用前置锚点与机器台账逐行链接原引用、近邻声明和条件;原始语句/分类仍以已复核台账为准。近邻条件不冒充控制流支配证明;没有可证明门的调用明确保留为“需要有效活动上下文”。不因本次接受null而宣称这些入口都安全。

门或前态 当前源码锚点(行号以受审源码为准) 本方案结论
fresh/Reset FishingChallengeManager:63、70~74;Replace:76~98 空态不创建默认挑战,不激活配置/异步行为
初始化 同文件CheckInit:237、Init:244、251/256/258、263 有活动时原Init可能创建对象;本方案不调用Init,也不承诺将来的活动开启永久保持null
首页显示 HomeBtnChallenge:67、75;Manager.ShouldOpen:139/143 正常OnEnable经过ShouldOpen,后续按钮回调仍依赖活动生命周期
状态观察 Manager.ChallengeState:1028/1034;IsSettleState:1062/1066 null返回state0;短路不进入时间判断
局部空判断 Manager:281、572、605、844、950、963、1074、1089 仅对应函数/分支的保护,不覆盖所有异步续体
奖励准入 UserService.KVFishingReward:127 非null才检查挑战状态,本方案不改奖励代码
直接/别名读取 Manager.IsFirstOpen:110、IsTimeAllow:169等;ChallengeMatchSceneRoad:81~82 依赖已初始化活动及合法场景进入;未证明任意场景调用都安全
资源异步入口 Manager.InChallenge:510、514及后续资源加载 状态0仍可能触发加载,限制保持;不批准101真实挑战验证

已有业务对象→Reset→显式null必须验证;fresh→显式null与fresh→缺键都为空但DTO/Raw存在性不同。禁止把此结论用于未Reset旧对象、热换号或任意跨账号异步回调。

4. M22:保留列表null而保留整个礼包

只在DecodeBargain局部读取selections token:属性存在且token.Type为Null才允许null;属性不存在(C# null)仍让原IntegerList拒绝。数组仍原IntegerList:字符串/对象/浮点/越界/元素null不接受。GeneralEventPackData根null、缺其余字段、未知或非PackType=3的redirectID仍走现有错误分类。

PlayerKVBargainState构造仅改变selections赋值:输入null保存null;否则仍复制为只读列表,包括空数组。Missing()仍传空数组,不改变“整个礼包缺键”的IsPresent=false和默认字段。

BargainPackData.ReplaceFromKVDecoded仅改变selections字段:state.selections为null则null,否则new List深复制。其余11整数、2布尔、1浮点及_kvLoadPresent保持原赋值。SnapshotForKVValidation现有构造调用自然保留null,不需要新增数据结构或setter。Reset保持新空List和_kvLoadPresent=false。不改旧LoadData/UpdateData/SaveData;序列化应保留selections:null,不能自动转[]。

场景 DTO/实际对象/Snapshot 其他合同
根key缺失 IsPresent=false,列表空 Reset与缺礼包既有合同
完整对象,selections=null IsPresent=true,列表null 所有其他字段保持原值
完整对象,selections=[] IsPresent=true,独立空集合 不与null混同
完整对象,selections=[7,9] 只读DTO、独立业务List、独立Snapshot 任一可变列表改变不能影响另两者
selections属性缺失/类型错或其余字段坏 原Malformed;错误redirect原UnsupportedSchema 不输出可Apply状态

旧101 redirect=4056001,冻结配置PackType=3/MaxCount=1;只作现有输入证据,不硬编码账号ID、礼包ID或字符串样本特判。

5. 验收矩阵与复用测试

先更新获审测试并记录旧生产代码下红测,再实施上述4文件最小差异并绿测。旧测试中M15“根null拒绝”及M22“selections属性null拒绝”两条期望需要精确改写;其他对象字段null/缺字段及M26/M27必须仍拒绝,不能整体删除严格循环。

编号 测试与证据 通过条件
T1 ActivitySchemaTests原RunActivitySchemaTests增加M15根空态边界 缺键/JSON null/带四种JSON空白null/对象四组DTO值正确;C# null、空、坏JSON、"null"字符串、数值、布尔、数组拒绝;修订1补NBSP/U+FEFF等非JSON空白、注释、null null、null尾逗号/对象注入拒绝,以及原共用Json("null")仍拒绝的隔离断言。对象每字段缺失/null仍拒绝。复用同一partial Tests已有Aggregate/ModernRaw,断言完整Decode的Data.Raw逐UTF8字节保留带空白null、模块外探针键原值及缺键差异,不修改AggregateTests文件
T2 D3SchemaTests原M22段与DTO集合测试 null、[]、非空区分;缺属性/非法数组元素及其余字段错误继续拒绝;DTO非空深复制;null不抛异常;错误PackType不绕过检查
T3 原Editor固定case M15-M26-M27,在RunActivities追加 fresh/null、合法对象→Reset→null、再次对象→Reset→缺键均正确;IsInactiveForKVValidation为true;ChallengeState=0/ShouldOpen=false;JsonConvert序列化业务对象为null;M26/M27原断言不变
T4 原Editor固定case M21-M22-M24,在RunD3追加 对象→null列表→[]→非空→Reset/缺键;每次Snapshot逐字段比对所有礼包字段与IsPresent;验证双向修改隔离,序列化保留null;原其他模块回归不删
T5 KVGameLoadTests全量,已有完整Decode/事务回归 运行精确DLL;旧严格语法/AD/鱼配置/失败不发布及其他模块不回归。T1/T2所在既有方法自动被现有Program执行,无新测试注册
T6 原Editor固定B2-20完整事务case;原固定InspectPFBackup读两个未改样本 B2-20回归通过;101两条目标诊断消失,102仍Valid;若出现第三问题记录并停止扩大。保留原样本值hash与Decode Raw复制的现有合同,附T1/T2 Raw输入前后相等断言;没有移除模块外键
T7 Unity CLI Loop重编译+现有ServiceHost编译命令 Editor无新增编译错误,SERVICE_HOST成功;不更改非Editor历史问题或工程配置。失败保留首败输出,不用重复运行掩盖
T8 diff/hash/运行边界 生产4文件仅约定成员、测试3文件仅新用例/精确期望及Bargain专用比较分支;配置和备份不变;无Play/permit/正式存档接口请求;结束环境恢复

测试无效保护:红测必须是预期兼容断言失败,编译失败或错误apphost启动不能充当红证据。已有共享apphost曾指向其他程序集;本轮使用dotnet <本次明确构建的KVGameLoadTests.dll>并记录DLL hash/时间/测试组名。没有涉及的奖励测试不为凑数重跑;只有新失败或发现依赖时再扩大回归并说明理由。

测试比较器已发现必要适配:PlayerKVEditorValidationHost:107的Bargain专用Same使用SameList,而:117通用SameList直接取Count,不能用它比较两个null列表。只在Bargain专用Same的selections子表达式中区分两者都null、仅一方null、双方非null后调用原SameList;其余14字段比较仍执行。通用SameList与其他模块不改。新增null/null相等、null/[]不等、其他字段不同不等的断言,避免比较器抛错或漏检冒充业务兼容通过。

离线构建沿用dotnet build CodexOutput/upserver/KVGameLoadTests/KVGameLoadTests.csproj --no-restore,运行该工程实际输出DLL;SERVICE_HOST沿用dotnet build CodexOutput/upserver/KVGameLoadTests/ServiceHost/ServiceHost.csproj --no-restore。不依赖dotnet run选择共享apphost,不新增测试工程。原Editor入口接受的case名称不变。

T3/T4仅对隔离真实对象调用受控Reset/Replace/Snapshot,不调用Init/CheckInit、SaveData/DoSave、购买或资源场景。沿用原测试容器与finally恢复,不发布游戏句柄。M15直接IsInactive观察及不会联网的ShouldOpen/ChallengeState在空态验证;不造真实账户身份。若原case共享容器恢复失败,停止并记录,不能继续Inspect。

6. Editor与数据权限

执行前通过CLI读取版本、进程和playing/enabled/ready/run/permit;90064只是上轮已测进程,不假设审批后仍相同。必须空闲、无运行许可、无活动validation/GContext,保留11个历史claimed,不改启用静态字段绕门。

需要重编译使用现有Unity CLI Loop;编译完成确认空闲后跑固定用例与Inspect。若状态不满足或需要新进程,先确认已退出Play/排水/无许可,再正常关闭重启;不用强杀或launch --restart。不得在方案未审时预写测试或更改源码触发编译。

本方案只申请本地修订和离线/Editor隔离测试,不申请101真实登录、PF/KV导入、远端Get/Set/Delete、收费或补偿。101/102旧授权不替代本轮限定范围。正式迁移前身份/权威源/独占窗口/CAS缺口及产品处理政策仍未解决。

7. 三条P3、未知键和102分叉

P3-1见§3及142行附件。P3-2/P3-3见定位尝试与102分叉时间线。引用原盘点已封存证据,不重新搜索后把历史结果写成当前新证据。

五种未定位键仍保留,不在本轮Schema新增模块或删键。其他26种模块外键与Present覆盖不足保持专项未决。102从旧PF导入后经历装备、消费及收鱼,末次KV备份已分叉;本次本地PF102复测只是兼容回归,不是重新认定PF为当前权威,更不能把旧档写回。

8. 检查点及关闭条件

检查点 责任与交付
B0 当前方案准备 完整设计、142引用锚点、定位/时间线附件、精确白名单、作者自审、提示词及受审hash;尚未编码
B1 独立方案复核返回后 主会话核hash逐条裁决、明确批准版本;保存7个待改文件前态,记录Editor准入与配置/样本基线
B2 红测与最小编码 仅7文件白名单;先记录预期红测,再修改4个生产文件;T1/T2及diff检查
B3 验证 T3~T8,记录当前UTC/版本/cfg指纹/原始输出,真实样本只读;发现第三问题登记,不自动扩大
B4 作者交付 结果、首败、限制、最终diff与hash、独立实施复核提示词;更新共享文档
B5 独立实施复核 主会话逐条裁决后关闭两类兼容;不关闭整体P2-α/G4,不推为101可正式启用

每个检查点更新开发日志和入口/TODO。若无阻塞可继续本已批准检查点,不反复征求同一授权。需要新增生产改动时交精确补充方案,不以“让101跑通”为理由顺手重构。

9. 作者自审

  • 已完成上下文、三路线取舍、完整合同和测试设计;无视觉设计问题,无需视觉伴侣。既有用户约定确定目标,无需重复问选择;采用brainstorming内容方法,文档放既有upserver目录,不自动提交共享Git工作区。
  • 接受根null与接受对象字段null严格分开;C# null/缺属性不是JSON null。M15新增旗标保持Raw存在性,M22所有其他值及深复制保留。
  • 生产改动4个已有文件;测试复用3个已有文件和固定入口;不增加Schema框架、运行许可机制或迁移工具。
  • 方案没有声称null会让所有玩法入口安全,没有批准101导入或修改当前102;报告附录中的未证明项仍是未证明。
  • 原B1已执行,B2仅完成旧生产代码的离线红测;修订1恢复实施前须差异补审裁决。B3~B5尚未执行。当前没有任何生产源码修改,两个离线测试文件处于有意保留的红测状态。