API Reference

FRED-80 carts run on standard Lua 5.4 — all Lua standard libraries (math, string, table, coroutine) are available. The functions below are FRED-80 built-ins.

A6000 tip: Prefer the built-in sin/cos/atan2, clamp/mid/flr over the math library — they use lookup tables and are significantly faster on the 68080.

System
time()
Returns elapsed seconds since the cart started.
time() → number
-- bob a sprite up and down
local x = 320 + sin(time() * 0.5) * 100
stat(n)
Returns engine diagnostic values.
stat(n) → number
nReturns
0Current FPS
print(stat(0) .. "fps", 580, 469, 3)
Math
Angles use the 0..1 full-circle convention — 0 = 0°, 0.25 = 90°, 0.5 = 180°.
sin(t)
Lookup-table sine. Fast on A6000 — integer table, no floating-point cost.
sin(t) → number
ParamDescription
tAngle in 0..1 full-circle range
local y = 240 + sin(time() * 0.3) * 80
cos(t)
Lookup-table cosine. Fast on A6000 — integer table, no floating-point cost.
cos(t) → number
ParamDescription
tAngle in 0..1 full-circle range
local x = 320 + cos(time() * 0.3) * 80
atan2(y, x)
Returns the angle of vector (x, y) in 0..1 range. Use to aim things toward a point.
atan2(y, x) → number
-- aim from center toward mouse
local angle = atan2(my - 240, mx - 320)
local tx = 320 + cos(angle) * 40
local ty = 240 + sin(angle) * 40
line(320, 240, tx, ty, 7)
rnd([n])
Returns a random float in [0, n). Defaults to 1.0.
rnd([n]) → number
local x = rnd(640)   -- random x position
local slot = flr(rnd(8))  -- random int 0..7
flr(x)
Floor — rounds down to the nearest integer.
flr(x) → integer
local tile_x = flr(x / 16)
ceil(x)
Ceiling — rounds up to the nearest integer.
ceil(x) → integer
local rows = ceil(count / 8)
abs(x)
Returns the absolute value of x.
abs(x) → number
if abs(dx) < 4 then snap() end
min(a, b) / max(a, b)
Returns the smaller or larger of two values.
min(a, b) → number
max(a, b) → number
x = max(0, x)        -- clamp to left edge
x = min(x, 624)     -- clamp to right edge
mid(a, b, c)
Returns the median of three values. Handy for clamping — mid(lo, val, hi).
mid(a, b, c) → number
x = mid(0, x, 624)   -- keep x within screen
clamp(v, lo, hi)
Clamps v between lo and hi.
clamp(v, lo, hi) → number
health = clamp(health + 10, 0, 100)
Input
btn(name)
Returns true while the named button is held down.
btn(name) → boolean
nameDescription
"left" "right" "up" "down"D-pad directions
"z" "x" "c"Action buttons
"enter" "escape"Menu buttons
if btn("right") then x = x + 2 end
btnp(name)
Returns true only on the first frame the button is pressed. Use for single-trigger actions like jumping or shooting.
btnp(name) → boolean
if btnp("z") then jump() end
joy_axis(joy, axis)
Returns the raw axis value from a joystick.
joy_axis(joy, axis) → integer (-32768..32767)
ParamDescription
joyJoystick index 0–3
axisAxis index (0=X, 1=Y typically)
local ax = joy_axis(0, 0) / 32768   -- normalise to -1..1
x = x + ax * 3
joy_btn(joy, btn)
Returns true if a joystick button is pressed.
joy_btn(joy, btn) → boolean
if joy_btn(0, 0) then fire() end
joy_hat(joy, hat)
Returns the raw hat switch bitmask.
joy_hat(joy, hat) → integer
BitDirection
1Up
2Right
4Down
8Left
local h = joy_hat(0, 0)
if h & 1 ~= 0 then move_up() end
Drawing
cls([c])
Clears the screen to a palette colour.
cls([c])
ParamDescription
cPalette index 0–255. Default 0 (transparent/black)
cls()     -- clear to black
cls(3)    -- clear to colour 3
camera([x, y])
Sets the global scroll offset. All draw calls shift by (-x, -y). Call with no arguments to reset to (0, 0).
camera([x, y])
camera(cam_x, cam_y)    -- scroll world
tilemap_draw(tm, 0, 0)
camera()                -- reset for HUD
print("100 pts", 4, 4, 7)
zoom([n])
Sets pixel scale 1–8. All coordinates and sizes are multiplied by n. Default 1.
zoom([n])
zoom(2)
spr(0, 160, 120)   -- draws at 32x32 pixels
zoom(1)
pset(x, y, c)
Draws a single pixel. Use pset_batch_xy when drawing many pixels per frame.
pset(x, y, c)
ParamDescription
x, yScreen position in pixels
cPalette index 0–255
pset(320, 240, 7)   -- white pixel at center
pget(x, y)
Returns the palette index of the pixel at (x, y). Returns 0 if out of bounds.
pget(x, y) → integer
local c = pget(mx, my)   -- sample colour under mouse
pal(i, r, g, b)
Remaps palette entry i to a 24-bit RGB colour at runtime. Takes effect immediately. Indices 0 (transparent) and 240 (collision mask) are protected.
pal(i, r, g, b)
ParamDescription
iPalette index 1–255 (0 and 240 are protected)
r, g, bRed, green, blue 0–255
-- flash player red on hit, then restore
pal(7, 255, 0, 0)
spr(player_slot, px, py)
pal(7, 240, 240, 240)
pset_batch(data)
Draws N pixels in a single Lua→C call. Much faster than calling pset() in a loop.
pset_batch(data)
ParamDescription
dataFlat table: {x1,y1,c1, x2,y2,c2, ...}
pset_batch({10,20,7, 11,20,7, 12,20,3})
pset_batch_xy(c, data)
Draws N pixels all in the same colour. Faster than pset_batch when colour is uniform (e.g. starfields).
pset_batch_xy(c, data)
ParamDescription
cPalette index for all pixels
dataFlat table: {x1,y1, x2,y2, ...}
local stars = {}
for i = 1, 80 do
  stars[i*2-1] = rnd(640)
  stars[i*2]   = rnd(480)
end

function _draw()
  pset_batch_xy(15, stars)   -- 80 stars, one C call
end
line(x0, y0, x1, y1 [, c])
Draws a Bresenham line between two points.
line(x0, y0, x1, y1 [, c])
ParamDescription
x0, y0Start point
x1, y1End point
cPalette index. Default 7
line(0, 0, 639, 479, 7)
rect(x0, y0, x1, y1 [, c])
Draws a rectangle outline. Avoid on the hot path on A6000 — pre-bake static UI into a canvas instead.
rect(x0, y0, x1, y1 [, c])
rect(10, 10, 100, 50, 7)
rectfill(x0, y0, x1, y1 [, c])
Draws a filled rectangle. Avoid on the hot path on A6000 — pre-bake static UI into a canvas instead.
rectfill(x0, y0, x1, y1 [, c])
rectfill(0, 460, 639, 479, 1)   -- HUD bar
circ(cx, cy, r [, c])
Draws a circle outline.
circ(cx, cy, r [, c])
circ(320, 240, 50, 7)
circfill(cx, cy, r [, c])
Draws a filled circle.
circfill(cx, cy, r [, c])
circfill(320, 240, 20, 8)   -- filled red circle
print(str, x, y [, c])
Draws text using the built-in 8×8 pixel font. \n advances to the next line.
print(str, x, y [, c])
ParamDescription
strString to draw
x, yTop-left position
cPalette index. Default 7
print("SCORE: " .. score, 8, 8, 7)
print("GAME OVER\npress Z", 260, 200, 8)
fb_fade(amount [, floor])
Subtracts amount from every palette index in the framebuffer each call, creating a decay/trail effect without cls(). AMMX-accelerated on A6000. floor clamps the minimum palette index so pixels never decay into index 0 (transparent pink) — default 1. Pass floor=0 only if you have remapped palette entry 0 to a dark colour.
fb_fade(amount [, floor])
ParamDescription
amountPalette index to subtract per frame (1–255). Default 1.
floorMinimum index after decay. Default 1.
function _draw()
  fb_fade(3)       -- decay all pixels, skip cls()
  circfill(ox, oy, 6, 91)   -- bright dot leaves colour trail
end
Sprites
The sprite bank holds 128 slots (0–127), each 16×16 pixels. Painted in the SPRITE tab, saved as sprites.spr. Colour 0 is always transparent.
spr(n, x, y [, flip_h, flip_v])
Draws a sprite from the bank at (x, y). Colour 0 is transparent.
spr(n, x, y [, flip_h, flip_v])
ParamDescription
nSprite slot 0–127
x, yScreen position (top-left of sprite)
flip_hMirror horizontally. Default false
flip_vMirror vertically. Default false
spr(0, player.x, player.y)
spr(2, enemy.x, enemy.y, true, false)  -- flipped
spr_batch(n, pos)
Draws slot n at N positions in a single Lua→C call. AMMX-accelerated on A6000 at zoom=1. Use instead of a spr() loop.
spr_batch(n, pos)
ParamDescription
nSprite slot 0–127
posFlat table: {x1,y1, x2,y2, ...}
local bullet_pos = {}

function _draw()
  -- fill bullet_pos with live positions...
  spr_batch(BULLET_SLOT, bullet_pos)
end
spr_batch_composite(tiles, pos)
Draws a multi-tile composite sprite (up to 16 tiles) at N positions in one call. Use for large sprites made from multiple bank slots.
spr_batch_composite(tiles, pos)
ParamDescription
tilesFlat table: {slot,dx,dy, slot,dx,dy, ...} up to 16 tiles
posFlat table: {x1,y1, x2,y2, ...}
-- 2x2 tank from 4 bank slots (32x32 total)
local TANK = {0,0,0, 1,16,0, 2,0,16, 3,16,16}
spr_batch_composite(TANK, tank_positions)
spr_flag(n [, mode])
Sets a persistent blend mode for slot n. Applied every time spr(n,...) is called. Pass no mode to reset to normal.
spr_flag(n [, mode])
modeDescription
"stipple"Checkerboard transparency
"blend50"50% mix with background
"blend25"25% mix with background
spr_flag(GHOST_SLOT, "blend50")   -- always semi-transparent
spr_flag(GHOST_SLOT)              -- reset to normal
spr_alpha(n, x, y, mode)
One-off draw with an explicit blend mode, without changing the slot's persistent flag.
spr_alpha(n, x, y, mode)
spr_alpha(SHIELD_SLOT, sx, sy, "blend50")
spr_new(w, h, pixels)
Creates a custom Sprite object from a flat pixel table (palette indices, row by row). Colour 0 is transparent. Automatically compiled as opaque if no transparent pixels, enabling faster blitting.
spr_new(w, h, pixels) → Sprite
ParamDescription
w, hWidth and height in pixels
pixelsFlat table of palette indices, 1-based, row by row
local px = {}
for i = 1, 16*16 do px[i] = 0 end
px[8*16+8+1] = 7   -- white dot at (8,8)
local dot = spr_new(16, 16, px)
spr_blit(spr, x, y [, flip_h, flip_v])
Draws a custom Sprite object created with spr_new().
spr_blit(spr, x, y [, flip_h, flip_v])
spr_blit(dot, 320, 240)
spr_get(n, px, py)
Returns the palette index of pixel (px, py) in sprite slot n.
spr_get(n, px, py) → integer
local c = spr_get(0, 8, 8)
hget(n, px, py)
Returns 1 if the hitbox layer is set at (px, py) of slot n, else 0.
hget(n, px, py) → 0 or 1
if hget(PLAYER, 8, 8) == 1 then -- center is collidable
end
spr_is_opaque(n)
Returns true if slot n has no transparent pixels. Use to branch between fast opaque blitting and transparency handling.
spr_is_opaque(n) → boolean
if spr_is_opaque(slot) then
  spr_batch(slot, positions)
end
spr_debug(n, x, y)
Draws a debug overlay showing transparent pixels (pink), hitbox pixels (green), and pixels that are both (Gunnar's Green). Use during development to verify hitbox coverage.
spr_debug(n, x, y)
spr_debug(PLAYER, player.x, player.y)
spr_hit(n1, x1, y1, n2, x2, y2)
Pixel-perfect collision test between two sprites. Returns true if their hitbox layers overlap. Paint hitboxes in the SPRITE tab using the M toggle.
spr_hit(n1, x1, y1, n2, x2, y2) → boolean
if spr_hit(PLAYER, px, py, ENEMY, ex, ey) then
  take_damage()
end
Canvas
Canvases are offscreen pixel buffers. Pre-bake anything static into a canvas in _init() and draw it with a single canvas_draw() per frame.
canvas_new(w, h)
Creates an offscreen canvas w×h pixels. All pixels start at 0 (transparent). Maximum 640×480.
canvas_new(w, h) → Canvas
local bg = canvas_new(640, 480)
canvas_set([c])
Redirects all drawing into canvas c. Call with no argument to restore drawing to the main screen.
canvas_set([c])
canvas_set(bg)
rectfill(0, 0, 639, 479, 2)
canvas_set()   -- back to screen
canvas_draw(c, x, y [, scale, flip_h, flip_v, alpha])
Composites a canvas onto the current draw target. Fastest at scale=1, no flip, alpha=100 (opaque) — uses a pre-clipped row-copy path.
canvas_draw(c, x, y [, scale, flip_h, flip_v, alpha])
ParamDescription
cCanvas object
x, yTop-left position on screen
scaleInteger ≥ 1. Default 1
flip_h, flip_vMirror flags. Default false
alphaOpacity 0–100. Default 100. Values below 100 are slow on A6000
canvas_draw(bg, 0, 0)           -- fast full-screen blit
canvas_draw(portrait, 10, 10, 3)  -- 3x scaled portrait
image_load(path)
Loads a .img file from disk and returns it as a Canvas. Convert PNGs first with tools/img_import.py.
image_load(path) → Canvas
local bg = image_load(CART_DIR .. "background.img")
Tilemaps
8 map slots (0–7), each 64×32 tiles. Tiles are 16×16 pixels. Paint maps in the MAP tab; they save to map0.datmap7.dat.
mload(slot)
Loads map<slot>.dat from the cart folder, builds a pre-rendered Tilemap using the current sprite bank, and returns the Tilemap object.
mload(slot) → Tilemap
function _init()
  tm = mload(0)
  map_use(tm)
end
tilemap_new(tw, th, cols, rows, tiles, data)
Creates a Tilemap from scratch without a file.
tilemap_new(tw, th, cols, rows, tiles, data) → Tilemap
ParamDescription
tw, thTile width and height in pixels
cols, rowsMap dimensions in tiles
tilesArray of Sprite objects (1-based)
dataFlat cols×rows array of tile indices (0 = empty)
tilemap_draw(tm, x, y)
Draws tilemap tm with its top-left at (x, y). Fast scanline copy path at zoom=1.
tilemap_draw(tm, x, y)
tilemap_draw(tm, 0, 0)
tilemap_parallax(tm, x, y)
Composites tilemap tm treating colour 0 as transparent. Use for layered parallax scrolling.
tilemap_parallax(tm, x, y)
-- two-layer parallax scroll
camera(flr(bg_x), 0)
tilemap_parallax(bg_tm, 0, 0)
camera(flr(fg_x), 0)
tilemap_draw(fg_tm, 0, 0)
camera()
tilemap_set(tm, col, row, idx)
Sets the tile at 1-based (col, row) and updates the pre-rendered buffer immediately.
tilemap_set(tm, col, row, idx)
tilemap_set(tm, 3, 2, 5)   -- place tile 5 at column 3, row 2
tilemap_get(tm, col, row)
Returns the tile index at 1-based (col, row).
tilemap_get(tm, col, row) → integer
local t = tilemap_get(tm, 3, 2)
map_use(tm)
Registers tm as the active map for mget/mset.
map_use(tm)
map_use(tm)
mget(x, y)
Returns the tile index at 0-based tile (x, y) of the active map. Returns 0 if out of bounds.
mget(x, y) → integer
local tile = mget(flr(px/16), flr(py/16))
mset(x, y, idx)
Sets the tile at 0-based (x, y) of the active map. Updates immediately.
mset(x, y, idx)
mset(5, 3, 0)   -- clear tile at (5,3)
Sound
sfx(freq [, dur, wave, vol, channel, env])
Plays a synthesised sound effect.
sfx(freq [, dur, wave, vol, channel, env])
ParamDescription
freqHz number, or note string e.g. "A4"
durDuration in seconds. Default 0.5
wave0=Square, 1=Sine, 2=Triangle, 3=Sawtooth, 4=Noise
volVolume 0–100. Default 100
channel0–7. Default -1 (first free channel)
envOptional table with ADSR, LFO, filter keys (see below)
env keyRangeDescription
pw0.0–1.0Pulse width / duty cycle (square only)
amsAttack time
dmsDecay time
s0–100Sustain level
rmsRelease time
lfoHzLFO rate (0 = off)
lfd0.0–1.0LFO depth
lft1/2/3LFO target: 1=pulse width, 2=volume, 3=both
flt0/1/2Filter: 0=off, 1=low-pass, 2=high-pass
fcHzFilter cutoff
fq0.1–10Filter resonance (Q)
sfx(note("C4"), 0.2, 0, 80)                          -- short blip
sfx(110, 1.5, 0, 90, 0, {a=10, d=200, s=60, r=300}) -- ADSR pad
sfx(220, 1.5, 0, 90, 0, {flt=1, fc=600, fq=3.0})    -- low-pass
note(name)
Returns the frequency in Hz for a note name. Middle C is "C4". Supports sharps and flats.
note(name) → number
sfx(note("A4"), 0.3, 0, 80)   -- 440 Hz
sfx(note("C#4"), 0.3, 1, 80)
sfx(note("Gb5"), 0.3, 2, 80)
music(seq [, loop, channel])
Plays a note sequence. Each entry is {freq, dur, [wave], [vol], [env keys...]}. A frequency of 0 is a rest.
music(seq [, loop, channel])
local melody = {
  {note("C4"), 0.2, 0, 70},
  {note("E4"), 0.2, 0, 70},
  {note("G4"), 0.4, 0, 80},
  {0, 0.1},              -- rest
}
music(melody, true)      -- loop forever
music_stop([channel])
Stops music on the specified channel. Default stops all channels.
music_stop([channel])
music_stop()    -- stop all music
music_stop(2)   -- stop channel 2 only
audio_stop([channel])
Stops all sound (sfx and music) on the specified channel. Default stops all channels.
audio_stop([channel])
audio_stop()   -- silence everything
snd_load(slot, path)
Loads a pre-baked .pcm file into a sound slot. PCM files are baked from Tommy compositions using bake aud in the shell.
snd_load(slot, path)
ParamDescription
slotSound slot index
pathPath to .pcm file
snd_load(0, "music.pcm")
snd_play(slot, loop, channel, vol)
Plays a loaded PCM sound. Zero CPU cost on A6000 — uses SAGA DMA hardware playback.
snd_play(slot, loop, channel, vol)
ParamDescription
slotSound slot loaded with snd_load()
looptrue to loop continuously
channelAudio channel 0–3
volVolume 0–100
function _init()
  snd_load(0, "music.pcm")
  snd_play(0, true, 0, 80)   -- loop on channel 0
end
snd_stop([channel])
Stops PCM playback. Pass a channel number (0–3) to stop that channel only. Omit to stop all channels.
snd_stop([channel])
ParamDescription
channelChannel 0–3. Omit to stop all channels.
snd_stop(0)   -- stop channel 0 only
snd_stop()    -- stop all channels
Tweens
Up to 16 simultaneous tweens. Always call tween_free() when done to release the slot.
tween_new(dur, start, end [, easing])
Creates a tween and returns its slot ID.
tween_new(dur, start, end [, easing]) → id
ParamDescription
durDuration in seconds
start, endStart and end values
easing"linear", "inQuad", "outQuad", "inOutQuad", "inCubic", "outCubic", "inSine", "outSine", "inOutSine", "outBounce", "inBounce", "outElastic", "inElastic", "outBack", "inBack"
local slide = tween_new(0.4, 660, 480, "outBack")
tween_step(id)
Advances the tween by one frame. Returns the current value and a done flag.
tween_step(id) → value, done
local x, done = tween_step(slide)
canvas_draw(panel, flr(x), 100)
if done then tween_free(slide) end
tween_reset(id)
Rewinds the tween to its start value without freeing the slot.
tween_reset(id)
tween_reset(slide)
tween_free(id)
Releases the tween slot.
tween_free(id)
tween_free(slide)
Bezier Curves
Pure polynomial math — no trig, safe on the 68080. Use bez_render() for anything called every frame.
bezier(t, x0,y0, x1,y1, x2,y2 [, x3,y3])
Returns x, y along a quadratic (3 points) or cubic (4 points) bezier curve at position t.
bezier(t, ...) → x, y
ParamDescription
tPosition along curve 0..1
x0,y0 … x2,y2Control points for quadratic curve
x3,y3Optional 4th point for cubic curve
bx, by = bezier(t, 100,240, 200,50, 300,240)
bez_render(n, x0,y0, x1,y1, x2,y2 [, x3,y3])
Pre-bakes n evenly-spaced points along the curve into a flat integer table {x1,y1, x2,y2, ...}. Do this once in _init() to avoid per-frame float math on A6000.
bez_render(n, ...) → table
local PATH
function _init()
  PATH = bez_render(32, 20,240, 160,40, 300,240)
end

function _update()
  local i = flr(enemy.t * 31) * 2
  enemy.x = PATH[i + 1]
  enemy.y = PATH[i + 2]
end
Physics
Verlet-integration rigid bodies and spring constraints. Bodies accumulate forces each frame; call body_update() once per frame to integrate. Always free bodies and springs when done.
body_new(x, y [, r, mass])
Creates a point-mass body at position (x, y). Returns a handle.
body_new(x, y [, r, mass]) → handle
ParamDescription
x, yInitial position
rCollision radius. Default 8.
massMass. Default 1.0.
local ball = body_new(320, 100, 8, 1.0)
body_force(handle, fx, fy)
Adds a force vector to the body for this frame. Accumulates — call multiple times for multiple forces (gravity, wind, player input).
body_force(handle, fx, fy)
body_force(ball, 0, 0.5)   -- gravity
body_update(handle [, damp])
Integrates velocity, applies damping, resets accumulated forces. Call once per frame per body.
body_update(handle [, damp])
ParamDescription
dampVelocity multiplier per frame. Default 0.98.
body_force(ball, 0, 0.5)
body_update(ball, 0.99)
body_pos(handle)
Returns the body's current position.
body_pos(handle) → x, y
local bx, by = body_pos(ball)
circfill(bx, by, 8, 9)
body_vel(handle)
Returns the body's current velocity.
body_vel(handle) → vx, vy
local vx, vy = body_vel(ball)
body_circle_collide(handle, ox, oy, or [, restitution])
Tests and resolves collision between this body and a static circle at (ox, oy) with radius or. Call after body_update().
body_circle_collide(handle, ox, oy, or [, restitution])
ParamDescription
ox, oy, orStatic circle centre and radius
restitutionBounce factor 0–1. Default 0.5.
body_circle_collide(ball, 320, 400, 60, 0.7)
body_aabb_collide(handle, x0, y0, x1, y1 [, restitution])
Resolves collision between this body and a static axis-aligned rectangle. Use for walls, floors, platforms.
body_aabb_collide(handle, x0, y0, x1, y1 [, restitution])
body_aabb_collide(ball, 0, 0, 639, 479, 0.6)   -- screen boundary
body_free(handle)
Releases the body slot. Always call when a body is no longer needed.
body_free(handle)
body_free(ball)
spring_new(a, b, rest_len, stiffness)
Creates a spring constraint between two body handles. Returns a handle.
spring_new(a, b, rest_len, stiffness) → handle
ParamDescription
a, bBody handles to connect
rest_lenNatural length of the spring in pixels
stiffnessSpring constant. 0.1 = loose, 1.0 = rigid.
local s = spring_new(head, tail, 30, 0.4)
spring_update(handle [, iters])
Applies spring forces to both connected bodies. Call after body_update() for each connected body. More iterations = stiffer behaviour at the cost of CPU.
spring_update(handle [, iters])
ParamDescription
itersConstraint solver iterations. Default 4.
spring_update(s, 6)
spring_draw(handle [, col])
Draws a line between the two connected bodies. Useful for debugging.
spring_draw(handle [, col])
ParamDescription
colPalette index. Default 7.
spring_draw(s, 11)
spring_free(handle)
Releases the spring slot.
spring_free(handle)
spring_free(s)
Persistent Storage
64 number slots shared across all carts, backed by fred80.dat in PROGDIR (the FRED-80 install folder on Amiga, the working directory on macOS). Survives cart restarts.
dget(slot)
Reads a number from persistent storage slot 0–63. Returns 0.0 if never written.
dget(slot) → number
local hi = dget(0)   -- load high score
dset(slot, val)
Writes a number to persistent storage slot 0–63 and saves to disk immediately.
dset(slot, val)
if score > hi_score then
  hi_score = score
  dset(0, hi_score)
end