I’ve been pondering scrolling for a few days, and whether I could figure out how to write a huge smooth-scrolling playfield or whether I’d just flip from one screen to the next. This evening it occurred to me that surely at some point the Commander Keen source code must’ve been released and I could just see how they handled scrolling. And indeed Keen Dreams is GPLed! https://github.com/keendreams/keen
Hmmmm this is... a little overwhelming. Lots of extra stuff that makes me wonder if I’m missing something important. Gotta keep in mind that it’s an entire game, after many rounds of optimization, instead of a week’s worth of occasional hacking, and my slower, dumber code can still lead to an outcome I’m happy with.
Keen keeps track of which 16x16 tiles are dirty via a dirt-simple byte array where they mark each tile position 1 or 0 - because only 21x15 tiles are onscreen, this only costs a few hundred bytes and is a huge optimization. I’ll probably steal this idea. (Extremely common retroprogramming pattern that has basically disappeared from modern computing: knowing there are, at most, N of something, where N is small.)
The tiles are redrawn by copying from what the code refers to as the “master screen”, which is an area of video memory after the two pages.
I don’t know how this memory is structured, but I don’t really need to - because of the page sizes, I know that a full redraw into a page MUST happen regularly without slowing everything down. So as long as the tiles live in video memory and I have a reasonably efficient copy loop I should be fine.
Also thanks to @darius reminding me of its existence I now have Dangerous Dave in Copyright Infringement running on my 286, along with John Romero’s tilemap editor, and I’m kinda thinking it’d be fucking rad to use it to build my maps instead of slogging through writing my own
Implemented my tile-blitting speedup
Pro: it is indeed much faster
Con: don't quite have all the bugs worked out yet https://mastodon.social/media/LEqZ5yj7ynPKv3PK5YU
Trying to figure out how to efficiently draw semitransparent sprites in EGA. It is... not as simple as I thought. Reading the Graphics Programming Black Book chapters about fast animation and some of the methods he's describing are absurd - chapter 43 is like "if you don't mind having every sprite be 1 colour and only using 5 colours total, here's a neat trick" and no actually I do mind those constraints, that's not helpful advice
Michael Abrash: Here’s how you reprogram the PC’s timer, but be warned! It will fuck with your system clock until it reboots! Here’s exactly what happens for this particular application and why
André LaMothe: yolo just chain your ISRs and shit will probably work out? Don’t worry about it, paste the code in, I don’t have time to explain and you don’t care. Also let’s just run all of your game logic in the timer interrupt handler, this is how multitasking works, what could go wrong
Oof! Got it. Two bugs conspired to cause a stack overflow:
* if a task was set to have its output ignored, it was leaving each character on the parameter stack. So the silent loading of the base definitions would leave a bunch of junk on the stack if there was any output. Usually there isn’t, so I didn’t notice.
* I added a definition that contained a comment before I defined the word that interprets comments, so the interpreter dumped a bunch of errors on the stack trying to figure THAT out
went to implement simple text drawing yesterday but ended up writing Jorth code to do animation lerps
managed to successfully write a word that takes five parameters on the stack, so I assume I'll be receiving some sort of Forth Programmer Certificate of Achievement in the mail soon
(Jorth still has no words that can touch anything on the stack beyond the top three values)
Getting tired of those patterns so I implemented map editing
PERSISTING map edits will have to wait for another day though
Implemented map resizing at the Jorth console, so I can design spaces that aren’t 100x100. Unexpected side benefit of integrating a live scripting language over the serial port: I don’t have to code a UI for anything in my map editor if I don’t want to. As soon as I implement the word to do the thing, I can just type it into the console.
I also drew a few new tiles.
Uhhhhh my map loading code is slightly broken because there appears to be a weird corner case where it’s reading two bytes at the beginning of the file but then it increments the stream by three bytes? Both fread and fgetc are doing this??
Ohhh I’m not specifying the “b” flag in fopen, and the map height happened to be the carriage return character :/
so remember when I was like “oh the source of all my startup performance problems is definitely byte-at-a-time unbuffered file reads”? https://mastodon.social/@SpindleyQ/101648207901180365
So I implemented a file cache and startup speed stayed pretty much the same. Turns out the problem is actually that tight Jorth loops over thousands of items are Not Fast :/
Hmm, interestingly the interpreter I defined in C is no faster at compiling all my Jorth code than my bootstrapped interpreter written in Jorth, which I guess makes sense given how little code it is
So it’s really the general VM overhead that’s killing me, and to solve that I’ve really only got two options:
* start rewriting the VM in assembly
* precompile code into an image that can be directly loaded into memory
Implemented image saving / loading! Startup time has gone from 27 seconds to, like, 3. When attempting to load game.jor it checks to see if game.jim exists and is newer than game.jor, and if so, loads it straight into RAM. If not, it compiles game.jor and then saves out the image to game.jim. Image loading has a basic sanity check to ensure it’s loading into RAM at the expected address.
Implemented traveling between areas! Each area unloads its code before loading the next, which should allow me to ensure that I don't run out of RAM to hold text. (I think I've got about 6kb left which _should_ be plenty, I hope...)
Definitely need to design a little helper DSL for defining reactions to the player bumping into areas without sprites, that code is already getting ugly...
Built my DSL - all my player collision code is much cleaner now.
Basically I have N things I need to check before I move the player - is it bumping into an object? Is the terrain walkable? Am I leaving the bounds of the map? - and if one of those questions is true, it needs to optionally perform an action and bail early on the rest. "else if" is not really a workable concept in Forth, so I needed to find another way to simplify this.
@SpindleyQ such a strong warm EGA aesthetic
@jplebreton thank you! NeoPaint did most of the texturing work for me tbh, but I’m pretty happy with how it’s looking
@SpindleyQ
Are you writing it as a COM then?
@dheadshot no, MS-DOS exe using the small memory model
@dheadshot basically there's a 64kb code segment and a 64kb data segment, so all pointers can still be 16-bit. If you're calling a function by pointer it uses one segment, if you're dereferencing a pointer it uses the other.
Honestly I didn't think I was anywhere near the 64kb data limit yet, but if I shrink the static array from 512 bytes to 256 bytes the program launches again. :/
@SpindleyQ
Would it be a good idea to use a separate Stack Segment to allow for more memory?
@dheadshot makes it so you can't use the address of anything on the stack as a near pointer, which I think I might occasionally do to eg. read data into a small local buffer. But I think Turbo C++ does provide the option.
@dheadshot The solution I'll likely take, rather than growing my memory model across the board, is to use farmalloc() for my large buffers. I've only got a couple of them, and they have very specific uses that I control, so I shouldn't have to worry too much about fighting with library calls that expect near pointers.
I implemented map saving and loading in Jorth and MAN was it slow, almost 5 seconds to load a 100x100 tilemap. So I implemented words to bulk read/write and now it’s very fast. (I am streaming off a compact flash disk, it should be!)
I’ve been noticing startup was slow, as all my Jorth source got loaded and compiled, and assumed it was the interpreter’s fault. But now I realize it’s probably actually because I’m doing unbuffered byte-at-a-time reads. Ooops.