The syntax for the script targeting the 24-bit interpreter works as follows:
First, an operator is supplied with a byte in the format aaabbbbb. bbbbb is the command to be executed. Commands that cause math to occur look at aaa to decide what sort of argument will follow the command. Other commands are things like "jump to subroutine" which always takes a 16 bit argument, or "return from subroutine", which doesn't take any argument.
The "return from subroutine" command 06 causes the game to pop an item from a stack of 16-bit addresses whose contents build up from address cc11, with the address of the top of the stack being tracked in cc0f-cc10.
When entering a command which does math, commands change meaning until "end of current expression" 1f is found. This second set of commands (which I've been calling operators) also follow a aaabbbbb format, with aaa having the same meaning as it does in the previous set of commands.
Within an expression, the game uses addresses cc05-cc07 as a 24-bit accumulator, while cc08-cc0a are in charge of receiving data from the argument. The interpreter will separate the data type from the operator's id, call a subroutine to grab the argument based on the data type, then call a subroutine to perform the operation requested with the two numbers now loaded.
The data types (aaa part of the commands listed above) appear to be as follows:
00 XX: Direct page. Points to c9XX as an 8 bit number.
20 XX: Direct page. Points to c9XX as a 16 bit number.
60 XX: Indirect page. Points to the data that the 16 bit address stored in c9XX itself points to.
c0 XX: Literal. Loads the number XX from the script.
e0 XX YY: Literal. Loads the number YYXX from the script as a 16 bit number.
Here are data types I haven't seen in action but that I suspect exist:
40 XX: Indirect page. Points to the data in the direct page that the 8 bit address stored in c9XX points to.
80, a0: should exist, but I have no knowledge of what they do. Maybe for 24 bit data? The virtual machine is set up to handle that but the code I've touched hasn't needed it.
Command 03, which is "call a GB-native code subroutine" is used sparingly. It's implementation is pretty cool, though - since the game boy's only CALL opcodes can only take a literal, but the game wants to use to the routine specified by the script, the game instead pushes the intended return address to the stack then jumps using JP (HL).
Here's an example of code I've written:
20 47 target c947 as a 16-bit number. This RAM seems to be unused.
ec 80 dc load the number dc80 - this is the address of a timer for animation; it cycles from 0 to f constantly. I'm going to use it like an RNG, which is probably OK because its current state depends on the exact frame when the player cleared the last text box.
1f save it
00 95 target c995 as a 8-bit number
6c 47 load the thing pointed to by c947. Notice I had to first point at the data and then use it; if there is a "load data from the following 16-bit address" data type, I haven't seen it.
c5 03 and with 03
1f save
05 YY YY call my "make sure robots have stuff" subroutine
05 cf 5d call subroutine 5dcf (does all the heavy lifting; I'm going to run out of space in this section of the ROM soon so continue code over there.)
02 e7 58 jump to 58e7 (i.e. continue on with the game's usual script here)
Inside subroutine 5dcf, here's a conditional where I use my "random" number:
61 47 Begin a conditional, with the left hand side of that conditional contained in the address c947 itself points to.
cf 02 0f seems to be the "<=" operator; a subtraction is performed immediately, and the game marks that 0f was the most recently used comparer in cc03. cf, then, is "compare with the number provided here".
1f Evaluate the expression. If it was true, do the next command. Otherwise, skip that command.
02 78 3f Jump to 3f78, where I handle turning the character into a robot.
The way this system handles data is the main reason I call it a virtual machine rather than a script engine - it has a direct page, it allows indirect indexing, etc, making coding for it feel far more like ASM than like BASIC or whatever. It maintains a stack pointer and an execution pointer, but doesn't seem to have the additional indexing registers processors generally do.
Well, the reason I ask about testing it on an actual Gameboy is because modern computers have tons of available memory and memory with a much higher hertz than the Gameboy. So even if your code is still within the memory limits of a Gameboy doesn't mean that it would work on an actual Gameboy.
This is irrelevant. Emulators emulate the exact environment of a given hardware setup. They're not perfect but for all intents and purposes they're accurate -- what the host's hardware configuration is has no bearing on the emulated environment.
I think Leeor would be less skeptical if it was proven that what you say works for the emulator, also works for the actual hardware of a Gameboy. Otherwise it is purely theoretical speculation.
My skepticism comes from knowing the limitations of the GameBoy... but Crow! has explained things in a way that I'm less skeptical. I'd like to see an assembly breakdown of the VM environment but I don't really have the time for it... basically saying it's emulating a higher bandwidth CPU is a bit of a stretch but from what I can tell it's doing some interesting things that could work on the 8bit CPU of the GameBoy.
So, basically what I'm trying to say is that if you say something far-fetched, then it is best to provide solid proof it actually works ... snip ...
He demonstrated how it could hypothetically works and knowing the hardware it might make sense. I would still like to see a disassembly of the code (assuming it's not obfuscated) and see if I can get an idea for it all.
Second, although the game boy itself has a paltry 8kB of RAM, the ROM of the cartridge can be much larger.
This. This right here is how so many games back on the NES/GameBoy/Etc. which had extremely limited resources could do things that the base hardware could never possibly do. It's a technique known as bank switching and on the NES they used what were called memory mapper chips (MMC). Nintendo provided the MMC1, MMC2, MMC3, MMC4, MMC5 and MMC6 (MMC3/6 [they're almost identical] is the most well known as it's used in Super Mario Bros. 3, it's what allows the static HUD at the bottom of the screen as well as vertical and horizontal scrolling at the same time, something the NES isn't capable of on its own).
(https://i.ibb.co/zFvzdQd/image.png)
That square chip at the top center of the PCB is the memory mapper chip.
The two large chips on the bottom are the Character ROM (graphics connected to the PPU chip in the NES) and Program ROM (connected to the CPU in the NES). This is what most NES games have. The small chip on the top left is the NES10 lockout chip (Nintendo's awful way of trying to prevent piracy). The last chip on the top right is additional RAM for use by the program. That's the interesting part -- this is the extra RAM that is built into the game's PCB that allows it to do way more than the NES would otherwise allow. Side note, HOLY SHIT is it slow! 100ns response time, yeeeeeeesh!
Anyway, GameBoy games are just as capable of using additional RAM chips like this. Hell, I wouldn't be surprised if they added an additional CPU core if they really needed it -- the SuperNES was well known for this via the SuperFX chip. It was basically an additional CPU that was dedicated to graphics manipulation and is what allowed for 3D graphics on the SuperNES (Sega's Virtua Processor was similar for the single game, Virtua Racing, that used it).
To sum up (for real this time), additional hardware on the cartridge would make this very possible. I'm just curious what hardware was on the PCB and how they mangaged the bank switching to make it work.