546 - bombShield(int a, int script)


Sets the bomb invulnerability flag (a=0 false, a=1 true), the caller's ANM script will change to script when a bomb is active.

611 - etEx(int etId, int async, int type, float a, float b)


Adds bullet transformation to bullet manager etId, Transformation of flag type determines the specific behaviour of the bullet. Documentation can be found here.

BulletEffectType-67108864 - etExDelay(string et, string isAsync, string time)


Hide the bullet for time frames. During this time, it will not move, will be invisible and won't have a hitbox. If used as a first transformation, can be used to delay when the bullet actually appears.

6 - isDelayedSpawn


While this flag is set, the bullet will be invisible and not have a hitbox. This flag is cleared once the delay timer ends.

4 - short_timer


Integer value between -1 and 15 inclusive (by default -1). If non-negative, shooters using short_timer shoot their bullets and timer increments by 1 per frame until 15 is reached. Once 15, timer reset to -1 or 0 depending on Z key state.

5 - long_timer


Integer value between -1 and 120 inclusive (by default -1). If non-negative, shooters using long_timer shoot their bullets and timer increments by 1 per frame until 120 is reached. Once 120, timer reset to -1 or 0 depending on Z key state.

503 - flagClear(int n)


Clears flag(s) according to n. Refer to the flag table here for exact values.

91 - floatTime(int slot, float var, int time, int mode, float start, float final)


In time frames using mode mode, variable var changes from start to final. slot is used to set the slot to be used by this ins, every enemy has 8 slots.

-9980 - F1


Local float variable, inherited by spawned enemies.

-9984 - I1


Local integer variable, inherited by spawned enemies.

-9989 - ANGLE_PLAYER


Angle from the enemy to the player.

-9962 - BOSS_Y


Final Y position of the boss.

-9990 - PLAYER_Y


Player's Y position.

-9963 - BOSS_X


Final X position of the boss.

-9991 - PLAYER_X


Player's X position.

81 - circlePos(float varX, float varY, float angle, float radius)


Performs following operation: varX = cos(angle) * radius and varY = cos(angle) * radius

-9981 - F0


Local float variable, inherited by spawned enemies.

300 - enmCreate(string sub, float x, float y, int hp, int score, int item)


Creates an enemy using subroutine sub at coordinates (x, y) (relative to position of the parent), health of created enemy is hp, score bonus is score and item drop is item.

601 - etOn(int etId)


Shoots bullet(s) using properties from bullet manager etId.

23 - wait(int time)


Stops sub execution for time frames.

1 - delete()


Returns to the top of current call stack.

2 - GAME_SPEED


The value controls by how much faster the game speed is run. This variable is often set to a number between 0 and 1. This value is often changed when a (final) boss dies or when a photo is taken. Its default value is 1.

-9985 - I0


Local integer variable, inherited by spawned enemies.

-9983 - I2


Local integer variable, inherited by spawned enemies.

-9982 - I3


Local integer variable, inherited by spawned enemies.

-9954 - ENEMY_HP


Enemy's current HP.

3 - goheiExists


A flag that determines if a gohei exists on-screen or not. Flag is set to true by active gohei. Flag is set to false if gohei goes off-screen or is despawning.

Item Collection Bonus Persisting


Specifications


  • Versions: 1.00a - 1.00b
  • Difficulty: Easy - Normal - Hard - Lunatic - Extra
  • Mode: Main game - Practice mode - Spell practice
  • Shottype: ReimuA - ReimuB - MarisaA - MarisaB - SakuyaA - SakuyaB

What happens


It is possible for the player to continuously increase item count for the point of collection bonus while not actually activating the bonus.

How it happens


For this bug to happen, the player must be at a very precise y-coordinate. The y-coordinates differ per character since Marisa has a lower PoC height. In particular, the PoC heights are as follows:

  1. Reimu or Sakuya: poc_height = 128
  2. Marisa: poc_height = 148

As such, this is mainly feasible to replicate when normalizing the position, meaning the player would be aligned with the top/bottom of the screen and only move vertically to ensure the player y-coordinate is exactly equal to the poc_height

A screenshot depicting the point of collection heights for Reimu, Marisa and Sakuya.
Figure 1: Two colored lines ressembling the exact height at which this bug takes place in. The lines represent the point of collection height. The blue color is the height for Reimu and Sakuya. The orange color is the height for Marisa.

Why it happens


To understand this bug we must first understand the two mechanics that make up the Bonus mechanic:

  1. Bonus Timer: Timer that represents the time left before the player obtains the bonus.
  2. Bonus Item Count: Counter that keeps track of how many items have been collected for the bonus.

Bonus Timer


The Bonus Timer is a value between 0 and 8 inclusive. This timer represents the time left before the player obtains the bonus. Its default value is 0. The Bonus Timer is set to 8 when it meets ALL the following criteria:

  1. player_height >= poc_height: the player must be above or at the PoC height.
  2. player_height != poc_height: the player must not be at the PoC height.

I wrote the list in this exact way because this is how it is represented in the code (for technical readers, assembly code can be found at offset of 0x43886a in 1.00b). Nonetheless, both conditions can be simplified to the following: player_height > poc_height.

The Bonus Timer remains at its maximum value of 8 as long as there is at least one item in the AUTO_COLLECT state. When an item is in the AUTO_COLLECT state, it flies straight towards the player. This state can be triggered through means such as bombing or going above the PoC height.

The Bonus Timer decreases by 1 every frame if there are no items in the AUTO_COLLECT state. When the Bonus Timer reaches 0, the player receives the Bonus.

Bonus Item Count


The Bonus Item Count increases when at least one of the following conditions are met:

  1. player_height >= poc_height: the player must be above or at the PoC height.
  2. item_state == AUTO_COLLECT: the item must be in an autocollect state.

Additionally, the counter only updates if any of the following three items are picked up: small power item, point item, or big power item. When an item is picked up, the counter increments by 1. This counter is reset to 0 when the bonus is triggered as described in the previous section. The player receives a different Score Bonus depending on the Bonus Item Count.

The difference


It is immediately noticeable that there are two differing conditions. The Bonus Timer only starts ticking when player_height > poc_height, but the Bonus Item Count increases when player_height >= poc_height, so the difference between the two statements is player_height == poc_height. When player_height == poc_height, the Bonus Timer remains at 0, but the player can still increase the Bonus Item Count, assuming that all items have item_state != AUTO_COLLECT



Replays


This section has no content yet. Would you like to add to this section? Contact me if you are interested!

Videos


This section has no content yet. Would you like to add to this section? Contact me if you are interested!