8e389c03f8
Реализовать двухпанельный Commander от платформенного PoC до этапов P6-P20: EMM-каталог, сортировку и выбор, операции с файлами и деревьями, транзакционное копирование, метаданные, политику конфликтов и предварительную проверку свободного места. Добавить проектную документацию, HDD/MAME-сценарии и проверенные артефакты. Расширить libc операцией bank_write_page, исправлением режима O_RDONLY и связанными регрессионными проверками.
433 lines
14 KiB
C
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;
|
|
}
|