Chasing F1 I examined a counter block I had skipped. this+0x10 decrements every update and fires output bit 0x4 at zero, guarded against the HELD word by cfg +0xA0; it is re-armed from +0xC4 = 10 and +0xC8 = 8. So my statement that "the only two timed inputs in the whole decoder are LB and LT" is false, and two constants I tabulated as ring-word button masks (B|Y and Y) are counter reload values. A number that is a valid mask AND a valid duration cannot be told apart by its value, only by its use, and I classified those two by value. What it does not change: F1 is still not answered. This channel is guarded by cfg +0xA0 = ring bit 1 = B, not a direction. The D-pad and left-stick paths still carry no counter, so "a held DIRECTION has no repeat timer in C_PAD_DECODER" stands, and the human's observation that it repeats in the real game is consistent with the caveat this page already carried: the repeat is in the layer above. Explicitly NOT offering 10 and 8 as F1's two numbers. They belong to a channel on a button that is not a direction, and at 60 units/s they are 0.167 s and 0.133 s -- about 7 moves a second, which does not match "a medium pace, slow enough to see which item is selected". Reporting them as the menu repeat would be the same read-a-config-infer-a-behaviour error this correction is about. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
7.1 KiB
✅ The decoder's timers are a double-tap detector on LB and LT — and directions have no repeat in this layer
Status: ✅ decoded from the image. Instrument: ⟨image⟩ — /image/sylpheed.pe
via tools/ppc-dis, database not consulted. 2026-09-02.
Answers the first half of H1 — does a held direction repeat in the menus, and at what rate? — with a negative, and decodes what the timers are actually for.
What the timing constants really are
C_PAD_DECODER's constructor sub_8220B610 writes five values that look like a
key-repeat pair — +0xB4 = 10, +0xB8 = 90, +0xBC = 10, +0xC0 = 90. They are
two identical channels of a double-tap detector, and the update
sub_8220B8C0 consumes them like this:
| channel | button | short timer | long timer | output bit |
|---|---|---|---|---|
| A | cfg +0x74 = LT |
this+0 ← +0xB4 = 10 |
this+4 ← +0xB8 = 90 |
0x20 |
| B | cfg +0x70 = LB |
this+8 ← +0xBC = 10 |
this+12 ← +0xC0 = 90 |
0x40 |
Buttons named in the decoder's own bit numbering
(input-button-numbering-is-remapped.md),
not XINPUT's.
The mechanism, in the order the code runs it
1 — both timers tick down once per update (0x8220BAE0):
if this+0 != 0: this+0 -= 1 ; if it lands on exactly 1000: this+0 = 0, this+4 = 0
if this+4 != 0: this+4 -= 1
2 — a press arms the short timer (0x8220BF74): on PRESSED ∧ this+0 == 0,
this+0 = 10.
3 — a release adds 1000 to it (0x8220BFC8, reading 20(r6) = RELEASED):
this+0 += 1000 if it is still running, else 0. The +1000 is a flag stored
inside the counter — "this press has already been released" — which is why the
tick in step 1 watches for the value 1000 and clears both timers there.
4 — a second press while the flag is set arms the long timer
(0x8220BF44): on this+0 > 1000 ∧ this+4 == 0 ∧ PRESSED, this+4 = 90.
5 — while the long timer runs, the output bit fires EVERY frame
(0x8220C0F8):
if this+4 > 0:
this+0 = 10 + 1000 ; re-armed and flagged
this+0x24C |= 0x20 ; the output bit, set on every update
📌 So it is not a repeat, it is a latch. A double-tap opens a 90-update window, and for its whole duration the output bit is asserted continuously. That is the shape of a dash / barrel-roll, not a menu cursor — which fits the buttons it is wired to.
🔴 The H1 answer: no key-repeat in this layer
The directions have no timer at all. The D-pad (ring bits 12–15) and the left
stick (ring bits 4–7) reach the output word through bare mask tests — the four
left-stick literals at 0x8220C458, 0x8220C474, 0x8220C490, 0x8220C4AC, and
DPAD DOWN through cfg +0xA4 — with no counter loaded, decremented or tested
on any of those paths (data/input-decoder-output-map.txt).
On the evidence of
C_PAD_DECODER, a held direction does not repeat. The only two timed inputs in the whole decoder are LB and LT, and what they do is latch, not repeat.
⚠️ Reach, and it matters here. This is one layer. The decoder hands a word to the menus; a menu could implement its own repeat on top of a held bit, and this page says nothing about that. What it does establish is that the repeat is not in the shared input decoder, so it would have to be per-screen — which also means it cannot be answered once for all screens from this function.
For the port: "one step per deflection" remains authored, and this narrows
rather than settles it. The next step is the layer above — the consumer of
this+0x24C — or a capture of a held direction on a real menu, counting cursor
moves against presents.
Refutation attempt, recorded per the adversarial duty
Target: my own earlier note in
input-pad-read-path.md that "a decoder between
the raw wButtons and the menus is where a game normally puts its repeat timing,
its edge detection and its button remap."
Result: two thirds SURVIVE, one third is REFUTED. The remap is there
(24 bits, bijective) and the edge detection is there (+12 held, +16 pressed,
+20 released). The repeat timing is not. The reasoning was "this is where it
normally goes", which is a prior about how games are written, not a reading of
this one — and it is exactly the kind of inference this corpus keeps having to
withdraw.
Reach
⟨image⟩, so it holds for every screen — that is the point of doing it statically. It says nothing about layers above the decoder, and nothing here has been confirmed against a running capture: no row may be labelled measured. The obvious check is to hold a direction on a real menu and count cursor moves per present.
🔴 Correction 2026-09-02 — there is a THIRD timed channel, and my "only two" was wrong
I wrote above: "the only two timed inputs in the whole decoder are LB and LT." That is false. Chasing F1 I examined a counter block I had skipped, and found a third timer on a different shape:
8220BB50 lwz r11,16(r31) ; this+0x10, a counter
8220BB5C addic. r11,r11,-1 ; decrements EVERY update
8220BB64 bne -> skip ; only when it reaches ZERO:
8220BB6C lwz r11,160(r31) ; cfg +0xA0
8220BB70 lwz r10,12(r10) ; ...against the HELD word
8220BBAC ori r11,r11,0x4 ; fires output bit 0x4
and it is re-armed from two more constants I had mis-read as button masks:
8220CB50 lwz r11,196(r31) ; +0xC4 = 10 -> this+0x10
8220CB58 lwz r11,200(r31) ; +0xC8 = 8 -> this+0x14
⚠️ So +0xC4 and +0xC8 are TIMINGS, not masks. My constructor table read
them as ring-word button sets (B|Y and Y) because 10 and 8 are legal masks.
They are counter reload values, exactly like +0xB4/+0xBC = 10. A number that
is a valid mask and a valid duration cannot be told apart by its value — only by
its use, and I classified these by value.
What it does NOT change
F1 is still not answered here. This third channel is guarded by cfg +0xA0,
which is ring bit 1 — B, not a direction. The D-pad and left-stick paths still
carry no counter, so the finding stands where it matters:
A held DIRECTION has no repeat timer in
C_PAD_DECODER.
The human watched the real game and reports that a held direction does repeat.
Both can be true, and the caveat this page already carried is the reason:
the repeat is in the layer above. That is where F1 must be answered — the
consumer of this+0x24C, or a capture holding a direction on a real menu.
⚠️ And I am not offering 10 and 8 as F1's two numbers. They are this channel's constants, on a button that is not a direction. At 60 units/s they would be 0.167 s and 0.133 s — about 7 moves a second, which does not match "a medium pace, slow enough to see which item is selected". Reporting them as the menu repeat would be the same error as reading a config value and inferring a behaviour, which is the error this correction is about.