In order to create Commando 2084, I had to disassemble Commando. My original intention was to patch what was only needed and stop there. But I got carried away and I ended up analyzing and commenting the entire Commando code.
The commented code (that can be recompiled to generate the exact original binary) is here:
Findings – Level 2
Apparently, the original idea was to ship Commando with 4 levels instead of 3. There is a lot of code/data that indicates that a “level 2” (the levels that are shipped with Commando are level 0, 1 and 3) was in progress, or even finished.
All the actions, charset-mask, trigger rows are present. What’s missing is the map and a partial charset. The charset used for the main screen is likely the one designed for level 2.
Speculation from my part, “Level 2” was probably removed due to lack of time (?) or due lack of RAM to create a single-load game (?).
For more info about this level, search for “LVL2” in the main.asm
.
Map and actions
- Each level consist of 40×200 map.
- Each level consists of:
- Charset unique for the level: LVL0 at
$C000
, LVL1 at$C800
, LVL3 at$D800
- Each char in the charset contains a mask that indicates the background priority. See
LVL0_CHARSET_MASK_TBL
in main.asm. - A map: LVL0 is at
$6000
, LVL1 at$8000
, LVL3 at$A000
- List of actions: each action represents a sprite that must be created. But an action can perform non-related sprite tasks as well, like opening a door. See
LVL0_ACTION_TBL
in main.asm. - List of rows: when the master row index (see
V_SCROLL_ROW_IDX
) matches this number, the associated action is executed. SeeLVL0_TRIGGER_ROW_TBL
in main.asm. - Actions can receive an optional “sprite X position” argument. See
LVL0_SPRITE_X_LO_TBL
andLVL0_SPRITE_X_HI_TBL
in main.asm
- Charset unique for the level: LVL0 at
Sprites
The game supports up to 16 virtual sprites.
- Sprite 0 is used for the hero
- Sprites 1-3 are used for hero’s bullets
- Sprite 4 is used for hero grenades
- Sprites 5-15 are used for enemies, including its bullets/grenades.
Each sprite has:
- X and Y coordinates (including X LSB and X MSB)
- Sprite frame (which sprite to render).
$FF
is an empty frame. - Sprite color
- Background priority (e.g: should it be rendered behind a tree?)
- Animation type (see below). If it is
0
, it means it is an empty sprite. - Extra variables that are used for different things. Varies from anim type to anim type.
Some enemies, like the motorcycle, take two sprites.
Animation type
An animation represents what the sprite should do during the game. For example, the animation type TYPE_ANIM_SOLDIER_BULLET
, animates the bullet. See TYPE_ANIM_TBL
in main.asm for the complete list of animation types.
An sprite can change its animation in runtime. For example, the TYPE_ANIM_SOLIDER_BEHIND_SMTH
has certain logic. But when the hero is at the same Y position as it is, then it changes its animation to TYPE_ANIM_SOLDIER
. Similar, the TYPE_ANIM_SOLDIER_JUMPING
animates the “jumping” solider. But when the solider lands, it changes its animation to TYPE_ANIM_SOLDIER
. Those are only two examples, but most animations change its animation type in runtime.
Animation type 0
When a sprite goes out of bounds, or an enemy dies, or an animation ends, that sprite is set with animation type 0
, which is the TYPE_ANIM_SPAWN_SOLIDER
.
What this animation does, is to create random enemies. And when an enemy is created, it changes in animation type to something different than 0.
That means, that when there map is “full” of enemies (all animation types are different than 0
), the TYPE_ANIM_SPAWN_SOLDIER
is not called. And when there many empty sprites, it gets called more often.
Collision detection
Collision detection is done in software (no hardware collision detection is used).
There is a routine to check whether the hero is hit (see CHECK_COLLISION
) and another for enemies (see TYPE_ANIM_HERO_BULLET
, TYPE_ANIM_HERO_GRENADE_END
).
Each animation type has contains a mask (see f2544
) that indicates whether it can collide with bullet, grenades, both or nothing. For example, when the soldier is in the trench, it can only be killed with a grenade.
Personal thoughts
From a high-level (architecture) point of view, the code is very well designed. It is pretty easy to add new types of actions, or create new levels, or modify existing ones with little change in the code/data.
From lower-level point of view, it seems that parts of the code could be improved (see FIXME
in main.asm), specially regarding performance and flickers. Seeing many Level-2 traces, plus seeing certain bugs makes me think that the development team was under pressure to release the game ASAP (something fairly common in the gaming industry).
Additionally, it seems that the assembler used didn’t optimize the code to use zero page. For example, calls to:
STY $FB,Y
are assembled to:
STY $00FB,Y ;3 byte variation, instead of the two-one.
The removal of level 2 was documented in a Zzap!64 magazine’s interview with, IIRC, the musician, Rob Hubbard. The music and SFX code was 5KB so the programmer (Chris Butler) had to hurredly rip out Level 2 to fit in the music. He’d had just 6 weeks to write the game in order to have it released in time for Christmas 1985.
Thanks for the info. Thanks explains many things.
Have you seen the Commando Arcade release? It has the full 8 levels https://csdb.dk/release/?id=130973&show=summary#summary
Yes. I’ve seen it.
Isn’t the part where you said “FIXME: Loop tried twice, probably a bug. Remove” a sorting routine? I think it’s like when loop is open, you sort twice. Or maybe I got it wrong. It’s been many years since I haven’t interacted any code like this.
It is trying to find an empty slot to create a new sprite, twice. Looks like a bug:
https://gitlab.com/ricardoquesada/c64-commando-2084/blob/orig/src%2Fmain.asm#L2195
Are you planning on disassembling any other C64 games? I would love to know the process or who I could reach out for help disassembling a different game.
I plan to disassemble a few more, but not in the short term.
The tool I used is this one: https://csdb.dk/release/?id=149429