---
title: 'DX-Ball Reverse Engineering: Sound Panning — REA'
url: https://rea.tools/showcase/dx-ball/
---

[Home](https://rea.tools/) / [Showcases](https://rea.tools/showcase/) / DX-Ball

Case study · DX-Ball 1.07

# Rebuilding DX-Ball from its executable.

The goal is a maintainable C version of the 1996 Windows game. REA
helps the agent inspect the original, one function at a time.

Windows i386 · C · REA

![DXBALL.EXE function 0x00406400 is inspected with REA. A brick-hit caller supplies 20 plus 30 times tile_x. Instructions load x, multiply by 1.5625, subtract 500.0, apply pan_scale and return an integer. The maintained C is checked independently against the original x86 in 3,205 cases and by VC4.0 compiler replay matching 63 bytes.](https://rea.tools/assets/figures/dx-ball-sound-pan-investigation.svg)

One function, from executable to checked C. REA supplies the
instructions, callers and data reads; the reconstruction project
implements and tests the result.
[Open figure](https://rea.tools/assets/figures/dx-ball-sound-pan-investigation.svg)

55
:   maintained C functions

45,380
:   comparisons with original code

33
:   functions with matching compiled bytes

Project checkpoint: 7 October 2026.
[Progress ledger](https://github.com/N0zoM1z0/dx-ball/blob/a55dca27ec0a07018c1b2c95ae2be027f7d8c3c4/docs/PROGRESS.md)
·
[Test coverage](https://github.com/N0zoM1z0/dx-ball/blob/a55dca27ec0a07018c1b2c95ae2be027f7d8c3c4/docs/ORACLES.md)

## The project so far

The recovered code covers board and resource handling, brick hits,
animations, particles and bonus production. Current builds provide
inspection utilities and an analysis library. Work on the playable
game continues with ball and paddle physics, power-up handling and
Windows integration.

## Finding the sound-pan calculation

When a brick is hit, its horizontal position is passed to the
sound code. The investigation needed to recover how that screen
coordinate becomes a left/right panning value.

Your coding agent

Use REA to find how DX-Ball calculates sound panning. Explain
the calculation and show the code.

An example prompt for your coding agent, with the local executable
available. The investigation below shows the REA queries and
evidence behind the answer.

The agent used REA to inspect the function, follow its brick-hit
caller and read the constants from the executable. Those results
supplied the input and arithmetic needed to write the C function
below.

The first function result for `0x406400` included
pseudocode declaring `FUN_00406400(void)` and calling
`__ftol()`. Its instruction view showed a stack input
and floating-point operations. That discrepancy gave the agent a
concrete reason to keep investigating.

The first decompiler view

REA result · pseudocode excerpt

```
longlong FUN_00406400(void)
{
  longlong lVar1;

  lVar1 = __ftol();
  return lVar1;
}
```

The output shows a conversion call. Reading the instructions
below reveals the input and the calculation feeding that call.

### Three questions answered through REA

1. #### Recover the missing input

   Agent → REA · `analyze_function`

   ```
   {"procedure": "0x406400"}
   ```

   REA → agent · instruction excerpt

   ```
   0x406409: MOV EAX, dword ptr [EBP + 0x8]
   0x40640c: MOV dword ptr [EBP + -0xc], EAX
   0x40640f: FILD dword ptr [EBP + -0xc]
   ```

   The function result includes instructions alongside pseudocode
   and callers. Here, `[EBP+8]` exposes the integer
   input missing from the decompiler view.
2. #### Follow the caller to understand the input

   Agent → REA · `analyze_function`

   ```
   {"procedure": "0x411f40"}
   ```

   REA → agent · caller excerpt

   ```
   0x411f5b: ADD EAX, 0x14
   0x411f5e: PUSH EAX
   0x411f5f: CALL 0x00406400
   ```

   REA identified this caller in the first result. Its preceding
   instructions scale `tile_x` by 30; adding 20 gives
   the brick's screen coordinate, `20 + 30 × tile_x`.
3. #### Read the values behind the memory addresses

   Agent → REA · two
   `read_bytes` requests

   ```
   {"address": "0x420068", "length": 16}
   {"address": "0x4210a0", "length": 8}
   ```

   REA byte results · decoded as doubles

   ```
   000000000000f93f → 1.5625
   0000000000407f40 → 500.0
   000000000000f03f → 1.0
   ```

   The returned bytes establish the two arithmetic constants and
   initial pan scale. The agent now has the values needed for the
   C expression.

### From REA's findings to C

Follow the five steps below. Each selection highlights the
original instructions and the C lines they informed.

Original x86 · excerpt

```
0x406409: MOV EAX, dword ptr [EBP + 0x8]
0x40640c: MOV dword ptr [EBP + -0xc], EAX
0x40640f: FILD dword ptr [EBP + -0xc]
0x406415: FMUL double ptr [0x00420068]
0x40641e: FSUB double ptr [0x00420070]
0x406427: FMUL double ptr [0x004210a0]
0x406430: CALL 0x0041678c
0x40643e: RET
```

Reconstructed C

```
DxBallInt dxball_screen_pan(DxBallInt x)
{
    double pan;
    pan = (double)x;
    pan = pan * 1.5625;
    pan = pan - 500.0;
    pan = pan * dxball_pan_scale;
    return (DxBallInt)pan;
}
```

**01 · Read the input.**
The instruction view in REA's function result reads
`[EBP+8]` and uses `FILD` to load the
integer. Following its caller through REA establishes the
screen-coordinate meaning of the C parameter `x`.

**02 · Multiply by 1.5625.**
`FMUL` reads the double at `0x420068`.
The bytes returned by REA's `read_bytes` decode to
`1.5625`; the C uses the same constant.

**03 · Subtract 500.0.** `FSUB` reads
the double at `0x420070`. REA's byte result decodes
to `500.0`, shifting the calculation's center to
screen coordinate `320`.

**04 · Apply the stored scale.** The second
`FMUL` reads `0x4210a0`. That global is
named `dxball_pan_scale` in the maintained source.
REA's second byte read establishes its initial value of
`1.0`.

**05 · Return an integer.** The call at
`0x406430` reaches `__ftol`, identified
in REA's callee results. The C casts the result to
`DxBallInt`; original-x86 comparisons check the
returned value.

The assembly excerpt selects the input, arithmetic and return
instructions. The full listing below also includes the intervening
stores and function setup and cleanup.

Full assembly and supporting evidence

REA result · 0x406400–0x40643e

```
0x406400: PUSH EBP
0x406401: MOV EBP, ESP
0x406403: SUB ESP, 0xc
0x406406: PUSH EBX
0x406407: PUSH ESI
0x406408: PUSH EDI
0x406409: MOV EAX, dword ptr [EBP + 0x8]
0x40640c: MOV dword ptr [EBP + -0xc], EAX
0x40640f: FILD dword ptr [EBP + -0xc]
0x406412: FST double ptr [EBP + -0x8]
0x406415: FMUL double ptr [0x00420068]
0x40641b: FST double ptr [EBP + -0x8]
0x40641e: FSUB double ptr [0x00420070]
0x406424: FST double ptr [EBP + -0x8]
0x406427: FMUL double ptr [0x004210a0]
0x40642d: FST double ptr [EBP + -0x8]
0x406430: CALL 0x0041678c
0x406435: JMP 0x0040643a
0x40643a: POP EDI
0x40643b: POP ESI
0x40643c: POP EBX
0x40643d: LEAVE
0x40643e: RET
```

### What the caller supplies

The brick-hit function at `0x411f40` scales its tile
coordinate by 30, adds 20, then pushes it before the call:

```
0x411f50: MOV EAX, dword ptr [EBP + 0x8]
0x411f53: ADD EAX, EAX
0x411f55: LEA EAX, [EAX + EAX*0x2]
0x411f58: LEA EAX, [EAX + EAX*0x4]
0x411f5b: ADD EAX, 0x14
0x411f5e: PUSH EAX
0x411f5f: CALL 0x00406400
0x411f64: ADD ESP, 0x4
```

### What the data reads establish

REA byte reads, decoded as little-endian doubles

| Address | Value | Used for |
| --- | --- | --- |
| `0x420068` | `1.5625` | First multiplication |
| `0x420070` | `500.0` | Subtraction |
| `0x4210a0` | `1.0` initially | Stored pan scale |

Recorded with REA 4.1.0 for the hash-pinned DX-Ball 1.07 target.
The linked investigation notes retain the Evidence IDs.

[Maintained source](https://github.com/N0zoM1z0/dx-ball/blob/a55dca27ec0a07018c1b2c95ae2be027f7d8c3c4/src/gameplay.c#L41)
·
[Investigation notes and evidence references](https://github.com/N0zoM1z0/dx-ball/blob/a55dca27ec0a07018c1b2c95ae2be027f7d8c3c4/docs/GAMEPLAY_OWNER.md)

## Checking the recovered function

The recovered function passes two checks: its return values match
the original, and its compiled bytes match the executable.

### 3,205 behavior cases

The tests execute the original x86 function and compare its
return value with the maintained C. They cover every integer
position from 0 to 640 at five pan scales: 0, 0.5, 1, 20 and
−1.

### 63 matching bytes

The C function is compiled with the pinned VC4.0 toolchain.
Compiler replay matches the complete function after applying
the reviewed relocations and checking the referenced
constants.

At a scale of 1, the recovered calculation returns −500 at the
left edge, 0 at the center and 500 at the right edge. The next
part of the audio work is integrating that value with the game's
sound backend.

[Read the compiler replay details](https://github.com/N0zoM1z0/dx-ball/blob/a55dca27ec0a07018c1b2c95ae2be027f7d8c3c4/docs/BUILD_MATCHING.md)

## Continue the investigation

The same approach is being used for particle updates, explosion
queues and bonus generation: inspect the relevant functions,
recover their state and dependencies, then compare the
implementation with the original.

To inspect the sound-pan function yourself, follow the DX-Ball
project's setup instructions and supply its matching original
target. Then run:

In the DX-Ball checkout

```
scripts/rea function original/DXBALL.EXE 0x00406400 \
  --snapshot .analysis/rea/dxball.snapshot.json --json
```

- [DX-Ball repository](https://github.com/N0zoM1z0/dx-ball)Current source, progress and local setup.
- [REA analysis workflow](https://rea.tools/guides/native/)Follow the calculation, then use the same approach with your
  own binary.
- [Particles and bonus production](https://github.com/N0zoM1z0/dx-ball/blob/a55dca27ec0a07018c1b2c95ae2be027f7d8c3c4/docs/ENTITIES_OWNER.md)A larger investigation into state, queues and pixel
  writes.

[Start with your own target](https://rea.tools/get-started/)
