DoDonPachi Reverse Engineering Notes

Unless otherwise states, all addresses refer to the ddonpachj romset.

Hitboxes

P1 hitbox is stored at 0x102c9c, P2 hitbox is stored at 0x102cdc. This player ship hitbox is 7x6 pixels and shrinks down to 7x4 when moving horizontally.

All enemy bullets have the same 0x0 hitbox.

Rank

DoDonPachi has a rank mechanic which affects the difficulty of some enemies and bosses. This is stored as a single byte at address 0x101968, while the code that computes it is implemented at address 0x3362.

The algorithm that calulates the rank is written as follows:

rank = 0;
for (each player in game) {
    if (player.is_shot_type) {
        rank += 2;
    }
    rank += player.ship_type;  // (A = 0, B = 1, C = 2)
    rank += 2 * player.shot_power;
    rank += player.laser_power;
    rank += current_stage_index;  // (stage1=0, ..., stage6=5)
}
if (both_players_are_in_the_game) {
    rank = rank/2;
}
rank += 2 * player1.lives_count;
rank += 2 * player2.lives_count;
rank += bees;
rank += rank_by_stage[current_stage_index];
rank += frames_since_last_death / 2048;  // (~ 2 * minutes since last death)

if (second_loop) {
    rank += 32;
}
if (rank > 63) {
    rank = 63;
}

Where rank_by_stage is one of the following 4 arrays:

rank_by_stage_easy = [ 8, 10, 12, 14, 16, 18, 20, 22 ]
rank_by_stage_normal = [16, 20, 24, 28, 32, 36, 40, 44 ]
rank_by_stage_hard = [32, 38, 44, 50, 56, 62, 68, 74 ]
rank_by_stage_very_hard = [48, 56, 64, 72, 80, 88, 96, 104]

Thus excluding constants related to current stage and player ship, the variables that affect rank (and their addresses) are:

The variables which affect the rank which cannot be manipulated are:

A few points to notice:

Slowdown

Slowdown in dodonpachi is caused by the game main game loop code (0xa92~0xbf2) failing to meet the 17ms deadline imposed by the vblank interrupt.

The main game loop is synchronized with the rendering code at 0x56dd6, via a semaphore stored at 0x100f44, meaning that each iteration of the main loop of the game ("frame") has to wait for a vblank interrupt before continuing. When one iteration of this loop takes longer than 17ms, the blitter circuitry will render the same frame twice. Naturally this means that each each frame can only stay on the screen for an amount of time which is a multiple of approximately 17ms (the time between 2 vblank interrupts).

The duration of a frame depends on the amount of computation that the game does during that frame. Spawning, updating, and checking collisions between entities are the main causes of slowdown, because the necessary computation time depends on the number of such entities.

The game is naturally "busier" on odd frames (for example, some sprites are only drawn on odd frames to give them the appearance of being transparent). Moreover, P1 collisions are only checked on odd frames (0xbba), while P2 collisions are only checked on even frames (0xbe6). This means that playing on P1 side will naturally have more slowdowns, since player bullets and enemy bullets collisions will be processed on the frame when the extra sprites are drawn.

Hibachi Slowdown

During the final pattern of Hibachi (0x3ee96), the game has a 50% chance to run 25% slower when player on the P1 side.

Hibachi's final pattern spawns 25 bullets at once every 5 frames. When these new bullets are spawned, the game has to allocate their memory and compute their trajectories, which typically means that this frame will miss the vblank deadline and therefore will be rendered twice. Since this happens once every 5 frames, this means the game runs at most at 83.3% speed during this pattern.

Every 2 frames since when it spawned, Hibachi will spawn a decorative particle (at 0x3e6a2). The highest amount of slowdown is obtained if Hibachi spawns on an odd frame and P1 is playing: the game will have to process collisions, draw transparent sprites and spawn Hibachi's particles all on the same frame, thus with high likelyhood that frame will miss the vblank deadline.

To be precise, with Hibachi stuck in the middle of the screen, P1 positioned in the middle of the bottom of the screen and constantly shooting a full-powered laser at Hibachi, 75% of even frames when Hibachi is not shooting will miss the vblank deadline. Since 1 out of 5 frames misses the vblank deadline due to Hibachi shooting, then half of all frames will miss the vblank interrupt (.5 = .2 + .75×.8×.5) causing the game to run at 66.7% of the normal speed in this worst case scenario.

Stage 2-3 midboss bug

Stage 2-3 midboss AI consists in launching 4 bullets forward (the large ones, type 13, constructed at 0x55164). Then, those bullets explode, which causes the emission of a lot of random bullets (type 11, constructed at 0x5515c).

The function that shoots both these bullet types (0x50a24) shoots 1 bullet in the first loop, but 2 in the second. In the second loop, it checks a flag of the enemy that shot them (0x509c6) to decide if it needs to give the bullets either 2 different angles (0x509cc) or 2 different speeds (0x50974).

Now, when "type 13" bullets are shot, the enemy that shot them is obviously the midboss, and this makes them go at 2 different speeds (notice that there are 4 "type 13" bullets, double than in the first loop, and they are shot at 2 different speeds).

However, when "type 11" bullets are shot out of the "type 13" bullets, they did not keep a reference to the enemy that shot them. When 0x509c6 attempts to dereference a pointer from address A5+6 = 0x100e48, which actually contains information about the P2 laser startup animation. This is going to be 0 when playing as P1, and some number between 0x400 and 0xa00 when playing as P2. In the end, on P2, this causes each pair of "type 11" bullets to come out with 2 different angles but with the same speed. In P1, they come out with the same angle, but with 2 different speed.

Notice that on P1, the fast bullets are always traveling in the same direction of a slower bullet which is behind them.

Undeterministic RNG

The function at 0x56dd6~0x56e2b is used to wait for a vblank ISR between one frame and the next. It is used to prevent the game state from updating while the screen is rendering, therefore preventing tearing (sort of like vsync in a modern game).

While the game waits for vblank, it repeatedly generates random numbers, thus incrementing the randomness seed (0x56df4). Since on the original DoDonpachi PCB, VBLANK is derived from the 28MHz crystal while the CPU clock is derived from the 16MHz crystal, over time these two signals will naturally desynchronize, making the RNG "undeterministic". This is because the number of CPU clock periods between 2 vblank interrupts is not constant; causing the number of times the RNG is incremented while waiting for vblank to be impossible to predict accurately.

More generally, the RNG in DoDonPachi is different from PCB to PCB, and even external factors such as temperature can affect the sequence of randomly generated numbers.

This is especially problematic when wanting to create a "cycle accurate" emulator; or when creating a TAS for DoDonPachi: the TAS would work perfectly on an emulator but would surely desynchronize on a real PCB.