stardance has been extended another month! the new deadline is october 31 :)

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

Riggurly

  • 46 Devlogs
  • 138 Total hours

I'm building a Game Boy emulator in Rust! All the code was written in Neovim (and maybe some lines in Doom Emacs, but it was nearly all Neovim).

Open comments for this post

2h 21m 9s logged

sleep(); sleep(); sleep(); allTestsPass()

Hello everyone! Let’s dive in so I can get back to work before I go to bed!

Testing, Testing, 1, 2, 3

For the last couple hours, I’ve been writing unit tests for the ROM module.
I’ve been going on and off with unit tests for a while. Before, I wanted to write unit test for cpu instructions before realising Blarrg made test ROMs for that.
Because of that, test ROMs are the primary mode of testing out Riggurly, especially since test ROM developers know much more about specifics, like the halt bug, than I do.
However, trest ROMs don’t test things like saving and loading, which is something I CAN write tests for in a reasonable amount of time.

A lot of the work I’ve done was just making one helper function and figuring out what I wanted to be covered by it and what I want the tests to do. I might also refactor some repeated stuff into another helper for more efficient code after I finish writing the save round trip unit tests.

That’s it for now friends, and don’t forget to listen to Flavor Foley’s new album, Mirror Modulation, which dropped today? on all music streaming platforms :3-blahaj-spinning:

0
0
42
Open comments for this post

2h 54m 11s logged

Little Bonuses

Hello Everyone! I have to go walk the dogs, so let’s dive right in!

Building Codes

Since I want a few more hours to try to see if I can get a Framework 13 and Celeste, I decided it would be good to add all the nicities I did for my last CLI, leading-zero-util (I know, terribly named), namely, man file and completion generation.
On lzu, I used a hidden flag for completions and had a build.rs for the man page. However, I found it best to merge all of them into one hidden flag for ease of use. At first I tried a build.rs, but it kinda made dependencies annoying and made either my or lib users’ lives worse.
That’s it for now folks! Next I’ll be working on unit tests for ROM stuff. :3-blahaj-spinning:

0
0
15
Open comments for this post

5h 32m 35s logged

Much TODO About Nothing

Hello everyone! We are finally back after a devlog hiatus due to me having to write essays to apply for my school’s software team! Let’s dive in so I can work on my history homework!

Read It and Weep

Last time, I talked about, in one title line by accident, how writing documentation and documenting my software’s flaws really exposed what was wrong with it. This update is all about fixing those flaws (mostly)

A Multitude of Screams

One of the biggest issues with my emulator was the audio. Not in the backend, but the frontend. Due to laziness, I didn’t bother adding mono audio support, which ended up being a 5-minute fix.

There was also another glaring issue with the audio: Once it crashed, it wouldn’t rebuild the stream. If you unplugged your headphones, thinking the audio would continue on your other speakers, it would just turn it off and modulate speed with std:🧵 :sleep(). It took me a while to write all the rebuilding code in such a way to maximise user QoL for all users.

When did I download you?!

Since Riggurly is both a CLI AND a library, it has dependencies that are used only in the CLI that are still a part of the package as a whole. Library users still have to download and compile these dependencies, which can be a hassle and a waste of time.

I’ve been meaning to put CLI-only dependencies behind a feature so library users can easily prevent themselves from downloading and compiling a bunch of heavy extra dependencies. This also taught me how to use features, which was good.

You’re a Waste of the Air You Breathe!

One thing that’s been bugging me for a while is efficiency. My array for the fallback memory bus array in the memory bus struct has always been 0x10000 bytes long (1 mebibyte, how much ram a GB has (kind of)). How reading and writing has worked is that there are match statements you go through to see if you access that array or you access the rom or if you access the vram or something else. The problem is that over 3/4 of that array’s length is useless and shadowed by ROM/CRAM/VRAM. I was wasting 768 kb of heap-memory. I ended up dropping the first 3/4 and just having a smaller array. Unfortunately, some space is still wasted, but it’s not much compared to before. I showed @ElffawIThink and they though it was funny.

Also, one of the ROM types comes with 512 HALF-BYTES(nibbles) of RAM, which I had been representing with a [u8,512] with the upper nibbles of EVERY BYTE going unused. I shrank this down to a [u8,256] and use both nibbles for a fish in the sea.

Take Me Home

Before, I had to put a disclaimer in my README that you couldn’t use ~ as your home directory in your default ROM path in your config.toml because different users have different home directories and I have to use the dirs crate to find that out at runtime. This was a surprisingly long fix, though it was quite simple. Just a few lines of code to strip the ~ and replace it with the already found home directory.

That’s all for tonight comrades! Make sure to listen to Jamie Paige, especially Clover! That song is now the first song in my playlist with vocal (sequentially, obviously my playlist has other songs with vocals), overtaking Lemon Boy by Cavetown, it’s just that good. It has also shown itself to be a better daily anthem than Villain by Teniwoha and Schifty Five, the YouTube classic.

0
0
31
Open comments for this post

2h 22m 33s logged

Lover Tell Me if You CanHello everyone! Let’s dive in so I can work on improving the user experience!## ManThis devlog is all about README. I have actually never used Markdown before, only org, Emacs’s outliner/md format, because it is more intuitive and Emacs gives you more toys. My style guide is in org, my todo lists are in org; the only things that aren’t are the AGENTS.md I stole from Ghostty and the README.md. I ended up using a combination of the Stardance and Phantom README guides to help me figure out what to put in it and how to do syntax. Working on the README put an emphasis on some flaws in my code:- config path ~ not expanding- my glaring lack of a homebrew tap- mono audio still being unsupported- feature gating on binary-only depsThat’s it for now folks!

1
0
72
Open comments for this post

3h 12m 44s logged

Erroneous

Hello everyone! I have a history in-class-writing in 6 hours, so let’s dive right in!

Error 404

Once I started adding support for loading ROMs and saves, failure points became possible. I hadn’t previously written errors before, so I just wrote strings and propagated them through with Result< T, Box> and the ? operator, with no regard to API users that may want to recover from a failure. I’ve been putting this off for so long, and was even considering doing this after shipping, but since I have so much time on my hands and the only other big TODOs left are the README.md and the Homebrew tap, I decided, why not? This has been a very interesting process over all, and it even gave me the impetus to do some proper gating on load_rom. Different MBCs only support some cram sizes, but because I wasn’t gating on load, these would fail at runtime with a mismatch.

Read All About It

Next up is the README. I previously had a mediocre, small, incomplete README in a file formet I forgot crates.io doesn’t have a renderer for, so I figured it would be best to start from scratch in Markdown instead of trying to port it over from Org. I also chnaged the name to Riggurly since that rolls off the tongue more.

That’s it for this morning! I also found the bathroom in ENA: Dream BBQ, which was cool. I still need to go inside. Also, make sure to listen to Jamie Paige, as she always boosts my productivity!

0
0
50
Open comments for this post

4h 9m 16s logged

Vendetta

Hello everyone! Let’s dive right in so I can work on my math homework so I don’t fail my calc test next week!

TUIzers

I COMPLETED THE TUIIIIII!!! I added a text entry box for people to manually enter the names of their new saves. I also stylised the highlighting a bit more, and , even though its not in the video, now, the color changes to green when you’re selecting a save. This was a really fun learning experience.

I originally wanted to make a TUI because one of the raters on my last project, leading zero util, complained about having to use a simple CLI and told me it wasn’t that good because of the lack of TUI, so this has been kind of a personal vendetta against that person to improve my software and user experience.

The Other Interface

This project isn’t just a CLI utility. The entire time I’ve been building this, I’ve wanted to enable others to make a frontend for it, whether it be on the web or a gui or some port of it in something. I’ve maintained a careful separation between lib and bin, with the exception that they are the same crate. For library users, dealing with my errors would be impossible since many of them are just strings and they are all just Box. Before throwing this on crates.io, I want to make error enums and stuff so people can do error matching.

Tap Tap

After completing error enums and stuff, I’m going to throw it on crates.io and make a github release so I can make a Homebrew tap. The last time I made a homebrew tap, I gave the project it’s own separate tap repo, but this time I’ll keep all my taps in a new tap repo on my GitHub instead of Codeberg for users.

That’s it for now! I’m excited to make error enums so I can have that skill under my belt as well! Also, I’ve reached the point where shipping would probably let me get a framework 12, which is very exciting for me.

0
0
22
Open comments for this post

3h 33m 59s logged

Pick Me

Hello everyone! Let’s dive in so I can go feed my gecko and sleep!

Seleccionar

For the past few hours, I’ve been working on the file picker for selecting a rom and a save file. A lot of it is just learning about how to use Ratatui. Originally, I was going to have the selecting happen all at once, by one function, but I switched it to two calls so that load_rom() and has_battery() can be called to check whether the save menu should even come up.

Write-In Candidate

The thing is, since with a new save, the file has to be constructed by the emulator and it doesn’t exist. Right now, I’m writing a little text input field to be triggered if the save menu gets close with Esc. This will be pretty short, and won’t require a large change to existing code. After that, I’ll wire it in and test.

Next Steps

  • Homebrew Cask
  • Separating frontend from backend crate by crate, so lib users don’t have to deal with frontend only imports.
0
0
63
Open comments for this post

2h 25m 28s logged

Ratatouille

Hello everyone, we’re back with another devlog! Let’s dive right in so I can hit bed so I can go rafting the day after tomorrow!

Compliance Chips

I’ve finally finished dealing with compliance! I’m surprised by how many extranneous parentheses I had! I knew I had a lot, but I didn’t know it was this much! I’m so happy that is over so I can get to real work!

Tui T. Sutherland

The last big feature I wanted to add before shipping was a TUI for selecting ROMs and saves. To do this, I need to use the ratatui crate, which fortunately run off the same crossterm my project already does.

So far, all I’ve done is learn about it and write the fn for getting the directories/files with the appropriate endings and sorting them for display use.

I’ll see y’all in a couple days!

0
0
13
Open comments for this post

1h 35m 43s logged

Comply-cations

Hello everyone!! We’re back with another devlog today. Let’s dive right in so I can start the next step!

Parensal Issues

As I said in my last devlog, while working on compliance for joypad.rs, I realised that I needed to delete extra parentheses around operators with obvious precedence. After working on that file, I continued down the buffers open in Neovim, also correcting that issue in addition to the others.

Because I only caught it midway through, I have to complete a second pass of compliance editing. Fortunately, I have mini.surround installed, so deleting parentheses is really easy.

That’s all that really happened. Make sure to listen to the daily dose of Jamie Paige and Vane Lily’s new song!

0
0
65
Open comments for this post

1h 56m 5s logged

You Will Be Terminated

Hello everyone! We’re back with another devlog! Let’s dive right in since it is 02:00 over here!

Lookin’ So Fab

I have contined with my quest to implement my BEAUTIFUL and living style guide.

Here’s what I did, since this work is mostly just repetitive and long.

  • gpu.rs and memory_bs.rs publicity compliance.
    Before today, 4/6 of Gpu’s fields were pub(crate) something they had no business being, so I refactored so everything uses methods. Took a chunk of time but it was very worth it.

  • registers.rs
    This one was pretty stupid. It had two main issues, publicity and masks.

The lackluster and incomplete guide I was following had me write registers.rs with a ton of bitmasks it didn’t need it in a way that was ugly and sad, so I dropped the masks.

All of Registers’ field and subfields are pub(crate), which I determined is optimal because it would be a major and massive chang to Cpu, and the registers are purely for the Cpu to use, and are part of the Cpu.

  • rom.rs
    rom.rs was pretty chill, only a few things here and there.

  • joypad.rs
    Despite this one taking a matter of minutes, this one is going to cause my so much work. All of its publicity was correct, as well as bit selecting formatting. Turns out I’ve been forgetting to remove parentheses around my files! Simply ridiculous!

ANyway, that’s all for now, since I need to go touch bed. So y’all later, and keep listening to Jamie Paige!

0
0
71
Open comments for this post

2h 17m 29s logged

If You Do Not Comply

Hello everyone! We’re back with another devlog! I need to go to school soon, so let’s dive right in!

Numerics, Literally

Since I made the style guide, it was time to start implementing it. So far, I’ve gone through emulator.rs, memory_bus.rs, apu.rs, and cpu.rs.

  • emulator.rs
    This one just needed doccomment formatting.

  • memory-bus.rs
    This one needed a few numeric literal changes. I also directly accesses the stat field of gpu, which is unnacceptable, but since I’m doing gpu.rs next, I left it for the time being.

  • apu.rs
    Despite being the file in which I came up with a chunk of my numeric literal style while writing, it felt like every literal was wrong.

  • cpu.rs
    This was the second easiest file, despite it being the longest by a wide margin. There was some repetitive numeric literal issues in the methods, but that was it. I also tried to split it into multiple files for length since it is double the size of any other file, but I lowkey failed.

Next up is gpu.rs compliance. I’ll see y’all next time!

0
0
44
Open comments for this post

4h 6m 6s logged

Dripmogging

Hello developer theme users! It’s been a couple days since my last devlog, since I couldn’t make one last night since my dad thought I had school today, so let’s dive in so I can work on compliance and becoming a peer tutor for math for my school!

Bitmasks: origins

One of the biggest parts of this project is working with bytes and getting data from bytes. To do this, you have to shift the bits you want to the end and then mask them like (byte >> shift) & bitmask. I feel like I’ve gone through dozens of different styles for bitmask and shift numeric literals, which is where the whole style guide idea came from.

Personal Stylist

For the past 4 hours, I’ve been perfecting a style guide for my codebase. It’s a beautiful org doc with that sweet sweet table of contents with rules on everything from numeric literals to tests to error enums. There’s not to much to say except the next step is going over all 5000+ lines of code to enforce compliance.

I’d like that written up

A quick note on org. Many online code hosting services like GitHub and Codeberg support org rendering. Rather unfortunately, crates.io only supports a README.md, and not a README.org, which gives me a big sad, especially since I’ll haveto rewrite my README in markdown (ew), which does not give me the luxury of an automatically generated table of contents.

That’s it for now folks, see y’all tonight!

0
0
33
Open comments for this post

5h 28m 25s logged

Scream it Loud and Scream it Proud

Hello everyone! We are back with the once again DAILY devlog!!! Let’s dive right in!

IT LIIIIIVES!!!

This morning, as soon as I came up, I sat my @ down to trouble shoot my audio processing on 4 hours of sleep :3-blahaj-spinning: . Despite adding the manual sleep time reduction, I was still experiencing some crackling, so I found a bug in the channels, solving which didn’t solve anything, but switching timing to be based off of audio when available worked like a charm, giving better and more accuracte speeds and crisp audio, so I was able to show off my COMPLETED EMULATOR CORE to some of my friends (as well as my backpack with Ferris on it).

IT DIIIIIES!!!

After adding the audio, I had to make sure that it wouldn’t cause the emulator to break, so I added a timeout that shuts off audio if it’s broken for more than 500 ms

YES CAAAAAAP

Because of how I was translating from u16 audio to f32, I was getting popping, so I implemented a software capacitor to slowly dampen sounds closer to EQUILIBRIUM

SWITCH IT UUUUP

After audio had all it needed, it was time to work on something I had long anticipated, a lib.rs for people o build their own frontends on top of my emulator. I also added an Emulator wrapper struct around Cpu so that it would be easier to manage pub methods and such without the ~1550 other lines in cpu.rs making things hard. There’s a reason this project isn’t just one big file of 5000 SLoC.

I also added some documentation, and I found the best use of Claude Code is checking for spelling errors (it’s like I’m illiterate!)

Speaking of SLoC, I cracked 5000 today! This is about 3.5x larger than my last two projects combined! I’ll see y’all next time, and don’t forget to listen to beabadoobee’s new album Pylon and Jamie Paige!

0
0
50
Open comments for this post

5h 57m 16s logged

BLAAALAKKLJHSLJHDKHJALKJHLKGSFG

Hello everyone! It seems that the devlogs have become oppositely bidaily — just this once, don’t you worry — than my last two devlogs. Let’s dive right in since we have so much to cover.

audio output master race

After I finished chan 4, it was time to implement master apu registers and channel mixing and panning.
Most of the registers were pretty simple, except the one with the master APU power on/power off, as to implement it I had to gate every write on whether it was on, except the wave RAM. I also did that during my PE class (while exercising, of course), which contributed to the difficulty. Fortunately, I write my code in Neovim, so it really sped things up, including 1-handed editing.
With that, the backend was nearly done, and I had reached the point at which I would no longer need Pan Docs or anything of the like, just straight Rust sailing.

Thread the Needle

In order to get the audio timed correctly, I had to hook up tick_channels() to a channel on which it sends a tuple of u16s (left and right amplitudes) to some receiver. This lets the audio be decoupled from whatever frontend one has.

Your Pal, CPAL

cpal is a a Rust crate for audio and stuff. Here’s the audio stuff because I can’t think straight it’s so late & I have an integration by parts quiz tomorrow and I don’t want to fail something light b/c I didn’t sleep.

fn get_audio_stream(cpu: &mut Cpu) -> Result<Option<Stream>, Box<dyn std::error: :Error>> {
    if true && let Some(device) = cpal::default_host().default_output_device() {
        let supported_stream_config = device.default_output_config()?;
        let stream_config = supported_stream_config.config();
        let sample_format = supported_stream_config.sample_format();
        let (sender, receiver) = std::sync::mpsc::sync_channel::<(u16, u16)>(5000);
        let error_callback = |_err: cpal::Error| {};

        cpu.set_audio_sample_rate(stream_config.sample_rate);
        cpu.set_audio_sample_sender(sender);

        let stream = match sample_format {
            cpal::SampleFormat::F32 => {
                let data_callback = make_data_callback::<f32>(receiver, stream_config.channels);
                let stream = device.build_output_stream(
                    stream_config,
                    data_callback,
                    error_callback,
                    None,
                )?;
                stream.play()?;
                Some(stream)
            }
            cpal::SampleFormat::I16 => {
                let data_callback = make_data_callback::<i16>(receiver, stream_config.channels);
                let stream = device.build_output_stream(
                    stream_config,
                    data_callback,
                    error_callback,
                    None,
                )?;
                stream.play()?;
                Some(stream)
            }
            cpal::SampleFormat::U16 => {
                let data_callback = make_data_callback::<u16>(receiver, stream_config.channels);
                let stream = device.build_output_stream(
                    stream_config,
                    data_callback,
                    error_callback,
                    None,
                )?;
                stream.play()?;
                Some(stream)
            }
            _ => Err(
                "Audio output sample format not supported! Try disabling audio in your config with 'audio = false'",
            )?,
        };
        return Ok(stream);
    }
    Ok(None)
}
// makedatacallback not shown due to char limits

Position,Velocity, Acceleration, Jerk, Snap, Crackle, and Pop

After that, I tested it, but there was some terrible audio crackling. Turns out it was due to oversleeping when trying to time things correctly, though before finding this out, I ended up slightly optimising draw_frame().The artifacts seem to be mostly gone and I will leave this to tomorrow silly. nearly @ 5000 sloc :3-blahaj-spinning:

0
0
27
Open comments for this post

1h 29m 27s logged

Madra Channels

Here we are; we’re back again, making this a bidaily devlog?! Let’s dive into the last 1½ hours of development so I can touch bed.

Me-Chan Navigator

After finishing channel 4, it was time to tackle the most complicated (maybe, but 4 is pretty funky), or at least the weirdest.

I thought channel 3 varied it’s pitch based on the info in the wave ram, BUT NOOO, it varies the waveform instead. It let’s the gameboy make more varied sounds rather than just variations of beeps, like saw waves and stuff.

Lots of its machinery is reused from the other channels, and it also doesn’t have envelope because you can vary the volume yourself.

That’s it for this devlog! See y’all tomorrow as I probably will finish audio tomorrow!
I don’t think there is an applicable image for this, so have some xkcds

0
0
39
Open comments for this post

3h 3m 16s logged

4Chan

Hello everyone! Welcome back to the Daily Devlog, where we go over the new developments in RustyGameGirl!! Let’s get started with today’s broadcast!

White Noise, Brown Noise, Black Noise, Grey Noise, What’s the Difference?

After finishing the two pulse channels, 1 and 2, it was time to start a new one. I went with channel 4 since it is the closest to 1 and 2 in terms of structure and simplicity.

Channel 4 is a randomised noise channel thingy, for stuff, like gunfire maybe? Here’s the summary from Pan Docs:

This channel is used to output white noise, which is done by randomly switching the amplitude between two levels fairly fast. The frequency can be adjusted in order to make the noise appear “harder” (lower frequency) or “softer” (higher frequency). The random function that switches the output level can also be manipulated. Certain settings can cause the wave to be more regular, sounding closer to a pulse than noise.

A lot of channel 4’s machinery is the same as 1 and 2’s, like envelope and length, which brings us to our next section.

Adaptive Water Body

Until today, I have never written a trait in Rust. I’ve used them, of course, especially Default and Drop, but also the num-traits crate from when I was making my fast-factor crate, but I’ve never written one before. I figured now was as good a time as any, especially since all four channels share many similar methods, like the nrxy reads and writes, output (to u8 only), clock_length, tick_period, etc., so I added the three following traits:

trait Channel {
    fn output(&self) -> u8;

    fn tick_period(&mut self, ticks: u32);
    fn clock_length(&mut self);

    fn nrx1_read(&self) -> u8;
    fn nrx2_read(&self) -> u8;
    fn nrx3_read(&self) -> u8;
    fn nrx4_read(&self) -> u8;

    fn nrx1_write(&mut self, byte: u8);
    fn nrx2_write(&mut self, byte: u8);
    fn nrx3_write(&mut self, byte: u8);
    fn nrx4_write(&mut self, byte: u8);
}

trait ChannelWithEnvelope {
    fn clock_envelope(&mut self);
}

trait ChannelSpecificFeature {
    fn nrx0_read(&self) -> u8;
    fn nrx0_write(&mut self, byte: u8);
}

Having all the separate impl blocks forced and ordered the same makes the code far more readable, and it’s not like this will slow the code, and it sped up the creation of channel 4 and will likely speed up channel 3’s creation.

Notes

Rather unfortunately, since I was coding piecemeal today and I was to lazy (probably b/c of the gudetama shirt I’m wearing), I didn’t start listening to music until ater I was done coding, while I was working on homework (English I think? It was the only real homework I had today since I did most of the rest in class), but a Polyphia song just popped up in my shuffle (Reverie), and I locked in so much harder.

Also, once I started working on the APU, I cracked 4000 SLoC!!! That’s more than my last two projects doubled and combined. It’s also likely the majority of code I’ve written in the 7 years I’ve been programming. I am currently at 4509 SLoC, which is over triple my last two projects combined!

See y’all tomorrow, if not later tonight!

0
0
57
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!

10
0
33
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.

2
0
80
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
47
Open comments for this post

43m 36s 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
14
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
12
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
23
Open comments for this post

2h 23m 41s 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 44s 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
140
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 9s 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
7
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
Open comments for this post

1h 18m 56s logged

Where are my keys?

Hello everyone! We are back with a little news, so let’s dive right in!

Hashmap, was not, in fact, the solution

The mapping from opcode to instruction enum (fn from_byte()) is finally done. Not much to say except that it was a very tedious process. While working, I realised I hadn’t implemented ADD SP, i8 (addassign a signed integer to the stack pointer) yet, so I quickly added that.

Unfortunately, that’s all folks! Next, I’m going to start dealing with graphics.

0
0
52
Open comments for this post

1h 54m 50s logged

Miscellany

Hello everyone! Let’s keep this quick because I have much tedium to get through.

What the Hecc is JR?!

I finished most of instructions, so I needed to figure out which ones I forgot. I was looking at the opcode table and I realised I had no idea what JR or DAA did, so I had to figure out what they did. JR is just a relative jump rather than an absolute one, and DAA does some number formatting after an arithmetic op for better display.

Let’s Table this Discussion

Since the instructions that I can implement right now (very nearly all of them, except for stop and interrupts, etc.), it’s been time to update the Instruction::from_byte() fn with all the new instructions. I’m still doing this, and it is purely looking at the opcode table, entering something, looking at the opcode table again, looking at what I wrote, and repeating this over and over and over again. I’m not even done with this!

Anyway, enjoy a hashmap meme that doesn’t apply to me b/c I am using a match

0
0
8
Open comments for this post

1h 38m 33s logged

Pushpops

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

Loading Shipments

I finally finished up the LD instruction! once I got through the weird targets, everything was smooth sailing.

Stack Overflow

Once LD was done, it was time to implement the stack. If you don’t know what (a/the) stack is, reference the wikipedia screenshot. For the GB, the two methods are PUSH and POP, and the stack actually goes down in memory from the end of the memory bus rather than from the beginning.

Dear Functional Bros

When I finished implementing the stack, which I though was going to be a lot harder, it was time to add functions to it. This was pretty simple: a CALL instr. that jumps to a point and puts the place where it left off on the stack and a RET instr. that goes to the last location on the stack.

0
0
47
Open comments for this post

3h 14m 27s logged

Loading Jump Time

Hello everyone! I lost track of time, and it appears I have once more logged too many hours with no devlog, yet no excuses I have. Let’s get right to it!

HL and D8

A lot of Instructions that I had already made have an HL target, in which they use the value in the joint register HL as an address in memory, and perform the operation on that byte. Up until this point, all HL targets were todos, but, since I had created the memory bus, I was able to quickly wire up those targets. A lot of those same instructions could also take the next byte in memory as the input (D8) so I wired those up too (though before HL). I actually wasn’t sure whether HL targets used a different memory bus than the one used for operations.

JP Morgan

The next instruction to deal with was JP (jump). Based on different conditions (the state of the zero flag or carry flag, or always) it would or wouldn’t jump to the address encoded over the next two bytes. Probably one of the easier instructions I’ve made.

Loading…

After JP was load. LD just copies data from one place to another. There are so many variations that I can’t talk about all of them, and there my code is too long for this devlog, so:

Byte: Load a byte from point a to point b
Word: just like the Byte type except with 16-bit valuesAFromIndirect: load the A register with the contents from a value from a memory location whose address is stored in some locationIndirectFromA: load a memory location whose address is stored in some location with the contents of the A registerAFromByteAddress: Just like AFromIndirect except the memory address is some address in the very last byte of memory.ByteAddressFromA: Just like IndirectFromA except the memory address is some address in the very last byte of memory.

This is the opcode table I’ve been using

0
0
45
Open comments for this post

4h 6m 54s logged

Timeliness

Rather unfortunately, a lot of work has been done since the last devlog due to yesterday’s hackatime outage.

Let’s get started!

House of Memories

As I mentioned in the last devlog, the next step was to create a program counter (pc, marks where in the program we are) and the memory bus (with all the instructions we have been emulating for the GB to run, and others)

Bite the Code

The instructions are not stored as plain text of enums, they are stored as bytes, one byte for each combination of instruction and target. I thus have to map each bytecode to each instruction that I have added so far.

Not English Class! Anything but English!

There are much more than 256 different bytecodes; there are actually nearly 512. To accomplish this and still have memory be 1 byte, there is a bytecode prefix, 0xcb, which tells the chip that the real instruction is the next one, and to look it up on a different table.

PCMR

To go through each instruction, I added a step function:

    fn step(&mut self) {
        let (instruction_byte, prefixed) = {
            let temp_byte = self.bus.read_byte(self.pc);
            if temp_byte == 0xcb {
                (self.bus.read_byte(self.pc + 1), true)
            } else {
                (temp_byte, false)
            }
        };

        let next_pc = if let Some(instruction) = Instruction::from_byte(instruction_byte, prefixed)
        {
            self.execute(instruction)
        } else {
            panic!("Unknown instruction found for: 0x{:x}", instruction_byte);
        };

        self.pc = next_pc;
    }

As you can see, execute returns the next pc value, something it didn’t do before. So, I had to go and make all the match arms blocks that do their associated action and also return self.pc.wrapping_add(1) for non-0xcb functions and self.pc.wrapping_add(2) for 0xcb functions, since no function that modifies the pc has been added yet.

Next is added yet more instructions! See y’all next time!

0
0
22
Open comments for this post

2h 20m 41s logged

Registering New Data

Hello Everyone! You know what they say: mo’ code mo’ devlogs.

To the right, to the right, to the right, the right, the right

I finished up all the instructions that act on register data!!! Specifically, these ones:

ADD - Add the value of the target register to the A register.
ADDHL (add to HL) - just like ADD except that the target is added to the HL register
ADC (add with carry) - just like ADD except that the value of the carry flag is also added to the number
SUB (subtract) - subtract the value stored in a specific register with the value in the A register
SBC (subtract with carry) - just like ADD except that the value of the carry flag is also subtracted from the number
AND (logical and) - do a bitwise and on the value in a specific register and the value in the A register
OR (logical or) - do a bitwise or on the value in a specific register and the value in the A register
XOR (logical xor) - do a bitwise xor on the value in a specific register and the value in the A register
CP (compare) - just like SUB except the result of the subtraction is not stored back into A
INC (increment) - increment the value in a specific register by 1
DEC (decrement) - decrement the value in a specific register by 1
CCF (complement carry flag) - toggle the value of the carry flag
SCF (set carry flag) - set the carry flag to true
RRA (rotate right A register) - bit rotate A register right through the carry flag
RLA (rotate left A register) - bit rotate A register left through the carry flag
RRCA (rotate right A register) - bit rotate A register right (not through the carry flag)
RLCA (rotate left A register) - bit rotate A register left (not through the carry flag)
CPL (complement) - toggle every bit of the A registerBIT (bit test) - test to see if a specific bit of a specific register is set
RES (bit reset) - set a specific bit of a specific register to 0
SET (bit set) - set a specific bit of a specific register to 1
SRL (shift right logical) - bit shift a specific register right by 1
RR (rotate right) - bit rotate a specific register right by 1 through the carry flag
RL (rotate left) - bit rotate a specific register left by 1 through the carry flag
RRC (rorate right) - bit rotate a specific register right by 1 (not through the carry flag)
RLC (rorate left) - bit rotate a specific register left by 1 (not through the carry flag)
SRA (shift right arithmetic) - arithmetic shift a specific register right by 1
SLA (shift left arithmetic) - arithmetic shift a specific register left by 1
SWAP (swap nibbles) - switch upper and lower nibble of a specific register

Some of the bitwise ops for rotation were a little difficult, but I was able to troubleshoot.

Next is the game ROM, the program counter, prefix instructions, and jump instructions!

0
0
30
Open comments for this post

1h 35m 33s logged

Untargeted Improvements

Hello everyone! New day, new devlog!

I added some new instructions!
CCF — Clear Carry Flag
SCF — Set Carry Flag
RRA — Rotate Right A
RLA — Rotate Left A
RRCA — Rotate Right Circular A
RLCA — Rotate Left Circular A
CPL — Complement Bits of A Register

Figuring out some of the bitwise logic for the rotation command, especially RLA (and because I was working in the car) was kind of difficult.

    fn rra(&mut self) -> u8 {
        let carry = if self.registers.f.carry { 1 } else { 0 };
        let value = self.registers.a;
        self.registers.f.zero = false;
        self.registers.f.subtract = false;
        self.registers.f.carry = value & 0x1 == 1;
        self.registers.f.half_carry = false;
        (value >> 1) | (carry << 7)
    }

    fn rla(&mut self) -> u8 {
        let carry = if self.registers.f.carry { 1 } else { 0 };
        let value = self.registers.a;
        self.registers.f.zero = false;
        self.registers.f.subtract = false;
        self.registers.f.carry = (value >> 7) & 0x1 == 1;
        // Originally I was doing the bit isolation before the bitshift,
        // and I forgot about the bitshift initially,
        // so I was just doing a bit isolation and then clippy complained b/c it would never be true.
        self.registers.f.half_carry = false;
        (value << 1) | carry
    }

Next are BIT, RES, and SET, and then more instructions!

The image is a question I saw on Protobowl a few months back.

0
0
72
Open comments for this post

2h 20m 29s logged

Target Practise

Hello everyone! We’re back with another devlog!

Instructor

I managed to finish up all the instructions (minus the HL sections of those because that uses the value in the combined HL register as a pointer to memory, which I did not yet add) that were already in the enum (screenshot from last devlog). I also just added the INC and DEC instructions, which increment or decrement the value in the target register or byte in memory pointed to by HL.

It was a bit of a hassle to figure out how I should do the target enums, since there is an 8 and 16 bit version of each INC and DEC, each with different targets. INC8 can target the byte of memory pointed to by HL, whereas INC16 can point to HL itself and the other 3 16 bit registers.

See y’all next time!

0
0
13
Open comments for this post

1h 43m 22s logged

We’ve Brought Some New Additions

After finishing up the LZU and its associated rating, it’s time to get back to work on this project!

There are no Antarctic Pyramids

Ever since I heard about the wonderful site that is Codeberg, I’ve wanted to swap to there, but the thing stopping me was this challenge, because I thought it only worked with GitHub, but when I was rating projects, I found that one of them was on Codeberg. Because of that, I swapped over this repo to Codeberg, which it made very easy, and continued on my coding journey.

Yesop

Like I said in my last devlog, I have to implement the instructions for the CPU in code! I managed to create most of the implementations for AddHL and Adc, only stopped by the fact that I still need to emulate memory. The next step is to finish Sub and the rest of the ops in this enum, then the rest of the instructions!

See y’all next time! :3-blahaj-spinning:

0
0
45
Open comments for this post

1h 22m 7s logged

Register Additions

Welcome to my first devlog! I am building a GameBoy emulator in Rust!!

Didn’t Register

The first step was emulating the registers. The GB has 8 8-bit registers, and sometimes they are combined 2-together to form 16-bit registers. 1 of the registers, register f, is the flag register. It’s upper four bits are different flags for things like carry and half carry.

Didn’t Compute

Next, I had to start making the cpu struct. It has to be capable of executing commands, like ADD, so there is a command enum with different commands, though it only has ADD right now, and I still need to figure out all the possible ADD targets.

0
0
15

Delete project?

Are you sure you want to permanently delete this project? This action cannot be undone.

All devlogs, followers, and associated data will be removed.

Followers

Loading…