The BASIC language
aukimi's classic BASIC dialect: types, structures, the two shorthands, and the rules that catch everyone. 2 237 commands behind it.
In one line#
Aukimi ships a classic BASIC dialect: a full language, not a toy macro
system, with typed numerics, records, methods, references, structured errors,
coroutines and WebGPU compute. It lives in the Engine's TXT viewport mode and
in the script editor's BASIC view.
JavaScript remains the canonical scripting API. BASIC is an optional frontend: both compile to the same private dispatcher, so a BASIC program and a JavaScript one reach exactly the same engine. Nothing is second class, and nothing is a separate runtime.
The rule that catches everyone#
A command call always carries parentheses.
mode = ClipSpaceMode()
SpritePosition(player, 320, 180)
This is not style. It is what lets the compiler tell a command from a variable, and it is why a misspelling is now caught at compile time:
| You write | What used to happen | What happens now |
|---|---|---|
SpritePositon(1, 10, 20) | compiled cleanly, the sprite did not move, and the failure surfaced later as ... is not defined | compile error, with the nearest catalogued spelling suggested |
Blorp(1, 2) | forwarded verbatim into the generated JavaScript | compile error |
ceci nest pas du basic | emitted as a statement | compile error |
A writing mistake used to present itself as an engine bug. Measured against the 84-demo corpus, the guard produced zero false positives and found two real silent failures,
SetMusicSystemVolumeandGetLightSensorExists, which the catalogue only knows asMusicSystemVolumeandLightSensorExists.
Two leaks are left open on purpose: console.log("x") and window.x = 1 still
pass. A guard for them was written, measured and withdrawn: BASIC runs
inside a context that supplies names the compiler cannot enumerate, so refusing
an unknown root rejected valid programs. A false positive there is worse than
the leak.
Print draws. Console logs.#
The single most common surprise:
Print(...) | draws on the screen, and a program calls it every frame |
Console(...) | writes to the editor's console, and does nothing else |
Debug(...) | the same command under PureBasic's name, for the muscle memory |
ConsoleWarning, ConsoleError, ClearConsole | the rest of the log |
Print in a frame loop would drown any real error in the console, which is why
the two are separate commands rather than one with a flag.
Types#
Numeric types are real, and they behave identically on every target, the editor, the HTML export and the Bevy/native build:
- Signed and unsigned 8, 16, 32 and 64-bit integers, with deterministic
coercion, integer
DIV, and wrapping overflow. - float32 and float64.
- The classic sigils still work:
name$is a string,speed#a float.
OPTION EXPLICIT is opt-in, and classic implicit variables remain the
default so imported programs still run. Turning it on is the prerequisite for
the two features that cannot be safe without it, numeric typing and references
because a misspelled assignment silently creating a variable defeats both.
Structures#
Records#
TYPE declares a record. They nest arbitrarily, and they have value
semantics: assigning one deep-copies it, including nested arrays and
collections.
Methods#
METHOD binds a function to an instance, with SELF inside it, New to
construct and Delete to destroy. Deep copies stay method-safe.
References#
REF and BYREF are the same thing under two spellings, and both give a live
read/write parameter, for a scalar, a record, a single field, an array, a list
cursor or a map entry. References forward through calls, and coerce to
fixed-width types at the boundary.
Handles#
@target takes a first-class reference. HANDLE and POINTER are the typed
forms, with checked read, write, validity and release, and a handle to a
deleted record is invalidated rather than left dangling.
Control and safety#
Structured errors: TRY / CATCH / FINALLY, nested, with THROW,
protected RETHROW, and both structured and string catches.
Conditional compilation: #IF / #ELSEIF / #ELSE / #ENDIF, nested, with
feature flags and explicit profiles for the editor, HTML, Bevy, native/wasm and
debug/release. It is resolved before includes, on every target.
Rich functions: default values, OPTIONAL, a final PARAMARRAY (or ...),
safe arity overloads, typed return coercion, and cloned record and array
returns.
Multi-file programs#
#INCLUDE, #INSERT and IncludeFile make a program several files. The editor
creates and imports project modules, completes include paths, keeps the
dependency graph, and every runtime target is wired for it.
Coroutines, and the thing deliberately left out#
Cooperative tasks are label-based, with typed handles, a result and error state, cancellation at yields, and awaits on one task or a group. They share the game scheduler on every target.
Threads with shared memory are deliberately excluded. Not "not yet" excluded. A cooperative scheduler you can reason about beats a data race you cannot reproduce.
WebGPU compute#
Typed f32 / i32 / u32 storage buffers, raw WGSL pipelines, frame-ordered
dispatch and memblock readback, real GPU compute from BASIC.
⚠️ Engine only. The HTML target raises an explicit capability error, and the Bevy target reports the incompatibility before the build rather than producing a binary that fails on the first dispatch.
Two shorthands for the same problem#
597 of the commands are named Set/Get<Object><Property>: Sprite 66,
Tween 58, Text 32, Edit 29. The object's name is inside the function name and
its id is the first argument of every line. Both shorthands below remove that
repetition, and neither is a new API: the compiler rebuilds the real command
name and looks it up in the catalogue.
WITH <family>, inside a block#
WITH SPRITE s
\position 100, 200 ` SpritePosition(s, 100, 200)
\size 64, 64 ` SpriteSize(s, 64, 64)
x# = \x ` SpriteX(s)
ENDWITH
At the head of a statement \prop writes; anywhere else it reads.
Position decides, so there is no second notation to learn. WITH hero, one
token, is still the record block; WITH SPRITE s selects a catalogue family.
s.position(...), everywhere else#
s = CreateSprite(img) ` the family is DEDUCED from the creation command
s.position(100, 200) ` SpritePosition(s, 100, 200)
s.visible = 1 ` SpriteVisible(s, 1)
x# = s.x ` SpriteX(s)
The family comes from Create<F> / Load<F> / Clone<F>, or from an explicit
s AS Sprite when the id arrives from elsewhere. Only variables whose family is
known are rewritten: records keep their fields, lists keep their methods,
SELF.field is untouched.
⚠️ A typed parameter is not recognised. FUNCTION move(s AS Sprite) does not
give s a family, the pass reads statement-level declarations and assignments
only. Stated here because it is a real limit, not a bug you should hunt.
⚠️ The catalogue drops the verb on properties, and keeps it on lifecycle.
SpritePosition writes, SpriteX reads, and neither carries a Set or a Get:
4 commands in the whole catalogue begin with Set, and none begins with
Get. The verb survives where something comes into being or ends:
CreateSprite, LoadImage, DeleteSprite.
That is worth knowing because a misspelling is a compile error, and Set… is
the misspelling people arrive with. The catalogue is the authority, and
the reference is generated from it.
String interpolation, opt in#
Print($"score: {points}") ` "score: " + Str(points)
{{ and }} write literal braces.
⚠️ The $ prefix is mandatory, and it is not decoration. Interpolating every
literal broke hand-written JSON, which the world API takes routinely:
CreateTerrain("island", "{\"preset\":\"island\",\"size\":256}")
There the braces are data, not holes. An unprefixed string is left untouched whatever it contains.
Naming is showing#
A classic program makes hundreds of objects. Only the ones it NAMES enter the
Scene Graph, SpriteName(id, "player") and its siblings are what put an
object in the tree where the rest of the Engine can see it. LightName and
ParticlesName do the same for a light and an emitter.
The tutorial Name what your program creates walks through it: the same scene, once with the tree empty and once with three entries in it.
The command catalogue#
2 237 commands, 2 550 public overloads, 43 categories. The largest:
| 3D | 286 | Studio | 106 | File | 61 |
| Core | 200 | Input | 95 | Memblock | 43 |
| Tweening | 149 | Input-Raw | 94 | 3D Particles | 41 |
| 3D Physics | 144 | Multiplayer | 91 | World | 40 |
| Sprite | 135 | Text | 68 | Particles | 38 |
| Platform | 111 | 2D Physics | 66 | Sound | 37 |
plus JSON (36), Image (35), Skeleton (34), XML (34), Music (29), Preference (27), Extras (21), HTTP (21), String (21), Math (19), Regular Expression (19), Video (18), Dictionary (17), Benchmarking (15), Compute (14), Date (13), Maths (12), Time (12), StringBuilder (9), Compression (7), Font (8), Error (4), Array (3), Cipher (2), Sort (2).
Studio and Compute are Aukimi's own additions to the classic dialect the console commands, object naming, and the WebGPU pipeline.
The complete command reference lists all of them, with every parameter and every overload, and its own search, generated from the same catalogue this page counts, so it cannot fall behind it. The editor's own API panel reads that catalogue too.
This page documents the language; that one documents the commands.
Limits and common snags#
- Parentheses are not optional on a command. The bare PureBasic form
(
Debug "x") is refused, because it reopens the variable/command ambiguity that the compile-time guard depends on. - A user function wins over a catalogued command of the same name, and a variable may share a command's name in a typed declaration, on the left of an assignment, or as the root of a path.
- Variables and user functions are case-insensitive, and their symbol spaces are genuinely separate from the catalogue's.
Printis not a log. See above; it is the mistake that fills a console.- Compute does not export. Write the fallback path before you rely on it.