Crash-tan
CrashCore 1.21.4 Released!
Crash BASIC 1.20.0 → 1.21.4: What Shipped
written by Crash-tan
About a month, seven releases, and two pretty big redesigns. Here's the whole arc in one file, pitched at whoever's coming back after a bit or picking the project up for the first time.
Two big stories
Story 1: event-driven synchronization between pattern animations and BASIC code. Before this, your pattern's wind-up frame and your dialog line ran on separate clocks with no way to get them to hit the same beat. DELAY UNTIL and WAITFOR solved that — classic boss-monologue-plus-pose setups work now.
Story 2: CrashNet was redesigned from the ground up. The old preallocated sprite-range model was replaced with explicit opt-in broadcast (SET SPRITE N SYNC) and first-come-first-served sprite ownership. Nothing syncs unless you say so. The CRASHNET block is mostly gone — just MAX PLAYERS n and custom data now. This was pre-release, so breaking it was fine.
Everything else slots into supporting those two stories, plus one chunky macOS fix, plus a collision-fix-of-the-collision-fix.
1.20.0 — The "Make Monologuing Great Again" Update
Two new synchronization primitives, both backed by a broadcast fire-counter in the EventManager.
DELAY UNTIL "event" [TIMEOUT ms] — blocks the calling thread until the named event fires. Broadcast wake (every thread waiting on the same event resumes), no event buffering, honors thread kill/halt for clean shutdown. Optional timeout in milliseconds.
PATTERN ... WAITFOR "event" [TIMEOUT ticks] — new pattern step type. Parks one sprite's pattern on the current step until the named event fires. Other sprites' patterns keep ticking normally. Can't take WHEN (sequential by construction). Timeout in pattern ticks (60/sec by default).
Canonical use case: a boss character with a pattern that holds each pose until the dialog system fires EVENT "beat" at every line break.
macOS .crashcart double-click (shipped mid-1.21.x work)
Finder double-click on .crashcart now reliably opens the game in /Applications/CrashPlayer.app. Three pieces working together:
- Modern Info.plist: migrated from legacy
CFBundleTypeExtensionstoUTExportedTypeDeclarations+LSItemContentTypes. Sonoma/Sequoia's Launch Services routes by UTI, not extension — the old declaration caused LS to either route to a stale handler or show "cannot open files in this format." - Self-healing LS registration: every launch calls
LSSetDefaultRoleHandlerForContentTypeso stale entries from old DMG installs don't steal the file. Users who've been upgrading through many releases have ten-plus stale handlers registered — this cleans them on first launch of the new build. - Clean re-exec on open: after capturing the AppleEvent, CrashPlayer re-execs itself with the path as argv, so winit/wgpu start from a pristine NSApplication state. The previous "no window ever appears" half-initialization issue is gone.
1.21.0 — The "Opt-In Everything" Update
Big one. Three things combine to reset how CrashNet multiplayer works.
SET SPRITE N SYNC | NOSYNC — sprites are now local by default. To broadcast a sprite's state to every client, opt in. No more preallocated player sprite ranges. No more "host sprite" range.
RESERVE SPRITE var — atomically reserve the next free sprite slot and write the reserved ID (or -1) into var. Unlike NEXTFREESPRITE, the slot is marked in use the instant it's returned, so concurrent server threads can't race onto the same ID.
LOCAL modifier on CALL TIMER/THREAD/RENDER/EVENT — mark a callback as client-side only. The CrashNet server skips LOCAL callbacks; remote clients still run them. Useful for cosmetic effects, UI animations, or anything the host doesn't need to authoritatively replicate.
CRASHNET block simplified. The old PLAYER SPRITES start TO end COUNT n and HOST SPRITES start TO end fields are gone. What's left:
CRASHNET
MAX PLAYERS 8
PARTICLES
TICK RATE 20
PLAYER DATA ... END PLAYER DATA
HOST DATA ... END HOST DATA
END CRASHNETAlso in 1.21.0:
- Transparent color erases on all shapes.
CIRCLE,LINE(includingB/BF), andPOLYGONnow behave likePSETdid at alpha=0 or palette index -1 — rasterize through the direct-write path so transparent genuinely erases, not "silently does nothing under alpha blending." CIRCLEnegative-radius guard.CIRCLE (cx, cy), -1, 15(typo —-1meant to be a color) used to trigger a WASM capacity-overflow panic. Humanized error now: "radius must be non-negative (got -1). Hint: -1 belongs in color/fill, not radius."- Embedded rigid-body escape fix — an earlier diagonal-corner fix introduced a regression where a parent-with-child fully inside a wall got snapped to the opposite side of the wall when trying to escape. Now the resolver allows motion when it reduces (or doesn't deepen) the penetration area, while still blocking corner clip-throughs for non-embedded sprites.
build-crashnet-server.sh— new dist script for the headless server. Replaces the stalebuild-crashserver.pyTauri wrapper. Same--platforminterface asbuild-crashplayer-dist.sh, ~40% the code. Linux output includes systemd unit,.deb/.rpm, and a runnableDockerfile.- Smart remote terminal client. CrashPlayer's
--remotemode runs the BASIC program locally with CrashCart providing local resources, while the server is authoritative for shared state. Server input events flow in over WebSocket, NetState syncs tick-by-tick, host changes propagate. - Server-side persistent VFS. SQLite-backed per-room storage (
crates/crashcore/src/server_vfs.rs), bundled rusqlite — no system SQLite dependency. - SHADER reference docs restored. 42-item built-in variable/function reference (built-in vars, color/vector/math/interpolation/texture functions, operators, swizzling, limitations) had been accidentally reduced to a one-line limitation note — back now.
1.21.1 — The "Case In Point" Fix
One-line bug. SET STATE "TitleScreen" reported "No SUB named 'TitleScreen' found" even when the SUB was defined in the same file — the registry lookup was case-sensitive while SUB names get stored uppercase at parse time. Fixed: uppercase the state name before lookup, matching how every other by-name SUB reference already worked. +2 regression tests.
1.21.2 — The "Yield Where It Actually Matters" Fix
Subtle correctness bite. TIMER/THREAD/EVENT callbacks run on background threads with a child ExecContext that shares the yield-starvation counter (Arc<AtomicU64>) with the main thread. mark_yield() at the top of each outer function fires at registration time, not on each callback tick — so every callback's statement work silently charged against the main thread's "time since last yield" budget.
Result in practice: games with busy callbacks hit the 100K yield-starvation error at a line that obviously hadn't done 100K of anything, with the error blaming the main thread for work done on a background thread. Fixed by calling child_ctx.mark_yield() at both ends of each callback closure.
Yield-starvation error now also includes the actual statement count — turns out that's the single most useful datum when debugging a runaway loop.
Also in 1.21.2: demos/bootime got 18 PUTIMAGE ... SCALE(w, h) call sites migrated to SCALETO(w, h). The demo predated the commit where SCALE on PUTIMAGE changed from target dimensions to a multiplier.
1.21.3 — The "Switch Back For Free" Fix
Cheap perf patch for games that switch fonts mid-render. The SetFont handler always iterated the ~150-char preload set (printable ASCII + common symbols) and called get_glyph on each. Per-glyph caches already deduplicated the rasterize + upload work, but the 150 iterations themselves — 150 String allocations for the lookup key, 150 HashMap lookups — ran on every SET FONT regardless.
Fix: GpuGlyphAtlasManager tracks which (font_name, rounded_size) combos have had the preload pass run (prebuilt: HashSet<(String, u32)>). SetFont checks is_prebuilt up front and skips the whole loop when it's a repeat. First-ever SET FONT for a given font+size still pays the build cost; every switch back to a warmed combo is effectively free.
Works identically for built-ins and LOADFONT-loaded fonts. Atlas entries persist for the process lifetime.
1.21.4 — The "Keep The Font, Just Resize" Patch
Tiny ergonomic addition to pair with 1.21.3.
FONT CURRENT, size / SETFONT CURRENT, size — when you want to change just the size without re-typing the font name, pass CURRENT as the name:
FONT "MyBody", 12
PRINT "body text"
FONT CURRENT, 24
PRINT "HEADING"
FONT CURRENT, 12
PRINT "back to body"CURRENT is a bareword keyword in the name slot; regular string expressions and variables still work unchanged. From a fresh context with no font set, falls back to the default ("IBM BIOS").
Combined with 1.21.3's atlas-preload gate: switching back to a font+size you've already used is a single HashSet lookup, zero glyph work.
Totals
- Tests: 1144 crashcore + 148 crashgfx, all green.
- Releases: 7 (1.20.0, 1.21.0–1.21.4).
- Biggest footprint: 1.21.0 CrashNet redesign (~620 lines changed across AST, builder, eval, net_state, net_manager, server, protocol, docs, tests).
- Smallest footprint: 1.21.1 (one-line fix + 2 regression tests).
- Largest behavioral change for game authors: the CrashNet sprite-sync rework. If you were using the old
PLAYER SPRITES ... COUNT nblock, that syntax is gone and sprites you want other clients to see need explicitSET SPRITE N SYNC. - Largest behavioral change hiding in a "fix": the transparent-color-erases rule for
CIRCLE/LINE/POLYGONin 1.21.0. Games that previously drew transparent shapes and expected "no visible effect" will now see those shapes erasing the layer.