Oof, yeah, FCEUX thinks several red-ish hues should all be plain red. Mesen is a lot more accurate. Maybe that level gets recolored in deep blues.
Mindbleach
Creator of
Recent community posts
The palette order vaguely conveys a day-night cycle, but is admittedly rough. It needs fixing. As with the diagonals and cliff edges - it's programmer art. A flat-color version would presumably look even moreso. Somewhere between Hovertank and Behind The Iron Gate.
This engine specifically might get reworked for a first-person Metroid affair. Fewer enemies (with respawning), limited draw distance, some kind of level streaming. I do want to play with controls that rely on timing. Like a double-jump that's also a Half-Life long-jump depending on when you press the button, and a unified gun for both pew-pew-pew and BLAM. And definitely SNES controller support, there, because I'm not doing multiplayer again unless it's really funny. Like if Slaughter 2 has four-player deathmatch. I can't beat Faceball on Game Boy supporting sixteen people.
The game now has what I think are the two final levels, and one of them should clear up the final level. The other kind of final level. You know what I mean.
Enemies are a little more diverse, but not super well balanced. I tore out a bunch of code expecting I'd draw more guys, and instead let each level change their height and behavior. That needs tweaking. Enemy placement and quantity also need tweaking. Mostly to fill out secretive areas beyond that one trolling hidden guy in the first level. That was a placeholder for a secret exit or something, an entire year ago, but I might as well put some combat in the upper area if that's simply not a feature.
The game isn't done, done, but I think this is it for capital-c Content. I went from barely fitting four levels to having enough ROM for ten or eleven. There's nine and that's enough. According to John Romero that's a megawad.
The menu needs a whole do-over. It works for both players, finally, but it should really appear on either side. (Same side or opposite side?) God help me, I may yet redo the whole sprite-projection function, or at least drop it to 30 Hz when the game chugs. The tileset is surprisingly underused... and a lot of diagonals and dropoffs are still placeholders, somehow. It deserves music, and I have room, but apparently I cannot be arsed. I took a hard look at SNES controller support and said no. Maybe after I leave it alone for another six months.
I'm not sure what comes after this. Possibly chopping down a quick and dirty Wolf3D port. Possibly aforementioned dot-throwing. Possibly a C64 port, since I've got all the hard parts in 6502 assembly, and that machine could save your edited levels. But from here to at least the end of the year, it's only tweaks, fixes, and telling people the game exists.
The latter of which I did get scooped on.
It has been fun to watch the numbers go up, and tell people on Youtube to try their own ridiculous projects. Mmmight do a lower-detail version, per suggestions? It'd be an easy ROM-hack. I could add a mapper and make the tile graphics swappable, but quite frankly, I don't wanna. It's too much of an invitation to put in even more work. I want to do what's next.
Part of what got this project rolling was bickering with the Former Dawn guys on reddit. They took a weird stance on what qualifies as a "real" NES game. They use the MXM-1 for gigabytes of full-motion video... but think pass-through audio like on Famicom would be cheating. If you push people about their add-on chips, they tend to end up going, 'if a Raspberry Pi streaming Mario 64 doesn't count, neither do battery-backed saves!' So I said: okay.
This game is NROM. There is no mapper. You could swap its chip for Super Mario Bros. (Or the much better-looking Micro Mages.) I doubt anyone involved still cares about that argument, but I consider the point made.
The posted version is a full 2x turning speed. It is a bit much, but... it really is better. If you're searching in a cheat-engine, the number wouldn't show up, because it's two INC or DEC instructions in a row. If you're looking at the source code, first of all, my apologies. But in any case it's about halfway down movement_alone. Ctrl+F "not strafing."
Reversed mouse input is definitely the game. I have no idea why it does that. I tried fixing it recently, to use the input more directly, and turning left was twice as fast as turning right. That's straight-up haunted. It's on the same to-do list as the hilarious variety of ways multiplayer can break. Less debugging, more exorcism.
Enemy placement is the screen with letters and numbers on top of floor tiles. There's a 7x7 subscreen, showing a zoomed-in view, since the grid for enemies is finer than the level grid.
How you'd save levels is a password system. Which I really thought I had an easy solution for. Quicksaves are already in VRAM - that's where they're stored. So this week I figured, I could store them as hex. Displaying the second screen would be a human-readable version of that data. Unfortunately for that human, even simple levels weigh about two hundred bytes, and then get fatter as enemies move around and bleed on things. My approach to that problem was: tough shit. The idea of someone typing in a four-hundred-digit "password" is funny enough, before the implication of a mapping scene. Unfortunately for me, even with compression, doubling the size of passwords like that can easily wind up too big to fit on one screen. So I still have to do it the hard way.
In the meantime, the way I save levels to put them in the ROM is opening up Memory Tools and copying them straight out of RAM.
Thinking of it like tank controls helps. You can usually back up while turning and dodge some fire. Some levels are just hard as nails, though. I did a test run for 100% kills yesterday. The first six levels took maybe twenty-five minutes. The last level took fifteen. Ten of them after a mid-level save, just trying to clear the final room.
Some near-future project will just throw sprites. Walls represented mostly by corners, lumpen floors marked as highs and lows, enemies... presumed. Slayer The Hawk was originally supposed to be vertex-only Wave Race, or like, Thunder Helix. But it could be another shooter. Throwing dots is so much faster than trying to squeeze a tilemap onscreen, six columns at a time.
Pain is the difficulty level, but all it changes is AI speed. Only a few tiles each frame let enemies "think." More pain, more tiles.
Suffer is kind of a joke at this point. I was padding empty space, early on, and leaned into the edgelord vibe of putting an ultraviolent blood-splattered game on NES. It's stuck around because it turns out to be useful for testing health, death, respawning, etc. If I keep it, it could give you ammo, as a trade-off. But it might just disappear.
Start Over-- is broken again? Dammit. It's supposed to restart the level.
Edit is a full-featured editor. There's terse instructions in the readme. There's mouse support. I'll have to document all the lower stuff at some point.
There's a secret option below Give Up that toggles scattered versus linear updates.
Mouse support also works with gameplay. Rapid turning causes visual issues, because the NES fillrate sucks. Then again... smooth movements seem fine. Can I just double the turn speed? Oh, wow, yeah, that was easy. Twitchy. Very prone to lingering columns. Especially with scattered updates. 1.5x speed, every-other-frame, judders. And still has visual errors on busy levels. Hmm.
Screw it, I'll upload the bare ROM.
Item scaling is harshly limited by hardware. I might add more steps for getting smaller with distance, but they simply cannot scale up, because of the sprite budget. That's why they circle in midair. The circle scales very well. If you see a tight little orbit, run to it.
Dropoff edges have distinct tiles, where any flat space ends. They're not pretty but they're there. The larger hurdle for platforming is that you can't look down. But if you're holding A, the fall speed is low enough that you can walk off a platform, and then back up back onto it.
The buzz has been fixed! Badly. The sound hardware needs 17 bytes somewhere in 0xC000, and it needs them aligned on 64. If cc65 has a sane way to place data at a specific address, then anybody who knows is keeping it a secret. Some function that happens to be in-range has to smuggle in eighty-one zeroes in a row... as instructions. Constant data is separated. So there's eighty-one BRK instructions, 0x00. Which I then manually locate in the output ROM, to change which address I write to the audio chip.
Some parts of this project feel ingenious. Other parts are dumb as hell.
Controls might be close to final - in part because I've been sloppy. Letting player-two pause keeps breaking things. The easy parts are the hard parts. Strafing is deliberately walking-speed so that the fastest movement option is forward. Or backwards. Some of the new levels have jumping puzzles (floating puzzles?) that are just as easy in either direction.
The menu needs a do-over, which I've been avoiding, even as I've been shaving away wasted space. That code is two years old and fragile. I'm gonna have to redo it from scratch, and I don't waaanna. Which should (should) be good news for passwords, because I found a stupid way to make that simple, and the menu code is bad enough that I'd rather do that.
As for the strafing/turning and gun jitter, I think your turbo button's on.
Sorry about the buzzing. The HUD is kept stable by abusing the sample channel, which can only read from a particular region in the middle of ROM, and placing things there specifically is nontrivial. I had thought it was pretty quiet, but these headphones might not be very good. I'll somehow point it at all-zeroes in future ROMs.
The HUD has to use the background now (where previously it was solid gray that scrolled freely) so I could afford to show three ammo counters. I tried using two sprites as the endpoints of their shared gauge, but the budget per-line is eight, so no. Fortunately I was hopelessly stubborn about the sample-channel thing. The NES's audio chip is on a separate clock that only kinda lines up. Using it for timing is rough. But I got it to fire an interrupt anywhere within the first dozen lines of the HUD, which was enough to leave the top part boring and fancy up the bottom. Aaand then I got the range down to within two lines. I'm still not sure how. It has to play a noise to do this, and I will make that noise a period of silence.
Multiplayer currently has friendly fire disabled, purely because I made hitscan independent of rendering, and have not bothered to check for the other player. Multiplayer also has some head-scratching bugs whenever it goes back to singleplayer. I had to push this update with several cans still freshly-kicked.
This update is mostly behind-the-scenes. I spent a few months tweaking performance as hard as possible, to where everything's about twice as fast as the last version. That let me update sprites on every frame and move fireballs more often. The levels are nearly the same, albeit with the new palette stuff, which is smaller and faster. Floor / wall graphics are more flexible, so there's now more than four types, but several of them are still fugly. The sound effects are different, mostly picked from randomly-generated examples. I was having no luck reasoning about the NES's sound chip. I made it cheap to throw a byte into a function and get out some kind of sqwonk or breep. I've picked good ones for everything that should make noise and a few things that are funny when they make noise.
This was supposed to head toward a NESdev 2024 release. But I guess that's cancelled? Even the Discord look ambiguous. In any case this was all cleanup and prep to have it Done™ somewhere in November. That should still happen. I'm gonna keep freeing up space so I can cram in more levels. Make it feel more like a complete game. That's definitely something the ammo has already changed. The enemy pinatas finally matter!
Some of the jumps in the last level are downright buggy. You can climb the towers off to the left... but it's frame-dependent, because gravity only happens sometimes.
Player rockets have AOE, but it's janky. Most notably: no height check, no visibility check. There's places you can blow up a dozen enemies in one go. But actually hitting anything is nontrivial because fireball aim is hilariously imprecise. (And Select+B remains a so-so choice of trigger.) Combat in general needs tweaking, but that'll wait until hitscan stops relying on rendering. Enemies might even get to shoot you in the back.
A lot of features have been around since 2022. They just work, now. Like the wobble effect when you shoot at portals.
Honestly? It's shockingly plausible, if you threw out the shooty parts of this game. Limiting it to a door-sized height, on a wall, would be a little wonky, but without the enemy grid there's plenty of RAM for saying "portal goes from here to here." How it's done now is a frankly ridiculous hack with a tiny array of "special tiles." Shooting the portals so they fritz out moves the special-tile reference a few steps way, and then it advances every few seconds until it finds a matching level tile. Which allows some bizarre level gimmicks where a shattered portal reappears somewhere else.
That Portal 64 port is slick as hell, though. Dude's polished it halfway to being the preferred way to play the game.
There will be as many levels as I can fit, once I get the dang password system working. Restoring a saved game and loading a level are basically the same idea. I want to compress savegames to make entering them as text, in-game, tolerable. Levels in ROM will basically be pre-typed passwords, and I only have to do the one system.
That's the same reason the enemy placement hasn't changed since the first release, even though some of them are inaccessible without cheating with the second controller. (Or rocket splash damage.) It is possible to have hundreds of enemies in one level. There's a rudimentary infighting system, if you get one type to shoot another. But they haven't moved because I don't want to code something I know I'll toss out later. I'm already replacing the wiggly rockets when you fire along a diagonal.
The deeply amusing side effect of a password system is that levels might work between platforms.
Found another way to do it, with one block of inline ASM tricking the processor into giving up the Instruction Pointer (because apparently Intel thinks it's supposed to be a secret!) and pointing earlier in the block of ASM to some custom code that definitely has no function / subroutine / procedure hang-ups. 86box don't care. 86box just does not like my sound function... when it is called by a COM file. An EXE works fine. So, screw it, this compo's getting a COM version specific to DosBox-X.
Aggravating. CC65 has similar forced diversions into 6502 ASM, but CC65 is a glorified assembler macro. (Which does make it lightning-fast.) Watcom is old enough that this was relevant and Open Watcom is active enough that bugs still get fixed, and here it is tripping over itself.
... nevermind, I solved it.
INT 21h will accept a normal function name in DX, for AX=0x251C (set interrupt vector 0x1C). The function will crash. But you can set it, and it will run. Once. Even _asm{ iret } won't return properly. The problem is, interrupts only push / pop the bare minimum of state. A function takes more setup and leaves a different stack.
But you can pad the function with _asm{ nop nop nop ... } and jump somewhere in the middle of those. Eight seems to work fine and I have no reason to push for fewer. The bare minimum presumably differs with the complexity of the function itself. So in the interest of doing things as sensibly as this jank allows - I wrote a wrapper function that only calls play_music normally and then does _asm{ iret }.
TLDR:
AX = 0x251C, DX = your_function_wrapper, DX += 8, INT 21h / int86 0x21. DS probably has to be zero as well.
void your_function_wrapper() {
_asm{ nop (8x, each one goes on a new line) }
your_function();
_asm{ iret }
}
I have a fully-working DOS game, in C. Open Watcom (version does not appear to matter) will take "void __interrupt __far play_music()" and "_dos_setvect( 0x1C, play_music )" and then call that function several times per second... for an EXE. Setting "-bcl=com" in the Makefile, or doing "wlink system com" manually, will instead crash the first time the function is called.
The linker does throw several "segment relocation" warnings, but Open Watcom's error documentation just says segment relocation is not allowed in the COM format. There is no indication of what does that, or how to avoid it.
Here is an incomplete list of things that don't solve the problem:
Prototyping "void __interrupt play_music()" has no effect whatsoever.
Prototyping "void play_music()" sees _dos_setvect throw type errors. Casting is either impossible, or locked behind a syntax different from the dozen ways I have tried coercing the one it friggin' asked for.
Laundering play_music or &play_music through a "void interrupt (*old_interrupt)(void) = NULL" pointer has no effect, even though "old_interrupt = _dos_getvect( 0x1C )" and "_dos_setvect( 0x1C, old_interrupt )" work fine.
"_chain_intr( old_interrupt )" from play_music makes no difference. It's not an issue of being 0x1C. The actual function is never called.
Manually writing addresses to the interrupt vector list (page zero) also never calls the function, even if it's just "void play_music()". It's possible I'm casting the address wrong, but I assure you, it is not for lack of trying.
I could just check time() more often and call the function from the main loop, but then it sounds like garbage on anything slower than a 386, when it should run fine on an 8088. That's why it's in the name.
I have exhausted my patience for convincing search engines that I do in fact mean MS-DOS and Open Watcom, instead of whatever nonsense they'd like to substitute that has so many more results I didn't ask for. I have gone half-mad plowing through archaic technical documents that are always the worst combination of hand-wavy and inscrutable. I have vicious criticism for every website about those documents which are somehow even more archaic and fanatically devoted to making Ctrl+F useless.
I give up. What combination of magic words will let a one-segment program call a goddamn function so I can make with the bleep-bloops?
The tiles wound up with a Windows 95 shareware vibe because the CGA text-mode hack for 16-color graphics made some shapes impossible. Triangle up: trivial. Triangle down: gave up and drew an urn.
Gameplay after failure is part of the game being ephemeral. There's no high score table. There's no ending. It just ramps up to barely-tolerable pressure and stays there. The only difference after topping out is the lack of screen clears, because scoring stops.
Difficulty is the same past level 0x10. There's a way to score high enough, on the first screen, to go straight there.
MDA even gets its own tileset and backgrounds, because of how different it is from CGA / EGA / VGA. I had fun trying to coerce any set of distinct objects from two shades of green and a fixed font. They might not even port over, if I get around to ridiculous Olivetti and palmtop versions.
Incidentally, I love how The Anarchic Kingdom uses a CGA palette to look like the Bayeux tapestry. Working within technical limits takes more thought than fighting them.
Oh right, I don't do anything to restore initial settings. Less of a concern on emulators. (And in EGA or VGA it's liable to remain stuck in faux CGA mode.) Should it try to set 80x25 on exit? Or is there a sensible way to detect and reset things?
I have been scrolling through esoteric documentation for days and the video setup is still 80% copy-paste and 10% guesswork. At least I got the snow to stop.
Regarding 86Box: get disk images from WinWorldPC, and use Linux-y mtools to fake floppies. E.g. "mformat -C -f 1440 -v MYFLOP -i myflop.img ::" to create a 1.44 MB file and "mcopy -i myflop.img program.exe ::" to put stuff on it. "::" acts as the fake drive letter. You will have to eject / re-mount in 86box if you update the file.
Full disclosure: my 30 KB-ish executable is not yet a proper COM file. Currently trying to exorcise some "segment relocation" nonsense.
It's all C. Shockingly, the best approach was straightforward inverse-Z x horizontal distance, right in hblank. Initially it just added a reciprocal to a list of offsets, and while that was efficient enough to scroll every two scanlines instead of every four, it drifted like crazy. Correcting that drift created visible jumps. I tried a wide variety of clever nonsense, and none of it beat rock-solid frame-to-frame tracking.
And I do apologize for the sound in the credits.
There might be an update that lets NPCs wander up and down each plane. I got it working a few days before the deadline, but just barely, so it didn't make the cut. It requires triple-buffering. The hblank function does the math, as now, but writes the results to an array. That'd get used to place the sprites based on which row they're tracking. But sprites have a one-frame delay. So hblank needs to scroll based on the math it did two frames ago. That part worked annoyingly well. Moving things between rows was super janky, though, and I was dropping frames any time cool stuff happened.
The mantra for this was clear: 60 Hz or die.
"Failure is always an option" whether we like it or not.
Game jam deadlines can be fudged by getting to a game. Nobody else sees what you wanted it to be. It is what it is. And if what it is, early and often, is a title screen, some gameplay, and a credits screen, that's a complete project. It's better than perfect. It's done.
... to that end, I'm hoping the judges grab ROMs now, instead of going with the first *_jam.gb file that was available. My just-in-case build from Tuesday did not have sound.
Try the -cga flag.
That would fix it if it's mis-detecting MDA... but the game might just be bugged. I have only tested this on emulators. One person did have trouble with a CGA+MDA card, but that still showed buggy CGA output. In MDA mode it never writes anything to that address space.
If it's simply wrong on hardware, the source code is one file. Open Watcom takes one arcane command (see top comments in source code) and runs on anything. The problem is presumably in cga_initialize_screen, which contains some magic register operations I fully admit I do not understand. 160x100 "mode" is reasonably well-documented and this is a 40-column version of that 80-column hack.
At some point I need to update this, myself. But not today and not tomorrow.
Turn acceleration is going in at some point. Probably after a menu clean-up. (There's a toggle for fast versus smooth rendering, but it's hidden below the last visible option.) In the meantime... Mesen lets you plug an SNES mouse into the second port.
Portals were another weirdly easy absurd feature thanks to raycasting. If you get a close look at enemies through one, you can tell the math is a little goofy. The surface was originally supposed to wobble or flex continuously. That's now limited to when they're shot, because the feedback is hard to distinguish at these framerates, Static-by-default lets one tucked into a corner look like contiguous space - like the one in that high alcove. (Which you can get to, with the glide-y jumping. Definitely keeping the bizarre platforming.)
The shotgun and machinegun might get combined. An amalgamated pew-pew weapon that fires differently based on how you hold B is extremely well-precedented on NES. Though it'd act more like you're always charging a shotgun blast, press B to release it, and spit bullets continuously while holding it. That's honestly more likely to go in "Slaughter 2," after this is complete enough to call a proper game. Anyway that'd free up Select to immediately fire rockets, making them easier and more useful. But so would nerfing the current rate-of-fire so bullets and pellets can get punchier. Even as a firehose of bullets, the sound effects make them sound like a polite suggestion. There'd be some power-up that brings back this rate-of-fire and absolutely rips through enemies.
Enemies should also be crammed into every inch of this level. Blue zombie guys in the green underground. Rabbit tank things around the exit. Their distribution hasn't changed since the compo, because I want the data stored in the same compressed format as the passwords, and that format is currently thrown out so I can start over. (This was not aided by finally discovering that reading data back from VRAM storage begins with a byte of junk. Nintendo. Whyyy.)
All of which has to wait until after GB Compo, which runs for another month. This update was an excuse to avoid my initial project. Two weeks ago I said "Screw it, I wanna play with scanlines!" and honestly it's been great. Expect 60 Hz parallax and a vulgar bird. Then more pew-pew NES stuff.
People have this confused with GB Jam, which is for any kind of game in four colors at 160x144.
Coincidentally: that one starts the same day this one ends. https://itch.io/jam/gbjam11
I am not joking when I say this is the best action game this platform has ever seen. It is the only action game this platform has ever seen. Because you can only emit characters, one per frame, at the bottom of the screen, in order. Obviously I would have placed the player further up the screen, except you literally can't.
The compromise was to move the obstacles. They appear (and end) before you hit them... underground. You are hitting the cellar as you dig underneath this Chilean squiggle of a kingdom.
Admittedly it'd be better-off moving the collision one row closer and relying on longer shadows for reaction time.
That sounds about right. Graphics are, to put it gently, limited.
I had the extra line below the code because the site's copy-paste is a little shaky on line endings. But I can see how that would cause other problems. A semicolon would eat anything typed before hitting Enter. A colon inserts more hex, or... treats an address as reading from tape? I don't want to end with a 300R, because the game starts right up, and the Load interface obscures it.
Ending with a 300 in the buffer should better handle whatever people do with my vague instructions.
In my defense (for the instructions, unrelated to the game) it is a stretch to say this machine had published software. Concise explanations are a step above providing a soldering diagram.
You hit things below the ground floor because you're underground.
Someone on reddit built a very clean replica, and posted a mediocre "digital rain" effect from The Matrix. I started a quick little effort to improve it and that got wildly out of hand. After a week, the resulting discussion of speed limitations concluded that the Apple I can print exactly one letter per frame. It is physically incapable of more. There is only one control character: the newline / carriage-return. Even a simple game at interactive speeds would be impossible. Right? Well. You can probably guess how my brain responds to the word "impossible."
I don't know how directly compatible the Apple II's ROM is. But where "20 EF FF" appears, the game expects to print the contents of the A register and then wait one frame. So insert... it feels wrong to call anything on 1970s hardware a polyfill. But point those JSRs elsewhere as-needed.
If it just prints characters as fast the Apple II is able, don't blink.
The sky was always a gradient, it's just upside-down and higher-contrast now. One of those things was on purpose. (On a possibly unrelated note: guess I shouldn't use green and red as distinct global colors, for the sake of colorblindness.) Tile graphics are likely to get another overhaul before any sort of proper release.
The level's the same, it just has a roof. I added ceilings for performance. It limits draw distance. This was a terrible idea for about three weeks, but now I'm pretty sure it's faster overall. Where it's not, well, I need better levels. That's why the editor is different. Right now I'm trying to compress savegames to where I can call them "passwords" with a straight face. Then people can share levels.
There's mild complexity in how ceilings work, where floors are allowed to stick through them. That's probably less confusing than where red walls also have red ceilings, because again, I do need to make better levels.
Enemies are animated now! Which admittedly looks so-so at current speeds. They're timed with the global vblank counter, which naively cycles every four seconds, so at some point I'll add another byte that only increments every fourth vblank or something. That should make the various bouncing and shifting look much better. Crank the overclocking in Mesen for an idea of what they're supposed to be doing.
But the blue guys do emerge from the floor when you first look at them. That was weirdly easy to implement. Kind of a zombie gimmick, applied to them for now because they're the right height for it. There will eventually be more enemy types, if only because an "invisible" spectre-style monster would also be weirdly easy to implement. It'll change the palette.
You'd need cc65 installed. I'm using v2.18, but the newer versions should work the same. In Windows you might as well install Windows Subsystem For Linux and use "make". But if you wanted to do it manually, or create a batch file, there's only four commands in the makefile. They're in reverse order. cc65 goes from slaughter.c to slaughter.s, ca65 goes from slaughter.s to slaughter.o, ca65 goes from reset.s to reset.o, and ld65 combines the .o files into slaughter.nes.
... I forgot to include a ROM in the update, didn't I. Lemme fix that real quick.
Ha ha, yeah, splitscreen multiplayer would be ridiculous.
So anyway the splitscreen multiplayer is jump-in co-op with friendly fire. Press Start on controller 2.
Originally this response was going to be within a day of your comment, because this project is so ass-backwards, I figured it would take about three hours to implement side-by-side splitscreen. I was wrong. It took five. Literally one night is all I needed to have two independent viewports, because all column-pairs are independently raycast. Even the FOV is maintained... so I guess this NES game is set for anamorphic 16:9. What I've been doing for the last two weeks, instead of delivering that 'ha ha so anyway' punchline, is finding ways the game breaks if it tries counting to two. If you picture Sideshow Bob in that field of rakes, it's basically that, with more swearing.
I spent an entire day reverse-engineering my own view-weapon code so that removing bits from a byte would make bullets disappear when they hit something. Rendering the other player took literally fifteen minutes. There's a reason all programmers are Like That.
Though if you're playing on PC you'll probably want Mesen to add scanlines after NMI. Single-player performance has been significantly improved. Splitscreen performance needs work.
This game got robbed, placing only sixth.
Personally I picked the shotgun and had no issue with the first few bases. (Using a controller, as well. Didn't know about mouse support until just now. Didn't know the NES had a mouse until just now.) Some of that's from playing Irritating Ship first. Some of that's from having seen Babylon Five dogfights.
Switched to the autocannon for the asteroid field. That fight proves the controls are not the issue. I mean. Side thrusters would be immensely useful - especially when enemy shots keep pushing you away, and you're pointed at the base, to fire (A gun that shoots out the side(s) would do about the same thing.) A no-brainer "slow down" button would make late corrections massively easier. (And could be discouraged by spinning the player randomly while doing it.) But I think complaints about the physics could be solved by a seemingly unrelated change:
Move the camera.
The draw distance is so short, even when travel and combat demand high speed. Some bases can barely be kept onscreen. Several times, I was fighting a pixel in the minimap, which itself is not especially wide. The player's not even centered in the playfield. They're centered onscreen. So anything "north" of you is hidden by four additional rows of HUD.
Displacing the camera by a fraction of the player's velocity vector would provide precious milliseconds of reaction time and improve spatial awareness. Even when stationary, it could be centered ahead of you, to better see what you're shooting at.
Displacing the player would be simpler. Same vector, opposite direction, looks about right. But it would undermine how good the starfield looks. Those dots do a fantastic job of conveying movement. "You didn't notice it... but your brain did." (I didn't even think about tilemap versus sprites until the fourth base.) Keeping the effect locked to the player makes any forward-looking camera feel like it's sampling from a larger screen. Turning around, looks around.
Leaning into that subtly excellent feature: the deepest layer could have constellations. Tighter clusters of stars, in distinct patterns, allows orientation more natural than glancing at two numbers.
And those arrow enemies... I have nothing clever. Please nerf.
This is a fantastic demo, and a few tweaks away from forming a killer game. The controls are about perfect. The ability to strafe was such a pleasant surprise. Levels are enormous for this platform. The intro overview shows that off, and provides guidance.
The simplest change that would make the biggest impact is more color. There's colored elements in the background - you've obviously handled scrolling that in. Tinting whole rooms, or decorating them with similarly-colored objects, would make navigation more natural. It would improve the at-a-glance identifiability of the already-present variety.
Enemies, same story. They have a great range of behaviors. The cliche of palette-swapping to show which thing they do is cliche for a reason. Having to guess whether a turret is going to fire diagonally, or even come at you, is holding back the combat, when movement and shooting are this tight.