Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
08c7ad0
Undertale: the engine's start (rooms cut from the wiki's maps, Frisk …
Mason363 Oct 14, 2026
9e8652d
Undertale: battles (FIGHT, ACT, ITEM, MERCY, the bullet board), the R…
Mason363 Oct 14, 2026
69b1699
Undertale: the whole Ruins, from the golden flowers to the door out
Mason363 Oct 14, 2026
2076b2a
Undertale: in NumPlay's releases, README, tests and How to play
Mason363 Oct 14, 2026
8db8eb8
Undertale: Snowdin, from the Ruins door to the edge of Waterfall
Mason363 Oct 16, 2026
9a491fe
Undertale: the longer speeches and signs in our own words
Mason363 Oct 16, 2026
2022ff1
Undertale: the pictures packed with LZMA, unpacked as they are needed
Mason363 Oct 16, 2026
2cf6bca
Undertale: Waterfall, from Snowdin to the bridge Undyne cuts
Mason363 Oct 16, 2026
21340aa
Undertale: Waterfall's marsh the way the game has it
Mason363 Oct 16, 2026
22fd044
Undertale: Waterfall past the bridge, to Temmie Village
Mason363 Oct 16, 2026
3e2d41f
Undertale: Waterfall's end, Undyne's fight, to Hotland's edge
Mason363 Oct 16, 2026
32b07d6
Undertale: Hotland's first floor, from the Lab to the elevator
Mason363 Oct 16, 2026
feebf6b
Undertale: Hotland's second floor, up to the elevator past the news
Mason363 Oct 16, 2026
26c356b
Undertale: fit every page of dialogue in its box
Mason363 Oct 16, 2026
19d257d
Undertale: Hotland's third floor, up to the MTT Resort's stairs
Mason363 Oct 16, 2026
482efda
Undertale: the MTT Resort, up to the door of the CORE
Mason363 Oct 16, 2026
15e8bfa
Undertale: the CORE and Mettaton EX, up to the elevator to the castle
Mason363 Oct 16, 2026
ad70fbc
Undertale: New Home, Asgore, Omega Flowey and the neutral ending
Mason363 Oct 16, 2026
350586f
Undertale: release notes for the whole game
Mason363 Oct 16, 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
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ jobs:
python3 tests/test_small_ram.py build/NumPlay.nwa
make -C games/numblocks host test
make -C games/championisland host test
make -C games/undertale host test

- uses: actions/upload-artifact@v4
with:
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ RAM_LIMIT = 148928
# custom builds of Epsilon 25.2's source give apps less: 133652 bytes; every game fits there too, in NumPlay and on
# its own (their big buffers are on the stack, 32 KB apart from that RAM, or in the RAM the software has left)
CUSTOM_RAM = 133652
BIG_APPS = Celeste HollowKnight ChampionIsland FinalEarth2 NumPowder NumBlocks NumDrive
BIG_APPS = Celeste HollowKnight Undertale ChampionIsland FinalEarth2 NumPowder NumBlocks NumDrive
# the calculator's space for installed apps (0x90200000 to 0x903F0000 in its flash)
APP_SPACE = 2031616
# Upsilon, the N0110 and N0115's custom software, gives .nwa apps less RAM: NumPlay-Upsilon.nwa has
Expand Down Expand Up @@ -205,6 +205,7 @@ APP_numvisuals = games/numvisuals/output/numvisuals.nwa:NumVisuals.nwa
APP_celeste = games/celeste/output/celeste.nwa:Celeste.nwa
APP_championisland = games/championisland/output/championisland.nwa:ChampionIsland.nwa
APP_hollowknight = games/hollowknight/output/hollowknight.nwa:HollowKnight.nwa
APP_undertale = games/undertale/output/undertale.nwa:Undertale.nwa
APP_finalearth2 = games/finalearth2/output/finalearth2.nwa:FinalEarth2.nwa
APP_numpowder = games/numpowder/output/numpowder.nwa:NumPowder.nwa

Expand All @@ -220,6 +221,7 @@ apps: | $(B)
$(MAKE) -C games/celeste build NWLINK="$(NWLINK)"
$(MAKE) -C games/championisland build NWLINK="$(NWLINK)"
$(MAKE) -C games/hollowknight build NWLINK="$(NWLINK)"
$(MAKE) -C games/undertale build NWLINK="$(NWLINK)"
$(MAKE) -C games/finalearth2 build NWLINK="$(NWLINK)"
$(MAKE) -C games/numpowder build NWLINK="$(NWLINK)"
cd games/tetris/tetris && NWLINK="$(NWLINK)" $(CARGO) build --release --quiet
Expand All @@ -235,6 +237,7 @@ apps: | $(B)
cp $(word 1,$(subst :, ,$(APP_celeste))) $(B)/apps/$(word 2,$(subst :, ,$(APP_celeste)))
cp $(word 1,$(subst :, ,$(APP_championisland))) $(B)/apps/$(word 2,$(subst :, ,$(APP_championisland)))
cp $(word 1,$(subst :, ,$(APP_hollowknight))) $(B)/apps/$(word 2,$(subst :, ,$(APP_hollowknight)))
cp $(word 1,$(subst :, ,$(APP_undertale))) $(B)/apps/$(word 2,$(subst :, ,$(APP_undertale)))
cp $(word 1,$(subst :, ,$(APP_finalearth2))) $(B)/apps/$(word 2,$(subst :, ,$(APP_finalearth2)))
cp $(word 1,$(subst :, ,$(APP_numpowder))) $(B)/apps/$(word 2,$(subst :, ,$(APP_numpowder)))
arm-none-eabi-strip --strip-unneeded $(B)/apps/Tetris.nwa
Expand Down Expand Up @@ -291,6 +294,7 @@ clean:
-$(MAKE) -C games/numvisuals clean
-$(MAKE) -C games/championisland clean
-$(MAKE) -C games/hollowknight clean
-$(MAKE) -C games/undertale clean
-$(MAKE) -C games/finalearth2 clean
-$(MAKE) -C games/numpowder clean
-cd games/tetris/tetris && $(CARGO) clean
Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ These are too big to fit next to NumPlay, so each one is its own app. Install it
<tr>
<td align="center"><a href="games/celeste/README.md"><img src="docs/media/thumb_celeste.png" width="200" alt="Celeste"><br><b>Celeste</b></a><br><sub>The whole climb, with every B-side and C-side</sub><br><a href="https://github.com/Mason363/NumPlay/releases/latest/download/Celeste.nwa">Download</a></td>
<td align="center"><a href="games/championisland/README.md"><img src="docs/media/thumb_championisland.png" width="200" alt="Champion Island"><br><b>Champion Island</b></a><br><sub>The whole island and its seven sports</sub><br><a href="https://github.com/Mason363/NumPlay/releases/latest/download/ChampionIsland.nwa">Download</a></td>
<td align="center"><a href="#roadmap"><b>More on the way</b></a><br><sub>Undertale, The Binding of Isaac, Plants vs. Zombies, Doom and more: see the roadmap</sub></td>
<td align="center"><a href="games/undertale/README.md"><img src="docs/media/thumb_undertale.png" width="200" alt="Undertale"><br><b>Undertale</b></a><br><sub>The whole Underground, from the golden flowers to the barrier and the neutral ending</sub><br><a href="https://github.com/Mason363/NumPlay/releases/latest/download/Undertale.nwa">Download</a></td>
</tr>
<tr>
<td align="center"><a href="#roadmap"><b>More on the way</b></a><br><sub>The Binding of Isaac, Plants vs. Zombies, Doom and more: see the roadmap</sub></td>
</tr>
</table>

Expand Down Expand Up @@ -171,7 +174,7 @@ Filmed on my own calculator, no emulator.
- [x] The Final Earth 2
- [x] The Powder Toy (NumPowder)
- [x] Block Blast
- [ ] Undertale
- [x] Undertale: the whole Underground, from the Ruins to New Home and the neutral ending
- [ ] The Binding of Isaac
- [ ] Plants vs. Zombies
- [ ] Doom
Expand Down Expand Up @@ -199,6 +202,7 @@ NumPlay is a fan project, made out of love for these games and for the NumWorks
- **Minecraft** by Mojang inspired NumBlocks, which follows Minecraft 1.8.8 exactly: it makes its worlds the way Minecraft 1.8.8 does and uses its blocks, items, recipes, textures, font and screens.
- **Celeste** inspired NumPlay's Celeste, which uses the game's own pictures, maps, texts and fonts. Not affiliated with Maddy Makes Games.
- **Hollow Knight** inspired NumPlay's Hollow Knight, which uses the game's own pictures, animations, maps, texts and fonts. Not affiliated with Team Cherry.
- **Undertale** by Toby Fox inspired NumPlay's Undertale, which uses the game's maps, pictures and texts from the [Undertale Wiki](https://undertale.fandom.com) and the game's free demo, and the **Pixel Operator** fonts by Jayvee Enaguas (public domain). Not affiliated with Toby Fox.
- **[The Powder Toy](https://powdertoy.co.uk)** inspired NumPowder, which ports its materials' behaviour, its font and its icons from [its source code](https://github.com/The-Powder-Toy/The-Powder-Toy) (GNU GPL v3.0), by Stanislaw K Skowronek, Simon Robertshaw and its contributors. Not affiliated with them.
- **[The Final Earth 2](https://store.steampowered.com/app/1180130/The_Final_Earth_2/)** by [Florian van Strien](https://florianvanstrien.nl) inspired NumPlay's The Final Earth 2, which uses the game's own pictures, fonts and texts, with additional sprites by LukXD99. Not affiliated with him.
- **Buckshot Roulette** by Mike Klubnika inspired NumPlay's Buckshot Roulette, whose scenes are rendered from the game through the Open Buckshot Roulette project (1503Dev). Fonts: Fake Receipt by Ray Larabie and Dot Matrix by Dionaea.
Expand All @@ -215,7 +219,7 @@ NumPlay is a fan project, made out of love for these games and for the NumWorks

NumPlay is licensed under the [GNU General Public License v3.0](LICENSE). Copyright (c) 2026 Mason Chen. Code and fonts from others keep their own licenses: the `UNLICENSE` files in the games' folders and the [LICENSES](LICENSES) folder. The names, artwork and music of the games NumPlay is inspired by belong to their makers and are not covered by it.

<sub>Unofficial fan games, not affiliated with NumWorks, RobTop Games (Geometry Dash), Hipster Whale (Crossy Road), the makers of Drive Mad, LocalThunk or Playstack (Balatro), Mike Klubnika or Critical Reflex (Buckshot Roulette), Hungry Studio (Block Blast!), Mojang or Microsoft (Minecraft), Maddy Makes Games (Celeste), Team Cherry (Hollow Knight), Florian van Strien (The Final Earth 2), The Powder Toy's developers (The Powder Toy), MateoConLechuga (Portal Returns), Valve (Portal), .GEARS (Flappy Bird), Bandai Namco (Pac-Man), Hasbro (Connect Four), Google (Snake, Solitaire, Block Breaker, Doodle Champion Island Games), STUDIO4°C or Microsoft (Minesweeper). Tetris is a trademark of The Tetris Company, Minecraft of Mojang Synergies AB, Pac-Man of Bandai Namco and Connect Four of Hasbro.</sub>
<sub>Unofficial fan games, not affiliated with NumWorks, RobTop Games (Geometry Dash), Hipster Whale (Crossy Road), the makers of Drive Mad, LocalThunk or Playstack (Balatro), Mike Klubnika or Critical Reflex (Buckshot Roulette), Hungry Studio (Block Blast!), Mojang or Microsoft (Minecraft), Maddy Makes Games (Celeste), Team Cherry (Hollow Knight), Toby Fox (Undertale), Florian van Strien (The Final Earth 2), The Powder Toy's developers (The Powder Toy), MateoConLechuga (Portal Returns), Valve (Portal), .GEARS (Flappy Bird), Bandai Namco (Pac-Man), Hasbro (Connect Four), Google (Snake, Solitaire, Block Breaker, Doodle Champion Island Games), STUDIO4°C or Microsoft (Minesweeper). Tetris is a trademark of The Tetris Company, Minecraft of Mojang Synergies AB, Pac-Man of Bandai Namco and Connect Four of Hasbro.</sub>

## Gallery

Expand Down Expand Up @@ -262,5 +266,6 @@ NumPlay is licensed under the [GNU General Public License v3.0](LICENSE). Copyri
</tr>
<tr>
<td align="center"><img src="docs/media/gallery/championisland.gif" width="100%" alt="Champion Island: climbing, artistic swimming and the marathon"><br><a href="games/championisland/README.md"><b>Champion Island</b></a></td>
<td align="center"><img src="docs/media/gallery/undertale.gif" width="100%" alt="Undertale: Flowey's friendliness pellets, Toriel's tile puzzle and her fire"><br><a href="games/undertale/README.md"><b>Undertale</b></a></td>
</tr>
</table>
Binary file added docs/media/gallery/undertale.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/thumb_undertale.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/undertale.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions docs/play.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,24 @@ Every sport shows its rules and keys before it starts. Your progress is also cop

<br clear="right">

## Undertale

<img src="media/undertale.gif" width="320" align="right" alt="Undertale: Flowey's friendliness pellets, Toriel's tile puzzle, Napstablook's tears and Toriel's fire">

Undertale comes as its own app, `Undertale.nwa`: it is too big to share the calculator with NumPlay.

| Key | What it does |
| --- | --- |
| **Arrows** (or 4, 6, 8, 2) | Walk, move your SOUL, choose |
| **OK**, **EXE** or 5 | Talk, read, confirm (Z in the game) |
| **Back** or **⌫** | Cancel, show the whole text at once (X in the game) |
| **toolbox**, **shift** or **var** | The menu: ITEM, STAT, CELL (C in the game) |
| **Home** | Keep the moment and quit |

Name the fallen human, then walk and talk your way through the Ruins, Snowdin, Waterfall, Hotland, the MTT Resort, the CORE and New Home. In a battle, FIGHT attacks (press **OK** when the bar crosses the middle), ACT checks a monster or does something to it, ITEM eats, and MERCY spares a monster that no longer wants to fight, or flees. Then move your SOUL to dodge; when it turns blue, it falls, and **Up** jumps; when it turns green, it stays put, and the arrows turn its shield to block. On ice you slide until something stops you. In Snowdin Town, walk into the shop to buy, and into the inn to rest. In Waterfall, walk into a bridge seed to push it into the water, where it blooms into a flower you can walk on; the bell calls the seeds back. A green bullet heals you. Walk up to a star and press **OK** to SAVE. Home keeps the moment you were at, and the next start is back there. Your save is also copied into `undertale_saves.py`, so installing the app again doesn't erase it. The Tem Shop in Temmie Village also buys what you carry. In Hotland, steam vents throw you over the lava, orange lasers hit you if you stand still and blue ones if you move, and in a shooting puzzle the arrows push the dark blocks and Z shoots. On the second floor, press the switches as the top conveyor belt carries you past them, and in Mettaton's news report, touch each bomb and defuse it while it's in the DEFUSE ZONE. When Muffet turns your SOUL purple, it keeps to three strings: **Up** and **Down** hop between them. In the tile puzzle, red tiles are walls, yellow ones shock you back, orange makes you smell of oranges (and blue water's piranhas bite then), and purple soap slides you on and smells of lemons. When your SOUL turns yellow, **OK** shoots. At the MTT Resort, the desk rents you a room and the hotel's doors want room service. On the CORE's laser bridge, walk while the power is off and freeze when it comes back on. Mettaton EX's show is won on its ratings: Boast, Pose, a Heel Turn and his brand's food raise them. In New Home, find both keys to unchain the stairs. Asgore breaks MERCY, so you'll have to fight him. Against Omega Flowey, dodge, and reach the ACT button that comes down among a SOUL's attack; once all six SOULs are on your side, reach FIGHT. The game ends at the neutral ending.

<br clear="right">

## Portal Returns

<img src="media/portal.gif" width="320" align="right" alt="Portal Returns: falling through a portal and flying across a test chamber">
Expand Down
2 changes: 2 additions & 0 deletions games/undertale/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
output/
build/
79 changes: 79 additions & 0 deletions games/undertale/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Undertale on the NumWorks calculator.
# make output/undertale.nwa
# make check link it like the calculator does (output/undertale.bin)
# make host build/play: the game on a computer, for tests and screenshots
# make test the game played on a computer (tests/check.py, with the sanitizers)
# make data src/data.bin and src/data.h from the public material tools/fetch.py gathers
# (REF=its folder, Python + Pillow + NumPy)
Q ?= @
# the ARM toolchain on the PATH, or in TOOLCHAIN=<its bin folder>
TOOLCHAIN ?=
BIN = $(if $(TOOLCHAIN),$(TOOLCHAIN)/)
CC = $(BIN)arm-none-eabi-gcc
NWLINK ?= npx --yes -- nwlink@1.0.0
STRIP ?= $(BIN)arm-none-eabi-strip --strip-unneeded
SIZE = $(BIN)arm-none-eabi-size
BUILD_DIR = output
REF ?=

# the drawing runs every frame at -O2; the rest is small at -Os
HOT = src/gfx.c src/bullets.c
COLD = $(filter-out $(HOT) src/plat_host.c src/plat_eadk.c,$(wildcard src/*.c)) src/lzma/LzmaDec.c
COLD_OBJ = $(patsubst src/%.c,$(BUILD_DIR)/cold/%.o,$(COLD))
HEADERS = $(wildcard src/*.h) ../common/epsilon_app.h ../common/epsilon_files.h

CFLAGS = -std=gnu11 $(shell $(NWLINK) eadk-cflags-device) -DDATA_BIN='"src/data.bin"'
CFLAGS += -O2 -Wall -Wextra -Wno-unused-parameter -fno-math-errno -fsingle-precision-constant
CFLAGS += -fno-tree-loop-distribute-patterns -flto -fno-fat-lto-objects -fwhole-program -fvisibility=internal
CFLAGS += -ffunction-sections -fdata-sections $(EXTRA)
LDFLAGS = -Wl,--relocatable -nostartfiles --specs=nano.specs
LDFLAGS += -Wl,-e,main -Wl,-u,eadk_app_name -Wl,-u,eadk_app_icon -Wl,-u,eadk_api_level
LDFLAGS += -Wl,--gc-sections -flinker-output=nolto-rel

.PHONY: build check clean host test data
build: $(BUILD_DIR)/undertale.nwa

# apps get 133652 bytes of RAM on custom builds of Epsilon 25.2 (148928 or more on official ones)
check: $(BUILD_DIR)/undertale.nwa
$(Q) $(NWLINK) nwa-bin --ram-length 133652 $< $(BUILD_DIR)/undertale.bin
@echo "BIN $(BUILD_DIR)/undertale.bin: $$(wc -c < $(BUILD_DIR)/undertale.bin) bytes"

$(BUILD_DIR)/data.o: src/data.S src/data.bin | $(BUILD_DIR)
$(Q) $(CC) -c -DDATA_BIN='"src/data.bin"' $< -o $@

$(BUILD_DIR)/cold/%.o: src/%.c $(HEADERS)
@mkdir -p $(@D)
$(Q) $(CC) $(CFLAGS) -Os -c $< -o $@

$(BUILD_DIR)/undertale.nwa: $(HOT) src/plat_eadk.c $(COLD_OBJ) $(HEADERS) $(BUILD_DIR)/icon.o $(BUILD_DIR)/data.o
@echo "LD $@"
$(Q) $(CC) $(CFLAGS) $(LDFLAGS) $(HOT) src/plat_eadk.c $(COLD_OBJ) $(BUILD_DIR)/data.o $(BUILD_DIR)/icon.o -lm -o $@
$(Q) $(STRIP) $@
$(Q) $(SIZE) $@

$(BUILD_DIR)/icon.o: src/icon.png | $(BUILD_DIR)
@echo "ICON $<"
$(Q) $(NWLINK) png-icon-o $< $@

$(BUILD_DIR):
$(Q) mkdir -p $@

# on a computer: tests/play.c plays it with scripted keys and saves screenshots
HOST_FLAGS = -std=gnu11 -O1 -g -Wall -Wextra -Wno-unused-parameter -Isrc -DHOST -DDATA_BIN='"src/data.bin"'
HOST_SAN = -fsanitize=address,undefined -fno-sanitize-recover=undefined
HOST_SRC = $(filter-out src/plat_eadk.c src/main.c,$(wildcard src/*.c)) src/lzma/LzmaDec.c
host: build/play
build/play: $(HOST_SRC) tests/play.c src/data.S src/data.bin $(HEADERS)
$(Q) mkdir -p $(dir $@)
$(Q) cc $(HOST_FLAGS) $(HOST_SAN) $(HOST_SRC) tests/play.c src/data.S -lm -o $@
@echo "HOST $@"

test: build/play
python3 tests/check.py build/play

data:
@test -n "$(REF)" || { echo "make data REF=<the folder tools/fetch.py filled>"; exit 1; }
python3 -I tools/pack.py $(REF)

clean:
$(Q) rm -rf $(BUILD_DIR) build
Loading
Loading