This repository has been archived on 2026-10-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Ananta-ps/internal/gameserver/routers/game_router_scene_item_explicit.go
T

122 lines
6.9 KiB
Go
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.
package routers
import (
"context"
"sync"
)
var _ = sync.Mutex{}
var _ = context.Background
//
// 场景物的「显式下发」通道 —— 把摊位 / 货箱 / 柴火 之类的烘焙场景物,从
// 「客户端按 40m 格从 blob 铺出来的占位壳」升级成「真的挂了 GameObject 的物体」。
// ════════════════════════════════════════════════════════════════════════════════════════
// ★ 为什么必须有这个文件(第 79 轮,2026-09-26)
// 症状:服务端 push 的 40m 格窗口从 49 格一路放到 289 格,在用 hold 数 2498 → 4531 → 5094
// (数据层面全部成功、日志干净、帧率 73),**但玩家在集市里什么都看不见**。
// frida 实测(只读,`gym_tools/_r79_render.py` / `_r79_ecs.py`):
// · 5094 个在用 hold 里,只有 157 个的 SceneItemObj 非空(3.1%)⇒ 93.7% 只是占位壳;
// · 交叉表 _useECSRender × SceneItemObj:
// ecs=false | go=true 18
// ecs=true | go=false 4975 ← 98%
// ⇒ 走 ECS 渲染的 hold **不生成 GameObject**;
// · 全部 5094 个的 _forceLODLevel 都 = Init,配合硬规则 8 的实测指纹
// (Lod0 = 服务端摆的 / Init = 客户端烘焙物)⇒ **它们全部来自客户端自己的 blob**,
// 没有任何一个是服务端显式下发的;
// · 客户端日志里那句 场景不支持ecs的group数量过多,可能会影响性能, levelConfigType:10,
// count:2725(从进入世界第一秒就有、不是本改动引入)说明本场景的 ECS 渲染被关卡守卫关掉 ⇒
// 被判给 ECS 的 4975 件**没有任何东西去渲染它们** ⇒ 看不见。
// ⇒ 结论:PushSceneItemGridAsync 只推「哪几格」,客户端就从 blob 铺壳;
// 要真正物化,必须走 aoiInfo.addInfos 显式逐对象下发。
// 这一点在仓库里早有明文(`BowlingWireContracts.cs:591`):
// 「the explicit addInfos inside aoiInfo are what actually materialise an object
// when the blob has nothing for that cell」
// 而**能用的健身房通道**(`GameRouter.Gadget.cs:137`)就是同一个模式:显式下发 + ForceLod0 = true
// ——注释原话「ForceLod0 keeps a freshly spawned prop from being culled to a billboard」,
// 并且用**客户端 blob 的真实 uid** 当 uniqueId,「so the entity the server places is the same
// instance the client would have built itself」。
// ════════════════════════════════════════════════════════════════════════════════════════
// ★ 所以在整个链路里的位置
// 1) 一次性:从客户端 dump 摆位(`gym_tools/_r79_dump_si_place.py`)→
// config/scene_item_placements.json(mtime 热重载,改完不用重启服务端);
// 2) 每次要让东西出现:POST /api/scene-item/push-explicit 或直接调本类的
// PushExplicitSceneItemsAsync;半径 / 条数 / 标记 / 通道全部是参数,
// **不需要 rebuild、不需要重启**,可以现场 A/B。
// ⛑️ 复用 Drmk.Server.RpcTypes.Client4229938 的保龄球 DTO(`BowlingWireContracts.cs`),
// 不用 `SceneItemGridMethods4229938.cs` 里那份同名 DTO:两者的元素类型标记不同
// (保龄球版 TypeMark = 1 = 客户端跳转表里的 BASE;SceneItem 版是默认标记),
// 而保龄球版是**在线上验证过能建出 hold 的**那一份。标记写错的表现是
// `parameter=DestructibleInfo` 解析失败——donor 第 23B 轮为这个赔过一整个会话。
//
//
// 找 config/scene_item_placements.json:从运行目录 / 当前目录各向上找 6 层
// (与 BowlingWireNow 同款做法)。找不到返回 null。
//
func GameRouter_SceneItemExplicit_FindSceneItemPlacementFile(ctx context.Context, ) any {
// Router implementation for GameRouter_SceneItemExplicit_FindSceneItemPlacementFile
var zero any; return zero
}
//
// 读摆位表(mtime 缓存)。JSON 键名做了宽松兼容,所以 dump 脚本换写法不用改这边:
// <code>
// { "items": [
// { "id": 123456789, "cfg": 80001921, "path": 1154760923,
// "x": -4713.9, "y": 176.2, "z": -2821.7, "yaw": 91.5, "scale": 100 },
// ...
// ] }
// </code>
// 键名兼容:id|uid|uniqueId|unique_id / cfg|cfgId|configId|config_id / path|pathId|path_id /
// x,y,z / yaw|ry|facing|rotY / scale|iScale|i_scale。缺 yaw / scale 时取 0 / 100。
//
func GameRouter_SceneItemExplicit_LoadSceneItemPlacements(ctx context.Context, ) []any {
// Router implementation for GameRouter_SceneItemExplicit_LoadSceneItemPlacements
return nil
}
func GameRouter_SceneItemExplicit_ParsePlacement(ctx context.Context, it any) any {
// Router implementation for GameRouter_SceneItemExplicit_ParsePlacement
var zero any; return zero
}
// ---------------------------------------------------------------------------------------
// 下发
// ---------------------------------------------------------------------------------------
//
// 把玩家周围 内的场景物显式下发,让客户端**真的**把 GameObject 装出来。
// :
// aoi = 只走 SyncDestructibleGridAOIDecrease(68969593) 的 aoiInfo.addInfos
// (保龄球用的就是这条,hold 建得出来);
// addlist = 只走 SyncDestructibleObjectAddList(68963429);
// both = 两条都发(保龄球实际是这么干的,默认)。
// 默认 true —— 这是**和健身房那条能用的通道唯一对齐的字段**
// (`GameRouter.Gadget.cs:137`),关掉它只剩「显式下发」一半的效果,留作 A/B 对照组。
// = true 时把下发的 uid 塞进 addGridInfo.ExceptIds,
// 并按它们所在的格填 StandardIndexList,让客户端**别再**从 blob 铺同一批壳
// (ExceptIds 这个字段就是为这件事存在的)。false = 只加不减,作对照组。
//
func GameRouter_SceneItemExplicit_PushExplicitSceneItemsAsync(ctx context.Context, session any, radiusMeters float64, maxItems int32, mode string, forceLod0 bool, exceptIds bool, addListReason byte) any {
// Router implementation for GameRouter_SceneItemExplicit_PushExplicitSceneItemsAsync
var zero any; return zero
}
// 诊断:摆位文件是否可用、总条数、解析失败的条数。
func GameRouter_SceneItemExplicit_SceneItemPlacementStatus(ctx context.Context, ) any {
// Router implementation for GameRouter_SceneItemExplicit_SceneItemPlacementStatus
var zero any; return zero
}
// 一条场景物摆位。字段名与客户端 hold 上的同名(cfgId / pathId / uniqueId / objPosition)。
type SceneItemPlacement struct {
UniqueId uint64 `json:"uniqueId"`
CfgId uint32 `json:"cfgId"`
PathId int32 `json:"pathId"`
X float32 `json:"x"`
Y float32 `json:"y"`
Z float32 `json:"z"`
Yaw float32 `json:"yaw"`
Scale int32 `json:"scale"`
}