---
title: 'TH04 Reverse Engineering: DOS Bullet Patterns — REA'
url: https://rea.tools/showcase/th04/
---

[Home](https://rea.tools/) / [Showcases](https://rea.tools/showcase/) / TH04

Case study · Touhou 4: Lotus Land Story

# Reconstructing TH04's bullet patterns.

A ring of bullets starts with a short angle calculation. Use REA to
inspect the original DOS function, then follow its instructions into
readable C++.

PC-98 · 16-bit DOS · C++ and assembly

[View the TH04 reconstruction](https://github.com/N0zoM1z0/th04)

From original instructions to checked source

1. 01 · Original game

   **MAIN.EXE**

   The PC-98 executable and its bullet-angle helper.
2. 02 · Inspect with REA

   **Instructions + references**

   Verify the loaded DOS code and read the angle calculation from
   the executable.
3. 03 · Read the C++

   **Space → aim → rotate**

   Connect each operation to the maintained reconstruction.
4. 04 · Check the source

   **2,139 matching bytes**

   Recorded compiler checks cover the bullet-generation code that
   contains this helper.

REA returns the original code and its analysis evidence. The TH04
project supplies the maintained source and compiler checks.

## How a ring aims at the player

Elly's attack requests an aimed ring with an initial count of 16.
One helper calculates the direction of each bullet. The same ring
can face a fixed direction or turn toward the player.

Your coding agent

Use REA to find how TH04 creates a ring of bullets and aims
it at the player. Explain the calculation and show the
code.

An example prompt, with the original executable and reconstruction
checkout available locally.

TH04 stores a full clockwise turn in 256 angle units. For 16
bullets, each index adds 16 units: `0, 16, 32, …, 240`.
Adding the player's direction rotates those positions together.

![Two diagrams of a 16-bullet ring. The fixed ring starts at angle zero. Adding a player direction of 40 rotates every direction by the same amount; the highlighted first bullet points toward the player. Both keep a spacing of 16 angle units, or 22.5 degrees.](https://rea.tools/assets/figures/th04-bullet-ring.svg)

An illustration of the recovered angle math for count 16, player
direction 40 and template rotation 0.
[Open figure](https://rea.tools/assets/figures/th04-bullet-ring.svg)

### The firing template in the recovered source

Elly's ring attack · source excerpt

```
bullet_template.angle = 0;
bullet_template.group = BG_RING_AIMED;
bullet_template.count = 16;
bullet_template_tune();
```

These lines select the pattern before difficulty and performance
tuning.
[Read the attack's source](https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/src/main/boss/elly_phase_ring16.cpp#L26).

## What REA returns

We inspect `bullet_velocity_and_angle_set()`, the
function that sets each bullet's velocity and angle. REA returns
its instructions, pseudocode and references. Three parts of that
result explain the pattern.

1. #### Check the DOS image before reading its code

   Agent → REA

   ```
   open_binary
   {"path": "MAIN.EXE"}

   inspect_native_load_image
   {}
   ```

   REA → agent · selected fields

   ```
   status: "verified"
   format: "dos-mz"
   load_segment: 4096
   header_bytes: 6144
   module_bytes: 150114
   ```

   The check ties the loaded code and relocation records to the
   supplied executable. The path above is shortened for display.
2. #### Read the ring's multiply and divide

   Agent → REA · `analyze_function`

   ```
   {"procedure": "0x2cfc8"}
   ```

   REA → agent · instruction excerpt

   ```
   0x2d0eb: MOV AX, SI
   0x2d0ed: SHL AX, 0x8
   0x2d0f0: MOV DL, byte ptr [0x53af]
   0x2d0f9: IDIV BX
   ```

   `SI` holds the bullet index. The shift multiplies
   it by 256, then the division uses the count read from
   `DS:53AF`. The maintained source names that field
   `bullet_template.count`. The complete sequence
   appears below.
3. #### Find where the pattern turns toward the player

   Same function result · aim excerpt

   ```
   0x2d190: SUB AX, word ptr [0x53a6]
   0x2d198: SUB AX, word ptr [0x53a4]
   0x2d19d: CALLF 0x1000:1da8
   0x2d1a2: ADD word ptr [BP + -0x2], AX
   ```

   Corresponding maintained C++

   ```
   group_angle_offset += iatan2(
     player_pos.cur.y - bullet_template.origin.y,
     player_pos.cur.x - bullet_template.origin.x
   );
   ```

   The instruction operands expose two coordinate differences and
   a direction helper. Its return value is added to the relative
   ring angle. Source names make those addresses readable; REA
   lets the agent check the original operations behind them.

## From assembly to readable C++

Select a step to match the original instructions with the angle
calculation. This summary follows the aimed-ring path; the full
source also handles spreads, stacks and random patterns.

REA · original 16-bit instructions

```
0x2cfcf: MOV SI, word ptr [BP + 0x4]
0x2d0eb: MOV AX, SI
0x2d0ed: SHL AX, 0x8
0x2d0f0: MOV DL, byte ptr [0x53af]
0x2d0f4: MOV DH, 0x0
0x2d0f6: PUSH DX
0x2d0f7: CWD
0x2d0f8: POP BX
0x2d0f9: IDIV BX
0x2d0fb: MOV word ptr [BP + -0x2], AX
0x2d18d: MOV AX, [0x4650]
0x2d190: SUB AX, word ptr [0x53a6]
0x2d194: PUSH AX
0x2d195: MOV AX, [0x464e]
0x2d198: SUB AX, word ptr [0x53a4]
0x2d19c: PUSH AX
0x2d19d: CALLF 0x1000:1da8
0x2d1a2: ADD word ptr [BP + -0x2], AX
0x2d1b9: MOV AL, byte ptr [BP + -0x2]
0x2d1bc: ADD AL, byte ptr [0x53ad]
0x2d1c0: MOV [0xbcc8], AL
```

Readable C++ · aimed-ring summary

```
unsigned char aimed_ring_angle(
    int index, int count,
    int player_direction,
    unsigned char rotation)
{
    int angle = index;
    angle = (angle * 256) / count;
    angle += player_direction;
    angle += rotation;
    return (unsigned char)angle;
}
```

**01 · Read the bullet index.** The stack
argument is loaded into `SI`. In a 16-bullet ring,
this member index runs from 0 through 15.

**02 · Space the directions around a full turn.**
`SHL AX,8` multiplies by 256. The count is
zero-extended into a word, then `IDIV` divides the
product by that count. For count 16, each index advances 16
angle units.

**03 · Add the player direction.** The two
subtractions form player-minus-origin coordinates. The
direction helper returns an angle in `AX`; adding
it turns the whole ring toward the player.

**04 · Add the pattern's rotation.**
`ADD AL,[0x53ad]` adds the template angle. Elly's
initial template sets it to zero; other patterns can use the
same helper with a rotation.

**05 · Keep the final angle in one byte.** The
store writes `AL` to the spawn-angle field. Values
wrap after 255, just as the unsigned-byte cast does. The
surrounding source also converts this angle and speed into
velocity.

The assembly selects the aimed-ring branch, aim calculation and
final store. The C++ above is a readable summary. Compiler checks
below apply to the maintained source in the linked project.

Read the maintained ring and aim source

Ring macro · TH04 branch, excerpt

```
angle_offset = (i * BULLET_ANGLE_FULL_TURN) / count;
if (i >= (count - 1)) {
    group_complete = true;
}
goto aim_or_no_aim;
```

Angle and velocity · source excerpt

```
aim:
    group_angle_offset += iatan2(
        (player_pos.cur.y - bullet_template.origin.y),
        (player_pos.cur.x - bullet_template.origin.x)
    );

no_aim:
    vector2_near(
        bullet_template.velocity,
        (group_angle_offset + bullet_template.angle),
        speed
    );
    bullet_spawn_angle = (group_angle_offset + bullet_template.angle);
```

[Ring macro](https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/src/main/bullet/add_impl.hpp#L4)
·
[Aim and velocity source](https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/src/main/bullet/add.cpp#L422)
·
[Angle representation](https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/src/main/bullet/types.hpp#L6)

## Checking the maintained source

The TH04 project's recorded checks compile the bullet-generation
code, including this helper, with its pinned Turbo C++ 4.0J
toolchain.

### 2,139 matching bytes

The compiled bullet-generation code matches a 2,139-byte
section of the original executable.

### Two clean builds

Two separate builds produce the same matching bytes. The
recorded input hashes match the maintained source shown here.

Recorded on 3 October 2026.
[Read the compiler checks](https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/docs/reconstruction/product/TH04_SEMANTIC_BULLET_GENERATION_V1236.md#validation).

Target identity and analysis scope

|  |  |
| --- | --- |
| Original target | `MAIN.EXE`, 156,258 bytes, DOS MZ |
| Helper entry | `0x2cfc8` in REA's loaded image |
| Original file offset | `0x1e7c8`, returned by `address_to_file_offset` |
| Recorded code inspection | REA 4.1.0 · 7 October 2026 |

Compiler checks cover the complete bullet-add code section,
including this helper. They also compare its MAP contribution
and overlapping relocation.

REA verified the static DOS load image at load segment
`0x1000`. Its addresses describe that analysis image.
The function result reports 503 body bytes in two ranges across
a 517-byte span; the selected instructions above lie in those
reported ranges.

The source names and compiler checks come from TH04 checkpoint
`0d72e980`. Earlier reconstruction observations used
the project's own analysis scripts; this page adds a fresh REA
inspection of the same original target. The supplied game's
identity is pinned by its repository manifest, with independent
pristine-dump provenance still open.

The figure illustrates the angle calculation. Full-game
controls, timing and PC-98 hardware behavior have separate
runtime checks in the reconstruction project.

## The full TH04 reconstruction

The project builds the game's four DOS products from maintained
C++ and assembly. Beyond bullet generation, the source covers
stages, bosses, rendering, input, menus, endings and score
handling.

[Explore the TH04 repository](https://github.com/N0zoM1z0/th04)

- [Bullet-generation source](https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/src/main/bullet/add.cpp#L292)The complete helper, other pattern types and template
  tuning.
- [DOS build and run guide](https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/docs/DOS_BUILD.md)Build the source and test it in the project's PC-98 emulator
  workflow.
- [Current project state](https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/docs/RE_HANDOFF.md)DOS verification and the separate native x64 port.
