8e389c03f8
Реализовать двухпанельный Commander от платформенного PoC до этапов P6-P20: EMM-каталог, сортировку и выбор, операции с файлами и деревьями, транзакционное копирование, метаданные, политику конфликтов и предварительную проверку свободного места. Добавить проектную документацию, HDD/MAME-сценарии и проверенные артефакты. Расширить libc операцией bank_write_page, исправлением режима O_RDONLY и связанными регрессионными проверками.
325 lines
18 KiB
Markdown
325 lines
18 KiB
Markdown
# Sprinter Commander: платформенные пробы P1
|
||
|
||
Статус: MAME 0.287 / BIOS 3.06 — `PASS`; проверка на реальном Sprinter ожидается
|
||
|
||
Связанные документы:
|
||
[требования](commander-requirements.md),
|
||
[архитектура](commander-architecture.md),
|
||
[клавиши](commander-keymap.md),
|
||
[roadmap](commander-roadmap.md),
|
||
[экранный backend P2](p2-screen-backend.md)
|
||
|
||
## 1. Назначение
|
||
|
||
Программа `p1probe.exe` объединяет платформенные проверки, которые должны
|
||
быть завершены до реализации UI Commander. Она не является ранней версией
|
||
Commander и не определяет его пользовательский интерфейс.
|
||
|
||
Исходники находятся в `tests/p1_platform/`.
|
||
|
||
Вторая программа, `p1child.exe`, используется только для проверки ESTEX
|
||
`EXEC`, возврата к родителю и кода завершения.
|
||
|
||
## 2. Ограничения безопасности
|
||
|
||
- Пробы не создают, не изменяют и не удаляют файлы.
|
||
- Каталожная проба выполняет только `getcwd`, `chdir(".")` и
|
||
`ffirst/fnext`.
|
||
- EMM-проба пишет только в выделенный ей блок из трёх страниц.
|
||
- Screen-проба пишет только в отдельную выделенную EMM-страницу.
|
||
- Дочерняя программа меняет видеорежим, но не пишет на диск.
|
||
- Проба не открывает файловые дескрипторы приложения напрямую.
|
||
- MAME не запускается целями `make` и `make all`.
|
||
|
||
Первый MAME-прогон выполнен 6 сентября 2026 года после отдельного согласования.
|
||
Все запуски использовали автоматически пересоздаваемый `mc.img`; исходные
|
||
дисковые образы не изменялись программами-пробами.
|
||
|
||
## 3. Состав
|
||
|
||
| Файл | Назначение |
|
||
|---|---|
|
||
| `p1probe.c` | меню, общие сообщения, последовательность тестов |
|
||
| `p1_sys.c` | app-local wrappers DSKINFO, EXEC, WAIT, WINREST |
|
||
| `p1_emm.c` | выделение трёх страниц и проверка границ |
|
||
| `p1_dir.c` | CURDIR/CHDIR/F_FIRST/F_NEXT и DSKINFO output |
|
||
| `p1_screen.c` | полный и частичный WINREST с ненулевым offset |
|
||
| `p1_keyboard.c` | вывод `ascii/scan/live modifiers` |
|
||
| `p1_exec_test.c` | два режима EXEC и проверка состояния родителя |
|
||
| `p1child.c` | дочерний EXE, меняющий режим на 40x32 и возвращающий `0x5A` |
|
||
| `mame_dump_keys.lua` | вывод карты полей AT-клавиатуры MAME |
|
||
| `mame_p1_keyboard.lua` | воспроизводимый ввод стрелок, F1–F10 и сочетаний |
|
||
| `Makefile` | сборка обоих EXE |
|
||
|
||
## 4. Сборка
|
||
|
||
```sh
|
||
cd applications/Volkov/tests/p1_platform
|
||
make
|
||
```
|
||
|
||
Диагностическая программа собирается в режиме `small --safe`. Это сделано
|
||
намеренно: P1 проверяет платформенные ABI и не должен одновременно отлаживать
|
||
банковую topology Commander. Сборка `big --safe` является критерием P2.
|
||
|
||
Дочерняя программа собирается как `tiny --safe`.
|
||
|
||
### Результат текущей сборки
|
||
|
||
`p1probe.exe`:
|
||
|
||
- `_CODE`: 11 062 байта;
|
||
- данные: 2 741 байт;
|
||
- файл EXE: 11 835 байт;
|
||
- heap после статики: 17 429 байт;
|
||
- стек: 1 279 байт.
|
||
|
||
`p1child.exe`:
|
||
|
||
- `_CODE`: 3 907 байт;
|
||
- данные: 849 байт;
|
||
- файл EXE: 4 453 байта;
|
||
- heap после статики: 10 092 байта;
|
||
- стек: 1 279 байт.
|
||
|
||
Компиляция выполнена без ошибок. Значения относятся к текущей диагностической
|
||
сборке и не являются бюджетом Commander.
|
||
|
||
## 5. Статическая проверка ABI
|
||
|
||
### 5.1. DSKINFO
|
||
|
||
Сигнатура:
|
||
|
||
```c
|
||
int p1_disk_info(uint8_t disk, P1DiskInfo *out);
|
||
```
|
||
|
||
Сгенерированный вызов передаёт `disk` в A, `out` в DE. Wrapper сохраняет IX,
|
||
а после RST сохраняет:
|
||
|
||
- A — sectors per cluster;
|
||
- HL — total clusters;
|
||
- DE — free clusters;
|
||
- BC — bytes per sector.
|
||
|
||
Размер `P1DiskInfo` проверяется typedef-assert и равен семи байтам.
|
||
|
||
### 5.2. EXEC
|
||
|
||
Сигнатура намеренно имеет 8-битный аргумент первым:
|
||
|
||
```c
|
||
int p1_exec(uint8_t path_mode, const char *path);
|
||
```
|
||
|
||
Для этого порядка SDCC передаёт `path_mode` в A и `path` в DE. Wrapper
|
||
перекладывает их в B и HL соответственно. Вариант
|
||
`p1_exec(const char *path, uint8_t mode)` был отвергнут после просмотра
|
||
сгенерированного ASM: второй 8-битный аргумент SDCC помещал на стек, а не в
|
||
DE.
|
||
|
||
На успехе A сохраняется как `p1_exec_exit_code`; на ошибке код из A проходит
|
||
через `__errno_set`.
|
||
|
||
### 5.3. WINREST
|
||
|
||
Wrapper повторяет уже подтверждённую раскладку `tests/winrest` и `mdview2`:
|
||
|
||
```text
|
||
A row
|
||
L column
|
||
SP+2 height
|
||
SP+3 width
|
||
SP+4 physical page
|
||
SP+5..6 offset in 16-KB page
|
||
```
|
||
|
||
IX сохраняется, перед RST устанавливается в `0xC000 + offset`. Сгенерированный
|
||
вызов проверен для полного экрана `32x80` и окна `2x20` с offset `0x2000`.
|
||
|
||
## 6. Меню `p1probe.exe`
|
||
|
||
| Клавиша | Проба | Автоматическая оценка |
|
||
|---|---|---|
|
||
| `1` | Каталог и F_FIRST/F_NEXT | да, плюс наблюдаемые коды |
|
||
| `2` | EMM: три страницы | да |
|
||
| `3` | WINREST full/partial | только завершение; изображение оценивается визуально |
|
||
| `4` | Клавиатурные коды | сбор наблюдений |
|
||
| `5` | EXEC и возврат | да для режима B=1 |
|
||
| `6` | DSKINFO текущего диска | да |
|
||
| `A` | Пробы 1, 2 и 6 | да |
|
||
| `0`/`Esc` | Выход | — |
|
||
|
||
## 7. Ожидаемые проверки
|
||
|
||
### 7.1. Каталог
|
||
|
||
Проба дважды перечисляет каталог по маскам `*.*` и `*`, печатает первые 20
|
||
записей и итоговые значения:
|
||
|
||
- число записей;
|
||
- наличие `.`;
|
||
- наличие `..`;
|
||
- `errno` последнего `fnext()`;
|
||
- совпадение результатов двух масок;
|
||
- сохранение CWD после `chdir(".")`.
|
||
|
||
Фактический результат DSS различается: `*.*` вернул обе записи с расширением,
|
||
а `*` завершился как пустой список с `ENOENT`. Поэтому `*.*` принят как
|
||
обязательный шаблон полного scan; совпадение масок не предполагается.
|
||
|
||
MAME-прогон установил фактическое завершение перечисления: `fnext()`
|
||
возвращает ошибку с `errno=3` (`ENOENT`). Код `0x0F` из старого описания DSS
|
||
для этой операции не подтверждён. Проба после измерения принимает только
|
||
`ENOENT`, чтобы возможная смена поведения не осталась незамеченной.
|
||
|
||
### 7.2. EMM
|
||
|
||
Проверяются:
|
||
|
||
- `mem_alloc_pages(3)`;
|
||
- три различных физических номера;
|
||
- первые 64 байта каждой страницы;
|
||
- последние 64 байта каждой страницы;
|
||
- независимые patterns страниц;
|
||
- восстановление `mem_info.free_pages` после освобождения блока.
|
||
|
||
### 7.3. Screen/WINREST
|
||
|
||
Первый вызов должен показать полный экран 80x32 с рамкой и цветными строками.
|
||
После клавиши второй вызов должен наложить окно 20x2 в позиции row 14,
|
||
column 30. Данные второго окна лежат по offset `0x2000`, поэтому тест
|
||
одновременно проверяет корректность IX.
|
||
|
||
Тест не открывает окно через BIOS: ESTEX WINREST здесь только копирует
|
||
прямоугольник в текущий текстовый экран. Повтор со снимками в `t=20` и `t=24`
|
||
без промежуточных вызовов подтвердил, что фон вне прямоугольника сохранён.
|
||
|
||
Первая версия pattern использовала арифметически полученные атрибуты
|
||
`0x11..0x17`. Верхняя граница `0x11` была обычным blue-on-blue, а часть
|
||
остальных сочетаний выглядела неоднозначно в разных моментах палитрового
|
||
цикла. Замечание о синих символах на синем фоне подтвердилось; raw-арифметика
|
||
атрибутов в UI запрещается.
|
||
|
||
После замены на именованные `COLOR(LIGHTBLUE..WHITE, BLUE)` полный экран и
|
||
частичное окно были видимы на кадрах `t=20` и `t=24`. Повторная проверка
|
||
исходных PNG установила, что их декодированные RGB-пиксели полностью
|
||
идентичны. Прежнее сообщение о чередовании P2 было ошибкой встроенного
|
||
предпросмотра, а не поведением MAME.
|
||
|
||
Отдельная проба P2.2 сохранила, установила, прочитала обратно и восстановила
|
||
шесть атрибутов во всех четырёх планах; многоцветная сцена осталась стабильной.
|
||
Строка P1-SCR-03 ниже подтверждена как `PASS`. Детали — в
|
||
[результатах P2.2](p2-palette-results.md).
|
||
|
||
### 7.4. Keyboard
|
||
|
||
Для 24 событий печатаются:
|
||
|
||
```text
|
||
ascii, scan, полное kbd_mod_state, compact Shift/Ctrl/Alt
|
||
```
|
||
|
||
`Esc` печатается последним и завершает сбор. В автоматическом MAME-сценарии
|
||
использован следующий набор из 24 событий:
|
||
|
||
- стрелки, Home/End, PgUp/PgDn;
|
||
- F1–F10;
|
||
- Insert/Delete;
|
||
- Ctrl+R;
|
||
- Ctrl+PgUp;
|
||
- Alt+F1;
|
||
- Esc.
|
||
|
||
### 7.5. EXEC
|
||
|
||
Выполняются два случая:
|
||
|
||
1. `B=0`, `P1CHILD.EXE` — наблюдение short-name/PATH semantics.
|
||
2. `B=1`, `.\\P1CHILD.EXE` — обязательный случай PoC.
|
||
|
||
Дочерняя программа:
|
||
|
||
- переключается в 40x32;
|
||
- показывает свои W1/W2/W3 и CWD;
|
||
- ждёт клавишу;
|
||
- возвращает `0x5A`.
|
||
|
||
После возврата родитель проверяет:
|
||
|
||
- результат непосредственного EXEC;
|
||
- результат WAIT;
|
||
- восстановление W1/W2/W3;
|
||
- восстановление CWD;
|
||
- возможность вернуть 80x32 и продолжить работу.
|
||
|
||
Ошибка случая B=0 записывается как наблюдение и не проваливает PoC. Ошибка
|
||
B=1 проваливает тест.
|
||
|
||
## 8. Выполненный порядок первого прогона
|
||
|
||
1. `2` — EMM.
|
||
2. `6` — DSKINFO.
|
||
3. `1` — каталог.
|
||
4. `3` — WINREST.
|
||
5. `4` — клавиатура.
|
||
6. `5` — EXEC последним.
|
||
7. Повторно `2` в том же процессе после EXEC, чтобы проверить EMM.
|
||
8. Возврат в меню; независимые запуски дополнительно подтвердили повторный
|
||
старт программы.
|
||
|
||
Такой порядок сначала проверяет неразрушительные примитивы и оставляет
|
||
наиболее сложное переключение контекста на конец.
|
||
|
||
## 9. Таблица runtime-результатов
|
||
|
||
Окружение MAME: `mame.arm` 0.287 (`b0c4527c`), машина `sprinter`, BIOS `v3.06`, запуск
|
||
`p1probe.exe` с дискеты A:. `PENDING` в аппаратной колонке означает только
|
||
отсутствие прогона на реальном Sprinter и не отменяет результата MAME.
|
||
|
||
| ID | Проверка | MAME 0.287 / BIOS 3.06 | Реальный Sprinter | Артефакт |
|
||
|---|---|---|---|---|
|
||
| P1-DIR-01 | CWD и `chdir(".")` | PASS | PENDING | [результат](../artifacts/mame-p1/directory/strict-enoent.png) |
|
||
| P1-DIR-02 | `*.*` против `*` | PASS: `2` против `0`; полный scan использует только `*.*` | PENDING | [результат](../artifacts/mame-p1/directory/strict-enoent.png) |
|
||
| P1-DIR-03 | Код конца F_NEXT | PASS, `ENOENT=3` | PENDING | [строгая проверка](../artifacts/mame-p1/directory/strict-enoent.png) |
|
||
| P1-EMM-01 | Блок 3 страницы | PASS, страницы `EF/F0/F1` | PENDING | [результат](../artifacts/mame-p1/emm/result.png) |
|
||
| P1-EMM-02 | Границы страниц | PASS | PENDING | [результат](../artifacts/mame-p1/emm/result.png) |
|
||
| P1-EMM-03 | Счётчик после free | PASS, `221 → 221` | PENDING | [результат](../artifacts/mame-p1/emm/result.png) |
|
||
| P1-SCR-01 | Полный WINREST 80x32 | PASS, визуально | PENDING | [контрастный экран](../artifacts/mame-p1/winrest/full-contrast.png) |
|
||
| P1-SCR-02 | Offset WINREST | PASS, row 14 / col 30 / `0x2000` | PENDING | [фон и окно вместе](../artifacts/mame-p1/winrest/partial-contrast-stable.png) |
|
||
| P1-SCR-03 | Цветные атрибуты в двух моментах цикла | PASS, RGB кадров идентичен; P2.2 подтвердил 6 атрибутов × 4 плана | PENDING | [кадр 1](../artifacts/mame-p1/winrest/partial-flash-phase.png), [кадр 2](../artifacts/mame-p1/winrest/partial-contrast-stable.png), [P2.2](p2-palette-results.md) |
|
||
| P1-SCR-04 | Raw-атрибуты `0x11..0x17` | OBSERVED, raw-арифметика запрещена | PENDING | [неудачный исходный pattern](../artifacts/mame-p1/winrest/partial-phase-hidden.png) |
|
||
| P1-KBD-01 | Базовые scan-коды | PASS | PENDING | [таблица](../artifacts/mame-p1/keyboard-commander/sprinter/0002.png) |
|
||
| P1-KBD-02 | Ctrl/Alt modifiers | PASS | PENDING | [сочетания](../artifacts/mame-p1/keyboard-commander/sprinter/0003.png) |
|
||
| P1-EXE-01 | EXEC B=0 | PASS | PENDING | [результат](../artifacts/mame-p1/exec/result.png) |
|
||
| P1-EXE-02 | EXEC B=1 | PASS | PENDING | [результат](../artifacts/mame-p1/exec/result.png) |
|
||
| P1-EXE-03 | Exit/WAIT `0x5A` | PASS / PASS | PENDING | [результат](../artifacts/mame-p1/exec/result.png) |
|
||
| P1-EXE-04 | Восстановление W1/W2/W3 | PASS, `F3/F2/FF` | PENDING | [результат](../artifacts/mame-p1/exec/result.png) |
|
||
| P1-EXE-05 | EMM после двух EXEC | PASS | PENDING | [контроль](../artifacts/mame-p1/exec-then-emm/emm-result.png) |
|
||
| P1-DSK-01 | DSKINFO | PASS | PENDING | [результат](../artifacts/mame-p1/dskinfo/result.png) |
|
||
|
||
### 9.1. Зафиксированные значения
|
||
|
||
- DSKINFO: 1 сектор на кластер, 512 байт на сектор, 2847 кластеров всего,
|
||
2814 свободно.
|
||
- EMM до выделения: 256 страниц всего, 221 свободна; после освобождения
|
||
снова 221.
|
||
- Оба режима EXEC: `rc=0`, `errno=0`, непосредственный exit=`5A`, WAIT=`5A`.
|
||
- CWD до и после child: `\`; страницы W1/W2/W3 до и после: `F3/F2/FF`.
|
||
- Модифицированная клавиша приходит с установленным битом 7 scan-кода:
|
||
Ctrl+R=`93`, Ctrl+PgUp=`D9`, Alt+F1=`BB`. После `scan & 0x7F` получаются
|
||
базовые коды `13`, `59`, `3B`; compact modifiers равны `02`, `02`, `04`.
|
||
- Esc: `ascii=1B`, `scan=01`.
|
||
|
||
## 10. Условия завершения P1
|
||
|
||
MAME-часть P1 завершена: все обязательные строки имеют `PASS`, фактические
|
||
клавиатурные коды перенесены в keymap, завершение `fnext()` уточнено,
|
||
`EXEC B=1`/WAIT и EMM после child подтверждены, артефакты сохранены.
|
||
|
||
Проверка на реальном Sprinter остаётся желательной аппаратной валидацией,
|
||
но не блокирует переход к P2 и реализацию platform API PoC. Если реальное
|
||
железо даст расхождение, оно оформляется отдельным репро и не маскируется
|
||
адаптацией UI.
|