Making a Gameboy emulator was one of my favorite and most instructive personal projects.
It's worth noting that there was actual extra hardware built into the carts, so you need to implement certain features like extra RAM based on the cart ID in the ROM file.
Not the cart ID, you're looking in the cart header at byte 0147 for the actual hardware (MBC1, MBC2, MMM01, etc) functionality in conjunction with bytes 0148 (ROM size) and 0149 (RAM size) for specific bank sizing/mapping.
In total I've spent roughly a month and a half working on it but mostly only spending an hour or so every couple days on it. After understanding the architecture, the coding isn't too difficult and is immensely rewarding!
The next step in emudev, with most compiled/systems languages at least, would be to create macros (such as OP_LD or OP_ADD) that generates static instructions at compilation. Another, cleanish method, in C is to generate a 256-length function pointer (void*) based static array and map those generated functions to that, to make the dispatch step simpler. Small trade off, performance-wise, but rarely matters for 8-bit CPU's (where you would usually use an opcode table over an instruction decoder).
For Z80 emulation the most efficient method I found was indeed a big nested switch-case statement. Compilers turn this into a jump table (or several if there are gaps), but don't need the function entry/exit boilerplate if each instruction code block would be its own C function referenced through a function-pointer-table. The switch-case code is generated with a python script which uses the 'algorithmic decoding approach' described here: http://z80.info/decoding.htm
Here's what such a code-generated instruction decoder looks like: https://github.com/floooh/chips/blob/master/chips/_z80_decod... (this is for a "real" Z80 with all undocumented opcodes, so it has a lot more cases to handle than the simple Gameboy Z80 variant).
The Gameboy CPU shares the same octal-structured opcode format as the Z80 from which it was derived, so it is possible to decode it even more concisely: http://www.z80.info/decoding.htm
You can see that in octal, 1xx are all moves while 2xx is all ALU ops.
Oh, it's definitely decodable. Most CPU's can be (though x86 post-386 wouldn't be fun), since that's what they're usually doing internally. But, with 8-bit CPU's, the opcode space is usually limited to a byte, which makes tables very doable and very efficient.
My rust-based GB emulator uses a decoder based on those very docs, in fact. Though, modified slightly since the GB's CPU only supports the CB-extended range operations.
It would be really cool if someone web-assemblied the best of class game system emulators and exposed them all through a uniform web interface. All vintage game consoles accessible through a single site!
I personally just code for self interest and enjoy a challenge since my job is rather stale. I can't speak from experience but I'm sure having a portfolio relevant to the type of work you're aiming for would impress potential clients and enhance corresponding skills.
I assure you having even a few hobby projects to show to potential employers is always beneficial. When you can talk about actual code you've written, often you don't even have to do any tests and the usual questions about the basics are skipped completely.
That hasn't ever been my experience. I love talking about code I've written, details of how it works, and so on. It has never gotten me out of coding tests.
It's worth noting that there was actual extra hardware built into the carts, so you need to implement certain features like extra RAM based on the cart ID in the ROM file.