121 lines
3.3 KiB
Zig
121 lines
3.3 KiB
Zig
// SPDX-License-Identifier: LCL-1.0
|
|
// Copyright (c) 2026 Markus Maiwald
|
|
// Stewardship: Self Sovereign Society Foundation
|
|
//
|
|
// This file is part of the Nexus Commonwealth.
|
|
// See legal/LICENSE_COMMONWEALTH.md for license terms.
|
|
|
|
//! Rumpk HAL: Matrix Protocol (Rainmaker Logic)
|
|
//!
|
|
//! Implements a "Matrix" falling rain effect for the boot splash.
|
|
//! Recedes into the background after a fixed lifetime.
|
|
//!
|
|
//! SAFETY: Uses a simple Xorshift PRNG. Animation state is global.
|
|
|
|
const std = @import("std");
|
|
const fb = @import("framebuffer.zig");
|
|
|
|
// Config
|
|
const FONT_W = 8;
|
|
const FONT_H = 8;
|
|
const COLS = fb.WIDTH / FONT_W;
|
|
const LIFETIME_FRAMES = 150; // ~5 seconds at 30FPS (reduced for impact)
|
|
|
|
// State
|
|
// SAFETY(Matrix): Drops array is initialized by `init()` before use.
|
|
// Initialized to `undefined` to save a 512B zero-sweep in BSS.
|
|
var drops: [COLS]i32 = undefined;
|
|
var frame_count: usize = 0;
|
|
var is_active: bool = false;
|
|
|
|
// Basic PRNG (Xorshift)
|
|
var rng_state: u32 = 0xDEADBEEF;
|
|
fn random() u32 {
|
|
var x = rng_state;
|
|
x ^= x << 13;
|
|
x ^= x >> 17;
|
|
x ^= x << 5;
|
|
rng_state = x;
|
|
return x;
|
|
}
|
|
|
|
pub fn init() void {
|
|
is_active = true;
|
|
frame_count = 0;
|
|
// Initialize drops at random heights off-screen
|
|
var i: usize = 0;
|
|
while (i < COLS) : (i += 1) {
|
|
drops[i] = -@as(i32, @intCast(random() % 100));
|
|
}
|
|
}
|
|
|
|
// Returns: TRUE if still animating, FALSE if finished (The Void)
|
|
pub fn update() bool {
|
|
if (!is_active) return false;
|
|
|
|
// 1. Fade existing trails
|
|
fb.fade_screen(15); // Fade speed
|
|
|
|
// 2. Are we dying?
|
|
frame_count += 1;
|
|
const dying = (frame_count > LIFETIME_FRAMES);
|
|
|
|
// 3. Update Drops
|
|
var active_drops: usize = 0;
|
|
var i: usize = 0;
|
|
while (i < COLS) : (i += 1) {
|
|
// Draw Head (Bright White/Green)
|
|
const x = i * FONT_W;
|
|
const y = drops[i];
|
|
|
|
if (y >= 0 and y < @as(i32, @intCast(fb.HEIGHT))) {
|
|
// Draw a simple "pixel block" glyph
|
|
// White tip
|
|
fb.put_pixel(@intCast(x + 3), @intCast(y + 3), 0xFFFFFFFF);
|
|
fb.put_pixel(@intCast(x + 4), @intCast(y + 3), 0xFFFFFFFF);
|
|
fb.put_pixel(@intCast(x + 3), @intCast(y + 4), 0xFFFFFFFF);
|
|
fb.put_pixel(@intCast(x + 4), @intCast(y + 4), 0xFFFFFFFF);
|
|
|
|
// Green body follow
|
|
if (y > 8) {
|
|
fb.fill_rect(@intCast(x + 3), @intCast(y - 4), 2, 4, 0xFF00FF00);
|
|
}
|
|
}
|
|
|
|
// Move down
|
|
drops[i] += FONT_H;
|
|
|
|
// Reset if off screen
|
|
if (drops[i] > @as(i32, @intCast(fb.HEIGHT))) {
|
|
if (!dying) {
|
|
// Respawn at top
|
|
drops[i] = -@as(i32, @intCast(random() % 50));
|
|
active_drops += 1;
|
|
} else {
|
|
// Do NOT respawn. Let it fall into the void.
|
|
}
|
|
} else {
|
|
active_drops += 1; // It's still on screen
|
|
}
|
|
}
|
|
|
|
// 4. Check for Total Extinction
|
|
if (dying and active_drops == 0) {
|
|
// Ensure pure black at the end
|
|
fb.clear(0xFF000000);
|
|
is_active = false;
|
|
return false; // STOP THE GPU FLUSHING IF IDLE
|
|
}
|
|
|
|
return true; // Keep animating
|
|
}
|
|
|
|
// --- EXPORTS FOR NIM ---
|
|
export fn matrix_init() void {
|
|
init();
|
|
}
|
|
|
|
export fn matrix_update() bool {
|
|
return update();
|
|
}
|