Show more

I really wanted to play with the split screen register, so now there's a static footer and I can scroll around the map with the arrow keys. Scrolling is a bit slower 'cause I have an actual map in RAM now. Name is absolutely not final.

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

I'll probably have a quick look at the Keen Dreams source again and then just build something slow and naive

Came up with my own strategy for sprite drawing; so far it seems to be going pretty swell

Very important to document these glitches before I fix them IMO. I've almost got the sprites drawing in the right place, though they're not even close to the right data. I replaced the background tiles with the car sprites I drew just to make sure my TIF loader was reading them OK.

Hey, I can see a mangled version of my car sprite! And it moves around smoothly!

I have unmangled my car sprite! Now to figure out why only the grass tile is being used as a background, and also wtf is going on with my pixel mask, jeez

Coding in C for the 286 is kind of like coding in a dynamic scripting language except the only data types are "array of bytes" and "little-endian words"

Like these bytes have structure but it's probably more trouble than it's worth to try to explain to the type system what it looks like

It's fun when figuring out a problem with something then uncovers exciting new problems with that thing

Implemented smooth horizontal scrolling, which in EGA makes the footer jitter. There’s a VGA-only fix but I think I’ll switch course a little and scroll the footer onscreen at times where the player can’t move. Of course getting rid of the footer ended up exposing a new delightful glitch

Starting to factor out the game loop; added the ability to pop-up the footer by tapping the space bar, which pauses normal game logic.

Nothing visually new today, just a bunch of overdue code cleanup. Everything used to be in one giant C file but now there's proper modules and header files. Fun Turbo C++ tip: to create a new multifile project, select "Open Project" and type in the name of a file that doesn't exist

I almost set up a Twitch stream but was having trouble with my headset making me sound like a weird glitchy robot for some reason

I’ve been frustrated at how difficult it is to log stuff for debugging when your only video card is currently in use, so I grabbed a null modem cable, connected my *other* 286, a Toshiba T3100e with a very dead hard drive but a working boot floppy, and wrote a dumb serial port byte-banging routine

BTW if anyone has experience tricking old BIOSes that only know two possible hard disk geometries into booting from a big compact flash card, hit me up

Time to join my two retrodev threads: Jorth ( jean forth) now runs in-game over my serial port!

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

I mean, LaMothe was _absolutely right_ when I was a kid reading Teach Yourself Game Programming in 21 Days, I didn’t care and I would not have understood. And he explained the concepts well enough that they were accessible to me when the time came to learn them for real.

Finally hit a bug where my computer doesn’t hang but it DOES corrupt the system state enough that when it crashes, subsequent runs stop working quite right and only a reboot clears it up

So, that’s fun

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

The tricky stuff I was messing with last night when I started noticing crashes - multitasking and compiler improvements - that was all totally fine. I just forgot a DROP in some I/O code last week.

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)

Tried to implement text drawing but something is fucked and it only draws garbage

It _should_ be a very simple BIOS call to fetch a pointer to the built-in 8x8 EGA font and just use it, I’m clearly missing something fundamental

had the idea to peek at the DOSBox source to verify the BIOS call works like I expect and, yup, it's very not complicated :/

Aha! Something has gone very wrong with my inline assembly. Not obvious to me what it is yet, but this narrows the possibility space considerably.

Ahahahaha fuck my life, turbo C++ assumes it can use the bp register to point to the stack variable, but the bios call overwrites it, so it tries to store the value of bp to a random bit of memory pointed to by bp-8

Getting tired of those patterns so I implemented map editing

PERSISTING map edits will have to wait for another day though

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.

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 :/

Character portraits! I made the footer taller to accommodate them and also maybe some more text. Then I drew a horse who you will definitely meet in-game at some point. I still haven’t figured out that much about what happens in this game but the horse is in, full-stop.

The thing that will kill me about working this way isn’t the memory limitations or x86 quirks, it’s that NeoPaint does not support cut/paste while zoomed in and I have to manipulate exact 16x16 pixel squares with an oversensitive trackball

Today I implemented tile walkability and looping sprite animations. Note that the guy can go places the car can’t.

It is spooky to me how quickly this has started coming together after two months of tech fiddling. It... paid off? I’m not used to this.

Implemented entities, and running scripts when the player bumps into them. I think I’ve officially written a game engine?? Like there’s a couple of nice things I still wanna add to it but I could stop and just focus on making a thing with this if I wanted. Holy shit.

Follow

Implementing a nicer / richer syntax for writing dialog and uhhh

Project has suddenly started immediately terminating on startup, like, before my code even runs??

It looks like maybe the 512-byte cache I just added somehow exceeded my 64kb RAM budget and Turbo C++ just doesn't tell you when you're over??

so remember when I was like “oh the source of all my startup performance problems is definitely byte-at-a-time unbuffered file reads”? mastodon.social/@SpindleyQ/101

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.

Image loading works for the 90% case but occasionally there are side effects that I don't capture, so I rewrote load/save in jorth and added a post-load hook. Mostly works great, but loading a file in the post-load hook in order to make sure ITS hook is called seems to crash everything...

Argh, nope, image save/load works fine, I just wasn't putting quite enough code into the post-load hook, so side-effects necessary for the game to run without crashing weren't happening when loading from an image :P

For the record, Jorth source files have the extension .JOR, while precompiled image files have the extension .JIM (jean image)

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.

Turns out lightweight coroutines are a pretty elegant solution to this! I implemented basically the exact inverse of my each/more construct (which yields in a loop until the coroutine returns 0) - begin/search yields in a loop until the coroutine returns non-zero, then aborts it.

Yesterday was my birthday and my son made me little clay figures of my EGA sprites!

Oh no I have completely run out of fun tech to build and now I have to make maps and write and draw stuff. Haaaaaaalp

I’ve made some progress with this but I don’t wanna post a constant stream of screenshots of everything I map out because then y’all won’t have any surprises when you eventually play the game.

Hoping I get some useful / inspiring feedback when I take it to Dirty Rectangles next week because I am far from certain I have an interesting game in me right now

Found a fun tech project this evening: create a git repository with a reasonably complete commit history and back it up somewhere. So you can download and play my WIP game now (I kept the EXEs in the repo on purpose), peek at the source code, repurpose my unoptimized Forth implementation for your own ends, whatever. Why not. bitbucket.org/SpindleyQ/pete28

Slowly realized that the speed of Jorth’s interpreter is likely bottlenecked by symbol lookup. Implemented an easy standard optimization I hadn’t bothered with (don’t do a string comparison if the lengths don’t match!) and suddenly cold startup goes from almost 30 seconds to 20 seconds.

Still glad I built precompiled image support but that’s a significant win

So my game loop was calling "tick" and "draw" by looking up the words in the dictionary every frame. Decided to cache the lookup; not as a serious optimization, just as a little cleanup.

Did I mention symbol lookup is by far the slowest thing in my Jorth interpreter? HOLY SHIT the game runs SO much smoother now. I couldn't believe it, I just started wandering around in-game, marvelling at how the animation wasn't stuttering, with a huge grin on my face.

PLUS, there was initially a bug where my C->Jorth code couldn't handle calling "tick" and "draw", and in trying to fix that, I realized I could get rid of a bunch of ugly special cases and substantially clean up my VM loop. All my core VM functions are now, like, 3 lines of code, I removed a bunch of overhead, and my code can correctly do MORE things than it could before. I finally feel like I have a real Forth implementation on my hands.

Added a shitty party system so Chuck the horse can follow Pete around. Not sure if it I want to improve it so they can move simultaneously, or just make Pete ride the horse.

Also this happens when Chuck follows Pete into his house, hmmmm

Show more
Show more

Rewrote my coroutine iteration protocol such that it doesn't care about the value on the value stack at all; instead, an iterator signals completion by yielding NULL on the return stack. This lets me unify begin/search and each/more and significantly simplifies the code for my begin/search use case

@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.

@68kmentat Thanks! I am kind of flailing in the dark right now in terms of what to put into it but I'm trying to keep the faith that if I commit to keep adding things, it will eventually become something substantial

Show more

@SpindleyQ for some reason the only way i can read this is as Chuck harassing Pete at every step

"hey"
"hey"
"where are we going"
"hey"

Sign in to participate in the conversation
Mastodon

Server run by the main developers of the project 🐘 It is not focused on any particular niche interest - everyone is welcome as long as you follow our code of conduct!