# Crash BASIC 1.45.0 Release Notes

*~written by Crash-tan~*

## THE "LET ASEPRITE DO THE WORK" RELEASE

You already draw your animations in Aseprite. You set the timing on the
timeline. You drag a slice box around the hitbox. Then you'd come over to
Crash BASIC and... re-type all of it by hand — a `FRAMERATE` number you
eyeballed, a `BBOX` you measured with your finger on the screen.

Not anymore. 1.45.0 teaches `LOADIMG` to read the parts of an `.ase` file
it used to throw away, and gives `ANIMATE` two new words so the animation
just *uses* them.

- **`FRAMERATE OFSPRITE`** — play back at the per-frame timing you set on
  the Aseprite timeline, instead of one flat fps for the whole clip.
- **`AUTOBBOX`** — let the collision box follow the animation, frame by
  frame, from the slice you drew in Aseprite.
- **`LOADIMG … SLICES boxes()`** — hand you the per-frame slice rectangles
  as an array, to do whatever you want with.
- **`LOADIMG … TAG("name")`** — load just one animation tag's frames out of a
  multi-animation `.ase`.

---

## `FRAMERATE OFSPRITE` — your timeline timing, honored

Aseprite lets you give every frame its own duration. A long pose, three
quick in-between frames, a long pose again. Until now Crash BASIC ignored
that completely — `ANIMATE` only knew one speed for the whole loop.

```basic
LOADIMG "hero.ase", "HERO" AS SPRITESHEET(32, 32)

BEHAVIOR "walk"
    ANIMATE "HERO", 0 TO 7 FRAMERATE OFSPRITE
END BEHAVIOR
```

`FRAMERATE OFSPRITE` says "use the durations baked into the file." Frame 0
holds for however long you set it in Aseprite, frame 1 for its own time,
and so on — variable per frame, exactly as authored. Re-time the animation
in Aseprite, re-export, and your game's playback updates with it. No magic
number to keep in sync.

It needs a real Aseprite load. Point `FRAMERATE OFSPRITE` at a plain PNG
sprite sheet (which carries no per-frame durations) and you halt with a
clear message telling you why — not a silently frozen sprite.

---

## `AUTOBBOX` — the hitbox follows the animation

Draw a slice in Aseprite, animate it across your frames (it's keyframed,
so a punch frame gets a wider box, a crouch frame a shorter one), and
`AUTOBBOX` applies it to the sprite automatically as the animation plays:

```basic
LOADIMG "hero.ase", "HERO" AS SPRITESHEET(32, 32)

BEHAVIOR "attack"
    ANIMATE "HERO", 0 TO 5 AUTOBBOX FRAMERATE OFSPRITE
END BEHAVIOR
```

Every time the animation advances, the bounding box updates to match the
slice on that frame. It feeds straight into the existing collision system
— boundary pushback, sprite-to-sprite, `SPRITECOLLISION()`,
`SPRITETOUCHING$()` — so your hitbox is whatever you drew, when you drew
it.

`AUTOBBOX` is independent of `FRAMERATE OFSPRITE`; combine it with either
that or a fixed fps:

```basic
ANIMATE "HERO", 0 TO 5 AUTOBBOX FRAMERATE 12   ' fixed speed, animated hitbox
```

Bare `AUTOBBOX` uses the file's **first** slice. If your file has several
slices and the first isn't the one you want, name it:

```basic
ANIMATE "HERO", 0 TO 5 AUTOBBOX "body" FRAMERATE OFSPRITE
```

A frame that no slice key covers simply leaves the box where it was. Ask
for `AUTOBBOX` on an image with no slices (or a name that doesn't exist)
and you halt loud, with the available slice names listed.

---

## More than one slice? Query the rest by name

A sprite has a single collision box, so `AUTOBBOX` drives one slice. But an
`.ase` can hold several named slices at once — a body, a sword, a feet
point — and you can read any of them on demand for your own (manual) hit
checks:

```basic
' World-space box of a named slice at the sprite's CURRENT frame:
sword = SPRITEBBOX(player, "sword")     ' (x1,y1)-(x2,y2) in world pixels
body  = SPRITEBBOX(player)              ' no name = the active collision box

' Relative-0-1 box of a slice on a specific frame, no sprite needed:
b = IMAGEBBOX("HERO", "feet", 3)
SET SPRITE 0 BBOX b
```

`SPRITEBBOX(id, name$)` gives you world coordinates (sprite position +
scale applied), ready to test against other things. `IMAGEBBOX(image$,
name$, frame)` gives the relative box straight from the file. Name a slice
that isn't there and you halt loud, with the available names listed.

---

## `LOADIMG … SLICES boxes()` — the rectangles, in your hands

Want the slice boxes for yourself instead of (or in addition to) the
automatic hookup? Add a `SLICES` clause and `LOADIMG` fills an array:

```basic
LOADIMG "hero.ase", "HERO" AS SPRITESHEET(32, 32) SLICES boxes()

' boxes(frame) is a (x1,y1)-(x2,y2) Range in relative 0–1 coordinates,
' one entry per frame — keyframes already expanded.
SET SPRITE 0, 100, 100, "HERO"
SET SPRITE 0 BBOX boxes(2)        ' use frame 2's slice as the hitbox
```

Each element is a **Range** in relative 0–1 coordinates, ready to drop
straight into the new range form of `BBOX` (below). The array is always
dense and frame-indexable — frames with no slice fall back to the full
frame `(0,0)-(1,1)` — so `boxes(anyFrame)` is always valid.

`SLICES` is Aseprite-only. Use it on a PNG, or on an `.ase` with no
slices, and you get a loud error rather than a quietly empty array.

---

## `TAG("name")` — load just one animation

An `.ase` usually holds every animation in one file — idle, run, jump, all on
the same timeline, separated by Aseprite **tags**. `LOADIMG … TAG("name")`
pulls out just one tag's frames:

```basic
LOADIMG "hero.ase", "HERO_RUN" TAG("run")

BEHAVIOR "running"
    ANIMATE "HERO_RUN", 0 TO 5 FRAMERATE OFSPRITE
END BEHAVIOR
```

Only the `run` tag's frames are loaded, re-indexed from 0 — so `ANIMATE … 0 TO
5` just plays the run, no offset math. The per-frame durations and slice boxes
come along narrowed to that tag, so `FRAMERATE OFSPRITE` and `AUTOBBOX` keep
working on it. An `.ase` already implies a sprite sheet, so you don't even need
`AS SPRITESHEET` — though you can still add `SLICES` to grab the tag's hitboxes.

Want several animations? Load the file once per tag, under different names:

```basic
LOADIMG "hero.ase", "HERO_IDLE" TAG("idle")
LOADIMG "hero.ase", "HERO_RUN"  TAG("run")
LOADIMG "hero.ase", "HERO_JUMP" TAG("jump")
```

Tag names match case-insensitively. Ask for a tag that isn't in the file (or
put `TAG` on a plain PNG) and you halt loud, with the available tag names
listed.

---

## `BBOX` now takes a Range too

To make those slice rectangles pluggable, `SET SPRITE … BBOX` learned a
second form. Alongside the classic four numbers, you can now hand it a
single Range value:

```basic
SET SPRITE 0 BBOX(0.2, 0.2, 0.8, 0.8)   ' the form you already know
SET SPRITE 0 BBOX boxes(curFrame)        ' new: a relative-0–1 Range value
```

Both mean the same thing — a relative-0–1 collision box. The range form
just lets you pass one you already have (from `SLICES`, or built yourself)
without unpacking it into four arguments.

While we were in there, **range values can now hold fractional
coordinates.** A range literal like `(0.1, 0.2)-(0.8, 0.9)` keeps its
decimals instead of truncating to whole numbers. Whole-number ranges are
completely unchanged — they print and step over the integer grid exactly
as before.

---

## Solid boundaries are solid again

Two collision bugs that let a sprite slip through a `SOLID` boundary are
fixed:

- **Pass-through after partial penetration.** If a sprite's box ended up
  partway inside a wall (a small hitbox, a teleport, a frame-change), the
  engine used to let it keep moving — it would flip to the far side and
  sail out the other end. Now a solid boundary blocks any motion that
  pushes deeper; a box that's genuinely stuck inside can still back out the
  way it came.
- **Tunneling at speed.** A single step bigger than a wall is thick could
  jump clean over it in one tick. A swept check now catches the
  overshoot and stops the sprite at the wall face.

Sliding along walls and ceilings is unchanged — only the
pass-straight-through cases got closed.

---

## Multiplayer

All of this works the same in single-player and in CrashNet. The Aseprite
per-frame durations and slice boxes are read on every interpreter when the
file loads, so `FRAMERATE OFSPRITE` timing and `AUTOBBOX` hitboxes resolve
identically on the server and on each client. Nothing new crosses the
wire; just call `LOADIMG` once at the top of your program as always.

---

## Also in 1.45.0 — arrays, sharper edges, a few new words

The headline is Aseprite, but a batch of language polish rode along.

### `REDIM` keeps your data now

`REDIM` used to quietly throw away everything in the array and hand you a
fresh zero-filled one. Now it **preserves** what fits: grow an array and
your existing elements stay put (the new slots default-fill); shrink it and
the elements that remain are kept. There's no `PRESERVE` keyword to
remember — preserving is just what `REDIM` does. (Want a clean slate? That's
what `DIM` and `ERASE` are for.)

`PUSH` / `POP` / `QUEUE` / `DEQUEUE` are now explicitly 1-D list operations.
Point one at a multi-dimensional array and you get a clear error instead of
an array whose shape quietly stops matching its contents.

### `POLYGON` takes an array of points

`POLYGON` (and its `UV` clause) already accepted a `MUTATION`. Now it also
takes a plain array of points — coordinate values `(x, y)`, objects with
`.x` / `.y`, or an `N×2` numeric array — so you can build the geometry
however is convenient and hand it straight in.

### A few new (and newly-documented) functions

- **`COUNT(text$, part$)`** — how many times `part$` appears in `text$`.
- **`REVERSE$(text$)`** — the string, backwards.
- **`MID$(text$, start)`** — the length is now optional; leave it off to get
  everything from `start` to the end.
- **`ASIN` / `ACOS` / `LOG10`** were always there; they're in the reference
  now.

### Errors where there used to be silence

A few operations that used to fail quietly now stop with a clear message:

- `SQR` of a negative number, and `MOD` by zero, halt — they used to produce
  an un-representable `NaN`.
- `COLLISION` with an unknown shape name or too few arguments, and `NEWIMAGE`
  with a zero/negative size, halt instead of silently doing nothing.

### Small behavior fixes

- **`MUSICPLAYING`** actually reports playback now (on desktop it was stuck
  on "not playing"), and reads `0` while paused.
- **`SPRITEDIRECTION`** returns an intuitive `0–360` (0 = right, 90 = down,
  180 = left, 270 = up).
- **`MAPTILEX` / `MAPTILEY` / `MAPWORLDTILEX` / `MAPWORLDTILEY`** return `-1`
  when there's no map under the point, so tile `0` is no longer mistaken for
  "nothing there."
- **`BBLINK 0`** turns blinking off (and leaves the sprite showing) instead
  of flickering every frame.
- **`FOR MAPDATA … LAYER n`** now actually scans layer `n`.
- **`DATE$`** reads as `MM/DD/YYYY`.

---

## Compatibility notes

- **Everything you already wrote still works.** `FRAMERATE fps`, the
  four-argument `BBOX(x1,y1,x2,y2)`, plain PNG sprite sheets — all
  unchanged. The new words are strictly additive.
- **`AUTOBBOX` and `OFSPRITE` are new keywords inside `ANIMATE`.** They
  only mean something in that position; variable and image names that
  merely contain those letters are unaffected.
- **`TAG(…)` is a new optional clause on `LOADIMG`.** Strictly additive —
  existing `LOADIMG` calls are unchanged, and `TAG` only applies to `.ase`
  files (it's a loud error on a plain PNG).
- **Range values are fractional now.** If you somehow depended on a range
  literal truncating decimals to integers, that no longer happens —
  whole-number ranges behave exactly as before, only fractional ones
  changed.
- **`REDIM` preserves now.** If any code leaned on `REDIM` wiping an array
  back to zeros, use `DIM` (or `ERASE`) for that instead.
- **`SPRITEDIRECTION` range changed** from −180…180 to 0…360 — "up" reads
  `270` now (it was `−90`). Adjust any angle comparisons that assumed the
  old range.
- **`MAPTILEX` / `MAPTILEY` return `-1` off-map** (they used to return `0`).
  If you tested for `0` to mean "no tile," test for `-1`.
- **`DATE$` uses slashes** (`MM/DD/YYYY`). Code that parses it expecting
  dashes needs a tweak.
- **A few silent results are now loud halts** — `SQR` of a negative, `MOD`
  by zero, a malformed `COLLISION`, a non-positive `NEWIMAGE`. Anything that
  relied on the old quiet `NaN` / `0` / no-op will now stop with an error.

---

*Draw it once. Let the file remember the timing and the hitbox. Go make a
game.*

*— Crash-tan*
