Dzisiaj kodersko. Aż dwa dema na odbywającym się w ten weekend w Opolu zlocie Moonshine Dragons powstały dzięki atarowcom. Łączy je również to, że obie zostały przygotowane na nieco zapomniane przez kolegów komodorowców platformy - Commodore PET i Commodore 128, a także, że obie produkcje zostały wystawione w kategorii Wild, gdyż nie było na zlocie innej, w której mogły wziąć udział programy dla tych maszyn. Pecik Demko dla Commodore PET 3032 zajęło pierwsze miejsce, zdobywając w głosowaniu 398 punktów, a Uneasy zajęło czwarte miejsce, zdobywając 310 punktów.
1. Commodore 128 i Bober, SimOne, Dma-sc
Kolega Rafał "Bober" Ciepiela z atarowskiej grupy M.E.C. postanowił wykonać produkcję scenową dla innej platformy niż Atari 8/16/32-bit, o czym opowiada:
"Jak już wiecie, wypuściłem na mijającym zlocie Moonshine Dragons intro pod tytułem "Uneasy" na C128 — maszynę chyba najmniej popularną na scenie pod względem liczby sensownych produkcji scenowych wśród popularnych komputerów (takich, których liczba sprzedanych egzemplarzy szła w miliony [1]). Nie chcę wchodzić w dyskusję o tym, który sprzęt jest lepszy, po prostu pochwalę się [2] swoim toolchainem.
Wstęp, czyli co to jest?
Historia powstania specyficznej konstrukcji C128 jest znana [6], a jej podstawowe cechy to:
1. Na płycie komputera są dwa procesory: 8502 (kompatybilny z 6502) oraz Z80. Niestety, nie mogą one pracować jednocześnie, ponieważ współdzielą magistrale. Podczas uruchamiania maszyną steruje Z80, ale jeśli nie wykryje środowiska CP/M, to przekazuje sterowanie do 8502.
2. Maszyna ma trzy tryby pracy: tryb C64, w którym kompatybilność z C64 jest bardzo duża; tryb CP/M, który jest zgodny z całym oprogramowaniem dla tego systemu, ale niestety z powodu słabego taktowania Z80 pracuje około połowę wolniej niż konkurencja; oraz natywny C128, który nie jest kompatybilny z C64.
3. W trybie C128 mamy dostępne m.in: a) Stronę zerową można przemapować na dowolną inną stronę w pamięci. Działa to w ten sposób, że MMU (osobny układ) przemapowuje odwołania do strony zerowej na dowolną inną, wcześniej skonfigurowaną stronę pamięci. Dla CPU to jest przezroczyste. b) To samo, co wyżej, można zrobić ze stosem. c) Maszyna ma 128k RAM-u. Szczegóły dotyczące tego, jak działa ta pamięć, poniżej. d) 8502 może pracować z częstotliwością 1 lub 2 MHz. e) C128 ma dwa układy graficzne: VIC oraz VDC. VIC generuje obraz identyczny jak w C64, jest on znany jako tryb 40-kolumnowy. Drugim układem jest VDC. Generuje on obraz 80-kolumnowy, zgodny ze standardem CGA. Każdy układ ma własne wyjście na monitor. Wygląda to zatem podobnie jak w przypadku Atari ST — do pełnej pracy potrzebne są dwa monitory. Jednak tutaj, gdy CPU pracuje na 1 MHz, oba układy wyświetlają obraz jednocześnie (ta funkcjonalność jest używana przez grę "PETSCII Robots' – na jednym monitorze jest ekran akcji, na drugim – mapa). Gdy przełączymy CPU na 2 MHz, VIC odłącza się od pamięci i generuje jednolity obraz w kolorze tła (wygląda to jak Antic z wyłączonym DMA). Zostaje nam tylko VDC. f) VDC ma niezależną od CPU pamięć graficzną. Dostęp do niej jest możliwy tylko przez jeden rejestr sprzętowy i jest dość wolny. Ogólnie ten układ trochę przypomina sytuację, w której atarowski XEP80 został wbudowany do środka XE.
na tym C128D, z niemałymi kłopotami technicznymi, było prezentowane demko "Uneasy"
Struktury plików wykonywalnych
Na początek zacznijmy od teorii i opisu tego, jak wyglądają struktury plików wykonywalnych na obu platformach.
Plik binarny dla XE to plik z nagłówkiem FFFF (dwa pierwsze bajty to $FF), które są wymagane w przypadku pierwszego bloku w pliku i opcjonalne dla kolejnych. Dalsze elementy bloku to adres początkowy (dwa bajty), adres końcowy (kolejne dwa bajty) oraz dane do załadowania. Jeśli adresy początkowy i końcowy są sobie równe, blok ma długość jednego bajtu. Bloków może być wiele i mogą nachodzić na siebie w pamięci (co jest używane, np. gdy ładujemy dane do pamięci rozszerzonej 130XE). System obsługuje jeszcze dwa specjalne bloki (adresy): a) INIT - ładowanie jest przerywane i uruchamiany jest blok inicjalizacyjny. Po jego zakończeniu ładowanie jest kontynuowane. Ta opcja jest używana np. w grach do wyświetlania napisu "loading" lub podobnego. b) RUN - blok określa adres uruchamiania aplikacji (entry point). System uruchamia kod zawarty pod tym adresem po załadowaniu ostatniego bloku. Jeśli nie ma bloku RUN, używa się adresu pierwszego bloku.
Na Commodore interfacem jest Basic, zatem programy binarne mają postać programów w nim napisanych. Standardowo wygląda to tak, że na początku pliku znajduje się stokenizowana linia z komendą SYS oraz adresem uruchomienia aplikacji (entry point). Linię tą można zobaczyć wpisując LIST po załadowaniu programu, zwykle jest to coś w stylu:
10 SYS 7181
Zaraz za tą linią w pamięci następuje kod aplikacji. Basic jednak tego kodu nie pokazuje. W przypadku C128 adres ładowania to $1c01 (dla C64 jest to $0801), co sprawia, że pliki dla C128 i C64 są ze sobą niekompatybilne. Z punktu widzenia XE wygląda to jak plik z jednym blokiem o zdefiniowanym na stałe adresie ładowania.
Pamięć
C128, jak już wspomniałem, ma 128 kB RAM-u. Pamięć ta jest podzielona na dwa banki po 64k każdy, więc podmieniamy całą pamięć widoczną dla CPU. Jednakże żeby łatwiej pracowało się z taką pamięcią, dostępne są wspólne okna na początku i/lub na końcu przestrzeni adresowej. Rozmiar wspólnych okien można zmieniać od 1k do 16k (od 1k na początku i 1k na końcu na pamięć wspólną do 16k na początku i 16k na końcu). Technicznie, pamięć wspólna to zawartość banku 0. Im większe okno, tym mniejszy rozmiar podmienianej pamięci (od 63k do 32k). Przykładowo, dla okna o rozmiarze 1k na początku i na końcu przestrzeni adresowej pamięć wspólna jest dostępna pod adresami $0000–$03FF oraz $FC00–$FFFF, a przestrzeń od $0400 do $FBFF jest zastępowana. Do tego dochodzą jeszcze ustawienia ROM-ów oraz widoczność rejestrów sprzętowych. W linku [7] znajdziecie mapę pamięci C128 oraz dodatkowe informacje.
Uneasy
Na początek tyle teorii. Ale do czego ona jest tu przydatna? Bo pisząc "Uneasy" dla C128 korzystałem z atarowskiego toolchaina i to może kogoś zainteresuje. Całe intro było kodowane w starym, dobrym MADS-ie [3], kompresowane exomizerem [4]. Nagłówek dla głównego pliku w asemblerze jest następujący: opt h-f+
dta a(upstart_end) ; link address dta a(10) ; line number dta b($9e) ; SYS .cbm "7181"; value of 'main' in decimal, text format dta b(0) upstart_end dta a(0)
main lda #$00 ; entry point [...]
To typowy nagłówek dla Commodore. Od wersji C64 różni go tylko adres $1c01 (7181 dziesiętnie). Kod dema rozpoczyna się od etykiety main. W "Uneasy" ów nagłówek trochę rozbudowałem, więc to słaby przykład, choć działa tak samo. "Uneasy" współdzieli silnik dema razem z innymi moimi produkcjami napisanymi dla XE, który to silnik opisałem już na AtariOnline.pl [5]. Różnica jest taka, że tutaj użyłem dwóch slotów zamiast czterech. Ze względu na specyfikę VDC tyle wystarczy.
Każdy efekt rezyduje w określonym miejscu pamięci, a jego źródło zaczyna się w jak najbardziej atarowski sposób. Dla przykładu tunel: org TUNNEL_DEST
Czyli deklaruję standardowy DOS-owy blok FFFF. Po zasemblowaniu powyższego źródła nagłówek jest dobrze widoczny na początku pliku obj (pierwsze sześć bajtów: FF FF 00 AA A8 DF): $ mads tunnel.asm -x -t:labels.out -o:tunnel.obj Writing object file... 521 lines of source assembled in 3 pass 9647 bytes written to the object file
Kolejnym krokiem jest kompresja przy użyciu exomizera. To narzędzie w trybie mem (kompresja danych z i do pamięci) rozpoznaje bloki FFFF: $ exomizer mem -l none tunnel.obj -o tunnel.exo filename: "tunnel.obj", loading from $AA00 to $CFA9 crunching from $AA00 to $CFA9 [...]
Później już z górki, bo skompresowany blok dołączam bezpośrednio do kodu (dyrektywą ins MADS-a): lib_tunnel ins 'tunnel.exo' lib_tunnel_end
lib_tunnel_size equ lib_tunnel_end-lib_tunnel
lda sta exod_zp_src_lo ; exomizer input data pointer lda >buffer sta exod_zp_src_hi
lda sta exod_zp_src_el ; exomizer buffer size lda >buffer+exo_tunnel_size sta exod_zp_src_eh
lda sta exod_zp_dest_lo ; exomizer target pointer lda >TUNNEL_DEST sta exod_zp_dest_hi jsr exod_decrunch ; decompress data [...]
Teraz można już efekt uruchomić. I tak w praktyce wygląda pisanie kodu dla C128 przy użyciu narzędzi dla XE. A jakby komuś było mało, to więcej programowania C128 jest np. w źródle [7]."
Demko zainicjował Filip "Zoltar-X" Golewski, chociaż opowiada, że to ja go do tego namówiłem. Dementuję, to nieprawda. A jeśli to prawda, to przepraszam, nie pamiętam albo może żartowałem. Tak czy siak, idea przyszła nam do głowy po tym, jak Filip pokazał mi Commodore PET na jednym z poprzednich zlotów Silly Venture. Pozyskał zdezelowany i niedziałający egzemplarz, którego wielkim wysiłkiem Drygola udało się przywrócić nie tylko do życia, ale i pięknego wyglądu. Szkoda, żeby maszyna stała bezczynnie, więc powstał pomysł na zrobienie czegoś na nim. Zielony ekran monitora aż się o coś prosił...
prace nad "Pecik Demko"
Ponieważ to urządzenie nie ma żadnego złącza wideo, bo przecież ma wbudowany monochromatyczny monitor, ma tylko tryb tekstowy 40x25 znaków i nie można definiować własnych znaków, a żadnych dźwięków nie wydaje, to wybór padł na wykonanie jakiegoś prostego, małego demka ze znaków PETSCII. W ciągu kilku tygodni Filip wypromptował narzędzie do rysowania takimi znakami i przystąpiłem do pracy. Dzień przed zlotem i w dniu zlotu, a nawet na samym zlocie rysowałem graficzki semigraficzne, nie wiedząc nawet, jak one będą wyglądać na prawdziwym sprzęcie. Udało mi się zaproponować jednak pewien design demka, nazwę i narysować animacje. Nie wszystko się jednak udało zmieścić. Swoje znaczki dosłał jeszcze nieobecny na party Robert "Tiger" Smoliński, a fantastyczny kawałek kodu napisał na samym party obecny Waldemar "Laoo" Pawlaszek. Z limitem około 29 kB na całość musieliśmy się pozbyć kilku narysowanych przeze mnie scenek. Ale tak to w pracy twórczej bywa. My z Zoltarem-X pracowaliśmy nad sekwencjami i układem, a w tym czasie Laoo i obecny Solo żywo dyskutowali o urozmaiceniu demka jakimś demoscenowym efektem. Waldek podesłał mi opis swojej pracy:
"Krystalizując kształt produkcji na PET-a na Moonshine Dragons, staraliśmy się z Zoltarem i Solem wymyślić, co można byłoby do niej realistycznie dodać ponad to, jaki szkielet miał już Zoltar-X, czyli silnik do prezentowania slide-show. Realistycznie, czyli coś, co zdąży się napisać do kompo w piątek i ewentualnie w sobotę. Na pomysł bump-mappingu wpadł Solo i po zawężeniu opcji na tym ostatecznie stanęło jako coś, co faktycznie można napisać najszybciej.
PET "w środku nic nie ma". W sprzęcie są zaszyte dwa generatory znaków po 128 znaków plus inwersja. Pamięć ekranu jest osobnym VRAM-em, trzymającym kody znaków i składającym się z 40*25 bajtów od adresu $8000. Samego RAM-u jest dużo, bo 32kB. Dodatkowo nie umieliśmy wyłączać przerwań i korzystanie ze strony zerowej było bardzo ryzykownym przedsięwzięciem, bo KERNAL i BASIC zostawiali niezagospodarowane tylko kilka bajtów, a używanie reszty mogło nieść ze sobą niespodziewane konsekwencje. Większość części demka miały stanowić spakowane grafiki, więc to zdeterminowało, że bump musi być optymalizowany pamięciowo.
"ciemnia fotograficzna"
Do dyspozycji był tylko tryb tekstowy z predefiniowanymi znakami, więc odcienie szarości, niezbędne do uzyskania efektu, trzeba było uzyskać, wybierając odpowiednie znaki z generatora. Pamiętałem, że w sieci jest dużo produkcji renderujących różne grafiki (nawet DOOMa) w trybie tekstowym, ale po szybkim przejrzeniu algorytmów wyboru znaków okazało się, że bazują one na wysokiej rozdzielczości i jasność znaku aproksymują liczbą zapalonych pikseli. Przy małej rozdzielczości z PET-a to nie zdałoby egzaminu. Próbowałem promptować jakiś algorytm, który bierze pod uwagę kształt fontów, ale taki konwerter grafiki lightmapy na znaki nie dał zadowalających rezultatów. Najlepsze, co mieliśmy, okazało się algorytmem z narzędzia Zoltara do konwersji grafiki na fonty — z lightmapą przekonwertowaną na dwa kolory po ditheringu poradził sobie na tyle zadowalająco, że ostatecznie tego użyłem, wycinając mapę 16x16 zakodowaną w pliku light16x16.bin.
Dalej do gry dołączył GIMP, w którym wygenerowałem dwie mapy wypukłości o rozdzielczości 41x26 jako grafiki o ośmiu poziomach jasności. Mapowanie nierówności korzysta z gradientów takiej mapy, na potrzeby czego wypromptowałem skrypt, który dla każdego piksela o względnej jasności 0–7 oblicza różnicę dla piksela po prawej oraz piksela u dołu. Różnica jest ze znakiem z przedziału -7 do 7, co koduję na wartości 0..14, dodając 8. Każdy piksel produkuje bajt, w którym gradient pionowy jest pamiętany na starszym niblu, a poziomy na młodszym. W ten sposób powstały dwie mapy pochyłości: slope1.bin i slope2.bin.
Mając już lightmapę oraz mapy pochyłości, nastał czas na sam algorytm. Idea jest prosta – w każdy znak ekranu wmapowuję jeden znak z lightmapy. Najprościej można sobie wyobrazić, że bez żadnych offsetów algorytm ma za zadanie wkopikować lightmapę w lewy górny róg ekranu. Ruch lightmapy realizujemy przez dodawanie odpowiednich offsetów w poziomie i w pionie, czyli wartości początkowej, jaka obowiązuje w lewym górnym rogu. Samo mapowanie wypukłości polega na tym, że dla każdego znaku ekranu mamy odpowiadający mu bajt z tablicy slope1.bin/slope2.bin i przesuwamy offset w pionie o wartość zakodowaną w starszym niblu, a w poziomie o wartość z młodszego nibla, co przesuwa nam lightmapę dla każdego piksela niezależnie, stąd złudzenie wypukłości. Oczywiście, w samym algorytmie jest kilka zawiłości technicznych, takich jak to, jak dekodować wartość dla każdej osi osobno. Na szczęście liczba znaków do przetworzenia jest na tyle mała, że bardzo nieefektywny algorytm robiący wiele przesunięć bitowych i dodawania działa dość żwawo.
ekipa autorska obecna na Moonshine Dragons
Dodatkowo, ze względu na niepewną stronę zerową, musiałem stosować kod, który z niej nie korzysta, kosztem intensywnej automodyfikacji adresów, z których czyta dane i do których zapisuje. Sam kod bumpa jest w bump.asm, jest tam też procedura przełączająca wykorzystanie dwóch map pochyłości. Kod testowy, który można zasemblować za pomocą dasm jest w bumptest.asm, asemblujemy przez dasm bumptest.asm -obumptest.prg. Powstały kod uruchamiamy w emulatorze PET-a przez xpet -mode 3032 bumptest.prg. Kod sprawdza naciśnięcie spacji i wtedy przełącza mapy pochyłości."
Tyle od Waldka, ale ja dodam jeszcze, że po ukończeniu rysowania i kodowania, nasza praca tak naprawdę się nie zakończyła. Powstał problem z... pokazaniem dema. Ostatecznie poproszono nas o przygotowanie nagrania wideo z ekranu PET-a, który w czasie kompotów był pokazywany na bigscreenie, a równocześnie odpaliliśmy demko z prawdziwego sprzętu, które pewnie mogli dostrzeć nieliczni, ze względu na malutki ekranik (9 cali). Aby nagrać to wideo, wykonałem ad hoc "ciemnię" do nagrywania telefonem Laoo - z tego co wpadło w rękę: kartony od jedzenia, czarna koszulka, bluza, a nawet baner działkowców. Nagranie i tak było dość kiepskie, ale zebrana kilkudziesięcioosobowa publiczność obdarzyła nas zaufaniem, że zrobiliśmy, co mogliśmy, i przyznała nam pierwsze miejsce w kategorii Wild. Dziękujemy!
prawie równoczesny pokaz wideo i prawdziwego programu
Plik demka "Uneasy" tutaj, a demko "Pecik Demko" tutaj oraz procedurki bump-mappingu od Laoo tutaj.
Filmik, który puszczaliśmy na zlocie, z podkładem Zoltara-X z Pokeya, poniżej:
Tak jest! I dla wszystkich uczestników. Twoje demko było moim faworytem, dałem mu w naszej kategorii najwięcej punktów. I dziwię się, że nie zostało docenione. Myślę, że duży wpływ miało na to zamieszanie z C128, który nie odpalił i prezentacja była przeniesiona na później. Na pocieszenie - nagród generalnie nie było :D
Benoit Leroy-Dubois @2026-05-31 21:48:50
Co to się porobiło na tym świecie. Atarowcy piszą demka dla ... commodore. eh.
bocianu @2026-05-31 23:21:25
dobra robota. gratki!
mono @2026-05-31 23:55:13
Tak, tak, gratulacje!
Drygol @2026-06-01 00:26:29
Dzięki za wspominkę! :D Mega się cieszę, że chłopaki zrobili produkcję na ten sprzęt!!! :D Petardunia!