RA2 全图预览 + 极简战况:功能与实现原理
本文对应 ra2-combined.user.js 2.1.1,描述当前代码的实际功能、两站适配方式、数据流、迷雾透明与图标实现,以及使用和维护时需要了解的边界。
脚本文件:ra2-combined.user.js
阅读建议:使用与功能见第 1~3 节;实现原理见第 4~10 节;刷新、存储、排查与维护见第 11~15 节。
1. 项目目标与组成
本项目把四个油猴脚本的主要能力整理到一个可安装的 userscript 中,对外提供两个功能:
- 全图预览:独立的地图对象分布面板,以及游戏主画面的战争迷雾半透明处理。
- 极简战况:显示玩家经济、单位和建筑明细、生产队列,并支持独立副屏窗口。
支持的地址:
| 游戏 | 匹配地址 | 战况实现 |
|---|---|---|
| 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 安装
- 在浏览器中安装支持 userscript 的扩展,例如 Tampermonkey。
- 新建脚本,将
ra2-combined.user.js的完整内容粘贴进去并保存,或通过扩展支持的方式导入文件。 - 停用此前分别安装的四个源脚本以及旧合并版,避免重复面板、重复钩子和材质相互覆盖。
- 打开任一支持的游戏地址,完整刷新页面后进入对局。
元数据配置为:
@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 数据缺失的含义
应区分三个状态:
- 没有对象或没有排队:接口可读,但返回的列表为空。
- 尚未捕获接口:脚本还未找到对应玩家或生产管理器。
- 当前客户端没有公开相应数据:即使发现 API,也无法取得该字段。
Chrono Divide 可能显示“当前未捕获到该玩家的只读生产队列”。staging 则区分“暂无队列”和“队列数据未公开”。这些文案是根据数据源是否存在作出的判断,无法证明服务器端是否确实没有生产。
staging 的未知资金、电力通常显示为 --。Chrono Divide 的部分数字字段缺失时会归一化为 0,因此异常的零值也需要结合接口发现状态判断。
4. 整体架构与数据流
文件外层是一个私有立即执行函数,内部有三块主要程序:
- Chrono Divide 战况程序与基础工具。
- 两站共用的地图预览及迷雾处理程序。
- 仅在 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 定位迷雾渲染对象
共用程序通过以下方式寻找迷雾:
- 从模块系统寻找
MapShroudLayer,包装其create3DObject()和update()。 - 观察
THREE.Object3D.add()中名称包含fog或shroud的对象。 - staging 额外从运行时根对象扫描相关渲染节点,并持续检查已记录材质。
- 对名称或属性符合迷雾特征的 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() 按如下逻辑选择图标:
- 根据单位代码寻找
CAMEO_ASSETS指定的图标文件。 - 解析单位记录中的图标字段、文件名映射和别名。
- 优先匹配 Wanger 图集。
- 没有匹配项时,按同类信息和单位代码尝试 Chrono 基础图集。
- 两份图集均未命中时,允许使用数据中的有效图片 URL。
- 没有可用图片时,仅显示文字,不用单位符号伪装成图标。
已命中图集时,图集优先于记录中的外部图片 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 处理能否重复执行、图集索引与元数据是否一致,以及缺失数据是否被正确表达。
/