Skip to content

Commit 522f5db

Browse files
docs: unify per-console README pattern + scale top-level tables
gba/README.md was using a different shape from gamecube/README.md (lineage upfront, build/embed mixed, no Loading/Releases sections); rewrite to follow the same outline as gamecube/: Intro → What it tests → feature deep-dives → Build → Loading → Embedding → Releases → Origin. Top-level README's License section enumerated each console inline, which doesn't scale. Move per-console license links into a new License column on the Consoles table; License section just points there. Codify both decisions in CLAUDE.md so future consoles drop into the existing structure: per-console README outline (sections + order) and top-level "tables only, no enumerated prose" rule.
1 parent 1baa76b commit 522f5db

3 files changed

Lines changed: 208 additions & 115 deletions

File tree

CLAUDE.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,46 @@ subdirs" list.
5757
| `README.md` | Audience-facing overview: what the app is, how to build, how to embed. |
5858
| `Makefile` | Build entrypoint. Use a Docker-based toolchain if it eases CI. |
5959

60+
### README structure (every console subdir)
61+
62+
Each `<console>/README.md` follows the same top-to-bottom outline so
63+
they're navigable as a set. Sections marked *(optional)* are included
64+
only when the console has that surface; everything else is required.
65+
66+
1. **Title**`# Joypad Tester — <Console>`
67+
2. **Intro** — one paragraph: what this build does and a link back to
68+
the top-level repo (`[Joypad Tester](../README.md)`).
69+
3. **`## What it tests`** — concrete ASCII example of the on-screen
70+
output, then a table covering the per-port / per-variant matrix
71+
(controller types, payload variants, etc.). Always state which
72+
fields are unavailable on this platform so reading "zeros" isn't
73+
ambiguous.
74+
4. **`## (feature deep-dives)`** — one `##` section per non-obvious
75+
subsystem (accessory paks, alt protocols, BIOS quirks, idle modes,
76+
etc.). Use as many as needed. Keep them concrete: protocol bytes,
77+
timings, register addresses where relevant.
78+
5. **`## Build`** — preferred toolchain commands (devkitPro / devkitARM
79+
/ etc.) followed by the Docker fallback (`./build_docker.sh` or a
80+
bare `docker run` line). End with a link to
81+
`.github/workflows/verify-build.yml`.
82+
6. **`## Loading on hardware`** — how the artifact gets onto real
83+
hardware: SD-card layout, multiboot upload, flash cart slot, etc.
84+
Include the canonical loader (Swiss, joypad-os, etc.) where it
85+
applies.
86+
7. **`## Embedding`** *(optional)* — only when the subdir produces a
87+
payload another product consumes. State the symbol names and the
88+
reference uploader file.
89+
8. **`## Releases`** — one paragraph: tag format (`<console>-v<semver>`),
90+
link to `CHANGELOG.md`, list of artifacts the release workflow
91+
attaches.
92+
9. **`## Origin / credits`** — upstream lineage (project + license +
93+
link), what this subtree adds, and a link to `LICENSE.md`.
94+
95+
`gamecube/README.md` is the canonical example. When adding a new
96+
console, copy its skeleton and fill each section in — don't reorder or
97+
rename headings, because the top-level README and CLAUDE.md both
98+
reference them.
99+
60100
### Build outputs
61101

62102
Intermediates (`*.o`, `*.d`, `*.elf`, `*.map`) should be `.gitignore`d.
@@ -121,6 +161,22 @@ To cut, e.g., `gba-v0.2.0`:
121161
If `VERSION` doesn't match the tag, the release fails (intentional —
122162
forces the changelog and version-bump commits to land before the tag).
123163

164+
## Top-level README
165+
166+
The repo-root `README.md` stays a thin index — its only per-console
167+
state lives in two tables:
168+
169+
- **Consoles** — `Console | Status | Path | License`. The License
170+
column links to `<console>/LICENSE.md` so the bottom-of-page License
171+
section never has to enumerate per-console licenses.
172+
- **Acknowledgements** — `Console | Origin / inspiration`. One row per
173+
console summarising upstream lineage; deep credits live in each
174+
console's `README.md` "Origin / credits" section.
175+
176+
Adding a console = adding one row to each table. Never enumerate
177+
consoles inline in prose anywhere else in the top-level README — it
178+
breaks at N consoles.
179+
124180
## When extending or refactoring
125181

126182
- Top-level scaffolding (workflows, CLAUDE.md, repo-wide README, top-

README.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ A collection of homebrew controller test ROMs across game consoles. Each console
44

55
## Consoles
66

7-
| Console | Status | Path |
8-
|---|---|---|
9-
| GameCube | working | [`gamecube/`](gamecube/) |
10-
| Game Boy Advance | working | [`gba/`](gba/) |
7+
| Console | Status | Path | License |
8+
|---|---|---|---|
9+
| GameCube | working | [`gamecube/`](gamecube/) | [zlib](gamecube/LICENSE.md) |
10+
| Game Boy Advance | working | [`gba/`](gba/) | [MIT](gba/LICENSE.md) |
1111

1212
## Per-console
1313

@@ -30,8 +30,6 @@ Originating copyrights are preserved in each console's source headers.
3030

3131
## License
3232

33-
Top-level repo (CI, build infra, this README): [MIT](LICENSE.md). Each
34-
console subdir carries its own license file matching its upstream
35-
origin — `gamecube/` is zlib via corenting's GC-Controller-Test (see
36-
[`gamecube/LICENSE.md`](gamecube/LICENSE.md)); `gba/` is MIT via
37-
Doridian's Joybus-PIO (see [`gba/LICENSE.md`](gba/LICENSE.md)).
33+
Top-level repo scaffolding (CI, build infra, this README): [MIT](LICENSE.md).
34+
Each console subdir carries its own `LICENSE.md` matching its upstream
35+
origin — see the **License** column of the Consoles table above.

gba/README.md

Lines changed: 145 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,165 @@
1-
# joypad-tester / gba
2-
3-
GBA multiboot payloads that ship inside [joypad-tester](..). Two ROMs
4-
build from this one source tree:
5-
6-
- **`joypad_mb.gba`** — Doridian-style joybus controller + animated
7-
cartoon eyes overlay. Consumed by
8-
[joypad-os](https://github.com/joypad-ai/joypad-os) via submodule for
9-
its GBA-as-controller mode.
10-
- **`tester_mb.gba`** — Doridian-style joybus controller + an on-GBA
11-
console showing live button state, with an idle screensaver that
12-
bounces the joypad logo (color-cycling on each wall hit) à la the
13-
GameCube tester. Embedded into joypad-tester's GameCube/Wii host so
14-
testers see visual feedback on the GBA, and runnable standalone from
15-
a flash cart as a pure button-tester ROM.
16-
17-
The `build/` tree ships pre-built artifacts so consumers using this
18-
folder as a submodule (e.g. joypad-os) don't need devkitARM unless
19-
they're modifying the source:
1+
# Joypad Tester — Game Boy Advance
2+
3+
Game Boy Advance build of the [Joypad Tester](../README.md). Two
4+
multiboot ROMs built from one source tree: one ships as the GBA-as-
5+
controller payload that joypad-os multiboots over the GameCube link
6+
cable, the other is a standalone tester ROM you can multiboot from this
7+
repo's GameCube/Wii host or run from a flash cart.
8+
9+
## What it tests
10+
11+
Each variant boots the same Doridian-style joybus controller loop and
12+
reports button state back to the host the moment a button changes. The
13+
two variants differ in what they render on the GBA screen:
2014

2115
```
22-
build/joypad/joypad_mb.gba # eyes ROM (raw multiboot binary)
23-
build/joypad/joypad_payload.c # (C array for embedding)
24-
build/tester/tester_mb.gba # tester ROM (raw multiboot binary)
25-
build/tester/tester_payload.c # (C array for embedding)
16+
joypad_mb.gba eyes overlay (cartoon eyes + emotion state machine)
17+
tester_mb.gba on-GBA text console + idle screensaver
2618
```
2719

28-
Each `*_payload.c` exports the same symbol names:
20+
| Variant | On-GBA display | Intended host |
21+
|--------------|--------------------------------------------------------------------------------------------------|-------------------------------|
22+
| `joypad_mb` | Mode-4 framebuffer + cylinder-eye renderer + emotion state machine (port of joypad-os's `eyes_anim`) | [joypad-os](https://github.com/joypad-ai/joypad-os) (RP2040) |
23+
| `tester_mb` | 30×20 text console (live button state, 2-column layout) + idle screensaver (Mode-4 page-flipped) | This repo's GameCube/Wii host |
2924

30-
```c
31-
extern const uint8_t joypad_payload[];
32-
extern const uint32_t joypad_payload_len;
33-
```
25+
Fields the GBA doesn't have (analog sticks, triggers, C-stick, rumble)
26+
don't exist on the wire — only the 10 face/dpad/shoulder/start/select
27+
buttons are reported. The 2-byte joybus payload carries them.
3428

35-
so a host can drop either file in and the embedding code doesn't have
36-
to change. Pick the variant that fits your product.
37-
38-
## Lineage
39-
40-
The joybus handshake + main loop in `source/main.c` and
41-
`source/main_tester.c` (the `0x30303030` exchange, status-bit polls,
42-
SVC `0x26` BIOS reset on `JOYCNT.RST`) is taken from **Doridian's
43-
[Joybus-PIO](https://github.com/Doridian/Joybus-PIO)** `gba/source/main.c`
44-
(2023). That sequence is load-bearing for the cable's level-shifter MCU;
45-
modifying it breaks the host's view of input. The eyes overlay and the
46-
tester console run on top of it.
47-
48-
Chain of credit:
49-
50-
- **[gbatek](https://problemkaputt.de/gbatek.htm)** (Martin Korth) —
51-
authoritative GBA hardware reference (SIO / Joybus / BIOS reset
52-
semantics)
53-
- **[libgbacom](https://github.com/Sage-of-Mirrors/libgbacom)**
54-
(Sage-of-Mirrors, ported from VisualBoyAdvance) — original
55-
reverse-engineering of the joyboot stream cipher
56-
- **[gc-gba-link-cable-demo](https://github.com/FIX94/gc-gba-link-cable-demo)**
57-
(FIX94) — canonical GameCube-side reference for booting a GBA over the
58-
link cable
59-
- **[Joybus-PIO](https://github.com/Doridian/Joybus-PIO)** (Doridian) —
60-
RP2040-PIO implementation of the GameCube side, plus the GBA-side
61-
controller payload these variants build on
62-
- **This subtree** — eyes overlay (port of joypad-os's `eyes_anim`),
63-
Mode-4 page-flipped screensaver matching the joypad-tester GC
64-
variant's logo + colour cycle, two-build Makefile, robust joybus
65-
reset handling (polls `JOYCNT.RST` inside the VBlank busy-wait so
66-
host cmd 0xFF triggers `SystemCall(0x26)` within microseconds)
29+
## Joybus protocol
6730

68-
## Embedding
31+
The handshake + main loop is taken verbatim from
32+
[Doridian/Joybus-PIO](https://github.com/Doridian/Joybus-PIO)'s GBA
33+
side — `0x30303030` exchange, status-bit polls, SVC `0x26` BIOS reset
34+
on `JOYCNT.RST`. Modifying it breaks the cable's level-shifter MCU, so
35+
both variants share the same loop and only swap the on-screen renderer
36+
behind it.
6937

70-
Drop the appropriate `build/<variant>/<variant>_payload.c` into your
71-
host firmware's source list, then feed `joypad_payload[]` /
72-
`joypad_payload_len` to your joybus multiboot uploader. See
73-
[joypad-os](https://github.com/joypad-ai/joypad-os)'s
74-
`src/native/host/gc/gba_multiboot.c` or this repo's
75-
[`../gamecube/ppc/gba.c`](../gamecube/ppc/gba.c) for reference uploader
76-
implementations (Kawasedo handshake, stream cipher, polled WRITE/READ,
77-
unconditional handshake-complete write).
38+
Hot-swap and host-reboot re-multiboot rely on noticing a host `cmd 0xFF`
39+
within a frame: `REG_JOYCNT.RST` is polled inside the VBlank busy-wait
40+
so any reset triggers `SystemCall(0x26)` within microseconds.
41+
42+
## Eyes variant (`joypad_mb.gba`)
43+
44+
Renders a pair of cartoon eyes (Mode-4 8bpp paletted framebuffer) whose
45+
gaze follows the dpad like a self-centering analog stick. An emotion
46+
state machine cycles `ACTIVE → wander → sleep` with per-emotion FG /
47+
pupil palette swaps. Designed to ship inside joypad-os as the GBA-as-
48+
controller "personality" layer — the buttons remain the product, the
49+
overlay just confirms the link is alive.
7850

79-
## Building (only if you change source)
51+
## Tester variant (`tester_mb.gba`)
8052

81-
Requires devkitPro / devkitARM:
53+
A 30-column × 20-row text console:
8254

83-
```bash
84-
# macOS
85-
brew install --cask devkitpro-pacman
86-
sudo dkp-pacman -S gba-dev
55+
```
56+
Joypad Tester — GBA
57+
GC Link: connected
58+
59+
Buttons:
60+
A:0 Start:0
61+
B:0 Select:0
62+
L:0 Up:0
63+
R:0 Down:0
64+
Left:0
65+
Right:0
66+
67+
Raw: 0000
68+
```
8769

88-
# Linux
89-
# https://devkitpro.org/wiki/devkitPro_pacman
70+
Live indicators flip 0/1 as buttons change; `Raw: XXXX` is the hex
71+
joybus payload. After 30 seconds of no input, a Mode-4 page-flipped
72+
screensaver kicks in: the 64×54 joypad logo bounces off the screen
73+
edges, color-cycling through red / green / yellow / blue / magenta /
74+
cyan / white on every wall hit — same image, palette, and speed as the
75+
GameCube tester's idle screensaver, so testers see the same animation
76+
on both screens.
9077

91-
export DEVKITPRO=/opt/devkitpro
78+
If the host drops the joybus link, the variant falls back to standalone
79+
mode and the console keeps reporting button state directly from
80+
`REG_KEYINPUT` — useful as a pure flash-cart button tester.
9281

93-
make # build both variants
94-
make joypad # build only the eyes ROM
95-
make tester # build only the tester ROM
82+
## Build
83+
84+
With devkitPro / devkitARM installed:
85+
86+
```
87+
make # → build/joypad/joypad_mb.gba + build/tester/tester_mb.gba
88+
make joypad # → build/joypad/joypad_mb.gba only
89+
make tester # → build/tester/tester_mb.gba only
9690
make clean # nuke build/
9791
```
9892

99-
Outputs land in `build/joypad/` and `build/tester/`. Both `*_mb.gba`
100-
and `*_payload.c` are committed; rebuild and commit them whenever you
101-
change source.
102-
103-
## Layout
93+
Without devkitPro, use the Docker image:
10494

10595
```
106-
.
107-
├── Makefile # two-variant build → build/<variant>/
108-
├── source/
109-
│ ├── main.c # eyes variant entry (joybus + eyes_anim)
110-
│ ├── main_tester.c # tester variant entry (joybus + console
111-
│ │ # + Mode-4 page-flipped screensaver)
112-
│ ├── display.c/.h # Mode-4 framebuffer for the eyes variant
113-
│ ├── eyes_anim.c/.h # cylinder-eye renderer + emotion state
114-
│ │ # machine
115-
│ └── platform/
116-
│ └── platform.h # stub for shared eyes_anim platform API
117-
├── tools/
118-
│ └── bin2c.py # binary .gba → C array converter
119-
└── build/ # committed pre-built payload artifacts
120-
├── joypad/
121-
│ ├── joypad_mb.gba
122-
│ └── joypad_payload.c
123-
└── tester/
124-
├── tester_mb.gba
125-
└── tester_payload.c
96+
docker run --rm -v "$PWD":/workspace -w /workspace \
97+
devkitpro/devkitarm:latest make
12698
```
99+
100+
CI builds both variants on every push to `main` (see
101+
[`.github/workflows/verify-build.yml`](../.github/workflows/verify-build.yml)).
102+
103+
The `build/` tree is committed so consumers using this folder as a
104+
submodule (e.g. joypad-os) don't need devkitARM unless they're
105+
modifying the source — rebuild and commit `build/<variant>/<variant>_mb.gba`
106+
+ `build/<variant>/<variant>_payload.c` whenever you change source.
107+
108+
## Loading on hardware
109+
110+
Two paths, depending on which variant and host:
111+
112+
**Multiboot over GameCube link cable** — both variants. Build this
113+
repo's GameCube/Wii host (see [`../gamecube/`](../gamecube/)), connect a
114+
GBA Link Cable from GameCube SI port 2 to the GBA, leave the GBA on the
115+
"Press Start" screen with no cartridge, and start the host. The host
116+
uploads `tester_mb.gba` via Kawasedo handshake + stream cipher and the
117+
GBA boots into the tester variant. For the eyes variant, the host
118+
uploader lives in [joypad-os](https://github.com/joypad-ai/joypad-os)'s
119+
RP2040 firmware (`src/native/host/gc/gba_multiboot.c`).
120+
121+
**Flash cart**`tester_mb.gba` only. Drop `tester_mb.gba` onto an
122+
EZ-Flash / EverDrive / etc., boot it. Standalone fallback kicks in
123+
after the 3-second joybus handshake timeout and the variant runs as a
124+
pure on-GBA button tester.
125+
126+
## Embedding
127+
128+
Drop the appropriate `build/<variant>/<variant>_payload.c` into your
129+
host firmware's source list, then feed the symbols to your joybus
130+
multiboot uploader. Both variants export the same symbol names so the
131+
host's externs don't change when switching ROMs:
132+
133+
```c
134+
extern const uint8_t joypad_payload[];
135+
extern const uint32_t joypad_payload_len;
136+
```
137+
138+
See [`../gamecube/ppc/gba.c`](../gamecube/ppc/gba.c) or joypad-os's
139+
`src/native/host/gc/gba_multiboot.c` for a reference uploader
140+
implementation (Kawasedo handshake, stream cipher, polled WRITE/READ,
141+
unconditional handshake-complete write).
142+
143+
## Releases
144+
145+
Tagged as `gba-v<semver>` from the repo root — see
146+
[`gba/CHANGELOG.md`](CHANGELOG.md) for per-version notes. The release
147+
workflow attaches `joypad_mb.gba`, `joypad_payload.c`, `tester_mb.gba`,
148+
and `tester_payload.c` to each GitHub Release automatically.
149+
150+
## Origin / credits
151+
152+
Built on Doridian's [Joybus-PIO](https://github.com/Doridian/Joybus-PIO)
153+
GBA payload (MIT) — see [`LICENSE.md`](LICENSE.md). The joybus handshake
154+
+ main loop (`0x30303030` exchange, status-bit polls, SVC `0x26` BIOS
155+
reset on `JOYCNT.RST`) is taken verbatim from Doridian's `gba/source/main.c`;
156+
the eyes overlay, tester console, Mode-4 page-flipped screensaver
157+
(matching the GameCube tester's logo + color cycle), two-build Makefile,
158+
and robust joybus reset handling (polling `JOYCNT.RST` inside the VBlank
159+
busy-wait) are added on top. Joyboot stream cipher reverse-engineered by
160+
Sage-of-Mirrors' [libgbacom](https://github.com/Sage-of-Mirrors/libgbacom)
161+
(ported from VisualBoyAdvance); canonical GameCube-side multiboot
162+
reference by FIX94's
163+
[gc-gba-link-cable-demo](https://github.com/FIX94/gc-gba-link-cable-demo);
164+
hardware semantics per [gbatek](https://problemkaputt.de/gbatek.htm)
165+
(Martin Korth). Eyes overlay is a port of joypad-os's `eyes_anim`.

0 commit comments

Comments
 (0)