Working Title: "A Hundred Days Off" [wip / poc]

A place where you can keep others updated about your NES-related projects through screenshots, videos or information in general.

Moderator: Moderators

Post Reply
rox_midge
Posts: 60
Joined: Mon Sep 19, 2005 11:51 am

Working Title: "A Hundred Days Off" [wip / poc]

Post by rox_midge » Mon Jun 29, 2020 10:02 pm

A Hundred Days Off is an open-world Metroidvania-style action/adventure platformer with RPG elements. Inspired by the mostly-continuous world of Metroid and the coming-of-age themes of The Legend of Zelda, A Hundred Days Off tells the story of a young man who finds himself awash in a war that engulfs and destroys his home village. Separated from his family during the attack, he sets out into the world alone, making his own way through a variety of landscapes and environments, confronting those responsible and working to set things right - growing up along the way.

...or, you know, that's what it will be, someday. Right now all I've really got is a technical demo that uses a hard-coded map and a camera control that's directly wired to the controller:

https://player.vimeo.com/video/433857163

Mods, if this is too early to warrant a thread, feel free to delete and I'll post something when I have something that looks more like a game.

I've been kicking this idea around for a long time. I love how Metroid hides palette swaps and tile bank switches using the corridor screens and the elevator, but I wanted to take it one step further - instead of transitioning to a mostly-blank screen and swapping all the tiles and palettes out at once, I would split the tiles up into 64-tile banks, and let each screen choose four banks and four palettes that it can use. And instead of loading an entire screen at a time, I split the screen up into "chunks" of 8x8 tiles (64x64 pixels), and load them only as needed. As soon as you cross the line that's 16 pixels from the edge of the loaded chunks, I start streaming in new chunks from the world map, one chunk per frame. Because I need to load a maximum of nine chunks (four when scrolling horizontally and five when scrolling vertically), I can comfortably scroll at a max speed of about three pixels per frame (when scrolling just one direction) or just under two pixels per frame (when scrolling diagonally).

I'm using vertical mirroring, with the status bar occupying the bottom four tiles in both nametables. Only six of these eight rows are displayed, and the overlap of two rows is exactly enough to allow the nametable and the attribute table to be updated during vertical scrolling. The horizontal line separating the playfield from the status bar hides both a tile swap (allowing the status bar to be drawn using its own bank of 64 tiles) and a palette swap (although due to timing constraints only a limited number of colors are available). The line itself is drawn using the normally-hidden palette entry $3F04, which means that it does not have to share a palette with either the playfield or the status bar.

Unfortunately I'm at that stage where I need to work on tooling in order to proceed much further, so I'm in the middle of writing a map parser for Tiled that will parse the world map, the chunks, and all of the source tiles into different tile banks, select the appropriate palettes, etc. This is turning out to be rather time consuming, but it has to be done before I involve any artists or even start thinking about gameplay, because manually preparing these maps is a gigantic pain in the neck.

Thanks for reading - I just wanted to get this out there before it withers on the vine!

User avatar
Goose2k
Posts: 79
Joined: Wed Dec 11, 2019 9:38 pm
Contact:

Re: Working Title: "A Hundred Days Off" [wip / poc]

Post by Goose2k » Mon Jun 29, 2020 11:14 pm

Sounds awesome man! Best of luck!

User avatar
Bregalad
Posts: 7884
Joined: Fri Nov 12, 2004 2:49 pm
Location: Chexbres, VD, Switzerland

Re: Working Title: "A Hundred Days Off" [wip / poc]

Post by Bregalad » Tue Jun 30, 2020 3:20 am

A Hundred Days Off is an open-world Metroidvania-style action/adventure platformer with RPG elements. Inspired by the mostly-continuous world of Metroid and the coming-of-age themes of The Legend of Zelda, A Hundred Days Off tells the story of a young man who finds himself awash in a war that engulfs and destroys his home village. Separated from his family during the attack, he sets out into the world alone, making his own way through a variety of landscapes and environments, confronting those responsible and working to set things right - growing up along the way.
Sounds amazing !
I'm using vertical mirroring, with the status bar occupying the bottom four tiles in both nametables
Mmh, it sounds like you'll have a though time scrolling vertically ! Do you use the method used by Krusty's Fun House, resetting the vertical scroll to zero where it should, varying the split point depending on the scroll value ? This definitely needs a mapper with scanline IRQ (or a very clever use of DMC IRQ).
I love how Metroid hides palette swaps and tile bank switches using the corridor screens and the elevator, but I wanted to take it one step further - instead of transitioning to a mostly-blank screen and swapping all the tiles and palettes out at once, I would split the tiles up into 64-tile banks, and let each screen choose four banks and four palettes that it can use. And instead of loading an entire screen at a time, I split the screen up into "chunks" of 8x8 tiles (64x64 pixels), and load them only as needed. As soon as you cross the line that's 16 pixels from the edge of the loaded chunks, I start streaming in new chunks from the world map, one chunk per frame. Because I need to load a maximum of nine chunks (four when scrolling horizontally and five when scrolling vertically), I can comfortably scroll at a max speed of about three pixels per frame (when scrolling just one direction) or just under two pixels per frame (when scrolling diagonally).
If I understand well, you want your game to be explored continuously without the screen ever fading out at all, and for that you load tiles and palettes and other assets progressively while scrolling - rather than faking it through a simple "corridor" or "elevator" generic room ? Sounds amazing ! Back then it would have been revolutionary.

rox_midge
Posts: 60
Joined: Mon Sep 19, 2005 11:51 am

Re: Working Title: "A Hundred Days Off" [wip / poc]

Post by rox_midge » Tue Jun 30, 2020 7:51 am

Goose2k wrote:
Mon Jun 29, 2020 11:14 pm
Sounds awesome man! Best of luck!
Bregalad wrote:
Tue Jun 30, 2020 3:20 am
Sounds amazing !
Thanks!
Bregalad wrote:
Tue Jun 30, 2020 3:20 am
Mmh, it sounds like you'll have a though time scrolling vertically ! Do you use the method used by Krusty's Fun House, resetting the vertical scroll to zero where it should, varying the split point depending on the scroll value ? This definitely needs a mapper with scanline IRQ (or a very clever use of DMC IRQ).
I didn't know that Krusty's Fun House specifically did this, but that's exactly what I'm doing, yes! I'm using MMC3, which has a scanline counter, and I reset the vertical scroll mid-frame if the seam would be visible (sometimes it's hidden by the status bar). I use the same technique to render the status bar itself, since half of its tiles come from nametable #0 and the other half come from nametable #1. The engine I've written allows for up to 16 splits, so I've got 13 that I can safely use for raster effects / parallax.

All of that is working already, with the notable exception of the attribute bytes. Due to the way vertical scrolling works, the attribute bytes associated with the chunks will be offset from the PPU's attribute table by a nybble in some cases. In those cases, I need to pull the upper nybble from one place and shift it into the lower nybble, then pull the lower nybble from somewhere else and shift it into the upper nybble. In some cases, one of the nybbles comes from the status bar instead of the playfield.

For now I'm just writing zero for all of the attribute bytes. I'm updating them at the appropriate times, but keeping track of the different states is giving me fits, and I'll have to come back to it later.
Bregalad wrote:
Tue Jun 30, 2020 3:20 am
If I understand well, you want your game to be explored continuously without the screen ever fading out at all, and for that you load tiles and palettes and other assets progressively while scrolling - rather than faking it through a simple "corridor" or "elevator" generic room ? Sounds amazing ! Back then it would have been revolutionary.
That's exactly right, yes! As you scroll, palettes and tile sets are being swapped out, so that there's not an abrupt transition between areas. Instead of reaching the end of the desert and walking off the screen into a forest, you're walking through the desert, and then there's some dead grass, then some patches of live grass, then it's mostly grass with some sand, then you've got all grass and some bushes, then there's a few small trees, then more trees, and then a wall of trees so thick you can't see through them anymore. If you keep walking, you come across a lake, and if you sail across it the trees are smaller at the other end -- or you can climb up a hill you encounter, and wind up above the treeline on the mountain. You can follow the mountain to its summit, or you can scale down the cliff face onto the rocky shore.

All the while, the screen has never blanked, nothing's been abruptly changed, and even though the desert and the rocky shore share no tiles or colors in common, you can smoothly get from one to the other, and it feels like a single, cohesive world. Basically all that's required is enough distance (4 chunks / 256 pixels) where one of the four 64-tile sets aren't being used so that it can be swapped out with a different bank of tiles.

The groundwork is already laid for this, but I'll need to finish the tooling before I can effectively demonstrate it!

User avatar
tokumaru
Posts: 11711
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Working Title: "A Hundred Days Off" [wip / poc]

Post by tokumaru » Tue Jun 30, 2020 9:04 am

rox_midge wrote:
Tue Jun 30, 2020 7:51 am
I didn't know that Krusty's Fun House specifically did this, but that's exactly what I'm doing, yes! I'm using MMC3, which has a scanline counter, and I reset the vertical scroll mid-frame if the seam would be visible
Crystalis is another game that does this.
All of that is working already, with the notable exception of the attribute bytes. Due to the way vertical scrolling works, the attribute bytes associated with the chunks will be offset from the PPU's attribute table by a nybble in some cases. In those cases, I need to pull the upper nybble from one place and shift it into the lower nybble, then pull the lower nybble from somewhere else and shift it into the upper nybble. In some cases, one of the nybbles comes from the status bar instead of the playfield.
Yeah, unless you make the map rendering area a multiple of 32 pixels tall, you'll be doing a lot of attribute juggling. If your status bar had only 4 rows of content, and the remaining 16 scanlines were just blank, you could use only the bottom 2 rows of each name table for it and have the whole 224 scanlines (a multiple of 32!) above that for rendering the background. And since the status bar would still cover 48 scanlines of the bottom of the screen, only 192 would be visible at a time, leaving you with a 32-pixel tall area for the scroll seam, completely eliminating the need to shift attribute bits around.

rox_midge
Posts: 60
Joined: Mon Sep 19, 2005 11:51 am

Re: Working Title: "A Hundred Days Off" [wip / poc]

Post by rox_midge » Wed Jul 01, 2020 6:54 am

tokumaru wrote:
Tue Jun 30, 2020 9:04 am
Yeah, unless you make the map rendering area a multiple of 32 pixels tall, you'll be doing a lot of attribute juggling. If your status bar had only 4 rows of content, and the remaining 16 scanlines were just blank, you could use only the bottom 2 rows of each name table for it and have the whole 224 scanlines (a multiple of 32!) above that for rendering the background. And since the status bar would still cover 48 scanlines of the bottom of the screen, only 192 would be visible at a time, leaving you with a 32-pixel tall area for the scroll seam, completely eliminating the need to shift attribute bits around.
That's a great point! I don't really need a giant status bar, and I'd rather dedicate more of the screen to the playfield anyway. Limiting the status bar to four rows of data (or three rows of data + a junk row I could use for spacing) simplifies the attribute updates significantly, and also allows me to show another two rows of tiles:
smaller-status-bar.png
I'm not entirely sure why I didn't go that direction in the first place! Possibly I was influenced by SMB3's 48-pixel tall status bar.

If necessary, as you said, I can just duplicate rows (or leave rendering off) to make the status bar any other height I'd like as long as its >= 32 pixels tall. The status bar keeps its own palette, and the attributes for the playfield won't need to be swapped. I'll still need to keep track of the top and bottom nybbles separately, since the same attribute byte will be used to cover the tiles at the bottom and top of the screen.

Either way, that'll be way easier to manage than trying to keep track of when I need to swap nybbles!

User avatar
gauauu
Posts: 696
Joined: Sat Jan 09, 2016 9:21 pm
Location: Central Illinois, USA
Contact:

Re: Working Title: "A Hundred Days Off" [wip / poc]

Post by gauauu » Wed Jul 01, 2020 7:25 am

Also -- don't forget to think about overscan. That $123 might be partially cut off on some TVs being tucked into the corner like that.

tepples
Posts: 21985
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Working Title: "A Hundred Days Off" [wip / poc]

Post by tepples » Wed Jul 01, 2020 3:46 pm

About overscan:

Image
Safe area reference


With the PocketNES safe area template (typical of period TVs):
hundred_pnessafe.png
hundred_pnessafe.png (1.53 KiB) Viewed 373 times
With the title safe area template (roughly the worst case of period TVs, and marked on Nintendo's background planning sheet):
hundred_titlesafe.png
hundred_titlesafe.png (1.49 KiB) Viewed 373 times

rox_midge
Posts: 60
Joined: Mon Sep 19, 2005 11:51 am

Re: Working Title: "A Hundred Days Off" [wip / poc]

Post by rox_midge » Sun Jul 05, 2020 9:16 pm

gauauu wrote:
Wed Jul 01, 2020 7:25 am
Also -- don't forget to think about overscan. That $123 might be partially cut off on some TVs being tucked into the corner like that.
tepples wrote:
Wed Jul 01, 2020 3:46 pm
About overscan
Ah! That explains why virtually all period games studiously avoid the edges of the screen. I think I remember reading about that and never really absorbed it.

I've reorganized the status bar so that it's pushed inside the safe area:
ntsc-safe.png
It turns out that I can't just disable rendering after the status bar, because if I do, I won't get the VBLANK interrupt that I need, and I can't start my VBLANK tasks early since I need to update palettes, which would cause visual issues if I'm not actually in VBLANK. To get around this, I'm displaying the last (blank) row of tiles three times; if the final art doesn't allow for that, I'll come up with some other way to do it later.

I'm going to put in a preference to allow the status bar to be all the way at the bottom of the screen, so that if you're playing in an emulator you can see another two rows of tiles:
full-screen.png
As you can see, I've also managed to get the attribute bytes updated correctly as I scroll. Most of the debug chunks have the big numbers colored blue, with the underlines in purple and red, but the "15" chunk is all red. The only parts that remain are to swap the tilesets and the palettes when the chunks are loaded, which I ought to be able to knock out tomorrow night.

After that, I start a long slog to get the map compiler fully operational. Hopefully I'll be done with that before the end of July.

lidnariq
Posts: 9417
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: Working Title: "A Hundred Days Off" [wip / poc]

Post by lidnariq » Sun Jul 05, 2020 9:34 pm

rox_midge wrote:
Sun Jul 05, 2020 9:16 pm
It turns out that I can't just disable rendering after the status bar, because if I do, I won't get the VBLANK interrupt that I need, and I can't start my VBLANK tasks early since I need to update palettes, which would cause visual issues if I'm not actually in VBLANK.
I assume you mean "you don't get the IRQ from the MMC3 that you're using as your vblank interrupt" ? 'Cuz ... once you've disabled rendering you're effectively in vblank.

Because the NMI is unrelated to whether rendering is enabled... and you could use the NMI to set a flag that lets the vblank IRQ know that it's ok to go ahead with palette updates.

User avatar
tokumaru
Posts: 11711
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Working Title: "A Hundred Days Off" [wip / poc]

Post by tokumaru » Sun Jul 05, 2020 9:44 pm

rox_midge wrote:
Sun Jul 05, 2020 9:16 pm
It turns out that I can't just disable rendering after the status bar, because if I do, I won't get the VBLANK interrupt that I need
Why is that? NMIs still fire normally even when rendering is off.
I can't start my VBLANK tasks early since I need to update palettes, which would cause visual issues if I'm not actually in VBLANK.
Sure you can, just leave the palette updates for last, but wait for the NMI handler to signal that vblank has started before you do it.
To get around this, I'm displaying the last (blank) row of tiles three times; if the final art doesn't allow for that, I'll come up with some other way to do it later.
You can use the Jurassic Park trick and select blank tiles all across the pattern table that contains background tiles.

rox_midge
Posts: 60
Joined: Mon Sep 19, 2005 11:51 am

Re: Working Title: "A Hundred Days Off" [wip / poc]

Post by rox_midge » Mon Jul 06, 2020 6:59 am

lidnariq wrote:
Sun Jul 05, 2020 9:34 pm
rox_midge wrote:
Sun Jul 05, 2020 9:16 pm
It turns out that I can't just disable rendering after the status bar, because if I do, I won't get the VBLANK interrupt that I need, and I can't start my VBLANK tasks early since I need to update palettes, which would cause visual issues if I'm not actually in VBLANK.
I assume you mean "you don't get the IRQ from the MMC3 that you're using as your vblank interrupt" ? 'Cuz ... once you've disabled rendering you're effectively in vblank.

Because the NMI is unrelated to whether rendering is enabled... and you could use the NMI to set a flag that lets the vblank IRQ know that it's ok to go ahead with palette updates.
tokumaru wrote:
Sun Jul 05, 2020 9:44 pm
Why is that? NMIs still fire normally even when rendering is off.
Although it didn't feel right to me, I didn't look at this too closely at the time, preferring instead to just do the simplest thing that I knew would work. Coming back this morning with fresh eyes, the reason was simple - I wasn't actually returning from my HBLANK IRQ.

For flexibility, my IRQ handler vector is pointed at a trampoline that jumps to a custom function. Those functions are responsible for handling the entire interrupt - saving the processor state, preserving any registers they modify, restoring them, and returning from the interrupt. I'd forgotten that this was the case, and erroneously ended my "disable rendering" IRQ handler with an RTS instead of an RTI. I don't know what the processor did after that, exactly, but it probably involved skipping some code and waiting for a signal that would never arrive.

In any case, it doesn't matter; it works correctly when I use the correct instructions, so I'll just use the correct instructions from now on! :D
tokumaru wrote:
Sun Jul 05, 2020 9:44 pm
You can use the Jurassic Park trick and select blank tiles all across the pattern table that contains background tiles.
I'm toying with the idea of having the status bar be composed of unique tiles, in which case I could just swap to a bank of decorative tiles to fill the remaining space. It really depends on whether or not I'll be displaying dialog or other text in the status bar under normal circumstances. Probably it's not worth the effort if the only effect is to make the screen pretty in a place that will never be seen.

tepples
Posts: 21985
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Working Title: "A Hundred Days Off" [wip / poc]

Post by tepples » Tue Jul 07, 2020 7:01 am

tokumaru wrote:
Sun Jul 05, 2020 9:44 pm
You can use the Jurassic Park trick and select blank tiles all across the pattern table that contains background tiles.
Or leave only sprites enabled. This works well for a status bar even if not so well for a playfield.

rox_midge
Posts: 60
Joined: Mon Sep 19, 2005 11:51 am

Re: Working Title: "A Hundred Days Off" [wip / poc]

Post by rox_midge » Tue Jul 07, 2020 9:17 pm

It works! Something about the palette updates is causing the screen split to wig out when new chunks are loaded, but it works as designed:
tile-streaming.png
The full video is here:

https://player.vimeo.com/video/436309520

This isn't just a palette swap, even though I could probably fit all of the necessary tiles into a single 8KB bank; each section is in its own 1KB bank (64 tiles) and is being swapped out along with the palette when it's no longer on the screen. Technically in this case you'd see evidence of the swap, but each bank has a blank tile at index zero, which hides the tileset swap.

Next up is the map compiler, which is going to take me a while...

rox_midge
Posts: 60
Joined: Mon Sep 19, 2005 11:51 am

Re: Working Title: "A Hundred Days Off" [wip / poc]

Post by rox_midge » Wed Jul 08, 2020 7:02 am

I fixed the wiggle on the status bar. It turns out that during VBLANK I was scheduling the MMC3 splits before updating the PPU, and because I cycle PPU A12 after loading a new chunk (due to the way I'm updating the nametable, attribute table, and palettes), all of the scheduled splits were being moved up by a single scanline when a chunk is loaded.

Moving the MMC3 split requests after the PPU updates calmed the status bar down considerably, and it's back to being rock-solid.

Here's the final version of this iteration, which includes a faster scroll (four pixels per frame instead of one) and a shorter distance between the chunks:

https://player.vimeo.com/video/436434952

Right now there needs to be a 5-chunk span (320 pixels / 1.25 screens) where a tileset / palette is unused in order to be able to swap it without the player noticing. I could probably get this down to 4 chunks (256 pixels / 1 screen), so that the tiles / palettes are updated just as the new tiles are scrolling onto the screen, but it would be more complicated, and probably not worth the effort.

Post Reply