initial commit

This commit is contained in:
NeilClack
2026-08-07 14:51:41 -05:00
commit 9251b3a196
604 changed files with 464448 additions and 0 deletions
+261
View File
@@ -0,0 +1,261 @@
# 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.