You are browsing as a guest. Sign up (or log in) to start making projects!

infinite_silly

@infinite_silly

Joined June 3rd, 2026

  • 50Devlogs
  • 4Projects
  • 3Ships
  • 46Votes
I'm just a silly human who likes Rust :3. Also a recovering Python user and Zig learner. I also don't know how to Δ my pfp. My best projects are a CLI tool I built to assist in the renaming of filenames, Leading Zeros Utility, and a GameBoy emulator, RustyGameGirl, both built in Rust :3
Open comments for this post

2h 15m 26s logged

Sweep It Under the Rug

Hello humans! We’re back with the DAILY DEVLOG!!!!! I need to go to bed and feed my geck, so let’s dive right in!

No Sweeping for You!

Today, I finished up last night’s work of finishing channel 2. All that was left was adding an output to u8 fn that’s just sitting there as dead code rn and wiring it up to read_byte(). relatively uneventful, except for when I forgot how bitwise operations work.

WeeeWoooWeeeWooo

After channel 2, the obvious next step was channel 1. Channel 1 is just channel 2 with an extra feature: sweep. Sweep is kinda funky. I thought it changes envelope, but it actually changes pitch. It does some weird funky shenanigans to change the pitch and also on note trigger. A lot of building channel 1 was just making echo fns for proper layering of structs. Pan docs was once again confusing.

Notes

A side note on documentation. While working, I decided to read the forword of pan docs, and lo and behold, its built for game developers, but there’s also a Complete GB Technical Reference which was supposedly built for emulator builders. I decided to check it out and see if it would be more useful than pan docs. BUT GUESS WHAT?? It had EVRYTHING EXCEPT AUDIO in there!! The word “audio” was used exactly once!! Apu wasn’t even mentioned!!! SO I have to continue with pan docs, which does have decentish detail for audio.

Music

I ended up listening to a lot of Chappell Roan, Cavetown, CITY LIMITS, and MAFUMAFU this session. Y’all should listen to the PEAK music of Sorry Ghost as well. See y’all next time!

0
0
1
Open comments for this post

3h 20m 15s logged

Apu, like from The Simpsons?

Hello beautiful humans! We’re back, and I’ve finally stopped procrastinating on the APU! Let’s dive right in!

CPU, GPU, TPU, NPU, LPU, DPU, and now APU?!

I’ve been very scared of dealing with the APU, since it’s seemed like the hairiest thing to deal with, and it’s also the last part of the emulator core.

The APU has four channels and mixing and fading and stuff I will get into.

2 Chan

The first channel I started with was channel 2, since it is the simplest (1 has an extra feature, but otherwise, they’re the same)
There are four things one needs to understand for these channels:

  • Period
  • Duty
  • Length
  • Envelope

Duty is the waveform of the audio, like what percentage of the square wave is at level 1 vs level 0. This changes the timbre of the sound.

Length is the amount of time the note plays for. It’s harder for devs to have to manually turn off the notes on their own b/c of timing.

Envelope is the fade in/fade out of the sound

Also, on channel 1, there is sweep, which changes envelope I think? I don’t know b/c Pan Docs is confusing for this (built for game developers it feels)

Aaaanyways, that’s it from me for now, as I have a history in-class writing test thing tomorrow and I’ve already hit my 2½ hour goal for the day.

Also, I was listening to Constant Companions and I realised I probably never listened to the album hard enough b/c “Clouddrop” is a lot more depressing than I remember, and I got ts on vinyl soo that’s something I will be embarrassed to tell my friends. Also, y’all should listen to Constant Companions to increase her listeners so that one day, Jamie can have her Paige again.

0
0
77
Open comments for this post

44m 46s logged

Blink Twice

Hello everyone! This is going to be a shorter devlog, so let’s dive right in!

NOOOOOOOOOOOooooooooo

When I set up the keypress handling a bit back, I wanted to use the default Retroarch keybinds so that anyone who had ever used Retroarch could use RGG without difficulty. The one discrepancy was that you only had to click escape one singular time to quit the emulator. I am no stranger to mistaken clicks, and today I realised this could be an issue for anyone who shares some of my clumsy tendencies. I figured it iwould be good to better copy Retroarch’s binds by making it so that you had to click Esc twice in quick succession to quit. A nice and easy change tha’s completion will probably force me to actually stop procrastinating audio/APU.

Anyyywayyy, y’all should listen to CityLimits, DeltaFey, and Black Polish. I feel all of their music is great for programming.

We’ll see if I can post another devlog today. ¡Adiós!

0
0
45
Open comments for this post

43m 12s logged

Frontman

Hello everyone! This will probably, maybe, be the shortest amount of time for a devlog, so let’s hop to it!

I’ll never let you go

Last night, I implemented the backend funtionality and started on frontend functionality, a ’lil bit. Over the past 30 odd minutes, I finished up the saver struct. I wanted to have Saver be have a custom drop impl, but that would require it to carry an immutable reference to the CPU through the running of the emulator, which would break ownership rules due to having to make a mutable reference to the CPU to step it.

What I did was I isolated the loop into a function and saved the return value to a variable, which gets propogated after the emulator attempts to save.

The video is of me loading in a save into Pokémon Red! Comment which theme you want me to show off next!

0
0
13
Open comments for this post

2h 55m 12s logged

SAAAAAAAVVVEEE MEEEEEEE

Hello everyone, we’re back with our daily devlog, this time before midnight! Let’s dive in!

I’ll never forget you

After finishing the MBCs, it was time to work on saving the game state. I had to save the data in binary form. Originally, I was going to use bincode, but I switched to postcard since bincode has been deprecated. I managed to get the plumbing working, so now I just have to implement the saving in main.rs. I had to keep this short since I need to go to bed. See y’all!

1
0
11
Open comments for this post

1h 22m 53s logged

Rap It Up

Hello friends! We’re back with the Daily Devlog! This one’s a quickie because the docs were (mostly) clear and I was listening to Rob Apollo while working. Let’s dive right in so I can run a mile on 5.5 hours of sleep rather than only 5!

The Final Boss of MBCs

When I started to do MBCs, the scope of this project was to do no MBC, MBC1, MBC2, MBC3, and maybe MBC5. I have been completing them largely in sequence:

  • MBC1
  • MBC2
  • No MBC
  • MBC3 (yesterday)

This leaves only MBC5, today’s feature. I thought MBC5 was going to be difficult, but it was probably the easiest MBC besides no MBC, which isn’t even an MBC!

MBC5 is basically a simpler and bigger version of MBC1, with two exceptions:

  • Rumble:
    Apparently, cartridges can shake it down because some MBC5 variants have a rumble motor controlled by bit 3 of the CRAM selector. If there is rumble, that bit is dead.

  • Chunks
    Every other MBC I’ve implemented followed this general rule: the ROM is exposed of 0x8000 bytes. Those bytes are split into four sections, each of 0x2000 bytes. Depending on which register you wrote to, it would be a different MBC command/

0
0
22
Open comments for this post

2h 22m 17s logged

Third Time’s the Charm

Hello everyone! I would have devlogged sooner, but I’ve spent most of the last time between this devlog and the last on a single feature, and the rest on YouTube. Let’s dive right in!

Clock Iiiiit!

After adding MBC2 and No MBC, the natural next step was MBC3.

MBC3 is a lot like MBC1. It uses mostly the same register, many of which are direct copies or basically the same. There are only two key differences: MBC3 supports more RAM and has an RTC (real time clock). The more RAM wasn’t much to deal with, but the RTC kinda was.

I messed up a good deal of the ticking on the irst pass of writing it, and I kept forgetting how if let worked, which did end up slowing me down.

Anyway, it’s well past midnight and I need to smash my face in my pillow. My one regret is that I didn’t listen to enough Jamie Paige while devving.

0
0
40
Open comments for this post

3h 11m 37s logged

Clip It

Hello comrades! We’re back with yet another devlog! Let’s dive in so I can get back to programming!

Memory Control (Not 1984 Style)

A bit ago, I mentioned that since cartridges were physical pieces of hardware, they could have logic chips of their own controlling their own RAM, so I would have to emulate those on top of the functionality of the actual GameBoy’s hardware.

The next MBC (memory bank controller) to emulate was MBC2. MBC2 is weird. It has ROM banks as standard, but it also has 512 half-bytes of RAM built straight into the MBC chip for some reason. Those were a little weird to emulate, but altogether straightforward. I also had duplicated RAM indexing logic for both reading and writing to the cartridge RAM, which I marked todo in my code, so I had an excuse to do a quick refactor.

After handling the weird little freak that is MBC2 was implementing No MBC. You may be asking, V, what’s there to emulate if there is nothing to emulate? And to that I say, it is once again weird and silly, just how we like it. Even if a game has no MBC, it can still have some RAM; it’s just always on and has no logic for accessing it. It ended up with me having to deal with a lil extra changes to handle some things I was not anticipating.

Looks Like You’re Trying to Kill a Stick Figure

A while ago, I watched a prime video (who was also the source of the idea for this project) about the Power of 10. I’ve since thought these rules are pretty neat and can serve to make code better (with possibly the exception of rule 7). I decided that I wanted to follow rule 10 for my code, so I foolishly set all lints to max pedantic, thinking I would have some lints, but I ENDED UP HAVING HUNDREDS OF MOSTLY USELESS LINTS, many of which ended up being me using as for casting causing less readable behaviour. Some of them were useful, such as me allocating MASSIVE arrays on the stack, even if they were boxed. Turns out Box::new([0; 0x20000]) creates the array on the stack first before memcopying to the heap in unoptimised builds, which is completely unnacceptable. Also, may of my other arrays were just straight up massive and unboxed, like the framebuffer or bus memory flat array.

Anyways, after spending the bulk of this devlog’s time dealing with that, I gave up knowing the rest of the lints were harmless and pointless and clogging up important lints.

In other news, despite listening to Jamie Paige for many hours this weekend, I still ended up needing my fren to tell me about it’s new song with Vane Lily and Yuma. I also showed them to Developer theme and it brought them some joy. I’m also very happy I got the thermometer question right, especially since I have an infrared thermometer on my desk and I had to find the correct emissivity setting to use on milk yesterday since my dad was making yoghurt. Anyway, I’ll end this before Grammarly shows me any more lints for this devlog.

0
0
42
Open comments for this post

5h 32m 38s logged

Can It Tetris?

I say hello to you again, my near and dear viewers, as I begin this devlog with a frustratingly long amount of time logged. Let’s dive in so I can put the last few days behind me.

DMing the Memory

Last time I menioned that there was a feature I needed to add that allowed the cpu to write a byte to a register in memory to trigger a transfer of data from the address specified by the written byte to the OAM (object attribute memory — sprites and stuff; this transfer is the DMA OAM tranfer) because that was the main way stuff was written to oam b/c otherwise it would be too slow.
Originally, I wrote a doubly recursive function kinda thing b/c it called read_byte() when it itself was called indirectly by read_byte() and it also called itself, but I scrapped it due to unguarenteed tail recursion and the slowness of repeatedly matching the section of memory being written to/read from, so I just used copy_within(). This also somehow fixed my input issues, so idk.

Double Bug Whammy

This is the part of the last few days that has been a slog and a horror. After adding the DMA OAM transfer, I tried running Tetris, and it worked…until I got into gameplay. The sprite showing the selected player amount on the start screen was working and it would respond to user input. However, when it hit actual gameplay, it wouldn’t render the tetromino sprites until they were baked into the background as part of setting in place. To try and diagnose the problem, I ran blarrg’s big cpu_instrs.gb test ROM, and it stopped rendering frames in the middle of the 3rd test, the standalone ROM for which had been working without a flaw. Testing in mGBA through RetroArch (I don’t have mGBA installed standalone on this device), I found that the test takes a second to run, but doesn’t cause the whole emulator to hang. Also, since new frames weren’t being completed, it wouldn’t poll for input since it only polls on a frame completion, which meant I had to close the terminal session to quit the emulator. I spent hours running tests just to find that it was getting hit by a STOP instruction it couldn’t escape. I checked Pan Docs and found there had been some unimplemented STOP behaviours. After implementing those and it still not working, I set STOP to only stop for a single cycle, which let the test ROM pass all the tests with no timing issues. After that, within 2 minutes, I was able to find an incorrect == (should have been !=) in my sprite attributes bit 7 sprite priority control, allowing Tetris to finally work!!!

Next up is:

  • MBC2
  • MBC3
  • MBC5
  • Saving
  • Audio
  • TUI rather than bare CLI
  • a lib.rs so others can write a frontend on top of Rusty Game Girl
  • A style guide and making my code compliant (hard to do while writing and over a codebase who’s size is at least 4.5x bigger than anything else I’ve ever worked with)
  • Unit tests

also here’s a video of me selling at Tetris b/c I wasn’t listening to Jamie Paige while playing

1
0
139
Open comments for this post

2h 1m 31s logged

RAM or ROM

Hello everyone! I’ve finally logged a normal amount of time again! Let’s dive right in!

MBC Say One!

I managed to bang out my first supported MBC (memory bank controller), a lot of it done while walking on the treadmill this morning. I had already implemented the ROM bank switching by this morning, so next I had to implement switching RAM banks, which has some pretty funky indexing.

Grace Hopper

Naturally, what does one do when they have a hopefully working MBC system, you try a rom!. I only found out tetris doesn’t use an mbc after removing my basic mbc substitute stuff that enabled me to run simple roms.
Speaking of Tetris, the screenshots are me attempting to run tetris iwth different themes (the themes work, tetris doesn’t which we’ll get to).
The themes in order

  • Wisteria
  • GBGRreen
  • Jungle
  • Developer
    (Don’t worry, there’s more themes, and you can always submit an issue or PR with a new theme)

Let’s get back to BUGSSSS!! When I tried to run tetris, it wasn’t accepting input and the arrow showing which options are selected was unpresent. These issues are the next things to resolve.

  1. I found that there was a critical error in the read methods on my button data stuct
    Here is the commit with the changes in case you are curious.

  2. I found out that there is a spot in memory you can write to to load a sprite from some address into OAM. This is the next thing I will be implementing before I try finding the next issue with input so I can at least see some pretty sprites.

Anyway, see y’all tomorrow after I hopefully get tetris working!

0
0
38
Open comments for this post

3h 17m 3s logged

You can switch the banks, switch banks, swi- swi- swi- swi- swi- — until you’re full of life

Hello everyone! I changed the name of my project directory, so I wasn’t seeing my hackatime or had the ability to write a devlog until I realised the problem, so let’s just dive right in!

Bank It

The next thing to deal with was bank switching and better behaviour for handling roms.

Cartridges are physical objects, and thus can have extra RAM and storage for the gameboy. These are controlled by MBCs (memory bank controllers). The most common ones are MBC1, MBC2, MBC3, and MBC5. For this project, for now, I am only emulating 1, 2, and 3. In the future, I will probably add more controllers once the full architecture is done for at least these three so I have an idea of hwo the skeletons should look.

The first step of bank switching was having the rom portion of the memory be read-only and writes to it do certain mbc actions, like enable ram or switch banks or switch ram. I am currently implementing the switching of banks. I need to get back to MBC1-specific flags and writes. See y’all next time!

0
0
24
Open comments for this post

7h 48m 37s logged

Human Error

Hello everyone! I got too much in the flow:3-blahaj-spinning: and in the RAGE, which is why I logged nearly 8 hours since the last devlog! Let’s get in the weeds!

Loading Bay

I added ROM loading!!!!! :3-pet-gabe: I can actually load a test ROM in now!!! Naturally, I immediately loaded one in, and that brings us to our first issue.

Speed? Issues

When I was loading in a test ROM, the text of the name of the test would peek from the bottom of the screen, then jump up and not show passed. I probably spent 2 HOURS on trying to fix this. I thought it was a speed issue, and I ended up making a ton of optimisations and added alternate screen switching.

BUT GUESS WHAT??? I zoomed out my terminal window, and there it was: Passed. I would share a screenshot, but I am currently working on changes to load_rom() that break the emulator currently (bank switching).

Don’t Stop

I mentioned a while ago that I had to emulate a bug in the hardware that caused the next instruction to occur twice if HALT is called when there is an interrupt pending and IME (interrupt master enable) is off. My implementation caused it to infinitely stall instead. So I fixed that bit, but the HALT bug test rom still wasn’t running the tests, so I found that LY couldn’t be read from memory, so fixing that fixed the thing, allowing the tests to run and FAIL. So I had to run a disassembler on the HALT bug test to find the exact mechanics of the HALT bug, then write it. Even though the values for the result were correct, it still wouldn’t pass. This was because of an oversight on my part when I was writing the interrupt handling. The IF byte (0xff0f, pending interrupts, whether or not they’re enabled) in hardware always has its highest 3 bits as 1, and I didn’t think it was necesary to implement this when I was writing interrupt handling, so I ignored it. And finally, fixing that, the HALT bug worked.

The screenshots are:
Gambatte failing the test.
mGBA passing.
RustBoy (May change to GameGirl/RustGirl for uniqueness and searchability) passing.

See y’all next time!

0
0
11
Open comments for this post

4h 58m 15s logged

You’ve Been Selected for a “Random” Screening

Hello Everyone! We are back! I don’t know how it’s already been over 4½ hours! Let’s dive straight into the progress!

They Know How to Push Each Other’s Buttons

After making constructors for the structs and refactoring the button data struct, I added a loop to handle all waiting button presses and set the data in the button data struct. Not particularly complicated, though it was fun to test something that actually did something.

Staaaaahhp

Instructions take a set amount of time, so it is necessary to have a pause, since even a TI-84 (buy the CG-50 or HP Prime G2 instead; don’t get the CG-100, it lacks plugin support, which is why I recommend the CG-50 at all) is faster. Since systems can’t pause for a single tick, it pauses frame-by-frame instead.

How to Exit Neovim

I didn’t take the time to add a break condition, so I added a check to see if the escape key had been pressed, and if it had, it quit the application.

Show Me the Money

The biggest development was the render loop. Now, there is actually a visual to see and play with.

Paint a Pretty Picture

I also felt it would be boring if there was only one colour theme, so I added an enum along with PixelValue => Colour matching so that you can pick a theme! Speaking of picking…

Staring at a Config File

Since the user has to be able to pick a theme, I felt it would be good to have config.toml support for ease of use.

0
0
6
Open comments for this post

5h 35m 6s logged

Building Buttons

Hi everyone!! It seems that time has slipped away from me yet again, so let’s dive straight into your scheduled content!

Pushy Pushy Buttons

After dealing with sprites (oh! the horror! the suffering! the despair!), it was time to deal with a new kind of beast: the joypad. Implementing joypad functionality meant it was time to make a frontend (ew) instead of just building subsystems and cobbling them together later.

This meant it was time to learn stuff about the crossterm crate and make some constructor functions.

The screenshot is my current struct for storing the button push data. It used to be 8 bools, but that was 7 bytes too many, so it’s just a u8 wrapper now.

Cross-multiply Those Terms!

Crossterm is a crate for dealing with terminal keyboard interrupt and such. The first and only thing I’ve done so far was make a wrapper with a custom destructor for Keyboard enhancement flags.

struct TerminalGuard {
    enhancement_pushed: bool,
}

impl TerminalGuard {
    fn new() -> std::io::Result<Self> {
        enable_raw_mode()?;

        let enhancement_pushed = supports_keyboard_enhancement().unwrap_or(false);
        if enhancement_pushed {
            execute!(
                stdout(),
                PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::REPORT_EVENT_TYPES)
            )?;
        }

        Ok(Self { enhancement_pushed })
    }
}

impl Drop for TerminalGuard {
    fn drop(&mut self) {
        if self.enhancement_pushed {
            let _ = execute!(stdout(), PopKeyboardEnhancementFlags);
        }
        let _ = disable_raw_mode();
    }
}

Computah, Make me a New Computah

Since it was time to write a main() fn, it meant I also had to write constructor functions for CPU and all it’s subsidiaries, since I’ve not bothered to do it before.

That’s it folks! See y’all next time!

2
0
21
Open comments for this post

2h 41m 16s logged

Crack Open a Sprite

Hello everyone! This dvelopment took longer than expected, so let’s just dive right in!

County Rogues, Bring me lOAM

After I finished the window layer, it was time to deal with sprites. Where each sprite is on the screen, which tiles to use, and sprite-specific render attributes are stored in a section of memory called OAM. 40 sprites’ data can be stored in OAM, each 4 bytes: the x position, y position, which tile(s) to use, and the attributes of that sprite (height, when to render, etc.)

Unlike the background and window, sprites have a lot more moving parts. Here’s my code for rendering the sprites to the buffer.

    fn render_sprites(&mut self) {
        let lcdc = self.read_byte(0xff40);
        if lcdc >> 1 & 0x1 == 0 {
            return;
        }
        let mut indexes = (0..40).collect::<Vec<u16>>();
        indexes.sort_unstable_by_key(|n| (self.read_byte(0xfe01 + n * 4) as i16 - 8, *n));
        let indexes: Vec<&u16> = indexes.iter().rev().collect();
        let height = if lcdc >> 2 & 0x1 == 0 { 8 } else { 16 };

        for n in indexes {
            let entry = 0xfe00 + n * 4;
            let sprite_y = self.read_byte(entry) as i16 - 16;
            let sprite_x = self.read_byte(entry + 0x1) as i16 - 8;
            let tile_index = self.read_byte(entry + 0x2);
            let attributes = self.read_byte(entry + 0x3);
            let palette = if (attributes >> 4) & 0x1 == 0 {
                0xff48
            } else {
                0xff49
            };

            for row in sprite_y.clamp(0, 144) as u8..(sprite_y + height).clamp(0, 144) as u8 {
                for column in sprite_x.clamp(0, 160) as u8..(sprite_x + 8).clamp(0, 160) as u8 {
                    if (attributes >> 7) & 0x1 == 1 && self.bit_7_check(row, column) {
                        continue;
                    }

                    let row_in_sprite = if (attributes >> 6) & 0x1 == 0 {
                        (row as i16 - sprite_y) as usize
                    } else {
                        height as usize - (row as i16 - sprite_y) as usize - 1
                    };

                    let column_in_sprite = if (attributes >> 5) & 0x1 == 0 {
                        (column as i16 - sprite_x) as usize
                    } else {
                        7 - (column as i16 - sprite_x) as usize
                    };

                    let which_tile = if row_in_sprite < 8 { (0, 0) } else { (1, 8) };

                    let pixel = self.index_to_tile_unsigned(tile_index + which_tile.0)
                        [row_in_sprite - which_tile.1][column_in_sprite];
                    if pixel == TilePixelValue::Zero {
                        continue;
                    }
                    let value = self.palette_lookup(pixel, palette);
                    self.gpu.set_pixel(row, column, value);
                }
            }
        }
    }

    fn bit_7_check(&self, row: u8, column: u8) -> bool {
        if self.window_active_for_pixel(column, row) {
            if self.window_pixel_value(column, row) == TilePixelValue::Zero {
                return false;
            }
            return true;
        }
        if self.background_pixel_value(column, row) == TilePixelValue::Zero {
            return false;
        }
        true
    }

This also excludes any boilerplate for window and background that I had to reuse. The most annoying part was dealing with ordering indexes efficiently, because the x value of the sprite is used as the method for sprite precedence over layers. I didn’t realise that sort_unstable_by_key dealt with like tuples and you could use that for precedence and tiebreaking.

Anyway, that’s all folks! see y’all next time!

0
0
7
Open comments for this post

1h 54m 14s logged

Ogres are like Onions

Hello everyone! This time, I’ve fortunately logged a normal amount of time. Let’s dive in!

Close the Window!

After the background rendering was complete, it was time to work on the next layer: the window. The window is exactly as it sounds — the layer between sprites and the background that can have ui and stuff.

The window starts at a position dictated by wx and wy (0xff4b and 0xff4a in memory), and those two dictate the top-left corner of the window layer (though wx - 7 is the actual top-left x coord). If a game wants to have a window layer that ends, it will switch off the window layer BETWEEN scanlines, which my current implementation cannot do because I render the whole frame at VBlank (after all scanlines) for simplicity, and it’s not strictly necessary.

The code implementation of the window layer was relatively similar to background:

    fn window_active_for_pixel(&self, screen_x: u8, screen_y: u8) -> bool {
        if (self.read_byte(0xff40) >> 5) & 0x1 == 0 {
            return false;
        }
        let wy = self.read_byte(0xff4a);
        let wx = self.read_byte(0xff4b);
        if screen_y >= wy && screen_x >= wx.saturating_sub(7) {
            return true;
        }
        false
    }

    fn window_pixel_value(&self, screen_x: u8, screen_y: u8) -> TilePixelValue {
        let window_y = screen_y - self.read_byte(0xff4a);
        let window_x = screen_x + 7 - self.read_byte(0xff4b);
        let window_tile_map_index = (window_y / 8) as u16 * 32 + (window_x / 8) as u16;
        let window_tile_map_base = if (self.read_byte(0xff40) >> 6) & 0x1 == 0 {
            0x9800
        } else {
            0x9c00
        };

        let tile =
            &self.index_to_tile(self.read_byte(window_tile_map_base + window_tile_map_index));
        tile[(window_y % 8) as usize][(window_x % 8) as usize]
    }

The next step is the final layer: Sprites! Sprites will be more involved and unique compared to background and window. I still can’t believe the Sharp SM83 did all this for developers. See y’all next time!

0
0
52
Open comments for this post

4h 53m 39s logged

No More GPU Crisis

Hello everyone! It seems that, yet again, time has slipped away from me! Let’s dive back in!

STATistics

When the chip swaps rendering modes, depending on whether the STAT register enables that specific interrupt, it will trigger a STAT interrupt (priority level 2). I had to set up a loop to shift modes so that each mode can easily be fully handled (see screenshot). This was just a matter of finding out how long each mode and line lasted, relatively simple.

Render Engines

After this, I had to start adding rendering features. There are three components to the rendering:

  • Background
  • Window
  • Sprites
    The first, and easiest part, was the background. Which tiles compose the background is dictated by a series of bytes after either 0x9800 or 0x9c00, which have references to tiles in memory. Pretty simple.
    fn render_to_buffer(&mut self) {
        for row in 0..144 {
            for column in 0..160 {
                self.gpu.set_pixel(
                    row,
                    column,
                    self.bgp_palette_lookup(self.pixel_value(column, row)),
                );
            }
        }
    }

    fn pixel_value(&self, screen_x: u8, screen_y: u8) -> TilePixelValue {
        let map_y = self.read_byte(0xff42).wrapping_add(screen_y);
        let map_x = self.read_byte(0xff43).wrapping_add(screen_x);
        let tile_map_index = (map_y / 8) as u16 * 32 + (map_x / 8) as u16;
        let tile_map_base = if (self.read_byte(0xff40) >> 3) & 0x1 != 0 {
            0x9c00
        } else {
            0x9800
        };

        let tile: &Tile = &self.index_to_tile(self.read_byte(tile_map_base + tile_map_index));
        tile[(map_y % 8) as usize][(map_x % 8) as usize]
    }

    fn index_to_tile(&self, index: u8) -> Tile {
        let unsigned = (self.read_byte(0xff40) >> 4) & 0x1 != 0;
        if unsigned {
            return self.gpu.tile_set[index as usize];
        }
        let signed_index = (256 + (index as i8 as i16)) as usize;
        self.gpu.tile_set[signed_index]
    }

    fn bgp_palette_lookup(&self, value: TilePixelValue) -> PixelValue {
        let index = value as u8;
        match (self.read_byte(0xff47) >> (index * 2)) & 0b11 {
            0 => PixelValue::White,
            1 => PixelValue::LightGray,
            2 => PixelValue::DarkGray,
            3 => PixelValue::Black,
            _ => panic!("Unknown shade"),
        }
    }

The next step is the window, then the sprites. Sprites will be hard due to their varied size. See y’all next time!

0
0
5
Open comments for this post

2h 0m 27s logged

Div(isions of time)

Hello everyone! This time, I actually remembered to make a devlog! Let’s dive in!

Div(vy up Labour)

Remember how I added the number of ticks each instruction took as a return value from the execute() fn? Well, it actually got put to use now! There is an internal 16-bit timer in the Sharp SM83, the upper 8 bits of which are exposed at memory address 0xff04. Programming this was pretty easy once I realised I could have read_byte() and write_byte() directly access the counter (Originally, I was going to have an update_div thingy probably and have the bte in memory and the accumulator be separate rather than this simpler approach).

    pub fn read_byte(&self, address: u16) -> u8 {
        let address = address as usize;
        match address {
            VRAM_BEGIN..=VRAM_END => self.gpu.read_vram(address - VRAM_BEGIN),
            0xff04 => (self.div_accumulator >> 8) as u8,
            _ => self.memory[address],
        }
    }

    pub fn write_byte(&mut self, address: u16, byte: u8) {
        let address = address as usize;
        match address {
            VRAM_BEGIN..=VRAM_END => self.gpu.write_vram(address - VRAM_BEGIN, byte),
            // Any write to div resets it and its accumulator
            0xff04 => self.div_accumulator = 0,
            _ => self.memory[address] = byte,
        };
    }

Now, last time, I set up interrupts, so with div set up, it was time to set up timer interrupts.

TIMA Time

What actually triggers timer interrupts, rather than div, is a separate timer called TIMA (0xff05 in memory). TIMA has settings set in TAC (0xff07 in memory). Bits 0 and 1 of TAC dictate which bit of the DIV accumulator (let’s call it divacc) to watch to see when TIMA should be incremented. Bit 2 dictates whether TIMA should be incremented at all.

TIMA gets incremented whenever the indicated bit of divacc falls from 1 → 0. This can sometimes be as few as every 16 ticks, and since instructions can take up to 24 ticks, this can cause the bit to go from 1 → 0 → 1, which means that you can’t just check the new divacc against the old one to see whether the bit fell, though I tried this as a temporary solution. This is what I had to do instead (see screenshot). I wish it could be prettier, but what can you do?

Anyways, y’all, see you next time!

0
0
6
Open comments for this post

2h 45m 34s logged

Interrupting Cow

Hello everyone! This took a lot longer than I expected due to a large amount of me getting confused by how interrupts work. Let’s dive in!

Halt!

After dealing with tick returns, the next step was dealing with interrupts. There are two bytes in memory that concern interrupts. One is IE (0xffff), which tells which interrupts to listen for, and the other is IF (0xff0f), which tells which interrupts are currently pending. Dealing with these was easy enough, but there was another thing that needed addressing:

The Halt Bug

The halt bug is a known bug that, should Halt be ran when there are interrupts pending and the IME (interrupt master enable I think) is off, it doesn’t halt and the next instruction runs twice. I was both confused by how this behaved and how it was triggered, which caused issues with my code that had to be addressed.

I wasn’t able to deal with anything besides interrupt handling, so that’s all for now!

0
0
10
Open comments for this post

3h 44m 20s logged

Ticking Time Bomb

Hello everyone! It seems that, yet again, I have forgotten to make a devlog for a few hours, so let’s dive in! Fortunately, not much has happened.

16 GB of VRAM

As I said in my last devlog, it was time to get started on graphical stuff. I set up a GPU struct with a separate VRAM, and set up the memory to write to that VRAM instead of the regular bus when it’s supposed to.

This was also the last part of the docs I’ve been following, so I’ve been on my own since this point. It’s a little pesky that my best resources on how the GB works are now Claude Code and a random opcode table, but what can you do?

Tick-Tock Goes the Clock

Different operations in the GB’s processor take different numbers of clock cycles to do, so I had to add this as a second return value to execute to add slowing in the future. This was the majority of the hours I logged, staring at the opcode table and trying to figure out the patterns in the timings.

Anyway, see y’all next time! These are some silly things that will probably land for at least some of you.

0
0
8
Loading more…

Followers

Loading…