> For example, if you know the carry flag will always be clear at the jump point, and if the jump distance is within branching range, you can replace JMP with BCC.
However if the BCC crosses a page boundary it'll take 4 cycles, one cycle longer than a JMP.
I love the stacking of boolean ops before branches, too.
It's a fascinating contrast to the modern 'move fast and break things' approach. Back then, if your routine was 3 cycles too slow, the sprite didn't just 'lag'—the entire raster effect collapsed. There was a level of deterministic discipline that we've largely abstracted away in favor of developer velocity.
It’s fascinating that in 2026, we’re needing more and more powerful hardware just to keep up with the bloat of basic applications, whereas the Seawolves devs were finding ways to squeeze 'art' out of 64 kilobytes.
In software, the 'cheaply made coat' equivalent (bloated frameworks, unoptimized dependencies) creates a massive technical debt that doesn't just affect the buyer—it affects the entire ecosystem's energy consumption and hardware requirements. The Seawolves devs weren't just saving money; they were respecting the constraints of the medium. When we treat resources as infinite because they are 'cheap,' we stop being engineers and start being assemblers.

With the release of my first ever commercial game on the Commodore 64, Seawolves, I thought it might be of interest to the coders among you as to how the game was constructed.
From the outset, brace yourself to read about some "code less travelled", as the game required several strange or quirky methods that are perhaps more associated with the madness that goes in the demo scene.
Let’s check them out in turn.
I first combined NMIs and IRQs inside a game environment in Parallaxian and again in The Wild Wood, to great effect, because it offers the following benefits:
If the foregoing sounds horrendous and esoteric, I can only apologise for making it thus through poor explanation skills, but really, it boils down to giving the developer a more coder-friendly way of slicing the screen up into horizontal layers that collectively form a useful game environment.
NMIs are timer interrupts, meaning that unlike IRQs, they can't be triggered by $D012 on the VIC-II chip, but instead are controlled by either of the two timers on CIA chip #2 (likewise, timer IRQs can be set up using either of the 2 timers on CIA #1).
The timers hold the number of cycles between each NMI instance in the form of a lo-byte, hi-byte 16-bit number stored in $DD04 + $DD05 (for timer A) or $DD06 + $DD07 (for timer B).
Those cycle counts are referred to, unhelpfully, in the Commodore 64 Programmer's Reference Guide as "frequencies".
Critical to setting up NMIs is a consistent start cycle on the same scanline each time the game is initiated.
Like IRQs, NMIs can stall too, but the effect is different; whereas an IRST stall consists of a fleeting collapse in the IRQ schema, an NMI stall event looks more like a regrouping of all NMIs in the chain down-screen from their proper position and is caused either by (a) unanticipated cycle steal caused by the presence of sprites, which take priority over NMIs every bit as much as they do over IRQs, and (b) an NMI handler not completing its tasks before the next NMI is scheduled to fire.
You really have to use a spreadsheet to calculate the timer 16-bit number for each NMI instance, which is the only very awkward aspect of using them.
For more, see my in-depth guide to setting up NMIs.
The torpedoes are, fundamentally, sprites... but not in the usual sense.
This is because they are rendered in real time on a multiplexed blank "canvas" consisting of a column of 8 sprites, each split into 3 horizontal slices that I call "splites" (from "split sprite").
Each splite is 7px deep (because 3 x 7 = 21 = the height of a standard sprite).

Thanks to an interrupt firing every 7 lines during the full vertical range of the torpedoes, each splite is assigned its own unique x-position (including, obviously, unique MSB value).

Next, we are free to render (in real-time) 7px high (or smaller) torpedo shapes on the blank sprite gfx data of the splites, but we have to ensure that there is a minimum of 7px (i.e. 7 scanlines) of vertical space between each torpedo.
This is to prevent ugly artefacts when the rendered torpedo moves from one splite to another.
We also have to ensure that when a torpedo is crossing between splites, both affected splites share the same x-position, to keep smooth continuity.

The diagram below shows how 8 sprites are vertically stacked to produced 24 splites.

Finally, here is a short video of me trying to explain the entire splite concept:
Your browser does not support the video tag.
After the splite torpedo is moved up by 1px, instead of wiping the last line of the torpedo's gfx data, we leave it alone so that a vertical trace is formed on the canvas.
Then we have a routine that scans all the canvas data for trailing traces and gradually makes them thinner until they vanish, so that we have a wake effect following each torpedo.
At the same time, we flicker the torpedoes in front of and behind the char data for the water blending in the foreground, to make the wakes looks more "frothy".
When a player submarine dies, instead of a clichéd explosion I thought it might be interesting to have it disintegrate under pressure.
To do that, the player sub is switched to hi-res mode and then we simply use some nice bit-shifting instructions to destroy the sub's displayed gfx data in real-time.

The same bit-shifting used for the real-time implosion (or rather, bit-rotating in this case) is used to make the distant waves on the sea animate.
This principle also applies, but in a vertical pattern, for the foreground rippling of the water, an effect that I modelled on the "get ready" screens of Ecco the Dolphin.
With the foreground ripples, there is also a horizontal component performed by using $D016 to rock left and right at varying rates.
It's all very simple stuff, but I think it works well in the game!
The very first special effect developed for Seawolves was a real-time water distortion effect for submerged or partially submerged objects in the foreground.
This was done simply through short vertical bands in which the y-expand of the affected sprites was activated, and enhanced by wobbling said bands up and down.
For a time during development this was taken further and full y-expand sprite stretching was used (i.e. more than 2x vertical expand), but the gains were minimal and too resource-hungry to be justified for the final game.

If you have experience moving multiple sprites vertically in the play area, at some point around mid-screen you may have run into the problem of a bad line leaving you with insufficient CPU cycles to render your sprites on time (I won't attempt to explain the problem further as you either know from experience what I am referring to or not).
To circumvent the issue, Seawolves performs one line of FLD, that is, it stalls the bad line at the affected sprite y-position so that the scheduled bad line then occurs on the next scanline, leaving us time to render our sprites (in this case, the "splite" columns).
However, this has the undesired side effect of shunting the characters on the screen below that point downwards, so on the very next scanline we have to compensate with a one line vertical shift back upwards again using Y-Scroll on $D011.
Even if we had enough RAM to have unique sprite definitions for all the animation frames of the enemy ships and the player subs, I still would not have done it that way.
Rather, it is far more RAM-efficient to stream-in the gfx data for the turning radars on the spy ship (for example), or the spray on the hydrofoil, or the helicopter rotor blades, etc.
This means that we only need predefined gfx for the parts of the sprite definition that get changed, not the whole sprite.
When the player subs change direction, they too are swiftly redrawn "in the blink of an eye" to their mirrored counterpart (except, if you look carefully, they are not literally mirrored, as that would make the lighting inconsistent and we can't have that!)
Again and again in the code for Seawolves, there are cases where a subroutine is only executed if multiple "logic gates" permit it.
You can do this the slow and ugly way by LDA CONDITION_n for n conditions with a branch instruction on the next line, or you can use the logical operators to save CPU time and RAM by stacking them as LDA CONDITION_0 then ORA CONDITION_1 to n, for n conditions with one shared branch instruction at the end.
Not understanding what I mean?
Check out my blog article on the subject, ORA: A Special Use in Branch Testing.
Often in 6502 coding you will want to jump ahead by a few bytes and the popular way to do that is JMP $****.
However, you can save 1 byte of RAM by using the branch instructions instead, as long as you know which flag(s), if any, are guaranteed to be on or off at the jump point.
For example, if you know the carry flag will always be clear at the jump point, and if the jump distance is within branching range, you can replace JMP with BCC.
The above are just some of the methods used in Seawolves to make the game come to life, and I left out the parallax scrolling on the "sea mist" levels as well details of the bespoke SFX player, which was developed with deployment in Parallaxian in mind also.
Despite its simple looks, Seawolves has very technical inner workings that might not be that normal outside of the kind of crazy stuff that the demo scene lunatics produce.
There are some other little tricks hidden away in the code, but I can leave that for others to discover at a later stage, so for now I hope the foregoing has been of interest to those of you with a coder mindset!
Finally, if you have not yet bought the game, it would be nice support for the developer (i.e. me!) if you did... it only costs £4.99 and yes, I know everyone expects stuff for free on the C64 these days, but this game was not knocked out in a few weeks by any means, as hopefully should be obvious when you play it.

PS - If you value my work and want to support me, a small donation via PayPal would be nice (and thanks if you do!)