Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
426194d
Bump version to v1.1.0
diegoparrilla Sep 19, 2026
3d66bf2
Document the release workflow; refresh stale CLAUDE.md facts
diegoparrilla Sep 19, 2026
f9e4761
font8x8: give descenders their own row; fix three corrupt glyphs
diegoparrilla Sep 19, 2026
2010388
Describe the behaviour in m68k and tool comments, not planning notes
diegoparrilla Sep 19, 2026
6e366de
Copy the cartridge image exactly into the window, and fail builds loudly
diegoparrilla Sep 29, 2026
9441627
Keep the heap out of the cartridge window, and give it room
diegoparrilla Sep 29, 2026
8dcbb5d
Turn the mouse and joysticks off while userfw runs, and back on at exit
diegoparrilla Sep 29, 2026
3e9d65e
examples/hello_text: build with the template's heap settings
diegoparrilla Sep 29, 2026
00e196b
The ST says hello at every boot, and the RP starts a new session
diegoparrilla Sep 29, 2026
06dc175
Read the keyboard from the ACIA's interrupt, not from the blit
diegoparrilla Sep 29, 2026
3d9097c
Run the debug console at 921,600 baud, so the cartridge is live in time
diegoparrilla Sep 29, 2026
4f6190a
Blit only a newly published frame, so a publish never overlaps a blit
diegoparrilla Sep 29, 2026
40ebebb
SELECT as in md-microfirmware-template, and a core 1 that never touch…
diegoparrilla Sep 29, 2026
cdd9d52
A guarded 4 KB stack for each core, and a ROM3 ring that counts overruns
diegoparrilla Sep 29, 2026
e7e9c05
SD card and settings: fatfs v3.6.2, bounded lookups, checks at run time
diegoparrilla Sep 29, 2026
4cd472c
Keep the symbol table in rp.elf
diegoparrilla Sep 29, 2026
50c062d
Drop the sidecartos_16mb board
diegoparrilla Sep 29, 2026
f46dd42
Build ID in the image, as in md-microfirmware-template
diegoparrilla Sep 29, 2026
92bc034
pico_w is the only board
diegoparrilla Sep 29, 2026
dcb2de6
Build scripts: one date for both images, and the same build twice
diegoparrilla Sep 29, 2026
e371a83
build_id.cmake: no +https suffix
diegoparrilla Sep 29, 2026
d93cdd6
CI: build both types with the documented toolchain
diegoparrilla Sep 29, 2026
56b64c3
Build: release and debug are both CMake Release
diegoparrilla Sep 29, 2026
8fe902e
Boot veto and return to Booster, after md-oric
diegoparrilla Sep 29, 2026
54f658d
Merge pull request #4 from sidecartridge/epic/08-sibling-backports
diegoparrilla Sep 29, 2026
9d1f57b
fb: count the publishes that time out waiting for the blit
diegoparrilla Sep 29, 2026
3da903d
tools/dev/swd.py: read, check and flash the RP over SWD
diegoparrilla Sep 29, 2026
aafed93
tools/dev: console capture and flash.sh
diegoparrilla Sep 29, 2026
14b3986
swd.py postmortem: no GDB memory map, so core 0 survives the resume
diegoparrilla Sep 29, 2026
4423c11
swd.py: grab the framebuffer as a PNG, and print the shared block
diegoparrilla Sep 29, 2026
71a9fd6
devhooks: a debug-only mailbox to drive the app from the host
diegoparrilla Sep 29, 2026
5077ac8
swd.py: key and app, through the devhooks mailbox
diegoparrilla Sep 29, 2026
397ed3a
tools/dev: measure builds, stack depth, and a harness for the tools
diegoparrilla Sep 29, 2026
b900230
Document the developer tools
diegoparrilla Sep 29, 2026
88bee44
Merge pull request #5 from sidecartridge/epic/09-hardware-dev-loop
diegoparrilla Sep 29, 2026
2967d9b
Host tests: a make target and a CI step
diegoparrilla Sep 29, 2026
a38cf46
Host test: the blits' clipping
diegoparrilla Sep 29, 2026
b351d5c
One copy of the cartridge window's layout on the ST side
diegoparrilla Sep 29, 2026
a1429bc
Host test: the ST side and the RP side agree on what they share
diegoparrilla Sep 29, 2026
295cefb
Host test: what the app draws is what the ST shows
diegoparrilla Sep 29, 2026
a72f7fd
Host test: the sample converter and the .YMS files it writes
diegoparrilla Sep 29, 2026
81e58ce
Document the host tests and the shared layout include
diegoparrilla Sep 29, 2026
96c1f31
Merge pull request #6 from sidecartridge/epic/10-host-tests-ci
diegoparrilla Sep 29, 2026
0b47960
Audio: slices the ST never plays while they are written
diegoparrilla Sep 29, 2026
53efa3c
swd.py counters: the audio slices written, late and underrun
diegoparrilla Sep 29, 2026
ca31bff
Host test: the audio slices against a simulated ST
diegoparrilla Sep 29, 2026
bc36768
Audio: the main loop may go 4 VBLs between top-ups, not 6
diegoparrilla Sep 29, 2026
f44f8a0
Document the audio slices: the rate chain, the pipeline, the latency
diegoparrilla Sep 29, 2026
701cf88
Merge pull request #7 from sidecartridge/epic/11-sound-never-drops
diegoparrilla Sep 29, 2026
1f49714
IKBD decoder: keys, mouse and joysticks from the byte stream
diegoparrilla Sep 30, 2026
85ddd30
Mouse and joysticks: the RP picks what the IKBD reports
diegoparrilla Sep 30, 2026
db4d6b5
Input test: the mouse, both joysticks and the IKBD path's counters
diegoparrilla Sep 30, 2026
0b51592
swd.py: the IKBD counters, ikbd-log and the function keys
diegoparrilla Sep 30, 2026
a1e59fd
Blit slack report: how much of the VBL the ST has left
diegoparrilla Sep 30, 2026
533a39d
Document the mouse and joysticks, and the ST's VBL budget
diegoparrilla Sep 30, 2026
61282b4
Merge pull request #8 from sidecartridge/epic/13-mouse-and-joysticks
diegoparrilla Sep 30, 2026
d27bc9f
STE DMA sound: the chip plays, the RP writes ahead
diegoparrilla Sep 30, 2026
85b652c
Debug commands: the audio output and a test tone
diegoparrilla Sep 30, 2026
a0ec0ec
wav_to_ym4.py: signed 8-bit PCM for the DMA chip
diegoparrilla Sep 30, 2026
5a03ab8
Document the STE DMA sound
diegoparrilla Sep 30, 2026
a9d7ad5
Merge pull request #9 from sidecartridge/epic/12-ste-dma-sound
diegoparrilla Sep 30, 2026
b5412b0
Blitter screen copy where the DMA chip plays the sound
diegoparrilla Sep 30, 2026
23dbdee
Document the blitter screen copy
diegoparrilla Sep 30, 2026
e58a8ff
Merge pull request #10 from sidecartridge/epic/17-blitter-screen-copy
diegoparrilla Sep 30, 2026
d6a42a3
png_to_bitmap.py: indexed PNGs to FB_BITMAP headers
diegoparrilla Sep 30, 2026
b6687a9
Two games: Arena for the joystick, Zap for the mouse
diegoparrilla Sep 30, 2026
63e2aea
Document the games and png_to_bitmap.py
diegoparrilla Sep 30, 2026
a1798b2
Merge pull request #11 from sidecartridge/epic/14-playable-example
diegoparrilla Sep 30, 2026
0af40ae
Two profiles: 50 fps, or 25 fps with 22 kHz and 25 kHz sound
diegoparrilla Sep 30, 2026
c9c17bd
Document the two profiles and the stopwatch
diegoparrilla Sep 30, 2026
40d8265
Merge pull request #12 from sidecartridge/epic/18-sound-profiles
diegoparrilla Sep 30, 2026
119436e
CHANGELOG: v1.1.0, dated
diegoparrilla Sep 30, 2026
080afeb
Merge pull request #13 from sidecartridge/epic/16-release-gate
diegoparrilla Sep 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 50 additions & 12 deletions .claude/skills/framebuffer-app/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,21 @@ Multi-device microfirmware apps** for the Atari ST / STE / MegaST(E) —
games, demos, and console/computer emulations where the speed of putting
a colourful 320×200 screen up matters. You draw a 320×200 16-colour
framebuffer in the Pico's RAM and the firmware blits it to the ST each
VBL (50 Hz), with keyboard input and YM audio for free.
VBL (50 Hz), with keyboard, mouse and joystick input and YM audio for
free.

**You develop 100% on the RP2040 side — the framework does the heavy
lifting:** a dual (page-flipped) framebuffer on the Atari ST side
(tear-free, managed for you); real 50 Hz locked to the ST's vertical
blank; **~19 ms of compute every VBL** to draw your frame; chunked
drawing on the RP2040 (one byte per pixel) with the chunked → ST planar
conversion done for you in **~1 ms per VBL** (split across both cores);
**~6 kHz, 6-bit sampled sound** out the YM2149; and Atari ST keyboard
handled on the RP2040 with decoded scancodes delivered to your app.
sampled sound (8-bit 12.5 kHz through the DMA chip on an STE / Mega STE,
~6 kHz 6-bit out the YM2149 elsewhere; or, with `APP_PROFILE=PROFILE_25FPS`
at build time, 25 fps with 25 kHz / ~22 kHz sound: `rp/src/include/profile.h`);
and the Atari ST
keyboard, mouse and joysticks handled on the RP2040, delivered to your app
as key events, mouse movement and stick states.

## The model (read this first)

Expand All @@ -43,15 +48,17 @@ double-buffering. `fb_publish()` blocks on the ST's VBL, so one call per
loop paces the app to 50 Hz.

`README.md` is the human guide; `CLAUDE.md` is the architecture
deep-dive; `examples/hello_text/` is a minimal working app.
deep-dive; `examples/hello_text/` is a minimal working app and
`examples/mini_game/` a game (`rp/src/game_arena.c` for the joystick,
`rp/src/game_zap.c` for the mouse, on `game_kit.c`).

## Starting a fresh app

**Quick path:** `examples/hello_text/apply.sh` does all of the below — it
backs up `rp/` to `rp.bak`, deletes the demo/menu files, and installs a
minimal `emul.c` + `CMakeLists.txt`. The manual steps:

1. **Delete the demos**: `rp/src/demo_*.c` (5 files), `rp/src/include/demo.h`,
1. **Delete the demos**: `rp/src/demo_*.c` (7 files), `rp/src/include/demo.h`,
and the asset headers (`sidecart_logo.h`, `sidecart_text.h`,
`solid3d.h`, `sprites_data.h`, `cojo_texture.h`, `cojo_font.h`,
`diego_sprite.h`, `uridium_surface.h`).
Expand All @@ -65,7 +72,11 @@ minimal `emul.c` + `CMakeLists.txt`. The manual steps:
4. **`desc/app.json`**: set your `uuid` (must match the UUID passed to
`build.sh`).

`examples/hello_text/emul.c` is a ready-made stripped `emul.c` to copy.
`examples/hello_text/emul.c` is a ready-made stripped `emul.c` to copy;
`examples/mini_game/emul.c` boots into a game instead. For a game's sprites
and tiles, draw indexed PNGs (16 colours, one transparent) and convert them
with `tools/png_to_bitmap.py` (`FB_BITMAP`s + the palette for
`palette_set()`), as `rp/src/assets/convert.sh` does.

## The API (keep these modules; they are your API)

Expand All @@ -87,10 +98,19 @@ palette_set_entry(2, PALETTE_RGB(7, 0, 0)); // or palette_set(entries[16])
fb_publish(); // once per frame, after drawing
// input: ikbd.h
ikbd_key_event_t k; while (ikbd_pop_key(&k)) { if (k.is_press) ... } // k.scancode
ikbd_set_input_mode(IKBD_INPUT_MOUSE); // or _KEYBOARD (default), _MOUSE_JOY1,
// _JOYSTICKS; keys work in every mode
ikbd_mouse_t m; ikbd_read_mouse(&m); // once a frame: m.dx/dy since last read,
// m.buttons, m.pressed (clicks between reads)
ikbd_joystick_t j; ikbd_read_joystick(1, &j); // j.state / j.pressed: IKBD_JOY_UP..FIRE
// audio: audio.h
audio_play_loop(data, bytes); // loop a baked-in buffer, OR
audio_set_fill_callback(cb); // cb(buf,bytes) per VBL, live
audio_render_frame(); // call every loop iteration
audio_set_fill_callback(cb); // cb(buf,bytes) per VBL of samples, live
audio_play_pcm_loop(pcm, n, rate); // or signed 8-bit PCM at any rate
audio_set_pcm_callback(cb, AUDIO_DMA_RATE_HZ); // (either kind plays on both outputs)
audio_render_frame(); // every loop iteration: tops up the FIFO
// (80 ms of stall allowed; 120 ms latency
// on the YM, ~175 ms on the DMA chip)
```

**SD card:** the microSD is already mounted at boot
Expand All @@ -110,6 +130,14 @@ recompiling): defaults live in `rp/src/aconfig.c` `defaultEntries[]` as
v)` then `settings_save(aconfig_getContext(), true)`. (This is how the SD
folder name `ACONFIG_PARAM_FOLDER` is supplied.)

**The ST's boot** (`st_session.h`): `st_session_consume_boot()` is true once
per ST boot (restart your app's state). `st_session_veto_boot("reason")`
makes the ST print the reason and go to GEM instead of starting the app
(e.g. a file the app needs is missing); it must be set within about 0.3 s
of a power-on to catch that boot, and holds until `st_session_allow_boot()`.
`st_session_return_to_booster()` leaves for Booster without a power cycle
(the ST cold-resets into it); call it from the main loop, never returns.

Main loop shape (in `emul_start()`):

```c
Expand All @@ -135,15 +163,25 @@ while (true) {
The user usually runs the build themselves; only build the **Atari/m68k
target** if you change `target/atarist/` asm. RP-only C changes don't
need it.
- **Optimization:** the global build is `MinSizeRel` (`-Os`). For hot
per-pixel loops add `#pragma GCC optimize("O3")` at the top of that
*compute-only* `.c` file and `__not_in_flash_func()` on the function —
never on bus/PIO/timing code. The demo sources are the reference for
- **Optimization:** both build types are CMake `Release` (`-O3`). Hot
per-pixel loops also want `__not_in_flash_func()` on the function, and
the demos put `#pragma GCC optimize("O3")` at the top of their
*compute-only* `.c` files, which keeps them fast in a `MinSizeRel`
build (`RP_CMAKE_BUILD_TYPE`). The demo sources are the reference for
the full toolbox (LUTs, the SIO interpolator, `fb_core1_dispatch`
dual-core).
- **Never** edit the `pico-sdk/`, `pico-extras/`, `fatfs-sdk/`
submodules — the build re-pins them. Don't add features to `main.c`
(use `emul.c`).
- **Input modes:** port 0 is the mouse or joystick 0, never both — pick
the mode (`ikbd_set_input_mode()`); it survives an ST reset and a
keyboard replug. Stick 1's fire is the right mouse button's wire. Every
IKBD byte costs the ST an interrupt (30–40 µs) out of ~0.27 ms of slack
after the full-screen blit: a mouse moved fast drops some frames to
30–40 fps (accepted; blit fewer lines if an app must hold 50). On an
STE / Mega STE the DMA chip plays the sound and the blitter copies the
frame: no frames lost. `fb_set_copy_mode()` forces the CPU or the
blitter (the blitter on the YM path roughens the sound).
- **Palette:** index 0 = white (text/border), 15 = black (background);
`PALETTE_RGB(r,g,b)` channels are 0..7. Re-publishing the palette each
frame is cheap (colour-cycling).
Expand Down
83 changes: 66 additions & 17 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,84 @@ on:
pull_request:
workflow_dispatch:

env:
# The toolchain CLAUDE.md asks for in local builds. Ubuntu's packaged
# gcc-arm-none-eabi is a different compiler, and a different compiler
# produces a different image from the same sources.
ARM_TOOLCHAIN_VERSION: 14.2.rel1
# stcmd runs `docker run -it` unless this is set; a CI job has no TTY.
STCMD_NO_TTY: 1

jobs:
build:
runs-on: ${{ matrix.os }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: [ "3.10" ]
include:
- os: ubuntu-latest
steps:
build_type: [release, debug]
steps:
- name: Checkout the code
uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
python-version: "3.10"

- name: Install the Pico Toolchain
- name: Install the build tools
run: |
sudo DEBIAN_FRONTEND=noninteractive apt update
sudo DEBIAN_FRONTEND=noninteractive apt install -y cmake gcc-arm-none-eabi libnewlib-arm-none-eabi build-essential libstdc++-arm-none-eabi-newlib

sudo DEBIAN_FRONTEND=noninteractive apt install -y cmake build-essential

# The firmware's pure logic, on the host, in seconds (tests/host/Makefile):
# a failing check fails the pull request before the firmware is built.
- name: Host tests
run: make -C tests/host test

- name: Cache the ARM GNU Toolchain
id: arm-toolchain
uses: actions/cache@v4
with:
path: ~/arm-gnu-toolchain
key: arm-gnu-toolchain-${{ env.ARM_TOOLCHAIN_VERSION }}-x86_64

- name: Download the ARM GNU Toolchain
if: steps.arm-toolchain.outputs.cache-hit != 'true'
run: |
mkdir -p ~/arm-gnu-toolchain
curl -fsSL "https://developer.arm.com/-/media/Files/downloads/gnu/${ARM_TOOLCHAIN_VERSION}/binrel/arm-gnu-toolchain-${ARM_TOOLCHAIN_VERSION}-x86_64-arm-none-eabi.tar.xz" \
| tar -xJ -C ~/arm-gnu-toolchain --strip-components=1

- name: Use the ARM GNU Toolchain
run: |
echo "$HOME/arm-gnu-toolchain/bin" >> "$GITHUB_PATH"
echo "PICO_TOOLCHAIN_PATH=$HOME/arm-gnu-toolchain/bin" >> "$GITHUB_ENV"

- name: Show the compiler version
run: arm-none-eabi-gcc --version

- name: Install AtariST Toolkit Docker image
run: curl -sL https://github.com/sidecartridge/atarist-toolkit-docker/releases/download/v1.3.0/install_atarist_toolkit_docker.sh | bash

- name: Run - remove interactive
run: sed -i 's/-it//' /usr/local/bin/stcmd
# Date the build by its commit, so this build and a local build of the
# same commit with the same RELEASE_DATE are byte-identical.
- name: Date the build by its commit
run: echo "RELEASE_DATE=$(git log -1 --format=%cd --date=format:'%Y-%m-%d %H:%M:%S')" >> "$GITHUB_ENV"

- name: Run - Build PICO_W release version
- name: Build PICO_W ${{ matrix.build_type }} version
env:
APP_UUID: ${{ secrets.APP_UUID }}
run: ./build.sh pico_w Release ${APP_UUID:-44444444-4444-4444-8444-444444444444}
run: ./build.sh pico_w ${{ matrix.build_type }} ${APP_UUID:-44444444-4444-4444-8444-444444444444}

# To the log as well: the job summary cannot be read back through the API.
- name: Build report
run: |
{
echo "### ${{ matrix.build_type }}"
echo '```'
sed -n 's/^#define RELEASE_BUILD_ID "\(.*\)"$/build ID: \1/p' rp/build-pico_w-${{ matrix.build_type }}/generated/build_id/build_id.h
echo "release date: $RELEASE_DATE"
arm-none-eabi-size rp/build-pico_w-${{ matrix.build_type }}/rp.elf
cat dist/rp.uf2.md5sum
echo '```'
} | tee -a "$GITHUB_STEP_SUMMARY"
36 changes: 30 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ on:
permissions:
contents: write

env:
# The same toolchain and toolkit as the Build workflow and local builds.
ARM_TOOLCHAIN_VERSION: 14.2.rel1
STCMD_NO_TTY: 1

jobs:
build:
runs-on: ${{ matrix.os }}
Expand All @@ -19,23 +24,42 @@ jobs:
- os: ubuntu-latest
steps:
- name: Checkout the code
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install the Pico Toolchain
- name: Install the build tools
run: |
sudo DEBIAN_FRONTEND=noninteractive apt update
sudo DEBIAN_FRONTEND=noninteractive apt install -y cmake gcc-arm-none-eabi libnewlib-arm-none-eabi build-essential libstdc++-arm-none-eabi-newlib
sudo DEBIAN_FRONTEND=noninteractive apt install -y cmake build-essential

- name: Cache the ARM GNU Toolchain
id: arm-toolchain
uses: actions/cache@v4
with:
path: ~/arm-gnu-toolchain
key: arm-gnu-toolchain-${{ env.ARM_TOOLCHAIN_VERSION }}-x86_64

- name: Download the ARM GNU Toolchain
if: steps.arm-toolchain.outputs.cache-hit != 'true'
run: |
mkdir -p ~/arm-gnu-toolchain
curl -fsSL "https://developer.arm.com/-/media/Files/downloads/gnu/${ARM_TOOLCHAIN_VERSION}/binrel/arm-gnu-toolchain-${ARM_TOOLCHAIN_VERSION}-x86_64-arm-none-eabi.tar.xz" \
| tar -xJ -C ~/arm-gnu-toolchain --strip-components=1

- name: Use the ARM GNU Toolchain
run: |
echo "$HOME/arm-gnu-toolchain/bin" >> "$GITHUB_PATH"
echo "PICO_TOOLCHAIN_PATH=$HOME/arm-gnu-toolchain/bin" >> "$GITHUB_ENV"

- name: Install AtariST Toolkit Docker image
run: curl -sL https://github.com/sidecartridge/atarist-toolkit-docker/releases/download/v1.3.0/install_atarist_toolkit_docker.sh | bash

- name: Run - remove interactive
run: sed -i 's/-it//' /usr/local/bin/stcmd
- name: Date the build by its commit
run: echo "RELEASE_DATE=$(git log -1 --format=%cd --date=format:'%Y-%m-%d %H:%M:%S')" >> "$GITHUB_ENV"

- name: Install dependencies
run: |
Expand Down
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,11 @@ dist/
!.claude/skills/
docs/epics/
__pycache__/
*.pyc
*.pyc

# Host-side developer tools: capture and build output
tools/dev/logs/
tools/dev/builds/

# Host tests: build output
tests/host/build/
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ PICO_TOOLCHAIN_PATH=/Applications/ArmGNUToolchain/14.2.rel1/arm-none-eabi/bin \
./build.sh pico_w release 123e4567-e89b-12d3-a456-426614174000
```

With the Debug Probe attached (SWD and the debug UART), `tools/dev/` builds, flashes and verifies the RP, captures its console and reads or drives it from the host (see `tools/dev/README.md`):
```bash
python3 tools/dev/console.py watch # console capture, leave running
tools/dev/flash.sh debug # build out of tree, flash, verify over SWD
python3 tools/dev/swd.py counters --watch 2 # frames, blits, overruns per second
python3 tools/dev/swd.py fb screen.png # what the ST shows, as a PNG
python3 tools/dev/tools_harness.py --build --flash --reset
```

## 3. Build Notes & Gotchas
- `CHARACTER_GAP_MS` constant lives in `rp/src/include/blink.h`. Keep it defined (700 ms) or the RP build fails.
- Expect harmless VASM warnings (`target data type overflow`, `trailing garbage after option -D`).
Expand All @@ -57,5 +66,7 @@ PICO_TOOLCHAIN_PATH=/Applications/ArmGNUToolchain/14.2.rel1/arm-none-eabi/bin \
- `/pico-extras`
- To change FatFs configuration, edit `rp/src/ff/ffconf.h`, not the file inside `/fatfs-sdk`.
- **Never add AI-tool attribution** to commits, PR descriptions, code comments, docs, or any other artifact. No `Co-Authored-By: Claude …`, no "Generated with Claude Code / ChatGPT / etc.", no "AI-assisted" notes. Write everything as the human author.
- Release workflow: a new version starts with `release/vX.Y.Z` branched from `main` (the name is what `version.txt` will contain). Each epic gets its own branch cut from the release branch, `epic/NN-<slug>`, and its pull request targets the release branch, never `main`; it is merged after Diego verifies it on hardware. `main` receives the release branch once, when the version is done, and only then is it tagged. Commit, push and open PRs only when asked.
- Planning notes (iterations, epics, stories) live in `docs/epics/`, which is gitignored and machine-local. Never name an epic, story, iteration or task in anything committed or pushed — comments, docs, changelog, commit messages, PR descriptions (epic branch names, `epic/NN-<slug>`, are the one exception). Write the information itself, not a pointer to a document the reader cannot open. Release check: `git grep -nIiE "\b(epic|story|iteration)[ -]?[0-9]|docs/epics" -- ':!CLAUDE.md' ':!AGENTS.md' ':!.gitignore'` must come back empty.

Keep this file updated as the process evolves so every agent starts with the latest tribal knowledge.
Loading
Loading