Crash-tan
CrashPlayer 1.35.0 locked and loaded!
Crash BASIC 1.35.0 Release Notes
written by Crash-tan
THE BAKE-IT-ONCE RELEASE
There's a new block in town: BEGIN MEMORYBUFFER … END MEMORYBUFFER.
Draw a pile of primitives inside it — PSET, LINE, CIRCLE, POLYGON,CLS — and instead of each one taking the slow road to the screen, the whole
batch gets rasterized once into a single texture you can reuse forever.
If you've ever written a loop that PSETs a few thousand pixels and felt it
drag, this is for you. That loop used to be a few thousand individual trips to
the renderer. Now it's one.
And it's not just for fresh canvases: seed a buffer FROM an image you
already have, then read its pixels back with POINT and POINTRGB — so
you can recolor, outline, mask, and post-process textures procedurally, not just
draw new ones from scratch.
SCREEN 1, 320, 240
BEGIN MEMORYBUFFER "stars"
FOR i = 1 TO 500
PSET (RND * 320, RND * 240), 15
NEXT
END MEMORYBUFFER
' "stars" is now a texture. Slap it down as many times as you like.
PUTIMAGE (0, 0), "stars"Why it's fast
Every PSET / LINE / CIRCLE you draw normally is its own little command
that travels to the renderer and gets drawn right then. Great for a handful of
shapes. Death by a thousand cuts when you're generating a whole texture pixel
by pixel — especially in the browser, where each command crosses a worker
boundary.
MEMORYBUFFER flips it around. Inside the block, your primitives are recorded,
not drawn. At END, the entire batch ships as a single operation, gets
rasterized into one RGBA texture, and is uploaded to the GPU exactly once. One
trip instead of N. The more primitives you were drawing, the bigger the win.
The result is a normal texture — hand it to SET SPRITE, PUTIMAGE, particles,
anything that takes an image name.
How to use it
BEGIN MEMORYBUFFER "name" ' size = current SCREEN resolution
BEGIN MEMORYBUFFER "name", 32, 32 ' or pick an exact sizeThe name is the texture you're building. Leave off the size and the buffer
matches your current SCREEN (so a SCREEN has to be active); pass width, height for something specific. Buffers go up to 8192×8192.
The buffer starts fully transparent. Draws layer on top of each other in
the order you make them, exactly like drawing to the screen — so later shapes
cover earlier ones. CLS inside the block fills the whole buffer (CLS 0 for
black, plain CLS for transparent).
BEGIN MEMORYBUFFER "coin", 16, 16
CLS ' transparent canvas
CIRCLE (8, 8), 7, 14, 6 ' gold rim, filled
CIRCLE (8, 8), 4, 14 ' inner ring
END MEMORYBUFFER
ENABLE SPRITE 0
SET SPRITE 0, 100, 100, "coin"You can use real control flow in there, too — that's the whole point of
generating textures procedurally:
BEGIN MEMORYBUFFER "grid", 256, 256
FOR x = 0 TO 256 STEP 16
LINE (x, 0)-(x, 256), 8
LINE (0, x)-(256, x), 8
NEXT
END MEMORYBUFFERStart from an existing image: FROM
A blank canvas is great, but sometimes you want to modify a picture you
already have. Add a FROM clause and the buffer starts as a copy of an
existing texture — and sizes itself to match:
BEGIN MEMORYBUFFER "out" FROM "source"
' "out" begins as a pixel-perfect copy of "source",
' the same size. Your draws composite on top.
END MEMORYBUFFERThe source can be anything you've already loaded: a LOADIMG image, aTEXTTOIMAGE label, a NEWIMAGE, even another memory buffer. You can even read
and write the same name for an in-place edit:
BEGIN MEMORYBUFFER "label" FROM "label" ' edit it in place
' ...
END MEMORYBUFFERBecause a transparent PRESET writes through as transparent, you get
cut-outs and masking for free — erase pixels right out of the seeded image.
Read the pixels back: POINT and POINTRGB
Seeding is only half the story. Inside a FROM block you can now read
pixels, too:
POINT(x, y)→ the palette index of that pixel, or-1if it's transparent.POINTRGB(x, y)→ the raw colour, packed as0xAARRGGBB.
Both sample the frozen original — the source exactly as it was at BEGIN,
not the draws you've made inside the block. That's the important bit: a filter
that reads neighbouring pixels and writes results never reads its own
half-finished output, so blurs and edge-detects come out clean.
This is the perfect tool for procedurally altering rendered text. Outline aTEXTTOIMAGE label by finding transparent pixels next to solid ones:
IF ME.ISCLIENT THEN
TEXTTOIMAGE "PLAY", "label", 48
BEGIN MEMORYBUFFER "outlined" FROM "label"
FOR y = 1 TO 46
FOR x = 1 TO 200
IF POINT(x, y) = -1 THEN ' transparent in the original...
IF POINT(x-1, y) <> -1 OR POINT(x+1, y) <> -1 OR _
POINT(x, y-1) <> -1 OR POINT(x, y+1) <> -1 THEN
PSET (x, y), 0 ' ...next to a glyph → outline it
END IF
END IF
NEXT
NEXT
END MEMORYBUFFER
END IFPOINTRGB hands you the exact colour for photos and anything that isn't a
palette colour — pull channels apart with the bitwise helpers:
c = POINTRGB(x, y)
r = BITAND(SHR(c, 16), 255)
g = BITAND(SHR(c, 8), 255)
b = BITAND(c, 255)A few rules so there are no surprises:
POINT/POINTRGBare only valid inside aMEMORYBUFFERblock — using
them anywhere else is a loud error.- An opaque colour with no exact palette entry resolves to the nearest
palette index (soPOINTalways gives you a usable index); reach forPOINTRGBwhen you need the exact colour. - Out-of-range coordinates read as transparent, so edge sampling never faults.
FROMreads pixel data, so it's client-only in multiplayer — wrap it inIF ME.ISCLIENT THEN. (NoFROMclause? Then it runs everywhere as before.)
What's allowed inside (and what isn't)
A MEMORYBUFFER block is for primitive rasterization only. That keeps it
fast, predictable, and identical on every platform. So:
Allowed: PSET, PRESET, LINE, CIRCLE, POLYGON, CLS, plus all the
control flow you'd expect (IF, FOR, WHILE, DO), variable assignment, and
calls to your own SUBs/FUNCTIONs (a helper that only draws primitives
composes just fine).
Not allowed: sprites, PRINT, PUTIMAGE, resource loads (LOADIMG and
friends), SETIMAGE, BEGIN FRAME, SCREEN, audio, and nesting anotherBEGIN MEMORYBUFFER. Try one and you get a loud, located error telling you
exactly what wasn't allowed — no silent weirdness.
Textured polygons stay on the GPU
A POLYGON with a texture argument is intentionally off-limits inside a
buffer. Texture sampling is a GPU job and always will be — it has no place in a
CPU-rasterized buffer. Solid-color and filled polygons are welcome here; if you
want a textured polygon, draw it straight to a layer or the screen, where the
GPU does the work.
Multiplayer
MEMORYBUFFER is a computed resource, just like TEXTTOIMAGE. It runs on
every interpreter and registers its texture everywhere, but it is not
broadcast over the wire — no per-pixel bandwidth. Build it on each client the
same way you build other resources:
IF ME.ISCLIENT THEN
BEGIN MEMORYBUFFER "hud", 320, 48
LINE (0, 0)-(319, 0), 7
' ...
END MEMORYBUFFER
END IFBecause the rasterization is plain deterministic CPU math, every client builds
byte-identical pixels. No drift, no sync needed.
Also new: BEGIN ENUM
A small quality-of-life win that pairs nicely with everything above. Instead of
a tower of CONST lines for a set of related values, declare an enum and let
it number itself from 0:
BEGIN ENUM
TITLE ' 0
PLAYING ' 1
PAUSED ' 2
GAMEOVER ' 3
END ENUM
mode = TITLE
IF mode = PLAYING THEN UpdateWorld()Each member is a plain global constant — read-only, case-insensitive, exactly
like CONST — so you use them bare: mode = PLAYING, not State.PLAYING.
Need specific numbers? Pin a member with = and the ones after it keep counting
from there:
BEGIN ENUM
HTTP_OK = 200
HTTP_CREATED ' 201
HTTP_NOTFOUND = 404
HTTP_TEAPOT ' 405
END ENUMYou can give the block a name for readability (BEGIN ENUM Direction) — it's
decorative; the members are still bare globals. One member per line; trailing
comments are welcome. Define your enums up top, the same way you'd place yourCONSTs.
Compatibility notes
- Purely additive. New
MEMORYBUFFERblock, an optionalFROMclause, two
new functions (POINT,POINTRGB), and the newBEGIN ENUMblock — nothing
existing changed. YourPSET/LINE/CIRCLEcalls behave exactly as
before outside a buffer, aMEMORYBUFFERwith noFROMworks exactly as it
did at first ship, andCONSTis untouched. - Same picture everywhere. The buffer is CPU-rasterized with deterministic
integer math — identical results on Mac, Windows, Linux, iOS, Android, and the
browser. - Wire-compatible. The new command rides the existing protobuf transport;
no breaking protocol or multiplayer changes.
Draw it once. Stamp it forever.
— Crash-tan