90 lines
8.2 KiB
Python
90 lines
8.2 KiB
Python
"""Render design facts only; does not modify or execute game code."""
|
||
import json
|
||
from pathlib import Path
|
||
|
||
base = Path(__file__).resolve().parent
|
||
calls = json.loads((base / 'calls.json').read_text(encoding='utf-8-sig'))
|
||
def assign(c):
|
||
n = int(c['Id'].split('C')[-1])
|
||
if c['Path'].endswith('/Core/PlayFabMgr.cs'):
|
||
return 'A保留旧区;C另审生命周期', 'PF原流程;本次不改'
|
||
if c['Method'] == 'SyncDataImmidiate' or n == 38:
|
||
return '4C消费合同;B先审归属', '消费/等待;不得KV启用'
|
||
if n == 190:
|
||
return '身份/外围专项保留PF', '读取他人Lv;不转本人KV'
|
||
if n in (168, 183, 184, 185, 186, 187, 188, 189):
|
||
return 'B归属+C初始化发布', '登录/渠道/GM;不复用PF初始化'
|
||
if n in (126, 194):
|
||
return 'B调试入口边界', '默认禁止KV调试写;不默授权限'
|
||
if n in (1, 2, 3, 127, 128, 129):
|
||
return 'B仓库与事件上游详审', '泛型/继承;捕获异常默认值须拆开'
|
||
if n in (213, 214):
|
||
return 'B渠道回调+身份专项', 'await后UI/奖励也须旧epoch拒绝'
|
||
if n in (63,64,65,66,67,151,152,171,176,182):
|
||
return 'B归属;奖励/支付结果另裁决', '先变内存/奖励风险;禁自动重试'
|
||
return 'B逐业务完整详审', '同步末端不等于上游无延后回调'
|
||
|
||
header = '''# 阶段4B3调用事实与分期附件
|
||
|
||
日期:2026-09-24。配套[分期与4B3A方案](阶段4B3分期与4B3A固定存档入口方案-待评审-2026-09-24.md)。
|
||
|
||
**本附件是语法调用事实和阶段归属,不是4B3B完成的回调隔离合同。** 所有B/C行当前均禁止凭此编码;它们既不进入A白名单,也不获得KV运行资格。A只新增四个尚无业务调用者的静态入口。
|
||
|
||
## 1. 可复现盘点和限制
|
||
|
||
使用本地.NET 9.0.310自带Roslyn,读取Client/Assets/Scripts全部2101个.cs文件,仅解析语法,不编译或执行游戏。五种条件符号合并去重:Editor Standalone、Editor Android+AGG、Player Android+AGG、Player iOS+AGG、Player Standalone。223个位置/81文件:外部facade 212、facade内部8、SDK3。原文本234行/84文件还包含注释、声明及B2配置引用,统计口径不同。
|
||
|
||
`B3DesignAudit/calls.json`逐项含源行、所在类型/方法/属性、完整参数、方法体、局部await行、编译条件、按方法名匹配的上游位置。`source-hashes.json`封存2101个源文件hash。工具源码也纳入受审基线,运行仅产生设计分析数据。
|
||
|
||
**重要限制**:CallerNameCandidates是名称候选,可能把不同类型的Save/Load混在一起;数量0也不能证明无UnityEvent/方法组/反射调用。AwaitLines只观察包含本调用的方法;空列表不是整条链同步的证据。未枚举的自定义编译符号和包内/持久化UnityEvent另需B审查。此处不输出未经语义验证的“全部上游已覆盖”结论。
|
||
|
||
可复现命令(项目内NuGet配置,无新包):`dotnet run --project CodexOutput/upserver/B3DesignAudit/B3DesignAudit.csproj -- C:/projects/ft C:/projects/ft/CodexOutput/upserver/B3DesignAudit`。送审后复跑需另给输出目录,不覆盖受审快照。
|
||
|
||
## 2. 已人工核对的调用链与设计约束
|
||
|
||
| 位置 | 已确认链/副作用 | 捕获和取消的待审合同 |
|
||
|---|---|---|
|
||
| C050 SavePlayerData | 17个按名候选,方法自身先Publish(this),再改NewPlayerDataDic并从Fish/Achievement/Box/Event读取;remote参数可传递 | 必须从最早内存变更者起捕获,不能到此方法末端才验epoch;需要保护整个跨模块序列化对象集合 |
|
||
| C038/C223 RewardTurntableRoll→ConsumeOnPlayfab | Panel:530传false;方法先Publish、扣券、更新过渡数据与红点,后localOnly保存;随后立即远端bool | B只隔离归属;4C设计未知结果与防重入;不能把两次保存当事务 |
|
||
| C051 OpenAppraisePopupPanel | FishingRewardPanel:963调用,await ShowUI后改IsOpenAppraise并存档 | 从打开UI前捕获,await后先检查;关闭旧面板和取消等待仍不能替代epoch检查 |
|
||
| C063~067 OnBuy/Replenishment→BuySuccess | OnBuy含SDK/支付await;Replenishment亦进入BuySuccess及奖励/事件 | 支付开始时绑定账号及业务单据,不能补单回调取当前账号;B处理归属,支付恢复语义另审 |
|
||
| C151/152 OnBuySuccess | LuckyADTaskPopupPanel:87将方法组交给Item.Init;即使方法内无await也有延后来源 | 在Item回调注册处捕获;面板退出退订/失效;进入回调即检查,早于领奖/计数 |
|
||
| C171 ClubSingin | 自身已有API身份/版本捕获与await后检查,随后signTime、保存、聊天及奖励 | 复用现有API防护并证明与KV Owner/epoch绑定;避免削弱既有ClubService边界 |
|
||
| C176 OnReceiveLinkAward;C182 GetSurveyCheckAnswer | 先AddItem/Publish后标记领取并保存;Survey catch会吞掉异常继续由调用者操作 | 防止“只看保存结果”方案;失败恢复/奖励契约单独裁决,不能写失败后重发奖励 |
|
||
| C001~003 EventDataRepository | ActivityManager:34注册单例;Activity.Data/AEventDataManager有Repository.Load??new T、Save后Publish、RefreshData中AutoSaveOnCreate | 仓库与上游默认值链一起改才有效;账号关闭必须使旧事件与实例归属失效 |
|
||
| C127~129 game.AEventDataManager | 构造时Subscribe(TargetEvent),Dispose释放;GetCacheData从全局facade读、缺档new T | 注册订阅时绑定账号归属,旧事件入队仍应拒绝;继承类和公开Data可变引用须另外追踪 |
|
||
| C213 OnLinkSns | 四个按钮方法await OnLinkSns后仍改按钮/发奖;OnLinkSns内部还有账号切换UI await | 外层及内层同一操作归属;不能仅在UpdateUserDataValue前检查;finally关闭等待UI也须限制原面板实例 |
|
||
| C190 GetPlayLv | 请求PlayFabId=参数playFabId,Keys=[Lv],写PlayLvs[playFabId]缓存 | 属他人PF资料;不接本人KV。个人身份切换及缓存清理另审 |
|
||
| C177/178 ShowNotice | 属性get惰性读缓存,setter会写存档 | 账号换代必须清旧缓存或绑定新实例;无await也不能默认为账号无关设置 |
|
||
|
||
## 3. 逐调用位置与拟归属
|
||
|
||
以下“局部await”列只描述所在方法语法。每行的上下文原文与上游候选保存在同ID JSON。key为源码表达式,不把泛型名/变量/插值key误记为一个固定键。
|
||
|
||
| ID | 源文件:行(相对Client/Assets/Scripts) | 所在方法/属性 | 调用 / key表达式 | 局部await | 拟归属 | 处理约束 |
|
||
|---|---|---|---|---|---|---|
|
||
'''
|
||
def cell(x):
|
||
return str(x).replace('|', '|').replace('\r', '').replace('\n', ' ')
|
||
rows=[]
|
||
classified=[]
|
||
for c in calls:
|
||
phase,note=assign(c)
|
||
args=c['Arguments']
|
||
key=args[0] if args else '—'
|
||
# Full SDK request remains in JSON; keep the table readable.
|
||
if c['Kind']=='sdk': key='见JSON完整请求'
|
||
rows.append('| '+' | '.join(map(cell,[c['Id'],c['Path'].replace('Client/Assets/Scripts/','')+':'+str(c['Line']),c['Member'],c['Method']+' / '+key,','.join(map(str,c['AwaitLines'])) or '无(仅局部)',phase,note]))+' |')
|
||
classified.append(dict(id=c['Id'],path=c['Path'],line=c['Line'],phaseProposal=phase,implementationApproved=False,upstreamIsolationComplete=False))
|
||
tail='''
|
||
|
||
## 4. B3B完整合同的进入条件
|
||
|
||
本轮不把B3B未知项隐藏到实施阶段。B方案送审前,每个拟启用的业务入口必须给出已验证的上游调用/事件注册位置、身份捕获点、首个字段/集合修改点、取消/退订点、共享对象生命周期、localOnly/奖励副作用、错误处置以及对应验收用例。整个B单元的启用白名单必须闭合;任何新增文件先入方案,不能从本81文件统计推导允许批量改动。
|
||
|
||
在此之前,旧业务路径维持原PF;公共kv登录仍BackendNotEnabled。主方案A验收不以该待完成工作为已解决。外部应对“可先实施A”单独裁决,若不同意则退回分期。
|
||
'''
|
||
(base.parent/'阶段4B3调用事实与分期附件-2026-09-24.md').write_text(header+'\n'.join(rows)+tail,encoding='utf-8')
|
||
(base/'classification.json').write_text(json.dumps(classified,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
|
||
print(json.dumps({'renderedCalls':len(calls),'allImplementationApproved':False}))
|