Files
Sprinter-SDCC/applications/Volkov/tests/p2_screen/p2screen.c
T
snark13 8e389c03f8 Volkov: добавить Sprinter Commander
Реализовать двухпанельный Commander от платформенного PoC до этапов P6-P20: EMM-каталог, сортировку и выбор, операции с файлами и деревьями, транзакционное копирование, метаданные, политику конфликтов и предварительную проверку свободного места.

Добавить проектную документацию, HDD/MAME-сценарии и проверенные артефакты. Расширить libc операцией bank_write_page, исправлением режима O_RDONLY и связанными регрессионными проверками.
2026-09-10 10:45:30 +03:00

433 lines
14 KiB
C

/*
* p2screen — проверка системного координатного backend Commander P2.1.
*
* Экранная модель лежит в одной EMM-странице как 80x32 пар (char, attr).
* Вывод: WINREST; проверка: RDCHAR; сдвиг: прямоугольный SCROLL 55h.
* BIOS window descriptors и прямой доступ к VRAM не используются.
*/
#include <stdint.h>
#include <string.h>
#include <conio.h>
#include <sprinter.h>
#include <sprinter_mem.h>
#include "p2_screen.h"
#include "sc_glyphs.h"
#define LEFT_X 1u
#define RIGHT_X 41u
#define PANEL_Y 1u
#define PANEL_W 38u
#define PANEL_H 27u
#define ATTR_UI COLOR(COLOR_BLACK, COLOR_CYAN)
#define ATTR_FRAME ATTR_UI
#define ATTR_TITLE ATTR_UI
#define ATTR_TEXT ATTR_UI
#define ATTR_DIM ATTR_UI
#define ATTR_ACTIVE ATTR_UI
#define ATTR_MARK1 ATTR_UI
#define ATTR_MARK2 ATTR_UI
#define ATTR_PASS ATTR_UI
#define ATTR_FAIL ATTR_UI
#define ATTR_KEYS ATTR_UI
static P2Cell row_buf[P2_SCREEN_W];
static P2Cell marker_buf[40u];
static uint8_t fail_x;
static uint8_t fail_y;
static uint16_t fail_want;
static uint16_t fail_got;
static uint8_t w3_bad;
static uint16_t cell_offset(uint8_t x, uint8_t y)
{
return (uint16_t)y * P2_ROW_BYTES + (uint16_t)x * 2u;
}
static void row_fill(uint8_t ch, uint8_t attr)
{
uint8_t x;
for (x = 0; x < P2_SCREEN_W; x++) {
row_buf[x].ch = ch;
row_buf[x].attr = attr;
}
}
static void cells_fill(P2Cell *cells, uint8_t count,
uint8_t ch, uint8_t attr)
{
uint8_t i;
for (i = 0; i < count; i++) {
cells[i].ch = ch;
cells[i].attr = attr;
}
}
static void cells_text(P2Cell *cells, uint8_t width, uint8_t x,
const char *text, uint8_t attr)
{
while (*text && x < width) {
cells[x].ch = (uint8_t)*text++;
cells[x].attr = attr;
x++;
}
}
static void cells_bytes(P2Cell *cells, uint8_t width, uint8_t x,
const uint8_t *bytes, uint8_t count, uint8_t attr)
{
while (count-- && x < width) {
cells[x].ch = *bytes++;
cells[x].attr = attr;
x++;
}
}
static void cells_dec2(P2Cell *cells, uint8_t width, uint8_t x,
uint8_t value, uint8_t attr)
{
if (x + 1u >= width) return;
cells[x].ch = (uint8_t)('0' + (value / 10u) % 10u);
cells[x].attr = attr;
cells[x + 1u].ch = (uint8_t)('0' + value % 10u);
cells[x + 1u].attr = attr;
}
static void cells_dec4(P2Cell *cells, uint8_t width, uint8_t x,
uint16_t value, uint8_t attr)
{
uint16_t div = 1000u;
uint8_t i;
for (i = 0; i < 4u && x + i < width; i++) {
cells[x + i].ch = (uint8_t)('0' + (value / div) % 10u);
cells[x + i].attr = attr;
div /= 10u;
}
}
static void row_panel_frame(uint8_t left, uint8_t y)
{
uint8_t x;
uint8_t right = (uint8_t)(left + 39u);
if (y == 0u || y == 28u) {
row_buf[left].ch = (y == 0u) ? SC_G_TL : SC_G_BL;
row_buf[right].ch = (y == 0u) ? SC_G_TR : SC_G_BR;
row_buf[left].attr = ATTR_FRAME;
row_buf[right].attr = ATTR_FRAME;
for (x = (uint8_t)(left + 1u); x < right; x++) {
row_buf[x].ch = SC_G_H;
row_buf[x].attr = ATTR_FRAME;
}
} else {
row_buf[left].ch = SC_G_V;
row_buf[right].ch = SC_G_V;
row_buf[left].attr = ATTR_FRAME;
row_buf[right].attr = ATTR_FRAME;
}
}
static void build_model(uint8_t page)
{
static const uint8_t boxes[] = {
SC_G_H, SC_G_V, SC_G_TL, SC_G_TR, SC_G_BL, SC_G_BR,
SC_G_TM, SC_G_BM, SC_G_ML, SC_G_MR, SC_G_X
};
static const uint8_t arrows[] = {
SC_G_ARROW_UP, SC_G_ARROW_DOWN, SC_G_ARROW_LEFT, SC_G_ARROW_RIGHT
};
static const uint8_t blocks[] = {
SC_G_SHADE_LIGHT, SC_G_SHADE_MEDIUM, SC_G_SHADE_DARK,
SC_G_BLOCK, SC_G_HALF_UP, SC_G_HALF_DOWN,
SC_G_HALF_LEFT, SC_G_HALF_RIGHT, SC_G_BULLET,
SC_G_CHECK, SC_G_SQUARE
};
uint8_t y;
for (y = 0; y < P2_SCREEN_H; y++) {
row_fill(' ', (y < 29u) ? ATTR_TEXT : ATTR_KEYS);
if (y <= 28u) {
row_panel_frame(0u, y);
row_panel_frame(40u, y);
}
if (y == 0u) {
cells_text(row_buf, P2_SCREEN_W, 11u,
" LEFT PANEL ", ATTR_TITLE);
cells_text(row_buf, P2_SCREEN_W, 50u,
" RIGHT PANEL ", ATTR_TITLE);
} else if (y >= 1u && y <= 27u) {
cells_text(row_buf, P2_SCREEN_W, 2u, "L ROW", ATTR_TEXT);
cells_dec2(row_buf, P2_SCREEN_W, 8u, y, ATTR_ACTIVE);
cells_text(row_buf, P2_SCREEN_W, 42u, "R ROW", ATTR_DIM);
cells_dec2(row_buf, P2_SCREEN_W, 48u, y, ATTR_DIM);
}
if (y == 20u) {
cells_text(row_buf, P2_SCREEN_W, 2u, "BOX", ATTR_TITLE);
cells_bytes(row_buf, P2_SCREEN_W, 6u, boxes,
(uint8_t)sizeof(boxes), ATTR_FRAME);
} else if (y == 21u) {
cells_text(row_buf, P2_SCREEN_W, 2u, "ARROWS", ATTR_TITLE);
cells_bytes(row_buf, P2_SCREEN_W, 10u, arrows,
(uint8_t)sizeof(arrows), ATTR_FRAME);
} else if (y == 22u) {
cells_text(row_buf, P2_SCREEN_W, 2u, "BLOCKS", ATTR_TITLE);
cells_bytes(row_buf, P2_SCREEN_W, 10u, blocks,
(uint8_t)sizeof(blocks), ATTR_FRAME);
} else if (y == 28u) {
cells_text(row_buf, P2_SCREEN_W, 13u, " 27 rows ", ATTR_TITLE);
cells_text(row_buf, P2_SCREEN_W, 53u, " 27 rows ", ATTR_TITLE);
} else if (y == 29u) {
cells_text(row_buf, P2_SCREEN_W, 1u,
"P2.1 SYSTEM COORDINATE BACKEND - NO BIOS WINDOWS, NO VRAM ACCESS",
ATTR_KEYS);
} else if (y == 30u) {
cells_text(row_buf, P2_SCREEN_W, 1u,
"STAGE 1: FULL WINREST + RDCHAR VERIFY",
ATTR_KEYS);
} else if (y == 31u) {
cells_text(row_buf, P2_SCREEN_W, 1u,
"SYSTEM ATTR 0x30: NO PALETTE WRITES KEY: next stage",
ATTR_KEYS);
}
bank_write(page, (uint16_t)y * P2_ROW_BYTES,
row_buf, P2_ROW_BYTES);
}
}
static void build_marker(uint8_t page)
{
cells_fill(marker_buf, 20u, ' ', ATTR_MARK1);
cells_fill(marker_buf + 20u, 20u, ' ', ATTR_MARK2);
cells_text(marker_buf, 20u, 0u, "PARTIAL RECT PASS", ATTR_MARK1);
cells_text(marker_buf + 20u, 20u, 0u, "OFFSET 0x2000 OK", ATTR_MARK2);
bank_write(page, P2_MARKER_OFF, marker_buf, sizeof(marker_buf));
}
static void model_apply_marker(uint8_t page, uint8_t x, uint8_t y)
{
uint8_t saved = _io_page_w3;
P2Cell *model;
sprinter_page_w3(page);
model = (P2Cell *)0xC000u;
memcpy(model + (uint16_t)y * P2_SCREEN_W + x,
marker_buf, 20u * sizeof(P2Cell));
memcpy(model + (uint16_t)(y + 1u) * P2_SCREEN_W + x,
marker_buf + 20u, 20u * sizeof(P2Cell));
sprinter_page_w3(saved);
}
static void model_status(uint8_t page, const char *text, uint8_t ok)
{
bank_read(page, 30u * P2_ROW_BYTES, row_buf, P2_ROW_BYTES);
cells_fill(row_buf, P2_SCREEN_W, ' ', ATTR_KEYS);
cells_text(row_buf, P2_SCREEN_W, 1u, text, ATTR_KEYS);
cells_text(row_buf, P2_SCREEN_W, 62u,
ok ? "PASS" : "FAIL", ok ? ATTR_PASS : ATTR_FAIL);
if (!ok) {
cells_text(row_buf, P2_SCREEN_W, 68u, "AT", ATTR_FAIL);
cells_dec2(row_buf, P2_SCREEN_W, 71u, fail_x, ATTR_FAIL);
row_buf[73u].ch = ',';
row_buf[73u].attr = ATTR_FAIL;
cells_dec2(row_buf, P2_SCREEN_W, 74u, fail_y, ATTR_FAIL);
}
bank_write(page, 30u * P2_ROW_BYTES, row_buf, P2_ROW_BYTES);
}
static void present_rect(uint8_t page, uint8_t x, uint8_t y,
uint8_t width, uint8_t height, uint16_t offset)
{
uint8_t saved = _io_page_w3;
p2_winrest(y, x, height, width, page, offset);
if (_io_page_w3 != saved) w3_bad = 1u;
}
static void present_model_row(uint8_t page, uint8_t x,
uint8_t y, uint8_t width)
{
present_rect(page, x, y, width, 1u, cell_offset(x, y));
}
static uint8_t verify_model(uint8_t page)
{
uint8_t saved = _io_page_w3;
uint8_t x;
uint8_t y;
for (y = 0; y < P2_SCREEN_H; y++) {
bank_read(page, (uint16_t)y * P2_ROW_BYTES,
row_buf, P2_ROW_BYTES);
for (x = 0; x < P2_SCREEN_W; x++) {
uint16_t got = rdchar(x, y);
uint16_t want = ((uint16_t)row_buf[x].attr << 8) |
row_buf[x].ch;
if (got != want) {
fail_x = x;
fail_y = y;
fail_want = want;
fail_got = got;
return 0u;
}
}
}
if (_io_page_w3 != saved) w3_bad = 1u;
return w3_bad ? 0u : 1u;
}
static uint8_t report_verify(uint8_t page, const char *stage)
{
uint8_t ok = verify_model(page);
model_status(page, stage, ok);
present_model_row(page, 0u, 30u, P2_SCREEN_W);
return ok;
}
static void mapped_new_row(P2Cell *line, uint8_t width,
uint8_t direction, uint16_t sequence)
{
cells_fill(line, width, ' ', ATTR_ACTIVE);
cells_text(line, width, 1u,
(direction == 1u) ? "NEW BOTTOM ROW" : "NEW TOP ROW",
ATTR_ACTIVE);
cells_dec4(line, width, 17u, sequence, ATTR_ACTIVE);
}
static void model_scroll(uint8_t page, uint8_t x, uint8_t y,
uint8_t width, uint8_t height,
uint8_t direction, uint16_t sequence)
{
uint8_t saved = _io_page_w3;
P2Cell *model;
uint8_t i;
sprinter_page_w3(page);
model = (P2Cell *)0xC000u;
if (direction == 1u) {
for (i = 0; i + 1u < height; i++) {
memmove(model + (uint16_t)(y + i) * P2_SCREEN_W + x,
model + (uint16_t)(y + i + 1u) * P2_SCREEN_W + x,
(uint16_t)width * sizeof(P2Cell));
}
mapped_new_row(model + (uint16_t)(y + height - 1u) * P2_SCREEN_W + x,
width, direction, sequence);
} else {
i = (uint8_t)(height - 1u);
while (i != 0u) {
memmove(model + (uint16_t)(y + i) * P2_SCREEN_W + x,
model + (uint16_t)(y + i - 1u) * P2_SCREEN_W + x,
(uint16_t)width * sizeof(P2Cell));
i--;
}
mapped_new_row(model + (uint16_t)y * P2_SCREEN_W + x,
width, direction, sequence);
}
sprinter_page_w3(saved);
}
static void system_scroll(uint8_t page, uint8_t x, uint8_t y,
uint8_t width, uint8_t height,
uint8_t direction, uint16_t sequence)
{
uint8_t saved;
uint8_t open_y;
model_scroll(page, x, y, width, height, direction, sequence);
saved = _io_page_w3;
scroll(x, y, width, height, direction, 0u);
if (_io_page_w3 != saved) w3_bad = 1u;
open_y = (direction == 1u) ? (uint8_t)(y + height - 1u) : y;
present_model_row(page, x, open_y, width);
}
static uint8_t wait_stage(void)
{
uint16_t key = getkey();
return ((uint8_t)key == 0x1Bu) ? 0u : 1u;
}
int main(void)
{
uint8_t previous_mode = gettextmode();
uint8_t block;
uint8_t page;
uint8_t ok = 1u;
uint16_t i;
(void)fail_want;
(void)fail_got;
if (settextmode(TEXT_MODE_80x32) != 0) return 1;
block = mem_alloc_pages(1u);
if (block == 0u) {
if (previous_mode == TEXT_MODE_40x32 ||
previous_mode == TEXT_MODE_80x32) {
(void)settextmode(previous_mode);
}
return 2;
}
page = mem_get_page(block, 0u);
if (page == 0u) {
mem_free_block(block);
if (previous_mode == TEXT_MODE_40x32 ||
previous_mode == TEXT_MODE_80x32) {
(void)settextmode(previous_mode);
}
return 3;
}
build_model(page);
build_marker(page);
present_rect(page, 0u, 0u, P2_SCREEN_W, P2_SCREEN_H, 0u);
if (!report_verify(page, "STAGE 1: FULL WINREST + RDCHAR")) ok = 0u;
if (!wait_stage()) goto done;
present_rect(page, 30u, 14u, 20u, 2u, P2_MARKER_OFF);
model_apply_marker(page, 30u, 14u);
if (!report_verify(page, "STAGE 2: PARTIAL OFFSET 0x2000")) ok = 0u;
if (!wait_stage()) goto done;
build_model(page);
present_rect(page, 0u, 0u, P2_SCREEN_W, P2_SCREEN_H, 0u);
system_scroll(page, LEFT_X, PANEL_Y, PANEL_W, PANEL_H, 1u, 1u);
if (!report_verify(page, "STAGE 3: LEFT RECT SCROLL UP")) ok = 0u;
if (!wait_stage()) goto done;
system_scroll(page, RIGHT_X, PANEL_Y, PANEL_W, PANEL_H, 2u, 2u);
if (!report_verify(page, "STAGE 4: RIGHT RECT SCROLL DOWN")) ok = 0u;
if (!wait_stage()) goto done;
model_status(page, "STAGE 5: 1000 RECT SCROLL OPERATIONS", 1u);
present_model_row(page, 0u, 30u, P2_SCREEN_W);
for (i = 0u; i < 1000u; i++) {
uint8_t direction = (i & 1u) ? 2u : 1u;
system_scroll(page, LEFT_X, PANEL_Y, PANEL_W, PANEL_H,
direction, (uint16_t)(i + 1u));
}
if (!report_verify(page, "FINAL: MODEL/RDCHAR/W3 AFTER 1000")) ok = 0u;
if (!ok) {
model_status(page, "FINAL P2.1 RESULT", 0u);
present_model_row(page, 0u, 30u, P2_SCREEN_W);
}
(void)wait_stage();
done:
mem_free_block(block);
textattr(COLOR(COLOR_WHITE, COLOR_BLACK));
clrscr();
if (previous_mode == TEXT_MODE_40x32 ||
previous_mode == TEXT_MODE_80x32) {
(void)settextmode(previous_mode);
}
return ok ? 0 : 1;
}