docs(website): add TH04 bullet-ring case study

Adds a TH04 case study showing how its original 16-bit PC-98 DOS code spaces a bullet ring and aims it at the player. Fresh REA instructions are paired with a five-step C++ comparison and a ring diagram; homepage and showcase-index entries link to the new page and the requested reconstruction repository.

The page credits TH04's maintained source and recorded compiler checks separately from the new REA inspection. Public provenance pins the target, source checkpoint and evidence. No original game assets, executables, private paths or raw result bundles are included. Pages remains manually dispatched.

Validation:
- Website verifier: 12 HTML pages, 185 local references, SVG XML and downloadable ZIP.
- Browser: root and `/rea/`, 32 copy checks, five comparison steps, eight viewport widths, keyboard, reduced motion and no-JavaScript content.
- Fresh real Ghidra verification through published REA 4.1.0: DOS load image verified, all 84 checks match; shown instructions match the function dossier. No Hopper verification was needed or run.
- Independent claims/privacy reviews passed; seven immutable public source links match the local TH04 checkpoint.
- `npm run check:fast`.

The two clean compiler replays and 2,139 matching bytes are recorded TH04 checks, reviewed against their retained receipt and source hashes; the website update does not rerun the compiler or game.
This commit is contained in:
Ysopwnable authored and GitHub committed 2026-10-07 22:09:16 +08:00
1 parent 33d4761cc7
commit f8d84da127
8 files changed
+955 -3

No files matched your search

+11 -1
View File
@@ -1,6 +1,6 @@
# REA website
An English static website with explanatory figures, worked guides and DX-Ball and Notion investigations.
An English static website with explanatory figures, worked guides and DX-Ball, Notion and TH04 investigations.
The public files are in `website/public/`. The site uses HTML, CSS and a small
script for copying code and following the assembly-to-C comparison. Python
packages the downloadable example; there is no frontend bundler or npm dependency.
@@ -22,6 +22,7 @@ Open <http://127.0.0.1:4173/>. Refresh the browser after editing a file.
- `public/showcase/index.html`: the case-study index.
- `public/showcase/dx-ball/index.html`: sound-pan investigation and project status.
- `public/showcase/notion/index.html`: Notion's Electron clipboard bridge and rich clipboard format.
- `public/showcase/th04/index.html`: TH04's 16-bit DOS bullet-angle calculation and compiler checks.
- `public/get-started/index.html`: agent setup, first CLI result and provider guides.
- `public/guides/`: a guide hub and native, JavaScript/Electron and browser examples.
- `public/examples/`: downloadable Electron source and an interactive Notes browser app.
@@ -84,6 +85,15 @@ the website.
[evidence/notion-clipboard.md](evidence/notion-clipboard.md) records the REA
package version, selected findings, source anchors and module-probe scope.
## TH04 case study
The TH04 case inspects the original PC-98 DOS angle helper through REA 4.1.0.
Its selected instructions are paired with readable C++ and a source SVG of
fixed and aimed rings. [evidence/th04-bullet-ring.md](evidence/th04-bullet-ring.md)
records the fresh load-image/function evidence and separately credited TH04
source and historical compiler replay. The figure illustrates the calculation;
original game assets and executable bytes are not website downloads.
## GitHub Pages
`.github/workflows/website-pages.yml` prepares and deploys only `website/public`.
+136
View File
@@ -0,0 +1,136 @@
# TH04 aimed-ring evidence
This case examines one helper in the Japanese PC-98 DOS TH04 (Lotus Land Story)
reconstruction. Source and historical compiler checks are pinned to
`0d72e9801a5c14c10384efcbb6734ceccb74ee63` in
[N0zoM1z0/th04](https://github.com/N0zoM1z0/th04). The page adds a fresh inspection
of the supplied original target through REA on 7 October 2026.
## Original target and analysis
- Target: `MAIN.EXE`, 156,258 bytes, DOS MZ, x86 real mode.
- SHA-256: `077440a3c4e9ab52e72e9bae411276c47edc11995b5c2b83dfc83fbc039dc58b`.
- Header: 6,144 bytes (`0x1800`); module: 150,114 bytes; relocations: 1,136.
- Connected analysis: published REA 4.1.0, Ghidra 12.1.4, Linux x64.
- The connected runtime's 761 files under `dist`, `bridge` and `scripts` match
the cached published REA 4.1.0 package byte for byte.
- Load-image profile: `MzLoader`, `x86:LE:16:Real Mode`, compiler `default`,
fixed load segment `0x1000`, linear byte addresses.
- Profile digest: `0d7ec8473c44e8d0a979c0308f7260b7572866613a369c30550026c81beff0e9`.
- Helper: `bullet_velocity_and_angle_set`, REA entry `0x2cfc8`, original
file offset `0x1e7c8` (124,872).
The executable's identity passes the reconstruction manifest and MZ checks. Its
manifest provenance is `candidate-local-attested`; no independent pristine-dump
claim is added. Windows is an emulator or port host here, not this executable's
format.
## Fresh REA requests
1. `open_binary` binds the original target with provider `ghidra`. The displayed
input is compact: its local path is shortened to `MAIN.EXE` and the explicit
provider selection is omitted from the product narrative.
2. `inspect_native_load_image {}` returns `status: verified`, header 6,144,
module 150,114, and load segment 4,096. Every returned check matches.
Evidence: `ev_dc53b8af38910ed3dc40b29dc003110b79842957d6f8ab7da8737847a4b4d3fb`.
3. `analyze_function {"procedure":"0x2cfc8"}` returns instructions, pseudocode,
callers, callees, data references and complete reported function-body ranges.
Evidence: `ev_d79b6834ff615b5b59df1e31f3ba01c32491582ddfd4f9383c6f05d83b59f77d`.
4. `address_to_file_offset {"address":"0x2cfc8"}` returns original file offset
124,872. Evidence:
`ev_aae51edacb1a224d1b52ecccc4c43f506f28b3216f255d05fb07efdfaf10eb4b`.
5. `read_bytes {"address":"0x2d0eb","length":19}` returns all 19 requested
bytes: `8bc6c1e0088a16af53b60052995bf7fb8946fe`. They match the original
target at file `0x1e8eb`. Evidence:
`ev_d984b3df53cc8cd63c0b7bdfdf8822d84f0ea594c3c10c00419ed9f8d4036108`.
The selected helper comes from the project's reviewed source boundary. The
example prompt is an invitation to the reader's agent, not a transcript or a
claim that this inspection independently discovered every source owner.
The ephemeral REA session was closed after these queries. The original
executable and the reconstruction's saved database were not modified.
The returned function body is noncontiguous: `0x2cfc8..0x2d14f` and
`0x2d15e..0x2d1cc`, 503 body bytes across a 517-byte enclosing span. The page's
selected ring, aim and store instructions lie within those reported ranges.
The historical project reconciles the omitted 14 bytes with raw decoding and
reviewed compiler ownership; the page does not claim REA reported a contiguous
517-byte body.
## Instructions and source meaning
| REA instruction evidence | Meaning supported by the maintained source |
| ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- |
| `0x2cfcf: MOV SI, word ptr [BP + 0x4]` | Group-member index |
| `0x2d0ed: SHL AX, 0x8` followed by count load and `IDIV BX` | `(index * 256) / count`, multiplication before integer division |
| Coordinate loads/subtractions at `0x2d18d..0x2d198`, far call at `0x2d19d`, then addition at `0x2d1a2` | Add `iatan2(player - origin)` for an aimed ring |
| `0x2d1bc: ADD AL, byte ptr [0x53ad]` | Add template rotation |
| `0x2d1c0: MOV [0xbcc8], AL` | Store the final byte angle, wrapping at 256 |
Assembly is transcribed verbatim from the fresh REA result. Short request/result
panels select instructions, while the five-step comparison retains the complete
19-byte ring quotient sequence. The names `count`, player/origin fields,
`iatan2`, template rotation and spawn angle come from the maintained source and
reviewed original reconstruction, not recovered original symbols.
The readable `aimed_ring_angle` function is explanatory C++, not the
compiler-sensitive complete helper or the source compiled by historical checks.
The ring macro display expands its TH04 branch and removes preprocessor
continuations for reading. The source labels and details distinguish those
summaries from linked complete source.
Real-mode near-call segment aliases can produce misleading static call edges.
The case uses instruction/data anchors and the project's reviewed source
relationships, not a claim of automatically verified whole-program call flow.
## Figure
The SVG draws sixteen directions using `index * 256 / 16`. The left ring has
zero aim, zero rotation; the right adds illustrative player direction 40 and
keeps zero rotation. The clockwise coordinates match the source's byte-angle
contract: right 0, down 64, left 128, up 192. The highlighted first ray points
at the illustrated player; every subsequent ray retains the same 16-unit step.
This is a mathematical diagram, not captured game output.
Elly's real source initially requests `BG_RING_AIMED`, count 16 and angle 0,
then calls `bullet_template_tune`. The page preserves that tuning call and
labels the figure's count explicitly; it does not claim every difficulty or
performance state emits sixteen bullets.
## Recorded compiler validation
The linked semantic-bullet note records two isolated cold builds on
3 October 2026 of owner `th04-main-module-th04-bullet-a-cpp-1cc33`, using the
pinned Turbo C++ 4.0J/TASM/TLINK toolchain. Both reproduce the complete
2,139-byte (`0x85b`) original extent starting at file `0x1e433`, plus exact MAP
contribution and overlapping relocation. Target and candidate slice SHA-256:
`f7dfaeae5b18749d6687a94139488a825f86f00b85a4eb4072985223a5f1cc5e`.
The retained receipt was read from the project's archive into private scratch
storage. Receipt SHA-256:
`76a361da712d8203f410068df0a864192ecdb64671e173ea15db358796b71e49`.
Its digest matches the archive manifest. It reports `pass: true`, no failures,
and both builds have `raw_exact`, `map_exact`, `relocations_exact` and
`object_valid` true. Recorded input snapshots match the current `add.cpp`,
`add_impl.hpp` and `types.hpp` bytes. No fresh compiler build or ledger
promotion was needed for the website update.
Public evidence reference: `ev-th04-semantic-bullet-exact-v1236` in the TH04
evidence ledger. The matching extent contains the full helper and surrounding
bullet-add functions; it is not a whole-executable or full-game equivalence
claim. The original reconstruction used project-specific analysis and replay
scripts. Its source and compiler evidence are credited separately from the
fresh REA inspection.
## Public source anchors
- [Ring macro](https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/src/main/bullet/add_impl.hpp#L4).
- [Complete angle helper](https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/src/main/bullet/add.cpp#L292).
- [Aim and velocity](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).
- [Elly's firing template](https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/src/main/boss/elly_phase_ring16.cpp#L26).
- [Compiler checks](https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/docs/reconstruction/product/TH04_SEMANTIC_BULLET_GENERATION_V1236.md#validation).
Original executables, game assets, complete raw Evidence, machine paths,
account/configuration details and private compiler products remain outside
the website.
+9
View File
@@ -4,6 +4,7 @@ The published diagrams are SVG source assets:
- `public/assets/figures/rea-investigation-flow.svg` — 1774 × 887 viewBox.
- `public/assets/figures/dx-ball-sound-pan-investigation.svg` — 1774 × 887 viewBox.
- `public/assets/figures/th04-bullet-ring.svg` — 1000 × 520 viewBox.
Two original layout references were generated with the built-in image generation
tool and reviewed visually. Their prompts are retained below. The final SVGs
@@ -18,6 +19,14 @@ the maintained C and separate execution/compiler checks. Recorded tool requests,
instruction excerpts and decoded byte reads appear as HTML text in the case
study; source reconstruction is the agent's work informed by REA results.
## TH04 figure
The TH04 figure is drawn directly as SVG from the byte-angle formula. It uses
16 bullets, zero template rotation and illustrative player direction 40, with
clockwise screen coordinates. The HTML overview separates REA's original-code
inspection from the reconstruction project's C++ and compiler replay. No
image generation or game screenshot is used for this mathematical figure.
## Review
Check spelling, arithmetic order, arrow directions and the separate verification
@@ -0,0 +1,95 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="520" viewBox="0 0 1000 520" role="img" aria-labelledby="title description">
<title id="title">TH04: spacing and aiming a ring of bullets</title>
<desc id="description">A full clockwise turn has 256 angle units. Sixteen bullets start at angles zero, sixteen, thirty-two, through 240. Adding an example player direction of forty turns all sixteen directions together; the first bullet points at the player. Template rotation is zero.</desc>
<defs><marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="#294f82" /></marker></defs>
<style>text { font-family: Arial, Helvetica, sans-serif; fill: #20252b; } .heading { font-size: 26px; font-weight: 600; } .note { font-size: 19px; fill: #5b6570; } .mono { font-family: Consolas, 'Liberation Mono', monospace; font-size: 19px; } .accent { fill: #294f82; } .spoke { stroke: #d2dbe4; stroke-width: 1.6; } .bullet { fill: #9eafc0; } .first { fill: #294f82; } </style>
<rect width="1000" height="520" fill="#ffffff" />
<line x1="500" y1="42" x2="500" y2="428" stroke="#e0e5ea" />
<text x="42" y="58" class="heading">Fixed ring</text>
<text x="42" y="88" class="note">First bullet: angle 0</text>
<text x="542" y="58" class="heading">Aimed ring</text>
<text x="542" y="88" class="note">First bullet: player direction</text>
<g id="fixed-ring" data-count="16" data-aim="0">
<circle cx="245" cy="246" r="118" fill="none" stroke="#e0e5ea" stroke-dasharray="3 5" />
<line x1="245" y1="246" x2="363.000" y2="246.000" stroke="#294f82" stroke-width="2.5" />
<circle cx="363.000" cy="246.000" r="6" class="first" data-index="0" data-angle="0" />
<line x1="245" y1="246" x2="354.018" y2="291.157" class="spoke" />
<circle cx="354.018" cy="291.157" r="4.5" class="bullet" data-index="1" data-angle="16" />
<line x1="245" y1="246" x2="328.439" y2="329.439" class="spoke" />
<circle cx="328.439" cy="329.439" r="4.5" class="bullet" data-index="2" data-angle="32" />
<line x1="245" y1="246" x2="290.157" y2="355.018" class="spoke" />
<circle cx="290.157" cy="355.018" r="4.5" class="bullet" data-index="3" data-angle="48" />
<line x1="245" y1="246" x2="245.000" y2="364.000" class="spoke" />
<circle cx="245.000" cy="364.000" r="4.5" class="bullet" data-index="4" data-angle="64" />
<line x1="245" y1="246" x2="199.843" y2="355.018" class="spoke" />
<circle cx="199.843" cy="355.018" r="4.5" class="bullet" data-index="5" data-angle="80" />
<line x1="245" y1="246" x2="161.561" y2="329.439" class="spoke" />
<circle cx="161.561" cy="329.439" r="4.5" class="bullet" data-index="6" data-angle="96" />
<line x1="245" y1="246" x2="135.982" y2="291.157" class="spoke" />
<circle cx="135.982" cy="291.157" r="4.5" class="bullet" data-index="7" data-angle="112" />
<line x1="245" y1="246" x2="127.000" y2="246.000" class="spoke" />
<circle cx="127.000" cy="246.000" r="4.5" class="bullet" data-index="8" data-angle="128" />
<line x1="245" y1="246" x2="135.982" y2="200.843" class="spoke" />
<circle cx="135.982" cy="200.843" r="4.5" class="bullet" data-index="9" data-angle="144" />
<line x1="245" y1="246" x2="161.561" y2="162.561" class="spoke" />
<circle cx="161.561" cy="162.561" r="4.5" class="bullet" data-index="10" data-angle="160" />
<line x1="245" y1="246" x2="199.843" y2="136.982" class="spoke" />
<circle cx="199.843" cy="136.982" r="4.5" class="bullet" data-index="11" data-angle="176" />
<line x1="245" y1="246" x2="245.000" y2="128.000" class="spoke" />
<circle cx="245.000" cy="128.000" r="4.5" class="bullet" data-index="12" data-angle="192" />
<line x1="245" y1="246" x2="290.157" y2="136.982" class="spoke" />
<circle cx="290.157" cy="136.982" r="4.5" class="bullet" data-index="13" data-angle="208" />
<line x1="245" y1="246" x2="328.439" y2="162.561" class="spoke" />
<circle cx="328.439" cy="162.561" r="4.5" class="bullet" data-index="14" data-angle="224" />
<line x1="245" y1="246" x2="354.018" y2="200.843" class="spoke" />
<circle cx="354.018" cy="200.843" r="4.5" class="bullet" data-index="15" data-angle="240" />
<circle cx="245" cy="246" r="7" fill="#20252b" />
<rect x="215" y="261" width="60" height="26" fill="#ffffff" />
<text x="245" y="280" text-anchor="middle" class="note">Origin</text>
</g>
<g id="aimed-ring" data-count="16" data-aim="40">
<circle cx="730" cy="246" r="118" fill="none" stroke="#e0e5ea" stroke-dasharray="3 5" />
<line x1="730" y1="246" x2="795.557" y2="344.113" stroke="#294f82" stroke-width="2.5" />
<circle cx="795.557" cy="344.113" r="6" class="first" data-index="0" data-angle="40" />
<line x1="730" y1="246" x2="753.021" y2="361.733" class="spoke" />
<circle cx="753.021" cy="361.733" r="4.5" class="bullet" data-index="1" data-angle="56" />
<line x1="730" y1="246" x2="706.979" y2="361.733" class="spoke" />
<circle cx="706.979" cy="361.733" r="4.5" class="bullet" data-index="2" data-angle="72" />
<line x1="730" y1="246" x2="664.443" y2="344.113" class="spoke" />
<circle cx="664.443" cy="344.113" r="4.5" class="bullet" data-index="3" data-angle="88" />
<line x1="730" y1="246" x2="631.887" y2="311.557" class="spoke" />
<circle cx="631.887" cy="311.557" r="4.5" class="bullet" data-index="4" data-angle="104" />
<line x1="730" y1="246" x2="614.267" y2="269.021" class="spoke" />
<circle cx="614.267" cy="269.021" r="4.5" class="bullet" data-index="5" data-angle="120" />
<line x1="730" y1="246" x2="614.267" y2="222.979" class="spoke" />
<circle cx="614.267" cy="222.979" r="4.5" class="bullet" data-index="6" data-angle="136" />
<line x1="730" y1="246" x2="631.887" y2="180.443" class="spoke" />
<circle cx="631.887" cy="180.443" r="4.5" class="bullet" data-index="7" data-angle="152" />
<line x1="730" y1="246" x2="664.443" y2="147.887" class="spoke" />
<circle cx="664.443" cy="147.887" r="4.5" class="bullet" data-index="8" data-angle="168" />
<line x1="730" y1="246" x2="706.979" y2="130.267" class="spoke" />
<circle cx="706.979" cy="130.267" r="4.5" class="bullet" data-index="9" data-angle="184" />
<line x1="730" y1="246" x2="753.021" y2="130.267" class="spoke" />
<circle cx="753.021" cy="130.267" r="4.5" class="bullet" data-index="10" data-angle="200" />
<line x1="730" y1="246" x2="795.557" y2="147.887" class="spoke" />
<circle cx="795.557" cy="147.887" r="4.5" class="bullet" data-index="11" data-angle="216" />
<line x1="730" y1="246" x2="828.113" y2="180.443" class="spoke" />
<circle cx="828.113" cy="180.443" r="4.5" class="bullet" data-index="12" data-angle="232" />
<line x1="730" y1="246" x2="845.733" y2="222.979" class="spoke" />
<circle cx="845.733" cy="222.979" r="4.5" class="bullet" data-index="13" data-angle="248" />
<line x1="730" y1="246" x2="845.733" y2="269.021" class="spoke" />
<circle cx="845.733" cy="269.021" r="4.5" class="bullet" data-index="14" data-angle="8" />
<line x1="730" y1="246" x2="828.113" y2="311.557" class="spoke" />
<circle cx="828.113" cy="311.557" r="4.5" class="bullet" data-index="15" data-angle="24" />
<circle cx="730" cy="246" r="7" fill="#20252b" />
<rect x="700" y="261" width="60" height="26" fill="#ffffff" />
<text x="730" y="280" text-anchor="middle" class="note">Origin</text>
<line x1="800.557" y1="351.597" x2="829.447" y2="394.833" stroke="#294f82" stroke-width="1.8" stroke-dasharray="4 4" marker-end="url(#arrow)" />
<path d="M 832.225 391.990 l 7 7 l -7 7 l -7 -7 z" fill="#294f82" />
<text x="848.225" y="405.990" class="note accent">Player</text>
</g>
<text x="42" y="413" class="mono">0, 16, 32, …, 240</text>
<text x="542" y="413" class="mono">40, 56, 72, …, 24</text>
<rect x="24" y="451" width="952" height="47" rx="4" fill="#edf2f8" />
<text x="500" y="481" text-anchor="middle" class="note accent">Same spacing: 256 ÷ 16 = 16 angle units = 22.5°</text>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

+16 -1
View File
@@ -841,6 +841,20 @@ pre code {
.notion-bridge-flow {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.th04-flow {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.th04-overview {
margin-bottom: 3.2rem;
}
.th04-ring-figure img {
min-width: 680px;
}
.th04-preview img {
display: block;
width: 100%;
height: auto;
}
.notion-channel {
color: var(--accent);
background: #edf2f8;
@@ -994,7 +1008,8 @@ pre code {
grid-template-columns: 1fr;
}
.flow-steps,
.notion-bridge-flow {
.notion-bridge-flow,
.th04-flow {
grid-template-columns: 1fr;
gap: 2rem;
}
+32
View File
@@ -232,6 +232,38 @@
</div>
</section>
<section class="section">
<div class="container feature-case">
<div>
<p class="eyebrow">Case study · PC-98 DOS</p>
<h2>TH04's aimed bullet rings</h2>
<p>
Inspect a 16-bit DOS function with REA. Follow its angle
calculation from original instructions to readable C++, then see
how adding the player's direction turns a ring of bullets.
</p>
<a class="text-link" href="showcase/th04/"
>See the bullet calculation</a
>
</div>
<figure class="workflow-figure case-preview th04-preview">
<p class="figure-title">Fixed and aimed rings</p>
<img
src="assets/figures/th04-bullet-ring.svg"
width="1000"
height="520"
alt="TH04's fixed and aimed 16-bullet rings side by side. The first bullet is highlighted in blue; adding the player direction rotates every bullet while preserving the spacing."
loading="lazy"
decoding="async"
/>
<figcaption>
Adding the player's direction rotates every bullet by the same
angle.
</figcaption>
</figure>
</div>
</section>
<section class="section">
<div class="container start-row">
<div>
+19 -1
View File
@@ -6,7 +6,7 @@
<title>Case studies — REA</title>
<meta
name="description"
content="Follow REA investigations through code and figures: recover a native calculation in DX-Ball and trace Notion's Electron clipboard bridge."
content="Follow REA investigations through code and figures: DX-Ball's sound pan, Notion's Electron clipboard and TH04's aimed bullet rings."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml" />
@@ -76,6 +76,24 @@
<a class="text-link" href="notion/">Read the Notion case study</a>
</div>
</section>
<section class="guide-entry">
<p class="eyebrow">PC-98 DOS · Reconstruction</p>
<div>
<h2>Understanding TH04's bullet patterns</h2>
<p>
Inspect the original 16-bit angle helper with REA. Match its
multiply, divide and coordinate operations to readable C++, then
see how they produce a ring aimed at the player.
</p>
<div class="guide-route">
<span>Original DOS code</span> <b aria-hidden="true">→</b>
<span>REA instructions</span> <b aria-hidden="true">→</b>
<span>Angle calculation</span> <b aria-hidden="true">→</b>
<span>Bullet ring</span>
</div>
<a class="text-link" href="th04/">Read the TH04 case study</a>
</div>
</section>
</div>
<section class="article-section">
<h2>Try a smaller example first.</h2>
+637
View File
@@ -0,0 +1,637 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>TH04 bullet patterns — REA</title>
<meta
name="description"
content="Inspect TH04's original 16-bit DOS code with REA. Follow the assembly and recovered C++ to see how a ring of bullets is spaced and aimed at the player."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../../assets/styles.css" />
<script src="../../assets/main.js" defer></script>
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<header class="site-header">
<div class="container header-inner">
<a class="brand" href="../../" aria-label="REA home"
><span class="brand-name">REA</span
><span class="brand-caption">Reverse Engineer Anything</span></a
>
<nav class="navigation" aria-label="Main navigation">
<a href="../">Showcases</a>
<a class="nav-start" href="../../get-started/">Get started</a>
<a href="../../guides/">Guides</a>
<a href="https://github.com/morluto/rea">GitHub</a>
</nav>
</div>
</header>
<main id="main" class="container">
<div class="page-heading">
<p class="breadcrumb">
<a href="../../">Home</a> / <a href="../">Showcases</a> / TH04
</p>
<p class="eyebrow">Case study · Touhou 4: Lotus Land Story</p>
<h1>Reconstructing TH04's<br />bullet patterns.</h1>
<p class="lede">
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++.
</p>
<p class="page-meta">PC-98 · 16-bit DOS · C++ and assembly</p>
<a class="text-link" href="https://github.com/N0zoM1z0/th04"
>View the TH04 reconstruction</a
>
</div>
<figure class="workflow-figure th04-overview">
<p class="figure-title">From original instructions to checked source</p>
<ol class="flow-steps th04-flow">
<li>
<span class="flow-label">01 · Original game</span
><strong class="flow-node">MAIN.EXE</strong
><span class="flow-note"
>The PC-98 executable and a reviewed bullet helper.</span
>
</li>
<li>
<span class="flow-label">02 · Inspect with REA</span
><strong class="flow-node">Instructions + references</strong
><span class="flow-note"
>Check the loaded image, read the arithmetic and anchor it to file
bytes.</span
>
</li>
<li>
<span class="flow-label">03 · Read the C++</span
><strong class="flow-node">Space → aim → rotate</strong
><span class="flow-note"
>Connect each operation to the maintained reconstruction.</span
>
</li>
<li>
<span class="flow-label">04 · Check the source</span
><strong class="flow-node">2,139 matching bytes</strong
><span class="flow-note"
>Recorded compiler checks cover the containing bullet-add
code.</span
>
</li>
</ol>
<figcaption>
REA returns the original code and its analysis evidence. The TH04
project supplies the maintained source and compiler checks.
</figcaption>
</figure>
<div class="article-layout">
<nav class="contents" aria-label="On this page">
<p>In this case study</p>
<ul>
<li><a href="#ring">The bullet ring</a></li>
<li><a href="#rea">What REA returns</a></li>
<li><a href="#source">Assembly to C++</a></li>
<li><a href="#checking">Checking the source</a></li>
<li><a href="#project">The full project</a></li>
</ul>
</nav>
<article class="article" aria-label="TH04 bullet-pattern case study">
<section class="article-section" id="ring">
<h2>How a ring aims at the player</h2>
<p>
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.
</p>
<div class="agent-terminal">
<div class="terminal-header">
<span class="terminal-title"
><span class="terminal-dots" aria-hidden="true"
><i></i><i></i><i></i></span
>Your coding agent</span
>
<button
class="copy-button"
type="button"
data-copy="th04-prompt"
aria-label="Copy TH04 example prompt"
>
Copy
</button>
</div>
<p class="terminal-prompt">
<span class="prompt-symbol" aria-hidden="true">›</span
><span id="th04-prompt"
>Use REA to find how TH04 creates a ring of bullets and aims
it at the player. Explain the calculation and show the
code.</span
><span class="terminal-cursor" aria-hidden="true"></span>
</p>
</div>
<p class="caption">
An example prompt, with the original executable and reconstruction
checkout available locally.
</p>
<p>
TH04 stores a full clockwise turn in 256 angle units. For 16
bullets, each index adds 16 units: <code>0, 16, 32, …, 240</code>.
Adding the player's direction rotates those positions together.
</p>
<figure class="explanatory-figure th04-ring-figure">
<div
class="figure-viewport"
tabindex="0"
role="region"
aria-label="TH04 fixed and aimed bullet rings; scroll horizontally on smaller screens"
>
<img
src="../../assets/figures/th04-bullet-ring.svg"
width="1000"
height="520"
alt="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."
decoding="async"
/>
</div>
<figcaption>
An illustration of the recovered angle math for count 16, player
direction 40 and template rotation 0.
<a href="../../assets/figures/th04-bullet-ring.svg"
>Open figure</a
>
</figcaption>
</figure>
<h3 class="small-heading">
The firing template in the recovered source
</h3>
<div class="code-block">
<div class="code-header">
<span>Elly's ring attack · source excerpt</span
><button
class="copy-button"
type="button"
data-copy="th04-template"
aria-label="Copy TH04 firing template"
>
Copy
</button>
</div>
<pre><code id="th04-template">bullet_template.angle = 0;
bullet_template.group = BG_RING_AIMED;
bullet_template.count = 16;
bullet_template_tune();</code></pre>
</div>
<p class="caption">
These lines select the pattern before difficulty and performance
tuning.
<a
href="https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/src/main/boss/elly_phase_ring16.cpp#L26"
>Read the attack's source</a
>.
</p>
</section>
<section class="article-section" id="rea">
<h2>What REA returns</h2>
<p>
The project already has a reviewed boundary for
<code>bullet_velocity_and_angle_set()</code>. Inspecting that
original function through REA returns its instructions, pseudocode
and references together. Here are three parts of the result that
explain the pattern.
</p>
<ol class="rea-queries">
<li>
<h4>Check the DOS image before reading its code</h4>
<div class="rea-query-pair">
<div class="code-block">
<div class="code-header"><span>Agent → REA</span></div>
<pre><code>open_binary
{"path": "MAIN.EXE"}
inspect_native_load_image
{}</code></pre>
</div>
<div class="code-block">
<div class="code-header">
<span>REA → agent · selected fields</span>
</div>
<pre><code>status: "verified"
format: "dos-mz"
load_segment: 4096
header_bytes: 6144
module_bytes: 150114</code></pre>
</div>
</div>
<p>
The check ties the loaded code and relocation records to the
supplied executable. The path above is shortened for display.
</p>
</li>
<li>
<h4>Read the ring's multiply and divide</h4>
<div class="rea-query-pair">
<div class="code-block">
<div class="code-header">
<span>Agent → REA · <code>analyze_function</code></span>
</div>
<pre><code>{"procedure": "0x2cfc8"}</code></pre>
</div>
<div class="code-block">
<div class="code-header">
<span>REA → agent · instruction excerpt</span>
</div>
<pre><code>0x2d0eb: MOV AX, SI
0x2d0ed: SHL AX, 0x8
0x2d0f0: MOV DL, byte ptr [0x53af]
0x2d0f9: IDIV BX</code></pre>
</div>
</div>
<p>
<code>SI</code> holds the bullet index. The shift multiplies
it by 256, then the division uses the count read from
<code>DS:53AF</code>. The maintained source names that field
<code>bullet_template.count</code>. The complete sequence
appears below.
</p>
</li>
<li>
<h4>Find where the pattern turns toward the player</h4>
<div class="rea-query-pair">
<div class="code-block">
<div class="code-header">
<span>Same function result · aim excerpt</span>
</div>
<pre><code>0x2d190: SUB AX, word ptr [0x53a6]
0x2d198: SUB AX, word ptr [0x53a4]
0x2d19d: CALLF 0x1000:1da8
0x2d1a2: ADD word ptr [BP + -0x2], AX</code></pre>
</div>
<div class="code-block">
<div class="code-header">
<span>Corresponding maintained C++</span>
</div>
<pre><code>group_angle_offset += iatan2(
player_pos.cur.y - bullet_template.origin.y,
player_pos.cur.x - bullet_template.origin.x
);</code></pre>
</div>
</div>
<p>
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.
</p>
</li>
</ol>
</section>
<section class="article-section" id="source">
<h2>From assembly to readable C++</h2>
<p>
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.
</p>
<div class="step-comparison" data-step-comparison>
<div
class="step-selector"
role="group"
aria-label="Follow the TH04 aimed-ring calculation"
>
<button
type="button"
data-select-step="1"
aria-pressed="true"
aria-controls="ring-assembly ring-source ring-step-detail"
>
<span>01</span> Index
</button>
<button
type="button"
data-select-step="2"
aria-pressed="false"
aria-controls="ring-assembly ring-source ring-step-detail"
>
<span>02</span> Space
</button>
<button
type="button"
data-select-step="3"
aria-pressed="false"
aria-controls="ring-assembly ring-source ring-step-detail"
>
<span>03</span> Aim
</button>
<button
type="button"
data-select-step="4"
aria-pressed="false"
aria-controls="ring-assembly ring-source ring-step-detail"
>
<span>04</span> Rotate
</button>
<button
type="button"
data-select-step="5"
aria-pressed="false"
aria-controls="ring-assembly ring-source ring-step-detail"
>
<span>05</span> Store
</button>
</div>
<div class="code-comparison">
<div class="code-block">
<div class="code-header">
<span>REA · original 16-bit instructions</span
><button
class="copy-button"
type="button"
data-copy="ring-assembly"
aria-label="Copy TH04 assembly excerpt"
>
Copy
</button>
</div>
<pre><code id="ring-assembly"><span class="code-step is-active" data-step="1">0x2cfcf: MOV SI, word ptr [BP + 0x4]
0x2d0eb: MOV AX, SI</span>
<span class="code-step" data-step="2">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</span>
<span class="code-step" data-step="3">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</span>
<span class="code-step" data-step="4">0x2d1b9: MOV AL, byte ptr [BP + -0x2]
0x2d1bc: ADD AL, byte ptr [0x53ad]</span>
<span class="code-step" data-step="5">0x2d1c0: MOV [0xbcc8], AL</span></code></pre>
</div>
<div class="code-block">
<div class="code-header">
<span>Readable C++ · aimed-ring summary</span
><button
class="copy-button"
type="button"
data-copy="ring-source"
aria-label="Copy readable aimed-ring calculation"
>
Copy
</button>
</div>
<pre><code id="ring-source">unsigned char aimed_ring_angle(
<span class="is-active" data-step="1">int index</span>, int count,
int player_direction,
unsigned char rotation)
{
<span class="code-step is-active" data-step="1"> int angle = index;</span>
<span class="code-step" data-step="2"> angle = (angle * 256) / count;</span>
<span class="code-step" data-step="3"> angle += player_direction;</span>
<span class="code-step" data-step="4"> angle += rotation;</span>
<span class="code-step" data-step="5"> return (unsigned char)angle;</span>
}</code></pre>
</div>
</div>
<div
class="step-detail"
id="ring-step-detail"
aria-live="polite"
aria-atomic="true"
>
<p data-step-note="1">
<strong>01 · Read the bullet index.</strong> The stack
argument is loaded into <code>SI</code>. In a 16-bullet ring,
this member index runs from 0 through 15.
</p>
<p data-step-note="2" hidden>
<strong>02 · Space the directions around a full turn.</strong>
<code>SHL AX,8</code> multiplies by 256. The count is
zero-extended into a word, then <code>IDIV</code> divides the
product by that count. For count 16, each index advances 16
angle units.
</p>
<p data-step-note="3" hidden>
<strong>03 · Add the player direction.</strong> The two
subtractions form player-minus-origin coordinates. The
direction helper returns an angle in <code>AX</code>; adding
it turns the whole ring toward the player.
</p>
<p data-step-note="4" hidden>
<strong>04 · Add the pattern's rotation.</strong>
<code>ADD AL,[0x53ad]</code> adds the template angle. Elly's
initial template sets it to zero; other patterns can use the
same helper with a rotation.
</p>
<p data-step-note="5" hidden>
<strong>05 · Keep the final angle in one byte.</strong> The
store writes <code>AL</code> 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.
</p>
</div>
</div>
<p class="caption">
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.
</p>
<details class="evidence-details">
<summary>Read the maintained ring and aim source</summary>
<div class="code-block">
<div class="code-header">
<span>Ring macro · TH04 branch, excerpt</span>
</div>
<pre><code>angle_offset = (i * BULLET_ANGLE_FULL_TURN) / count;
if (i &gt;= (count - 1)) {
group_complete = true;
}
goto aim_or_no_aim;</code></pre>
</div>
<div class="code-block">
<div class="code-header">
<span>Angle and velocity · source excerpt</span>
</div>
<pre><code>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);</code></pre>
</div>
<p class="caption">
<a
href="https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/src/main/bullet/add_impl.hpp#L4"
>Ring macro</a
>
·
<a
href="https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/src/main/bullet/add.cpp#L422"
>Aim and velocity source</a
>
·
<a
href="https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/src/main/bullet/types.hpp#L6"
>Angle representation</a
>
</p>
</details>
</section>
<section class="article-section" id="checking">
<h2>Checking the maintained source</h2>
<p>
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.
</p>
<div class="verification">
<div>
<h3>2,139 matching bytes</h3>
<p>
The rebuilt code matches the complete original target extent.
The comparison also checks the MAP contribution and
overlapping relocation.
</p>
</div>
<div>
<h3>Two clean builds</h3>
<p>
Two isolated compiler runs produce the same matching result.
The recorded input hashes match the maintained bullet source
shown here.
</p>
</div>
</div>
<p class="caption">
Recorded on 3 October 2026.
<a
href="https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/docs/reconstruction/product/TH04_SEMANTIC_BULLET_GENERATION_V1236.md#validation"
>Read the compiler checks</a
>.
</p>
<details class="evidence-details">
<summary>Target identity and analysis scope</summary>
<div class="evidence-table-wrap">
<table class="evidence-table">
<tbody>
<tr>
<th scope="row">Original target</th>
<td><code>MAIN.EXE</code>, 156,258 bytes, DOS MZ</td>
</tr>
<tr>
<th scope="row">Helper entry</th>
<td><code>0x2cfc8</code> in REA's loaded image</td>
</tr>
<tr>
<th scope="row">Original file offset</th>
<td>
<code>0x1e7c8</code>, returned by
<code>address_to_file_offset</code>
</td>
</tr>
<tr>
<th scope="row">Recorded code inspection</th>
<td>REA 4.1.0 · 7 October 2026</td>
</tr>
</tbody>
</table>
</div>
<p>
REA verified the static DOS load image at load segment
<code>0x1000</code>. 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.
</p>
<p>
The source names and compiler checks come from TH04 checkpoint
<code>0d72e980</code>. 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.
</p>
<p>
The figure illustrates the angle calculation. Full-game
controls, timing and PC-98 hardware behavior have separate
runtime checks in the reconstruction project.
</p>
</details>
</section>
<section class="article-section" id="project">
<h2>The full TH04 reconstruction</h2>
<p>
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.
</p>
<p>
<a class="text-link" href="https://github.com/N0zoM1z0/th04"
>Explore the TH04 repository</a
>
</p>
<ul class="reading-list">
<li>
<a
href="https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/src/main/bullet/add.cpp#L292"
>Bullet-generation source</a
><span
>The complete helper, other pattern types and template
tuning.</span
>
</li>
<li>
<a
href="https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/docs/DOS_BUILD.md"
>DOS build and run guide</a
><span
>Build the source and test it in the project's PC-98 emulator
workflow.</span
>
</li>
<li>
<a
href="https://github.com/N0zoM1z0/th04/blob/0d72e9801a5c14c10384efcbb6734ceccb74ee63/docs/RE_HANDOFF.md"
>Current project state</a
><span>DOS verification and the separate native x64 port.</span>
</li>
</ul>
</section>
</article>
</div>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<p>REA · Reverse Engineer Anything</p>
<div class="footer-links">
<a href="https://github.com/morluto/rea">Source</a
><a href="https://github.com/morluto/rea/blob/main/LICENSE"
>MIT license</a
><a href="https://discord.gg/GkcryMnJDM">Community</a>
</div>
</div>
</footer>
<p class="sr-only" id="copy-status" role="status" aria-live="polite"></p>
</body>
</html>