STASIS
Samples / WebAssembly

Play the public builds.

Activate one release Wasm package at a time. Each primary card shows the source shape, touch/pointer input, and links to the public sample plus platform packaging docs.

Primary sample / pointer + keyboard

Pointer Pong

Move the mouse to guide the left paddle without pressing; touch-drag and arrow keys are fallbacks. The right paddle is deterministic. A bundled digit font keeps both scores visible.

Wasm · 6.4 KiB raw · 2.6 KiB gzip

Not loaded.
Full Stasis source
import "/vendor/stasis/stdlib/graphics.stasis";

const SCREEN_W: f32 = 640.0;
const SCREEN_H: f32 = 360.0;
const PADDLE_W: f32 = 14.0;
const PADDLE_H: f32 = 72.0;
const BALL_SIZE: f32 = 12.0;

struct PongState {
    left_y: f32;
    right_y: f32;
    ball_x: f32;
    ball_y: f32;
    ball_dx: f32;
    ball_dy: f32;
    left_score: i32;
    right_score: i32;
}

struct ScoreDigits {
    zero: TextRun;
    one: TextRun;
    two: TextRun;
    three: TextRun;
    four: TextRun;
    five: TextRun;
    six: TextRun;
    seven: TextRun;
    eight: TextRun;
    nine: TextRun;
}

global score_font: i32;
global score_digits: ScoreDigits;
global state: PongState;

function score_for_display(score: i32): i32 {
    if (score < 0) {
        return 0;
    }
    return score % 100;
}

function score_digit(score: i32, place: i32): i32 {
    let display_score: i32 = score_for_display(score);
    if (place == 10) {
        return display_score / 10;
    }
    return display_score % 10;
}

function load_score_digits(): void {
    score_digits.zero.load_text_from(score_font, "0");
    score_digits.one.load_text_from(score_font, "1");
    score_digits.two.load_text_from(score_font, "2");
    score_digits.three.load_text_from(score_font, "3");
    score_digits.four.load_text_from(score_font, "4");
    score_digits.five.load_text_from(score_font, "5");
    score_digits.six.load_text_from(score_font, "6");
    score_digits.seven.load_text_from(score_font, "7");
    score_digits.eight.load_text_from(score_font, "8");
    score_digits.nine.load_text_from(score_font, "9");
}

function draw_score_digit(digit: i32, x: f32, y: f32, r: f32, g: f32, b: f32, a: f32): void {
    if (digit == 0) {
        score_digits.zero.draw(x, y, r, g, b, a);
    } else if (digit == 1) {
        score_digits.one.draw(x, y, r, g, b, a);
    } else if (digit == 2) {
        score_digits.two.draw(x, y, r, g, b, a);
    } else if (digit == 3) {
        score_digits.three.draw(x, y, r, g, b, a);
    } else if (digit == 4) {
        score_digits.four.draw(x, y, r, g, b, a);
    } else if (digit == 5) {
        score_digits.five.draw(x, y, r, g, b, a);
    } else if (digit == 6) {
        score_digits.six.draw(x, y, r, g, b, a);
    } else if (digit == 7) {
        score_digits.seven.draw(x, y, r, g, b, a);
    } else if (digit == 8) {
        score_digits.eight.draw(x, y, r, g, b, a);
    } else {
        score_digits.nine.draw(x, y, r, g, b, a);
    }
}

function clamp_paddle(y: f32): f32 {
    if (y < 0.0) {
        return 0.0;
    }
    if (y > SCREEN_H - PADDLE_H) {
        return SCREEN_H - PADDLE_H;
    }
    return y;
}

function paddle_from_pointer(y: f32): f32 {
    return clamp_paddle(y - PADDLE_H / 2.0);
}

function pointer_controls_paddle(pointer_count: i32): bool {
    return pointer_count > 0;
}

function reset_ball(direction: f32): void {
    state.ball_x = SCREEN_W / 2.0 - BALL_SIZE / 2.0;
    state.ball_y = SCREEN_H / 2.0 - BALL_SIZE / 2.0;
    state.ball_dx = direction * 4.0;
    state.ball_dy = 3.0;
}

function update_player(): void {
    // Pointer position is continuously available for mouse hover and touch.
    // A press is not required; keyboard movement remains the fallback when no
    // pointer has entered the game canvas yet.
    if (pointer_controls_paddle(input_pointer_count())) {
        state.left_y = paddle_from_pointer(input_pointer_y_logical(0));
        return;
    }

    if (is_key_down(82)) {
        state.left_y = state.left_y - 5.0;
    }
    if (is_key_down(81)) {
        state.left_y = state.left_y + 5.0;
    }
    state.left_y = clamp_paddle(state.left_y);
}

function update_cpu(): void {
    let target: f32 = state.ball_y - PADDLE_H / 2.0;
    if (target > state.right_y) {
        state.right_y = state.right_y + 3.0;
    }
    if (target < state.right_y) {
        state.right_y = state.right_y - 3.0;
    }
    state.right_y = clamp_paddle(state.right_y);
}

function update_ball(): void {
    state.ball_x = state.ball_x + state.ball_dx;
    state.ball_y = state.ball_y + state.ball_dy;

    if (state.ball_y <= 0.0 || state.ball_y >= SCREEN_H - BALL_SIZE) {
        state.ball_dy = 0.0 - state.ball_dy;
    }

    if (state.ball_x <= 32.0 && state.ball_x >= 20.0 && state.ball_y + BALL_SIZE >= state.left_y && state.ball_y <= state.left_y + PADDLE_H) {
        state.ball_dx = 4.0;
    }
    if (
        state.ball_x + BALL_SIZE >= 608.0
        && state.ball_x <= 620.0
        && state.ball_y + BALL_SIZE >= state.right_y
        && state.ball_y <= state.right_y + PADDLE_H
    ) {
        state.ball_dx = -4.0;
    }

    if (state.ball_x < 0.0) {
        state.right_score = state.right_score + 1;
        reset_ball(1.0);
    }
    if (state.ball_x > SCREEN_W) {
        state.left_score = state.left_score + 1;
        reset_ball(-1.0);
    }
}

function main(): i32 {
    init_window(640, 360, "Stasis Pointer Pong");
    score_font = load_font("assets/score.ttf", 32);
    load_score_digits();
    state.left_y = 144.0;
    state.right_y = 144.0;
    state.left_score = 0;
    state.right_score = 0;
    reset_ball(1.0);
    return 0;
}

function tick(): i32 {
    if (should_quit()) {
        return 1;
    }
    update_player();
    update_cpu();
    update_ball();
    return 0;
}

function render(): i32 {
    let left_tens: i32 = score_digit(state.left_score, 10);
    let left_ones: i32 = score_digit(state.left_score, 1);
    let right_tens: i32 = score_digit(state.right_score, 10);
    let right_ones: i32 = score_digit(state.right_score, 1);
    begin_frame();
    clear(0.02, 0.05, 0.09, 1.0);
    fill_rect(20.0, state.left_y, PADDLE_W, PADDLE_H, 0.33, 0.85, 0.98, 1.0);
    fill_rect(606.0, state.right_y, PADDLE_W, PADDLE_H, 0.98, 0.70, 0.32, 1.0);
    fill_rect(state.ball_x, state.ball_y, BALL_SIZE, BALL_SIZE, 0.96, 0.98, 1.0, 1.0);
    fill_rect(318.0, 0.0, 4.0, SCREEN_H, 0.16, 0.30, 0.40, 1.0);
    draw_score_digit(left_tens, 274.0, 16.0, 0.33, 0.85, 0.98, 1.0);
    draw_score_digit(left_ones, 294.0, 16.0, 0.33, 0.85, 0.98, 1.0);
    draw_score_digit(right_tens, 338.0, 16.0, 0.98, 0.70, 0.32, 1.0);
    draw_score_digit(right_ones, 358.0, 16.0, 0.98, 0.70, 0.32, 1.0);
    end_frame();
    return 0;
}

Primary sample / touch

Tap Target

Tap the target. A fixed sequence moves it after each hit, showing pointer edges and bounded state.

Wasm · 1.3 KiB gzip

Not loaded.
Full Stasis source
import "/vendor/stasis/stdlib/graphics.stasis";

const SCREEN_W: f32 = 360.0;
const SCREEN_H: f32 = 640.0;
const TARGET_SIZE: f32 = 72.0;

struct TargetState {
    x: f32;
    y: f32;
    taps: i32;
}

global state: TargetState;

function target_contains(x: f32, y: f32): bool {
    return x >= state.x && x <= state.x + TARGET_SIZE && y >= state.y && y <= state.y + TARGET_SIZE;
}

function move_target(): void {
    // A fixed sequence keeps every platform and test deterministic.
    if (state.taps % 3 == 0) {
        state.x = 48.0;
        state.y = 180.0;
    } else if (state.taps % 3 == 1) {
        state.x = 240.0;
        state.y = 320.0;
    } else {
        state.x = 112.0;
        state.y = 456.0;
    }
}

function main(): i32 {
    init_window(360, 640, "Stasis Tap Target");
    state.taps = 0;
    move_target();
    return 0;
}

function tick(): i32 {
    if (should_quit()) {
        return 1;
    }
    if (input_pointer_count() > 0 && input_pointer_went_down(0)) {
        if (target_contains(input_pointer_x_logical(0), input_pointer_y_logical(0))) {
            state.taps = state.taps + 1;
            move_target();
        }
    }
    return 0;
}

function render(): i32 {
    begin_frame();
    clear(0.04, 0.06, 0.10, 1.0);
    fill_rect(0.0, 0.0, SCREEN_W, SCREEN_H, 0.04, 0.06, 0.10, 1.0);
    fill_rect(state.x, state.y, TARGET_SIZE, TARGET_SIZE, 0.95, 0.25, 0.48, 1.0);
    fill_rect(state.x + 12.0, state.y + 12.0, TARGET_SIZE - 24.0, TARGET_SIZE - 24.0, 1.0, 0.82, 0.30, 1.0);
    end_frame();
    return 0;
}
Primary sample / touch

Color Switch

Tap anywhere to cycle three fixed colors. The whole game is a small explicit state transition.

Wasm · 1.2 KiB gzip

Not loaded.
Full Stasis source
import "/vendor/stasis/stdlib/graphics.stasis";

const SCREEN_W: f32 = 360.0;
const SCREEN_H: f32 = 640.0;

global color_index: i32;

function next_color(index: i32): i32 {
    if (index >= 2) {
        return 0;
    }
    return index + 1;
}

function main(): i32 {
    init_window(360, 640, "Stasis Color Switch");
    color_index = 0;
    return 0;
}

function tick(): i32 {
    if (should_quit()) {
        return 1;
    }
    if (input_pointer_count() > 0 && input_pointer_went_down(0)) {
        color_index = next_color(color_index);
    }
    return 0;
}

function render(): i32 {
    begin_frame();
    if (color_index == 0) {
        clear(0.12, 0.32, 0.88, 1.0);
    } else if (color_index == 1) {
        clear(0.10, 0.72, 0.48, 1.0);
    } else {
        clear(0.92, 0.28, 0.22, 1.0);
    }
    fill_rect(64.0, 176.0, SCREEN_W - 128.0, SCREEN_H - 352.0, 0.04, 0.05, 0.08, 0.86);
    fill_rect(116.0, 228.0, SCREEN_W - 232.0, SCREEN_H - 456.0, 0.98, 0.98, 0.92, 1.0);
    end_frame();
    return 0;
}
Primary sample / packaged assets

Neon Breakout

Clear 35 neon bricks with a mouse or touch-controlled paddle. Tap or press Space to launch or restart.

Wasm · 6.6 KiB raw · 2.8 KiB gzip · assets 254,233 B prepared PNG + 919 B SVG

A four-step Stasis ImageAsset loader requests one external image at a time, shows progress without assets, and publishes sprites before simulation starts. Failed loads stay on the error screen; no per-frame asset work runs after readiness.

Not loaded.
Full Stasis source
import "/vendor/stasis/stdlib/graphics.stasis";

const SCREEN_W: f32 = 640.0;
const SCREEN_H: f32 = 360.0;
const BRICK_COUNT: i32 = 35;
const BRICK_W: f32 = 78.0;
const BRICK_H: f32 = 22.0;
const PADDLE_W: f32 = 96.0;
const PADDLE_H: f32 = 14.0;
const BALL_SIZE: f32 = 12.0;
const PADDLE_Y: f32 = 326.0;
const ASSET_TOTAL: i32 = 4;

struct Brick {
    x: f32;
    y: f32;
    alive: bool;
}

struct Assets {
    background: Sprite;
    brick: Sprite;
    paddle: Sprite;
    ball: Sprite;
}

struct AssetLoader {
    current: ImageAsset;
    step: i32;
    assets_ready: bool;
    failed: bool;
}

global bricks: Brick[35];
global assets: Assets;
global loader: AssetLoader;
global paddle_x: f32;
global ball_x: f32;
global ball_y: f32;
global ball_vx: f32;
global ball_vy: f32;
global bricks_left: i32;
global ball_active: bool;
global round_done: bool;
global presentation_variant: i32;

function loading_completed_count(step: i32): i32 {
    if (step < 0) {
        return 0;
    }
    if (step > ASSET_TOTAL) {
        return ASSET_TOTAL;
    }
    return step;
}

function loading_percent(step: i32): i32 {
    return loading_completed_count(step) * 100 / ASSET_TOTAL;
}

function loading_is_blocking(): bool {
    return !loader.assets_ready && !loader.failed;
}

function clamp_paddle(value: f32): f32 {
    if (value < 0.0) {
        return 0.0;
    }
    if (value > SCREEN_W - PADDLE_W) {
        return SCREEN_W - PADDLE_W;
    }
    return value;
}

function set_paddle_x(value: f32): void {
    paddle_x = clamp_paddle(value);
}

function initialize_bricks(): void {
    foreach (let brick, i in bricks) {
        let column: i32 = i % 7;
        let row: i32 = i / 7;
        brick.x = 25.0 + i32_to_f32(column) * 82.0;
        brick.y = 36.0 + i32_to_f32(row) * 28.0;
        brick.alive = true;
    }
    bricks_left = BRICK_COUNT;
}

function reset_round(): void {
    initialize_bricks();
    paddle_x =(SCREEN_W - PADDLE_W) / 2.0;
    ball_x = SCREEN_W / 2.0 - BALL_SIZE / 2.0;
    ball_y = PADDLE_Y - BALL_SIZE - 2.0;
    ball_vx = 2.2;
    ball_vy = -2.6;
    ball_active = false;
    round_done = false;
}

function update_pointer(): void {
    if (input_pointer_count() > 0) {
        let pointer_x: f32 = input_pointer_x_logical(0);
        set_paddle_x(pointer_x - PADDLE_W / 2.0);
        if (input_pointer_went_down(0)) {
            if (round_done) {
                reset_round();
            }
            ball_active = true;
        }
    }
    if (is_key_down(80)) {
        set_paddle_x(paddle_x - 4.0);
    }
    if (is_key_down(79)) {
        set_paddle_x(paddle_x + 4.0);
    }
    if (is_key_down(44)) {
        if (round_done) {
            reset_round();
        }
        ball_active = true;
    }
}

function update_ball(): void {
    if (!ball_active) {
        ball_x = paddle_x + PADDLE_W / 2.0 - BALL_SIZE / 2.0;
        ball_y = PADDLE_Y - BALL_SIZE - 2.0;
        return;
    }

    ball_x += ball_vx;
    ball_y += ball_vy;
    if (ball_x < 0.0) {
        ball_x = 0.0;
        ball_vx = 0.0 - ball_vx;
    }
    if (ball_x + BALL_SIZE > SCREEN_W) {
        ball_x = SCREEN_W - BALL_SIZE;
        ball_vx = 0.0 - ball_vx;
    }
    if (ball_y < 0.0) {
        ball_y = 0.0;
        ball_vy = 0.0 - ball_vy;
    }

    let hit_brick: bool = false;
    foreach (let brick, i in bricks) {
        if (
            !hit_brick
            && brick.alive
            && ball_x + BALL_SIZE >= brick.x
            && ball_x <= brick.x + BRICK_W
            && ball_y + BALL_SIZE >= brick.y
            && ball_y <= brick.y + BRICK_H
        ) {
            brick.alive = false;
            bricks_left -= 1;
            ball_vy = 0.0 - ball_vy;
            hit_brick = true;
        }
    }
    if (bricks_left == 0) {
        ball_active = false;
        round_done = true;
        return;
    }

    if (ball_vy > 0.0 && ball_y + BALL_SIZE >= PADDLE_Y && ball_y <= PADDLE_Y + PADDLE_H && ball_x + BALL_SIZE >= paddle_x && ball_x <= paddle_x + PADDLE_W) {
        ball_y = PADDLE_Y - BALL_SIZE;
        ball_vy = 0.0 - ball_vy;
        ball_vx =(ball_x + BALL_SIZE / 2.0 -(paddle_x + PADDLE_W / 2.0)) * 0.06;
        if (ball_vx > 3.4) {
            ball_vx = 3.4;
        }
        if (ball_vx < -3.4) {
            ball_vx = -3.4;
        }
    }
    if (ball_y > SCREEN_H) {
        ball_active = false;
        round_done = true;
    }
}

function request_current_asset(): void {
    if (loader.step == 0) {
        loader.current.load_image("assets/arena_background.png", 640, 360);
    } else if (loader.step == 1) {
        loader.current.load_image("assets/brick.svg", 78, 22);
    } else if (loader.step == 2) {
        loader.current.load_image("assets/paddle.svg", 96, 14);
    } else if (loader.step == 3) {
        loader.current.load_image("assets/ball.svg", 12, 12);
    }
}

function publish_current_asset(): bool {
    if (loader.step == 0) {
        return loader.current.publish(assets.background);
    }
    if (loader.step == 1) {
        return loader.current.publish(assets.brick);
    }
    if (loader.step == 2) {
        return loader.current.publish(assets.paddle);
    }
    return loader.current.publish(assets.ball);
}

function poll_asset_loading(): void {
    if (loader.assets_ready || loader.failed) {
        return;
    }
    if (loader.step >= ASSET_TOTAL) {
        loader.assets_ready = assets.background.handle > 0 && assets.brick.handle > 0 && assets.paddle.handle > 0 && assets.ball.handle > 0;
        if (loader.assets_ready) {
            reset_round();
        } else {
            loader.failed = true;
        }
        return;
    }
    if (loader.current.state == AssetState.None) {
        request_current_asset();
        if (loader.current.state == AssetState.Failed) {
            loader.failed = true;
        }
        return;
    }
    if (loader.current.failed()) {
        loader.failed = true;
        return;
    }
    if (loader.current.ready()) {
        if (!publish_current_asset()) {
            loader.failed = true;
            return;
        }
        loader.step += 1;
    }
}

function main(): i32 {
    if (!init_window(640, 360, "Neon Breakout")) {
        return 1;
    }
    loader.step = 0;
    loader.assets_ready = false;
    loader.failed = false;
    presentation_variant = 0;
    return 0;
}

function tick(): i32 {
    if (should_quit()) {
        return 1;
    }
    poll_asset_loading();
    if (loading_is_blocking()) {
        return 0;
    }
    if (loader.failed) {
        return 0;
    }
    update_pointer();
    update_ball();
    return 0;
}

function render(): i32 {
    begin_frame();
    clear(0.02, 0.02, 0.08, 1.0);
    if (loader.failed) {
        fill_rect(96.0, 140.0, 448.0, 5.0, 0.45, 0.10, 0.16, 1.0);
        fill_rect(96.0, 140.0, 448.0, 5.0, 0.95, 0.30, 0.35, 1.0);
        fill_rect(220.0, 180.0, 200.0, 8.0, 0.95, 0.30, 0.35, 1.0);
        end_frame();
        return 0;
    }
    if (!loader.assets_ready) {
        let progress_width: f32 = 448.0 * i32_to_f32(loading_percent(loader.step)) / 100.0;
        fill_rect(96.0, 140.0, 448.0, 5.0, 0.10, 0.18, 0.30, 1.0);
        fill_rect(96.0, 140.0, progress_width, 5.0, 0.20, 0.85, 0.98, 1.0);
        fill_rect(220.0, 180.0, 200.0, 8.0, 0.10, 0.18, 0.30, 1.0);
        fill_rect(220.0, 180.0, 2.0 * i32_to_f32(loading_percent(loader.step)), 8.0, 0.20, 0.85, 0.98, 1.0);
        end_frame();
        return 0;
    }
    assets.background.draw(0.0, 0.0, 235, 0);
    foreach (let brick, i in bricks) {
        if (brick.alive) {
            assets.brick.draw(brick.x, brick.y, 255, 0);
        }
    }
    assets.paddle.draw(paddle_x, PADDLE_Y, 255, 0);
    assets.ball.draw(ball_x, ball_y, 255, 0);
    if (presentation_variant == 1) {
        fill_rect(0.0, 0.0, SCREEN_W, 2.0, 1.0, 0.30, 0.78, 1.0);
    }
    end_frame();
    return 0;
}

// Compatible swaps preserve the active round and only change a visible accent.
function on_code_swap(): void {
    if (presentation_variant == 0) {
        presentation_variant = 1;
    } else {
        presentation_variant = 0;
    }
}

Stress sample / fixed state + hot swap

Swarm Field

8,192 agents update and render every tick. Drag the field with mouse or touch; tap toggles attract/repel.

Wasm · 1.9 KiB gzip · 5.1 KiB raw

The fixed Agent[8192] collection lives in bounded SoA-backed Wasm memory. Ordered deterministic ticks use no per-frame allocation; the same source runs on Web, Windows, and Android. Compatible hot swaps call on_code_swap without resetting agent state.

Not loaded.
Full Stasis source
import "/vendor/stasis/stdlib/graphics.stasis";

const SCREEN_W: f32 = 640.0;
const SCREEN_H: f32 = 360.0;
const AGENT_COUNT: i32 = 8192;
const AGENT_SIZE: f32 = 1.8;
const MAX_SPEED: f32 = 2.8;

struct Agent {
    x: f32;
    y: f32;
    vx: f32;
    vy: f32;
    band: i32;
}

// Fixed-size state: no entity allocation or iteration-count changes at runtime.
global agents: Agent[8192];
global field_x: f32;
global field_y: f32;
global field_mode: i32;
global presentation_variant: i32;

function abs_f32(value: f32): f32 {
    if (value < 0.0) {
        return 0.0 - value;
    }
    return value;
}

function clamp_speed(value: f32): f32 {
    if (value < 0.0 - MAX_SPEED) {
        return 0.0 - MAX_SPEED;
    }
    if (value > MAX_SPEED) {
        return MAX_SPEED;
    }
    return value;
}

function wrap_x(value: f32): f32 {
    if (value < 0.0) {
        return value + SCREEN_W;
    }
    if (value >= SCREEN_W) {
        return value - SCREEN_W;
    }
    return value;
}

function wrap_y(value: f32): f32 {
    if (value < 0.0) {
        return value + SCREEN_H;
    }
    if (value >= SCREEN_H) {
        return value - SCREEN_H;
    }
    return value;
}

function initialize_agents(): void {
    // 128 columns x 64 rows gives a stable, dense starting lattice.
    foreach (let agent, i in agents) {
        let column: i32 = i % 128;
        let row: i32 = i / 128;
        agent.x = i32_to_f32(column) * 5.0 + 2.0;
        agent.y = i32_to_f32(row) * 5.0 + 2.0;
        agent.vx = i32_to_f32((i % 5) - 2) * 0.22;
        agent.vy = i32_to_f32(((i / 5) % 5) - 2) * 0.18;
        agent.band = i % 6;
    }
}

function toggle_field_mode(): void {
    if (field_mode == 0) {
        field_mode = 1;
    } else {
        field_mode = 0;
    }
}

function update_field_from_pointer(): void {
    if (input_pointer_count() <= 0) {
        return;
    }
    field_x = input_pointer_x_logical(0);
    field_y = input_pointer_y_logical(0);
    if (input_pointer_went_down(0)) {
        toggle_field_mode();
    }
}

function update_agent(index: i32): void {
    let column: i32 = index % 128;
    let row: i32 = index / 128;
    let home_x: f32 = i32_to_f32(column) * 5.0 + 2.0;
    let home_y: f32 = i32_to_f32(row) * 5.0 + 2.0;
    let dx: f32 = field_x - agents[index].x;
    let dy: f32 = field_y - agents[index].y;
    let home_dx: f32 = home_x - agents[index].x;
    let home_dy: f32 = home_y - agents[index].y;

    // A spring keeps the lattice distributed while the pointer adds a local orbit.
    let in_core: bool = abs_f32(dx) < 70.0 && abs_f32(dy) < 70.0;
    let radial_sign: f32 = 1.0;
    if (field_mode == 1 || in_core) {
        radial_sign = -1.0;
    }
    let ax: f32 = home_dx * 0.004 + dx * 0.00055 * radial_sign - dy * 0.00035;
    let ay: f32 = home_dy * 0.004 + dy * 0.00055 * radial_sign + dx * 0.00035;
    if (ax > 0.18) {
        ax = 0.18;
    }
    if (ax < -0.18) {
        ax = -0.18;
    }
    if (ay > 0.18) {
        ay = 0.18;
    }
    if (ay < -0.18) {
        ay = -0.18;
    }
    agents[index].vx = clamp_speed(agents[index].vx * 0.992 + ax);
    agents[index].vy = clamp_speed(agents[index].vy * 0.992 + ay);
    agents[index].x = wrap_x(agents[index].x + agents[index].vx);
    agents[index].y = wrap_y(agents[index].y + agents[index].vy);
}

function update_agents(): void {
    foreach (let agent, i in agents) {
        update_agent(i);
    }
}

function main(): i32 {
    init_window(640, 360, "Stasis Swarm Field");
    field_x = SCREEN_W / 2.0;
    field_y = SCREEN_H / 2.0;
    field_mode = 0;
    presentation_variant = 0;
    initialize_agents();
    return 0;
}

function tick(): i32 {
    if (should_quit()) {
        return 1;
    }
    update_field_from_pointer();
    update_agents();
    return 0;
}

function render(): i32 {
    begin_frame();
    clear(0.015, 0.025, 0.055, 1.0);
    fill_rect(0.0, 0.0, SCREEN_W, 1.0, 0.16, 0.28, 0.42, 1.0);
    fill_rect(0.0, SCREEN_H - 1.0, SCREEN_W, 1.0, 0.16, 0.28, 0.42, 1.0);

    // One rectangle per agent: 8,192 commands, below the 10,000 command cap.
    foreach (let agent, i in agents) {
        let red: f32 = 0.22;
        let green: f32 = 0.78;
        let blue: f32 = 0.98;
        if (agent.band == 1) {
            red = 0.98;
            green = 0.72;
            blue = 0.28;
        } else if (agent.band == 2) {
            red = 0.42;
            green = 0.96;
            blue = 0.54;
        } else if (agent.band == 3) {
            red = 0.92;
            green = 0.38;
            blue = 0.76;
        } else if (agent.band == 4) {
            red = 0.98;
            green = 0.42;
            blue = 0.30;
        } else if (agent.band == 5) {
            red = 0.64;
            green = 0.50;
            blue = 0.98;
        }
        if (presentation_variant == 1) {
            let swap: f32 = red;
            red = blue;
            blue = swap;
        }
        fill_rect(agent.x, agent.y, AGENT_SIZE, AGENT_SIZE, red, green, blue, 0.94);
    }

    let field_red: f32 = 0.30;
    let field_blue: f32 = 0.96;
    if (field_mode == 1) {
        field_red = 0.98;
        field_blue = 0.32;
    }
    fill_rect(field_x - 8.0, field_y - 1.0, 16.0, 2.0, field_red, 0.92, field_blue, 1.0);
    fill_rect(field_x - 1.0, field_y - 8.0, 2.0, 16.0, field_red, 0.92, field_blue, 1.0);
    end_frame();
    return 0;
}

// Compatible swaps keep agents moving and only change the color presentation.
function on_code_swap(): void {
    if (presentation_variant == 0) {
        presentation_variant = 1;
    } else {
        presentation_variant = 0;
    }
}

Gambit Guard

A featured external game. This site links to it without bundling its source or package.

Public sample boundary

These hosted builds include the small samples, the Neon Breakout asset sample, and the Swarm Field stress sample. Proprietary projects are not copied, embedded, or linked to source repositories.