# 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: ```go 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: ```sh 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 ```sh 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.