Порт seg002: check_shadow (0064), do_init_shad (0000), do_auto_moves (1089),
autocontrol_shadow_level5 (1157) + таблица shad_drink_move (data.h:866).
Механика. При входе в комнату 24 уровня 5, если зелье (кол 3, ряд 0) ещё
на месте, слот соперника занимает ТЕНЬ — и занимает его ВМЕСТО стража:
enter_guard в этой комнате оригинал не зовёт НИКОГДА (в данных страж есть,
guards_tile[23] = 8, но check_shadow уходит в return до него — в том числе
когда зелье уже выпито). Поэтому pop_check_shadow вернула 1 = «событие
обработано, стража не поднимать», и вызов стоит ПЕРЕД pop_guard_enter.
Тень стоит, пока не откроются ворота комнаты (openness >= 80), затем
проигрывает ЗАПИСЬ ходов: подойти, взять, выпить, развернуться, уйти за
левый край. do_auto_moves — тот же движок, что у демо-режима заставки;
тонкость выбора записи (при достигнутом времени индекс сдвигается, а ход
берётся из ещё не сдвинутой записи) портирована дословно.
Три вещи, найденные по дороге:
1. ГЕЙТ ВЫКЛЮЧЕННОГО ПЕРСОНАЖА. play_guard_frame (seg000:1248) начинается
с `if (Guard.direction != dir_56_none)`, и clear_char выключает
персонажа именно так — он не трогает charid. У нас гейт был только по
charid, поэтому ушедшая за край тень продолжала тикать: do_auto_moves за
концом таблицы отдаёт последнюю запись {0x31,1} = «вперёд», тень
разворачивалась, вбегала обратно, пробегала верхний ряд и падала в проём
(наблюдение пользователя в MAME). Гейт добавлен и в pop_guard_tick, и в
pop_guard_phys_tick.
2. ЭФФЕКТ ЗЕЛЬЯ — ТОЛЬКО КИДУ. proc_get_object (seg006:16CB) начинается с
`if (Char.charid != charid_0_kid || ...) return`: предмет забирает любой
персонаж, а эффект достаётся только Киду. На этом стоит всё спецсобытие
— тень пьёт, зелье исчезает и не достаётся никому. Без проверки
hitp_delta уходил бы Киду, то есть тень его ЛЕЧИЛА бы.
3. ВЕТКА ТЕНИ в check_guard_fallout (seg002:0241): исчезает, только если
реально падает (action == 4). Была помечена в коде как «появится с
L3-SKEL» — на самом деле она про тень.
Чит ROOMNAV: телепорт ставил Кида в первый попавшийся пол, то есть почти
всегда (0,0). Теперь ищет пол С МАКСИМАЛЬНЫМ Y (ряд 2 -> 1 -> 0, по
просьбе пользователя): в комнате 24 старая посадка была вплотную к тени, и
вместо сцены кражи начиналась схватка, которой там быть не должно.
Отладочный тумблер POP_DBG_SHADOW_NOWAIT (pop_tune.h, по умолчанию 0) —
посмотреть сцену, не проходя уровень до кнопки.
Проверено в MAME (уровень 5, комната 24): при закрытых воротах тень стоит
за левым краем и не видна; с тумблером — подошла, выпила (зелье исчезло),
развернулась, ушла за край, выключилась и НЕ вернулась. tests-host 5/5,
make size-check OK. Цена: резидент +102 Б (24637 -> 24739), банк 1
+368 Б, банк 3 +11 Б.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sprinter C Compiler — v1.0
C toolchain for Sprinter — the Z80-based home computer by Peters Plus, running
ESTEX DSS. Host: macOS / Linux. Target: .EXE files in SprintEXE format.
Built on top of SDCC 4.5 (vendored in third_party/sdcc/). This repository adds
everything Sprinter-specific: crt0, linker integration, libc wrappers over ESTEX,
banked-call trampolines, graphics & accelerator API, mouse driver wrappers, and the
mkexe utility for producing SprintEXE images.
What you get
bin/sprinter-cc— one-line driver:sprinter-cc -o foo.exe foo.c- Memory modes:
tiny,small,big,huge,manual— see below. - stdio + conio: printf, puts, putchar, getchar, fopen/fread/..., cprintf, cputs, putch, textcolor/textbackground/textattr, gotoxy, kbhit/getch.
- Graphics: 320×256×256 and 640×256×16 modes, accelerator-backed primitives (hline / vline / rect / fill_rect / line via Bresenham, plus clear), bitmap-font text in both modes via BIOS character generator.
- File I/O: POSIX (
open/read/write/close/lseek/unlink/creat), FILE* streams (fopen/fgets/fwrite/...), directory listing (ffirst/fnext),chdir/getcwd/mkdir/rmdir,stat/fstat. - Memory: 32 KB heap (W2-resident), banking-aware page allocator (
mem_alloc_pages/bank_read/bank_write), explicit memory modes for sub-16 KB programs. - Mouse: full Sprinter driver wrapper (14 functions including custom cursor bitmaps).
- Environment:
getenv/putenv/sysenvover ESTEX$46. - Time:
getdatetime/setdatetime+ POSIXtime/localtime/mktime/asctime/ctime. - Misc:
errno/strerror/perror,atexit,setjmp/longjmp,sleep, full argv parsing in crt0.
Quick start
git clone <this repo> sprinter-c
cd sprinter-c
make sdcc # one-time: fetch SDCC 4.5 binary (~25 MB)
make all # build mkexe + libsprinter.lib + 27 examples
make floppy # pack everything into mame/v306/IMG/mc.img
cd mame/v306 && ./run_mame.sh # boot Sprinter in MAME
Compile a single program:
cat > hello.c <<EOF
#include <stdio.h>
int main(void) { puts("Hello, Sprinter!"); return 0; }
EOF
bin/sprinter-cc -o hello.exe hello.c
That's it — hello.exe is now a valid SprintEXE you can RUN HELLO from the ESTEX shell.
Memory modes
Sprinter's address space is four 16 KB windows (W0 / W1 / W2 / W3). DSS allocates pages by program size — small programs get only one page. Pick a memory mode based on what your program needs:
| Mode | Code lives in | Banking | Use when | Note |
|---|---|---|---|---|
tiny (default) |
W2 (0x8100+) | no | code+data < 14 KB | |
small |
W1-W2 (0x4100+) | no | code+data < 30 KB | |
big |
W2 + W1 banking | yes (W1) | tiny + extra code modules | |
huge |
W1-W2 + W3 banking | yes (W3) | small + extra code modules | |
manual |
user-specified | optional | special layouts | Not implemented |
sprinter-cc --memory small -o big.exe bigprog.c
sprinter-cc --memory huge -o app.exe main.c --bank 1=engine.c --bank 2=ai.c
Banked functions are declared with __banked:
void engine_tick(int dt) __banked; // lives in BANK1, automatically swapped
Examples (27 total)
| Example | What it demonstrates |
|---|---|
hello |
Hello world with stdio + conio Turbo-C-style colors |
argv |
argv parsing in crt0 |
cat |
File I/O — read & print TEST.TXT |
seek |
32-bit lseek over a 100 KB file |
ls |
Directory listing via ffirst/fnext |
filetest |
FILE* streams (fopen/fread/...) |
stattest |
stat/fstat on files and directories |
errno |
errno / strerror / perror |
mem_test |
Page allocator + bank_read/bank_write |
malloc |
Heap stress test (200+ allocations) |
banked |
Banked code in W3 (huge mode) |
bankedbg |
Banked code in W1 (big mode) |
banklocl |
Bank-local static data and BSS |
mouse |
Mouse driver in text mode |
gfx_mous |
Mouse with custom bitmap cursor in graphics mode |
gfx_demo |
320×256×256 lines / rects / accelerator |
gfx_d16 |
640×256×16 same primitives |
gfx_text |
Bitmap-font text on graphics screen |
timedir |
Date/time + directory listing |
ptime |
POSIX time API |
openenv |
open() flags + environment vars |
conio |
conio API smoke test |
attrprob |
Probe Sprinter text attribute byte layout |
strtest |
string.h test (from SDCC's z80.lib) |
stdlib |
stdlib.h test (qsort / rand / strtol / etc.) |
assrtest |
assert() |
rt_test |
Runtime helpers (sleep, setjmp, atexit) |
Headers
Standard:
<stdio.h>— puts / printf / FILE* + Sprinter-specific dec/hex helpers<stdlib.h>— atoi / atof / malloc / qsort / ... (from SDCC z80.lib)<string.h>/<ctype.h>/<math.h>— from SDCC z80.lib<unistd.h>— read / write / close / lseek / unlink<fcntl.h>— open / creat + O_RDONLY / O_CREAT / ...<errno.h>— errno + error names + strerror<sys/stat.h>— stat / fstat<setjmp.h>/<assert.h>— from SDCC
Sprinter-specific:
<conio.h>— putch / cputs / cprintf, textcolor / textbackground / textattr, kbhit / getch, clrscr, gotoxy, wherex/y<gfx.h>— gfx_init/done, palette, putpixel, hline/vline/rect/fill_rect/line, text — for both 320 and 640 modes (gfx_*16 variants)<mouse.h>— full 14-function driver wrapper + mouse_cursor_t with bitmap support<dir.h>— chdir / getcwd / mkdir / rmdir / ffirst / fnext / ffblk<time.h>— getdatetime / setdatetime + POSIX time / localtime / etc.<sprinter.h>— raw ports, ESTEX/BIOS function numbers, env API<sprinter_exit.h>— exit / _exit / atexit<sprinter_mem.h>— mem_alloc_pages / mem_free_block / bank_read / bank_write<sprinter_compat.h>— Solid-C compatibility layer (aliases + BOOL/WORD/uint types)
Toolchain commands
make all # build mkexe + lib + every example
make floppy # repack mame/v306/IMG/mc.img with all .exe files
make check # 17 mkexe unit-tests
make clean # remove all build artefacts
make sdcc # one-time: fetch SDCC 4.5 binary
sprinter-cc options
sprinter-cc -o foo.exe foo.c [more.c ...] [options]
--memory MODE tiny | small | big | huge | manual (default: tiny)
--memory-manual SPEC explicit placement (CODE=W1|W2,DATA=W1|W2|SAME,BANKED=W1|W3)
--stack-size N bytes reserved for the stack (default ~1278)
--crt0=TYPE default | minimal | banked | small
--bank N=FILE.c compile FILE.c into bank N (repeatable, max 15)
--debug enable runtime diagnostics (defines DEBUG_RT)
-I PATH extra include path
-L 0xADDR / -E / -S override load / entry / stack addresses
-Wl FLAG pass FLAG to sdldz80
--mkexe FLAG pass FLAG to mkexe (e.g. --mkexe -p --mkexe 0 for bank padding)
-v verbose
Status
What works in v1.0:
- Compile / link / pack to SprintEXE — verified on all 27 examples
- Four memory modes (tiny / small / big / huge)
- Graphics (both modes) with accelerator
- Mouse (text + graphics cursor)
- File I/O, directories, environment, time
- All headers listed above
Deferred to v2.0 (see docs/TODO.md):
- Turbo-C-style BGI graphics API —
initgraph/setcolor/circle/getimage/putimage/ etc. on top of ourgfx_*primitives - Remaining Solid-C compatibility gaps (Phase 2/3) — see
docs/solid_c_compatibility.md - Manual memory mode
- Rewrite FILE* stream API (current implementation is very primitive and doesn't use buffers)
Deferred to v3.0:
- IM2 interrupt handlers — research complete (
docs/im2_isr_design.md), implementation scheduled for v3 - Audio API (AY-3-8910 + COVOX) — requires IM2
- ISA-8 slot drivers — requires IM2 (???)
Documentation
docs/TODO.md— roadmap and open work itemsdocs/solid_c_compatibility.md— gap analysis vs Solid-C 2004docs/im2_isr_design.md— interrupt handler design (v2)docs/converted/— source documentation (ESTEX, BIOS, architecture) converted to plain text forgrepdocs/reference/,docs/samples/,docs/memory management/— original Russian docs and code samples from Peters Plus
Repository layout
bin/sprinter-cc one-line compiler driver (bash)
toolchain/mkexe/ host-side tool: .ihx -> .exe SprintEXE
toolchain/check_banks.py post-link bank size enforcer
runtime/ crt0 variants (default, minimal, small, banked)
bank trampolines, heap, heap_top
libc/include/ headers
libc/io|stdio|mem|gfx/ C and asm sources for libsprinter.lib
lib/ Makefile that archives libsprinter.lib via sdar
examples/ 27 example programs
mame/v306/ MAME binary + Sprinter ROM/HDD images + floppy script
third_party/sdcc/ vendored SDCC 4.5 (fetched via `make sdcc`)
third_party/solid-c/ reference: original Sprinter native C (for compat target)
docs/ documentation
License
This repository contains:
- Original code in
bin/,toolchain/,runtime/,libc/,lib/,examples/— MIT-licensed. third_party/sdcc/— SDCC 4.5 under GPLv2 with linking exception (seethird_party/sdcc/COPYING.txt)third_party/solid-c/— original Sprinter Solid C, used only as a reference
Credits
- Sprinter / Peters Plus — Иван Мак, Дмитрий Паринов and the original team
- SDCC — for the underlying Z80 compiler
- MAME — for the Sprinter Sp2000 emulation
For questions / patches: see CONTRIBUTING.md (TBD) or open an issue.