161 lines
8.4 KiB
Go
161 lines
8.4 KiB
Go
package routers
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
)
|
||
|
||
var _ = sync.Mutex{}
|
||
var _ = context.Background
|
||
|
||
//
|
||
// World props WITHOUT the AnantaGadgetLoader exe — the server-side half of what that tool does
|
||
// by frida injection.
|
||
// The loader calls GadgetMgr.SyncGadgetGridAOIAddByIndex(cx, cz, exceptIds, sectorIds,
|
||
// gridIndex) inside the client process; the props themselves come from the client's own
|
||
// GadgetBlob, so nothing has to be authored server-side. The wire equivalent is
|
||
// SyncGadgetGridAOIDecrease(68956797) — the only grid-AOI method id that exists, and it
|
||
// carries both directions (add cells in addGridInfo, remove in removeGridInfo).
|
||
// ⛔ CORRECTED 2026-09-27 (user live-tested; the previous wording here was wrong twice):
|
||
// · The old line said "Donor server proved this end to end (HANDOFF round 12)". NO donor
|
||
// ever said that. HANDOFF-2026-09-19.md:2598 "ROUND 12 VERDICT" is OUR OWN round,
|
||
// user-tested 2026-09-20. The HANDOFF docs mix donor material with our own round log —
|
||
// round 12/13/15D/15F/15G/15H/16E/16I are all ours (they carry "user-tested" / "TEST NOW").
|
||
// · "appeared AND stayed interactive" is also wrong as written. Appearing and being
|
||
// interactable are TWO INDEPENDENT things on this channel:
|
||
// - 68956797 only materializes props out of the client's own GadgetBlob, and only for
|
||
// cells+sectors we actually push => only a PART of the world ever appears.
|
||
// - interactability is decided SOLELY by how the server answers the Ask* device RPCs
|
||
// (see GameRouter.SceneDevices.cs: an Ask* INVOKE is only a REQUEST; the client's
|
||
// device-state manager waits for the server to echo the transition before it runs any
|
||
// presentation). No reply shape => no interaction, injector or not.
|
||
// HANDOFF-2026-09-18.md:341 (ROUND 15H) pinned the ONE session where vending + outfits +
|
||
// signs + elevators ALL worked at once — Sep 20 01:32 (packets-20260920-012232.log) — and
|
||
// that session's wire was an 8-byte struct {int CheckIndex; uint Error}, silent, ALL
|
||
// devices. Cross-era census (user-verified): 012232 = 8b struct silent -> all families OK;
|
||
// 021225 = 6b blob silent -> vending STUCK (retry storm); r13-15F = 10b + echoes ->
|
||
// vending OK but menu prompts DISARM.
|
||
// · So "no injection needed" means "no frida injector needed to MATERIALIZE", NOT
|
||
// "the server can sit idle and still get interactions".
|
||
// Two rules the loader's author established and we keep:
|
||
// · sector pairing matters — one message carries ONE sector list, so cells are grouped
|
||
// by identical sector list (sectorIdList=[0] is outdoor only; interior furniture lives
|
||
// in hashed sectors from GadgetCells.json);
|
||
// · never spam — a cell is pushed once and re-pushed only on a cell crossing or the
|
||
// periodic refresh. The loader's own note warns that hammering the grid channel kills it.
|
||
//
|
||
//
|
||
// ★ donor《content-streaming-implementation》§3 的窗口差分(2026-10-01 落地)。
|
||
// prevLoaded = 客户端当前持有的格子;window = 本轮要展示的正方形窗口。
|
||
// <code>
|
||
// added = window − prevLoaded // 新格子 → 见到才加载
|
||
// keep = prevLoaded 里仍落在卸载边界内(Chebyshev ≤ unloadRadius)的那些(保留环)
|
||
// removed = prevLoaded − keep // 真·出界 → 显式下发卸载
|
||
// next = window ∪ keep // 成为新的 prevLoaded
|
||
// </code>
|
||
// ⛑ retainVisitedCells=false ⇒ 没有保留环,出窗口即卸载(等价于 unload = radius)。
|
||
// ⛑ 窗口与卸载边界都用 Chebyshev(正方形),与 §2 的 (2r+1)² 定义一致,别改用曼哈顿。
|
||
//
|
||
func GameRouter_GadgetGrid_ComputeGridWindowDiff(ctx context.Context, prevLoaded []any, window []any, cx int32, cz int32, unloadRadius int32, retainVisited bool, added []any, removed []any, next []any) any {
|
||
// Router implementation for GameRouter_GadgetGrid_ComputeGridWindowDiff
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// ★ donor《content-streaming-implementation》§1 —— 传送 / 远跳时两个窗口根本不相交
|
||
// ⇒ 必须 Init 全 re-seed(清空声明过的格子),不能只补差集。
|
||
// 2026-10-01 起用它解决「owner.Sent 只增不减」这一条:以前每次远跳都会留下一份
|
||
// 玩家已经离开的格子,永远不会消失,把卸载这件事全推给客户端自己去猜。
|
||
// ⛑ 判据 = 格子 Chebyshev 距离 > unloadRadius(窗口不相交,donor §3 原文)。
|
||
//
|
||
func GameRouter_GadgetGrid_IsFarGridJump(ctx context.Context, fromX int32, fromZ int32, toX int32, toZ int32, unloadRadius int32) bool {
|
||
// Router implementation for GameRouter_GadgetGrid_IsFarGridJump
|
||
return true
|
||
}
|
||
|
||
func GameRouter_GadgetGrid_GadgetGridStateOf(ctx context.Context, session *GameSession) any {
|
||
// Router implementation for GameRouter_GadgetGrid_GadgetGridStateOf
|
||
var zero any; return zero
|
||
}
|
||
|
||
// World-ready hook: start the grid stream. Idempotent; no-op when disabled.
|
||
func GameRouter_GadgetGrid_EnsureGadgetGrid(ctx context.Context, session *GameSession) any {
|
||
// Router implementation for GameRouter_GadgetGrid_EnsureGadgetGrid
|
||
var zero any; return zero
|
||
}
|
||
|
||
// Disconnect / scene switch: stop the loop. Props already out stay out.
|
||
func GameRouter_GadgetGrid_StopGadgetGrid(ctx context.Context, session *GameSession) any {
|
||
// Router implementation for GameRouter_GadgetGrid_StopGadgetGrid
|
||
var zero any; return zero
|
||
}
|
||
|
||
// Cells handed to the client so far. Panel/debug display only.
|
||
func GameRouter_GadgetGrid_GadgetGridSentCount(ctx context.Context, session *GameSession) int32 {
|
||
// Router implementation for GameRouter_GadgetGrid_GadgetGridSentCount
|
||
return 0
|
||
}
|
||
|
||
func GameRouter_GadgetGrid_RunGadgetGridLoopAsync(ctx context.Context, session *GameSession, owner any, token context.Context) any {
|
||
// Router implementation for GameRouter_GadgetGrid_RunGadgetGridLoopAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_GadgetGrid_TickGadgetGridAsync(ctx context.Context, session *GameSession, owner any) any {
|
||
// Router implementation for GameRouter_GadgetGrid_TickGadgetGridAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// Push every not-yet-sent cell around (cx,cz), grouped by sector list, one message per group.
|
||
// Returns how many cells went out.
|
||
// ★ 2026-10-01 重写为 donor《content-streaming-implementation》§3 的 add/keep/remove 三元组:
|
||
// 以前只算 added(差额补发),removeGridInfo 恒为空 ⇒ 走过的格子永远被声明、
|
||
// 客户端永不卸载(这正是 2026-09-27 §10.9.3 内存项第 3 条)。现在走出
|
||
// unloadRadiusCells 边界的格子会被显式下发卸载。
|
||
//
|
||
func GameRouter_GadgetGrid_PushGadgetGridAsync(ctx context.Context, session *GameSession, owner any, cx int32, cz int32, reason byte, fullResend bool) int32 {
|
||
// Router implementation for GameRouter_GadgetGrid_PushGadgetGridAsync
|
||
return 0
|
||
}
|
||
|
||
//
|
||
// ★ donor《content-streaming-implementation》§4 —— 显式的「请卸载这批格子」。
|
||
// 单独一条 68956797:addGridInfo 空表、removeGridInfo 装出界的格子。
|
||
// ⛑ BuildingId 必须写 int32(值 0)—— donor §6 第 2 条:漏写会让整条 payload 错位。
|
||
// ⛑ sector 仍然要带:一条报文一个 sector 列表,和加载侧同律分组。
|
||
//
|
||
func GameRouter_GadgetGrid_PushGadgetGridRemovalAsync(ctx context.Context, session *GameSession, removed []any, cx int32, cz int32, reason byte) any {
|
||
// Router implementation for GameRouter_GadgetGrid_PushGadgetGridRemovalAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
type GadgetGridSessionState struct {
|
||
SyncRoot any `json:"syncRoot"`
|
||
Cts *context.CancelFunc `json:"cts"`
|
||
Loop *any `json:"loop"`
|
||
// Cells already handed to the client. A cell is re-sent only on refresh.
|
||
Sent []any `json:"sent"`
|
||
LastCellX int32 `json:"lastCellX"`
|
||
LastCellZ int32 `json:"lastCellZ"`
|
||
HasCell bool `json:"hasCell"`
|
||
LoadedSent bool `json:"loadedSent"`
|
||
//
|
||
// Scene AOI switch (68418115) already pushed for this session. Research lever only —
|
||
// gated by SceneAoiSwitchEnabled, which defaults to false.
|
||
//
|
||
SceneAoiSwitchSent bool `json:"sceneAoiSwitchSent"`
|
||
LastPushMs int64 `json:"lastPushMs"`
|
||
ReadyAtMs int64 `json:"readyAtMs"`
|
||
Pushes int32 `json:"pushes"`
|
||
CellsSent int32 `json:"cellsSent"`
|
||
// 首次成功整窗推送的时刻(Environment.TickCount64)。入世界守卫的计时基准。
|
||
FirstPushMs int64 `json:"firstPushMs"`
|
||
//
|
||
// ★ 2026-10-04「入世界守卫」是否已经做过。每会话只做一次;
|
||
// 远跳 reseed(ReseedOnFarJump)已经替我们清过窗时也算完成,避免重复砸卸载报文。
|
||
//
|
||
SettleDone bool `json:"settleDone"`
|
||
}
|
||
|