Files
radiantwave/archive/AGENTS.md
T
2026-08-07 14:51:41 -05:00

8.7 KiB
Raw Blame History

RadiantWave — Codebase Architecture

What This Is

A kiosk application for "RadiantWave" wellness technology. It displays scrolling proclamation text rendered with custom shaders, plays audio at solfeggio frequencies, and manages WiFi/provisioning on a headless Linux kiosk.

Core libraries: SDL2, OpenGL 3.3, SQLite (GORM), D-Bus (NetworkManager). Build system: Nix flake. CI: Forgejo on a self-hosted runner.


Application Lifecycle

rhemawave.go  →  internal/application.Run()

Run() performs:

  1. SDL2 init (video, audio, events)
  2. Create window with SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN
  3. OpenGL 3.3 context
  4. ShaderManager singleton init
  5. FontManager singleton init
  6. Mixer singleton init
  7. SQLite connect + GORM auto-migrate
  8. Sync proclamations from remote API
  9. Start audio download goroutine
  10. Enter SDL event loop

The event loop polls SDL events, calls Router.Update() + Router.Render(), and runs checkState() to drive the provisioning state machine.


Provisioning State Machine

Defined in internal/application/application.go:

FIRST_RUN → NEEDS_INTERNET → NEEDS_LICENSE_KEY → NEEDS_EMAIL_ADDRESS → DOWNLOADING_ASSETS → READY

Each state maps to a page. checkState() evaluates DB values and network status to decide which page to show.


Page System (internal/page/)

All pages implement the Page interface:

type Page interface {
    Init(ctx *PageCtx)
    HandleEvent(event sdl.Event) error
    Update(dt float64)
    Render()
    Destroy()
}

PageCtx provides: *gorm.DB, screen dimensions, DownloadDone channel, NavigateTo(pageType) callback.

Router (router.go) maps pageType enum → concrete page struct, manages lifecycle (Destroy old → Init new).

Page Purpose
Home Version info, download progress, license status, F2/F3 keybinds
ScrollerPage Main screen: scrolling proclamation text with shaders, line patterns, color patterns, audio playback, velocity control
SettingsPage Menu: audio, WiFi, proclamations, license, email, logs, volume
AudioOptions Audio device selection (via PipeWire pactl), on/off, volume
ProclamationOptions Browse/search/select proclamations from DB
WiFiSetupPage WiFi scan, WPA connect, password entry
LicenseKeyPage License key text input, saves to DB
EmailAddressPage Email text input with regex validation
LogViewerPage Scrollable log viewer from DB
DownloadingAssets Stub during download phase
Message Generic message page (stub)

OpenGL Rendering

Virtual Framebuffer (internal/graphics/virtual_framebuffer.go)

Renders to a 1920x1080 FBO, then glBlitFramebuffer() to the native window resolution. This decouples layout from display size.

Shaders (share/rhemawave/shaders/)

Shader Purpose
scroller.vert/frag Text with font texture; uniform u_textColor for per-word coloring
text.vert/frag Non-scrolling text elements
solid_color.vert/frag Flat color (backgrounds, UI)

Font System (internal/fontManager/)

FontManager (singleton) wraps SDL_ttf + OpenGL textures:

  • Loads TTF/OTF fonts, creates GL texture atlas per font.
  • GetGlyph(fontName, rune) → glyph metrics (advance, bearing, texture coords).
  • RenderString(fontName, text, x, y, color, scale) → renders to GL.
  • Scrambled font mode: Characters are spatially rearranged in the atlas, creating visual "frequency signatures" when rendered.

Fonts (share/rhemawave/fonts/)

File Use
Phibonacci_regular.otf Decorative display font
Roboto-Regular.ttf Clean sans-serif (UI)
SpaceMonoNerdFont-Regular.ttf Monospace with icons

Pattern System (internal/pattern/)

Line Patterns (internal/pattern/line/)

LinePattern interface: GetNextLine(words, wordIndex, ctx) []string. Registered via init() (plugin-style Registry map).

Pattern Behavior
Fibonacci Oscillating sequence (1,2,3,5,8,13,21) for words-per-line; creates wave in 2D
Sine Wave Sine function → line width as % of screen; 5 cycles over 67 lines

Color Patterns (internal/pattern/color/)

Distributes colors across words using Fibonacci-oscillating durations (1,1,2,3,5,8,13,21,34,55 words per color, then reverses).


Audio System

Mixer (internal/mixer/)

Singleton wrapping SDL_mixer:

  • PipeWire sink enumeration via pactl list sinks short
  • Volume persistence to DB
  • Device switching with volume restore
  • Play(wavPath), Stop(), SwitchDevice(deviceName)

Audio Files

.wav files are gitignored. Downloaded at runtime from files.radiantwavetech.com, verified against checksums.md5, stored in ~/.radiantwave/audio/. A goroutine handles async download; a channel signals completion.

Solfeggio Frequencies

9 presets (174Hz–963Hz), each mapped to a "healing intention". Stored in DB, selectable in AudioOptions page.


Database (internal/db/)

SQLite at ~/.local/share/rhemawave/database.sqlite (WAL mode).

Model Purpose
AppConfig Key-value store: license key, email, audio device, volume, download state
Log Application logs (level, message, timestamp)
Proclamation Synced proclamation text (title, content, category, intent colors)
ProclamationCategory Category metadata
SolfeggioFrequency 9 frequency presets
AudioFile Downloaded audio files (path, checksum, status)

Network (internal/network/)

D-Bus integration via gonetworkmanager:

  • Async WiFi scan (channel-based results)
  • WPA/WPA2 connection support
  • Captive portal detection

Remote APIs

Endpoint Purpose
portal.radiantwavetech.com/api/v1/proclamations Sync proclamations
files.radiantwavetech.com/files/ Audio file downloads
HTTP API License validation

Generic HTTP client in internal/netreq/netReq.go: typed GetJSON[T]() and Download() with progress callback.


Assets (share/rhemawave/)

Copied into nix store at build time:

  • fonts/ — 3 font files (see above)
  • shaders/ — 3 GLSL shader pairs
  • proclamations/home/ — 16 text files (healing, wellness, productivity)
  • proclamations/commercial/ — 1 text file
  • tos/ — 3 HTML files (Welcome, Notice, Getting Started)

C++ Rebuild Map

These are the components that need to be reimplemented in C++ with CMake/Ninja:

Component Go Package C++ Equivalent External Deps
Entry / Event Loop rhemawave.go, application.go main.cpp SDL2
Provisioning FSM application.go State machine class —
Page Router page/router.go Router class —
Page (11 types) page/*.go 11 page classes implementing Page interface —
OpenGL Context application.go GL context init SDL2, OpenGL 3.3
Virtual Framebuffer graphics/virtual_framebuffer.go FBO + blit OpenGL
Shader Manager graphics/shaderManager.go Shader class OpenGL
Font Manager fontManager/fontManager.go Font atlas class SDL_ttf, OpenGL
Line Patterns pattern/line/*.go Pattern classes + registry —
Color Patterns pattern/color/*.go Color pattern class —
Mixer mixer/mixer.go Mixer class SDL_mixer
Network / WiFi network/network.go NetworkManager wrapper D-Bus (sdbus-c++)
Database db/*.go SQLite wrapper SQLite3 (direct) or ORM
HTTP Client netreq/netReq.go HTTP client libcurl or cpp-httplib
Logging logger/logger.go Logger class —
Config / App State db/models.go Config struct + DB SQLite3

Build & Run

Dev shell via direnv (use flake in .envrc). Inside nix dev shell:

go build -o bin/rhemawave rhemawave.go   # Build
./bin/rhemawave                           # Run
nix build -L                              # Nix build (CI)

CGO required (CGO_ENABLED=1). The nix shell sets CGO_CFLAGS/CGO_LDFLAGS for SDL2. Do not go build outside the nix shell.

Tests

go test ./internal/mixer/

CI runs go test then nix build -L.

Branch Model

dev → main via PRs. CI in .forgejo/workflows/:

  • build-dev.yml: PRs to dev
  • build-prod.yml: PRs to main

Both run nix build -L on self-hosted neils-server-runner.

Common Pitfalls

  • dev_scripts/reset.sh referenced in nix devShell hook does not exist.
  • share/rhemawave/audio/*.wav is gitignored — never commit audio assets.
  • vendor/ is committed — run go mod vendor after go mod tidy.
  • CI uses a self-hosted Forgejo runner, not GitHub-hosted runners.
  • packaging/ contains a kiosk-specific updater, not part of the main build.