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.
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.
The firing template in the recovered source
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.
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.
-
Check the DOS image before reading its code
Agent → REAopen_binary {"path": "MAIN.EXE"} inspect_native_load_image {}REA → agent · selected fieldsstatus: "verified" format: "dos-mz" load_segment: 4096 header_bytes: 6144 module_bytes: 150114The check ties the loaded code and relocation records to the supplied executable. The path above is shortened for display.
-
Read the ring's multiply and divide
Agent → REA ·analyze_function{"procedure": "0x2cfc8"}REA → agent · instruction excerpt0x2d0eb: MOV AX, SI 0x2d0ed: SHL AX, 0x8 0x2d0f0: MOV DL, byte ptr [0x53af] 0x2d0f9: IDIV BXSIholds the bullet index. The shift multiplies it by 256, then the division uses the count read fromDS:53AF. The maintained source names that fieldbullet_template.count. The complete sequence appears below. -
Find where the pattern turns toward the player
Same function result · aim excerpt0x2d190: SUB AX, word ptr [0x53a6] 0x2d198: SUB AX, word ptr [0x53a4] 0x2d19d: CALLF 0x1000:1da8 0x2d1a2: ADD word ptr [BP + -0x2], AXCorresponding 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.
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
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
angle_offset = (i * BULLET_ANGLE_FULL_TURN) / count;
if (i >= (count - 1)) {
group_complete = true;
}
goto aim_or_no_aim;
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);
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.
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.
- Bullet-generation sourceThe complete helper, other pattern types and template tuning.
- DOS build and run guideBuild the source and test it in the project's PC-98 emulator workflow.
- Current project stateDOS verification and the separate native x64 port.