diff --git a/AGENTS.md b/AGENTS.md index 1b8b13b..711d93b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -688,6 +688,28 @@ and the offset are all accepted. **Whether control then reaches the overlay is s probe samples every 100 ms and saw no overlay address, no `APP ERROR` screen appears, and the app does not draw. That is where this stands -- the pipeline is verified up to the load, not up to execution. +**No overlay app actually runs in this emulator: the code is copied and control is never transferred.** +Measured by polling the PC through QMP every 30 ms (finer than the model's own 100 ms probe), with the app +installed through the page's own endpoint: + +* after launching Tetris, memory at 0x20000280 holds Tetris's own code byte for byte (f0b56b4c85b0036f...), + so the loader's copy is correct and aligned. The earlier claim in this session that it was shifted by a byte + was my own parsing dropping the first value, not the model. **172 PC samples over 4.5 s and 64 more over 2 s + were all inside firmware flash (0x08013260, a wait loop); not one landed in the 4 KiB overlay.** +* a 16-byte app that calls nothing at all -- no display, no keys, no struct member, just a volatile counter in a + loop -- behaves the same way, so this is not about what an app does once it starts. +* the header is not the reason either: Breakout's header (flags 0x1, capabilities 0x0, entry_off 0, abi 1, + api_min 1, hdr 1) is field-for-field the shape of ours, and the firmware still copies its code. + +So the failure is upstream of the app: the firmware validates and copies, and then does not enter the overlay. +**This makes the earlier "Tetris runs, DOWN moves the piece" note stale -- per this file's own rule, treat it as +unverified until it reproduces.** The screen seen after MENU (the radio's own top line, e.g. "F4 APRS", with the +rest blank) is the main screen: the app menu has gone away without the app ever starting. + +Next instruments, in order: whether the firmware waits for a key release before jumping (the PC sits in the same +wait loop at 0x08013260 both before and after MENU), and whether the copy of an upstream app is ever entered when +it is launched from the radio's own menu path rather than through this page. + ## The keypad: two real bugs, both fixed The old note here said "keys reach the firmware but the UI does not react" and diff --git a/AGENTS.zh-CN.md b/AGENTS.zh-CN.md index f762e06..c09273d 100644 --- a/AGENTS.zh-CN.md +++ b/AGENTS.zh-CN.md @@ -556,6 +556,26 @@ app 区被读过 —— 而多系统菜单读的是**固件槽**,不是应用 一次、没采到叠加区地址,也没有出现 `APP ERROR` 屏,应用也没有画出来。现状就是如此 —— **管线验证到了 "加载"这一步,还没到"执行"**。 +**这个模拟器里现在没有任何叠加应用真的跑起来:代码被拷进了叠加区,控制权却从未交出去。** +量法:通过 QMP **每 30 ms 轮询一次 PC**(比模型自带的 100 ms 探针更细),应用是用页面自己的接口装进去的: + +* 启动 Tetris 之后,0x20000280 处的内存**与 Tetris 自己的代码逐字节相同**(f0b56b4c85b0036f...), + 说明加载器的拷贝是正确且对齐的。本会话里我先前说的"错位一个字节"**是我自己的解析吃掉了第一个数值**, + 不是模型的问题。**4.5 秒里 172 次采样、外加 2 秒里 64 次采样,全部落在固件 flash 内(0x08013260,一个等待 + 循环),没有一次落进那 4 KiB 叠加区。** +* 一个 **16 字节、什么都不调用**的应用(不碰显示、不读按键、不访问结构体成员,只是一个自增的 volatile 循环) + 表现完全相同 —— 所以这**不是**"应用启动之后做了什么"的问题。 +* 头也不是原因:Breakout 的头(flags 0x1、capabilities 0x0、entry_off 0、abi 1、api_min 1、hdr 1)与我们的 + 逐字段同形,而固件照样把它的代码拷了进去。 + +也就是说,故障在应用**之前**:固件完成校验与拷贝,然后**没有进入叠加区**。 +**这使得本文件早先那条"Tetris 能跑、按 DOWN 棋子会动"的记录失效 —— 按本文件自己的规矩,在它能复现之前一律 +当作未验证。** 用户按下 MENU 之后看到的画面(电台自己的顶行,例如 "F4 APRS",其余空白)就是主界面, +即应用菜单已经退出、而应用从未启动。 + +接下来该用的仪器,按顺序:固件在跳转前是否在等按键释放(按 MENU 前后 PC 都停在 0x08013260 这个等待循环里); +以及从**电台自己的菜单路径**启动上游应用时,拷贝进去的代码到底有没有被执行。 + ## 键盘:两个真 bug,都已修复 这里原来的笔记写的是"按键到达了固件但界面不反应",并且归咎于机器模型。结果发现有**两个 diff --git a/tools/test_webui.py b/tools/test_webui.py index db76008..c3d7507 100644 --- a/tools/test_webui.py +++ b/tools/test_webui.py @@ -8,6 +8,7 @@ import subprocess import tempfile import time import unittest +from unittest import mock import uvk5_image # Flask is the one thing the fast suite needs from pip, so it is not always there. @@ -1167,3 +1168,30 @@ class TestAppsFrontEnd(unittest.TestCase): self.assertNotIn("navigator.serial", self.page) self.assertNotIn("getUserMedia", self.page) self.assertNotIn("AudioContext", self.page) + + +class TestWorkingCopyDirectory(unittest.TestCase): + """_edit_flash must create its working-copy directory, not assume it exists. + + On the machine this was written on work/firmware/ already existed, so the missing + makedirs() was invisible; a fresh checkout (CI) fails with FileNotFoundError on the + first install. Found by the copilot/fix-github-actions-unit-job branch. + """ + + def test_the_working_copy_directory_is_created(self): + directory = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, directory, True) + flash = os.path.join(directory, "flash.img") + with open(flash, "wb") as fh: + fh.write(b"\xff" * (2 * 1024 * 1024)) + slot = FlashSlot(flash) + missing = os.path.join(directory, "not", "created", "yet") + app = webui.create_app(None, frame_addr=0x1000, status_addr=0x2000, flash=slot) + with mock.patch.object(webui, "upload_dir", return_value=missing): + client = app.test_client() + import uvk5_apps + blob = uvk5_apps.build(b"\x01" * 32, "Beam", "1.0") + r = client.post("/api/apps/0", data=blob) + self.assertEqual(r.status_code, 200, r.get_data(as_text=True)[:200]) + self.assertTrue(os.path.isdir(missing), "the working-copy directory was not created") + self.assertTrue(os.path.exists(os.path.join(missing, "flash-current.img")))