Files
Sylpheed/docs/re/pad-decoder-double-tap-not-key-repeat.md
sylph-decoder dcaa322146 re: the decoder's timers are a double-tap latch on LB/LT, not key repeat
Answers H1's first half with a negative, and decodes what the timing
constants are for.

C_PAD_DECODER's +0xB4/+0xB8 and +0xBC/+0xC0 (10 and 90, twice) are not a
key-repeat delay/interval pair. They are two identical channels of a
DOUBLE-TAP detector, on cfg +0x74 (LT) and +0x70 (LB), firing output bits
0x20 and 0x40.

The mechanism, from the image: both timers tick down once per update; a
press arms the short timer to 10; a RELEASE adds 1000 to it as a flag
stored inside the counter, which is why the tick watches for exactly 1000
and clears both there; a second press while the flag is set arms the long
timer to 90; and while the long timer runs the output bit is asserted on
EVERY update, with the short timer re-armed to 1010 each time.

So it is a latch, not a repeat: a double-tap opens a 90-update window and
the bit is held for its whole duration. That is a dash or barrel-roll
shape, which fits the buttons it is wired to.

H1: the directions have NO timer. The D-pad and left stick reach the output
word through bare mask tests -- the four left-stick literals at 0x8220C458,
C474, C490, C4AC, and DPAD DOWN through cfg +0xA4 -- with no counter
loaded, decremented or tested on any of those paths. On the evidence of
this layer a held direction does not repeat.

Reach: one layer. A menu could implement repeat on top of a held bit, and
this says nothing about that -- but it does establish the repeat is not in
the shared decoder, so it would have to be per-screen. "One step per
deflection" stays authored for the port; this narrows rather than settles.

Refutation of my own earlier note that a decoder "is where a game normally
puts its repeat timing, its edge detection and its button remap": remap
survives, edge detection survives, repeat timing is REFUTED. That clause
was a prior about how games are written, not a reading of this one.

Also commits tools/ppc-dis, the minimal PowerPC disassembler this corpus
has been rebuilding in scratch and losing to container restarts three times
in one session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-02 16:20:50 +00:00

105 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ✅ 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`](../../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`](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 1215) and the left
stick (ring bits 47) 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`](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`](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.