Retrocomputing by Macc

2015 Február 20.

Bele is vágnék a közepébe: kb. 8 éve egy akkori kollégám szerzett nekem egy régi Mac-et kerek 2500 Forintért, pontosabban egy Mac Classic II-t. Mivel mindig is szerettem a Macintosh gépeket, így nagy örömmel fedeztem fel a régi kis gépet, volt rajta egy használható rendszer, szépen boot-olt, bár programok nem nagyon voltak hozzá, de elsőre megkedveltem a kis fekete-fehér kijelzőt. Mivel nem sokat tudtam a gépről, így kicsit meglepődtem, hogy még szürke árnyalatokat sem tudott megjeleníteni, csak fekete v. fehér pixeleket. Na abban a pillanatban kapott el az az érzés, hogy régi nagy c64-es kedvencemet a Head over Heels-t jó lenne portolni erre a gépre. A gond csak az volt, hogy kb. semmit nem tudtam arról, hogy hogyan is lehetne ehhez hozzálátni...

Azóta eltelt jó néhány év, a gép egy szekrény alján porosodott, mikor újra elővettem már be sem kapcsolt... Azaz bekapcsolt, de csak néhány zavaros csík volt a képernyőn, a boot-gong meg sem szólalt. :(

Ekkor kezdődött el egy kisebb kutató munka, hogy mit is lehet kezdeni a helyzettel, és kiderült, hogy ezekben a gépekben szeretnek elfolyni az elöregedett elektrolit-kondenzátorok(?) - Tehát rendeltem újakat, a gépet szépen alkatrészeire szedtem, megpucoltam, régi kondikat leforrasztottam, újakat a megtisztított helyükre tettem, és láss csodát, újra működött szépen! Bla-bla-bla, mielőtt túl unalmas lenne az egész történet, átugranám az elhalálozó régi SCSI vinyók kálváriáját, a lényeg, hogy most már van négy öt tökéletesen működő öreg Mac-em (Mac Classic II, Mac LC, Mac LC III, Mac IIci, saját 13" RGB Apple Display-el + Mac LC 475), ebből kettőben vinyó helyett egy AztecMonster SCSI-CF CARD adapter dolgozik, ezeket használom most fejlesztésre...

Hosszas kutatómunka után, most már működő telepített fejlesztői környezet fut a régi gépeken (Metrowerks CodeWarrior 4), az online elérhető dokumentációkat mind letöltöttem, amit nem lehetett, megrendeltem (Inside Macintosh Volume IV, V, VI).

Jelenleg ott tartok, hogy elkészült első c64 intro jellegű programom Mac-re. Itt most tennék egy rövid kitérőt: c64-en tanultam programozni, csináltam rá sok intro-t, pár bénább demo-t, a lényeg hogy a hardver közvetlen kódolása, a spórolás a RAM-mal, és a bitek közvetlen manipulációja az agyamba égett. Ezek a dolgok máig hiányoznak a mai munkáimból. Éppen ezért ezeken a régi gépeken nem izgat különösebben egy ablakos program létrehozása gombokkal, listákkal, lassú recegős animációkkal. Ezek tonnaszám elérhetőek, a régi Mac-es játékok egyáltalán nem nyűgöznek le (azért akad persze 1-2 kivétel, de azok sem a jó vizuális effektjeikkel hatottak rám). Mivel az ősi c64-en szinte minden játék gyönyörűen, vaj sima animációkkal futott (50fps), nagyon hiányoltam a képernyővel szinkronban futó sima, gyors animációkat. Már-már beképzeltem, hogy ezeken a gépeken ilyet nem is lehet csinálni, de mint hamarosan kiderül, lehet...

Ugye C64-en, Amigán, vagy egy Game-Boy-on van egy csomó hardveres opciónk, hogy hogyan is manipuláljuk a képernyőn megjelenő tartalmat, eltolhatjuk bitenként erre vagy arra, csak egy pár bitet kell megváltoztatnunk egy regiszterben, vagy ugyanilyen egyszerűen váltogatunk képernyő üzemmódok között, megfelelő raszterpozíciónál megszakítást válthatunk ki, és lám kész is vannak a szép rasztertrükkök, de Mac-en (és most a régi motorola 68k procis szériáról beszélek) ezekből nincsen semmi, van egy halom képpontunk, egy felbontásunk (monitor függő), és a legjobb amit tehetünk ha az oprendszert megkerülve, közvetlen a Video RAM-ba irkálva, elfoglaljuk a terepet. Szóval bármi lehetséges, csak számoljon a proci jó gyorsan. Én 2 gépre koncentrálok most fejlesztés közben, az egyik (a munkaállomás) egy Mac LC III (1993-ból, 36 MB RAM, motorola 68030 25 MHz, 640x480 max. 32 bit/pixel kijelzővel) és egy Mac Classic II (1991-es, 10 MB RAM, motorola 68030 16 MHz, 512x342 1 bit/pixel), és az első benyomások elég meggyőzőek, már ami a sebességet illeti, pedig még teljesen optimalizálatlan a kódom, azaz sima C-ben íródott minimális assemblyvel keverve. Meggyőző alatt azt értem, hogy egy félképernyős stretch effekt 60 fps sebességgel fut, és még a rendelkezésre álló erőforrás felet sem használtam ki.

A Head over Heels port projekt

A mozgatórugója annak a sok mindennek amit most csinálok, a címben szereplő régi kedvenc portolásának megvalósíthatóságára való törekvés. Magyarul, ahhoz, hogy megszülethessen a játék Mac-en (Ha elkészül, szerintem a platform egyik legjobb játéka lesz, ami elsősorban az eredeti játék készítőinek érdeme), először is meg kell tanulnom gyorsan manipulálni a pixeleket a képernyőn, és büszkén jelenthetem ki, hogy ez meg is történt. Ez rengeteg elfeledett dokumentum átolvasásának az eredménye, amit még több on-line kutatás előzött meg.

Az egészben az volt a legproblémásabb, hogy amit akarok, az abból a szempontból rendhagyó, hogy a képernyő közvetlen módosítását legtöbben kifejezetten nem ajánlják (érthető okokból), ezért ezekről nagyon kevés információ áll rendelkezésre, valamint egy régi elfeledett, amúgy sem a legnépszerűbb platformról van szó. Az én szívemhez viszont nagyon közel áll, mégiscsak ugyanaz a szív dobog benne mint egy Amigában:)

Szóval minden adott, a HOH pont egy olyan játék, amihez nem kell semmi spéci video effekt, csak egy 1 bites képernyő, és némi erőforrás, mert a c64 azért rendesen megizzadt az izometrikus pálya renderelésével. Az eredeti játékból a grafikáját könnyen ki lehetett szedni, a felbontás különbség viszont kicsit gondolkodóba ejt: 320x200-ra optimalizált grafikát 512x342-n nem egyszerű megjeleníteni. 1:1-ben egyszerűen apró lenne az amúgy is kicsi (9") kijelzőn, 2-szeres felbontásban (ami viszont ideális a 640x480-hoz) meg már nem férünk el, úgyhogy jelenleg egy 150%-os nagyításon gondolkozom, ami egy sor további problémát vet fel, de ezt majd később.

Na kicsit átugranék a C64-es vonalra, mert ugye a portolni kívánt játék eredetije azon fut. Sok szempontból szerencsém van, mert a játék kb. 45 KB, így egy sima memória snapshot-tal kinyerhető minden információ. Kis ügyeskedéssel gyorsan meg lehet szabadítani a kinyert adatokat a feleslegtől, megvan a belépési pont is ($5B03). Kis matatás árán rögtön megvoltak a grafikai elemek (Jelen pillanatban a pislogás és a pálya pereme meg nincs meg), valamint a teljes disassembly. Ezt jókedvemben ki is nyomtattam, lett kb. 150 oldal. Jegyzetelni, átfutni jó lesz. A következő fázis a programkód értelmezése, vagyis visszafejtése. Ez egy igen jópofa feladat, és hozzá sem látnék, ha nem ismernem jól a c64-es gépikódját. Na de amitől ez a rész érdekes, hogy a kinyomtatott listán, főleg egy 8000 soros assembly programlistán eligazodni nem egyszerű. Ezt megkönnyítendő, írtam egy speciális c64 emulatort...

Saját c64 emulator

Na szóval, biztos sok hasonló létezik, nem tudom, nem találtam, de nekem egy olyan emulator kellett, amivel futás közben látom a teljes C64 memóriáját, nyomon követhessem a program futását, írást/olvasást, a regiszterek állapotát, a Stack-et oly módon, hogy a program belépési pontjától nyomon követhető legyen milyen szubrutinokat hívott meg a kód, ezt log-olja, disassembly-t keszit kommenttel (hogy éppen pontosan milyen érteket honnan, hova mozgat). Na most ez az emulator már eddig is sokat segített, pedig még szinte el sem készült. Segített megérteni, hogyan építi fel az izometrikus tér részleteit a kód, valamint, hogy a kód melyik része fut mikor épp ez történik, stb.

Custom C64 emulator

A terv

A folyamat neheze most következik, és ez válaszút elé állít... Két lehetőségem van: vagy vert izzadok hosszú időn át, és megfejtem a program működését, vagy az alapján amit látok, érzékelek megírom a játékot.

Nyilván egyik sem egyszerű, a kérdés az, van-e értelme az eredeti kódot visszafejteni, és ha igen, milyen mértékben. Mi az amit meg lehet figyelni egy játékon, és mi az amihez pontos számszerű adatok kellenek, hogy klónozni lehessen. Kérdés az is, hogy mennyire ragaszkodjak az eredetihez, és min kellene javítani?

Mivel a játék úgy jó, ahogy van, a legjobb lenne mindent úgy hagyni, ahogy van, de van pár dolog amin azért mégis szeretnék illetve változtatni kell:

Kódolas Mac-en

Miután félkész állapotba került az emulator, kis klíma változásként elkezdtem a kódolást a Mac-en. Nehezen vettem rá magam, mivel teljesen idegen környezet volt. Egyrészt sosem programoztam C-ben. Régebben sok forráskódot olvastam már, néhányon módosítgattam pár dolgot, de sosem írtam C programot, főleg nem a semmiből. Szerencsémre viszont a Mac-en futó CodeWarrior 4 eddig teljesen stabilnak bizonyult (korábban kínlódtam egy sort az MPW-vel, de szerencsére sokat fagyott, mert a CW jobb, és kisebb kódot is generál*), így viszonylag gyorsan eljutottam arra a szintre, hogy OS-ből indítható app, közvetlenül és zavartalanul írjon a képernyőre, VBL azaz Vertical Blanking Interrupt (megszakítást) használjak a sima animációkhoz, és szépen kilépjek a programból vissza a rendszerbe. Ennek a tesztelésnek lett az eredménye egy kis intro (amiről korábban már említést tettem), egy scroller-rel, és egy hullámzó "logo"-val vagy nevezzük annak aminek akarjuk... * Valamiért szeretek tudni minden byte-rol a programjaimban (már ahol ez lehetséges), és kifejezetten zavar, ha olyan kód van benne, amit nem is futtatok soha.

2015 Március 04.

Egy kis idő eltelt az utolsó mondat óta, közben létrejött pár dolog ami a projekthez tartozik. Folytatva a Mac-en kódolást, megszületett a 2-es számú Mac Intro is, melyben egy kicsit nagyobb grafikát sikerült szép gyorsan megmozgatni a képernyőn, (Mac Classic-on meg nem az igazi) valamint egy DYCP scroller is készült. Lásd majd a kepéken, JS demo-ban...

Kódvisszafejtés II.

Nagy lökést adott a kódvisszafejtésnek, hogy egy egyszerű script segítségével, az összes rutin kapott egy hívás listát, a kód és adatblokkok szét lettek választva és sorszámoztattam őket, valamint egy másik kis script-tel a program összes változóját is listába szedtem kiemelve azok elérésének helyét módját, ez utóbbi egy 5000 soros lista lett, kb. így kell elképzelni:

       7743 - STA $5B2C        ; $5b2c-be visszairas.
       a60a - LDA $5B2C
       a60f - STA $5B2C
5B2D:  796d - LDA $5B2D
5B2E:  5c1f - LDA $5B2E        ;4. frame-re var... (ill 4 frame utan nullazodik)
       5c26 - STA $5B2E
       95f9 - DEC $5B2E        ; csokkentjuk $5b2e-t, a frame counter-t
       95fe - INC $5B2E        ; ha negativ lett vissza 0-ra

; CODE block 3 ---------------------------------------------
5C6F:  5c54 - STA $5C6F
5C73:  5c4e - STA $5C73

; DATA block 4 ---------------------------------------------
5CE2:  5ccc - LDX $5CE2
5CE3:  5ccf - LDY $5CE3
5CE4:  5cd5 - LDX $5CE4
5CE5:  5cd8 - LDY $5CE5

Az előző kódrészlet értelmezése: Változó címe: hivatkozás címé - kód ami a változóra hivatkozik (írja és/v. olvassa).

A Rutinok hívás listájának összeállítása egyszerűen úgy történik, hogy a kódban megkeressük minden JSR, JMP és Bcc utasítást, és megnézzük hova mutat. A mutatott címnél feljegyezzük, hogy honnan ugrottunk oda. Végigfutva az egész kódon az eredmény egy hosszú lista amiben minden hivatkozott cím után van egy sor címünk, ahonnan hivatkoztunk rá. A kódban a hivatkozott kódrészlet elé raktam zárójelbe a hivatkozás helyet: [JMP] (JSR) {Bcc}

; CODE block 32 ---------------------------------------------
[7846, 8af1]				; JMP hivatkozas
(7bea, 7d9a, 8685, 8a82, 8ac4)		; JSR hivatkozas
.C:84d0  8D AF 84    STA $84AF		; szubrutin kod
.C:84d3  0A          ASL A
.C:84d4  0A          ASL A
.C:84d5  6D CE 84    ADC $84CE

Vannak meg indirekt ugrások a kódban, valamint táblázatból vett értékkel átírt ugró utasítások is, azokat majd a kód behatóbb elemzése után fogom tudni csak feltárni, de hát ugye ez egy nagy kirakós...

A változók (nevezzük így őket) összegyűjtése hasonló módon történik, azaz a kódot átnézzük, és kigyűjtünk minden olyan memória címet ahova ír/olvas a kód, és extraként feljegyezzük, hogy hol is történt ez a kódban, és milyen utasítással. Itt szintén marad egy vakfolt, mégpedig a táblázatokból feltöltött indirekt változók, ezeknek pontos műveleti területe (a változónak nevezett memóriaterület) egy ideig meg rejtély marad. A létrehozott lista nagyon hasznos, egyrészt átlátható melyik változóval hol és mit dolgozik a program, azt is lehet látni, hogy mennyire sűrűn használt egy változó, továbbá, megkönnyíti a konstansok megtalálását is (amiket sehol nem írunk, csak kiolvassuk az értekét). Ezek az információk mind-mind közelebb visznek a program megértéséhez. Csak érdekességképpen megjegyezném, hogy 952 változót használ a játék, továbbá 693 szubrutin hivast, 304 elágazást, és 1274 feltételes elágazást, mindezt 12299 kódsorban...

Apple Macintosh LC475

2015 Március 09.

Közben meglett az új Mac-em is, egy LC 475. Ez a pizzás doboz formájú LC sorozat legerősebb tagja. Azonos, 25 MHz-es órajelen átlagosan 3x gyorsabb mindenben mint az LC III-as, kivéve a lebegőpontos számításokat, mivel jelenleg egy M68LC040-es processzor van a gépben, amiből hiányzik az integrált FPU. A fullos proci, amiből semmi nem hiányzik, már úton van Németországból. Itt jegyezném meg, hogy ez a processzor, az utódjával együtt (68060) szerintem a valaha készített legszebb processzorok.

Motorola 68LC040

Na most a közvetlen VRAM manipulációs terveim kicsit megdőlni látszanak. Egyrészt tetszik, hogy amit a memóriába írok az rögtön meg is jelenik a képernyőn, másfelől viszont mivel az LC475 teljesen másképp kezeli a video memóriát, felvetődik a kérdés, érdemes-e a sok különböző architektúrára optimalizálni a kódot? Mivel a Head over Heels nem igényli az extrém gyors VRAM manipulálást, ezért egyértelműen NEM. Ezt a technikát inkább megtartom intro-k és demo effektek írásához. Tehát sokkal jobban járok, ha a rendszer kompatibilis képernyőkezelést választom (Offscreen GWorld), sebességben sokat nem vesztek, a játékban lévő animációkkal valószínűleg a Mac Classic is bőven elbír. Pont.

Izometrikus pálya renderelése

2015 Március 17.

Bármennyire is szórakoztató a c64-es kódvisszafejtés, rá kellett döbbennem, hogy túl hosszadalmas ezen a módon reprodukálni a játékot. Persze jó érzés, amikor pár órás kutatással megfejtek 1-2 algoritmust, és néhány ködös részlet kitisztul, mégis egy olyan komplex kód megfejtése ami egy izometrikus pályát tárol/renderel kihámozni az ismeretlenség homályából igencsak időigényes lenne, és annak hosszútávon már a munkám látná kárát.

Éppen ezért (és Krissz munkásságát, hatékonyságát látva) a talán hagyományosabb módját választottam a játékátírásnak: nézni, és leprogramozni:) Szóval legelőször az általam legrejtélyesebbnek ítélt problémát akartam megoldani, mégpedig az izometrikus pálya renderelését. Biztos vagyok benne, hogy sokfelé megoldása létezik a dolognak, így hat próbáltam valami használható információt fellelni a neten, ám a legrészletesebb leírás sem érintette az általam kérdéses részleteket. Leginkább sík, azaz 2 dimenziós tile-alapú pályák leképzéséről olvastam csak, azok meg teljesen egyszerűen 2 egymásba ágyazott for ciklussal megoldhatók. Viszont amikor függőleges irányba mozdul el egy test, akkor a for ciklusok kudarcot vallanak.

Két nap fejtörés után meglett a megoldás, és utólag nagyon örülök, hogy nem találtam meg más megoldását, mert sokkal jobb érzés mindig ha az ember maga jön rá a titok nyitjára. A megoldás fájdalmasan egyszerű: az egymásba ágyazott for ciklusokat, amik az éppen aktuális elem renderelő funkcióját hívják meg, nem is kell piszkálni. A renderelő rutinon kell csak kicsit változtatni úgy, hogy abban az esetben, ha az éppen kirajzolandó elem mögött van egy még le nem renderelt elem, akkor azt rendereljuk le először, és így tovább. Megpróbálom a problémát és megoldását vizuálisan is részletezni.

Az izometrikus nézettel sima 2D technológiával jeleníthetünk meg 3d-s teret, vagy egyszerű 2D-s játékteret jeleníthetünk meg kvázi 3d-ben. A szépsége a dolognak az egyszerűsége. Egy sima 2D-s tile alapú játékteret így alakítunk izometrikussá:

iso_1

Izometrikus térben, a sima 2D-s felülnézeti pályánk 3D hatasúvá válik. Ha az elemeket egymás tetejére pakoljuk, akkor valódi 3D-s teret alkothatunk, aminek megjelenítésére tökéletesen alkalmas az izometrikus nézet:

iso_2

Amíg minden elem rácsra van igazítva, addig a renderelés is pofon egyszerű, csak hátulról előre, és lentről felfelé kell haladni a rajzolással. Tehát például 3 egymásba ágyazott for ciklus szépen teszi a dolgát. Ha viszont mozgásnál szép folyamatos animációt akarunk látni, akkor nem ugorhatunk egyik pontról a másikra, hanem két rácspont között az utat több lepésben kell megtennünk, ahol a korábbi módszer már kudarcot vall:

iso_3

Ahhoz, hogy az elemeket minden helyzetben és kombinációban le tudjuk renderelni, csak annyi kell, hogy mielőtt bármelyik objektumot kirajzolnánk, ellenőrizni kell, hogy nincs-e mögötte olyan elem, ami meg nincs kirajzolva a képernyőre, ha van, akkor először azt kell kirakni. Hogy az átfedések vizsgálatát leegyszerűsítsem, és a renderelés ténylegesen a kamerához közeledve haladjon, a koordináta rendszert kicsit átalakítottam: isoX és isoY gyakorlatilag képernyő koordináták, míg az isoZ a képernyőtől való távolságot jelzi (példánkban minél nagyobb az isoZ, annál közelebb van hozzánk).

iso_4

Az új koordinátarendszerben számolva, a kirajzolandó objektumainkat sorba rendezzük Z,Y,X koordináták alapján növekvő sorrendben. Így a listában az első elem a leghátsó, legalsó elem lesz: tökéletes kiindulási pont. Egy ciklusra van csak szükségünk (a előbbi rendezés miatt), amely az elemek listáján végigmegy, és meghívja a renderelő funkciót az adott elem kirajzolásához. A renderelő funkció mielőtt kirajzolná az elemet, megnézi, van-e a fedésében olyan elem, ami esetleg még nincs kirajzolva, és ha igen, meghívja önmagát hogy az új elemet rajzolja ki előbb (és mivel rekurzív a függvény, az új elemnél is megnézi először, hogy van-e valami mögötte, és így tovább...).

Az izometrikus pálya renderelésének nehezén túl vagyok (legalábbis jól esik így hinni), most már csak az olyan apróságok kellenek, mint padlók, falak, ajtók renderelése. Majd azután jöhet a mozgás és animáció. De az nem ma lesz...

Grafika Remake 1.

2015 Március 21.

Most már biztos, hogy csak 2-szeres felbontás lesz! Sajnos kicsit nagy lesz 512x342-es felbontásnál, viszont a mozgások és az animációk időzítése miatt, irreális többletmunkát igényelne, ha 150%-os méretben is meg kéne csinálnom... Na ezen felbuzdulva, elkészítettem a falak remake-jét, lásd alább - A képre húzva az egeret, felsejlik az eredeti.

head over heels walls after
head over heels walls before

A törekvésemen, miszerint az eredeti játékot a lehető legkevesebb módosítással reprodukáljam, egyre több csorba esik: először megpróbáltam a grafikát úgy felhúzni, hogy szinte azonos maradjon az eredetivel, csak az íveket, ferde vonalakat akartam simítani. Az eredmény elég kiábrándító lett: a vonalak túl vastagok voltak, és hiányolta a szemem a felbontástól elvárható részleteket. Ráadásul bizonyos elemeket kénytelen voltam részletezni, az meg nagyon elütött a korábbi technikával készített részektől. Ezért inkább elkezdtem következetesen elvékonyítani egyes vonalakat, és hozzáadni némi pixel koszt.

A pixelezés közben folyamatosan váltogattam az eredeti és új változat között és próbáltam úgy dolgozni, hogy hunyorítva szinte észrevehetetlen legyen a változás. Legalább úgy... Sokszor próbálkoztam 1-1 pixel hozzáadásával, törlésével, hogy az adott részlet kivehető legyen, térben jól helyezkedjen el. Bármilyen hihetetlen, de egy-egy pixel nagyon nagy jelentőséggel tud bírni, ha rossz helyen van simán előfordulhat, hogy megtörik egy ív, elváltozik egy arc, vagy csak megváltozik egy hossz. Tisztelem nagyon Bernie Drummond -ot aki negyed ennyi pixelből kihozta a grafikát, ott az előbb említettek hatványozottan érvényesek.

Volt egy olyan álmom is, hogy a készülő játék az eredetihez hűen szintén a lehető legkisebb futtatható állomány legyen. Magyarul legyen kicsi a file mérete. Az eredeti, kitömörítve is csupán 48 KB. Na most ez sok dolog miatt nem fog sikerülni. Kapásból duplázzuk a felbontást, ez ugye rögtön négyszeres növekedés a grafikai adatoknál. Ha még az animációkat is bővíteni akarom, az megint plusz adat. Ehhez hozzájön még az is, hogy az eredeti játék kényszerből (mert hogy nem volt több rendelkezésre álló RAM, és ragaszkodtak az 1 file-hoz, amiért áldom őket) olyan megoldásokat alkalmazott, hogy a 4 irányba néző objektumoknak csak 2 változatát tárolták, és futás közben tükrözték a figurát ha épp kellett. Ezzel is spóroltak sok memóriát, és file méretet. Ugyanez igaz a fal elemekre is, ajtókra... Na most 8 bites processzornál, frappáns módszerrel, egy 256 elemből álló fordító táblát generáltak, így kiolvasva a grafika egy byte-ját és azt indexként használva ezen a táblán, megkapták a 8 képpont tükörképet. Ez egy 68k processzornál szívás, mert a byte műveletek sok időt elpazarolnak, a grafika 4x annyi byte-ból van. 16 bites, 64 KB-os fordítótáblát is kellemetlennek tartanám, szóval 2 lehetőség tűnik ésszerűnek: eltárolom a grafikák tükörképét is, vagy a lassú byte alapú tükrözést elvégzem a program indításánál. Ez utóbbi nem csökkenti a program memória igényét, de legalább a file méret kisebb lesz, végül is ezt akartam... A lényeg, hogy futás időben nem tükrözök, mert nagyon lassítaná a játékot!

Néhány szó arról, miért is nem jó a 150%-os grafika méretnövelés: az eredeti játék pixelméretei, és sebessége tökéletes összhangban van a képernyő felbontásával. A képernyőn egy egységnyi terület bármely pontja a szomszédos terület azonos pontjától 16 pixelre van vízszintesen, és 8 pixelre függőlegesen. A térbeli függőleges egység 12 pixel magas. Ebből következik, hogy egységnyi távolságot az eredeti játék 8 lépésben tesz(tehet) meg síkban, és 6 lépésben függőlegesen, mindezt 12,5 fps sebességgel (50 Hz-es képfrissítésnél, minden negyedik frame-en volt mozgás). Ha duplázzuk a felbontást, akkor minden tökéletesen reprodukálható, csak duplázzuk a pixel elmozdulásokat. Ha finomítani akarom a mozgásokat, akkor ha minden második frame-en animálok, akkor arra is lehetőség van, mivel a dupla felbontásnál a lépésközöket tudom felezni. Tehát az eredeti játékban, a mozgás 1 fázisa alatt 2 pixelt lepek X, és 1 pixelt Y irányban, dupla felbontásnál ez 4 és 2 pixel, tehát tudom felezni, hogy növelhessem a másodpercenkénti képkockák számát. 150%-kal nagyított grafikánál, az alapegységek 24, 12 és 18 pixelre változnak. ezeket a távolságokat 8 egyenlő lépésben nehéz megtenni. Ha csak az lenne a cél hogy azonos idő alatt tegyen meg 1 egységnyi távolságot az ami mozog, meg lehetne oldani az fps módosításával, de akkor az animációink sebessége változna. Szóval megoldható, de sok módosítás és extra munka árán. "Nekem ez a kutya nem kell, ez gúnyolódik"

HOH Render Engine

2015 Április 1.

Az elmúlt tíz nap elég eseménydúsra sikeredett: először is nagyjából megterveztem a renderelőt, és a teljes játéklogikát, majd mikor már az utolsó kérdéses részlet is megoldódott, elkezdtem a programozást. Első körben kicsit átírtam a korábbi izometrikus objektum renderelőt, megváltozott a koordináta rendszer kissé, hogy egyes játék funkciókat mint például az összekapcsolt szobák, könnyebb legyen megvalósítani. Utána jött a maradék: padló, falak, ajtók. Bár semmiségnek tűnhet a tetszőleges elemekből álló izometrikus tér rendereléséhez képest, mégis legalább 2x olyan hosszú lett a kódja. Ezt bemutatandó, itt van ez a kis játszószoba:

North
door
East
door
South
door
West
door
X
size
Y
size
Main Room

Amit itt látunk, az gyakorlatilag egy háttér (kivéve persze az ajtókat), amit a renderelő 32 pixel széles oszlopokból allít össze, 2 menetben. Az egyik az északi fal mentén halad (bal felső) és magába foglalja a függőlegesen alatta levő padló és szegély részletet, a másik meg a keleti fal mentén halad hasonló módon. A renderelésen csavar egy kicsikét az tény, hogy a renderelő max 128x128 pixel meretű négyzetekből rakja ki a képet. Ez a "viewPort" módszer (ahogyan az eredeti játék is dolgozik) azért fontos, mert később, amikor majd kis elemek mozognak és animálódnak a játékban, ezeket a képernyő lehető legkissebb részletének frissítésével kell megoldani. Böngészőben, javascript-tel simán ki lehetne rajzolni a teljes kepernyőt minden frissítéskor, de a cél még mindíg a Mac Classic, ahol nem dúskálunk az erőforrásokban. A JS alapú verzó, amit be is fogok fejezni tulajdonképpen egy prototípus, ami alapjan majd elkészítem a C + ASM változatot. Ez persze a javascript kódban is meglátszik majd, hiszen sok programrészlet a végleges célplatform jellemzői alapján készül. Ugyanakkor vannak olyan részletek is, tipikusan a változók tárolásánál, ahol javascript-ben nem akartam bajlódni az utolsó bitek kispórolásával, ezekkel elég lesz játszadozni majd a CodeWarrior-ban.

A következő fázis most a renderelő befejezése lesz, olyan funkciók hozzáadásával mint a szövegek, grafikak képernyőre másolása, ami után elkészűlhet a menü rendszer, es a játék alatt a pontszámok/életek/stb. kiírása (OSD mint On Screen Display).

Bár az előző kis demón annyira nem látszik, de a renderelő lényege kész és nagyon jól működik (tökéleteset nem merek írni, 1 kis apró hibát már a demóban is észrevettem, és még tesztelnem kell berendezett szobában való mozgást is) képes az összes lehetséges szoba alaprajzot megjeleníteni az összes grafikai témában, képes bármilyen legális berendezést megjeleníteni, és abban a mozgó elemeket bárhol megjeleníteni úgy, hogy mindig a megfelelő elemek vannak takarásban. Legális alatt azt értem, hogy minden elemnek úgy kell elhelyezkednie, hogy ne lógjon bele más tárgyakba. A jaték során, erről az ütközésvizsgálat fog gondoskodni.

Helyzetjelentés

2015 Április 20.

Az elmúlt közel három hétben sokminden történt, többek között, elkészült a grafika 90%-a, már csak az animációk vannak hátra. Miután siketült belefáradni a kb 10 nap folyamatos pixelrajzolgatásba, hozzáláttam a következő fázisként beharangozott renderelő befejezéséhez - az gyorsan elkészült, nem volt egy komplex feladat, a lényeg már megvolt elötte is. Ezek után, elkezdtem a játék menürendszerét lekódolni, és örömmel jelentem, hogy ez is elkészült tegnap éjszaka. A program 640x480 es 512x342-es felbontasokra optimalizálva készül, a menüt is úgy állítottam össze, hogy ezen a két felbontáson legyen használható. A színektől eltekintve, gyakorlatilag teljesen azonos lett az eredetivel: http://iparigrafika.hu/hoh_proto/menu/

Menu remake screenshot

Lényegében már "csak" a játékmenet van hátra, és ez elég jó érzés, hiszen az összes olyan sallangot letudtam, amit más esetben a végére szoktam hagyni. Szóval a játékmenet: Játékosok irányítása, váltás a játékosok között, a pályák, azok adatainak tárolása, az ellenfelek mozgatása. Ezek lesznek a következő hullám főszereplői. Itt jegyezném meg, hogy ha ezt mind befejeztem, akkor következik a lényeg, hiszen amiről eddig beszéltem, az a Javascript verziója a játéknak, ami magától még nem fog Mac 68k platformon futni, tehát ezek után kezdődik egy kemény menet: C-ben újra létrehozni az egészet.

Game test screenshot

Helyzetjelentés újra

2017 Szeptember 24.

Meglepően sok idő telt el az utolsó frissítés óta, de semmi nem állt meg, sőt:)

Korábban már kifejtettem, hogy mennyire fontosnak tartom a kis file méretet, ezert idén év elején, elkezdtem kicsit foglalkozni az adattömörítéssel. Szívesen írnék róla bővebben, de megpróbáltam és eléggé rosszul ment: http://iparigrafika.hu/retrocomputing/packer/ (halkan megjegyezném, hogy az aktuális verzió már jobban tömörít, nincs olyan állomány a tesztgrafikák között, ahol a zip nyerne). Mindenesetre végül sikerült egy olyan tömörítő algoritmust összehozni, ami nagyon hatékony az én kisméretű 1 bites pixelgrafikáim esetében (konkrétan a zip-re köröket ver) és a kitömörítés is jó gyors (188 byte méretű 68k assembly kód, ennek készítéséről is szeretnek írni). Szóval a file mérete nem a grafika miatt lesz túl nagy, ha túl nagy lesz.

Az év elején készült még egy kis valami a régi gépekre, ez nem igazan köthető semmihez, csak szórakozás (sajnos a youtube széttömörítette, majd megoldom vagz nem):

Elkezdtem továbbá foglalkozni az animációkkal is, kb. 50%-on állok velük.

A hangokon is merengtem sokat. Nagyjából 2 megoldást tudnék elképzelni: Az egyik sample-lökkel működne, és kellene hozza írnom egy mod player (Igaz van egy forrásom hozza, de CodeWarrior alatt nem sikerült eddig lefordítanom). A másik megoldás, egy SID emulator írása (csak a hangchip emulációja kellene, mert nem az eredeti c64 kód játszaná le a zeneket, hangeffekteket). Az utóbbi megoldás azért szimpatikusabb, mert sokkal kisebb méretben meg lehetne valósítani mint hangmintákkal, és amúgy is nagyon szeretem az eredeti játék hangjait, de jó lenne azok kíséretében játszani. Az utóbbi hátránya, hogy egyelőre közelébe se kerültem annak, hogyan tudnám a SID chip-et emulálni. De mint eddig minden, ez is meg fog oldódni valahogyan, én optimista vagyok!

UPD: Az eltelt időben sok más vonalon is haladt a dolog. A kód visszafejtesben is előrébb járok, minden grafikai részlet meglett már rég (mint a pislogás pl. és a pálya szelei), továbbá a pálya adatok is megvannak, bár csak részlegesen tudtam eddig visszafejteni a kódot, ami az adatokból pályát hoz létre...

A hang és SID dolgok

2017 December 30.

Még mindig nem döntöttem el, hogyan valósítsam meg a hangokat a végleges játékban, viszont azért haladtam az alapokkal. Egy kisebb keresgélés után rátaláltam Hermit csodalatos jsSID player-ere, ahogy a neve is utal rá javascriptben van írva, böngészőben fut. Egyrészt már ez is nagyon sokat lendített előre a lehetőségek feltérképezésében, de elkezdtünk levelezni, és nagyon sok technikai segítséget kaptam Hermit-től a SID emulációval kapcsolatban. Ha már van emulátor a SID-hez, az nagyon jó, ezt majd később portolnám 68k assembly-be. A következő lépés az volt, hogy az eredeti c64 zenét, zenelejátszó rutint visszafejtettem - meglepően frappánsan ment, kb. 2-3 nap alatt meg is volt. Ebből készítettem egy javascript lejátszó rutint (a zenei adatokban eredetileg sok mutató volt közvetlen memóriacímekre, ezert azt is szét kellett boncolni, a mutatókat átírni, és hobbiból kicsit átstrukturálni): http://iparigrafika.hu/emu/hoh_sconv/hoh_jsplayer.html.

Van zenelejátszó, van SID emulator. Most kéne elkészítenem ezek portjait is a célplatformra (68k Mac), de addig is, kicsit úgy érzem necces 68030@16 MHz-en, úgy, hogy emellett még a játéklogikát, és grafika megjelenitését is, azaz a teljes játékot futtatni kell. Tehát megállás nélkül próbálok alternatívakat találni, és ilyenkor a leglehetetlenebb dolgok is eszembe jutnak, például milyen lenne ha a SID-et valahogy a régi Mac-hez tudnám kapcsolni. Mivel mostanában töltöttem pár órát Arduinozassal, kézenfekvő megoldásnak tűnt, ezert elkezdtem kutakodni SIDshield-ek ügyében, találtam kettőt (RealSIDShield, SID-Shield for Arduino), ami elég jónak tűnt, de azok csak lelkes próbálkozások voltak, nem lehetett megrendelni őket. Jó megoldás volt mindkettő, de valamiért nekem nem felelt meg. Zavart, hogy egy plusz IC-vel volt megoldva a SID és az Arduino közötti kommunikáció. Én még az Arduino-t is kihagynám a képletből legszívesebben, de annak van értelme, ha többféle számítógéppel terveznem összekötni a tákolmányomat. Tehát valamilyen gép -> soros csatlakozás -> Arduino -> SID. Magyarul az Arduino UNO-nak kellene (mert éppen ilyenem van) közvetlenül meghajtania a SID-et. Végül erre is sikerült találnom egy jó példát: http://www.deblauweschicht.nl/tinkering/mos6581_1.html. A kapcsolásban minimális módosítás után, és teljesen új kód használatával született meg az alábbi csoda:

Kicsikét bővebben a működéséről

a SID-nek a működéshez a következőkre van szüksége: +5V, +9V (SID 8580) vagy +12V (SID 6581). Kell neki továbbá egy 1 MHz-es jel (phi2), ami a belső működéséhez elengedhetetlen. Kell neki egy Reset (RES) jel. Kell 5 cím és 8 adatvonal. Jelezni kell neki továbbá, hogy írni vagy olvasni akarjuk (R/W), és azt is, hogy mikor állnak készen a bemeneti adatok (CS) (cím és adat íráshoz v. olvasáshoz).

SID original schematic

Az Arduino-hoz a SID-et az alábbi (pin-láb) kiosztással csatlakoztattam:

Arduino  SID        Arduino  SID
5V_______25 (Vcc)   D2_______15 (D0)
GND______14 (GND)   D3_______16 (D1)
Vin______28 (Vdd)   D4_______17 (D2)
                    D5_______18 (D3)
A0_______ 9 (A0)    D6_______19 (D4)
A1_______10 (A1)    D7_______20 (D5)
A2_______11 (A2)    D8_______21 (D6)
A3_______12 (A3)    D9 pwm___ 6 (Phi2)
A4_______13 (A4)    D10______22 (D7)
A5_______ 5 (RES)   D11______ 8 (CS)
                    D12______ 7 (RW)

Az Arduino Vin kimenetét használom 9V-ként, ezért fontos, hogy ne USB-ről, hanem a tápcsatakozóra kötött 9V-os tápról kapjuk az áramot. Ezt a feszültséget nem szabályozza az Arduino, úgyhogy mindenképpen merjük ki mielőtt a SID-hez érhetne!

Mivel én SID 8580-nal "dolgozom", ezért a CAP1 és CAP2 -re kötött kondikat 22000pF (22nF)-ra cseréltem, és az 1K-s ellenállást nem használom a hangkimenetnél, az csak a 6581-nek kell. Az egész hangkimenet más mint ahogyan a fenti rajzon látszik, azt az eredeti C64 kapcsolási rajz alapján készítettem, lásd alább. A tápbemenetekhez közel elhelyeztem 1-1 .1uF-os kerámia kondit, a nem használt analóg bemenetekhez meg 1000pF-osakat.

SID Audio OUT original schematic

A cím (A0-A4), az adatbusz (D0-D7), a RES, a R/W és a CS szerepét az Arduino egy-egy digitális kimenete látja el. Az 1 MHz-es jelet is könnyen előállíthatjuk az egyik PWM digitális kimeneten. Tehát nincs más dolgunk, mint hogy a megfelelő időben a megfelelő digitális kimeneteket állítgassuk.

A kódról röviden: nem kell külső lib, egyszerű mint a faék, és elég gyors.

#define pSID_RES  A5    // Reset
#define pSID_PHI2 9     // 1 MHz
#define pSID_CS   11    // Chip Select
#define pSID_RW   12    // Read/Write

#define pSID_D0   2     // Data
#define pSID_D1   3
#define pSID_D2   4
#define pSID_D3   5
#define pSID_D4   6
#define pSID_D5   7
#define pSID_D6   8
#define pSID_D7   10

#define pSID_A0   A0    // Address
#define pSID_A1   A1
#define pSID_A2   A2
#define pSID_A3   A3
#define pSID_A4   A4

Miután a rend kedvéért definiáltuk a használt pin-eket, beállítjuk mindet kimenetre, hiszen olvasni nem kívánunk (legalábbis én).

pinMode(pSID_RES, OUTPUT);
pinMode(pSID_PHI2, OUTPUT);
pinMode(pSID_CS, OUTPUT);
pinMode(pSID_RW, OUTPUT);

pinMode(pSID_D0, OUTPUT);
pinMode(pSID_D1, OUTPUT);
pinMode(pSID_D2, OUTPUT);
pinMode(pSID_D3, OUTPUT);
pinMode(pSID_D4, OUTPUT);
pinMode(pSID_D5, OUTPUT);
pinMode(pSID_D6, OUTPUT);
pinMode(pSID_D7, OUTPUT);

pinMode(pSID_A0, OUTPUT);
pinMode(pSID_A1, OUTPUT);
pinMode(pSID_A2, OUTPUT);
pinMode(pSID_A3, OUTPUT);
pinMode(pSID_A4, OUTPUT);

Az 1 MHz-es jel előállítása Arduino UNO-n, ami 16 MHz-en ketyeg:

TCNT0  =  0;                        // Reset timer
TCCR1A =  _BV(COM1A0);              // Set PORTB1 to toggle on OC1B compare match
TCCR1B =  _BV(WGM12);               // CTC mode, OCR1A as TOP
OCR1B  =  0b0000000;
OCR1A  =  0b0000111;                // Set counter TOP to 8-1 = 7. For a 16MHz. clock this = 1 Mhz.
TCCR1B |= _BV(CS10);                // set prescale to div 1 and start the timer

Az inicializálás végeként beállítjuk a CS és RW jeleket és reset jelet küldünk a SID-nek:

digitalWrite(pSID_CS, HIGH);        // Set SID Chip Select HIGH (idle)
digitalWrite(pSID_RW, LOW);         // Set SID R/W LOW (Write)
digitalWrite(pSID_RES, LOW);        // Set SID RESET LOW (reset)
delayMicroseconds( 200 );           // Wait a bit...
digitalWrite(pSID_RES, HIGH);       // Set SID RESET HIGH

Most már nincsen más dolgunk, csak kódból írni a SID regisztereket, és ugyanúgy fog viselkedni mint C64 alatt. Ezt megkönnyítendő, itt van pár segédfunkció:

void setData( byte data ) {
  digitalWrite(pSID_D0, data&0x01 );
  digitalWrite(pSID_D1, data&0x02 );
  digitalWrite(pSID_D2, data&0x04 );
  digitalWrite(pSID_D3, data&0x08 );
  digitalWrite(pSID_D4, data&0x10 );
  digitalWrite(pSID_D5, data&0x20 );
  digitalWrite(pSID_D6, data&0x40 );
  digitalWrite(pSID_D7, data&0x80 );
}
void setAddr( byte addr ) {
  digitalWrite(pSID_A0, addr&0x01 );
  digitalWrite(pSID_A1, addr&0x02 );
  digitalWrite(pSID_A2, addr&0x04 );
  digitalWrite(pSID_A3, addr&0x08 );
  digitalWrite(pSID_A4, addr&0x10 );
}
void writeSID( byte reg, byte val ) {
  setAddr( reg );                   // set addr pins
  setData( val );                   // set data pins
  digitalWrite(pSID_CS, LOW);       // data and addr ready, set CS LOW: data/addr read by SID
  digitalWrite(pSID_CS, HIGH);      // and set CS HIGH again...
}
void resetSID() {
  for( byte i=0; i<25; i++ ) writeSID( i, 0 );
}

A teljes Arduino sketch a Head over Heels zenéjével letölthető innen. Megjegyzéskent szerepeljen itt annyi, hogy EEPROM-ban tárolok egy változót, amit minden ujraindítás után növelek eggyel: ez a sorszáma a subtune-nak amit lejátszik. Összesen 14 van. Volt már olyan változat is ahol 3 ledet vezéreltem a 3 hangcsatorna GATE bitjének állapota alapjan...

Hozzátenném viszont, hogy a SID reset-elése nem 100%-os, nem minden esetben reset-elődik rendesen, aminek eredményeképpen az ADSR "inaktív", az envelope generator nem működik, és minden hangszín a gate bit bekapcsolásával egyidőben teljes hangerővel megszólal. Ha ezt kiderítettem miért csianlja, frissítem a cikket.

Mini Demo Mac Classic-ra

2020 Szeptember 18.

Olyan regen irtam, hogy mar szinte szegyellem folytatni, de most akkor ezen tul is lendulnek: szoval, tavaly a nagy karacsonyi szunetben vegre megvalosult egy apro almom, hogy egy anno javascript-ben megirt hullamzo scroller effect-et atportoltam Mac-re, 100% Assembly kod az egesz, es a magam szintjen eleg komplex kis 16 KB-os program lett.

A kiindulasi pont, a Javascript-ben megirt effekttel az 1. problema az volt, hogy a hullamgorbet rengeteg sin/cos szamitassal, szorzassal, osztassal vegezte a kod, es ebbol elegge sokra volt szukseg, es minden frame-ben, a rajzolas elott ezt ki kellett kalkulallni. A masodik problema pedig, hogy a karaktereket runtime forgatta (szamolta ki a pixeleket), ami elforgatott karakterenkent 4096x(sin/cos/osztas/szorzas/gyokvonas/stb). Ezek egyike sem portolhato egy 16 MHz-es gepre, ha tartani akarom a vsync-et.

Felmerult, hogy szepen kiszamolom elore az elforgatott karaktereket, es ugy... De a 720 KB-nyi adatmennyiseg, egy ilyen scrollerhez, egy ilyen regi gepen nevetsegesnek tunik nekem, aki amugy is mindig a leheto legkisebb file-meretre torekszik. Maradt az a megoldas, hogy M68k assemblyben forgatok. Ehhez ki kellett talalnom, egy kisebb, elore kiszamolt ertekeket tartalmazo tablazatokkal dolgozo eljarast. Az eredeti elforgato rutin, vegigfut a (64x64px) 4096 pixelen, kiszamolja a kozepponttol valo tavolsagot (pixelben), meghatarozza a ramutato vektornak a szoget, ehhez hozzaadja az elforgatas merteket, az uj szogbol, es a tavolsagbol visszaszamolja a forras pont helyet: amilyen pixelt ott talal az elforgatatlan karakteren, az lesz az adodd pixelen (ahonnan indultunk). Ez ugy lett leegyszerusitve, hogy 3 elore kiszamolt tablazatot hasznalok, az elso a pontok tavolsaga a kozeppontol, a masikban a szoguk, a harmadik, a szog alapjan, beszorozva a tavolsaggal megadja az elforgatott pixelpoziciot. Az osszes tabla, es grafikai adat amit hasznal a program 10 KB-ra tomoritve van tarolva (MPackerX ami a kis javascriptes prototipusbol app-pa fejlodte ki magat, lett commandline tool Mac OS, es Linux ala, valamint egy swift-re portolt Mac OS verzio, GUI-val), a maradek 6 KB az assembly kod, amiben maradt par extra, ha jol emlekszem (pl egy sajat PRINTF implementacio, igaz az kb 200 byte, egy debug font)

; RSRC     packed/unpacked    filename
; $80         476     2688    16x14_terminalfont_LR2.pkx
; $81         164     1116    apple_logo_96x93_LR2.pkx
; $82        1700    16896    64x48_funnyscrollfont.pkx
; $83        2560     8192    rotTA_NR2.pkx
; $84         564     2048    rotTRS_NR8.pkx
; $85        3174     8192    rotTR_.pkx
; $86         322     1024    distsc.pkx
; $87        1714     2434    sin_sina_mul_dirs_NR2.pkx
;-------------------------
;           10674    42590
		

Masik erdekesseg a rendereles (most atugrottam a hullamgorbe szamolasat, pedig az is erdekes), merthogy igaz csak 13 karaktert rajzolunk ki, de az a kepernyo tetejetol az aljaig barhol lehet. Es mivel nem csak kirajzolni kell, hanem torolni is (mivel EOR-ral rajzolunk, ua a rutin csinalja) ezert 26 karakter inkabb. Szoval Y poziciojuk alapjan rendezett sorban vegigfutunk a karaktereken, es rajzolunk (nem is volt erdekes igy utolag...).