Overview
Demofruit is a love letter to the C64/Amiga/DOS graphics and music demoscene of the 1980s and 90s, built to run on the Fruit Jam. Six classic demoscene effects have been ported using Claude Opus 4.8 to run in Arduino on the RP2350-based Fruit Jam
- Starfield forward-warping star field with bouncing text
- Plasma palette cycled summed sine waves
- Chrome Sphere a dual-core raytraced renderer
- Valley Run wireframe terrain flyover is a nod to the vector-landscape demos
- Rotozoom - tiling bitmap texture tranforms with rotation and scaling and lens effect
- The Juggler - port of Eric Graham's 1987 Amiga Juggler raytracer — an animated character juggling three mirrored balls over a checkered floor
All of the demos (and the launcher screen) are accompanied by tracker MOD songs created by kqvc that playback using the PocketMod MOD player.
Page last edited July 21, 2026
Text editor powered by tinymce.
Intro to Demoscene
From Wikipedia, the free encyclopedia
The demoscene is an international computer art subculture focused on producing demos: self-contained, sometimes extremely small, computer programs that produce audiovisual presentations. The purpose of a demo is to show off programming, visual art, and musical skills. Demos and other demoscene productions (graphics, music, videos, games) are shared, voted on and released online at festivals known as demoparties.The scene started with the home computer revolution of the early 1980s, and the subsequent advent of software cracking. Crackers altered the code of computer games to remove copy protection, claiming credit by adding introduction screens of their own ("cracktros"). They soon started competing for the best visual presentation of these additions. Through the making of intros and stand-alone demos, a new community eventually evolved, independent of the gaming and software sharing scenes.
Starfield Effect
The classic space demo -- 3D points using a perspective projection to calculate 3D depth of 2D point coordinates.
Plasma Effect
Plasma is the name of a VGA graphics demo created by Bret Mulvey in 1988 and released on CompuServe. It uses a diamond-square algorithm to generate a 2D pattern, and then cycles the colors using hardware palette in its 256-color mode.
Chrome Ball Raytracer
A dual-core raytracer: a reflective chrome sphere orbiting over a cream-and-red checkerboard, with specular highlights, and a shadow. Core 1 renders while core 0 upscales and blits.
Vector Valley aka Fractal Landscape
Continuously scrolling grid of wireframe 3D terrain moving toward camera.
Rotozoom
2D image/texture effect with continuously rotating and scaling image. Added complexity with a magnifying glass effect superimposed over a region.
Amiga Juggler Demo
From Wikipedia:
Sculpt 3D is a raytrace application released in 1987 for Amiga computers programmed by Eric Graham. Sculpt 3D was one of the first ray tracing applications released for the Amiga computers. It proved that raytracing could be done on home computers as well as on mainframes.
The first demo that showed the raytracing capabilities was an animation of a juggler juggling three chrome balls. Even though the juggler was constructed out of spheres, the balls' reflections and movement made it look realistic. The juggler demo was generated on an experimental version of Sculpt 3D. The animation, released in January 1986, generated so much interest that the full 3D application was programmed.
Page last edited July 21, 2026
Text editor powered by tinymce.
Demofruit Installation and Use
Software Installation
Head here first to download the latest release UF2 of Demofruit for Fruit Jam.
Enter RP2350 bootloader mode
Entering the RP2350 bootloader is easy. Complete the following steps.
Before you start, make sure your Fruit Jam is plugged into USB port to your computer using a data/sync cable. Charge-only cables will not work!
Turn on the On/Off switch - check that you see the ON light on so you know the board is powered.
To enter the bootloader:
- Press and hold the BOOT/BUTTON1 button down. Don't let go of it yet!
- Press and release the Reset button. You should still have the BOOT/BUTTON1 button pressed while you do this.
- Continue holding the BOOT/BUTTON1 button until you see the RP2350 drive appear.
- You can now release but BOOT/BUTTON1 button.
Drag UF2
Navigate to the folder where you downloaded the uf2 file, then drag it onto the RP2350 drive.
The board will automatically reboot and Demofruit will launch automatically.
Fruit Jam Connections
Plug in an HDMI cable to the Fruit Jam's DVI-D Video Output port, then plug the other end of the cable into an HDMI display.
Plug in a 3.5mm TRS cable to the Fruit Jam's Stereo Headphone jack, and then plug the other end into a set of powered speakers.
Play Demos
You can use the Fruit Jam's buttons to navigate the Demofruit menu. Directions are listed on the main page:
- Button #3 = Up
- Button #2 = Down
- Button #1 = Launch
- Reset = Quit a demo and go back to menu screen
Starfield
A forward-warping star field with per-letter bouncing chrome text. The classic "space + scroller" demo opener.
- Button1 — cycle the text message (`
Adafruit`, `Fruit Jam`, `kqvc`,
`L 4 r 5 5 5`; editable at the top of starfield_impl.h) - Button2 — step warp intensity
- Button3 — randomize star density
Plasma
Palette-cycled plasma — smoothly morphing colored blobs generated from summed sine waves. Originally used the RP2350's hardware palette (DVHSTX8); in the sampler it renders through the shared 16-bit display via a software palette LUT.
- Button1 — cycle palette (Fire / Ice / Toxic / Lava / Rainbow)
- Button2 — toggle palette-rotation vs. full-animate mode
- Button3 — randomize wave frequencies and drift (can produce very large, slow cells)
Chrome Sphere
A dual-core raytracer: a reflective chrome sphere orbiting over a cream-and-red checkerboard, with specular highlights and a shadow. Core 1 renders while core 0 upscales and blits.
- Button1 — randomize light angle
- Button2 — randomize camera height
- Button3 — randomize camera distance
Valley Run
A wireframe trench flythrough with a flat-shaded fighter ship strafing and banking through it — a nod to the vector-landscape demos and to Star Fox.
- Button1 — cycle wire color (ship auto-picks the complementary color)
- Button2 — step scroll speed (5 presets)
- Button3 — randomize terrain
Rotozoom
A tiling bitmap rotating and scaling in real time (a "rotozoomer"), with a blue-tinted spherical lens bubble drifting over it in a Lissajous orbit. Uses fixed-point math and dual-core rendering. The source bitmap is a 128×128 image baked into `texture.h` (see "Creating the rotozoom image" below).
- Button1 — cycle motion preset (Normal / Fast / Reverse / Slow Drift /
Zoom Frenzy) - Button2 — toggle the lens
- Button3 — randomize the zoom range
The Juggler
A port of Eric Graham's 1987 Amiga *Juggler* raytracer — a robotic figure juggling three mirrored balls over a checkered floor. The original took about an hour per frame on a 7 MHz 68000; the Fruit Jam renders a frame in about 1.5 seconds.
On launch it raytraces 24 frames of a juggling cycle into PSRAM (watch the "venetian blind" render, ~20 seconds, both cores working), then loops smooth playback forever — the same render-then-playback architecture as Graham's original `movie` player.
- Button1 — slower playback
- Button2 — faster playback (3/6/12/18/24/30 fps)
- Button3 — pause / resume
Page last edited July 21, 2026
Text editor powered by tinymce.
Customization
Demofruit is written in Arduino, so if you want to customize it go ahead and use the information on the next couple of pages to install Arduino IDE and set it up for use with the Fruit Jam.
Texture Conversion
The Rotozoom demo tiles and spins a 128×128 pixel source image stored in texture.h as an RGB565 array.
Image requirements:
- 128×128 pixels (power-of-two - the demo wraps with a fast bitwise mask,
TEXTURE_MASK 0x7F, instead of division) - 24-bit uncompressed BMP (Windows/DIB), no alpha, no RLE
- Bold, high-contrast content — thick shapes, chunky patterns, or a strong central subject. Concentric shapes, mandalas, chunky pixel art, or a bold face/logo all work well.
Making the BMP
Any image editor works — Photoshop, GIMP, Affinity, Procreate, Pixelmator. Create or resize to 128×128 and export/save as BMP, 24-bit, uncompressed.
Converting the BMP to texture.h
The helper script, bmp2texture.py, bakes the image into the C header the sketch expects. It reads the BMP, handles its bottom-up row order, converts each pixel to RGB565, and writes the `texture[128][128]` array with the TEXTURE_W, TEXTURE_H, and TEXTURE_MASK defines.
python3 bmp2texture.py yourimage.bmp texture.h
The second argument is optional and defaults to texture.h. The script validates its input and will refuse anything that isn't a 128×128, 24-bit, uncompressed BMP — with a message telling you what to fix (wrong size, wrong bit depth, or compression enabled). Overwrite the project's texture.h with your converted version and recompile to change the rotozoom image.
Music
Each demo (and the menu) has its own soundtrack, played as a ProTracker MOD file. MODs are the native music format of the demoscene: a small bank of digitized instrument samples plus pattern data describing which sample plays at what pitch on each channel.
Playback uses pocketmod, an MIT-licensed MOD player, which renders the music in real time on core 0. The audio path is:
pocketmod mixes stereo float samples → converted to 16-bit → sent to the TLV320DAC3100 codec over I2S (via the RP2350's PIO + DMA).
Songs live in the sketch as C headers, one per slot:
Each header contains a MOD_DATA byte array; the sketch wraps each in its own namespace so they don't collide. pocketmod supports up to 32 channels, so 6-channel, 8-channel, and wider MODs play fine (not just 4-channel) — though authentic 4-channel MODs are the period-correct choice and mix with the least CPU. If a song fails to load, the sketch logs the failure and simply runs silent rather than crashing.
Converting your own MOD files
A helper script, mod2header.py, converts any ProTracker .mod file into the header format the sketch expects. It does a straight byte-to-array conversion — it does not alter the music.
python3 mod2header.py yoursong.mod song_4.h
The second argument is optional; if omitted it writes yoursong.h. To change a demo's music, convert your MOD directly to the matching song_N.h , then recompile. To change the menu music, target song_0.h.
Where to find MODs
- The Mod Archive filter by license and by file size (leaner tracks make smaller headers and faster compiles)
- Compose your own in a tracker such as MilkyTracker runs natively on macOS, Windows, and Linux
Notes on file size and editing
The bulk of a MOD's size is its sample data, not its pattern/song length. To shrink a header:
- In a tracker, the biggest lever is downsampling the largest samples in the Sample Editor (roughly halves their size per octave of rate reduction); trimming silence and tightening loops also helps.
- Shortening the order list (how many patterns play in sequence) plus deleting the now-unused patterns/samples reduces size only modestly, but is the right tool for fitting a track's *length* to a demo.
pocketmod loops a MOD automatically at its end, so a clean loop point often matters more than absolute length. Whatever you edit, listen to the loop seam (play to the end and let it wrap) before converting to a header.
Speaker Easter Egg
The onboard speaker port on the Fruit Jam is muted by default; the headphone jack always works. To enable the speaker: connect the A1 pad (GPIO41) on the GPIO header to GND with a jumper wire, then reset/power on the board.
To mute it: remove the jumper and reset.
The jumper is read once at startup (with an internal pull-up: tied to GND = speaker on, left open = muted), and the state is printed to the serial monitor. The speaker connector needs the board powered from 5 V to be audible.
Page last edited July 21, 2026
Text editor powered by tinymce.
Arduino IDE Setup
The Arduino Philhower core provides support for RP2350 microcontroller boards. This page covers getting your Arduino IDE set up to include your board.
Arduino IDE Download
The first thing you will need to do is to download the latest release of the Arduino IDE. The Philhower core requires version 1.8 or higher.
Download and install it to your computer.
Once installed, open the Arduino IDE.
Adding the Philhower Board Manager URL
In the Arduino IDE, and navigate to the Preferences window. You can access it through File > Preferences on Windows or Linux, or Arduino > Preferences on OS X.
The Preferences window will open.
In the Additional Boards Manager URLs field, you'll want to add a new URL. The list of URLs is comma separated, and you will only have to add each URL once. The URLs point to index files that the Board Manager uses to build the list of available & installed boards.
Copy the following URL.
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
Add the URL to the the Additional Boards Manager URLs field (highlighted in red below). If you already have a board support package in that field, type a comma at the end then add the address above. Both will then be loaded by the Arduino IDE.
Click OK to save and close Preferences.
Add Board Support Package
In the Arduino IDE, click on Tools > Board > Boards Manager. If you have previously selected a board, the Board menu item may have a board name after it.
In the Boards Manager, search for RP2040. Scroll down to the Raspberry Pi Pico/RP2040/RP2350 by Earle F Philhower, III entry. Click Install to install it.
Once installation is complete, click Close to close the Boards Manager.
Choose Your Board
In the Tools > Boards menu, you should now see Raspberry Pi RP2040/RP2350 Boards (in sketchbook).
Navigate to the Raspberry Pi RP2040/RP2350 Boards menu. You will see the available boards listed.
In the Raspberry Pi Pico/RP2040/RP2350 Boards menu choose Adafruit Fruit Jam RP2350.
Now you're ready to begin using Arduino with your RP2350 board!
Page last edited July 21, 2026
Text editor powered by tinymce.
Code the Fruit Jam
If you want to customize the Demofruit or just have a look at the code, download the Project Bundle linked below.
// SPDX-FileCopyrightText: 2026 John Park with Claude Opus 4.8 for Adafruit Industries
//
// SPDX-License-Identifier: MIT
//
// Demofruit Fruit Jam Demoscene demo
// music by kqvc
// ═══ DEMOFRUIT — FRUIT JAM DEMOSCENE SAMPLER ═══
// Six demos, one UF2. Boot menu selects; watchdog-scratch reboot launches
// each demo with its ideal display configuration; RESET returns to menu.
//
// STARFIELD — warp field + bouncing chrome text (DVHSTX16)
// PLASMA — palette plasma, software palette (DVHSTX16)
// CHROME SPHERE — dual-core raytracer (DVHSTX16)
// VALLEY RUN — wireframe trench + shaded ship (DVHSTX16)
// ROTOZOOM — texture spin/zoom + lens (DVHSTX16, dual-core)
// THE JUGGLER — Eric Graham 1987, PSRAM playback (DVHSTX16, dual-core)
//
// REQUIREMENTS: Board = Adafruit Fruit Jam RP2350, CPU Speed = 150MHz.
#include <Arduino.h>
#include <new>
#include <Adafruit_dvhstx.h>
#include <Adafruit_GFX.h>
#include <Fonts/FreeSansBold24pt7b.h>
#include <pico/multicore.h>
#include <hardware/watchdog.h>
#include <math.h>
#include "common.h"
#include "audio.h"
#include "leds.h"
// ── Songs: song_0.h = menu, song_1..6.h = demos in menu order ────────────────
// Each is a standard mod2header output (MOD_DATA), wrapped in a namespace.
namespace song0 {
#include "song_0.h"
}
namespace song1 {
#include "song_1.h"
}
namespace song2 {
#include "song_2.h"
}
namespace song3 {
#include "song_3.h"
}
namespace song4 {
#include "song_4.h"
}
namespace song5 {
#include "song_5.h"
}
namespace song6 {
#include "song_6.h"
}
struct SmpSong { const unsigned char *data; unsigned size; };
static const SmpSong g_songs[7] = {
{ song0::MOD_DATA, sizeof(song0::MOD_DATA) }, // menu
{ song1::MOD_DATA, sizeof(song1::MOD_DATA) }, // starfield
{ song2::MOD_DATA, sizeof(song2::MOD_DATA) }, // plasma
{ song3::MOD_DATA, sizeof(song3::MOD_DATA) }, // chrome sphere
{ song4::MOD_DATA, sizeof(song4::MOD_DATA) }, // valley run
{ song5::MOD_DATA, sizeof(song5::MOD_DATA) }, // rotozoom
{ song6::MOD_DATA, sizeof(song6::MOD_DATA) }, // the juggler
};
#include "texture.h" // rotozoom bitmap (global: plain data)
// ── Demo modules, each in its own namespace ──────────────────────────────────
namespace sf {
enum RampPhase { CRUISE, RAMP, WARP, SNAP, RECOVER };
#include "starfield_impl.h"
}
namespace pl {
#include "plasma_impl.h"
}
namespace rt {
#include "raytracer_impl.h"
}
namespace vy {
#include "valley_impl.h"
}
namespace rz {
#include "rotozoom_impl.h"
}
namespace jg {
#include "juggler_impl.h"
}
#include "launcher.h"
// ── Displays ──────────────────────────────────────────────────────────────────
// DVHSTX16 as a GLOBAL — identical to every proven standalone sketch
// (the library does clock setup tied to static construction). One display,
// one begin() per boot. All demos render through it; plasma uses a
// software palette LUT instead of DVHSTX8 hardware palette (runtime
// display construction proved unreliable).
DVHSTX16 display16(ADAFRUIT_FRUIT_JAM_CFG, DVHSTX_RESOLUTION_320x240,
true /* double_buffered */);
static void dispFail() {
pinMode(LED_BUILTIN, OUTPUT);
for (;;) {
Serial.println("FATAL: display.begin() failed");
digitalWrite(LED_BUILTIN, HIGH); delay(150);
digitalWrite(LED_BUILTIN, LOW); delay(850);
}
}
static DVHSTX16 *use16() {
DVHSTX16 *d = &display16;
Serial.println("display16.begin()..."); Serial.flush();
if (!d->begin()) dispFail();
Serial.println("display16 OK"); Serial.flush();
smpPsramFix();
return d;
}
// ── Boot dispatch ─────────────────────────────────────────────────────────────
void setup() {
smpBtnInit();
ledInit();
Serial.begin(115200);
uint32_t t0 = millis();
while (!Serial && (millis() - t0) < 2000) delay(10);
int id = smpBootDemoId(); // -1 = menu; clears scratch so reset -> menu
Serial.printf("boot: demo id %d\n", id); Serial.flush();
switch (id) {
case 0: { // STARFIELD
DVHSTX16 *d = use16();
smpAudioInit(g_songs[1].data, g_songs[1].size);
ledAll(LED_C_STARFIELD); // signature color carried from menu
Serial.println("demo: starfield");
sf::starfieldRun(*d);
}
case 1: { // PLASMA — software palette through the shared display
DVHSTX16 *d = use16();
smpAudioInit(g_songs[2].data, g_songs[2].size);
ledAll(LED_C_PLASMA); // signature color carried from menu
Serial.println("demo: plasma");
pl::plasmaInit();
for (;;) { smpAudioPump(); pl::plasmaTick(*d); }
}
case 2: { // CHROME SPHERE
DVHSTX16 *d = use16();
smpAudioInit(g_songs[3].data, g_songs[3].size);
ledAll(LED_C_CHROME); // signature color carried from menu
Serial.println("demo: raytracer");
rt::rtInit(*d);
multicore_launch_core1(rt::rtCore1Entry);
for (;;) { smpAudioPump(); rt::rtCore0Loop(*d); }
}
case 3: { // VALLEY RUN
DVHSTX16 *d = use16();
smpAudioInit(g_songs[4].data, g_songs[4].size);
ledAll(LED_C_VALLEY); // signature color carried from menu
Serial.println("demo: valley");
vy::valleyInit();
for (;;) { smpAudioPump(); vy::valleyTick(*d); }
}
case 4: { // ROTOZOOM
DVHSTX16 *d = use16();
smpAudioInit(g_songs[5].data, g_songs[5].size);
ledAll(LED_C_ROTOZOOM); // signature color carried from menu
Serial.println("demo: rotozoom");
rz::rzInit();
multicore_launch_core1(rz::rzCore1Entry);
for (;;) { smpAudioPump(); rz::rzCore0Loop(*d); }
}
case 5: { // THE JUGGLER
DVHSTX16 *d = use16();
smpAudioInit(g_songs[6].data, g_songs[6].size);
ledAll(LED_C_JUGGLER); // signature color carried from menu
Serial.println("demo: juggler");
jg::jugglerRun(*d);
}
default: { // MENU
DVHSTX16 *d = use16();
smpAudioInit(g_songs[0].data, g_songs[0].size);
Serial.println("menu"); Serial.flush();
menuRun(*d);
}
}
}
void loop() { /* unreachable — every path above loops forever */ }
How It Works
Each demo began as a standalone sketch and was merged into the sampler launcher. They are listed here in menu order, which is also roughly chronological by technique.
Because the each demo uses a slightly different display configuration, and the DVI HSTX library display object is constructed once at startup, Demofruit uses a reboot launcher. This is a similar technique multi-part Amiga demos used to chain-load segments.
On power-up or after pressing reset, no launch flag is set, so the sketch shows the menu. This includes a tumbling, 3D cube behind green text, with the cube tinted to the highlighted demo's signature color.
Menu navigation
- Button 3 = up
- Button 2 = down
- Button 1 = launch
Selecting a demo writes its ID plus a magic number into the RP2350's watchdog scratch registers (which survive a soft reboot) and triggers a watchdog reboot.
On the next boot the sketch sees the flag, immediately clears it, and constructs that demo's ideal display and runs it forever. Because the flag was cleared at demo start, pressing **Reset** returns you to the menu.
The NeoPixel strip reinforces the UI throughout: in the menu it glows the highlighted demo's signature color; inside a demo it shows the current setting (e.g. Valley Run lights *N* of 5 pixels for the speed preset, in the current wire color). Pixel order is top-to-bottom. A brief blink acknowledges "randomize"-type buttons that have no persistent state to display.
Starfield
The star field is one of the oldest tricks in the demo scene, and it works through a bit of faked perspective. Each star is really just a point with an X, Y, and Z position, where Z represents how far away it is. To draw a star, the code divides its X and Y by its Z: distant stars (large Z) get pulled in close to the center of the screen and drawn small, while near stars (small Z) spread out toward the edges and grow large.
To create the sense of flying forward, every frame simply decreases each star's Z a little. As Z shrinks, that division makes the star rush outward and accelerate, and when it passes the viewer it gets recycled to a new random position far away. That single divide per star is the whole illusion of three-dimensional motion.
On the Fruit Jam this runs at full frame rate because the math is so simple, which leaves plenty of room for the bouncing chrome text layered on top. Rather than allocating memory for each letter, the code reads the font's pixel data directly from the Fruit Jam's flash and plots the glyphs over the moving field, letting them bob and scale independently. The result is the classic "warp speed plus scroller" opener that demos have used for decades.
Plasma
This effect comes from adding several sine waves together — one that varies across the screen horizontally, one vertically, one along the diagonal, and one radiating from a center point. Where those waves reinforce each other you get bright values; where they cancel, dark ones. Summed across every pixel, the overlapping ripples produce those signature organic, lava-lamp blobs. The demoscene's key insight was that you do not need to recompute all that trigonometry every frame. Instead you precompute the wave values into tables once, then animate purely by sliding color-lookup offsets through those tables, a cheap addition instead of expensive sine calls.
The Fruit Jam version keeps that period-authentic approach. It builds the wavetables at startup and, in its lightest mode, animates by rotating a 256-entry color palette rather than touching the image at all.
The original Amiga and VGA effects leaned on hardware palette registers to do this for free; the Fruit Jam instead keeps an 8-bit index map and translates it through a software color lookup table into the 16-bit RGB565 values the display wants.
That software palette pass is so fast on the RP2350 that the demo also offers a fuller mode where the wave map itself is recomputed every frame, letting the plasma physically writhe and drift rather than just cycle colors.
Chrome Sphere
This demo is a real raytracer, the same fundamental technique used to render photorealistic 3D images. For every pixel on the screen, it casts an imaginary ray out from the camera into the scene and asks what that ray hits. If it strikes the checkered floor, the code works out which check square it landed in and shades it accordingly. If it strikes the chrome sphere, the ray bounces, it reflects off the surface and continues on to see what the sphere would be mirroring, which is what gives the ball its shiny, reflective look. The code also traces a second ray from each floor point toward the light to decide whether that spot sits in the sphere's shadow. Raytracing was far too slow for real-time animation in the demoscene era, which is exactly why doing it live here is satisfying.
The Fruit Jam pulls this off by putting both of the RP2350's processor cores to work. The chip has two cores, and this demo hands half the image to each: they raytrace alternating rows of the frame simultaneously, roughly doubling the speed. To keep the frame rate smooth, the scene is traced at a reduced resolution and scaled up to fill the display. As you press the buttons to move the light and camera, the new values are shared between the cores so both stay in sync, and the whole reflective, shadowed scene re-renders continuously in real time.
Valley Run
Valley Run is a flat-shaded 3D fly through in the spirit of the vector landscape demos and games like Star Fox. The trench is a mesh of points laid out in a grid, scrolling toward you to create forward motion. Each of those 3D points is projected onto the 2D screen using the same perspective divide the star field uses: divide by distance so far parts of the trench converge toward a vanishing point. The walls are drawn as wireframe lines connecting the grid, and the fighter ship is built from a handful of triangular faces. Each face is "flat shaded," meaning the code calculates how directly it faces the light and fills the whole triangle with a single brightness, giving that crisp, faceted, low-poly look without the cost of smooth shading.
Because the geometry is simple, the Fruit Jam renders the whole scene on a single core with cycles to spare. The ship's motion is more than decoration: it's knee-like banking and side-to-side strafing are computed each frame, so it leans into its movement naturally. A subtle animation also runs along the trench walls so the corridor appears to breathe as it scrolls. The buttons let you change the wire color, step through five scroll speeds, and randomize the terrain's height and roughness on the fly.
Rotozoom
A "rotozoomer" takes a flat image and rotates and scales it in real time while tiling it infinitely across the screen, and it was a demoscene staple because it looks far more computationally expensive than it actually is. The trick is to work backwards. Instead of asking "where does each pixel of my image end up on screen," the code asks, for every screen pixel, "which pixel of the source image belongs here?" It walks across the screen stepping through the source image at an angle and scale determined by the current rotation and zoom. When the coordinates run off the edge of the 128x128 source, they simply wraparound, which is what tiles the pattern seamlessly. Crucially, this stepping is done with fixed-point math — numbers that use integer arithmetic to represent fractions — because integer operations were, and on a microcontroller still are, much faster than floating point for this kind of per-pixel work.
The Fruit Jam version tiles a 128x128 texture whose power-of-two size lets the wrap-around happen with a single fast bitwise mask instead of a division. Both processor cores share the work of filling the screen. On top of the spinning texture, a translucent "lens" bubble drifts around in a looping orbit, magnifying and blue-tinting whatever is beneath it by resampling those pixels through a spherical distortion, a little flourish that shows off how cheap the underlying sampling really is. You can cycle motion presets, toggle the lens, and randomize the zoom range with the buttons.
The Juggler
The Juggler is a piece of computer graphics history. In 1986, Eric Graham created a raytraced animation of a robot juggling three mirrored balls to showoff what the new Commodore Amiga could do, and it became one of the most famous demos of its era. The catch was that each frame took the Amiga's 7 MHz processor around an hour to raytrace, so Graham rendered the frames ahead of time and then played them back in sequence like a movie. This demo is a faithful port of Graham's original 1987 raytracer code, right down to how it bounces rays off the reflective spheres and the checkered floor, with its recursion depth capped so mirror-in-mirror reflections terminate cleanly.
Demofruit follows Graham's exact strategy of render-first, play-later, just way faster. When you launch the demo, both cores of the RP2350 raytrace 24 frames of a full juggling cycle and store them in the Fruit Jam's 8 MB of PSRAM — you can watch the image build in a "venetian blind" pattern as the two cores fill alternating rows, which takes about 20 seconds. Once all 24 frames are cached, the demo plays them back in a smooth loop, and the buttons let you change the playback speed or pause. A single frame that took Graham's Amiga roughly an hour now renders in about a second and a half. The juggling motion itself — a proper three-ball cascade, with the figure's knees bending and body shifting its weight — was recreated for this port, since the original animation data was never part of the published source code.
Page last edited July 21, 2026
Text editor powered by tinymce.