# Runtime Tracer Implementation Spec

This document is a handoff brief for implementing a runtime tracer in the web
compatibility layer for `Hwanse2.exe`.

## Background

The project has already recovered a large amount of static information from the
EXE and CNS/WLK/MLK/MIDI assets:

- CNS source rectangles and frame catalogs are mostly stabilized.
- Battle formulas, hit/miss/guard/critical flags, actor fields, and many action
  VM payloads are grounded.
- HUD/window templates, right menu payloads, status UI layout, prompt text
  rendering, and descriptor-stack primitives are partially grounded.
- Field movement, collision flags, camera projection, and actor walking
  descriptors are mostly grounded.

However, the remaining blocked analysis fronts have the same problem:

- The data tables and low-level consumers are visible statically.
- VM opcode handlers and helper routines are visible statically.
- But the **live producer/root** that chooses a route during gameplay is not
  always visible from static references alone.

In other words, static analysis can often answer:

> "What does this handler do once it is called?"

but not always:

> "Which live game state caused this handler/stream/table to be selected now?"

The web compatibility layer can execute the game by virtualizing the EXE
instruction/API environment. That makes it possible to observe real runtime
state transitions. The tracer should capture those transitions in a way that can
be correlated with the existing static analysis reports.

## Goal

Build a tracer that records high-signal runtime events from the emulated game:

- VM opcode execution
- object script pointer changes
- selected-root and descriptor stack changes
- global flag changes
- input, RNG, resource, map, battle, and draw events

The tracer should not dump every instruction by default. It should record enough
context to answer:

- Which VM stream is currently executing?
- Who changed `object+0x40` to a new stream?
- Which root selected a prompt/scene/event/encounter/menu stream?
- Which descriptor was added/removed/rebuilt?
- Which flags or selected-root bytes changed immediately before a visible game
  transition?

## Important Address Convention

All addresses below are original EXE virtual addresses, not host/browser
addresses. The compatibility layer should report original PC/VA values whenever
possible.

Example:

```json
{
  "pc": "0x00402321",
  "address": "0x0059de30"
}
```

## Core Event Schema

Every trace event should include these common fields:

```json
{
  "frame": 12345,
  "tick": 592381,
  "event": "vm-op",
  "pc": "0x00402321",
  "sceneHint": "field",
  "mapHint": "map1_02b"
}
```

Recommended fields:

- `frame`: emulator/game frame counter.
- `tick`: original or emulated tick time, preferably the same timing basis used
  by the game loop.
- `event`: event type.
- `pc`: original EXE PC at the event.
- `caller`: caller VA if available.
- `sceneHint`: coarse mode, such as `title`, `field`, `menu`, `prompt`,
  `battle`, `opening`, `unknown`.
- `mapHint`: current map id if known.
- `object`: current VM object pointer if applicable.
- `notes`: short human-readable summary if useful.

## Event Types

### 1. Function Events

Record selected function entry/exit, not every function.

```json
{
  "frame": 2048,
  "event": "function-enter",
  "pc": "0x00402321",
  "function": "genericVmRunner",
  "args": ["0x00574100"]
}
```

Priority functions:

- `0x00402321` generic VM runner
- `0x00402360` nested VM runner
- `0x00431fe8` active descriptor add
- `0x00432323` active descriptor rebuild
- `0x00432541` active descriptor remove
- `0x00406dbb` selector byte writer
- `0x0040ad9b` selectedRoot indexed writer
- `0x0040adc9` selectedRoot executor
- `0x0040ae0e` selectedRoot direct/inline writer
- `0x00411476` field update/tick route
- `0x00422d74` input poll
- `0x00427730` RNG
- `0x0042449c` map/resource loader
- `0x00423a2f` resource mini-stream runner

### 2. VM Opcode Events

This is the most important trace layer.

For every opcode executed by `0x00402321` or `0x00402360`, record:

- current object
- current stream VA before execution
- opcode
- raw command bytes
- handler VA
- decoded operands if available
- next stream VA after execution
- whether `object+0x40` changed

Example:

```json
{
  "frame": 2048,
  "event": "vm-op",
  "runner": "0x00402321",
  "object": "0x00574100",
  "stream": "0x0047e688",
  "opcode": "0x6e",
  "raw": "6e 01 00 00 7c e6 47 00",
  "handler": "0x0040....",
  "decoded": "active group-key branch",
  "nextStream": "0x0047e690"
}
```

Opcode groups that must be logged clearly:

- `0x04`: call/push continuation
- `0x05`: return/pop continuation
- `0x07`: create child object
- `0x28`: runtime object slot store/load
- `0x29`: runtime object slot destroy
- `0x2f`: prompt/text command
- `0x31`, `0x32`: global flag set/check
- `0x40`: bind/input/base
- `0x60`, `0x61`, `0x62`, `0x63`: descriptor index queue/reset/add/remove
- `0x6d`: active group key set
- `0x6e`: active group key branch
- `0x81`, `0x82`: selectedRoot store/call
- `0x84`: prompt wait
- `0xe6`: active descriptor count branch

### 3. Memory Write Events

Record writes to watched globals and watched object fields.

Example:

```json
{
  "frame": 2049,
  "event": "mem-write",
  "pc": "0x00431fe8",
  "address": "0x004576e8",
  "symbol": "activeDescriptorCount",
  "old": "0x00",
  "new": "0x01"
}
```

If possible, include:

- write width: `u8`, `u16`, `u32`
- old value
- new value
- current object pointer
- current VM stream if the write happened inside a VM opcode handler

### 4. Object Stream Events

Whenever an object script pointer changes, emit a dedicated event.

Watched fields:

- `object+0x40`: current VM stream
- `object+0x44`: return stack base area
- `object+0x5c`: return stack depth
- `object+0x62`: timer/state
- `object+0x64`: delayed stream
- `object+0xec`: interaction/active script candidate

Example:

```json
{
  "frame": 2050,
  "event": "object-stream-change",
  "pc": "0x0040adc9",
  "object": "0x00574100",
  "field": "+0x40",
  "old": "0x0047e664",
  "new": "0x004a2d38",
  "reason": "selectedRoot executor"
}
```

### 5. Engine Events

These are high-level events that make traces easier to read.

Useful engine events:

- `input`
- `rng`
- `resource-load`
- `resource-release`
- `map-load`
- `map-transition`
- `battle-enter`
- `battle-formation-select`
- `battle-action-start`
- `prompt-open`
- `prompt-choice`
- `prompt-close`
- `sound-play`
- `draw-surface`

Example:

```json
{
  "frame": 3001,
  "event": "resource-load",
  "pc": "0x00423a2f",
  "slot": "0x30",
  "resource": "compile.cns",
  "sourceStream": "0x004a3cc4"
}
```

## Watch Groups

The tracer should be configurable by watch groups. This allows focused traces
without overwhelming output.

### `vm-core`

Use this by default.

Watch:

- `0x00402321` generic VM runner
- `0x00402360` nested VM runner
- all `object+0x40` writes
- all `object+0x44/+0x5c/+0x62/+0x64/+0xec` writes

### `selected-root`

Watch:

- `0x0059de30` selectedRoot
- `0x004576da` selector A
- `0x004576db` selector B
- `0x00406dbb`
- `0x0040ad9b`
- `0x0040adc9`
- `0x0040ae0e`

### `descriptor-stack`

Watch:

- `0x004576e8` active descriptor count
- `0x004576e9` active descriptor id order
- `0x00457750` descriptor slot table
- `0x0059db30` active slot pointers
- `0x0059dd70` runtime object pointers
- `0x00431fe8` add descriptor
- `0x00432323` rebuild descriptor
- `0x00432541` remove descriptor

### `field`

Watch:

- `0x00411476` field update/tick route
- `0x00422d74` input poll
- `0x0059e310` current input mask
- `0x0059e312` edge input mask
- `0x00595af0` live layer0 tile grid
- `0x0058d7d0` layer1/collision/flags
- `0x005957d0` visible/dirty tile flags
- `0x004576dc` view x
- `0x004576de` view y
- `0x00595ada` map width

### `scene`

Watch:

- prompt opcode `0x2f`
- prompt wait opcode `0x84`
- selectedRoot events
- global flag bitset writes at `0x0059db60`
- choice/branch opcodes around prompt streams

### `encounter`

Watch:

- `0x00411476` field tick
- `0x00427730` RNG
- `0x0040bcc9` RNG selector candidate
- `0x0040c084` formation consumer candidate
- `0x00423a2f` resource runner
- `0x0042449c` map/resource loader

### `battle`

Watch:

- battle actor stat/result fields
- damage result writes
- hit/miss/guard/critical flags
- battle action VM stream
- sound helper calls

### `resource`

Watch:

- CNS/MLK/WLK/MID resource load/release
- surface/resource slot id
- source stream VA that requested the load

### `draw-lite`

Do not log every pixel or every draw by default.

Record only:

- DirectDraw surface id
- source rect
- destination rect
- color key flag
- caller PC
- current scene/map hint

## Capture Profiles

### Default Profile

Enable:

- `vm-core`
- `selected-root`
- `descriptor-stack`
- `field`
- `resource`

Disable by default:

- full instruction trace
- full draw trace
- full memory dump

### Burst Capture

The most useful feature is a bounded capture window:

- keep a rolling buffer for the previous 60 frames
- when a trigger fires, keep recording for 180 frames
- flush the combined window to JSONL

Useful triggers:

- input edge bit changes
- selectedRoot write
- descriptor count changes
- map id/resource changes
- prompt opens
- battle enter
- object+0x40 jumps to a new high-value stream

## Output Format

Prefer JSONL for large traces:

```text
trace-hud-open-0001.jsonl
trace-map-transition-0002.jsonl
trace-encounter-0003.jsonl
```

Each line is one event object.

Also write a small manifest:

```json
{
  "traceId": "hud-open-0001",
  "game": "Hwanse2.exe",
  "profiles": ["vm-core", "selected-root", "descriptor-stack", "field"],
  "startFrame": 12000,
  "endFrame": 12240,
  "trigger": "input-edge-0x0200",
  "mapHint": "map1_02b",
  "notes": "ESC/X opened status menu"
}
```

## First Capture Scenarios

The following scenarios unlock the most blocked static-analysis fronts.

### 1. Normal Field Menu Open/Close

Steps:

1. Stand still on a field map.
2. Press `ESC` or `X`.
3. Let status/menu HUD fully open.
4. Press `ESC` or `X` again.
5. Let it close.

Capture profiles:

- `vm-core`
- `selected-root`
- `descriptor-stack`
- `field`
- `resource`

Questions this should answer:

- What stream/root opens the status menu?
- Does `0x004ddc6c` execute in normal field play?
- Which stream executes `0x62` descriptor add, if any?
- Who changes `object+0x40` near menu open?

### 2. Prompt and Choice Flow

Steps:

1. Trigger a dialogue prompt.
2. Advance with confirm.
3. Trigger a choice prompt if possible.
4. Select one option.

Capture profiles:

- `vm-core`
- `selected-root`
- `scene`
- `descriptor-stack`

Questions:

- Which stream emits `0x2f` prompt payload?
- How does `0x84` wait resume?
- Which selectedRoot or branch changes after a choice?

### 3. Map Transition

Steps:

1. Walk to a known exit.
2. Trigger map transition.
3. Stop after the new map appears.

Capture profiles:

- `vm-core`
- `field`
- `resource`
- `selected-root`

Questions:

- Which event/root decides the destination?
- Is the transition direct field-controller logic or VM script-driven?
- Which resource stream loads the target map?

### 4. Field Encounter

Steps:

1. Walk in an encounter-enabled field.
2. Continue until battle starts.
3. Stop after the battle background and monsters are selected.

Capture profiles:

- `field`
- `encounter`
- `resource`
- `vm-core`

Questions:

- Which walking/tick state calls RNG?
- Which table selects monster formation?
- Which table selects battle background?

### 5. Map Animation

Steps:

1. Stand near an animated map area, such as fire or waterfall.
2. Record several seconds without input.

Capture profiles:

- `field`
- `resource`
- `draw-lite`

Questions:

- Is animation done by layer0 tile rewrite, palette change, dirty redraw, or draw-time source shift?
- Which function/stream writes animated tile ids or marks dirty cells?

## Performance Rules

- Do not trace every CPU instruction by default.
- Use address/function hooks and watched writes.
- Use a ring buffer for pre-trigger history.
- Deduplicate repeated unchanged state snapshots.
- Allow per-profile enable/disable.
- Allow temporary full instruction trace only around a tiny function range and
  short frame window.

## Minimum Useful Implementation

If time is limited, implement only this first:

1. JSONL event writer.
2. Function enter hooks for:
   - `0x00402321`
   - `0x00402360`
   - `0x00431fe8`
   - `0x00432323`
   - `0x00432541`
3. VM opcode logger for `object+0x40` streams.
4. Write watch for:
   - `object+0x40`
   - `0x0059de30`
   - `0x004576da`
   - `0x004576db`
   - `0x004576e8`
   - `0x004576e9`
   - `0x0059db30`
   - `0x0059dd70`
   - `0x0059e310`
   - `0x0059e312`
5. Burst capture around input edge changes and selectedRoot writes.

This minimum tracer should already be enough to attack HUD opener, prompt route,
and descriptor attachment producer gaps.

## What Not To Do

- Do not rely on screenshots as primary evidence.
- Do not dump every instruction for minutes of gameplay.
- Do not treat raw bytes that look like opcodes as executed commands unless a
  live stream reaches them.
- Do not use host/browser memory addresses without also reporting original EXE
  VAs.
- Do not collapse multiple object streams into one timeline without object id.

## Expected Handoff Result

After implementation, provide trace files for the first scenario:

- `trace-hud-open-0001.jsonl`
- `trace-hud-open-0001.manifest.json`

The most important thing to verify in that trace is:

> From the moment `ESC/X` is pressed, what exact VM stream and/or descriptor
> operation creates the normal field status/menu HUD?

