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
+
+ The project already has a reviewed boundary for
+ bullet_velocity_and_angle_set(). Inspecting that
+ original function through REA returns its instructions, pseudocode
+ and references together. Here are three parts of the result that
+ explain the pattern.
+
-
+
-
+
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. +
+
+ -
+
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+
+SIholds 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. +
+ -
+
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. +
+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);
+ + Ring macro + · + Aim and velocity source + · + Angle representation +
+Checking the maintained source
++ The TH04 project checks this compiler-sensitive C++ with its + pinned Turbo C++ 4.0J toolchain. Recorded checks cover the + complete bullet-add source owner containing this helper. +
+2,139 matching bytes
++ The rebuilt code matches the complete original target extent. + The comparison also checks the MAP contribution and + overlapping relocation. +
+Two clean builds
++ Two isolated compiler runs produce the same matching result. + The recorded input hashes match the maintained bullet 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 | +
+ 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 +
+-
+
- + 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. + +