Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 3 additions & 6 deletions .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
name: Bug report
description: Report unexpected behavior in Stickwalker.
description: Report a Stickwalker problem.
body:
- type: textarea
id: reproduction
attributes:
label: What happened?
description: Describe the steps, expected result and observed result.
label: Steps and result
validations:
required: true
- type: textarea
id: setup
attributes:
label: Version and setup
description: Which release or custom build are you using? Is USB connected? For button problems, include the layout and rotation; for connection problems, include the game, distance and alignment.
label: Firmware version
validations:
required: true
- type: textarea
id: evidence
attributes:
label: Screenshots or logs
description: Attach anything that helps explain the problem, if available. Keep full flash and EEPROM backups private because they contain your game data.
8 changes: 1 addition & 7 deletions .github/ISSUE_TEMPLATE/change.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@ body:
- type: textarea
id: need
attributes:
label: What would this help you do?
description: Describe the use case and any current obstacle.
label: What would you change?
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed behavior
description: Include an example or tradeoff if you have one.
6 changes: 1 addition & 5 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
Describe the problem and resulting behavior.

## Validation

Record the checks run and any physical qualification still needed.
<!-- Describe the change and how you checked it. -->
20 changes: 0 additions & 20 deletions CONTEXT.md

This file was deleted.

55 changes: 4 additions & 51 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,9 @@
# Contributing

For firmware changes, start with the [Stick development guide](stick/README.md)
or the [application source guide](docs/source.md).
Use [Issues](https://github.com/lumirth/stickwalker/issues) for bugs and feature requests. Run the [firmware and formatting checks](docs/build.md#checks) for code changes.

## Report a problem
Test through compiled-firmware workflows with physical or simulated inputs and independent expected results. Extend an existing workflow where possible. Do not add unit tests, integration tests or tests that mirror the implementation.

Include the release version, or commit for a custom build, what you did, and
what happened. A short sequence that reproduces the problem is especially
useful. Also include the details relevant to it:
Changes to shared Pokéwalker code must still match the original H8 binary under both supported compilers.

- For buttons or wake, the layout, rotation and whether USB was connected.
- For infrared, the photodiode wiring, other device or game, distance and
orientation. Include how often the connection succeeds or fails.
- For a test script, the command and relevant output.

Keep flash backups and EEPROM images private; they contain registration and
game data. Share the diagnostic output needed to describe the problem.

## Propose a change

Explain the problem and how the patch changes the Stick's behavior. Stick
hardware adapters live in `stick/`; game rules and the infrared protocol live
in `src/`. Preserve stored record sizes, byte order and the order in which tasks
use shared working memory.

Run `uv run python stick/check.py` for code changes. It builds the firmware and
executes the E2E workflows.
Describe the device tests you ran. Shared H8 application changes also need the
format, lint and binary-matching checks in the [H8 build guide](docs/build.md).
For documentation, check the instructions and links you changed.

## Tests

Run the compiled firmware through complete user workflows. Drive physical
inputs on the Stick or its simulated board, then check what someone can see,
hear, receive over infrared or recover from saved flash. Establish expectations
from the user contract, protocol specification or independent recordings.

The automated [firmware E2E checks](stick/simulation/README.md) boot the production
image through the ESP32-S3 ROM and its bootloader. Their board models implement
GPIO and bus devices from the hardware specifications. Keep the application and
SDK drivers in that execution path.

Review coverage before adding a test for a bug fix. Extend a relevant workflow
when there is a genuine gap. Remove unit and integration tests,
tautologies and tests that merely detect implementation changes. Expectations
must describe useful behavior and come from evidence independent of the code
being exercised. Apply this review to existing tests too.

## Source and license

Contributions use the project's [MIT license](LICENSE). Credit third-party
material and keep its notices. Keep ROM dumps, extracted retail artwork,
proprietary compiler binaries and confidential source out of contributions.
See [third-party notices](docs/third-party.md) for the included dependencies.
Contributions use the [MIT license](LICENSE). Retain third-party notices. Do not commit ROMs, extracted retail artwork or proprietary compilers.
64 changes: 12 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,67 +1,27 @@
<p align="center">
<img src="docs/images/stickwalker-logo.svg" alt="Stickwalker handheld with a Pineco on its screen" width="480">
<img src="docs/images/stickwalker-logo.svg" alt="Stickwalker" width="480">
</p>

# Stickwalker

Stickwalker is firmware that turns an M5Stack Stick S3 into a recreation of the
Pokéwalker. Take a Pokémon from HeartGold or SoulSilver for a walk, earn Watts,
catch Pokémon and find items, then send your Pokémon and finds back to the game
over infrared.
Stickwalker ports my [matching Pokéwalker firmware reconstruction](https://github.com/lumirth/pw) to the M5Stack Stick S3.

I built it from my [binary-matching reconstruction of the Pokéwalker firmware](docs/h8-reconstruction.md).
It runs the original game logic, including step counting, minigames, progression,
sounds and communication with the games and other walkers.
## Install

## Get started
You need a Stick S3, a BPV10NF photodiode and a USB-C data cable. Connect the photodiode's anode to GND and cathode to G5.

You'll need:
Download the firmware ZIP from [Releases](https://github.com/lumirth/stickwalker/releases) and follow the [installation guide](stick/docs/install.md).

- An M5Stack Stick S3.
- A BPV10NF photodiode for receiving infrared. The Stick has a built-in transmitter.
- A USB-C data cable and a computer to install the firmware.
- An infrared-equipped HeartGold or SoulSilver Game Card and a console that can run it.
Downloads use placeholder built-in artwork. For the original graphics, [build with artwork from your own ROM](docs/build.md#artwork).

Download the firmware ZIP from [Releases](https://github.com/lumirth/stickwalker/releases),
then follow the [installation guide](stick/docs/install.md). It covers backups,
firmware updates and the two photodiode connections, G5 and GND. Once the Stick
is ready, [connect to your game and choose a Pokémon to take with you](stick/docs/playing.md).
## Controls

The download uses placeholder pictures and a font for the built-in screens.
Pokémon and route artwork arrive from your game. You can also
[build the firmware](stick/docs/build.md) with
[artwork extracted from your own Pokéwalker firmware](assets/README.md).
L is Left, M is Center and R is Right. The default orientation is left-side-down.

## The Pokéwalker on the Stick S3
Hold M for about half a second to wake the screen. Hold L+R for one second to open Stick Settings. See [controls](stick/docs/controls.md) for the other layouts.

Step counting uses the Pokéwalker's original algorithm. Both devices use Bosch
accelerometers, the BMA150 in the Pokéwalker and the BMI270 in the Stick.
Stickwalker converts the readings to the original scale and processes them at
the original cadence, so step tracking should behave much like an original
Pokéwalker. [How step counting works](stick/docs/motion.md) explains the details.
## Development

The original 96×64 screen appears at twice its size on the Stick's display.
You can rotate it and choose a light or dark appearance. The speaker plays the
original tone sequences. Your Pokémon, route and progress stay saved across
restarts. Return your Pokémon to HGSS before updating pre-release firmware;
updates can require resetting game data.
[Build instructions](docs/build.md) · [Contributing](CONTRIBUTING.md) · [Issues](https://github.com/lumirth/stickwalker/issues)

Walking continues while the screen is off. Hold M for half a second to wake it.
With the default controls, L moves left, M confirms, and R moves right.
The screen starts left-side-down. Hold L+R for one second to open Stick Settings.
See [controls and settings](stick/docs/controls.md)
for the other button layouts.

## Help and contribute

To report a problem or work on the firmware, start with
[Contributing](CONTRIBUTING.md). The [development guide](stick/README.md) maps
out the code. [Release notes](https://github.com/lumirth/stickwalker/releases)
cover changes and testing for each version; the
[validation history](stick/docs/validation-history.md) records the device trials.

Stickwalker is an unofficial project by [lumirth](https://github.com/lumirth),
released under [MIT](LICENSE). Dependencies and marked assets have their own
[licenses and notices](docs/third-party.md). The
[references and acknowledgments](docs/references.md) credit the research and
tools used in the reconstruction and port.
By [lumirth](https://github.com/lumirth), under [MIT](LICENSE). [Credits](docs/references.md) and [third-party licenses](docs/third-party.md).
130 changes: 10 additions & 120 deletions assets/README.md
Original file line number Diff line number Diff line change
@@ -1,129 +1,19 @@
# Artwork inputs
# Edit built-in artwork

The H8 build reads six NCG images and a BMP font sheet. Each file comes from
`assets/local/` when present, with original artwork in `assets/placeholders/`
filling any missing entry. The build reports the placeholders it uses.
Copy the images and palette you want to edit from `assets/placeholders/` into `assets/local/`, or [extract them from your own ROM](../docs/build.md#artwork).

Extract the resident artwork and font once:
Build with `--artwork local` to use your edits.

```sh
uv run python -m tools.assets extract /path/to/retail.bin
```

Extraction checks the ROM's size and SHA-256, then saves the seven images and an
editor palette in the ignored `assets/local/` directory. Future builds read those
files, so the ROM can be moved or removed. Existing files are protected; use
`--force` to replace the extracted images and palette. Other files in the local
directory are preserved. Keep a copy of edited artwork before extracting again.

| File | Size | Contents |
| --- | --- | --- |
| `walker.ncg` | 32 × 32 | Device image used by the connection screens |
| `face-neutral.ncg` | 16 × 8 | Waiting expression |
| `face-smile.ncg` | 16 × 8 | Successful connection expression |
| `face-frown.ncg` | 16 × 8 | Failed connection expression |
| `button-arrow.ncg` | 8 × 8 | Button prompt |
| `ir-signal.ncg` | 8 × 8 | Infrared activity indicator |
| `font.bmp` | 108 × 8 | 36 adjacent 3 × 8 glyphs: `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ` |
| `grayscale.ncl` | 16 palette entries | Editor palette; the first four entries show the firmware shades |

For Stickwalker, the default build always uses placeholders. After extracting
or editing local images, select them with `--artwork local`; missing entries
fall back to placeholders. See the [Stick build guide](../stick/docs/build.md).

## Editing

To create custom artwork without a ROM, copy the desired placeholder files and
`grayscale.ncl` into `assets/local/` and edit them there.

NCG and NCL are Nitro intermediate graphics formats supported by
[NitroPaint](https://github.com/Garhoogin/NitroPaint). Keep four-bit tiles,
palette zero, and pixel indices 0 through 3. Those indices select white, light
gray, dark gray, and black. The supplied NCL palette displays this mapping in
an editor.

For an ordinary bitmap editor, convert an image to BMP and back:
Open NCG images in [NitroPaint](https://github.com/Garhoogin/NitroPaint), or convert them to BMP for another editor.

```sh
uv run python -m tools.assets convert assets/local/walker.ncg walker.bmp
# Edit walker.bmp, preserving its dimensions and grayscale colors.
uv run python -m tools.assets convert walker.bmp assets/local/walker.ncg --force
uv run -m tools.assets convert assets/local/walker.ncg walker.bmp
# Edit walker.bmp.
uv run -m tools.assets convert walker.bmp assets/local/walker.ncg --force
```

Use opaque grayscale values **255, 170, 85, and 0**. The converter also accepts
**173 and 82**, the neighboring display values produced by the NCL palette's
five-bit channels. The font uses opaque **255 and 0**. Disable antialiasing while
drawing, and use nearest-neighbor scaling when viewing pixel art. Unsupported
colors produce an error identifying the file and pixel to fix.

The font sheet can be edited directly as a BMP. Every three consecutive columns
form one glyph, and every row of each cell is preserved. The firmware supplies
the blank separator column when drawing text.

### Walker pixels and rest timing

The sound engine's 42 note-period constants are defined in
`src/application/pw_builtin.c`, immediately before the walker image. Rest code
`0x7D` indexes beyond that table into the image and uses packed byte `0x53` as
its duration divisor. That byte is **2** in the retail image. Zero creates a
zero divisor; other values change the duration of rests.

All 256 walker bytes describe image pixels. Byte `0x53` is the low bit plane of
column **9**, rows **8–15**, in the generated firmware packing. To preserve it,
use light gray or black at **(9, 9)** and white or dark gray at **(9, 8)** and
**(9, 10–15)**. Coordinates start at zero.
Keep the original dimensions and use opaque grayscale values 255, 170, 85 and 0. The converter also accepts 173 and 82. Edit `font.bmp` directly, using only 255 and 0. Turn off antialiasing.

The placeholder includes a light-gray pixel at (9, 9) for this purpose.
`config/artwork.json` records the required byte, and conversion checks it for
both local and placeholder images. An incompatible edit stops conversion with
the filename, packed offset, and expected value.

## File conversion

[`config/artwork.json`](../config/artwork.json) records each resource's ROM range,
dimensions, firmware packing, and shared audio-byte requirements.
[`tools/assets.py`](../tools/assets.py) performs
extraction and conversion. BMP handling uses
[Pillow](https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#bmp).

The sprite files use the `NCCG` signature and a `CHAR` block containing dimensions
in tiles, four-bit color depth, and 8 × 8 tiles. Tiles appear from left to right
and top to bottom; each byte holds the left pixel in its low nibble. The public
[NitroPaint NCG reader](https://github.com/Garhoogin/NitroPaint/blob/be4d73c4843350665e0547590d7449996c6c33e8/NitroPaint/object/NitroCharacter.c#L642)
and [NCL writer](https://github.com/Garhoogin/NitroPaint/blob/be4d73c4843350665e0547590d7449996c6c33e8/NitroPaint/object/NitroPalette.c#L513)
describe these containers.

Extraction reconstructs the sprite pixels from the ROM. Its NCG writer supplies
fresh palette-zero tile attributes and a link to `grayscale.ncl`. The palette lets an editor display the four firmware shades. The BMP font sheet
arranges the stored glyph bytes into editable cells.

The generated firmware images consist of eight-row pages, read from top to
bottom. Each page contains columns from left to right. A column stores two bytes:
the high bit plane, then the low bit plane. Bit zero describes the upper pixel.
This is the layout consumed by `DisplayBlit` and `RasterColumn`.

The font stores one byte per column, with bit zero at the top row and a set bit
for black. `DisplayText` reads the 108 column bytes as 36 adjacent glyphs. All
eight rows survive extraction and conversion, including pixels outside the
usual letter height.

Conversion produces the aggregate initializer included by
`src/application/pw_builtin.c`.
The font and all six images retain their byte extents and order. Artwork sent
from the console into serial EEPROM is handled by the firmware's communication
path and remains separate from these resident images.

## Placeholders

The bundled artwork shows a simple rectangular device, facial expressions, a
button arrow, a radio symbol, and small alphanumeric glyphs. Its drawing source
is [`placeholders/draw.py`](placeholders/draw.py). Regenerate the files with:

```sh
uv run python -m assets.placeholders.draw
```
If conversion flags byte `0x53` in `walker.ncg`, keep pixel 9,9 light gray or black, and pixels 9,8 and 9,10 through 9,15 white or dark gray. Coordinates start at zero.

The source and images are [available under CC0](placeholders/LICENSE.md).
Placeholder builds retain the resident image dimensions and glyph cells while
producing a different firmware hash. The firmware continues to obtain its other
artwork through console-provided EEPROM resources.
The bundled placeholders are [CC0](placeholders/LICENSE.md).
Loading
Loading