JARU 0.9.7
- Date: November 2026
- Components: IDE (0.9.7.0) · Windows VM · ESP32 / ESP32-S3 VM · ESP32-P4 VM · ESP32-S31 VM (new) · Compiler
- Previous version: 0.9.6
This release brings four big blocks:
- The scene editor, new in the IDE: it lays out 2D rooms with layers, objects, behaviours, rules and state machines, and translates them into plain JARU code.
- More speed and less memory on the boards: superinstructions in the bytecode, jump tables in the interpreter, partial screen updates, a single image per sprite and many KB less internal RAM per game.
- A refreshed IDE: one visual style everywhere (JARU Slate), a new sprite editor with actors and poses, and some eighty fixes from a usability review.
- A new board, the ESP32-S31 (ESP32-S31-Korvo-1), and support for I2C gamepads.
On top of that, Display.fps and Display.dt give games a fixed pace on every board, map collision gets a long round of fixes, and the compiler no longer silently accepts some mistakes that used to leave the program doing nothing.
Highlights
- Scene editor. A new resource, the scene (
.scn): tile, image and object layers with parallax, twelve behaviours you add without writing code, room rules, state machines, routes, variables that carry over from one room to the next, and a preview that runs the room in the VM. - The interpreter is faster. Superinstructions give 1.47-1.48x over JaruBench on ESP32, ESP32-S3 and ESP32-P4, and jump tables add another 1.2-1.7x on ESP32 and S3.
- Less memory on the boards. Without the Arduino core's heap poisoning, with strings in a single block, with repeated constants shared and with one BMP per sprite instead of one per frame, each game uses tens of KB less internal RAM. JaruGoody loads on the TTGO in 6.1 s instead of 49.4 s.
- Fixed pace:
Display.fps = 30makesDisplay.update()wait for the next step and report how many are due;Display.dtgives the real seconds between frames. - New sprite editor, with undo, batch slicing, editable pivot and collider, and actors and poses: an actor groups a character's sprites and the IDE generates the code that loads them.
- JARU Slate, a dark style shared by every window of the IDE.
- ESP32-S31 with an 800x480 RGB screen, USB keyboard and SD, and an I2C gamepad (Adafruit Mini I2C Gamepad) configured from the IDE.
sprite.contacts()returns the normal and depth of each contact, for games that resolve their own collisions.- Installer examples in English, with JaruBlocks, new and with original music, and the JaruCade arcade's I2C gamepad in the 23 playable games.
Breaking changes
| Change | What to do |
|---|---|
| Programs compiled with 0.9.7 need 0.9.7 firmware. A 0.9.6 firmware cannot reject them (it only compared the major number) and stops at the first new opcode with "Unknown Opcode encountered". The same goes for sprites from the new Build, which come as a single image. From 0.9.7 on, the loader also compares the minor and the revision, and a program newer than the VM is rejected with a clear message. | Flash the VM firmware on each board from the IDE (Flash Firmware) before uploading programs. If you cannot do that yet, compile from the command line with --no-superinstr, which emits 0.9.6 bytecode; sprites still need the new firmware. |
/ between two integers is computed in double precision, as it already was with decimals. It used to be single precision: 1 / 3 gave 0.3333333432674408 and int(80 / (40 / 30)) gave 59. div between integers is exact over the whole 32-bit range (it used to fail from 16,777,216 up). On the ESP32-P4, int(), toInt(), Math.floor and Math.ceil with a negative decimal returned 0. | Nothing, unless the program depends on the old rounding. Large decimals converted to integer now give the same result on Windows and on every board. |
NaN is no longer equal to itself: nan == nan is false and nan != nan is true, as in any language. And 0.0 == -0.0 is true. | To test whether a value is NaN, use v != v. Lists and maps still find a NaN key as before. |
A function without its end is a compile error. The compiler used to finish fine and the program did nothing, because everything after it became the function's body. The typical cause is an else if, which in JARU is elsif: the error points it out. | Add the missing end or change else if to elsif. |
grid.set and grid.clear only accept values from 0 to 255. They used to clip without warning: 256 was stored as 0 and -1 as 255. | Out-of-range values throw ERR_INVALID_VALUE and the cell does not change. |
flipX and flipY also flip the collider, inside the frame. An off-center box (a sword, a snout) used to stay on the original side when the sprite was flipped. On the boards, flipped drawing is now anchored as on Windows. | Check off-center colliders on sprites that flip. |
An inactive sprite does not collide, not even the one asking: hits, contacts and hitsGrid return nil if the calling sprite is inactive. And a.hits(a) no longer returns [a]. | Nothing, if the game already treated inactive sprites that way. |
The sprite resource carries its pivot, collider, loop and yoyo as set in the sprite editor. The VM used to ignore them, and the sprite came out with the pivot in the center and a whole-frame collider. | If your code compensated by hand for what the editor showed, remove that compensation. |
addImage() throws on a sprite loaded as a sheet, which is how the Build now exports sprites from the editor. | To keep adding frames, start from a sprite created with Sprite.new(bitmap). |
On the boards, Power.wakeReason() returns the WAKE_* constants. It used to return ESP-IDF's wake-up cause number, which didn't match them: after a timer wake-up it returned 4, the value of WAKE_EXT1. And Power.wakeOnGPIO() takes the pin and the level; without them it did nothing. | Compare with Power.WAKE_TIMER and the other constants, not with numbers, and call wakeOnGPIO(pin, level). |
In an enum, the names without = that come after one with a number can change value: with RED, GREEN = 5, BLUE, BLUE is 6 (it was 7), and with A = -1, B, B is 0 (it was 1). And a repeated name in an enum no longer compiles; the first one used to count. | If you save those values to a file or compare them with numbers, check them. Remove the repeated names. |
| An array written between braces with rows of different sizes no longer compiles. A shorter row used to shift the following ones (or, if it was the last one, was padded with zeros), and a longer one wrote outside the array. | Write all the rows with the same number of items. |
File.open only takes the modes in its table: r, w or a, followed by + and b if needed. With another one, like "rt" or "rw", it throws ERR_INVALID_VALUE. Before, on Windows, "rt" and "wt" opened the file and others closed the VM, and on the boards it opened for reading or for writing depending on the letters. | Use one from the table: "r" instead of "rt", and "w" instead of "wt". |
Sprite.load throws the exception if the image of a frame or of the sheet is missing. It used to return an error value. | If you checked what it returns, catch the exception with try. |
find(substring, n) with n below 1 throws ERR_INVALID_VALUE; it used to return 0. And from the third occurrence on it gives another position: the right one. | Use n from 1. |
toUnsigned() of a negative number returns a float, its unsigned value; it used to return the same integer. | If you added 4294967296.0 to it, remove that. |
grid.path from a place to the same place returns [x, y]; it used to return nil. | If you checked for nil to know whether you had arrived, compare the origin with the goal. |
On the boards, with the 32 timers in use, after() and every() return an error value; they used to cancel the first existing one to make room. | Check the timer number with id > 0, or use fewer timers. |
MQTT on Windows logs in with the user name and password in the settings. It didn't use them before, and projects saved with an earlier version have User iotdev and a sample password: test.mosquitto.org, the default broker, rejects that user. | If the project uses MQTT on Windows with test.mosquitto.org, leave User empty in Config › Platform › Windows (Settings). New projects already have it empty. |
toFloat() reads all the digits: "3.14".toFloat() == 3.14 is true. It used to keep about 7: "3.14" gave 3.1400001049041748, "1e39" gave infinity and "1e-46", 0. | Nothing, unless the program relies on the old rounding. |
toInt() of a text throws ERR_TYPE_MISMATCH if the number doesn't fit in an integer, as when the text isn't a number; also with "inf" and "nan". It used to give a useless number, with no error: "3000000000".toInt() gave -2147483648 on Windows and 2147483647 on the boards. | If the text can bring a number like that, convert it inside a try. |
On a board, with no free PWM channels, awrite, frequency(pin, hz) and resolution(pin, bits) throw ERR_OUT_OF_MEMORY, as on Windows. They used to do nothing, and on the P4 and the S31 the ninth pin took the first one's channel. | Use at most as many PWM pins as the board has channels (16 on the ESP32 and 8 on the S3, the P4 and the S31), or free one with detach. |
Scene editor (new)
A new resource type, the scene (.scn), with its own editor in the IDE. It lays out 2D rooms and, on build, translates them into plain JARU code. That is why it needs nothing special from the VM: it works the same on Windows, ESP32, ESP32-S3, ESP32-P4 and ESP32-S31.
How it fits in a game
- Scenes live in the project's Scenes folder and each one is edited in its own tab of the scene editor.
- On build, each scene becomes
Code/<Room>.gen.aru, plus a sharedCode/SceneRuntime.gen.aruwith what all of them use. They are not edited by hand: they are regenerated on every build, and the runtime only carries what the project uses. - The game imports the room and uses it:
<Room>Load(spawn)to load it and, in the loop,<Room>Update(room),room.scene.update(),<Room>Resolve(room)and<Room>Draw(room). The header of the generated.aruexplains it with an example. - Rooms as data. If the game code uses
SceneLoad, the Build leaves each room's map in its.gmapand everything else about all the rooms in one table,Build/flash/Scenes/World.gmap, with a single loader. A game with many rooms no longer carries code for each one, and what appears in every room is loaded once. A room with behaviours, state machines, templates, variables or code of its own does not run them as data: it keeps its.gen.aru, and the Build says so. spawn(e)is optional and lets the game decide about each object in the room: return a sprite for the room to use instead,falseto skip it, ornilto let the scene create it.- What belongs to the game rather than to a room lives in
Scenes/Game.jsonand is edited in the Game tab: the variables that carry over from one room to the next (score, lives, health and max health, plus your own), the controls, with their key, the GPIO for each board or a button of the I2C gamepad, and the game pace (Display.fps). In code,room.gameis the same object in every room,room.dtcomes fromDisplay.dt,SceneInputMap()maps the controls andSceneGameReset()starts a new game.
Layers and camera
- Tile layers with free position, parallax and repetition (Wrap). One of them is the scene map, the one drawn right under the sprites. Layers with Wrap show a dimmed copy on each side in the canvas.
- Image layers, with parallax and automatic scrolling in px/s: backgrounds, clouds.
- Object layers, and decoration layers: objects drawn at their place in the stack but left out of physics and rules.
- Collision map with solid values and one-way platforms, or by the tile number of the layer that paints it (Tile numbers).
- Camera bounds and mode (CLAMP, FREE, WRAP), following an object, and a frame with the device screen that you drag to see what the camera will show.
Objects and behaviours
- Sprites, images, shapes, zones and markers, with body type, tag, fixed rotation and "Keep in room". The collider is the sprite's: it is defined in the sprite editor and the scene uses it.
- Per-object physics: mass, friction, bounce, gravity scale, damping, collision filter, continuous collision, and initial velocity and spin.
- Twelve behaviours you add to an object without writing code: Linear motion, Patrol, Follow route, Follow / flee, Sine wave, Platform control, Top-down control, Health, Destroy when it leaves, Show a variable (shows a variable on screen), Animation and Spawner. They can face where they are heading; Patrol turns around and Follow stops at the map in rooms without physics too; Animation can ping-pong or play backwards.
- Routes: the Path tool draws a polyline node by node and "Follow route" follows it by its tag, looping, back and forth or to the end. Several markers with the same tag also work as a route.
- Templates: an object saved without a position, which spawners and the "Create from a template" action copy, properties included.
- Zones that the game can draw as a rectangle of their color ("Show in game"), to see where they are while testing.
Room rules
- Events: on start, every so often, when two tags touch, on entering a zone, when an object leaves the room (falling into a pit), when a control is pressed and when a variable meets a condition.
- Actions: change a variable, create from a template, destroy, take health, lose a life, start a new game, turn a behaviour on or off, play an animation, play a note or an effect, play music, ask for another room (or repeat this one) and call your own function, which receives
f(spr, other, room). - Rules can be copied and pasted between rooms with Ctrl+C and Ctrl+V.
State machines
- An object can have states. Each one says which behaviours are on, with which values (patrol slowly and chase fast with the same component), what it does on entry and what takes it out. On leaving a state, the values go back to the object's own.
- Transitions: after some time, closer or farther than a distance from another object, on touching a tag and depending on a variable.
- They are edited in their own window; the canvas shows the distance radii and the whole machine of the selected object. Ten objects with the same machine share a single copy of the generated code.
Your own code
- Room creation code and per-object code, in windows that edit like the IDE editor: autocompletion (also for
room.androom.game.), signatures, Ctrl+click and underlined errors.
Working on the canvas
- Zoom towards the cursor, a ghost while placing, multiple selection, snapping guides to other objects (Alt releases them), align and distribute, and step-by-step drawing order.
- Problems panel: warns about what will not work before running (a call to a function that does not exist, a behaviour that cannot move its body, a game with no input on the board, a collision layer that does not move with the map...) and in some cases fixes it with one click.
- Play builds the room on its own and runs it in the VM; whatever the game prints reaches the IDE Output. Gizmos draw on the canvas what each behaviour does without running anything.
Speed and memory on the boards
- Superinstructions. The compiler fuses the most frequent bytecode sequences (
local + constant + operation,comparison + jump,i++...) into 27 new opcodes that every VM runs. Measured on JaruBench: ESP32 1.48x, ESP32-S3 1.48x and ESP32-P4 1.47x, with up to 2.5x in tight loops; on Windows, 1.2x. Nothing changes in the language: each fused instruction falls back to the classic path with any type other than integer. - Jump tables in the board interpreter, which the IDF and Arduino compiled as a tree of comparisons: JaruBench 1.2-1.7x faster on ESP32 and S3, and up to 1.9x on the P4.
- 8-bit tile maps on ESP32 and S3, and the background of a tilemap that does not change is reused.
- Partial screen updates (ESP32 and S3 with PSRAM):
Display.update()sends to the LCD only the 8x8 blocks that changed. - One BMP per sprite instead of one per frame: JaruMix goes from 145 to 84 files and about 375 B less RAM per frame; JaruGoody loads on the TTGO in 6.1 s instead of 49.4 s.
- Less internal RAM on ESP32 and S3: the heap without heap poisoning saves 16.7 B per allocated block (40-61 KB per game on the TTGO); strings live in a single block (6-9 KB per game); each image object goes to PSRAM (360 B less per frame); the compiler shares the repeated constants of each function and the VM sizes its tables exactly.
- No line numbers, if you need it: the Line numbers checkbox in Build › Target (or
--no-line-infoin the compiler) saves the program without its line tables, which are only used for error traces: 1.8 to 9.4 KB of internal RAM depending on the game.
Language and API
Display.fps = N(0-1000; 0 by default, as until now): after presenting,Display.update()waits for the next step and returns how many are due, 1 or more if the frame was late.Display.dt: seconds between the last twoDisplay.update()calls. A game thus runs at the same speed with or without scrolling, and on any board.sprite.contacts(sprite | list)returns, for each sprite touched,[other, nx, ny, depth], with the normal pointing where the sprite must move to get out. Works with rotated rectangles and circles, without a scene or physics.sprite.prevXandsprite.prevY, read and write: the pointhitsGridsweeps from. A program without a scene sets them at the start of each frame; any program, when teleporting a sprite.Input.set(action, state): an external source for anInputaction (an I2C gamepad, for example), added to the mapped key or GPIO.I2C.transfer(port, addr, bytes, n, delayUs): optional fifth argument for chips that need a pause between writing and reading.draw.getPixelreturns the color on Windows, as it already did on the boards (it used to print some text and returnnil).Input.mapaccepts GPIO 0-63 on every board.HW_ESP32S31(= 7), andplatform()returns it on the ESP32-S31.bitmap.tcolor(color)changes the bitmap's transparent color; it used to do nothing. On the boards,draw.bitmapanddraw.gridBitmapuse the bitmap's transparent color (the one fromBitmap.load(file, color)or fromtcolor) instead of a fixed black, as Windows and sprites already did.foreachover a map: assigning to the loop variable (v = x) changes the value of the current entry. It used to create new numeric keys and could overwrite other entries.- MQTT:
MQTT.unsubscribealso exists on Windows, and without a connectionpublish,subscribeandunsubscribedo nothing (on Windows,publishandsubscribeused to close the program).MQTT.pubinfo()publishes the VM version to<user>/<id>/deviceand returnstrue, orfalsewithout a connection; it used to just print it to the console. - Power:
wakeOnGPIO(pin, level)wakes the board with any pin, not just the RTC oneswakeOnPin()accepts, and with several at once: each call adds one. It used to take no pin and woke the board with none. On the boards, each program starts without the wake-up sources the previous one left set.sleep()andwakeOnTimer()no longer print[DEBUG]messages to the console. - I2C:
readReg16andwriteReg16honor their last argument,littleEndian; they used to ignore it and always used Big Endian. If a program passedtrueand also swapped the bytes itself to make up for it, the swap is no longer needed.
Physics and collisions
- Stacks of bodies hold up on their own, also at 20-30 fps on the boards: the solver starts each contact with the impulses of the previous step, solves both corners of a face at once and corrects positions in four passes.
- A spinning KINEMATIC hits: a pinball flipper that turns by changing
angleor withangularVelpasses its spin to what it touches, harder at the tip than at the base. - A body resting on an elastic grid no longer bounces forever, and a bounce against the grid no longer gains energy.
- Map collision:
grid.clone()copies the whole map;hitsGridno longer reports the normal of a face covered by another tile; a one-way platform stepped on at the corner counts as ground, with 1 px of slack; a rotated rectangle collides with the map as it is;grid.getwraps with wrap and accepts decimals; the errors ofhits,get,setandseename the real method.
Compiler
- A
returnwithout a value right beforeelsif,else,case,catchorelfornow compiles. - Chained calls,
f(1)(2), really call what the previous one returns; they used to compile without error and the VM ended up crashing. return,breakandcontinueinside atryrelease the handler on the way out.- Loops inside nested functions no longer overwrite each other's state, and the symbol table scope goes back where it was after each statement.
- Assigning to a native function or to something that cannot be assigned (
Math.sqrt = 5) is a compile error; it used to close the compiler. - The variables of a single
varstatement are evaluated in the order they are written: invar a = 1, b = a,bseesa. The order used to depend on the names. A name repeated in the same statement is an error, as with twovars. - Every error carries its line and file (some came out at line 0 and with no file). After an error no half-written
.jxris left behind, and compiling from the command line without-owrites the.jxrnext to the source instead of overwriting the.aru. - Native compilation (
jaruaot) no longer breaks on a string constant longer than 4 KB: its line in the.cppwas cut and lost the closing quote. x++andx--no longer swallow the rest of the expression:var c = a++ + baddsb. And they work on an element of a list or a map:list[0]++added on the next line and++list[0]closed the compiler. With something that can't be incremented, like5++, they give an error.- In an
enum, the count carries on correctly after a name with a number: withRED, GREEN = 5, BLUE,BLUEis 6, not 7, and withA = -1, B,Bis 0. A repeated name is an error; the first one used to count. - An
operatorthat can't be overloaded, or with too many or too few parameters, and a circularimportgive an error with its line; they used to close the compiler without any message. .., which isn't a JARU operator, gives a compile error; it used to compile without a warning and throw the VM out of step. An array written between braces with rows of different sizes, which shifted the rows or wrote outside the array, and a struct with more than 127 fields, which closed the VM when declared, also give an error.- The compiler's error messages are in English, like the VM's (many were in Spanish), and without typos: “Unrecognised”, “method”. An error at the end of the file, like a missing
end, comes out after the last thing written and saysend of file; before, it came out on the line after the last one, with the text of somewhere else. And in the IDE's Output, each error says what the compiler found, as in the console. - A function (
func) named like one from the standard library, such aslen,absorrgb, gives a warning when compiling: it hides it in the whole program, also in imported files. The program still compiles; the warning shows in yellow in the IDE's Output. Methods (def) with those names don't warn: they're used with the object in front. - Floats written in the code no longer lose digits:
1e-18became0and1.23456789e-10became1.2345679e-10, and a program with a float of1e110or more didn't load (Error loading file). Only one smaller than2.2e-308, not counting0, becomes0.
VMs
- New VM for the ESP32-S31 (ESP32-S31-Korvo-1 board): 800x480 RGB screen, USB keyboard, SD and wake-up by EXT1.
- When a program ends on a board, its timers, GPIO interrupts, MQTT callback and pending collisions are released. They used to stay alive when the next program was loaded and could hang it.
- ESP32-P4: no longer shows a black frame every few seconds; the interpreted VM runs in its own 16 KB task (a large game exhausted the stack while loading sprites); the CPU runs at 360 MHz, because the 400 it asked for do not boot on boards before revision 3;
Display.loadTFTnow draws outside the view (on the S31 too). - Sprites: a decimal
frameis rounded down;loopaccepts integers;addImagekeeps its own pivot;yoyoanddir = -1really animate, andyoyowith a single frame no longer crashes the VM;Sprite.loadno longer leaves freed frames inside the sprite;Sprite.new(sprite)keeps the collider of the original. clone()copies arrays nested in structs, lists, maps and instances, and leaves the same bitmap in the clone: in a struct, an instance or an array it used to leave an unfilled one that could crash the VM.foreachover a map walks the table in one pass: each step used to count again from the start (150,000 entries, about 7 s).- Changing the value of a key that already exists (
m[k] = x) no longer rebuilds the table of the map or object: inside aforeachover that map the walk no longer repeats or skips entries, and overwriting no longer changes the order ofkeys(),printorJSON.stringify. - Reading a property that does not exist reports its name.
- A
spi_hostof 2 inboot.cfgno longer breaksDisplay.openwhen the screen is not SPI. - Without
jaru_appinboot.cfg, the board warns every 5 s instead of retrying. - Fixed several faults that showed up when the garbage collector ran in the middle of an operation: assigning into a map (
m[k] = v),hitsGrid, an exception traceback andm.items(). Also one after a runtime error, which kept using the stack that had been cleared. - ESP32 and ESP32-S3: LovyanGFX 1.2.27, with its version pinned in the firmware.
- On Windows,
draw.textwith an empty string draws nothing; it used to close the VM. An Http request abandoned on timeout no longer closes it either, even if the server answers later. - In the GPIO simulator, a pin in
GPIO.OUTPUTorGPIO.OPEN_DRAINshows as a digital output, and the LED lights up withGPIO.write. It used to show as a PWM with no channel. MQTT.publish,subscribeandunsubscribewith something that isn't a string throwERR_TYPE_MISMATCH; a number used to close the VM.JSON.stringifywrites integer and boolean keys as text ({1: "one"}comes out{"1": "one"}), and throws its error if a value that can't be written is inside a list or a map; both used to close the VM.- Drawing a sprite with
frameequal toframesno longer closes the VM:nextFrame()on the last frame goes back to the first, and reactivating a sprite that finished its animation without loop starts the animation again. pauseMusic()silences the notes that were playing; they used to keep sounding untilresumeMusic()orstopMusic().- With the buzzer, notes with a duration from
Sound.tone,pulseandnoisestop on their own when they end, as with I2S; they used to sound untilSound.stop(), and the channel stayed busy:isPlaying()gavetrueand a note with lower priority was dropped. - With the buzzer and without
Sound.init(),Sound.isPlaying,loadSampleandplaySamplereturnfalse, as with the sound started; they used to be able to bring the VM down. - On the boards,
File.openwith"r+","w+"and"a+"reads and writes, as on Windows;"r+"used to empty the file, and"w+"and"a+"didn't allow reading. type()of an error value givestError; it used to return nothing and throw the function's variables out of place. And on the boards,type(e)of the error in acatchgivestError, as on Windows (it used to be 25).draw.byteswith the palette in a list or with fewer bytes than it draws returns its error value without throwing the function's variables out of place; the program used to fail or close later.- On the boards,
Display.loadBGwith a file that doesn't exist returnsfalseand keeps the previous background, as on Windows; the board used to restart. File.openwith a mode that isn't in the table, like"rw", throwsERR_INVALID_VALUE; it used to close the VM on Windows, and on the boards"rw"emptied the file.Bytes.newwith a negative size throwsERR_INVALID_VALUE; the VM used to end.- Strings:
find(substring, n)gives the right position from the third occurrence on, and withnbelow 1 throwsERR_INVALID_VALUE;rightStrip()of a string with nothing but spaces leaves it empty; andreplace("", other)returns the same text instead of hanging. text[i] = …with something that isn't a string throwsERR_TYPE_MISMATCH, and with an empty string,ERR_INVALID_VALUE; a number used to close the VM and an empty string damaged the string.toUnsigned()of a negative number gives its unsigned value, as a float (-1gives4294967295); it used to return the same integer.toFloat()reads the number with all its digits; it used to cut it to about 7, and"1e39"gave infinity. And on the Windows VM,"nan".toFloat()gives a real NaN: it used to give a broken value, and with"-nan"the VM closed.printandprintlnwrite floats with just the digits needed to read back the same number:0.1comes out as0.1(it used to be0.10000000000000001) andPIas3.141592653589793;0.1 + 0.2, which isn't exactly 0.3, still comes out as0.30000000000000004. It's the same on Windows and on every board (the ESP32-P4 and the ESP32-S31 already wrote them this way). Very large round numbers come out in scientific notation from1e15on; before, from1e17.toString()doesn't change.- A map's
get()andcontains()take the same keys asmap[key]: anything except lists and maps. They used to throwERR_TYPE_MISMATCHwith a float or an object as the key. And on the ESP32-P4,map[-1]finds what was stored inmap[-1.0], as on the other platforms; it used to miss it. The error for a map written with a list or a map as a key no longer says that only strings, integers, booleans and nil are allowed. toInt()of a text with a number that doesn't fit in an integer throwsERR_TYPE_MISMATCH; it used to give a useless number, different on Windows and on the boards.- On Windows, with
showBG,autoClearclears the view before drawing the background image, as on the board; before, what the image didn't cover stayed from one frame to the next. - On Windows,
draw.cls()clears only the view, as on the board; it used to clear the whole window and, with it, theloadTFTframe. - On the boards,
draw.bitmapBatch,draw.tilemapand the layers ofdraw.sceneuse the image's transparent color (the one fromBitmap.loadortcolor), as on Windows; they used to always take black. - On the boards,
GPIO.awriteonly looks at the PWM channels the chip has (16 on the ESP32 and 8 on the S3, the P4 and the S31) and, when none is free, throwsERR_OUT_OF_MEMORY, as on Windows; before, on the S3 it did nothing, and on the P4 and the S31 the ninth pin output the same as the first.detachfrees the channel, andgetdutyof a pin without PWM returns 0 without giving it a channel. grid.pathwith the origin equal to the goal gives[x, y]; it used to givenil.Sprite.loadthrows the exception, with the file name, if the image of a frame or of the sheet is missing; it used to return an error value thattrydidn't see.- On the boards,
GPIO.awritewith a negative value outputs 0, as on Windows; it used to output the maximum. - On the ESP32 and the ESP32-S3,
I2C.readandtransferwith more than 128 bytes, or with a negative length, throwERR_INVALID_VALUE; they used to write outside their memory. - On the boards, with the 32 timers in use,
after()andevery()return an error value, as on Windows; they used to cancel the first existing one without warning. - On the ESP32 and the ESP32-S3, the WiFi comes back by itself: if the network drops, the board connects again, and if it doesn't connect at startup, it connects when the network shows up, and MQTT with it. Before, a drop left it without WiFi until you restarted it, and with no network at startup MQTT never connected.
- On the ESP32 and the ESP32-S3, with several access points with the same name (a mesh network), the board scans every channel and tries the strongest one first. Before, it kept the first one it found, even if it was far away, and sometimes it didn't manage to connect at startup.
- On the ESP32-P4 and the ESP32-S31, HTTPS works and checks the server's certificate; requests to
https://addresses used to always fail. - MQTT on the boards: with Interval ms below 1000, the board uses 60 seconds; it used to end up as 0, and on the ESP32 and the ESP32-S3 the connection was cut right away.
- On the ESP32 and the ESP32-S3, if the MQTT broker takes a while to answer when connecting, or doesn't answer, the board waits 4 seconds at most and tries again. Before, after 5 seconds it restarted, and since it connects again at startup, it kept restarting.
- On the ESP32-P4 and the ESP32-S31, MQTT no longer redoes the connection during the first minute: a subscription made right after connecting got lost without
MQTT.isConnect()saying so. If the connection drops, it connects again every 10 seconds. - MQTT on Windows logs in to the broker with the Client ID, User and Password in the settings; with User empty, with no user name. It used to always log in as
JARU_Win32with no user name or password: a broker with a password wouldn't let it in, and two VMs on the same broker kicked each other out.MQTT.isConnect()givestrueonce the broker has accepted the connection, and if it doesn't accept it, the console says so. - MQTT on Windows no longer needs internet: the VM waits for the broker 3 seconds at most and, if it doesn't connect, the program starts anyway and the VM keeps trying every 5 seconds; if the connection drops, it connects again. It used to try
www.google.combefore starting the program: without internet, or without the broker, the program never started, with one processor core at 100%, and if the broker restarted it didn't connect again. - VM error messages: those of
%say the operator (it said “operator ''”); those ofdraw.tilemapandtSizenamegrid.tileset = bitmapandgrid.tSize(width, height), which are the ones that exist; the one for GPIO interrupts, on Windows, is in English; and the trace of a serious VM error no longer says<<main>>.
IDE
JARU Slate
- One dark style for every window: the same colors for content, panels, cards and fields, flat buttons, Segoe UI for the interface and Consolas for code.
- Blue only for what is on, pressed or the main action; on hover, a button lightens without changing color. Docked panel headers mark the one with focus.
- Hints are readable on a blue background.
- The sprite and map editors open a third faster.
Sprite editor
- Rebuilt: undo and redo, batch slicing (marquee, "Cut All" and automatic detection), a frame strip you can select, reorder, duplicate and reverse, editable pivot and collider with guides always visible, the previous frame's silhouette and trimming of the sheet's empty margin.
- Actors and poses: an actor groups a character's sprites (idle, walk, jump...). The editor shows the pose bar, a ghost of another pose to align against and the shared pivot; the explorer finds sprites by actor; and the Build generates
Code/Actors.gen.aru, with one structure per actor and the functions that load and copy it (ActorTikiLoad(),ActorTikiCopy(tiki)). - A new sprite starts at 8 fps and inherits the sheet and background of the previous one; "Fit to content" wraps all the actor's poses; Shift+click selects a range of frames in the Sprite Manager.
Image and map editors
- Images: rectangular selection (move, copy, cut and paste), a row with the colors the image uses, tile grid, 1:1 button and ellipse without anti-aliasing. New Image suggests the size of the last one.
- Maps: Rect and Line tools; Map Size is applied when leaving the field.
- Tracker: centered template, full headers and two-digit instrument numbers.
- Images: the ones the IDE creates or imports get Include in Build on; Include in Build and Palette are saved in the project as soon as they change; Output follows the size of the image when you undo, and saving with Crop can be undone.
Run and debug
- Target selector in the toolbar (Windows or the project's boards) for Run.
- Play no longer leaves a console open: what the game prints goes to the Output, like the errors.
- When stopping at a breakpoint, the IDE says where and comes to the front; F9, F5 and F10 work in the editor; Watch opens floating.
- Run on a board shows its steps, checks what the board carries and, if it is not the JARU VM, offers Flash Firmware. Open Serial uses the board's port and comes back after the reset; Scan Ports picks the port that looks like a board.
- VM window x1 to x4 on Windows (Windows configuration, Display panel): 320x240 games no longer look tiny on a PC monitor.
- If the compiler crashes, never connects or does not start, the IDE no longer gets stuck compiling with the ribbon locked: it says so and is ready again.
- A key pressed in the Output while a program is running no longer makes the IDE raise an error; whatever the program asks for is still typed in its console.
- Run also builds again if images, sprites, maps or music have changed. A sprite that was never saved no longer stops the Build (it is skipped with a warning), and a file that can't be read no longer leaves the IDE stuck compiling.
Boards and hardware
- ESP32-S31 in the IDE: chip, RGB screen with its timings and its 21 pins, USB keyboard and SD. The embedded Python ships esptool 5.4.0, which can flash it.
- Seesaw I2C gamepad (Adafruit Mini I2C Gamepad): I2C bus in the board profile, buttons in Game › Controls of the scene editor, and a driver in the driver manager for hand-written games.
- Gamepad driver 1.2.0: two sources on the same action (two buttons, or a button and the joystick) add up. Only the last one used to count, and on an action with no key or GPIO
justPressedfired on every frame. - The rooms' I2C gamepad only uses the pins of your board's profile. If it has no I2C bus, it doesn't open the bus and says so on the console; before, it took the pins of another board of the project with the same chip, such as the JaruCade's GPIO 11 and 10.
- Board profiles named after the board, brand and model as the maker sells it:
Waveshare_ESP32-S3-Touch-LCD-2.8,LILYGO_T4_v1.3,Espressif_ESP32-S31-Korvo-1… The chip, screen and bus are already shown under the list.Generic_SH8601Z_QSPI_(template)is a template with no pins, not a board. JARU_JaruCadeprofile for the JaruCade arcade: Waveshare ESP32-S3-LCD-2.8, with or without touch, with the Adafruit I2C gamepad on its bus.- Factory board profiles update themselves when the IDE starts: if yours are as an earlier version left them, they are replaced by the new ones; if you edited them, they are left alone.
- New in ESP32 Tools offers the devices of recent projects.
- If Target is a board, choosing another one in Device in ESP32 Tools, saving it or creating a new one puts it in Target too, and answering No to the unsaved changes leaves the window with the saved board, its chip and its port. Before, Run could announce one board and send the
boot.cfgof the other one, or through the port of another one. - I2C drivers: the PCF8563 example compiles (it kept the time in a variable called
time), and without AI, an address without0xis read as decimal, as the form said:118no longer comes out as0x118. - Get Info, Generate and Get MAC put the board's Device Name as the MQTT Client ID if it's still the default one or empty. The default one was the same on every board, and two with the same one kicked each other out of the broker.
Code intelligence
- Autocompletion goes down into the fields of a
struct, and Ctrl+click reaches those of a struct defined in another file. - The build reports what the editor underlines as an error, and checks the editor again after generating the
.gen.arufiles. - If a function is left open by an
else if, the hint says so. - The warning about
Input.mapwith a key name in a program for a board recognizesif (windows)whenwindowsholdsplatform() == HW_WIN32, and no longer flags lines that never run on the board.
Update notices
- The new-version notice has Download, Remind me later (it comes back after 7 days) and Skip this version, and a link to what's new. An important update shows on every start.
- The IDE can also show short notices for certain versions, for example about a known problem.
- It still sends nothing: it downloads the same file as everyone else and compares the versions on your computer, which is also where your choice on each notice is kept.
Other improvements
- Closing the project, opening another one or closing the IDE asks about unsaved work in every editor, and Cancel closes nothing. Reopening or renaming a sprite that is already open, deleting a map other than the open one, or saving a new map or image no longer loses work.
- New Project no longer writes over a folder that has something in it: it says so and lets you choose another name.
- Git: switching branches first saves anything unsaved and no longer throws away uncommitted changes; if they clash with the other branch, it doesn't switch and says so. It also reloads the open tilemaps and rooms.
- Deleting from the explorer sends the file to the Recycle Bin when the drive has one (on a USB stick or a network folder it is deleted outright, and the question no longer promises the Recycle Bin), and the explorer shows the real size of each file.
- Floating windows fit on the screen when they open and no longer cover all of Windows.
- The project wizard has the new logo, no longer stays on top of other programs, and closing it can no longer crash the IDE.
- The ribbon shows the help of its buttons; the main window appears on the taskbar.
- The texts the IDE still showed in Spanish now come out in English: the Git panel (the Type column, the details of a commit, New Branch and its messages), the end of the Debug console, the Beautifier warning, several Error List messages, the GPIO simulator LED with no channel, the JSON module help, the Other category of the I2C drivers, an ESP32 Tools warning and the disassembler and Watch messages.
Examples
- The 38 installer examples, in English: screen texts, messages and comments. Particulas is now called Particles, and LanceOfAshes and FistOfAsphalt join them.
- JaruBlocks, new: falling blocks, with original music.
- The JaruCade's I2C gamepad (Adafruit Mini I2C Gamepad) in the 23 playable games: 20 with the driver in their code and 3 through the scene editor's controls.
- Every example ships with the JARU JaruCade board profile and opens without the profile-not-found warning.
- The TTGO buttons are mapped only on the TTGO and the keys only on Windows: six games put the S3 into a reboot loop and xnom did not start on the S3 or the P4.
- The examples no longer carry the art sources, the Python scripts or the launchers, and PacMan leaves the installer.
Documentation
- New guide, Your first game with rooms: the scene editor's rooms, the code they generate, the game loop and how to switch rooms.
- Guides updated with what is new:
Display.fpsandDisplay.dt,sprite.contacts,prevX/prevY, the frame sheet, anddivand/in the operators guide. - Fixed the examples that used functions that do not exist (
Display.redraw,GPIO.IN,draw.rectfill...) and thebitmap,powerandtimerpages.
Known issues
Input.justPressedcan miss presses. Workaround: readInput.pressed()once per frame and compute the edges yourself.- Renaming, moving or deleting an asset does not rebuild the resources of the scene editor's Play. Workaround: delete
Build/assets.stampbefore pressing Play, or build with Build › Compile, which always rebuilds them. - Compiler: indexing what a call returns,
f(x)[0], does not work. Workaround: store it in a variable first. readLine()on a board gets no input over serial: the examples that ask for input (CheckPrime, Factorial_ForLoop, FibonacciGenerator and Palindromes) keep waiting. They work on Windows.
The full list, with all the known issues of 0.9.7 and how to work around them, is in Known issues.
Installation / upgrade notes
- Windows IDE: installer
JARU_IDE_0.9.7_Setup. When you upgrade from 0.9.6, it opens what's new in 0.9.7 in the browser at the end; on a new install, the first steps; reinstalling the same version opens nothing. - Flash the VM firmware (Flash Firmware) on each board before uploading programs compiled with 0.9.7. With the 0.9.6 firmware there is no warning: the program stops at the first new instruction with "Unknown Opcode encountered".
- Rebuild your project after upgrading to regenerate the resources (sprite sheets, scene editor rooms, actors).
- Board profiles: when the new IDE starts, the old-named ones you have not edited go away and the new ones appear; one you edited keeps its old name. Your projects do not change: the name of the profile each board came from is only a reference.
- Check the breaking changes table: integer division,
NaN, functions withoutend,grid.set/grid.clearand flipped colliders are the points that may require touching code.