Skip to content

实测数据

这里记录了一些笔者实测的数据,以及一些笔者的猜测。

JSON 读取规则

  1. 读取到重复的属性时,后者会被忽略;这与 JavaScript 的 JSON.parse方法不同。

    • 参考实测:{"bpm":120,"bpm":60} 等价于 {"bpm":120}
  2. 目前为止,浮点数的精度为 float 而非 double

    • v2.5.0 之前,记录精度与 JavaScript 一致,如 2.2 记为 2.200000047683716

    • v2.5.0 及后续版本,长度被尽可能压缩,如 29.217391967773439 记为 29.217392

  3. 数字类型支持科学计数法表示;不支持的格式会读取失败,表现为谱面无法打开。

    • 参考正则表达式:/^-?(0|[1-9]\d*)(\.\d+)?([Ee][+-]?\d+)?$/
  4. 所有的属性值都是非必需的;即存在默认值,但不保证其符合规范。

    • 默认值对应类型的空值,如int0float0.0Array[]Object{}

    • int若为其他类型的数值,将被强制取整(非四舍五入),如"3.9Music"转换为3

游戏画面

右上角分数显示规律(基于v2.3.1
  • 实时分数大于等于1000000时,始终显示为1000000

  • 否则存在以下实测结果(部分结果使用技术手段得到):

    实时分数右上角显示
    7812.50007813
    -33333.30-033332
    0-2147483648

    只有对实时分数先加 0.5 再取整,才能满足上述实测结果。

  • 以下是笔者对此的 JavaScript 实现:

js
// 模拟右上角分数显示逻辑
function getScoreText(score) {
  score = isFinite((score += 0.5)) ? score | 0 : 1 << 31;
  if (score >= 1e6) return '1000000';
  return '0' + (score / 1e5).toFixed(5).replace('.', '');
}
音符渲染细节(基于v2.3.1
  • 音符currentFloorPosition 小于 -0.001未打击时不会被渲染。

    • 由于浮点误差的存在,该判定极其复杂,难以用自然语言描述。

    以下是笔者对此的 JavaScript 实现:

js
// 获取note最大可见位置,当判定线实时位置超过此值时,note不会被渲染
function getMaxVisiblePos(x) {
  const n = Math.fround(x);
  if (!isFinite(n)) throw new TypeError('Argument must be a finite number');
  const magic = 11718.75;
  const prime = n >= magic ? 2 ** Math.floor(1 + Math.log2(n / magic)) : 1;
  const a = n / prime + 0.001;
  const r = Math.fround(a);
  if (r <= a) return r * prime;
  const a_ = new Float32Array([a]);
  new Uint32Array(a_.buffer)[0] += a_[0] <= 0 ? 1 : -1;
  return a_[0] * prime;
}
  • 长度0Hold 音符不会被渲染。

    • 这包含两种情况:speed0holdTime(取整后)为 0

    • holdTime 的取整逻辑在负数的表现实测比较奇怪:

      • -0.999 -1的结果为0-1.001 -2的结果为-1

    以下是笔者对此的 JavaScript 实现:

js
// 模拟 holdTime 的取整逻辑
function floor(t) {
  t = Math.floor(t);
  return t < 0 ? -~t : t;
}
  • v2.0.0及后续版本中,音符实时垂直距离 Y(t)>2 H 时变得不可见。

    speed * currentFloorPosition > 3.3333336 →音符不可见

    • 实测数据:3.3333336 正常显示,3.3333337 消失。

    • 分析:这里的 3.3333336 为浮点数,乘以 0.6 刚好等于 2

      如果是 3.3333337 则计算结果为 2.000000238418579,略大于 2

  • Hold 音符的打击特效间隔似乎与对应判定线 bpm 成反比,且与实际打击时间无关。

    • bpm30 时打击特效间隔为 1 秒,bpm60 时打击特效间隔为 0.5 秒(需要验证)。

    • 打击特效触发时刻不随实际打击时间变化,提前/延迟打击不会提前/延迟打击特效。

判定线渲染细节(基于v2.3.1
  • 以下像素数据均为 1920x1080 的屏幕实测。

    • 长度:5.76 H (6220.8 px)

    • 宽度:0.0075 H (8.1 px)

      • 实测:(6.712±0.997)×103 H
    • 不透明度叠加公式:a=a1+a2a1a2

    • 颜色值:#ffffff#a2eeff(FC)、#feffa9(AP)。

  • 谱面读取速度事件时不会使用 floorPosition 原始值,而是实时计算。

    • 第一个事件的 startTime 不为 0 时,其 floorPosition 计算值为 startTime / bpm * 1.875

      相当于在其之前插入一个起止时刻分别为 0startTimevalue1 的事件。

    • 无论其后的事件时间如何,其 floorPosition 均正常计算。

  • 接下来考虑其它事件各种不规范的情况:

    • 事件与上一个事件相离时,该事件在相离的这段时间进行解析延拓;

      事件与上一个事件相交时,该事件在相交的这段时间被忽略。相当于:

      • 该事件的 startTime 变为上一个事件的 endTimeendTime 不变;

      • 同时保持 (end-start)/(endTime-startTime)end 不变,重新计算 start

      • 同时保持 (end2-start2)/(endTime-startTime)end2 不变,重新计算 start2

    • 事件的 startTime 大于或等于 endTime 时,该事件被忽略。

    • 以下行为会导致谱面停顿或播放异常:

      • 判定线数目超过 100

      • 任何一条判定线的 bpm 为负数或零;

      • 任何一个事件列表为空数组;

      • 谱面运行时刻超过任何一个事件列表所有事件的结束时刻;

    • 异常的具体表现:音符不再垂直移动,问题所在及其后的判定线动画中断。

谱面性能分析(基于v2.3.1
  • 音符或事件数目没有上限,但过多会导致谱面打开延迟,对于音符尤为明显;

    实测分析音符时间复杂度为 O(n2),事件时间复杂度为 O(n)

    音符数目谱面打开延迟
    163840.25s
    327681.50s
    655366.00s
    13107230.25s
    262144159.75s
音频与进度条动画流程(基于v2.3.1
  • 音频大小可能影响谱面的播放状态:

    • 要使谱面正常播放,音频大小至少 4B(否则谱面静止)。

    • 要使谱面正常结束,音频大小至少 180B(否则进度条停在中间)。

  • 笔者将谱面播放过程分为以下几个阶段(以下 offset 均指根结构的 offset 值):

    • 播放开始过渡动画,即 UI 从上下两侧淡入,表演判定线从中央扩展。

    • 谱面从头开始播放,进度条从 0% 开始移动。

      • 进度条范围为音乐时长,值为谱面播放进度。

      • 对于音乐:offset 为负时延迟 offset 的绝对值秒播放,否则立即播放。

      • 对于谱面:延迟 offset 的绝对值秒,然后从 (音乐进度 - offset) 处开始播放。

    • 音乐进度到达 (音乐时长 - 0.22049) 秒时,谱面和进度条停止移动,音乐停止播放。

    • 播放结束过渡动画,即 UI 向上下两侧淡出,画面向中间收缩然后结算。

  • 由上可知,进度条位置与谱面进度关联,但结算时刻与音乐进度关联,因此存在以下情况:

    • offset 为负时,进度条可能会提前到达终点甚至超过终点;

    • offset 为正时,进度条未到达终点时已经结算。

    • 特别地,offset 大于 (音乐时长 - 0.22049) 将直接结算。

    offset 相对于音乐时长很小,且官谱从未出现过负数 offset

    因此以上情况不会影响游戏体验,可以放心游玩。

补充

实测数据(基于v1.6.11

  • SpeedEvent.floorPosition 不会被读取,实际上以 float 精度实时计算;
  • 判定线长度与屏幕高度成正比,比值为 5.76
  • Note.positionX 单位为 0.05625 个屏幕宽度,值为 1 时常用于时钟效果;
  • Note.floorPosition 单位为 0.6 个屏幕高度;
  • 事件时间 T 等于现实时间(秒)乘以 JudgeLine.bpm 除以 1.875

实测数据(基于v2.3.1

  • 感谢 luch4736 提供的 adb 教程;
  • 暂停按钮的判定区间 (249.6,-1.0)~(336.0,102.6) 测试设备分辨率:2400x1080

CC-BY-NC-4.0 Licensed