Crash-tan
Well, 1.57.0 became more than a fix pack.
Crash BASIC 1.57.0 Release Notes
written by Crash-tan
A dial for how fast the world moves, and two better answers about where your
game is running.
THE WORLD CAN RUN FASTER NOW
Behaviors have always ticked sixty times a second. Sometimes that is not the
speed you want — a title screen that drifts, a boss phase that bites, a
bullet-time beat before the hit lands.
SET BEHAVIOR RATE 30 ' calm
SET BEHAVIOR RATE 120 ' briskIt takes effect on the very next tick, so you can change it between game states
without restarting anything.
What speeds up is what you wrote in ticks. Velocities, ticksToLive,
oscillation, friction — all of it scales, which is the whole point. What you
wrote in real time does not move: ANIMATE ... FRAMERATE 4 is still four
frames a second, and a 100ms Aseprite frame is still a tenth of a second. So
your sprites cross the screen twice as fast while their walk cycles play exactly
as you drew them.
Everything else keeps its own clock. DELAY, CALL TIMER, COOLDOWN and
shader TIME are wall-clock and do not care what the behavior engine is doing —
the behavior engine was never the master clock, and now that is a thing you can
rely on rather than a thing you assume.
The range is 1 to 240. Ask for 0 and you get a runtime error instead of a
mystery, because a rate of zero is not a slower game, it is a stopped one.
Multiplayer: every interpreter runs the same program, so set the rate
somewhere they all reach — not inside IF ME.ISHOST. Otherwise the host
simulates at one speed and everyone else at another, and nothing will look
wrong until the sprites disagree about where they are.
APPLE TV IS NOT A PHONE
ISMOBILE() answered yes on an Apple TV. It should always have answered no —
the documentation said so, and a television is not a handheld.
If your cart uses ISMOBILE() to decide whether to draw touch controls, it was
drawing them onto a screen nobody can touch. That is fixed: ISMOBILE() now
means what it says — a handheld with a touchscreen, so iOS and Android, and
nothing else.
Nothing else in the engine read that flag, so this changes exactly one thing:
the answer your program gets. The on-screen gamepad was never at risk — GAMEPAD ON has been a no-op on tvOS since 1.51.0, for the same reason.
AND A CONSOLE IS NOT A DESKTOP EITHER
Which leaves the question that had no way to be asked. "Not mobile" was never
the same as "desktop": a console has no touchscreen, but it has no keyboard and
mouse either. It is a pad or a remote, and a screen across the room.
IF ISCONSOLE() THEN
GAMEPAD PHYSICAL ON ' a remote or a pad, across the room
ELSEIF ISMOBILE() THEN
GAMEPAD ON ' an on-screen pad you can touch
ELSE
PRINT "WASD to move" ' keyboard and mouse
END IFISCONSOLE() is 1 on tvOS today, and will be 1 on the other living-room
platforms as they arrive — your cart will not need changing when they do. The
three classes never overlap, so exactly one branch runs:
| Platform | ISMOBILE() | ISCONSOLE() |
|---|---|---|
| iOS, Android | 1 | 0 |
| tvOS | 0 | 1 |
| Desktop, web | 0 | 0 |
Both answer 0 until the screen is up, so ask after your first SCREEN.
WHILE YOU ARE THERE
A console has no pointer, so MOUSEX() and MOUSEY() stay at 0 — nothing on
tvOS can move them. That makes them a reliable way to tell whether a real mouse
is in play before you follow one, and it is what lets you hand a virtual cursor
to the stick when there isn't:
IF MOUSEX() = 0 AND MOUSEY() = 0 THEN
cursorX = cursorX + JOYAXIS(0) * speed
cursorY = cursorY + JOYAXIS(1) * speed
END IFSHAPES ARE SPRITES NOW, PROPERLY
Bound polygons were sold as sprites that happen to be made of points. In
practice most of what you can do to a sprite either had no spelling for a shape
or quietly did nothing to it. That is fixed, in both directions.
SET POLYGON takes the sprite modifiers. All of them that mean anything for
a shape, in either the BIND form or on its own:
SET POLYGON 3, ALPHA(0.6) SCALE(1.4, 1.4) ORIGIN(0.5, 0.5) ROTATE(30)
SET POLYGON 3, BEHAVIOR "spin"
SET POLYGON 3, PATTERN "patrol"
SET POLYGON 3, PARENT(9) MAXVELOCITY(6, 6)
SET POLYGON 3, SHOW
SET POLYGON 3, HIDE
SET POLYGON 3, RESETLOCATION, SCALETO, FLIPX, FLIPY, MAXACCELERATION, SYNC and NOSYNC
come too. Combine them freely in one statement, the way you would on a sprite.
Why not SET SPRITE? Because a polygon's sprite half does not live at the
id you gave the polygon — SET SPRITE 3 is sprite 3, and it always was. It
never said so. The same goes for SHOW SPRITE and HIDE SPRITE, which is why
hiding a shape appeared to do nothing at all. Use SET POLYGON 3, HIDE.
A hollow polygon is hollow again. SET POLYGON n BIND shape COLOR 14 with noFILL was documented as an outline and drew nothing — the outline colour reached
the renderer and the renderer never used it. It is now stroked on every backend.COLOR and FILL the same colour still draws once, so a solid shape does not
get a double-blended rim.
Behaviors drive shapes. Attach one with SET POLYGON n, BEHAVIOR "name" and
it moves and turns the shape itself:
- movement now follows the sprite's whole travel, so
BMOVEworks and not onlyBVELOCITY— previously the most obvious way to move a thing was the one way
that did nothing; BROTATEturns the shape, instead of turning a sprite nobody draws;PARENTcarries the shape with its parent;MAXVELOCITYandMAXACCELERATIONcap it.
ORIGIN decides what LOCATION means, exactly as on a sprite: the point you
name is where the anchor goes. The default ORIGIN(0, 0) places a shape by the
top-left of its extent; ORIGIN(0.5, 0.5) places and spins it about its centre,
which is what you want for anything authored around its own middle.
' The flame keeps flickering while the ship flies it around.
SET POLYGON 1 BIND flame COLOR 4 FILL 12
SET MUTATION flame BEHAVIOR "flicker"
SET POLYGON 1, ORIGIN(0.5, 0.5) LOCATION(shipX, shipY) ROTATE(heading)That last line is the point of all of it. Writing a mutation's points ends the
behavior driving it — deliberately, so that taking control means taking control
— so a shape that has to keep its behavior can only be moved through its sprite
half. Now it can be.
The collider modifiers work in the modifier-only form now. SOLID, GHOST,TAG and BBOX only ever applied on the BIND form — the form you use once at
setup. The form a running game uses, SET POLYGON n, ..., dropped them silently,
so a shape could not be made solid, re-tagged or ghosted in response to anything.
It can now.
One placement change to know about. LOCATION on a bound polygon used to
move the shape by the coordinates you gave rather than to them, so a shape
authored anywhere other than (0, 0) landed short or long by its own position.
It now lands where you sent it. If you had compensated for the old behaviour by
subtracting the shape's own coordinates, remove that.
PARTICLES TAKE THE SAME MODIFIERS
EMITPARTICLE shares its modifier vocabulary with SET POLYGON, so everything
above reached it too. Now it acts on what it accepts.
The fourth argument names the motion, and it can be a BEHAVIOR or aPATTERN — for either kind of particle. It used to mean PATTERN for a sprite
particle and BEHAVIOR for a polygon one, so which of the two you could reach
depended on which kind you happened to be emitting, and naming the other did
nothing whatsoever. The name says which it is. BEHAVIOR "x" and PATTERN "x"
also work as modifiers, for attaching the other one — or both.
A name matching neither is now a fatal error. It used to spawn a particle
that simply never moved, which looks exactly like a physics bug and is really a
typo. If a cart names a pattern it never defined, it will now say so.
Pose and limits apply at birth, which is what gives a burst variety without
writing a behavior per variation:
EMITPARTICLE (x, y), rock, "debris", "tumble", 90 SCALE(0.4, 0.4) ROTATE(RND * 360) ALPHA(0.8)MAXVELOCITY and MAXACCELERATION cap what its motion builds up to. And the
ones a particle has no answer for — LOCATION, SHOW/HIDE, RESET,PARENT, SYNC/NOSYNC — say so instead of being quietly dropped.
A DESTROYED SHAPE NO LONGER HAUNTS THE ROOM
DESTROY POLYGON unbound the shape and told the renderer to drop it, but left
the collider behind — still solid, still answering to its tag, frozen at the
last place the shape stood. An invisible wall where something used to be, with
nothing on screen to explain it. The shape and its collider now go together.
SINGLE PLAYER WAS PLAYING MULTIPLAYER
ME.score = 7 halted a single-player game with "Local-client interpreters
can't mutate ME.
So did writing HOST.<field>. PLAYERS(1) was not an object, a CRASHNET
block's MAX PLAYERS and TICK RATE never reached the values you read back,
and a field declared in PLAYER DATA never got its starting value.
One cause: a solo launch attaches a NetState, and every check asked whether
one existed rather than whether the game was solo. All of it now behaves as the
reference has always described — ME.SLOT is 1, CRASHNET.CONNECTED is 0,PLAYERS.COUNT is 1, and ME.<field> is yours to write.
NAMES ARE EASIER TO WRITE
Behavior and pattern names ignore case, like everything else in the
language. A PATTERN step naming "Drift" when the block said "drift" used
to attach nothing and say nothing. HASPATTERN(n, "walk") is new, so you can
ask which pattern is running instead of comparing SPRITEPATTERN$ against a
literal.
A name can be built. Eleven places took a quoted string and nothing else:
TILEMAP "lvl" + STR$(level)
CREATE MAP "lvl" + STR$(level) AS LAYER 0
CALL TIMER Tick() EVERY 1 SECONDS AS "spawn" + STR$(i)
CALL PLAYER OnJoin() AS "room" + roomId$
DATA "wave" + STR$(n) ' and OPEN DATA, STOP PLAYER, STOP HOSTIF done THEN END is legal, along with THEN DELAY, THEN PAUSE,THEN YIELD and THEN DEBUGGER.
MISTAKES THAT USED TO PASS IN SILENCE
None of this changes what working code does. Every item below was already
wrong — a discarded argument, a value nobody computed, a save that failed
quietly — and the only difference now is that the language says so.
If an existing cart contains one of them it will stop rather than carry on
pretending: at load time for LOCATE and CREATE MAP, when the line runs for
the rest. That is where to look after updating.
VALon a non-number is an error, not0. A typo, an empty entry box and
a real zero all used to look identical. Guard blank input:IF entry$ <> "" THEN n = VAL(entry$).LOCATEtakes two arguments. A third was accepted and discarded.CREATE MAP FROM ... AS LAYER 1 TO 3is refused. One array is one map
layer, so a screen-layer range had nothing to spread. UseTILEMAPwith aTILESclause per layer for a real multi-layer map.KEYDOWNis unchanged. An unrecognised name still answers 0. Watch the
spelling: the key isEscape, soKEYDOWN("ESC")is never true.WAITVIDEOcan be released bySTOPVIDEO. The documented skippable
cutscene hung the game with the video already gone from the screen.FLOODFILLfills. It ran on a copy and threw it away; only the count
survived. Filled cells are marked-1.- A
SQLblock with two statements says so rather than running the first
and silently discarding the rest. CLOSEreports a failed save. A file that could not be written printed a
warning to stderr — which goes nowhere on web and mobile — and reported
success.#handles are checked in both directions, so one number can no longer be
a file and a database at once.- A
PATTERN'sDISABLEstep runs once, instead of re-disabling a sprite
that was already gone on every tick afterwards. SPRITEBBOXaccounts forORIGIN, so it returns the rectangle collisions
actually test.BORBIT ... DECAYwithoutSPEEDis an error instead of quietly becoming
a speed.CRASHNET TICK RATEoutside 1-60 is an error instead of a divide-by-zero
in the server.
DOCUMENTATION
The reference was audited entry by entry against the code. Every example in it
now parses, and the behavior and pattern commands are documented inside theBEHAVIOR and PATTERN entries rather than as separate keywords — they only
work inside those blocks, and presenting them as standalone statements is why
so many of their examples were written as code that could not run.
Entries corrected: ANIMATE, ASARRAY, BASE64TOIMG, BASE64TOMP3,BASE64TOWAV, BEHAVIOR, BIMPULSE, BORBIT, BROTATE, BSTOP, CALL,CLOSE, COS, CREATE MAP, DECLARE, DESERIALIZE$, DURATION,EMITPARTICLE, EXP, FLOODFILL, FOR MAPDATA, GAMECONTEXT$,GETSPRITEMETA, ISMOBILE, JOYBUTTON, LOADFONT, LOADJSON, LOADMP3,LOADMUSIC, LOADWAV, LOCATE, MAPMETA$, MAZE, NAMESPACE, OPEN,OPEN DATABASE, PATTERN, PLAYVIDEO, POINT, POLYGON, RESTORE,SET MUTATION BEHAVIOR, SET SPRITE, SPRITECOUNT, SQL, STOPVIDEO,TAN, TILEMAP, TIMER, VAL, WAITFOR.
Three entries were removed because they document nothing that exists: LET,
a bare STOP, and UNPUT ALL. Six were added for things that existed with no
entry at all: PI, TRUE, FALSE, SPRITEDIST, SPRITECOLLISIONID andTRANSITION$.