re: the pad word is REMAPPED -- my own bit table was mislabelled throughout
input-pad-read-path.md says of the word the C_PAD_DECODER reads: "There is no shift and no remap on the way in -- the bit positions are XINPUT's own." That is wrong. sub_8220D500 rebuilds the word out of XINPUT_GAMEPAD before anything else sees it, into the game's own numbering. bits 0-3 A B X Y bits 4-7 left stick UP DOWN LEFT RIGHT (+/-20000 of 32767) bits 8-11 right stick UP DOWN LEFT RIGHT bits 12-15 D-pad UP DOWN LEFT RIGHT bits 16-17 START, BACK bits 18-19 LB, RB bits 20-21 LT, RT -- digital, threshold >220 of 255 bits 22-23 L3, R3 Extracted mechanically from the image, no row typed by hand. The control is the shape of the result: the 24 assignments land on bits 0..23, each used exactly once, none repeated. A misdecode does not produce a bijection over a contiguous range, and coincidence does not put the stick and D-pad directions in the same order in two aligned nibbles. So every mask in that page's tables names the wrong button. The 0xE000 x18 site, read there as "B | X | Y", is "D-pad DOWN | LEFT | RIGHT" -- eighteen sites testing a menu cursor, which is what 18 sites should be. And its headline negative is REFUTED: "LB and RB are not menu inputs" is false. They are bound at config fields this+0x70 and this+0x84, LT/RT at +0x74/+0x80. The negative was searched for 0x0100/0x0200 -- LB and RB in XINPUT's numbering -- in a word where they live at 0x40000/0x80000. Right function, right buttons, wrong bit positions, so it could only come back empty. A negative is only as good as the numbering it was searched in. Also decodes the ring record: +12 HELD, +16 PRESSED, +20 RELEASED, +28/+32 raw trigger bytes. Edge and level are one struct four bytes apart, which displaces that page's guess that press-vs-hold was split between GetState and the XamInputGetKeystrokeEx queue. The superset claim in that page survives and is untouched: sub_82457038 really does compare every XINPUT_GAMEPAD field, and it really is XINPUT-layout. This page depends on it. Not decoded: which output bit means which ACTION, and per-screen sets. 5 of 18 output-bit sites did not resolve to a pad guard, so the output map is a lower bound -- in particular "START is not tested" is NOT claimed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
This commit is contained in:
@@ -143,6 +143,7 @@ files, which is how the same ground got covered twice.
|
||||
| [`h3-units-per-frame-measured.md`](h3-units-per-frame-measured.md) | How many keyframe units elapse per guest frame, and which declared time the title's settle anchor is | ✅ **measured against a pre-registration, 2026-09-01. 2 units per guest frame**, on `ptbtn00`'s own declared ramp (`t=214→236`, T=22): three consecutive gap-free steps of **exactly 23** = 255×2/22, ramp span 10 labels against a predicted 11 (±1). The Port's inferred **5 is excluded by >2×**. 🔴 Why 5 appeared: **an alpha step is not a clock rate** — `Δα/frame = 255·(units/frame)/T`, so splash B's 34-with-T=15 and the plate's 23-with-T=22 are ONE clock. Also: the **title settle anchor is t≈160**, not t=118 — it is `ptcopyright`, the last build-in element and the only glyph one, full at label 5350 → t≈168–176. 🔴 **units/SECOND is NOT settled** and two of my own captures disagree ~2.9× on it; the gap is in frames→seconds, not units→frames. |
|
||||
| [`h3-units-per-frame-preregistration.md`](h3-units-per-frame-preregistration.md) | The prediction, committed before the capture was read | ✅ kept as the control on the row above |
|
||||
| [`input-pad-read-path.md`](input-pad-read-path.md) | What the game asks the console for, on the pad | ✅ **decoded from the image, 2026-09-01.** `sub_82457038` is the only function that reads controller *data*, and it compares **every** field of `XINPUT_GAMEPAD` — `wButtons` (full 16 bits), both triggers, all four stick axes — against the previous state. **14/14** loads verified byte-for-byte against `/image/sylpheed.pe`, database used as an index only. **Two input paths**: the polled state and an 8-byte-record `XamInputGetKeystrokeEx` ring. ⚠️ This is the **superset the game can see, not the per-screen set** — reading a field is not acting on it. ❔ Which bits each screen tests is open; footholds are the image's own `C_PAD_DECODER` / `C_PAD_RINGBUF` trace strings (`sub_8220B610` / `sub_821A6470`). |
|
||||
| [`input-button-numbering-is-remapped.md`](input-button-numbering-is-remapped.md) | The bit numbering the game's pad decoder actually uses | ✅ **DECODED ⟨image⟩**, and 🔴 **refutes `input-pad-read-path.md`'s central claim** that there is *"no shift and no remap — the bit positions are XINPUT's own"*. `sub_8220D500` rebuilds the word out of `XINPUT_GAMEPAD` into the game's numbering: bits **0–3 A B X Y**, **4–7 left stick** (±20000), **8–11 right stick**, **12–15 D-pad**, **16–17 START/BACK**, **18–19 LB/RB**, **20–21 LT/RT** (digital >220), **22–23 L3/R3**. ✅ **Control**: the 24 assignments land on bits 0…23, each used exactly once — a misdecode does not yield a clean bijection. 🔴 **The old page's negative "LB and RB are not menu inputs" is FALSE** — bound at config fields `+0x70`/`+0x84`; it was searched at XINPUT's bit positions in a word that does not use them. ✅ Also decodes the ring record: `+12` HELD, `+16` PRESSED, `+20` RELEASED, `+28`/`+32` raw trigger bytes — so **edge and level are one struct** and the keystroke queue is not needed to tell a press from a hold. 📌 The left stick is digitised to 4 direction bits at 61 % deflection, which is why the game cannot move a cursor at a speed (play-test finding 2). ⚠️ Not decoded: which output bit means which ACTION, and per-screen sets; 5 of 18 output sites unresolved, so the map is a lower bound |
|
||||
| [`ui-splash-draw-pass.md`](ui-splash-draw-pass.md) | The splash draw pass — is there a post-process, and where does the fade come from | ✅ **decoded from GPU state, 2026-09-01.** **No post-process pass exists**: over all 1 048 draws of both splashes, one render target (`rt0=[tile=0…]` 1 048/1 048), one pitch, no MSAA, only kColorDepth/kCopy modes, resolve destinations only the two front buffers, and **no texture base anywhere equals a resolve destination**. The only texture bound is the sprite page. Three trivial pixel shaders; the sprite shader **premultiplies**, so `ONE/ONE_MINUS_SRC_ALPHA` is algebraically **source-over**. Parameters come from **per-vertex `k_8_8_8_8` colour** in a per-frame vertex buffer — the shaders read **zero** float constants (`ps_c[n=0]` 1 048/1 048). Ramp reproduces the ✅ 34/frame law. ⚠️ Censusing the whole 600-frame log instead finds the attract movie's 640×360 chroma planes and reads as a half-res blur chain — the frame window is what avoids that. |
|
||||
| [`splash-glow-is-a-baked-texture.md`](splash-glow-is-a-baked-texture.md) | What the splash "blur" actually is, and which quad is which sprite | ✅ **DECODED (disc), confirmed against the oracle 8/8.** Answers play-test finding 4 as a mechanism: **there is no blur pass and no filter — each logo ships a SECOND texture that IS the blur.** `palogo_<x>_eff.t32` is the same artwork **outset by exactly 10 px on every side**, concentric to ≤ 1.5 px, drawn as its own alpha-over quad. ✅ **The capture's Q0…Q7 are now NAMED**: predicting each NDC rect from the declared position + decoded sprite size matches all eight bijectively, every match ≤ 0.0061 with every runner-up ≥ 0.0272 (4.5–8.9× margin — the control). 🔴 **Refutes `splash-quad-timeline.txt`'s "the same rects scaled slightly larger"**: x/y scale factors differ by up to 0.28, so a port must **load the `_eff` texture, not transform the logo**. ✅ **Blend bit tested OUT of sample** on entries 10/11 (never in its 35-row fit): pre-registered `additive = false` for all eight against 0 additive draws in 1 048 — held 8/8, control still reports 9 additive on entry 6. 📌 The ⟨our-reader⟩ 🟡 on splash geometry resolves **in the reader's favour** — the prediction is ours, the target is the oracle, so agreement is evidence *about* the reader |
|
||||
| [`ui-keyframe-record-layout.md`](ui-keyframe-record-layout.md) | A keyframe's time word comes **before** its pose — the placement record, decoded | ✅ CONFIRMED, **decoded**. A group is an 8-byte header then `frames` records of `{u32 time; 36-byte pose}`, so the time precedes the pose; the group's lead-in word at `header+8` is pose 0's time and **every** pose is timed. Disc-wide over 13 991 groups in 33 archives, each test with a control: lead-in prepended is non-decreasing **13 991/13 991**; a non-zero lead-in is strictly below the next time **5 058/5 058** (control 70.9 %); a multi-segment alpha ramp runs at a constant `dα/dt` **857/1 540** against **0/1 042** under the old reading. 🔴 Retires two long-standing corpus claims — *"a group's data stops 4 bytes short of its final block's time slot"* and *"the last keyframe carries no time"* — both of which were this off-by-one. Adoption is free: all 12 `GP_TITLE` builds render byte-identically, and over 217 builds only two elements pick a different `rest()` pose, both between equally invisible ones. ❔ the executable's own parser was **not** found (the 40/60 stride query is weak, not negative) |
|
||||
|
||||
52
docs/re/data/input-decoder-output-map.txt
Normal file
52
docs/re/data/input-decoder-output-map.txt
Normal file
@@ -0,0 +1,52 @@
|
||||
# C_PAD_DECODER: what sets each bit of the OUTPUT word at this+0x24C.
|
||||
# Guards are in the RING word's numbering (see ringmap.txt), NOT XINPUT's.
|
||||
# 'cfg +0xNN' = a REMAPPABLE binding written by the ctor sub_8220B610
|
||||
# 'literal' = a mask hard-coded in the update sub_8220B8C0
|
||||
# '(internal)'= guarded by decoder state, not by a pad bit, in this window
|
||||
|
||||
site out bit guard means
|
||||
8220BBB0 0x000004 (internal) -
|
||||
8220BE78 0x002000 cfg +0x90 R3
|
||||
8220BEBC 0x000002 cfg +0x84 RB
|
||||
8220BF00 0x000001 cfg +0x80 RT>220
|
||||
8220BFB8 0x000800 cfg +0x74 LT>220
|
||||
8220C0B0 0x000800 cfg +0x70 LB
|
||||
8220C118 0x000020 (internal) -
|
||||
8220C13C 0x000040 (internal) -
|
||||
8220C2F0 0x000010 cfg +0xA0 B
|
||||
8220C338 0x000100 cfg +0x7C X
|
||||
8220C37C 0x200000 cfg +0xA4 DPAD DOWN
|
||||
8220C404 0x010000 cfg +0x98 BACK
|
||||
8220C458 0x040000 literal LS LEFT
|
||||
8220C474 0x080000 literal LS RIGHT
|
||||
8220C490 0x000200 literal LS UP
|
||||
8220C4AC 0x000400 literal LS DOWN
|
||||
8220C4EC 0x100000 (internal) -
|
||||
8220CB48 0x000008 (internal) -
|
||||
|
||||
# 13 of 18 output bits resolve to a pad guard.
|
||||
|
||||
# every config field the ctor sets to a ring-word button set:
|
||||
# +0x4C = 0x00000100 RS UP
|
||||
# +0x64 = 0x00000001 A
|
||||
# +0x70 = 0x00040000 LB
|
||||
# +0x74 = 0x00100000 LT>220
|
||||
# +0x7C = 0x00000004 X
|
||||
# +0x80 = 0x00200000 RT>220
|
||||
# +0x84 = 0x00080000 RB
|
||||
# +0x8C = 0x00400000 L3
|
||||
# +0x90 = 0x00800000 R3
|
||||
# +0x94 = 0x00000001 A
|
||||
# +0x98 = 0x00020000 BACK
|
||||
# +0x9C = 0x00000008 Y
|
||||
# +0xA0 = 0x00000002 B
|
||||
# +0xA4 = 0x00002000 DPAD DOWN
|
||||
# +0xAC = 0x00000014 X | LS UP
|
||||
# +0xB4 = 0x0000000A B | Y
|
||||
# +0xB8 = 0x0000005A B | Y | LS UP | LS LEFT
|
||||
# +0xBC = 0x0000000A B | Y
|
||||
# +0xC0 = 0x0000005A B | Y | LS UP | LS LEFT
|
||||
# +0xC4 = 0x0000000A B | Y
|
||||
# +0xC8 = 0x00000008 Y
|
||||
# +0xD4 = 0x00000002 B
|
||||
# +0xD8 = 0x00000078 Y | LS UP | LS DOWN | LS LEFT
|
||||
32
docs/re/data/input-ring-record-layout.txt
Normal file
32
docs/re/data/input-ring-record-layout.txt
Normal file
@@ -0,0 +1,32 @@
|
||||
# C_PAD_RINGBUF output record -- the tail of sub_8220D500, read from
|
||||
# /image/sylpheed.pe. r3 = the ringbuf; r9 = the XINPUT_GAMEPAD source.
|
||||
# This is where a menu gets edge-vs-level, and it is one struct, not two paths.
|
||||
|
||||
8220D7C0 7D0A582E
|
||||
8220D7C4 9103000C +12 <- cur : HELD (level)
|
||||
8220D7C8 7D0A582E
|
||||
8220D7CC 80E3000C
|
||||
8220D7D0 7D083278 r8 = cur XOR prev : CHANGED
|
||||
8220D7D4 91030010 +16 <- changed : (overwritten below)
|
||||
8220D7D8 5508003E
|
||||
8220D7DC 7CCA582E
|
||||
8220D7E0 7D063078 r6 = changed ANDC cur : RELEASED this frame
|
||||
8220D7E4 90C30014 +20 <- released : FALLING EDGE
|
||||
8220D7E8 7D6A582E
|
||||
8220D7EC 90E30018 +24 <- cur : HELD (second copy)
|
||||
8220D7F0 7D0B5838 r11 = changed AND cur : PRESSED this frame
|
||||
8220D7F4 91630010 +16 <- pressed : RISING EDGE (final value)
|
||||
8220D7F8 8969002A
|
||||
8220D7FC 9163001C +28 <- bLeftTrigger : RAW ANALOG byte
|
||||
8220D800 8969002B
|
||||
8220D804 91630020 +32 <- bRightTrigger : RAW ANALOG byte
|
||||
8220D808 A169002C
|
||||
8220D80C 7D6B0734
|
||||
|
||||
# So the ring record is:
|
||||
# +12 buttons HELD (level) -- a menu that repeats on hold reads this
|
||||
# +16 buttons PRESSED (rising) -- a menu that fires once per press reads this
|
||||
# +20 buttons RELEASED (falling)
|
||||
# +24 buttons HELD (copy)
|
||||
# +28 bLeftTrigger raw 0..255, analog value preserved alongside the bit
|
||||
# +32 bRightTrigger raw 0..255
|
||||
34
docs/re/data/input-ring-word-remap.txt
Normal file
34
docs/re/data/input-ring-word-remap.txt
Normal file
@@ -0,0 +1,34 @@
|
||||
# C_PAD_RINGBUF button word -- the REMAP from XINPUT_GAMEPAD into the game's
|
||||
# own bit numbering. Built by sub_8220D500 and stored to ringbuf+12.
|
||||
# Every row read straight out of /image/sylpheed.pe (VA - 0x82000000); the
|
||||
# 'raw' column is the instruction word in the image at that address.
|
||||
|
||||
site raw XINPUT source condition ring bit bit#
|
||||
8220D55C 39000001 wButtons A 0x00000001 0
|
||||
8220D578 61080002 wButtons B 0x00000002 1
|
||||
8220D594 61080004 wButtons X 0x00000004 2
|
||||
8220D5B0 61080008 wButtons Y 0x00000008 3
|
||||
8220D5CC 65080001 wButtons START 0x00010000 16
|
||||
8220D5E8 65080002 wButtons BACK 0x00020000 17
|
||||
8220D604 61081000 wButtons DPAD_UP 0x00001000 12
|
||||
8220D620 61082000 wButtons DPAD_DOWN 0x00002000 13
|
||||
8220D63C 61084000 wButtons DPAD_LEFT 0x00004000 14
|
||||
8220D658 61088000 wButtons DPAD_RIGHT 0x00008000 15
|
||||
8220D674 65080004 wButtons LB (left shoulder) 0x00040000 18
|
||||
8220D690 65080008 wButtons RB (right shoulder) 0x00080000 19
|
||||
8220D6AC 65080040 wButtons L3 (left thumb click) 0x00400000 22
|
||||
8220D6C8 65080080 wButtons R3 (right thumb click) 0x00800000 23
|
||||
8220D6E0 65080010 bLeftTrigger bLeftTrigger > 0xDC (220) 0x00100000 20
|
||||
8220D6F8 65080020 bRightTrigger bRightTrigger > 0xDC (220) 0x00200000 21
|
||||
8220D714 61080040 sThumbLX sThumbLX < -20000 0x00000040 6
|
||||
8220D728 61080080 sThumbLX sThumbLX > +20000 0x00000080 7
|
||||
8220D744 61080020 sThumbLY sThumbLY < -20000 0x00000020 5
|
||||
8220D758 61080010 sThumbLY sThumbLY > +20000 0x00000010 4
|
||||
8220D774 61080400 sThumbRX sThumbRX < -20000 0x00000400 10
|
||||
8220D788 61080800 sThumbRX sThumbRX > +20000 0x00000800 11
|
||||
8220D7A4 61080200 sThumbRY sThumbRY < -20000 0x00000200 9
|
||||
8220D7B8 61080100 sThumbRY sThumbRY > +20000 0x00000100 8
|
||||
|
||||
# 24 mappings, covering every field of XINPUT_GAMEPAD.
|
||||
# ring bits used: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23
|
||||
# contiguous 0..23 with none repeated: True
|
||||
176
docs/re/input-button-numbering-is-remapped.md
Normal file
176
docs/re/input-button-numbering-is-remapped.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# 🔴 The pad word is REMAPPED — `input-pad-read-path.md`'s bit table is mislabelled throughout
|
||||
|
||||
**Status: ✅ decoded from the image.** Instrument: ⟨image⟩ — every row read out of
|
||||
`/image/sylpheed.pe` at `VA − 0x82000000`, with the database used for nothing.
|
||||
2026-09-01.
|
||||
|
||||
This **refutes the central claim** of
|
||||
[`input-pad-read-path.md`](input-pad-read-path.md), which says of the word the
|
||||
`C_PAD_DECODER` reads:
|
||||
|
||||
> *"It reads a 32-bit word at `+12` of the `C_PAD_RINGBUF` (`this+76`) and masks
|
||||
> its low 16 bits directly. **There is no shift and no remap on the way in** — the
|
||||
> bit positions are XINPUT's own."*
|
||||
|
||||
**There is a remap.** `sub_8220D500` rebuilds the word bit by bit out of
|
||||
`XINPUT_GAMEPAD` before anything else sees it, and the result is the game's own
|
||||
numbering. Every mask in that page's tables is therefore labelled with the wrong
|
||||
button, and its headline negative is false.
|
||||
|
||||
---
|
||||
|
||||
## 1 — the remap, complete
|
||||
|
||||
[`data/input-ring-word-remap.txt`](data/input-ring-word-remap.txt) — 24 rows, each
|
||||
the instruction word in the image at that address.
|
||||
|
||||
| ring bit | mask | is | from |
|
||||
|---|---|---|---|
|
||||
| 0–3 | `0x1` `0x2` `0x4` `0x8` | **A, B, X, Y** | `wButtons` `0x1000 0x2000 0x4000 0x8000` |
|
||||
| 4–7 | `0x10` `0x20` `0x40` `0x80` | **left stick UP, DOWN, LEFT, RIGHT** | `sThumbLY/LX` vs **±20000** |
|
||||
| 8–11 | `0x100` `0x200` `0x400` `0x800` | **right stick UP, DOWN, LEFT, RIGHT** | `sThumbRY/RX` vs **±20000** |
|
||||
| 12–15 | `0x1000` … `0x8000` | **D-pad UP, DOWN, LEFT, RIGHT** | `wButtons` `0x1 0x2 0x4 0x8` |
|
||||
| 16–17 | `0x10000` `0x20000` | **START, BACK** | `wButtons` `0x10 0x20` |
|
||||
| 18–19 | `0x40000` `0x80000` | **LB, RB** | `wButtons` `0x100 0x200` |
|
||||
| 20–21 | `0x100000` `0x200000` | **LT, RT** — digital | `bLeft/RightTrigger` **> 220** |
|
||||
| 22–23 | `0x400000` `0x800000` | **L3, R3** | `wButtons` `0x40 0x80` |
|
||||
|
||||
**The control is the shape of the result.** Extracted mechanically — no row typed
|
||||
by hand — the 24 assignments land on bits **0…23, each used exactly once, none
|
||||
repeated**. A misdecode does not produce a perfect bijection over a contiguous
|
||||
range, and a coincidence does not put the four stick directions and the four
|
||||
D-pad directions in the same order in two aligned nibbles.
|
||||
|
||||
**Both analog axes and both triggers are digitised here**, at ±20000 of 32767
|
||||
(61 %) for the sticks and >220 of 255 (86 %) for the triggers. The raw analog
|
||||
values are *not* discarded — see §3.
|
||||
|
||||
## 2 — what that does to the old table
|
||||
|
||||
The old page read masks against XINPUT's numbering. Under the real numbering:
|
||||
|
||||
| mask | old page says | actually is |
|
||||
|---|---|---|
|
||||
| `0xF000` ×1, *"any face button"* | A \| B \| X \| Y | **the whole D-pad** |
|
||||
| `0xE000` ×**18** | B \| X \| Y | **D-pad DOWN \| LEFT \| RIGHT** |
|
||||
| `0x000F` ×1 | the whole D-pad | **A \| B \| X \| Y** |
|
||||
| `0x0003` ×1 | Up \| Down | **A \| B** |
|
||||
| `0x0030` ×1 | START \| BACK | **left stick UP \| DOWN** |
|
||||
| `0x0060` ×1 | BACK \| left thumb | **left stick DOWN \| LEFT** |
|
||||
| `0x00FF` ×5 | D-pad + START + BACK + thumbs | **A B X Y + all four left-stick directions** |
|
||||
| `0x0001` | D-pad Up | **A** |
|
||||
| `0x0010` | START | **left stick UP** |
|
||||
| `0x0020` | BACK | **left stick DOWN** |
|
||||
|
||||
The `0xE000` count is the tell: **18 sites** testing "D-pad DOWN, LEFT or RIGHT"
|
||||
is a menu cursor. Eighteen sites testing "B, X or Y" never made sense.
|
||||
|
||||
⚠️ A second, independent defect in that table: it counted `rlwinm` sites without
|
||||
checking **which register** each masks. Several attributed to D-pad bits —
|
||||
`0x8220BB9C`, `0x8220BEA8`, `0x8220BEEC` — mask the decoder's own state word at
|
||||
`this+52`, not a pad word at all.
|
||||
|
||||
### 🔴 And its headline negative is REFUTED
|
||||
|
||||
> *"The shoulder buttons are the only pad inputs the decoder never tests …
|
||||
> **LB and RB are not menu inputs.** A binding table that maps them to anything is
|
||||
> mapping them to nothing."*
|
||||
|
||||
**False.** [`data/input-decoder-output-map.txt`](data/input-decoder-output-map.txt):
|
||||
|
||||
| decoder reads | via | sets output bit |
|
||||
|---|---|---|
|
||||
| **LB** | config field `this+0x70` = `0x00040000` | `0x000800` |
|
||||
| **RB** | config field `this+0x84` = `0x00080000` | `0x000002` |
|
||||
| **LT** | `this+0x74` = `0x00100000` | `0x000800` |
|
||||
| **RT** | `this+0x80` = `0x00200000` | `0x000001` |
|
||||
| L3 / R3 | `this+0x8C` / `this+0x90` | — |
|
||||
| B, X, BACK, D-pad DOWN | `+0xA0`, `+0x7C`, `+0x98`, `+0xA4` | `0x10`, `0x100`, `0x010000`, `0x200000` |
|
||||
| left stick UP/DOWN/LEFT/RIGHT | hard-coded literals | `0x200`, `0x400`, `0x040000`, `0x080000` |
|
||||
|
||||
**Why the old negative was wrong is the useful part.** It searched for `0x0100`
|
||||
and `0x0200` — LB and RB *in XINPUT's numbering* — and found only `ori`. But in
|
||||
the word the decoder actually reads, LB and RB are `0x00040000` and `0x00080000`.
|
||||
It was looking in the right function for the right buttons at the wrong bit
|
||||
positions, so it could only ever find them absent. **A negative is only as good as
|
||||
the numbering it was searched in.**
|
||||
|
||||
📌 The bindings are **not immediates in the update** — they are fields the
|
||||
constructor `sub_8220B610` writes, so this layer is remappable by design and a
|
||||
reader must go through the constructor to know what any output bit means.
|
||||
|
||||
## 3 — the ring record: edge and level, in one struct
|
||||
|
||||
[`data/input-ring-record-layout.txt`](data/input-ring-record-layout.txt), the tail
|
||||
of `sub_8220D500`:
|
||||
|
||||
| offset | is |
|
||||
|---|---|
|
||||
| `+12` | buttons **HELD** (level) |
|
||||
| `+16` | buttons **PRESSED** this frame — `(cur XOR prev) AND cur` |
|
||||
| `+20` | buttons **RELEASED** this frame — `(cur XOR prev) ANDC cur` |
|
||||
| `+24` | buttons HELD, second copy |
|
||||
| `+28` / `+32` | `bLeftTrigger` / `bRightTrigger`, **raw 0…255** |
|
||||
|
||||
📌 **This displaces a guess in the old page.** It said *"a menu that responds to a
|
||||
**press** is likely reading the queue; anything that responds to a **hold** must be
|
||||
reading the polled state,"* offering `XamInputGetKeystrokeEx` as the edge source.
|
||||
Edge and level are both **right here**, computed by the game, four bytes apart.
|
||||
Nothing needs the keystroke queue to tell a press from a hold. The update reads
|
||||
`+12` (held) and `+16` (pressed) and picks per action.
|
||||
|
||||
The triggers keep their analog value beside the digital bit, so a screen wanting
|
||||
a proportional trigger has it.
|
||||
|
||||
## The set, as the brief asked for it
|
||||
|
||||
Every entry ⟨image⟩ — **decoded**, none measured, none guessed.
|
||||
|
||||
| input | reaches the decoder? | as |
|
||||
|---|---|---|
|
||||
| A, B, X, Y | ✅ | ring bits 0–3 |
|
||||
| D-pad ×4 | ✅ | ring bits 12–15 |
|
||||
| START | ✅ digitised | ring bit 16 |
|
||||
| BACK | ✅ | ring bit 17, bound at `+0x98` |
|
||||
| LB, RB | ✅ **contra the old page** | ring bits 18–19, bound at `+0x70` / `+0x84` |
|
||||
| LT, RT | ✅ | digital at **>220**, ring bits 20–21; raw byte at `+28`/`+32` |
|
||||
| L3, R3 | ✅ | ring bits 22–23, bound at `+0x8C` / `+0x90` |
|
||||
| left stick | ✅ | digitised at **±20000**, ring bits 4–7 |
|
||||
| right stick | ✅ | digitised at ±20000, ring bits 8–11 |
|
||||
|
||||
**No field of `XINPUT_GAMEPAD` is dropped**, and the sticks are digitised to four
|
||||
directions each with a 61 % deflection threshold — which is the fact the play-test's
|
||||
finding 2 (*"the left stick moved the cursor far too fast"*) needed: the game gets
|
||||
one direction bit per axis, not a velocity.
|
||||
|
||||
## What is NOT decoded
|
||||
|
||||
* ❔ **Which output bit means which ACTION** (confirm, cancel, up, down). The word
|
||||
at `this+0x24C` is the decoder's output; naming its bits needs the layer above.
|
||||
* ❔ **Per-screen sets.** This is the game-wide layer. Nothing here says which
|
||||
bits the title, the menu or `EXTRAS` acts on.
|
||||
* 🟡 **5 of 18 output-bit sites** did not resolve to a pad guard in the window
|
||||
scanned; they are guarded by decoder state. So the output map is a **lower
|
||||
bound** on what the decoder tests, never an upper one — and in particular
|
||||
**"START is not tested" is NOT claimed**: no config field holds `0x00010000`,
|
||||
but five sites are unaccounted for.
|
||||
|
||||
## Reach
|
||||
|
||||
⟨image⟩, so it holds for every screen — that is the point of doing it statically.
|
||||
It is a fact about the shipped executable, not about a boot. Nothing here has been
|
||||
confirmed against a capture, so no row may be labelled *measured*: the natural
|
||||
next experiment is to read `ringbuf+12` out of guest memory while pressing one
|
||||
button at a time and check the bit that lights.
|
||||
|
||||
## Refutation attempt, recorded per the adversarial duty
|
||||
|
||||
**Target:** `input-pad-read-path.md` §"Which buttons the decoder actually tests —
|
||||
the complete set", and its negative "LB and RB are not menu inputs".
|
||||
|
||||
**Result: REFUTED, both.** The bit table is mislabelled because the word is
|
||||
remapped; the negative is false because it was searched in XINPUT's numbering.
|
||||
That page's ✅ status on the *superset* question (§"What the game reads: the whole
|
||||
of `XINPUT_GAMEPAD`") is **untouched** and survives — it was read off
|
||||
`sub_82457038`'s field comparisons, which really are XINPUT-layout, and this page
|
||||
depends on it.
|
||||
@@ -1,6 +1,23 @@
|
||||
# The pad read path — what the game asks the console for
|
||||
|
||||
**Status: ✅ decoded from the image** for the driver, for the field set, and for
|
||||
> # 🔴 CORRECTION 2026-09-01 — the bit tables below are MISLABELLED
|
||||
>
|
||||
> The word this page calls "XINPUT's own bit positions, no shift and no remap"
|
||||
> is **remapped**. `sub_8220D500` rebuilds it out of `XINPUT_GAMEPAD` before the
|
||||
> decoder sees it, into the game's own numbering: `0x1`=A, `0x10`=left-stick UP,
|
||||
> `0x1000`=D-pad UP, `0x40000`=LB. **Every mask in the two tables below names the
|
||||
> wrong button**, and the negative *"LB and RB are not menu inputs"* is **false** —
|
||||
> they are bound at config fields `this+0x70` and `this+0x84`.
|
||||
>
|
||||
> ✅ **What survives:** §"What the game reads: the whole of `XINPUT_GAMEPAD`".
|
||||
> That was read off `sub_82457038`, which really is XINPUT-layout.
|
||||
>
|
||||
> Corrected in
|
||||
> [`input-button-numbering-is-remapped.md`](input-button-numbering-is-remapped.md).
|
||||
> The sections below are kept for the addresses, which are right; only the
|
||||
> **labels** are wrong.
|
||||
|
||||
**Status: 🔴 partly REFUTED — see the correction above.** ✅ decoded from the image for the driver, for the field set, and for
|
||||
the complete button set `C_PAD_DECODER` tests; ❔ **not decoded** for which of
|
||||
those each individual screen acts on, or for the decoder's own output bit
|
||||
numbering. Instrument: ⟨image⟩ — the executable's own
|
||||
|
||||
Reference in New Issue
Block a user