Posts: 7
Threads: 1
Joined: Apr 2025
Reputation:
0
07-17-2026, 04:59 AM
Hello everyone,
I'm not a programmer, and apart from industrial controllers like Siemens S7 or a bit of Arduino, I can't code.
Multiple attempts with Gamebuino and MakeCode Arcade didn't get me the success I was hoping for either, porting my favorite shoot 'em up since childhood.
Nine months ago I made my first attempt at getting the whole thing running on the NGPC via GPT, which wasn't really successful.
Now with Claude things look different, and I've made pretty good progress. Unfortunately, as great as AI is, it still doesn't replace people with actual understanding, right now I have three problems, one of which I'll probably manage to solve on my own.
Problem number one: the status bar at the bottom for score and lives currently consists of nine tiles that cycle through to create the effect of a static bar.
The downside is that this needs too many tiles, and the approach someone suggested to me on Reddit works fine in the emulator but not on real hardware.
Problem number two: even though I haven't maxed out the sprite limit of 64, sprites keep disappearing. I'm trying to solve that today together with my friend Claude, though I'm not sure it'll work out.
Problem number three: with too many enemies on screen, the framerate drops.
In emulation it runs reasonably smoothly, my guess is it's related to the collision detection, which gets killed once an enemy is dead or off screen. I'm out of ideas, and Claude seems to be out of a plan too.
I hope someone here can help me figure these problems out, because as a noob I'm honestly a bit lost.
https://www.reddit.com/r/ngpc/comments/1..._the_ngpc/
Posts: 98
Threads: 13
Joined: Feb 2018
Reputation:
4
Hi, that was me on reddit, so I'm glad you found your way here.
Some actual code examples of the bits that are causing you grief will help, it's difficult to debug via trying to guide you to ask Claude the right questions. I guarantee you'll get better answers here, and we're a lot cheaper
Posts: 121
Threads: 8
Joined: Apr 2012
Reputation:
5
Posts: 98
Threads: 13
Joined: Feb 2018
Reputation:
4
I wondered where that was hiding
Posts: 7
Threads: 1
Joined: Apr 2025
Reputation:
0
Problem 1 the status bar (SOLVED )
This was the big one. The old bar faked a "static" HUD by cycling through a bunch of pre-shifted tile variants it ate a lot of tiles and, as several of you predicted, the raster trick that worked in the emulator did not hold up on real hardware.
We finally got it working properly with a MicroDMA raster split: instead of a CPU interrupt rewriting the scroll registers every scanline (which flickered on hardware because the interrupt timing jitters during heavy scrolling), a MicroDMA channel triggered by Timer0/HBlank rewrites the scroll register per line with zero CPU cost and no jitter. The bar is now rock-solid on real hardware, and it freed up ~126 tiles.
Huge thanks to Tixu (NgpCraft / kuroi_dokutsu) studying that engine and its docs is what pointed us to the MicroDMA approach. The CPU-interrupt version taught us a lot, but MicroDMA was the real fix.
Problem 2 disappearing sprites (root cause found )
It wasn't a sprite-count problem. The bug: enemy spawn coordinates were stored as unsigned 8-bit but read back as signed, so any coordinate ≥ 128 was interpreted as negative and the sprite got culled as "off-screen" after a short time.
Switching those to signed 16-bit fixed it all enemies stay on screen now, confirmed on hardware. Also worth noting for anyone hitting this: the NGPC has no per-scanline sprite limit, only the 64 total so if sprites vanish, it's almost certainly a logic/coordinate bug, not a hardware limit.
Problem 3 framerate drops (understood, not a mystery anymore )
The scary part random ~3-second drops to single-digit fps turned out to be a measurement bug, not a real slowdown: the fps counter used an 8-bit VBlank counter that wraps every ~4.3 s, and the maths broke at the wrap. Tixu independently confirmed the same diagnosis.
On top of that we made real optimizations (only scanning spawn triggers when the scroll row actually changes, dropping a redundant per-sprite write). Heavy scenes (8–10 enemies + bullets) are genuinely close to the hardware's per-object budget, so we locked the game to a rock-solid 30 fps and rescaled all movement speeds to feel right much better than a fluctuating framerate.
Current status: now integrating a bigger level/enemy set the tiles freed by the new bar approach are exactly what makes it fit. Sorting out some sprite-numbering after the map update, but the hard structural problems are behind me.
Honestly couldn't have gotten here without this sub and Tixu's work being open. Thank you all
Posts: 7
Threads: 1
Joined: Apr 2025
Reputation:
0
07-31-2026, 06:24 AM
Xenon (NGPC homebrew): stuck on optimisation, could use a second pair of eyes
I've been working on getting my Neo Geo Pocket Color project to run at a stable 30 fps for over a week now, and I've run out of ideas.
I went through it systematically with Claude, measuring block by block, and it can't find anything else either. So either this is as good as it gets, or someone with real experience on this hardware will spot something I don't.
The project is a vertical shoot 'em up in the style of Xenon 2, written in C89 for the TLCS-900H and built with cc900. Source and ROM are here:
https://github.com/Napsterix/xenon-ngpc
About the numbers at the bottom of the screen, that's a measurement display I built in.
The one on the right is VBlanks per 30 game frames in the current window. The one on the left is the average over the last 30 windows, so roughly 30 seconds. Scale: 60 means 30 fps, 90 means 20 fps, lower is better.
The problem: I'm averaging somewhere between 90 and 115. Compute time per frame sits just barely above 2 VBlanks, so the frame keeps flipping between 2 and 3. Since every movement value in the game is per frame, that also makes the game speed visibly wobble. Enemies speed up and slow down as you watch.
Here's what I measured, disabling one block at a time with the frame cap off, then confirming the ranking on real hardware: - sprite drawing: about 26 percent of frame time
- enemy update: about 18 percent
- everything else (terrain animation, collisions, HUD, worms, shot systems): roughly 1 percent each or less
Of that 26 percent for drawing, I can only account for about 5 percent through the individual sub parts I can switch off. The rest is spread across code I haven't managed to break down properly yet.
Things I already tried that made no measurable difference at all: moving animations to the end of the frame, skipping animation cells that are off screen, reading animation data from RAM instead of ROM, changing the OAM allocation order (two different approaches), shadow OAM, and flicker multiplexing for player shots. All of them got built, measured, and ripped back out. Every single one came out at plus minus zero.
To get under the 2 VBlank threshold I need roughly 17 percent off the frame. I'd really rather not cut the game down to 20 fps, so I'm asking here: does anyone see something I'm missing? Any pointer would be appreciated.
Posts: 121
Threads: 8
Joined: Apr 2012
Reputation:
5
07-31-2026, 04:55 PM
(This post was last modified: 08-01-2026, 01:49 AM by sodthor.)
I'll try to take a look this week-end (starting this evening).
You can also ty to see what is the asm generated from your code to have an overview of what seems to be too complicated compared to your C code.
Without rewriting parts in asm, you can try to modify your c code and see if the generated asm is better (more simple) or worse.
For example, doing multiply in loops could be replaced by cumulative additions...
You can also check with Tixul if he can add temetry in his emu...
Posts: 121
Threads: 8
Joined: Apr 2012
Reputation:
5
@Napomex,
https://drive.google.com/file/d/1pFuWAEL...sp=sharing
A version with some std optimizations:
.move some computation:
- only where they can be used to avoid them if tests avoid this code to be reached
- outside a (inner)loop to make it only once if possible
. use some #define for "inlining" calls (to avoid stack push/pop), for example:
#define UnsetSprite(SpriteNo) SpriteControl(SpriteNo, SPR_OFF, 0)
to remove from the lib...
. try to avoid mul/div in loops too (but note that use of multi-dim arrays add "mul" instructions)
I didn't apply this principals in all the code, maybe you can take a look at it and take what you want and apply some idea where you want.
Not tested on HW yet, only emus (vdmgr & ngpcraft)
I'll continue to investigate later this week-end.
Posts: 7
Threads: 1
Joined: Apr 2025
Reputation:
0
08-01-2026, 07:35 AM
(This post was last modified: 08-01-2026, 07:36 AM by Napomex.)
XENON (NGPC) how we got the frame rate stable
The big one: the emulator runs without wait states. NgpCraft can model them, but only if you ask it to. The core ships with Default cart_wait=0. Silicon-calibrated values, confirmed on our own hardware with the emulator author's calibration ROM: cart_wait 3, cart_data_wait 0, ldir_cost 14 (the datasheet's 7 is a floor), vram_wait 3.
That is a factor of 2.88 same scene, same build: 99,638 vs 286,958 cycles per frame. Code that reads a lot of instruction bytes is otherwise ~3.4x too cheap, which is exactly why plausible optimisations kept measuring "exactly zero" for us. It also explained our hardware readings: the 30 fps build needed 3.01 VBlanks per frame — the game never ran at 30 fps.
Two measurement traps. You cannot measure a block that creates work by switching it off: our enemy dispatch loop reported 11.9 % that way, the real figure is 1.3 % (the rest was "no enemies, so no drawing and no collisions"). And the test scene is part of the measurement the same pass was worth 18 %, 8 % and 3 % in three different scenes. We optimised the wrong one for a full round and saw nothing of it on hardware.
What helped, 45 % together:
Skip off-screen objects entirely: 27 %. Ours had an animation tick running independently of their position, so the draw routine searched every frame for screen rows that didn't exist for them. But the guard must be cheaper than what it prevents — our first version used the same search as the guard and was 48 % slower than no guard at all.
Linear searches out of per-frame loops: 16 %. One ran per cell instead of per row; a one-entry cache was enough.
Function calls out of cell loops two per cell, each re-deriving the same values.
Struct sizes to powers of two: 1–13 %. Otherwise cc900 emits a 16-bit multiply for every field[i].x (26 cycles vs 2 for a register move). It can backfire on small structs though: field displacements leave the 8-bit range and the code grows. Padding 18 → 32 bytes cost us 1.2 %.
Pointers instead of indices only where register pressure is low the same change: −2.5 % in a draw loop, +1.9 % in an update loop.
What didn't help: flicker multiplexing for player shots (+1.4 %), drawing every second frame (only pays as a whole; the separable parts are 1.7 % together), halving the enemy update (+9.6 %, enemies stop despawning).
Result: a constant 20 fps with headroom instead of a wobbly 20-to-15. That's as far as we got with this object count — a pro can probably squeeze out a bit more.
Sorry for the German comments ?
If you find anything else, that would be great, though I doubt it'll be possible to get the project running at a steady 30 fps.
I've updated GitHub
Posts: 121
Threads: 8
Joined: Apr 2012
Reputation:
5
@Napomex,
here is some files updated (the same way as the one before, with some more stuff)
https://drive.google.com/file/d/1DcLjQvw...sp=sharing
My approach: check code, extract from loops what can be extracted, avoid mul/div (especially in loops), put computation next to where they are used (if computation becomes useless because of break/continue/return), as I said above.
+ generate asm and check what looks too heavy for what it should do, try to modify/simplify the c code and recheck the new asm to keep the best c version.
You can compare the c file in the archive in the link of this message with yours and compare the asm generated for both versions (and maybe ask Claude what it thinks about it).
to generate asm:
cc900 -S -O3 xenon.c
Tell me if you think that what I have done may be useful to you or not...
|