App

Implemented in src/lua/luaappapi.cpp.

Application-level primitives — concerns that belong to the running app/game itself, not to any one engine subsystem (camera, sound, sprites, ...).

app_set_name(name)

Set the application window's title. Scarab (the engine) has no opinion on what a game calls itself — the window opens with a generic default title ("Scarab", main.h's APP_NAME) before any Lua exists to override it, since the window has to exist before main.lua's first line runs. Call this once, early, with the game's real name:

-- main.lua
app_set_name( "Caravellius" )

screen_fade(alpha)

Set a whole-screen fade overlay, drawn on top of every other rendered element (tilemap, sprites, everything) — not a per-texture effect. alpha ranges from 0.0 (fully visible, no overlay) to 1.0 (fully black). Intended for stage-start/stage-end and game-over transitions: ramp it over several frames from on_update to fade in/out rather than jumping straight to an endpoint.

-- fade in from black over ~1 second at 60fps
local __FADE_STEP = 1 / 60
local fade_alpha = 1.0

function on_update( dt )
    if fade_alpha > 0 then
        fade_alpha = math.max( 0, fade_alpha - __FADE_STEP )
        screen_fade( fade_alpha )
    end
end

app_set_fullscreen(fullscreen, strategy) / app_get_fullscreen()

Enter/leave fullscreen, or query the current state. The game always renders at it's own fixed internal resolution regardless of the actual window/monitor size — the engine handles scaling and letterboxing (black bars, preserving aspect ratio) to fit whatever the real window size ends up being, whether that's from this toggle or the player manually resizing the window. Nothing else needs to account for it.

strategy (optional, sunlight v0.14.0+) picks which of two fullscreen strategies to enter with:

Switching strategy while already fullscreen in the other one is unsupported — call app_set_fullscreen(false) first, then re-enter fullscreen with the new strategy.

-- toggle fullscreen on a key press (edge-triggered, not held) - defaults to real fullscreen
if input_is_key_released( KEY_F5 ) then
    app_set_fullscreen( not app_get_fullscreen() )
end

-- explicitly ask for the older borderless-windowed behavior instead
app_set_fullscreen( true, FULLSCREEN_STRATEGY_BORDERLESS_WINDOWED )

app_set_target_fps(fps) / app_get_target_fps()

Set (or query) the renderer's own target frame rate — the cap the game loop paces itself against, not the current measured FPS (see app_get_draw_fps's own on-screen counter, right below, for that). Scarab's own default is main.h's FRAMES_PER_SECOND (60 today), applied once at boot; this primitive is a genuine live change, taking effect immediately, in either direction:

-- halve the target frame rate at runtime
app_set_target_fps( app_get_target_fps() / 2 )

app_set_draw_fps(draw_fps) / app_get_draw_fps()

Show or hide the on-screen FPS counter (raylib's own, drawn at the top-left corner of the window every frame), or query the current state. Scarab's own default (main.cpp) is false — a generic engine-level choice, not a game one. Caravellius overrides it to true, early in main.lua:

-- main.lua
app_set_draw_fps( true )

app_set_window_resizeable(resizeable) / app_get_window_resizeable()

Allow or disallow the user resizing the window by dragging its edges/corners, or query the current state. A genuine live toggle — safe to call both before the window exists and at any point after, in either direction (unlike raylib's own pre-InitWindow-only FLAG_WINDOW_RESIZABLE config flag). Scarab's own default (main.cpp) is true; Caravellius overrides it to false (the game renders at a fixed internal resolution with letterboxing, so a resizeable window offers no benefit):

-- main.lua
app_set_window_resizeable( false )

app_set_stretch_to_fill(stretchToFill) / app_get_stretch_to_fill()

Choose how the engine's fixed internal render resolution (DISPLAY_WxDISPLAY_H, 1260x920 today — what screen_get_width/screen_get_height report, unrelated to the real window size) gets blitted onto the real window/screen whenever the two sizes differ (fullscreen, or a live-resized window). Recomputed every frame, so it applies immediately regardless of when it's called.

app_set_stretch_to_fill( true )   -- fill the window completely, no letterboxing

draw_filled_rectangle(x, y, width, height, r, g, b, a)

Draw a filled, solid-color rectangle in screen space — same coordinate system as draw_text/screen_get_width (the fixed 1260x920 design resolution, independent of the live window size, fullscreen state, or app_set_stretch_to_fill), not a world-space/camera-relative one. Meant for simple HUD elements — a progress/health bar, a meter — that don't warrant a whole sprite/texture asset. A thin pass-through to ITileMap::DrawFilledRectangle, itself a thin pass-through to IEngine::DrawFilledRectangle — the same underlying call the whole-screen screen_fade overlay already uses internally, just exposed here as it's own arbitrary-position/size/color primitive rather than that fixed, full-window, single-alpha-value overlay.

-- a simple health bar: a dark background plate, then a colored fill
-- proportional to some fraction (0.0-1.0)
draw_filled_rectangle( x, y, width, height, 40, 40, 40, 255 )
draw_filled_rectangle( x, y, math.floor( width * fraction ), height, 220, 40, 40, 255 )

app_get_platform()

Query which host platform Scarab was compiled for — PLATFORM_WINDOWS, PLATFORM_MACOS, PLATFORM_LINUX, or PLATFORM_UNKNOWN (compiled for something other than those three). A pure compile-time fact (_WIN32/__APPLE__/__linux__ preprocessor macros), not a runtime probe — the result never changes for the lifetime of a given built executable, and there's no dependency on IEngine/raylib/GLFW or any other engine-owned state.

Useful for any platform-conditional Lua behavior a game needs — for example, picking which app_set_fullscreen strategy to default to per platform. FULLSCREEN_STRATEGY_REAL causes a measured performance regression on Linux. Windows isn't affected by that specific issue, but FULLSCREEN_STRATEGY_REAL is kept macOS-only project-wide rather than per-platform — Linux and Windows both use FULLSCREEN_STRATEGY_BORDERLESS_WINDOWED here by deliberate choice, not because Windows itself has a problem with FULLSCREEN_STRATEGY_REAL:

local platform = app_get_platform()

if platform == PLATFORM_MACOS then
    app_set_fullscreen( true, FULLSCREEN_STRATEGY_REAL )
else
    app_set_fullscreen( true, FULLSCREEN_STRATEGY_BORDERLESS_WINDOWED )
end

app_quit()

Requests the game close, the programmatic equivalent of the player pressing the configured exit key (ESC by default) or clicking the window's close button. Takes effect on the next frame, not immediately — safe to call from anywhere on_update/an engine callback reaches, including mid-frame; whatever's already queued to draw this frame still finishes drawing normally before the game actually closes.

There was no programmatic way to quit at all before this — every prior sample/tool worked around that by just telling the player to close the window manually.

function on_update(dt)
  if quit_button_pressed then
    app_quit()
  end
end

app_set_exit_key(key) / app_get_exit_key()

Sets which key, when pressed, triggers the default quit-the-application behavior (app_quit()'s own effect, but from a raw keypress rather than a direct call) — KEY_ESCAPE (see the "Keyboard keys" constants on input.md) by default. Pass KEY_NULL to disable this entirely, so pressing ESC no longer closes the game on its own — useful for a game that wants ESC to mean something else instead (a back/cancel action in its own menu system, for example), handled entirely in Lua by polling input_is_key_released(KEY_ESCAPE) each frame, rather than an un-interceptable quit racing ahead of it.

-- disable the default ESC-quits behavior; this game's own
-- Lua code decides what ESC does instead
app_set_exit_key( KEY_NULL )

app_get_time()

Returns the real, wall-clock time in seconds (a fractional number, sub-millisecond precision) elapsed since the game window was initialized. Monotonic — never jumps backward, and unaffected by the system clock changing — and entirely independent of the per-tick dt passed to on_update, which is a fixed 1000 / 60 (truncated to 16) rather than a measured frame time, so accumulating it drifts slow against real time, and drifts further on any machine that can't sustain the target frame rate.

Use this instead of accumulating dt for anything that has to track real elapsed time at sub-second precision — a typewriter-style text reveal, a cutscene timeline meant to line up with a song. (os.time() is the other real-time source, but only has whole-second granularity.) Anchor off a start timestamp and compare against it each frame, rather than counting up.

Under --headless (the null renderer) this is a virtual clock instead: each frame advances it by exactly 1 / target FPS, however fast the frames actually run, so a long timeline can be replayed in a fraction of the time.

-- reveal one character every 50ms of real time, regardless
-- of the actual frame rate
local CHAR_SECONDS = 0.05
local text         = "Hello, world!"
local start        = app_get_time()

function on_update(dt)
  local shown = math.floor( ( app_get_time() - start ) / CHAR_SECONDS )
  draw_text( text:sub( 1, shown ), 16, 16, 16, 255, 255, 255, 255 )
end

Fullscreen strategies

The strategy argument app_set_fullscreen optionally takes — see that primitive's own doc for what each one actually does and why FULLSCREEN_STRATEGY_REAL is kept macOS-only project-wide.

FULLSCREEN_STRATEGY_REAL, FULLSCREEN_STRATEGY_BORDERLESS_WINDOWED

Platforms

The values app_get_platform() returns.

PLATFORM_WINDOWS, PLATFORM_MACOS, PLATFORM_LINUX, PLATFORM_UNKNOWN