boss gemini helmet glitch documentation
This commit is contained in:
parent
eb94c2a0cd
commit
a6cb97d08d
87
docs/Bugs.md
87
docs/Bugs.md
|
@ -65,4 +65,89 @@ Extracted sound sample: [sound_ff.mp3](attachments/sound_ff.mp3?raw=true)
|
|||
|
||||
Note that the boss defeated audio (`sound_55`) is still played because the enemy
|
||||
defeated routine is set to `boss_ufo_routine_09` (see
|
||||
`enemy_destroyed_routine_05`). `boss_ufo_routine_09` plays `sound_55`.
|
||||
`enemy_destroyed_routine_05`). `boss_ufo_routine_09` plays `sound_55`.
|
||||
|
||||
# 3. Level 4 Boss Gemini Vulnerability
|
||||
|
||||
Under normal circumstances, when the level 4 indoor/base boss gemini helmet
|
||||
(enemy type = #$1c) splits into 'phantoms', then they don't take damage. Only
|
||||
when the helmet re-combines is it vulnerable to damage. However, `Mr. K`
|
||||
[researched](http://www.youtube.com/watch?v=hL1BMFRt6aA) a glitch to find that
|
||||
if a player's bullet collides with the helmet at just the right frame, then when
|
||||
the helmet separates into two, it can still take damage.
|
||||
|
||||
The reason this happens is because the boss gemini uses `ENEMY_ANIMATION_DELAY`
|
||||
to know when to be vulnerable or when to be invulnerable.
|
||||
`ENEMY_ANIMATION_DELAY` specifies how long for the helmet to stay still when
|
||||
merged, or when at the farthest distance apart. The timer is set to #$20 when
|
||||
farthest apart, and #$30 when merged. If the helmets are moving (either toward
|
||||
each other or away), the value will be #$00.
|
||||
|
||||
When merged and `ENEMY_ANIMATION_DELAY` is 2, the helmet is not moving, but
|
||||
about to start separating. The value will be decremented to 1. This logic
|
||||
happens in `boss_gemini_routine_02`. After `boss_gemini_routine_02` runs,
|
||||
`bullet_enemy_collision_test` is executed to check for a bullet to enemy
|
||||
collision. If during this frame, a bullet hits the gemini, then the
|
||||
`ENEMY_ROUTINE` is updated to `boss_gemini_routine_03`. This is known as the
|
||||
enemy destroyed routine and it will be executed in the next frame. However,
|
||||
boss gemini is special, in that it isn't automatically destroyed in the
|
||||
destroyed routine. Instead, unless boss gemini doesn't have any more health
|
||||
(`ENEMY_VAR_4`), the routine will decrement `ENEMY_ANIMATION_DELAY` to 0 and
|
||||
set back to `boss_gemini_routine_02` to be called the next frame.
|
||||
|
||||
Now when the next frame executes and the `boss_gemini_routine_02` routine is
|
||||
run, `ENEMY_ANIMATION_DELAY` is 0 and game thinks that the code that makes the
|
||||
helmet invulnerable has already been executed when it hasn't!
|
||||
|
||||
In short, the bug happens because for the special time when
|
||||
`ENEMY_ANIMATION_DELAY` is decremented from 1 to 0, the code should make the
|
||||
helmet invincible by calling `disable_enemy_collision`. However, if you time
|
||||
the bullet collision to happen on the frame when `ENEMY_ANIMATION_DELAY` is 2,
|
||||
then the regular game code will decrement the timer to 1, and then next frame
|
||||
will have a different routine (`boss_gemini_routine_03`) set the timer to
|
||||
0, but that routine doesn't call `disable_enemy_collision`, leaving the helmet
|
||||
in a vulnerable state.
|
||||
|
||||
Interestingly, if you take advantage of this bug, then you can exploit the same
|
||||
logic mistake when the helmet is not moving at the edge of the screen, before it
|
||||
starts merging. If a bullet collides with the helmet right when
|
||||
`ENEMY_ANIMATION_DELAY` is 2, then the helmets will remain vulnerable while
|
||||
merging.
|
||||
|
||||
```
|
||||
; ----- Frame 1 -----
|
||||
boss_gemini_routine_02:
|
||||
...
|
||||
lda ENEMY_ANIMATION_DELAY,x ; ENEMY_ANIMATION_DELAY = 2
|
||||
beq @calc_offset_set_pos ; branch doesn't occur
|
||||
dec ENEMY_ANIMATION_DELAY,x ; ENEMY_ANIMATION_DELAY = 1
|
||||
bne @set_x_pos ; helmet still not moving, branch
|
||||
...
|
||||
rts
|
||||
|
||||
...
|
||||
|
||||
bullet_enemy_collision_test:
|
||||
...
|
||||
jsr bullet_collision_logic ; set boss gemini routine `boss_gemini_routine_03`
|
||||
|
||||
; ----- Frame 2 -----
|
||||
boss_gemini_routine_03:
|
||||
lda ENEMY_ANIMATION_DELAY,x ; ENEMY_ANIMATION_DELAY = 1
|
||||
beq @continue ; branch doesn't occur
|
||||
dec ENEMY_ANIMATION_DELAY,x ; ENEMY_ANIMATION_DELAY = 0
|
||||
...
|
||||
lda #$03 ; a = #$03
|
||||
jmp set_enemy_routine_to_a ; set enemy routine index to boss_gemini_routine_02
|
||||
|
||||
; ----- Frame 3 -----
|
||||
boss_gemini_routine_02:
|
||||
lda ENEMY_ANIMATION_DELAY,x ; ENEMY_ANIMATION_DELAY = 0
|
||||
beq @calc_offset_set_pos ; branch skipping disabling of collision!!
|
||||
dec ENEMY_ANIMATION_DELAY,x ; skipped!
|
||||
bne @set_x_pos ; skipped!
|
||||
jsr disable_enemy_collision ; skipped!
|
||||
|
||||
@calc_offset_set_pos:
|
||||
...
|
||||
```
|
|
@ -523,25 +523,33 @@ The health of the boss gemini helmets are #$01 and each hit 'destroys' them.
|
|||
However, the destroyed routine `boss_gemini_routine_03` will check `ENEMY_VAR_4`
|
||||
for the boss gemini helmet's actual HP.
|
||||
|
||||
Note that this enemy uses `ENEMY_Y_VELOCITY_FRACT` and `ENEMY_Y_VELOCITY_FAST`
|
||||
not for anything with the y velocity, but rather to control speed of x movement
|
||||
and keep track of x distance from initial position respectively.
|
||||
|
||||
* `ENEMY_FRAME` - offset into `boss_gemini_sprite_tbl`, which contains the exact
|
||||
sprite code: `sprite_68`, `sprite_69`, `sprite_6b`, `sprite_6c`.
|
||||
* `ENEMY_VAR_1` - initial x position
|
||||
* `ENEMY_VAR_2` - timer after being hit - #$00 down to #$00
|
||||
* `ENEMY_VAR_2` - timer after being hit - #$10 down to #$00
|
||||
* `ENEMY_VAR_3` - whether or not the boss gemini's health is low (less than
|
||||
#$07). Used to show a red brain instead of a green one.
|
||||
* `ENEMY_VAR_4` - actual representation of ENEMY_HP
|
||||
* `ENEMY_X_VELOCITY_FRACT` - always #$80 (.50). Used with
|
||||
`ENEMY_Y_VELOCITY_FRACT` to move gemini by 1 every #$02 frames
|
||||
* `ENEMY_X_VELOCITY_FAST` - x direction of boss gemini
|
||||
* #$00 - boss gemini are travelling away from center
|
||||
* #$ff - boss gemini are travelling towards center
|
||||
* #$00 - boss gemini are traveling away from center
|
||||
* #$ff - boss gemini are traveling towards center
|
||||
* `ENEMY_Y_VELOCITY_FRACT` - alternates every frame between #$00 and #$80. Used
|
||||
with `ENEMY_Y_VELOCITY_FRACT` to move gemini by 1 every #$02 frames
|
||||
* `ENEMY_Y_VELOCITY_FAST` - offset from initial x position. Either added to or
|
||||
subtracted `ENEMY_VAR_1` based on whether the frame is even or odd. Always
|
||||
either #$00 or #$80
|
||||
subtracted `ENEMY_VAR_1` based on whether the frame is even or odd. Goes from
|
||||
#$00 to #$30
|
||||
* `ENEMY_HP` - always #$01 until hit by bullet. The 'enemy destroyed' routine
|
||||
will reset `ENEMY_HP` back to #$01 until `ENEMY_VAR_4` is #$00.
|
||||
* `ENEMY_ANIMATION_DELAY` - how long for the helmet to stay still when merged,
|
||||
or when farthest distance apart. The value is set to #$20 in game for when
|
||||
farthest apart, and #$30 when merged. If the helmets are moving (either
|
||||
toward each other or away), the value will be #$00.
|
||||
|
||||
### 1D - Gardegura
|
||||
|
||||
|
@ -718,7 +726,7 @@ simplify defining the enemy vars.
|
|||
* all other orbs - the running total of index into `dragon_arm_orb_pos_tbl`.
|
||||
Each farther orb has the next value. Then the orb's `ENEMY_VAR_1` is added to
|
||||
get position.
|
||||
* `ENEMY_VAR_1` - used in correlation wiht shoulder's `ENEMY_X_VELOCITY_FRACT`
|
||||
* `ENEMY_VAR_1` - used in correlation with shoulder's `ENEMY_X_VELOCITY_FRACT`
|
||||
to set position. `ENEMY_VAR_1` is the distance from the previous orb's
|
||||
position index.
|
||||
* `ENEMY_VAR_2` - duration timer for rotation direction. positive = clockwise,
|
||||
|
@ -797,7 +805,7 @@ No attributes exist for this enemy.
|
|||
|
||||
Appears randomly and creates flying saucers (enemy type - #$15) and drop bombs
|
||||
(enemy type - #$16). One of the few enemies that use BG_PALETTE_ADJ_TIMER to
|
||||
create a fadeing in effect.
|
||||
create a fading in effect.
|
||||
|
||||
#### Logic
|
||||
|
||||
|
|
|
@ -5514,8 +5514,16 @@ boss_gemini_routine_02:
|
|||
|
||||
@wait_delay_update_pos:
|
||||
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
|
||||
beq @calc_offset_set_pos ; branch to calculate new position if animation delay has elapsed
|
||||
dec ENEMY_ANIMATION_DELAY,x ; animation delay hasn't elapsed, decrement enemy animation frame delay counter
|
||||
; this specifies how long the helmets should freeze when merged or when really far apart
|
||||
; always #$00 when moving
|
||||
; !(BUG?) if a bullet collision with the boss gemini occurs in a frame when ENEMY_ANIMATION_DELAY is #$02
|
||||
; then the disable_enemy_collision method will not be called
|
||||
; and the boss gemini will be vulnerable until the next time it starts moving again
|
||||
beq @calc_offset_set_pos ; branch if moving, i.e. animation delay is #$00,
|
||||
; or animation delay has elapsed and helmets should start moving
|
||||
; to calculate new position
|
||||
dec ENEMY_ANIMATION_DELAY,x ; helmets are staying still, either merged, or really far apart
|
||||
; decrement enemy animation frame delay counter
|
||||
bne @set_x_pos ; if animation delay still hasn't elapsed, set position based on FRAME_COUNTER and ENEMY_VAR_1
|
||||
jsr disable_enemy_collision ; animation delay has elapsed and boss gemini are about to separate
|
||||
; prevent player enemy collision check and allow bullets to pass through enemy
|
||||
|
@ -5525,25 +5533,29 @@ boss_gemini_routine_02:
|
|||
clc ; clear carry in preparation for addition
|
||||
adc ENEMY_X_VELOCITY_FRACT,x ; load x fractional velocity. Always #$80 (.5)
|
||||
sta ENEMY_Y_VELOCITY_FRACT,x ; store result back into x position offset
|
||||
; this overflows every #$02 frames, causing ENEMY_Y_VELOCITY_FAST to increment
|
||||
; this overflows every #$02 frames, causing ENEMY_Y_VELOCITY_FAST (x position) to increment
|
||||
; every other frame
|
||||
lda ENEMY_Y_VELOCITY_FAST,x ; load x position offset (#$00 or #$80)
|
||||
lda ENEMY_Y_VELOCITY_FAST,x ; load x position offset from merge point (#$00 to #$30)
|
||||
adc ENEMY_X_VELOCITY_FAST,x ; add x direction (#$00 = away from center, #$ff = towards center)
|
||||
; this includes any overflow from previous addition
|
||||
sta ENEMY_Y_VELOCITY_FAST,x ; set x position offset
|
||||
sta ENEMY_Y_VELOCITY_FAST,x ; set new x position offset (#$00 to #$30)
|
||||
ldy ENEMY_X_VELOCITY_FAST,x ; load x direction (#$00 = away from center, #$ff = towards center)
|
||||
bmi @check_combined_set_x ; branch if boss gemini helmets are going to the center (combining), or have combined
|
||||
cmp #$30 ; see if x position offset is at maximum (#$30)
|
||||
bcc @set_x_pos ; branch if x position offset is less than max (#$30)
|
||||
lda #$20 ; x position offset is max, reset to #$20
|
||||
lda #$20 ; x position offset is max, set animation delay to #$20
|
||||
bne @set_delay_reverse_dir ; always branch to reverse direction
|
||||
|
||||
; phantom helmets moving toward center (merging)
|
||||
@check_combined_set_x:
|
||||
tay ; transfer x position offset to y
|
||||
bpl @set_x_pos ; branch if boss gemini haven't yet combined
|
||||
tay ; transfer x position away from merge point (#$00 to #$30) offset to y
|
||||
; x position will temporarily underflow to #$ff (-1)
|
||||
; this is when helmet freeze for ENEMY_ANIMATION_DELAY amount of time
|
||||
bpl @set_x_pos ; branch if boss gemini haven't yet combined, i.e. their offset isn't #$ff
|
||||
jsr set_enemy_y_velocity_to_0 ; boss gemini have combined to become solid, pause motion
|
||||
jsr enable_bullet_enemy_collision ; allow bullets to collide (and stop) upon colliding with boss gemini
|
||||
lda #$30 ; a = #$30 (delay when gemini is fused)
|
||||
lda #$30 ; a = #$30 (delay when gemini is not moving)
|
||||
; either merged or really far apart
|
||||
|
||||
@set_delay_reverse_dir:
|
||||
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
|
||||
|
|
|
@ -125,7 +125,7 @@ game_end_routine_tbl:
|
|||
.addr game_end_routine_04 ; CPU address $bae3 (music change and presented by Konami)
|
||||
.addr game_end_routine_05 ; CPU address $bb87
|
||||
|
||||
; set level to #$08 (ending routing)
|
||||
; set level to #$08 (ending routine)
|
||||
game_end_routine_00:
|
||||
lda #$08 ; a = #$08
|
||||
sta CURRENT_LEVEL ; set current level to 'level 9' (special ending level)
|
||||
|
|
|
@ -6859,14 +6859,14 @@ bullet_enemy_collision_test:
|
|||
cmp #$80
|
||||
bcs @next_bullet
|
||||
lda PLAYER_BULLET_OWNER,x
|
||||
jsr bullet_collision_logic
|
||||
lda PLAYER_BULLET_SLOT,x
|
||||
cmp #$05
|
||||
bne @set_routine_exit
|
||||
stx $08
|
||||
txa
|
||||
jsr bullet_collision_logic ; subtract enemy HP, play collision sound (if appropriate), award points
|
||||
lda PLAYER_BULLET_SLOT,x ; load bullet type + 1
|
||||
cmp #$05 ; see if laser
|
||||
bne @set_routine_exit ; branch if not laser
|
||||
stx $08 ; laser, store bullet slot number in $08
|
||||
txa ; move bullet slot number to a
|
||||
ldx #$00 ; x = #$00
|
||||
cmp #$0a
|
||||
cmp #$0a ; see if bullet slot number to #$0a
|
||||
bcc @continue_2
|
||||
ldx #$0a ; x = #$0a
|
||||
|
||||
|
@ -6879,8 +6879,9 @@ bullet_enemy_collision_test:
|
|||
bne @continue_2
|
||||
|
||||
@set_routine_exit:
|
||||
jsr set_bullet_routine_to_2 ; move to bullet routine 2 and reset PLAYER_BULLET_TIMER to #$06
|
||||
ldx ENEMY_CURRENT_SLOT
|
||||
jsr set_bullet_routine_to_2 ; move to bullet routine 2, which destroys the bullet (player_bullet_collision_routine)
|
||||
; reset PLAYER_BULLET_TIMER to #$06
|
||||
ldx ENEMY_CURRENT_SLOT ; restore x to the current enemy slot
|
||||
rts
|
||||
|
||||
; set PLAYER_BULLET_ROUTINE,x to #$02, which destroys the bullet (player_bullet_collision_routine)
|
||||
|
@ -6893,6 +6894,7 @@ set_bullet_routine_to_2:
|
|||
rts
|
||||
|
||||
; subtract enemy HP, play collision sound (if appropriate), award points
|
||||
; set enemy destroyed routine if HP is #$00
|
||||
bullet_collision_logic:
|
||||
sta $17 ; store PLAYER_BULLET_OWNER in $17 0 = p1, 1 = p2
|
||||
stx $11 ; backup bullet index in $11 for logic
|
||||
|
|
Loading…
Reference in New Issue