We have released RumiCar Simulator v10.3.0. The release notes below are also available on GitHub.
v10.3.0
Fixed places where the interpreter for learning programs gave values different from Python or C (gcc) without any error, Python and Arduino C idioms that could not be written, and errors that were shown in Japanese even in English mode (Stage BK; the driving physics is untouched — driving on the shipped courses and the result of the shipped official sample race are unchanged; the official sample race’s verifyHash e9f1da0b and the frozen hashes f0–f3 are the same as before, and so are the output and the parse result (syntax tree) of the shipped sample programs). Your program may behave differently — check the lists “forms that ran without error in v10.2.0 but raise an error in v10.3.0” and “forms whose value changes without any error” below. This time C changes too ((5) and the value of C’s function-style int(c)). (1) Error messages and deep recursion (py and C) — lexical and syntax errors are now shown in the display language (Lexical error: … and Syntax error (Py/C): … in English mode; v10.2.0 showed them in Japanese even in English mode; the Japanese wording and the line numbers are the same as in v10.2.0). When user function calls are nested 200 levels deep (loop()/setup() count as one level), this is now the runtime error “Function calls are nested too deeply” with a line number (v10.2.0 showed JavaScript’s “Maximum call stack size exceeded” without a line number). A deeply indented body can hit this error before 200 levels (how deep the JavaScript stack lasts depends on how the body is written and on the environment). The error for break/continue outside a loop gives the loop()-specific hint only directly in a function of a program that has loop() (not in a program that runs once, nor at top level). The evaluation-step-limit (infinite loop) error adds “millis() and micros() do not advance within one pass of loop(); wait with delay() or until the next loop()” only when that pass of loop() called millis()/micros() (how time advances is unchanged). (2) Python syntax — several statements separated by ; are accepted (a = 1; b = 2; both statements of if x: a = 1; b = 2 are the body of the if; what follows the ; in global x; x = 1 and import math; x = 1 runs too; v10.2.0 gave a syntax error or silently dropped what followed the ;). A line break inside brackets ( [ { continues the statement (multi-line lists, dicts and function arguments). An unclosed bracket is the syntax error “‘(‘ was never closed” (on that bracket’s line). Adjacent string literals form one string (s = "a" "b" is “ab”, and "y" f"{x}" is concatenated too; v10.2.0 silently dropped the second literal or raised a call error). The string prefixes r/R (raw), u/U and fr/rf are available (b"…" is the syntax error for an unsupported form). in/not in are available (items of a list/tuple, substrings of a str, keys of a dict). A list target [a, b] = 1, 2 assigns. --1 and ++x are stacked unary operators (--1 is 1). An assignment with two or more targets and one value splits the value (a, b = "xy" gives x and y). Anything that follows a statement on the same line (x = 1 2, print "x", break 5 inside a loop) is a syntax error everywhere. (3) Format specs (py) — format specs in f-strings and str.format now give the same strings as python3 (with x = 3.14159, f"{x:.2f}" and "{:.2f}".format(x) go from “3.14159” to “3.14”; width, alignment, sign, ,, 0 padding, e/g/%/x/b and so on; rounding is round-half-to-even as in python3). v10.2.0 silently dropped the format spec. f"{x=}" (self-documenting), the conversions !r/!s/!a, nested f"{x:{w}}", and str.format‘s numbered {0}, named {a} (.format(a=1)), indexed {0[1]} fields and {{ }} work too. A lone } in an f-string is a syntax error. (4) Value semantics (py) — and/or return an operand (0 and 1 is 0, [] or 0 is 0). Python truthiness follows python3: an empty list, tuple or dict is false (in if, elif, while, not, the conditional expression, and/or and bool() alike; if lst: skips its body when the list is empty, and while q: stops once q is empty; v10.2.0 treated them as true even when empty, and while q: hit the evaluation-step limit). print, str, f-strings, str.format, %s and Serial.print show a bool as True/False and None as None (v10.2.0 showed true/false and 0). None is a value distinct from 0 (None == 0 is false), and a function that ends without reaching a return gives None. Using None in arithmetic, an ordering comparison or as an index is an error. ==/!= on lists/tuples compare item by item ([1] == [1] is True, nested lists too; in, count, index and remove follow the same rule). 0 ** -1, 1 / False, 1 // False and 7 % False are division-by-zero errors. // is floor division as in python3 (1 // 0.1 is 9). A bool is a number in count, index, remove, dict keys and indexes as well. list.index/list.remove of a missing value is an error. range() takes integers only. (5) C idioms — static int n = 0; inside a function is initialized only the first time and keeps its value (across passes of loop() too; v10.2.0 re-initialized it on every call, so f(); f(); f() gave 1). Casts (int)x, (long), (unsigned long), (char), (float), (double), (bool) and (void)x ((int)3.7 is 3, (float)7/2 is 3.5, and (T)x with #define T int too), numeric suffixes 1.0f, 10L, 10UL, 10u, 0x1Fu, the comma operator (for (int i = 0, j = 0; i < 3; i++, j += 2), a = 1, b = 2;, x = (a, b);, 1 ? 2, 3 : 4), %= (-7 %= 2 gives -1) and sizeof(int(x)) are available (v10.2.0 gave syntax errors). A cast of a char (a one-character string in this interpreter) gives its character code ((int)'A' is 65; (char)'A' stays a character). A /* left open to the end of the file is the lexical error “unterminated block comment” (in v10.2.0 it silently swallowed the setup()/loop() after it). Compound assignments (+=, %= and the rest) and ++/-- evaluate the target’s array and index only once (C and py). case 1, 2: and an array size int a[2, 3] remain syntax errors, as in gcc. Forms that ran without error in v10.2.0 but raise an error in v10.3.0 (most are errors in Python or gcc too): (a) recursion nested 200 levels or more (f(300) with def f(n): … return 1 + f(n - 1), and the same in C; v10.2.0 ran to about 600 levels; Python’s limit is 1000) (b) something following a statement on the same line (x = 1 2, print "x", print f"{x}", break 5 inside a loop) (c) an assignment whose item count does not match (a, b = [1], a, b = [1, 2, 3]) (d) a lone } in an f-string (f"a}b", f"a\}b") (e) mistakes in str.format ("{}{}".format(1) gave “1undefined” in v10.2.0, "}".format() gave “}”, "{0}{}".format(1, 2) gave “11”, and "{0!x}" was left as is) and format specs that do not fit the type (d on a string, a width on a list) (f) 0 ** -1, 1 / False, 1 // False, 7 % False, 0.0 ** -1.5 (v10.2.0 gave Infinity or NaN) (g) None in arithmetic, ordering comparisons, as an index and in "%d" % None (v10.2.0 computed with 0; 1 / None was a division-by-zero error), None as a list or string index (v10.2.0 used index 0), and random.choice(None) (with JavaScript’s error message) (h) list.index/list.remove of a missing value (v10.2.0 gave -1 / did nothing) (i) None, non-integer numbers or strings in range(), and a step of 0 in a range expression (list(range(1, 5, None)) and list(range(1, 5, 0)) were empty in v10.2.0, range(2.5) gave 0, 1, 2) (j) an unterminated /* in C (k) C’s (int); and int y = (int);. In addition, passing None to the device API is treated as an invalid argument (RC_read(None) gives -2; RC_drive(None, 100) and RC_steer(None) return 0 and do nothing; v10.2.0 treated None as 0, the same as LEFT/FREE). Forms that were errors and now work in v10.3.0: statements separated by ;; line breaks inside brackets; in/not in; the prefixes r/u/fr; [a, b] = …; --1/++x; f"{x=}"; .format(a=1); "y" f"{x}"; C casts, numeric suffixes, the comma operator, %=, (void)x and sizeof(int(x)). Forms whose value changes without any error (they now give Python’s or gcc’s value): py — format specs (f"{x:.2f}" from “3.14159” to “3.14”); f"{'a'!r}" (a → ‘a’); f"a\{x}b" (a5b → a\5b); print(True), str(False), "%s" % True (true → True); print(None) (0 → None); None == 0 (true → false); the value of a function without return (empty when printed → None); 0 and 1 (false → 0); [] or 0 (empty display → 0) and x = q or [0] with an empty q (q → [0]); the truthiness of an empty list/tuple/dict (if lst:, not [], bool([])); [1] == [1] (false → true); [True, 2].count(1) (0 → 1); {1: "x"}.get(True) (not found → “x”); remove(True) removes the first 1; the start/end arguments of index take effect ([1, 2, 1].index(1, 1) from 0 to 2); the values of range(True, 3) are numbers; 1 // 0.1 (10 → 9); "a" "b" (a → ab); a, b = "xy" (a was “xy” → x and y); what follows global x; x = 1 (0 → 1); None as a dict key (the same key as 0 → a key of its own); None in string methods and join (“0” → “None”); the index of a compound-assignment target (a[k()] += 1 calls k() twice → once). C — a static inside a function (f(); f(); f() from 1 to 3); the value of function-style int(c), float(c) and bool(c) on a one-character value is its character code (int('7') and float('7') from 7 to 55, int(' ') from 0 to 32, int('\t') from 0 to 9, bool('\0') from true to false, int(c) - 48 with c = ‘7’ from -41 to 7, int('A') from NaN to 65; the same as gcc/g++); a[i++] += 4 (v10.2.0 advanced i by 2 and wrote to the next element a[1] → i advances by 1 and a[0] is written). Forms whose error message changed: an unclosed bracket (x = (1⏎y = 2 from “expected ‘)’ but got ‘NEWLINE'” to “‘(‘ was never closed”, and if (1: likewise; with a mismatched closing bracket, x = [1, (2⏎, 3] gives “‘[‘ was never closed” and does not name the inner (); keyword arguments other than in .format (print(x, end="") from “expected ‘)’ but got ‘='” to “keyword argument ‘end=’ is not supported”); len(None) (“len(): 対象は list/str/dict のみ” — only list/str/dict — → “len(): 対象がありません” — there is nothing to measure; still Japanese-only and without a line number); continue/break in a program that runs once and at top level (the loop()-specific hint is gone); in C, x %=, 1 , and (int) at the end of the file (“expected ‘;’ but got …” → “unexpected token ‘EOF'”), for (int i = 0, (→ “expected ‘ID’ but got ‘EOF'”) and sizeof(unsigned long(x)) (“expected ‘)’ but got ‘(‘” → “… ‘long'”). What did not change: a name assigned inside a function without global overwrites an existing global of that name (an intentional design). In C, / is still true division ((int)7/2 is 3.5), types have no width ((uint8_t)300 is 300), a block { } does not create a scope, and print(true) still shows “true”. The items fixed in Stage BK and the differences from Python that remain are listed under “Learning-program language semantics” in §9 of the physics model notes (the 21 items fixed in v10.3.0 moved to “Differences from Python fixed in v10.3.0 (Stage BK)” with what remains of each written in the item, and the 9 items not fixed keep their v10.2.0 numbers). In addition, the permanent verification gates went from 79 to 80 (wf_bk1_interp2.mjs).
Verifying this release yourself
git clone https://github.com/RumiCar-group/RumiCar-Simulator.git
cd RumiCar-Simulator
node wf_run_all.mjs # the full assertion-gate suiteThe suite runs from a fresh clone with no dependencies beyond Node.js.
The real-browser checks under browser/ additionally need npm install and a display.
