Skip to content

Add Nintendo 3DS port (Embedded Swift)#1

Merged
colemancda merged 7 commits into
masterfrom
feature/3ds
Jul 13, 2026
Merged

Add Nintendo 3DS port (Embedded Swift)#1
colemancda merged 7 commits into
masterfrom
feature/3ds

Conversation

@colemancda

Copy link
Copy Markdown
Member

Overview

Adds ports/3DS: a Nintendo 3DS homebrew port of ClassicUI built with Embedded Swift, following the structure of junkbot-swift's ports/3DS — a Makefile-driven build targeting armv6-none-none-eabi (the 3DS's ARM11 application core), linked against devkitPro's libctru and packaged as a .3dsx with 3dsxtool/smdhtool.

The bottom (touch) screen is 320×240 — the exact iPod Classic LCD — and hosts the whole UI through a software rasterizer into an RGB565 row-major canvas, transposed into the column-major LCD framebuffer by common/shim.c. The top screen shows a static controls banner.

Design

The desktop SwiftUI-subset core (Sources/ClassicUICore) cannot compile under Embedded Swift: the view resolver requires Mirror (ViewBuilder tuple flattening), untyped existentials (any View), Foundation, and Observation, none of which exist on that target. Following the junkbot engine/port split, the port carries a small embedded mini-core (source/ClassicCore.swift) that reproduces the same iPod semantics:

  • menus with per-screen selection restore and scroll windowing (window math copied from NavigationModel.swift)
  • settings rows with right-aligned live values (Toggle-style)
  • Notes-style word-wrapped text pages with line scrolling
  • Now Playing stacks with a live progress bar and elapsed/remaining times
  • the eased navigation slide with a pinned status bar

The UI font is rasterized at build time from the host's Helvetica at 13 px (tools/gen_font.py, Pillow) into a 1bpp variable-width glyph table — the same face and size the desktop Silica renderer resolves. Renderer.swift deliberately imports nothing, so it also compiles on the host for pixel-level snapshot verification.

Controls

Input Action
D-pad / circle pad up/down rotate the click wheel
A center button (select)
B Menu (back), with the slide animation
X / Y play/pause
L / R previous / next track
START exit to the homebrew menu

Building

export DEVKITPRO=/opt/devkitpro
export DEVKITARM=$DEVKITPRO/devkitARM
cd ports/3DS && make

Requires devkitPro with 3ds-dev, a Swift toolchain whose Embedded stdlib ships an armv6-none-none-eabi slice, and python3 with Pillow. Verified: builds a 102 KB ClassicUI.3dsx.

Testing

  • Host-side snapshot renders of all five screen types (main menu, scrolling song list, settings values, Now Playing, text page) pixel-verified against the desktop renderer's look.
  • .3dsx packaging verified; not yet run on hardware or in an emulator.

Same shape as junkbot-swift's ports/3DS: the port's source/ compiles
whole-module for armv6-none-none-eabi (the 3DS's ARM11), links against
devkitPro's libctru, and packages as a homebrew .3dsx.

The bottom screen is 320x240 -- the exact iPod Classic LCD -- and hosts
the whole UI through a software rasterizer (RGB565 row-major canvas,
transposed into the column-major LCD framebuffer by common/shim.c). The
top screen shows a static controls banner.

The desktop SwiftUI-subset core cannot compile under Embedded Swift (no
Mirror, no untyped existentials, no Foundation, no Observation), so the
port carries an embedded mini-core (source/ClassicCore.swift) that
reproduces the same iPod semantics: menus with selection restore and
scroll windowing, settings value rows, Notes-style wrapped text pages,
Now Playing stacks with a live progress bar, and the eased navigation
slide with a pinned status bar.

The UI font is rasterized at build time from the host's Helvetica at
13 px (tools/gen_font.py, Pillow) into a 1bpp variable-width glyph
table -- the same face and size the desktop Silica renderer resolves.

Controls: D-pad/circle-pad up/down = click wheel, A = select, B = Menu
(back), X/Y = play/pause, L/R = previous/next track, START = exit.
Builds ClassicUI.3dsx on macos-26 with devkitPro's 3ds-dev and a Swift
nightly toolchain (release toolchains lack the armv6-none-none-eabi
Embedded stdlib slice; nightlies are pruned, so the latest snapshot is
resolved at run time -- same approach as junkbot-swift's 3DS CI), and
uploads the .3dsx as a build artifact.
Replace the 1bpp thresholded glyph table with 8-bit antialiased
coverage (tools/gen_font.py keeps Pillow's grayscale rasterization
instead of hard-thresholding it away) and alpha-blend each glyph pixel
against the destination in RGB565 (drawBytes/blend565). Glyphs draw one
column past their advance so antialiased right edges aren't clipped.

Font table grows from ~2.7 KB to ~21 KB; text now matches the desktop
renderer's look instead of hard-stairstepped edges.
@colemancda
colemancda merged commit 67a8997 into master Jul 13, 2026
3 checks passed
@colemancda
colemancda deleted the feature/3ds branch July 13, 2026 19:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant