First post, by theelf
- Rank
- Oldbie
Hi, first sorry, i write this in spanish (is at the end of post) i ask translator to do the job, but maye is not accurate or use the right words, but is too long text to write myself in english
BENEATH A STEEL SKY FOR 286 - 3 YEARS OF FUN (AND SUFFERING)
================================================================
The Saturday of my bad decision (October 2023)
It all started on a Saturday in October 2023. It was raining cats and dogs outside. I had just come back from the supermarket to buy grated cheese because my wife was cooking pasta. I walked into the kitchen, dropped the cheese, asked if she needed anything else, and she said "NO."
Perfect, clear path, I escaped like a rat.
I was pretty bored and felt like playing something. But... I couldn't just pick something simple... no... and I remembered Beneath a Steel Sky. Great graphic adventure, amazing graphics, shitty music, which I never actually finished.
But I glanced out of the corner of my eye at my beautiful Harris 286-25 and thought: "Pfffff, what a shame this requires a 386... all it would take is 10 minutes of my life to pull the 486 off the shelf...".
But then an idea hit me: Who needs a 386+ when you have a burning desire to overcomplicate your life?
Honestly, 3 years later, I think I should have just played a match of Sango Fighters or finished Sherlock Holmes which I had left halfway through...
First steps: Optimism and the first punches from the RAM (October 2023 - October 2024)
• Progress estimate: An hour or so on weekends. A lot of staring at code on my modern PC and saying "Ugh, what did I get myself into."
• December 2023: I downloaded the original ASM source code from Revolution Software (the one floating around GitHub/abandonware sites). It was a total nightmare of 32-bit register assembly (eax, ebx, esi, edi, start32code). Porting that by hand directly for a 286 was impossible/difficult/stupid?I changed my strategy: I used ScummVM as a roadmap to understand the internal logic of the engine and write my own code from scratch, or steal whatever I could from the other two! The original ASM was left solely as the supreme judge to clear up specific doubts when ScummVM did weird things, because it really wasn't worth the hassle of porting it directly.
• Between December '23 and January 2024: The original game runs in protected mode with its DOS extender. I checked it, and it manages the screen in a way that is completely useless to me on a 286. And ScummVM? Don't even get me started—it draws everything through the graphical backend of the modern OS it's running on.So, nothing for it, I had to write directly to the mode 13h video memory (320x200, 256 colors), which is the simplest and fastest mode a real VGA card gives you without any BS. It's simply writing to the game's framebuffer, pixel by pixel, directly to where the video card expects it.To at least save my eyesight, I implemented double buffering to reduce screen tearing and graphic flickering during redraws. However, it consumed way too much RAM, so I built a viable alternative: I programmed a single framebuffer in RAM that gets dumped to the VGA using _fmemcpy, synchronized with the vertical retrace (video_wait_vsync) to avoid tearing.
• January 2024: First major roadblock. The game uses RNC (Rob Northen Compression), the typical format used in Sega Mega Drive and Amiga games. I read through all the documentation on various websites, including some interesting Sega Retro documents, and downloaded a generic decompressor from the internet (I think it was dernc).It seemed to work on simple files, but with BASS's heavy files, it just spit out a garbled mess of broken pixels on the screen. Of course, the format has its complexities (Huffman trees), and the generic tool didn't understand the exact bit alignment this engine uses. It cost me many hours of sleep before I decided to throw the generic tool in the trash and port the exact decompressor from ScummVM (rnc_deco.cpp) bit by bit into my rnc_deco.c.
• January 2024 (Continued): After several tests and banging my head against the wall, I concluded that a 286 cannot decompress on the fly while you play. It's a turtle and just doesn't have the horsepower.Solution: I put together a Python script (predecompress_v2.py) on my modern PC to extract the data from the original factory .dnr/.dsk files and leave them pre-decompressed on the disk. While fighting with this, I discovered that the format uses 8-byte entries (not 6, as an old script claimed) and inherits bit 22 from the original Disk.asm. If you don't respect that header bit, the game applies width/height values to things that shouldn't have them, completely breaking the intro video. Once the script was fixed, it worked like a charm.
• March 2024: I wanted to use ScummVM's sky.cpt format, which gives you all the "compacts" (game objects, palettes, tables, Foster's state) already resolved in a convenient binary, instead of parsing the 219 original .asm/.inc files by hand for each room. But I hit the 16-bit wall: the file for room 5 (SKY5.CPT) weighed 84KB, and of course, the absolute limit is 64KB.Since that limit doesn't exist in the original game or ScummVM, I came up with my own workaround: I split the file in two (SKY5.CPT with the first 476 compacts, and SKY9.CPT with the rest reindexed from 0, using room number 9 which was free). I programmed compact.c to load them in tandem and translate the IDs on the fly. Not very elegant—the ideal solution would have been to build a network of compacts, rooms, and states—but it worked.
Determined to finish it: Opcodes and the Pathfinding labyrinth (April 2024 - May 2025)
• April-May 2024: I dove into porting the script interpreter (the opcode dispatcher for the Virtual Theatre engine). There were 115 native opcodes. In the original ASM, it was a direct jump table in memory.Instead of going crazy putting together a disgusting cascade of "ifs", I built it elegantly in logic.c and mcodes.c: a table of 115 function pointers in C, indexed directly by the opcode number. If the script requests opcode 40, it jumps instantly to the corresponding function.
• Mid-2024: It was finally starting to look like a game, so I decided to implement mouse support. I programmed the driver directly via INT 33h (the standard DOS API used by the original game in mouse.c).Compatibility fix sorted out. I also rewrote fnNoHuman (an engine mcode); at first, I misunderstood it and turned off the character's logic bit, breaking everything. I had to look at ScummVM's mouse.cpp source code to realize it only handles the mouse cursor, not the character's underlying process. I rewrote it properly in mcodes.c. I hadn't noticed it before since I didn't have mouse support working yet.
• Second half of 2024: I tackled Pathfinding (autoroute.c), the actual path-searching algorithm the game uses every time you click to walk (you can't just use a straight line or you'll crash into everything). I ported the 281 lines directly from ScummVM, but sweating bullets over the pointer arithmetic with negative offsets that the original game had.On top of that, the collision map (the wall grid) was designed for 32-bit. If I did a direct pointer cast in Watcom's 16-bit model, it threw garbage at me due to sign-extension and alignment issues. I had to rewrite the reading routine in grid.c so it reads byte by byte and rebuilds the data manually using bit shifts.
• Late 2024 / Early 2025: I stumbled upon a bug that made my life a living hell for about two weeks. In the route calculation mode (L_AR), Foster would start walking, and when he finished, he would get stuck in an infinite loop requesting the exact same route over and over. The game would freeze.I went crazy looking for it in autoroute until I compared it against the mode flow in the original Logic.asm and realized my logic_engine_step was poorly ported. I wiped it out and rewrote it as a pure switch statement based on ScummVM's _logicTable. The proper flow returned, and the loop vanished. I also ported fnGetTo, fnAr, and fnArAnimate, which I previously had as empty stubs, and turned out to be the ones triggering the tracking mode with animation.
The intro, RAM management, and audio (Mid-2025 - September 2026)
• Mid-2025: The original game intro is a massive pain in the ass for real mode (especially the logo sequence with the zoom; it decompresses a file over 640KB!!!). Without resorting to XMS/EMS, the absolute limit is 640KB of RAM, and with DOS loaded, drivers, etc., you're lucky if you have 580-590KB of conventional memory left. Obviously, the original game ran with a 32-bit DOS Extender and had RAM "to spare." Here, it just blew up.I came up with my own workaround in rnc_deco.c: rnc_unpack_streaming. It's a variant of the decompressor that, instead of dumping everything into RAM, uses a circular buffer of just 64KB and streams the output into a temporary file on the hard drive.Still, the title zoom never ran smoothly because it was just too many full frames; I would have had to rewrite everything just with that logo in mind, so I threw the original sequence in the trash and drew a smaller alternative logo on the screen instead. Problem solved, and you don't miss the intro. Let's be honest, the original cheesy 90s-style 3D logo looked awful anyway.
• Second half of 2025: All-out war against RAM. The character sprite cache kept growing room after room until it completely ran out of memory. The original game didn't face this issue because it had RAM to burn.In mcodes.c, I programmed a custom sprite cache with a fixed budget in KB and a flushing algorithm: if it exceeds the limit, it evicts the oldest sprite from RAM. This introduced a bug that I didn't notice at first, because the sprite cache and the "last used sprite" pointer sometimes pointed to the exact same block; I fixed it by adding a flag to know if the pointer was borrowed or owned.I also discovered that the allocation order mattered: if the cache grew first, it starved the room's background buffer of space. I solved it by forcing the background to load first in mcodes_init_screen0 before caching any sprites.
• Early-to-mid 2026: By now I had something playable that didn't crash, but it was completely mute. Time to get some audio working. Here, I pretty much ignored the original code—and obviously ScummVM, which is designed for modern systems—and wrote everything from scratch, taking inspiration here and there from the original source files so I wouldn't reinvent the wheel.First, I implemented OPL2 (AdLib). Once that was functional, I wrote an MT-32 driver, and that's when I discovered something awesome: the game outputs sound FX if you have a CM-32L! So I started investigating how it worked and found out it goes through channel 10. You never stop learning in this world.I added support for SFX through the Sound Blaster, and by pure chance, I found out about the "Enhanced Music Released by James Woodcock." I couldn't resist: I burned the OGG files to a CD-ROM and implemented CD-Audio support. Magnificent.
Today (October 2026)
Three years later, squeezing in an hour whenever I could between work, family, and life, the game runs on my 286. Now all that's left to do is actually play it!
So here comes the question: who wants to beta-test this on their 286?I dont ask play all game, but at least play a little in different setups
I dont want to public share still until test more
thanks
Original en Español
BENEATH A STEEL SKY PARA 286 - 3 AÑOS DE DIVERSION (Y SUFRIMIENTO) ============================================================= […]
BENEATH A STEEL SKY PARA 286 - 3 AÑOS DE DIVERSION (Y SUFRIMIENTO)
================================================================El Sabado de mi mala decicion (octubre 2023)
------------------------------------
Todo empezo un sabado de octubre de 2023. Llovia como loco afuera. Yo venia del super de comprar queso rallado porque mi señora estaba cocinando unos fideos. Entre a la cocina, deje el queso, pregunte si hacia falta algo mas y me dijo "NO". Listo, via libre, escape como rataEstaba bastante al pedo y me dio jugar a algo.Pero.. no podia elegir algo simple...no.. y me acorde del Beneath a Steel Sky, buena aventura grafica, grandes graficos, musica de mierda, que nunca finalize, pero mire de reojo mi hermoso Harris 286-25 y dije: "pfffff, que lastima que esto pide un 386...... simplemente tenia que perder 10 minutos de mi vida en sacar el 486 de la estanteria....". Pero se me ocurrio una idea. ¿Quien necesita un 386+ si tenes ganas de complicarte la vida?
Sinceramente 3 años despues pienso tendria que haberme echado una partida al Sango Fihters o al Sherlock Holmes que lo tenia a medio camino...
Primeros pasos El optimismo y las primeras piñas con la RAM (octubre 2023 - octubre 2024)
-------------------------------------------------------------------------------
* Estimado de avance: Una horita los fines de semana. Mucho mirar codigo en la PC moderna y decir "uuh, donde me meti".- diciembre 2023: Me baje el codigo original en ASM de Revolution Software (el que anda por github/web abandonwares). Una quilombo asm de registros de 32 bits (eax, ebx, esi, edi, start32code). Imposible/dificil/estupido¿? portar eso a mano directo para un 286. Cambie de estrategia: ScummVM como mapa para entender la logica interna del motor y hacer mi propio codigo de 0 o robar lo que pudiera de los otros dos!! El ASM original quedo solo como juez supremo para sacarme dudas puntuales cuando ScummVM hacia cosas raras, porque realmente no valia la pena meterse a portar
- Entre diciembre 23 y enero 2024: El original, corriendo en modo protegido con su DOS extender, revise y maneja la pantalla de una forma que a mi no me sirve para nada en un 286 y ScummVM ni hablar, el dibuja todo a traves del backend grafico del sistema operativo moderno donde corre. Asi que nada, tuve que escribir directo a la memoria de video modo 13h, 320x200, 256 colores, que es el modo mas simple y rapido que te da una VGA real sin historias, es simplemente escribir el framebuffer del juego, pixel por pixel, directo a donde la placa lo espera.Lo que si implemente para al menos no quedar sin ojos, es doble buffer, para reducir tearing y el parpadeo de graficos al redibujar, pero me consumia mucha ram, asi que hice una alternativa valida, programe un solo framebuffer en RAM que se vuelca con _fmemcpy a la vga, sincronizado con el retrace vertical (video_wait_vsync) para evitar el tearing
- enero 2024: Primer problema gordo. El juego usa compresion RNC (Rob Northen Compression), la misma tipica de los juegos de Sega Mega Drive y Amiga. Me lei toda la documentacion de diferentes web, como unos documentos interesantes Sega Retro, me baje algun descompresor generico de internet (creo q fue dernc). Parecia andar en archivos simples, pero con los archivos pesados del BASS me hacia una maraña de pixeles roto en la pantalla. Claro, el formato tiene su complejidad (arboles Huffman) y el generico no entendia la realineacion de bits exacta que hace este motor. Me llevo horas de sueño decidir mandar a la mierda el generico y portar bit a bit el descompresor exacto de ScummVM (rnc_deco.cpp) a mi rnc_deco.c.
- enero 2024: Despues de varias pruebas y golpes contra la pared, llege a la conclusion 286 no puede descomprimir en caliente mientras jugas, es una tortuga y no le da el cuero. Solucion: arme un script en Python (predecompress_v2.py) en mi PC moderna para extraer los datos de los .dnr/.dsk originales de fabrica y dejarlos ya predescomprimidos en el disco. Ahi renegando descubri que el formato usa entradas de 8 bytes (no 6 como decia un script viejo) y hereda el bit22 del Disk.asm original. Si no respetas ese bit de la cabecera, el juego le mete ancho/alto a cosas que no lo llevan y te rompe toda la intro de video. Con el script corregido anduvo joya.
- marzo 2024: Quise usar el formato sky.cpt de ScummVM, que te da todos los "compacts" (objetos del juego, paletas, tablas, el estado de Foster) ya resueltos en un binario comodo, en vez de ponerme a parsear a mano los 219 archivos .asm/.inc originales por sala. Pero me tope con la pared de los 16 bits: el archivo de la sala 5 (SKY5.CPT) pesaba 84KB y claro el limite absoluto son 64k. Como en el original o en ScummVM ese limite no existe, me invente una solucion mia: parti el archivo en dos (SKY5.CPT con los primeros 476 compacts y SKY9.CPT con el resto reindexados desde 0, usando el numero de sala 9 que estaba libre). Programe el compact.c para que los cargue en cascada y traduzca los IDs en el aire. No muy elegante, lo ideal hubiera sido hacer una red de compacts y salas/situaciones, pero funciono.
Ya decidido a acabar: Opcodes, y el laberinto del Pathfinding (abril 2024 - mayo 2025)
--------------------------------------------------------------------------------------------- abril-mayo 2024: Me meti a portar el interprete de scripts (el despachador de opcodes del motor Virtual Theatre). Eran 115 opcodes nativos. En el original en ASM era una tabla de saltos directos a memoria. En vez de volverme loco metiendo una cascada asquerosa de "ifs", lo arme elegante en logic.c and mcodes.c: una tabla de 115 punteros a funcion en C indexada directo por el numero de opcode. Si el script pide el opcode 40, salta al tiro a la funcion correspondiente.
- mediados de 2024: Por fin ya empesaba a parecer un juego, asi que decidi implementar el mouse. Directo prorame l driver via INT 33h (la API estandar de DOS que usa el original en mouse.c)
Solucionado el fix de compatibilidad. Tambien reescribi fnNoHuman (un mcode del motor); al principio entendi mal y apague el bit de logica del personaje, rompiendo todo. Tuve que ir al fuente de mouse.cpp de ScummVM para ver que solo maneja el cursor del mouse, no el proceso del muñeco. Lo rehice bien en mcodes.c. No me habia dado cuenta al no tener mouse
- segunda mitad de 2024: Me meti con el Pathfinding (autoroute.c), el algoritmo real de busqueda de caminos que usa el juego cada vez que haces click para caminar (no podes meter linea recta porque te chocas todo). Porté las 281 lineas directo de ScummVM, pero sudando con la aritmetica de punteros con offsets negativos que tenia el juego original. Ademas, el mapa de colisiones (el grid de paredes) venia pensado para 32 bits. Si hacia un casteo directo a puntero en el modelo de 16 bits de Watcom me tiraba basura por temas de alineacion de signo. Tuve que reescribir la lectura en grid.c para que lea byte por byte y reconstruya los datos a mano usando desplazamientos de bits (shifts).
- fines de 2024 / principios de 2025: Me tropeze con un bug que me llevo por la calle de la amargura como dos semanas. En el modo de calculo de ruta (L_AR), Foster se ponia a caminar y cuando terminaba se clavaba en un bucle infinito pidiendo la misma ruta una y otra vez. Se tildaba el juego. Me volvi loco buscando en autoroute hasta que compare contra el flujo de modos del Logic.asm original y vi que mi logic_engine_step estaba mal portado. Lo borre y lo reescribiri como un switch puro basado en la _logicTable de ScummVM. El flujo real volvio y el bucle desaparecio. Tambien portie fnGetTo, fnAr y fnArAnimate que los tenia como stubs vacios y eran los que activaban el modo de seguimiento con animacion.
La intro, gestion ram y el audio (mediados de 2025 - septiembre 2026)
-------------------------------------------------------------------------------------------------
Recta final. Arreglar una cosa rompía tres. Pero ya lo tenia encaminado, habia q terminar el hijo de puta- mediados de 2025: La intro original del juego es un dolor de huevos para modo real (en especial la secuencia del logo con zoom, descomprime un archivo de mas de 640KB!!!!). Sin ir a xms/ems el limite absoluto es de 640KB de RAM, y con el DOS cargado, drivers, etc con suerte te quedan libres 580-590KB de memoria convencional. Obvio que el original corria con DOS Extender en 32 bits y le "sobraba" ram. Aca explotaba. Invente una solucion propia en rnc_deco.c: el rnc_unpack_streaming. Es una variante del descompresor que en vez de tirar todo a la RAM usa un buffer circular de solo 64KB y va escupiendo el resultado a un archivo temporal en el disco duro.
Igual, el zoom del titulo nunca anduvo fino porque eran demasiados frames completos, tendria que haber reescrito todo solo pensando en este logo, asi que mande la secuencia original a la mierda y dibuje un logo alternativo mas chico en pantalla. Problema resuelto y no se pierde la intro. Seamos sinceros, el logo original estilo 3D cutre de los 90 era un horror de todas maneras
- segunda mitad de 2025: Batalla total por la RAM. La cache de sprites de los personajes crecia sin parar sala tras sala hasta agotar la memoria. El original no tenia este problema porque le sobraba RAM. Programe en mcodes.c una cache de sprites propia con presupuesto fijo en KB y algoritmo de vaciado: si se pasa del limite, quita de ram el sprite mas viejo. Eso me metio un bug que no me di cuenta al principio, porque la cache de sprites y el puntero del "ultimo sprite usado" a veces apuntaban al mismo bloque; lo arregle metiendo una bandera para saber si el puntero era prestado o propio. Tambien descubri que el orden de reserva importaba: si la cache crecia primero, dejaba sin espacio al buffer del fondo de pantalla de la sala. Lo solucione forzando a cargar el fondo primero en mcodes_init_screen0 antes de cachear cualquier sprite.
- principios-mediados de 2026: Ya tenia algo jugable y que no explotaba, pero mudo. Tocaba tiempo de escuchar algo, aqui practicamente pase del codigo original, y ovbiamente de scummvm, que esta pensado para sistemas modernos, y fui escribiendo todo de 0, inspirandome aqui y alli en los codigos originales para tampoco reinventar la rueda
Primero implemente OPL2 (Adlib), una vez funcional, hice un driver de MT32, y ahi descubri algo grandioso, el juego saca sonidos FX si tienes un CM32L, asi que me puse a investigar como funcionaba, encontre que es a travez del canal 10, nunca se deja de aprender en este mundo
Agrege soporte para FX a traves de la sound Blaster, y de casualidad di que existia un "Enhanced Music Released by JamesWoodcock" asi que no pude resistirme, grabe los OGG a un CDrom e implemente AudioCD, marabilloso.
Hoy (octubre 2026)
-----------------
Tres años despues, metiendo una horita cuando se podia entre el trabajo, la familia y la vida, el juego corre en mi 286. Ahora lo que tengo que hacer es jugarlo!Asi que viene la pregunta, quien quiere hacer de beta tester con su 286?