Rounds 91-92: the framebuffer is page-major, and the game draws

Round 91 put the count on the glass instead of in a bar: eleven pixels of n on row 0 and a marker at (0,0). The result was ink 52, marker 0, all bits 0, n = 0 -- draw() leaves the framebuffer empty, and the app's own marker did not appear either. That ruled out the bar's width and pointed at api->fb itself.

Reading app_api.h:71 rather than reasoning: 'The framebuffer is the resident gFrameBuffer[FRAME_LINES][128]'. gFrameBuffer is 1024 bytes, eight pages of 128, so fb's first index is the PAGE and the pixel within it is a bit: A->fb[y >> 3][x] |= 1u << (y & 7). The app had been writing fb[y][x] with y up to 63, eight times past the end, and the round 89 change from bit-packed to one byte per pixel moved it from wrong to differently wrong.

The frame now arrives and the ASCII reader makes it readable: the game's header, drawn by print_tiny at y=1, in a box from columns 42 to 84. Rows 7 and below are empty and that is correct -- every cell of a fresh game is closed and unflagged, so the 81-cell loop has nothing to paint.

Two habits: when a contradictory measurement appears, make the next one unarguable, and when a type is in doubt read the header that defines it -- the answer was two lines above the typedef. Next: press MENU inside the game and watch the field appear.
This commit is contained in:
mckero committed 2026-10-02 15:28:52 +08:00
1 parent f190892826
commit b9291c22a6
2 files changed
+89

No files matched your search

+44
View File
@@ -3055,3 +3055,47 @@ wrote minutes ago needs the same scepticism as one written rounds ago.
Next measurement, and it is designed to be unarguable: have the app write the count itself into a few bytes of
the framebuffer as bits -- 11 bits of n, in known positions -- and read them straight out of the panel hex.
No bar, no width, no decode: the number is on the glass or it is not.
**Rounds 91-92: the framebuffer is page-major, the game draws, and the page now says so.**
Round 91 settled the contradiction by putting the number on the glass instead of in a bar: the app writes
the framebuffer's non-zero byte count as eleven pixels on row 0 and a marker pixel at (0,0), then blits.
after MENU ink 52, marker pixel 0, all eleven bits 0, n = 0
n = 0 -- draw() leaves the framebuffer empty -- and the app's own marker did not appear either. That ruled out
the bar's width as the explanation and pointed at api->fb itself, so the next thing was to read the API's own
documentation rather than reason about it. app_api.h:71 says it in one line:
/* The framebuffer is the resident gFrameBuffer[FRAME_LINES][128]; the app draws
* into it and calls a blit_* to push it to the LCD. */
typedef uint8_t (*app_fb_t)[128];
gFrameBuffer is 1024 bytes, which is eight pages of 128 -- so fb's first index is the PAGE, not the pixel row,
and the pixel within a page is a bit:
A->fb[y >> 3][x] |= (uint8_t)(1u << (y & 7));
The app had been writing fb[y][x] with y up to 63, eight times past the end of the buffer, and the round 89
"fix" from bit-packed to one byte per pixel moved it from wrong to differently wrong. Both helpers are now
page-major and the arithmetic is the API's own.
The frame then arrives, and the ASCII reader makes it readable for the first time -- this is the game's header,
drawn by print_tiny at y=1:
0|...........................................#########################################......
1|..........................................##...#.#.#.#.#.#.#.#.######.##..##..###..##.....
2|..........................................##.###.#.#.#.#.#.#...#####.#.#.#.#.#.#.####.....
6|...........................................#########################################......
Rows 7 and below are empty, and that is correct rather than a fault: every cell of a fresh game is closed and
unflagged, so draw()'s 81-cell loop has nothing to paint. What is on the glass is the title, the counter and the
cursor, which is exactly the opening frame of the game.
Two habits from this round, both already in the file. When a contradictory measurement appears, make the next
one unarguable -- eleven pixels of a number cannot be misread as a width. And when a type is in doubt, read the
header that defines it and uses it: the answer was in a comment two lines above the typedef, and rounds 89 to 91
were spent inferring what the compiler could have been told.
Next: press MENU inside the running game and watch the field appear, which is the last thing standing between
this and playable.
+45
View File
@@ -2728,3 +2728,48 @@ boot 485,随后 ink 43,第 2 行读到第 42..84 列、26 个亮 —— 那
**下一次测量,且它被设计成无可争辩** ✓:**让应用把那个数本身以位的形式写进帧缓冲的几个字节里** ✓ —— **`n` 的 11 个位,放在已知的位置上** ✓ ——
**然后直接从面板的十六进制里读回来** ✓。**没有竖条、没有宽度、没有解码:那个数要么在玻璃上,要么不在** ✓。
**第 91–92 轮:帧缓冲是「页为主」的,游戏在画,而页面第一次说明了这一点。**
**第 91 轮用「把数字放到玻璃上」而不是「竖条」来终结那个矛盾** ✓:**应用把帧缓冲的非零字节数写成第 0 行上的十一个像素 ✓,并在 (0,0) 写一个标记像素 ✓,然后 blit** ✓。
```
MENU 之后 ink 52,标记像素 0,十一个位全 0,n = 0
```
**`n = 0` —— `draw()` 留下的帧缓冲是空的 ✓ —— 而应用自己的标记也没出现** ✗。
**这就排除了「竖条宽度」这个解释 ✓,并把矛头指向 `api->fb` 本身 ✓ —— 所以下一步不是推理,而是去读 API 自己的文档** ✓。
**`app_api.h:71` 一行就说清了** ✓:
```
/* The framebuffer is the resident gFrameBuffer[FRAME_LINES][128]; the app draws
* into it and calls a blit_* to push it to the LCD. */
typedef uint8_t (*app_fb_t)[128];
```
**`gFrameBuffer` 是 1024 字节 = 8 个页 × 128 ✓ —— 所以 `fb` 的第一个下标是「页」而不是「像素行」✓,
页内的像素是一个位** ✓:
```
A->fb[y >> 3][x] |= (uint8_t)(1u << (y & 7));
```
**应用此前一直写 `fb[y][x]`、`y` 到 63 ✓ —— 越出缓冲末尾八倍** ✗;**而第 89 轮那次「从按位打包改成一字节一像素」的「修法」,只是把它从错的一种改成了错的另一种** ✗✓。
**两个辅助函数现在都是页为主 ✓,算术就是 API 自己的** ✓。
**画面随之出现 ✓,而 ASCII 读法第一次让它可读** ✓ —— **下面是 `print_tiny` 在 y=1 画的表头** ✓:
```
0|...........................................#########################################......
1|..........................................##...#.#.#.#.#.#.#.#.######.##..##..###..##.....
2|..........................................##.###.#.#.#.#.#.#...#####.#.#.#.#.#.#.####.....
6|...........................................#########################################......
```
**第 7 行往下是空的 ✓,而这是对的、不是故障** ✓:**新开一局的每个格子都未翻开、未插旗 ✓,`draw()` 的 81 格循环本来就无可画** ✓。
**玻璃上的是标题、计数器与光标 ✓ —— 那正是游戏的开局画面** ✓✓。
**这一轮留下两条习惯,本文件其实都已有别的说法** ✓。**当出现互相矛盾的测量时,让**下一次**测量无可争辩** ✓ —— **一个数的十一个像素不可能被误读成宽度** ✓。
**而当类型存疑时,去读定义并使用它的那个头文件** ✓:**答案就在 typedef 上方两行的注释里 ✓,而第 89 至 91 轮花在了推测编译器本可以告知的事情上** ✓✓。
**下一步:在运行中的游戏里按 MENU,看雷区长出来** ✓ —— **那是挡在「可玩」前面的最后一件东西** ✓✓。