Login
You're viewing the felin.social public feed.
  • Nov 11, 2026, 4:16 AM

    2) Not all instructions are equal. Some are actually quite rare. Some are a product of their time; verify() ensures your disk is readable and the game data matches its checksum. Most players would never call it at all, and some modern games don't even support it. save() and restore() are obviously important but you could imagine them being loaded on demand then discarded. There are other weird corners of the Z-machine specification that are less obvious, like two different bit shift opcodes.

    💬 1🔄 0⭐ 4

Replies

  • Nov 11, 2026, 4:20 AM

    So the real answer to "how do you fit an interpreter in 64K" is "you don't, quite, and you don't have to." We developed a kind of dynamic library manager and integrated it with the cache manager that a Z-machine needs to implement anyway. The DLL manager loads routines directly from the interpreter binary (.SYSTEM file), using standard ProDOS I/O. I know you're thinking "wait you can't do that, that's illegal," but it's not. Nothing is illegal these days.

    💬 1🔄 0⭐ 6
  • Nov 11, 2026, 4:27 AM

    Each dynamically loaded library is given a (fake) negative Z-address, and the DLL manager requests that address from the cache manager. The cache manager maps that to a (real) positive file offset in the interpreter binary and reads it into memory. (The library self-modifies on load since we don't know in advance where it will be end up in memory.) So each DLL is "just" another cached page, which stays in memory and can be reused without rereading from disk as long as memory pressure allows it.

    💬 1🔄 0⭐ 3
  • Nov 11, 2026, 4:39 AM

    One small snag: we rely on ProDOS MLI calls for all file I/O, and ProDOS can not read from disk into anything but main memory. On 128K+ machines, we obviously want to use auxiliary memory for cache. To accommodate ProDOS, we use a borrow-and-return (hot-swap) technique. We borrow a page by locating the active page in the cache, making a note of its seek location, reading the new content, moving it to extended memory, and then returning the page by reading from the original location.

    💬 1🔄 0⭐ 3
  • Nov 11, 2026, 4:42 AM

    The fun(*) part is what happens when the cache is full but we want to load a dynamic library. If no cache pages are available, the DLL manager uses the same borrow mechanism to temporarily evict a page from the cache and use the resulting free memory for the DLL code it reads from disk. When the DLL code returns, the DLL manager re-reads the evicted cache page from disk and no one is the wiser.

    (*) not guaranteed, actual fun may vary

    💬 1🔄 1⭐ 6
  • Nov 11, 2026, 4:48 AM

    I'm skipping a lot of complexity here. The cache manager also tracks several "active" pages, like "the page containing the Z-program counter" i.e. the game code our interpreter is currently executing. It would be bad if that page disappeared! Hilarity would not ensue! In theory we could make it work by ensuring the page's existence before parsing the next opcode, but that would be a huge unnecessary slowdown in the optimized inner parser loop. Much better to just not evict it in the first place.

    💬 1🔄 1⭐ 4
  • Nov 11, 2026, 4:58 AM

    This is the CRAFT. This is the GOOD STUFF. This is the WHOLE THING.

    💬 0🔄 2⭐ 10