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

288 lines
15 KiB
Go
Raw Permalink Normal View History

package routers
import (
"context"
"sync"
)
var _ = sync.Mutex{}
var _ = context.Background
//
// 车辆路网(服务端侧 ZoneGraph 简化版)—— 给 T3-7 车流提供「车道 handle + 沿车道里程」。
// 数据来自捐赠端 `zonegraph_lanes.csv`(WorldMap_Release,14171 条车道),
// 由 `_gen_vehicle_lanes.py` 压成两份运行时表:
// `Drmk.Server/Drmk.App/data/vehicle_lanes.csv` = handle;bx;by;bz;ex;ey;ez;length;speed
// `Drmk.Server/Drmk.App/data/vehicle_lane_links.csv` = handle;to1;to2;...
// ★ 为什么用捐赠端那份而不是 `_ref/lanes/vehicle-lane-links.csv`:
// 后者 Type==1 前向边只有 10911 条、只覆盖 56.9% 的车道,且是捐赠端 next 的**真子集**;
// 捐赠端 next 是多值后继,覆盖 13910/14171 = 98.2%,换道接缝中位 0.00m。
// ★ 与 PedLaneGraph 的关键差别:客户端按 DistanceAlongLane 在**它本地的真实车道曲线**上
// 插值(MassTrafficInterpolationUtils),所以服务端不需要车道折线,只要里程单调推进。
// 这里的 bx/ez 只用于「玩家附近挑车道」和 despawn 判定。
//
type VehicleLaneGraph struct {
Cache Table `json:"cache"`
Available bool `json:"available"`
LaneCount int32 `json:"laneCount"`
// 最大强连通分量里的车道数(导航只会吸附到这些车道上)。排障用。
RoutableLaneCount int32 `json:"routableLaneCount"`
// 强连通分量个数。理想值 1;2026-09-30 实测 286(见 MarkRoutableLanes)。
ComponentCount int32 `json:"componentCount"`
NavCache NavIndex `json:"navCache"`
}
const (
VehicleLaneGraph_CellSize float32 = 100
)
//
// 算一条车道的弓高(弦中点相对真实曲线的最大横向偏离,米)。见 Lane.Sagitta。
// 直车道(弧长≈弦长)返回 0。
//
func GameRouter_VehicleLanes_SagittaOf(ctx context.Context, lane any) float32 {
// Router implementation for GameRouter_VehicleLanes_SagittaOf
return 0
}
//
// 弦上参数 t(0..1)处的横向偏离(米)。t=0/1 落在端点 ⇒ 偏离 0;t=0.5 最大 ⇒ 等于弓高。
//
func GameRouter_VehicleLanes_LateralDeviationAt(ctx context.Context, sagitta float32, t float32) float32 {
// Router implementation for GameRouter_VehicleLanes_LateralDeviationAt
return 0
}
func GameRouter_VehicleLanes_Load(ctx context.Context, ) any {
// Router implementation for GameRouter_VehicleLanes_Load
var zero any; return zero
}
// -----------------------------------------------------------------------------------------
// ⛑⛑ 2026-09-30:车道图有 **285 条孤岛车道**,这是「自动驾驶开不到自定义标点」的真因。
//
// 体检脚本 `_vehicle_lane_graph_check.py` 的结果(可直接复跑):
// 车道 14171 条、链接表只覆盖 13910 条 ⇒ **261 条没有任何后继、255 条没有任何前驱**,
// 两者的样例 id 完全重合(89/93/135/140/232/236/410/417…)⇒ 它们是<b>双向断开的孤儿车道</b>。
// Tarjan 的结果更狠:全图 **286 个强连通分量**,最大的那个有 13886 条(97%),
// 剩下 285 条各自成形单影只的分量。
//
// 后果:日志里 `车道图不通 start=1795 target=1972` 反复出现且**永远不解开** —— 因为 lane 1972 /
// 6901 / 13108 的「能到它的车道数」= 1(只有它自己)。而 <c>SolveRoute</c> 的 Dijkstra 只走前向
// 后继,玩家把目的地标点落到这种车道附近时,**永远解不出路** ⇒ Start 之后根本没有 cruise 下发
// ⇒ 现象就是「按了自动驾驶没反应」。
// 商店/POI 之所以大多没事:它们的 endPos 是走 `GpsAreaConnectMgr` 算出来的**路口/出入口点**,
// 天然落在主干道上;自定义标点给的是玩家自己点的原始坐标,撞上孤儿车道就是纯运气。
//
// ⇒ 治法:**导航吸附时只认最大强连通分量里的车道**。孤儿车道本来就没法参与路径,
// 拿它当起点/终点必然失败;跳过它去找次近的正常车道,代价是终点可能偏移几米。
// (真要根治得重跑 `_gen_vehicle_lanes.py` 把这 261 条接上去,本次不动数据。)
// -----------------------------------------------------------------------------------------
func GameRouter_VehicleLanes_MarkRoutableLanes(ctx context.Context, table any) any {
// Router implementation for GameRouter_VehicleLanes_MarkRoutableLanes
var zero any; return zero
}
func GameRouter_VehicleLanes_Next(ctx context.Context, table any, handle int32) any {
// Router implementation for GameRouter_VehicleLanes_Next
var zero any; return zero
}
func GameRouter_VehicleLanes_Num(ctx context.Context, s string) float32 {
// Router implementation for GameRouter_VehicleLanes_Num
return 0
}
func GameRouter_VehicleLanes_CellOf(ctx context.Context, x float32, z float32) any {
// Router implementation for GameRouter_VehicleLanes_CellOf
var zero any; return zero
}
//
// 在 (px,pz) 的**环形**区间内随机挑一条车道当作出生点:距玩家
// [minDistance, radius]。
// ⛑ 为什么必须有 minDistance(2026-09-29 修):原来只筛「≤ radius」,
// 于是车会直接刷在玩家脚边几米处;而且出生点固定取车道起点(Distance=0),
// 而车道起点大量落在路口里 ⇒ 观感就是「车刷在我身边,还不在车道上」。
// 更糟的是客户端 AetherVehicleViewManager 会在
// CANDIDATE_GO_DISTANCE_SQ=400(20m)内把车流车提升成 GO(完整载具实体),
// 提升后不再吃我们的 Aether 车道插值 ⇒ 冻在原地,于是玩家身边越积越多不动的车。
//
func GameRouter_VehicleLanes_TryPickSpawn(ctx context.Context, px float32, pz float32, radius float32, minDistance float32, rng any, state any, x float32, y float32, z float32, speed float32) bool {
// Router implementation for GameRouter_VehicleLanes_TryPickSpawn
return true
}
//
// 「召唤载具从车道开过来」的生成点(2026-10-03):在 (px,pz) 的环形区间
// [minDistance, maxDistance] 内,挑一条车道上距离合适的点,并返回车道朝向。
// ⛑⛑ 与 TryPickSpawn(T3-7 车流出生)的三点差别,别混用:
// ① 只挑 Lane.Routable 的车道。吸附到那 285 条孤岛车道上 ⇒
// Dijkstra 解不出路 ⇒ 一条 Cruise 都不发 ⇒ 「叫了车但没反应」(2026-09-30 血泪)。
// ② 不只取车道起点:沿弦采样 5 个 t,用落在环内的那一段 —— 车道起点大量落在路口里,
// 刷在路口的车开出来会横穿。
// ③ 返回车道朝向(FacingOf 同一套角度约定),车头顺着路,
// 不然车要先原地掉头,观感是「车卡了一下才走」。
// :允许的横向偏离(米),0 = 不过滤(旧行为)。
// 车道 CSV 只有弦端点,弯道时弦中点在真实路面之外 ⇒ 车会刷到人行道/花坛里。
// 给 1.5 之类的值后,弯车道只保留靠近端点的那些采样点(偏离随 t 两端趋 0)。
// :多台车连叫时不想每次都刷在同一个位置,交给调用方给随机源。
//
func GameRouter_VehicleLanes_TryPickDriveInSpawn(ctx context.Context, px float32, pz float32, minDistance float32, maxDistance float32, maxLateralMeters float32, rng any, x float32, y float32, z float32, facingDegrees float32) bool {
// Router implementation for GameRouter_VehicleLanes_TryPickDriveInSpawn
return true
}
func GameRouter_VehicleLanes_BuildNavIndex(ctx context.Context, ) any {
// Router implementation for GameRouter_VehicleLanes_BuildNavIndex
var zero any; return zero
}
//
// 找 (px,pz) 半径内最近的车道,并把点投影到它的弦上(T = 归一化里程)。
// 只看 XZ 平面:竖直方向(y) 对「哪条路最近」没有意义,而我们本来也只有端点。
// ⛑⛑ 2026-09-30:只返回 Lane.Routable 的车道(最大强连通分量内的)。
// 吸附到孤儿车道上 ⇒ Dijkstra 永远解不出路 ⇒ 按自动驾驶没反应。详见 MarkRoutableLanes。
// 车流的 TryPickSpawn 不受影响(那条路径不走这里)。
//
func GameRouter_VehicleLanes_TrySnapLane(ctx context.Context, px float32, pz float32, maxRadius float32, snap any) bool {
// Router implementation for GameRouter_VehicleLanes_TrySnapLane
return true
}
//
// 同 TrySnapLane(float, float, float, out LaneSnap),多一个横向偏离过滤。
// ⛑⛑ 召唤车「停在人行道 / 花坛里」的另一半根因:停车点是玩家坐标投影到最近车道弦上
// 得到的,弯车道时这个点同样在路面之外。给 (如 1.5)
// 即可只接受贴近真实路面的投影位置。
// ⛑ 传 0(= 上面那个重载)保持旧行为,自动驾驶导航走的就是那条,别动它。
//
func GameRouter_VehicleLanes_TrySnapLane_2(ctx context.Context, px float32, pz float32, maxRadius float32, maxLateralMeters float32, snap any) bool {
// Router implementation for GameRouter_VehicleLanes_TrySnapLane_2
return true
}
// 一条车道的弦与长度。找不到返回 false。
func GameRouter_VehicleLanes_TryLaneGeometry(ctx context.Context, handle int32, bx float32, by float32, bz float32, ex float32, ey float32, ez float32, length float32) bool {
// Router implementation for GameRouter_VehicleLanes_TryLaneGeometry
return true
}
// 后继车道(多值)。没有后继返回空数组。
func GameRouter_VehicleLanes_NextOf(ctx context.Context, handle int32) []any {
// Router implementation for GameRouter_VehicleLanes_NextOf
return nil
}
//
// 规划一条**直行优先**的路径:从起始车道出发,每一步都挑「方向最贴合当前朝向」的后继。
// 返回接下来要走的车道序列(不含起始车道本身)。
// ★★ 为什么需要它(2026-09-28):车流原来是每到一条车道就**随机挑后继**(PickNext),
// 于是车会乱拐、掉头、钻死路、很快被回收 —— 用户看到的就是「开很短距离就停下/消失」。
// 有了预规划路径,车沿道路连续跑 1~2 段甚至更远,观感从"随机乱窜"变成"正常行驶"。
// ★ 不直接用导航那套 Dijkstra:Dijkstra 求的是**到指定目标**的最短路,
// 而车流没有目的地,要的是"顺着路往前开"。直行优先恰好表达这个意图,
// 且天然排除掉头(dot 为负的后继排最后)。
// ★ 接缝超过 的后继直接跳过 —— 与 Advance 的硬上限一致,
// 否则规划出来的路径里会混进几百米的瞬移跳。
//
func GameRouter_VehicleLanes_PlanRoute(ctx context.Context, startLane int32, maxHops int32, maxHop float32) []any {
// Router implementation for GameRouter_VehicleLanes_PlanRoute
return nil
}
//
// 沿车道推进指定距离(米)。换道时把 切到下一条并把里程归零。
// 返回 false = 走到路网死路,调用方应当回收这辆车。
// ★ :只在接缝 ≤ 该值的后继里挑。捐赠端 next 是多值后继,
// 实测 5.4% 的后继接缝 &gt; 12m(最大 340m),直接随机挑会让车「瞬移一段甚至倒退」。
// 找不到合格后继时退回「接缝最小」的那条,并如实把 Gap 报给调用方。
//
//
// 换道接缝的**绝对硬上限**(米)。超过就判定"开到尽头",返回 false 让调用方回收。
// 为什么需要它:PickNext 在所有后继都被"方向反折"过滤掉时会退回 fallback
// (全局最小接缝)—— 那条可能远在天边。实测日志里出现过 worstGap=394.1m,
// 而车道数据本身 p99 接缝就有 347m、max 2965m(>30m 的占 13.92%)。
// 跳过去就是几百米的瞬移,比车在路口消失难看得多 —— 宁可回收。
//
func GameRouter_VehicleLanes_Advance(ctx context.Context, s any, meters float32, rng any, maxGap float32, hop any, maxHop float32, preferredNext int32) bool {
// Router implementation for GameRouter_VehicleLanes_Advance
return true
}
//
// 挑下一条车道:在「接缝最小」的基础上放宽 米的容差再随机选,
// 并排除方向反折的后继(那会让车掉头,视觉上像倒退瞬移)。
// ★ 为什么是**相对容差**而不是「接缝 ≤ N 米」的绝对阈值:
// 实测 400 起点 × 600 跳,绝对阈值 8m 会让 15.05% 的衔接 &gt; 12m、p99 达 607m;
// 而「总选最小缝」只有 2.55% &gt; 12m、p99 50m。原因是绝对阈值一旦命中就随机选,
// 容易拐进后继质量差的车道;贪心取最近则倾向留在连贯的主干道上。
// 所以 maxGap=0 = 严格取接缝最小(并列最小里随机),是实测最优。
//
func GameRouter_VehicleLanes_PickNext(ctx context.Context, from any, table any, rng any, maxGap float32, gap float32) int32 {
// Router implementation for GameRouter_VehicleLanes_PickNext
return 0
}
// 服务端侧的线性插值的粗略世界坐标(只用于 AOI 判定 / 日志,客户端自己有真实曲线)。
func GameRouter_VehicleLanes_PositionOf(ctx context.Context, s any, x float32, y float32, z float32) any {
// Router implementation for GameRouter_VehicleLanes_PositionOf
var zero any; return zero
}
// 距当前车道末端还有多少米(用于「接近路口就减速」)。
func GameRouter_VehicleLanes_RemainOf(ctx context.Context, s any) float32 {
// Router implementation for GameRouter_VehicleLanes_RemainOf
return 0
}
// 当前车道的限速(m/s)。车换道后会变,所以每 tick 都要重取。
func GameRouter_VehicleLanes_LimitOf(ctx context.Context, s any) float32 {
// Router implementation for GameRouter_VehicleLanes_LimitOf
return 0
}
//
// 朝向 弧度(0 = +Z,顺时针为正),由车道端点方向推出。
// ★ 2026-09-21 修正:原先这里返回「度」却被直接填进 ClientVehicleInitData.Facing,
// 实测线上包里 Facing = -88.5 —— 那是度数当弧度用了。Facing 是弧度,
// EulerAngles 才是度(调用处各自换算)。
//
func GameRouter_VehicleLanes_FacingOf(ctx context.Context, s any) float32 {
// Router implementation for GameRouter_VehicleLanes_FacingOf
return 0
}
type DriveState struct {
Lane int32 `json:"lane"`
Distance float32 `json:"distance"`
}
// ---------------------------------------------------------------------------------------------
// 路线规划(GameRouter.VehicleNav.cs)用的取值口。数据仍然只有这一份:
// 只为「最近车道」另建了一份索引(按起点/中点/终点三个关键 cell 挂号),因为 try 9 是长车道
// 只按起点挂号会漏掉「从路中间上车」的情形,而我们不能动 TryPickSpawn 那张 begin-only 索引
// (改了会改变 T3-7 车流的随机出生分布)。
// ---------------------------------------------------------------------------------------------
type NavIndex struct {
Cells map[any][]int32 `json:"cells"`
}
// 一次 TrySnapLane 的结果。
type LaneSnap struct {
Handle int32 `json:"handle"`
T float32 `json:"t"`
Distance float32 `json:"distance"`
X float32 `json:"x"`
Y float32 `json:"y"`
Z float32 `json:"z"`
}
// 一次车道衔接的细节(诊断用)。
type LaneHop struct {
FromLane int32 `json:"fromLane"`
ToLane int32 `json:"toLane"`
// 旧车道终点 → 新车道起点的接缝距离(米)。越大越像瞬移。
Gap float32 `json:"gap"`
}