igofreely
发布于 2026-10-15 / 56 阅读
0
0

RA2 全图预览 + 极简战况

RA2 全图预览 + 极简战况:功能与实现原理

本文对应 ra2-combined.user.js 2.1.1,描述当前代码的实际功能、两站适配方式、数据流、迷雾透明与图标实现,以及使用和维护时需要了解的边界。

脚本文件:ra2-combined.user.js

阅读建议:使用与功能见第 1~3 节;实现原理见第 4~10 节;刷新、存储、排查与维护见第 11~15 节。

1. 项目目标与组成

本项目把四个油猴脚本的主要能力整理到一个可安装的 userscript 中,对外提供两个功能:

  1. 全图预览:独立的地图对象分布面板,以及游戏主画面的战争迷雾半透明处理。
  2. 极简战况:显示玩家经济、单位和建筑明细、生产队列,并支持独立副屏窗口。

支持的地址:

游戏 匹配地址 战况实现
Chrono Divide https://game.chronodivide.com/* 以 cdhelper.js 的查询、汇总和界面逻辑为基础
Wangerhuoda staging https://staging.wangerhuoda.com/* 独立使用以 wangerhuoda-units.user.js 为基础的采集与战况逻辑

参考源文件:

源文件 合并版主要沿用的能力
cdhelper.js Chrono Divide API 发现、战况汇总、队伍明细、副屏界面和基础图集
mapreview2.js 地图坐标采样、Canvas 预览、迷雾材质及乘法混合 shader 处理思路
mapriveiw.fixed.user.js 分批采样、材质原值记录、钩子恢复及生命周期处理思路
wangerhuoda-units.user.js staging 对象发现、玩家与生产队列适配、全玩家战况、专属图集和副屏自适应布局

合并版保留一个安装入口,同时保留两站必要的差异化逻辑。原始四个文件是参考材料,安装合并版后不需要同时启用。

2. 安装与操作

2.1 安装

  1. 在浏览器中安装支持 userscript 的扩展,例如 Tampermonkey。
  2. 新建脚本,将 ra2-combined.user.js 的完整内容粘贴进去并保存,或通过扩展支持的方式导入文件。
  3. 停用此前分别安装的四个源脚本以及旧合并版,避免重复面板、重复钩子和材质相互覆盖。
  4. 打开任一支持的游戏地址,完整刷新页面后进入对局。

元数据配置为:

@grant   none
@run-at  document-start
@noframes

脚本尽早启动以捕获游戏 API 和渲染对象;界面通常在 DOM 就绪后创建。它只在顶层页面运行,不依赖 GM_* 接口,也没有额外的 @require 下载依赖。

2.2 全图预览操作

预览面板默认位于右下角,包含地图画布、对象数量、当前 Tick 和两个按钮。

操作 效果
暂停/继续 控制预览的采样与绘制;staging 的后台快照发布仍可继续
圆点按钮 将面板缩为悬浮点,再点恢复
调整浏览器窗口大小 重新计算画布尺寸和绘制比例

暂停预览或缩成圆点,不会关闭主地图的迷雾透明处理。 当前没有独立的“迷雾透明开关”。

2.3 Chrono Divide 战况操作

主界面默认位于右上角:

  • 拖动信息条的手柄可移动位置。
  • “全部玩家明细”可展开或收起逐型号统计。
  • 明细中可折叠我方队伍、调整字号和面板宽高,也可恢复默认大小。
  • 最小化按钮将战况缩为可恢复的小点。
  • “副屏弹窗”打开独立窗口;把窗口拖到副屏后,可使用“铺满屏幕”。

2.4 staging 战况操作

主界面默认位于左上角,按玩家显示资金、电力、分类单位、场上施工和生产队列。

  • 拖动标题栏可移动主面板。
  • “收起面板”隐藏主面板,并显示恢复用的小点。
  • “副屏弹窗”打开全玩家战况窗口,窗口会根据人数和可用空间安排列数与缩放。
  • 副屏提供全屏和关闭按钮。
  • 主面板以文字为主;单位、建筑的图集图标在副屏显示。

副屏由用户点击按钮时创建。如果浏览器拦截弹窗,需允许对应站点弹窗后重试。全屏仍受浏览器交互限制,可使用 Esc 退出。

3. 功能说明

3.1 独立全图预览

全图预览是一个 Canvas 2D 对象分布图,根据游戏对象的坐标绘制:

图形 含义
方块 建筑,包括能被类型识别为建筑的防御设施
圆点 可移动单位
紫红色圆点及白色边框 数据中带有隐形、伪装、潜航或雷达不可见标记的单位
边界矩形 采集到的地图尺寸;尺寸不可用时使用估算范围

普通对象颜色由所有者名称生成稳定色相,建筑与单位使用不同亮度。预览图颜色不保证等同于游戏中的玩家颜色。

预览只显示能获取到数据且能解析出坐标的对象。它不重建地形贴图、矿石、道路、树木或完整游戏小地图,也没有点击移动镜头、框选单位和下达命令等交互。

3.2 主地图战争迷雾半透明

此功能修改游戏渲染层中的迷雾材质,使原本遮黑的区域变为较淡的暗色覆盖。当前目标透明参数为 opacity = 0.34。

它与独立预览是两条数据和渲染路径:

  • 主地图效果由 Three.js 迷雾材质决定。
  • 预览中有没有敌方单位、建筑,由采集到的对象列表和坐标决定。

因此,“迷雾变透明”不等于“游戏已经渲染未探索区域中的全部模型”。即使某个对象能被统计或绘制到预览面板,游戏主画面仍可能根据可见性规则不显示它。

3.3 极简战况

两站都围绕玩家状态与资产汇总展示信息,但界面布局和部分字段有差别。

内容 Chrono Divide staging
当前玩家识别 支持 支持
国家、玩家颜色 尽力读取并归一化 尽力读取并归一化
对局时间 主信息条显示,由 Tick 与 TickRate 换算 快照保留 Tick,当前战况面板不单独显示时间
资金 当前玩家摘要及各玩家明细 各玩家明细
电力 摘要显示余量,明细显示供电/耗电 明细显示供电/耗电
单位、建筑逐型号数量 支持 支持
队伍与敌我关系 支持队伍归类、我方折叠 主要按玩家排列并标识本方
场上施工独立分组 未设置 staging 式独立分组 支持,取决于施工字段是否可读
生产队列 支持,取决于队列源是否可获取 支持,兼容多种队列结构
录像/观察模式 有专门识别与展示分支 过滤观察者并显示可读的参战玩家;效果取决于可发现的数据源
副屏图标与全屏 支持 支持

单位通常分为建筑、步兵、坦克与载具、飞机、舰船和其他;生产队列另分建筑、防御设施、步兵、车辆、飞机和舰船六类。

统计项以“名称 × 数量”展示。staging 在数据包含进度时还会展示百分比。场上施工与已部署分类可能包含同一个尚未完工的建筑,不能直接把两个分组相加当作总资产。

3.4 数据缺失的含义

应区分三个状态:

  1. 没有对象或没有排队:接口可读,但返回的列表为空。
  2. 尚未捕获接口:脚本还未找到对应玩家或生产管理器。
  3. 当前客户端没有公开相应数据:即使发现 API,也无法取得该字段。

Chrono Divide 可能显示“当前未捕获到该玩家的只读生产队列”。staging 则区分“暂无队列”和“队列数据未公开”。这些文案是根据数据源是否存在作出的判断,无法证明服务器端是否确实没有生产。

staging 的未知资金、电力通常显示为 --。Chrono Divide 的部分数字字段缺失时会归一化为 0,因此异常的零值也需要结合接口发现状态判断。

4. 整体架构与数据流

文件外层是一个私有立即执行函数,内部有三块主要程序:

  1. Chrono Divide 战况程序与基础工具。
  2. 两站共用的地图预览及迷雾处理程序。
  3. 仅在 staging 启动的 Wanger 战况程序。

通过域名分流,staging 不启动 Chrono Divide 战况界面;Chrono Divide 也不启动 Wanger 战况程序。基础图集通过外层私有变量 chronoCameos 共享,避免再次内嵌相同图片。

flowchart TD
    A[单个 userscript 启动] --> B{当前域名}
    B -->|game.chronodivide.com| C[Chrono API 发现与战况采集]
    B -->|staging.wangerhuoda.com| D[Wanger 对象发现与独立适配]
    C --> E[Chrono 主面板与副屏]
    D --> F[Wanger 主面板与副屏]
    C -->|ra2web:game-api| G[共用 Canvas 预览]
    D -->|ra2host:staging-map| G
    D -->|可发现的运行时根对象| H[staging 迷雾对象扫描]
    A --> I[共用迷雾类与 Three.js 钩子]
    H --> J[迷雾材质透明处理]
    I --> J

脚本没有自行实现游戏网络协议,也没有独立请求远端战况服务。数据来自游戏已加载到当前页面的 JavaScript 对象;图标来自文件内嵌的图片,未命中图集时 staging 也允许使用数据中已有的图片 URL。

5. Chrono Divide 数据采集原理

5.1 多入口发现 API

脚本组合使用两类方式:

  • 方法观察钩子:查找 GameApi、PlayerApi、ProductionApi、BotManager,包装游戏原本会调用的方法,在保持原返回值的基础上记录实例和关联关系。
  • 有限对象图扫描:从窗口上的已知对象、应用状态、React 相关入口等位置出发,查找符合 API 特征的对象。

钩子关注的方法包括 getCurrentTick()、getPlayerData()、BotManager.init() 等。目的在于获得实例引用,不是替换游戏计算结果。

createPassiveScanner() 使用 WeakSet 避免重复遍历,优先检查游戏相关字段,并把工作分到空闲回调或后续任务中。代码设置了深度、字段数和总对象量限制,以免无限扫描页面。

如果发现的是另一种公开 API,createNativeGameApiAdapter() 会将它适配成统一的查询形式。

5.2 构建快照

collectSnapshot() 读取当前 Tick、TickRate、本方玩家、资金、电力、对象列表和生产队列,并构建供界面使用的普通数据对象。

主要查询包括:

getCurrentTick()
getTickRate() / getBaseTickRate()
getPlayers() / getCombatants()
getPlayerData(playerName)
getAllUnits()
getUnitData(id)
getQueueData(queueType)

collectAllCombatantIntel() 按所有者和对象类型汇总逐型号数量;collectEnemyIntel() 结合联盟关系生成敌方汇总。生产数据与玩家名称关联后,再拼接到各玩家记录上。

对局时间的基本计算是:

对局秒数 = Tick / TickRate

当本方身份不可用、玩家被识别为观察者,或不是参战者时,脚本可进入录像/观察模式,显示参战玩家明细,而不是伪造一个本方经济状态。

5.3 给地图预览复用 API

战况程序通过页面内的 CustomEvent 发布 ra2web:game-api,事件包含 gameApi 和 active 状态。

地图预览接收该引用后,自行采样坐标,不再完整复制一套战况 API 发现程序。API 失效时,预览取消未完成采样、清空状态并尝试恢复此前记录的视觉修改。

6. staging 独立适配原理

6.1 为什么使用独立路径

staging 的游戏实例、玩家对象、世界对象及生产管理器可能通过不同字段或模块暴露。直接要求它符合 Chrono Divide 的完整 API 结构,容易出现“地图 API 已发现,战况仍未启动”或“只读到了可见对象”的问题。

因此,合并版保留 Wanger 程序的独立发现、适配、汇总与界面流程,再把地图快照交给共用预览程序。

6.2 发现游戏和当前对局

discover() 综合检查以下入口:

  • werhd、ClientApi、CdApi、battleControl、worldInteraction 等窗口对象。
  • System/SystemJS 模块导出。
  • React 根节点关联的状态对象。
  • 游戏、世界、玩家、生产队列相关的嵌套字段。
  • 页面上下文桥接所提供的对象引用。

stageGameFrom() 从候选根对象中识别游戏实例;stageAdapter() 则把当前游戏包装成统一查询接口。

hookBattleControl()、hookLifecycle() 和 Tick 观察共同帮助追踪当前对局。切换游戏实例,或检测到较明显的 Tick 回退时,clearMatchState() 会清空玩家、队列和对象缓存,减少上一局数据混入下一局的风险。

6.3 从全局对象集合读取单位和建筑

stageObjects() 优先尝试世界对象集合,如:

game.world / game.worldState / game.simulationWorld
world.getAllObjects() / world.getObjects()
world.objects / world.objectRegistry / world.entities
game.getAllUnits() 与对象详情查询
player.getOwnedObjects()

具体取用顺序取决于哪些入口存在。对具备 isTechno() 的集合,优先筛选游戏中的单位与建筑类对象;同时过滤已销毁、未生成和中立对象,并按 ID 或对象引用去重。

当当前采样暂时为空时,代码允许回退到最近 30 秒内记录的对象集合。因此,短暂的数据中断可能表现为旧对象暂留,不能把画面上的每个记录都当作本帧实时状态。

找到对象后,通过玩家名称、玩家 ID、House ID、槽位等别名归属到玩家。此处理用于兼容“所有者是名字”“所有者是对象”“只有数字索引”等不同结构。

6.4 经济和生产队列适配

经济字段由 stagePlayerStats()、playerEconomy() 等函数从游戏、玩家和已观察到的 API 中读取并归一化。

生产队列不只寻找一个固定字段,而是兼容多种方法与结构:

getQueueData / getQueue / getBuildQueue
getProductionQueue / getQueueByType
productionQueues / queues / queueData
buildingQueue / infantryQueue / vehicleQueue 等
items / entries / currentItem / pendingItems 等

installPlayerHooks() 和 installProductionHooks() 还会观察游戏自己的读取操作,记录玩家与生产 API 的关系,补足单靠扫描找不到的引用。

queueItems() 将不同结构整理为统一的名称、代码、数量和进度;同型号合并后显示,进度统一到 0~100 范围。某些情况下会构造游戏已有的 ProductionApi 查询包装器,但脚本没有主动调用生产、取消生产或下达游戏命令的方法。

6.5 强数据源与公开 API 回退

优先使用与当前对局绑定的内部游戏或世界对象。无法获得适当对象时,可回退到 werhd 的玩家、单位和生产队列查询。

这个回退结果会标记为非全局。即使调用名为 units("all"),也不据此承诺它包含所有未探索区域对象。

界面中的“全图对象/可见对象”是脚本根据 API 能力作出的范围判断,不是服务端对数据完整性的认证。

6.6 发布地图快照

publishMapSnapshot() 基于当前 staging 适配器取得对象,保留底层记录可访问的坐标和状态,整理所有者、建筑分类、地图尺寸与 Tick,再通过 ra2host:staging-map 发布。

共用预览程序接收后转成:

{
  units: [{ x, y, owner, building: false, cloaked }],
  buildings: [{ x, y, owner, building: true, cloaked: false }],
  width,
  height,
  tick
}

这个过程独立于 Chrono Divide 的 ra2web:game-api 事件,因此 staging 的地图更新不再要求 Chrono 战况采集器运行。

7. 地图坐标与 Canvas 绘制原理

7.1 坐标解析

position() 优先读取 rx / ry,再尝试 tile.rx / tile.ry,最后读取 worldPosition 中的平面坐标。其中世界坐标的 x / z 被用作地图二维坐标。

没有可靠坐标的对象会跳过绘制;这类对象仍可能出现在战况统计中,所以地图点数与战况数量不一定相同。

地图尺寸优先来自 getRealMapSize()、getMapSize() 或等价字段。取不到时,以默认范围及对象最大坐标估算,不是重新测量游戏地图边界。

7.2 缩放和居中

设画布可用尺寸为 W × H,地图尺寸为 MW × MH,边距为 P:

scale = min((W - 2P) / MW, (H - 2P) / MH)
offsetX = (W - MW × scale) / 2
offsetY = (H - MH × scale) / 2

screenX = offsetX + mapX × scale
screenY = offsetY + mapY × scale

这是保持比例的二维分布绘制,不会复现游戏主画面的等距投影。画布实际像素尺寸还会乘以 devicePixelRatio,降低高分屏上的模糊。

Chrono Divide 采样按批推进,每批最多处理约 100 个对象,同时以约 4 毫秒作为批内时间预算,批间通过定时任务让出执行权。单轮预览最多检查 10,000 个对象。

8. 战争迷雾透明原理

8.1 定位迷雾渲染对象

共用程序通过以下方式寻找迷雾:

  1. 从模块系统寻找 MapShroudLayer,包装其 create3DObject() 和 update()。
  2. 观察 THREE.Object3D.add() 中名称包含 fog 或 shroud 的对象。
  3. staging 额外从运行时根对象扫描相关渲染节点,并持续检查已记录材质。
  4. 对名称或属性符合迷雾特征的 DOM 覆盖层,尝试设置 CSS 透明度;staging 还使用 MutationObserver 观察后续变化。

定位后,transparentMaterials() 遍历对象的材质和子节点,再交给 transparentMaterial() 处理。

8.2 为什么仅设置 opacity 不够

此前核对的 staging 构建中,迷雾使用 PaletteBasicMaterial 和 Three.js 的 MultiplyBlending。该混合路径的关键关系可简化为:

最终背景颜色 = 原背景颜色 × 迷雾源 RGB

黑色迷雾的 RGB 为 0,会把背景乘成黑色。这个混合方式不会像普通 alpha 混合那样,仅靠降低 alpha 就自动露出背景。

因此,脚本不仅设置:

material.transparent = true;
material.opacity = 0.34;
material.depthWrite = false;

还会调整支持的 fragment shader 输出,使透明部分趋近白色。白色是乘法混合的中性颜色,乘上白色不会压暗背景。

8.3 shader 改写

对于能识别的标准输出:

gl_FragColor = vec4(outgoingLight, diffuseColor.a);

脚本将其替换为:

// MAPREVIEW_SHROUD_ALPHA
gl_FragColor = vec4(
  mix(vec3(1.0), outgoingLight, clamp(diffuseColor.a, 0.0, 1.0)),
  diffuseColor.a
);

若原迷雾颜色为黑色、有效 alpha 为 0.34,则 RGB 近似变成:

1 × (1 - 0.34) + 0 × 0.34 = 0.66

乘法混合后背景保留约 66% 的亮度,形成暗色半透明效果。灰度和透明边缘继续依据原纹理与有效 alpha 处理。

脚本保留原有纹理、几何和 UV,没有重新绘制一张三角形迷雾图层。depthWrite = false 用于避免透明迷雾写入深度并遮挡后续内容。

8.4 2.1.1 修复的三角网格问题

旧合并逻辑第一次成功改写 shader 后,没有记录“已完成改写”的标记。下一次维护时,原始输出表达式已经不存在,代码把它误判为不支持的 shader,并退回 NormalBlending。

staging 会在更新钩子和持续维护中反复处理材质,这个错误很容易触发。混合方式改变后,迷雾网格及纹理的覆盖关系异常,出现用户看到的三角图案。

2.1.1 的处理方式是:

  • 使用 MAPREVIEW_SHROUD_ALPHA 标记识别已处理 shader。
  • 在 WeakMap 中记录原材质的混合方式和 shader。
  • 对原本的乘法混合材质保持或恢复 MultiplyBlending。
  • 遇到无法安全识别的乘法 shader 时保留原样,不再强制退回普通混合。
  • 仅在材质属性确实变化时设置 needsUpdate,避免重复触发无意义的材质更新。

因此,同一材质可以被多次检查,结果仍保持一致。更新脚本后应完整刷新游戏页面,避免新实例读取到旧版本已经破坏的材质作为“原始状态”。

8.5 原值恢复与边界

程序记录材质的 opacity、transparent、depthWrite、blending 和 fragmentShader,并保存受影响 DOM 节点的原始样式。

restore() 会尝试恢复记录值与仍由本脚本持有的方法包装。材质恢复依赖现代浏览器提供的 WeakRef。这属于地图模块的恢复机制,不是整个合并脚本所有定时器和钩子的统一卸载入口;需要彻底停用时,应禁用脚本并刷新页面。

9. CAMEO_SPRITE 图标原理

9.1 两份内嵌图集

CAMEO_SPRITE 包含图片 Data URL 和布局元数据;CAMEO_INDEX 则把图标代码映射到单元格序号。

图集 图片尺寸 单格尺寸 列数 用途
Chrono 基础图集 1024 × 288 64 × 48 16 Chrono 图标,以及 staging 缺失基础图标的补充
Wanger 图集 2496 × 48 64 × 48 39 staging 原有图标及 CFV、CHAR、CMCV 等专属图标

两张图片都内嵌在同一个 userscript 中。Wanger 图集与源脚本一致;Chrono 图集只保存一份,通过私有变量共享,不向全局暴露整份资源。

9.2 staging 图标查找顺序

itemHtml() 与 cameoIconKey() 按如下逻辑选择图标:

  1. 根据单位代码寻找 CAMEO_ASSETS 指定的图标文件。
  2. 解析单位记录中的图标字段、文件名映射和别名。
  3. 优先匹配 Wanger 图集。
  4. 没有匹配项时,按同类信息和单位代码尝试 Chrono 基础图集。
  5. 两份图集均未命中时,允许使用数据中的有效图片 URL。
  6. 没有可用图片时,仅显示文字,不用单位符号伪装成图标。

已命中图集时,图集优先于记录中的外部图片 URL。分类标题中的建筑、步兵、飞机等小符号属于分类标签,仍然保留,和每种单位旁的 CAMEO 图标用途不同。

9.3 图标裁切和缩放

设图标序号为 index,图集列数为 columns:

column = index % columns
row = floor(index / columns)

副屏以 CSS background-image 展示整张图集,再通过负方向 background-position 定位单格:

backgroundPositionX = -column × 显示宽度
backgroundPositionY = -row × 显示高度

backgroundSizeWidth = 图集宽度 / 原单格宽度 × 显示宽度
backgroundSizeHeight = 图集高度 / 原单格高度 × 显示高度

staging 默认参考显示尺寸为 48 × 36,再乘以副屏自适应缩放值。基础图标使用 .base-cameo 区分图片来源,并按自己的 16 列元数据定位;Wanger 图标按 39 列定位。

如果把两份图集误用同一列数或背景尺寸,会出现错图、裁切错误和空白图标。

9.4 为什么保留两张图

Wanger 原图只覆盖部分型号,单独恢复它不能补齐所有基础单位。合并版通过复用现有 Chrono 图集补齐,避免重复放入相同基础资源。

此前比较过将两张图无损合成一张 PNG 的方案:由于两图颜色和编码结构不同,合图反而更大,因此当前保留双图集。为了减少代码而清空 CAMEO_SPRITE.url 会直接破坏图标功能,不应再次使用这种方式压缩。

10. 副屏与界面隔离

两个战况界面均使用 Shadow DOM 隔离内部样式,减少游戏页面 CSS 对字体、按钮和布局的干扰。

副屏是通过 window.open() 创建的同源空窗口,由主页面脚本更新其 DOM。它不会再次连接一场游戏,也不会独立运行一个完整采集器;主窗口提供当前快照。

staging 的 popupColumnCount() 根据人数、窗口宽高和最小列宽估算列数;schedulePopupFit() 对缩放值进行最多 9 次二分试探,寻找能容纳内容的尺寸。缩放同时覆盖文字、间距和图集图标,内容过多时仍允许滚动。

snapshotKey() 用于判断快照是否变化,减少无变化时重建 HTML。经济更新另有 updateEconomyUi() 路径,只更新资金、电力文本,避免每次读取经济状态都重建整个列表。

11. 刷新周期与性能策略

以下为代码的目标检查周期或节流阈值。浏览器后台限速、定时器调度、缓存和对象规模都会增加实际延迟。

模块 周期或阈值 说明
Chrono 类发现 250 毫秒轮询 关键类钩子准备好后停止该轮询
Chrono 被动对象扫描 4 秒 分批执行,设置遍历预算
Chrono 战况 1 秒 页面隐藏时跳过该周期的采集与更新
共用地图检查 1 秒 Chrono 在预览激活时采样;staging 接收外部快照
staging 地图快照发布 2.5 秒,且完整更新时也发布 可能使用缓存的对象集合
staging 完整战况 按人数设置 2~12 秒阈值 由每 1.5 秒调用一次的 update() 检查
staging 经济增量 按人数设置 1.5~5 秒阈值 由每 1 秒检查一次的定时器触发
staging API 再发现 4 秒浅检查、10 秒深检查,另有 60 秒周期发现 已确认多人全局快照后减少部分频繁发现
staging 迷雾已记录材质维护 前台按动画帧,隐藏时约 250 毫秒 重复检查保持幂等,未变化时不标记材质更新
staging 迷雾运行时扫描 大于 2.5 秒阈值 由 1 秒检查循环触发,隐藏页面时跳过

staging 完整战况节流阈值:

已知玩家数/状态 完整战况阈值 经济更新阈值
已进入对局且不超过 1 人 2 秒 1.5 秒
常规 2~11 人 5 秒 1.5 秒
12~23 人 6.5 秒 2 秒
24~47 人 8.5 秒 3 秒
48 人及以上 12 秒 5 秒

例如,5 秒完整更新阈值由 1.5 秒间隔的调用检查,实际可能约 6 秒才更新,不能当作精确 5 秒刷新。

另外,staging 为对象列表、玩家索引、经济、生产源和队列设置了多级缓存。缓存能减少重复遍历,但也意味着单位数量、建造进度和资金不保证在同一帧采样。

Chrono 的全玩家与敌方汇总当前最多检查 getAllUnits() 前 3,000 项;Canvas 预览的 10,000 项上限属于另一条采样路径,不能把两者视为相同统计范围。

12. 本地保存与数据行为

Chrono 界面使用站点自身的 localStorage 保存部分偏好:

键名 内容
cdhelper.position 主战况面板位置
cdhelper.minimized 主面板最小化状态
cdhelper.hideOwnTeam 是否折叠我方队伍
cdhelper.panelSize 主明细面板尺寸
cdhelper.fontSize 主界面字号
cdhelper.secondaryWindowBounds 副屏窗口边界与位置
cdhelper.secondaryPanelSize、cdhelper.secondaryFontSize、cdhelper.secondaryHideOwnTeam 等 副屏配置使用的对应键

staging 的主面板位置与副屏布局主要由当前运行时维护,没有同样的一套偏好持久化逻辑。两个域名的浏览器存储也彼此隔离。

玩家和对象引用、队列缓存、地图快照保存在内存中。脚本本身没有战况上传、导出对局文件或独立遥测逻辑;从数据中加载图片 URL 时,仍可能产生该图片的正常浏览器请求。

“只读”指战况查询和预览不主动下达建造、移动或攻击命令。迷雾功能会修改本地渲染材质、DOM 样式以及相关方法包装,所以并非完全不修改页面运行状态。

13. 常见问题与排查

现象 可能原因与处理
页面有两个战况面板或重复预览 检查旧脚本和源脚本是否仍启用;只保留当前合并版后完整刷新
一直显示等待对局 尚未进入可采集对局、游戏 API 结构变化,或当前实例未被发现
预览为空,但战况有单位 可能能读到统计字段却读不到坐标;检查坐标和地图快照路径
未探索区域单位、建筑不全 当前只发现了可见对象 API,或世界对象集合不完整;透明材质不会生成缺失数据
自己的单位被归到其他玩家或缺失 所有者名称、House ID 或本方玩家识别不匹配,需检查适配和别名归属
生产队列不可用 玩家生产源尚未捕获、队列结构变化,或客户端没有提供该玩家队列
资金、电力停留在旧值 经济数据源失效、缓存尚未刷新、页面后台限速或人数节流
开局瞬间透明,随后黑回去 检查是否启用旧版、其他脚本覆盖材质,或游戏创建了未被捕获的新迷雾材质
迷雾出现三角图案 确认使用 2.1.1,停用旧版后完整刷新;重点排查是否仍被改为 NormalBlending
更新后同一页面仍有旧迷雾异常 热注入可能把旧版已经修改的材质记录为原值,需完整刷新重建游戏材质
副屏部分图标为空 图集中无该型号且没有有效图片 URL;当前只显示文字,不再用符号代替
副屏图标错位或显示其他单位 检查所选图集、索引、列数、背景尺寸和缩放位置是否配套
副屏打不开 允许站点弹窗,或关闭已有同名战况窗口后重试
暂停预览后主地图仍透明 当前设计如此;暂停只控制预览,不是迷雾开关

地图预览状态可在浏览器控制台查看:

window.__ra2Mapreview?.version

当前应为 2.1.1。该对象仅代表地图模块,并不是全部战况程序的统一控制器。

14. 验证范围与已知限制

2.1.1 此前已完成的检查包括:

  • node --check 语法检查。
  • 两站 document-start 启动的模拟环境检查。
  • 使用 staging 当时公开版本的实际 fragment shader 验证材质改写。
  • 同一材质连续处理 1,000 次,保持 shader 和乘法混合稳定,未变化时不重复标记更新。
  • 模拟 shader、混合方式和透明度被外部改动后重新应用处理。
  • 不支持的乘法 shader 保持原样,以及材质原始值恢复检查。
  • 常见基础和 staging 专属单位图标映射、原图完整性、索引范围及副屏缩放坐标检查。

上述检查不等于已经验证所有地图、人数、游戏模式和浏览器上的真实 GPU 对局画面。后续游戏版本如果修改模块名、API、坐标字段或 shader,需要重新核对。

其他边界:

  • “全图”表示尽量读取当前页面可获得的全局集合,不保证客户端永远拥有全部敌军数据。
  • 列表统计、Canvas 点位和游戏主画面模型的可见性各自独立。
  • 静态名称和图标映射无法覆盖未来所有新增单位。
  • 存在短期缓存、遍历上限和人数节流,大规模对局可能出现延迟或范围截断。
  • staging 的页面隐藏策略与 Chrono 不完全一致;不能假设切到后台后所有任务都会停下。
  • 生产队列只呈现已读到的状态,不推测或补造不可读的队列。

15. 维护入口

修改时可按问题类型定位函数:

问题类型 主要入口
Chrono API 未发现 installObservationHooks()、findObservationClasses()、createPassiveScanner()
Chrono 战况错误 collectSnapshot()、collectAllCombatantIntel()、collectPlayerDetails()、collectQueues()
staging 游戏识别或跨局串数据 discover()、stageGameFrom()、stageAdapter()、clearMatchState()
staging 单位建筑缺失或归属错误 stageObjects()、inventory()、ownerBuckets()、playerRecord()
staging 经济或队列错误 stagePlayerStats()、playerEconomy()、productionFor()、queueItems()
staging 地图没有点位 publishMapSnapshot()、onStagingMap()、position()
迷雾变黑、三角网格 captureRenderHooks()、patchShroud()、transparentMaterial()
图标错误 CAMEO_SPRITE、CAMEO_INDEX、cameoIconKey()、itemHtml()、setPopupScale()
副屏排版或字号问题 popupColumnCount()、schedulePopupFit(),以及 Chrono 的副屏适配逻辑

维护时应同时检查:两站启动是否仍然正确分流、shader 处理能否重复执行、图集索引与元数据是否一致,以及缺失数据是否被正确表达。

/


评论