nathanielbabiak 2020-11-24 12:26 (Edited)
I thought someone might find these useful, figured I'd share my notes on how LowRes NX works (and common mistakes I keep making)...
The LowRes NX console performs operations in the order shown here:
() | ^ | - | */\ | MOD | +- | <>=<=> | NOT | AND | ORXOR | =
Note some operations share the same character but are contextually distinct:
Simple variables are not explicitly declared, but (as stated in the manual) they need to be initialized with a value before you can read from them... most of the time. The first exception is global variables, which are initialized to zero when GLOBAL
is encountered. The second exception is more nuanced: when the equal symbol (=) is encountered is when initialization to zero occurs, e.g., X=X+1
is a valid initialization. (This is useful inside nested loops with convoluted program flow.) (Note the console's developer says here the behavior isn't intentional.)
LowRes NX treats the evaluation of all numeric expressions, and variable values, as single-precision floating point (not 32 bit int
variables). The paramount consequence of this is: so-called integers are limited to 24 significant bits.
String length has no effective limit (besides clock cycles)
Arrays are limited to four dimensions and 2^15 elements total ('total' means multiplicative product of all dimensions)
If a variable is global and shares the name of a subprogram argument, then within that subprogram, access to the name will be local (not global).
The emulator can handle up to 256 simple variables and up to 256 array variables. Variables are added to the appropriate list at the time the declaration is first encountered by the emulator and are removed from the list when the emulator reaches END SUB
. (This applies to variables from the main program, global variables, subprogram local variables, and recursive subprograms.)
Subprogram arguments may be passed by reference (as stated in the manual). To force pass by value, encapsulate the argument within parenthesis (...)
. This forces the emulator to evaluate the argument as an expression, and as a bonus does not cost any clock cycles.
The manual includes the following text, but I've sorted it below a bit differently to emphasize the relations between the terms.
All graphics in LowRes NX are based on characters. A character is an 8x8-pixel image.
Sprites are independent objects, consisting of references to characters, which can be freely moved on the screen.
A background is a map of 32x32 characters, which is used for cell (i.e., text and tile) based maps or images. Each cell has the information of which character it references.
The display is composed of 3 layers, which are from back to front: Background 1, Background 0, and Sprites.
The manual's discussion of layers is incorrect. The manual's text is corrected with the additions and deletions marked here:
The display is composed of 3 layers, which are from back to front:
Each sprite and background cell has an attribute called "priority". By setting it, the cell or sprite will appear in front of the other 2 layers. on a higher display layer. Actually there are 6 layers, from back to front:
Background 1 (BG 1) - prio 0
Background 0 (BG 0) - prio 0
Sprites - prio 0
Background 1 (BG 1) - prio 1
Background 0 (BG 0) - prio 1
Sprites - prio 1
These videos explain the graphics for the historic console that inspired this fantasy console:
How Graphics worked on the Nintendo Game Boy by Modern Vintage Gamer
Super Mario Land 2 - Memory Exploration by Retro Game Mechanics Explained
These operations each round negative floats towards negative infinity:
INT
(as stated in the manual)These operations each round negative floats towards zero:
\
MOD
AND
OR
XOR
x
and y
within SPRITE n, x, y,[c]
IF
, UNTIL
, and WHILE
interpret expr=0
as false, and expr<>0
as true (and do not typecast float-to-int before interpretation).
NOT
can yield a negative value, and true evaluations return -1
(for example, X=-1
after evaluating X=1=1
).
Negative ints don't shift rightwards (x\2^n
) predictably. They're signed!
Negative ints shift leftwards (x*2^n
) predictably.
Positive ints shift leftwards (x\2^n
) or rightwards (x*2^n
) predictably.
Parenthesis don't count towards clock cycles, even when changing order of operations.
Parenthesis do count towards tokens, as does each line (even a commented line).
All constants are positive (negation and a constant would be 2 cc).
Memory modification counts number of bytes modified (as stated in the manual), but this is in-addition to counting the cost of commands and arguments.
PEEKW
, POKEW
, PEEKL
, and POKEL
don't count as memory modification.
CALL subprogram
costs 1 cycle. CALL subprogram(a,b,c,...)
costs 1+n cycles. SUB SUBPROGRAM(a,b,c,...)
costs 0 cycles. END SUB
costs 1 cycle.
READ
costs 1 cycle. READ a,b,c,...
costs 1+n cycles. RESTORE [label]` costs 1 cycle.
The search performed by INSTR
does not count as memory modification.
Rather than following the manual's "string creation and modification count 1 cycle per letter,", HEX$(n[,len])
and BIN$(n[,len])
costs are calculated assuming len=8
and len=16
, respectively.
Check out Cycle counter by Rilden to test your own instructions!
For-Next Loops.
FOR var=a TO b STEP s
commands
NEXT var
The initialization cycle cost is paid even if commands
are not executed:
CCvar=a
+ CCb
+ CCs
Where:
CCexpr
is the cycle cost of expr
If the loop's commands
execute n
times, the additional cycle cost is:
n
* ( 2 + CCb
+ CCs
)
Single-Line If-Then Statements.
IF expr THEN command [ELSE IF expr THEN command][ELSE IF ...] [ELSE command]
The cycle cost is:
CCIF
+ CCexpr
+ CCcommand
+ CCconfirm
Where:
CCIF
is 1 cycle
CCIF
, CCexpr
, and CCcommand
are only paid if the interpreter parses that portion of the line
CCconfirm
is 1 cycle and is only paid in one specific circumstance. This cost is only paid if the interpreter needs to determine the end of command
by parsing the subsequent ELSE
on the same line. If command
ends and is followed by end-of-line, this cost is not paid.
PRINT CHR$(10)
and TRACE CHR$(10)
produces a new line
ASC(CHR$(n))
returns a signed value (rather than unsigned)
PEEKW
returns a signed value while POKEW
takes a signed or unsigned value. (This is as-stated in the manual for both.)
VAL(a$)
can interpret a variety of values. The console internally uses this function.
Decimal -456.789
(as implied in the manual) and exponent -123.456E-3
Hexadecimal -0X789.ABC
and exponent -0X789.ABCP-3
Infinity: -INF
or -INFINITY
Not-a-Number: -NAN
Negative (-) may be replaced with positive (+) or omitted
Decimal point (.) may be omitted
E
exponent is 10^E
, where E
is decimal (i.e., scientific notation)
P
exponent is 2^P
, where P
is decimal (not hexadecimal)
The word and long POKEW
, POKEL
, PEEKW
, and PEEKL
write to RAM little end first. (Conceptually, this makes POKE a, VAL("0X"+hex$)
much easier to understand than POKEW
or POKEL
.)
SPRITE VIEW ON/OFF
shows or hides the complete sprite layer without changing any of the sprites' settings (as stated in the manual). And, while hidden, both SPRITE
instructions still work (the screen just doesn't show any sprites).
SPRITE OFF [n]
and SPRITE OFF a TO b
hide sprite(s) (as stated in the manual). This instruction sets the sprite position to coordinate -32, -32. This instruction also preserves the sprites character number and attributes.
For the COPY
instruction, the manual says: The source and the destination areas may overlap. The console copies the source area to a temporary, inaccessible buffer, then copies that buffer to the destination.
PRINT
includes a WAIT 2
instruction after scrolling and printing the first character on the bottom line
Additional ROM space may be mimicked (with sequential access only) using DATA
commands, containing strings of hex-values, use VAL("0X"+hex$)
to retrieve up to three bytes at a time.
The INSTR(
instruction does not use any clock cycles to perform the search.
(This means strings of search-data can be developed at run-time to increase speed.)
rilden 2020-11-24 16:40
Another thing I found out:
LowRes NX waits 2 frames when PRINT causes text to scroll up. So if you print a lot of lines, the text will scroll at 30 frames per second.
was8bit 2020-11-24 17:13
Unless you use PRINT TEXT$;
Then it only scrolls when it reaches the end of the screen
was8bit 2020-11-24 17:15
Plus, if you already use WAIT VBL in a DO LOOP, that will also add to the timing of things..
Timo 2020-11-26 18:39
"Boolean (AND/OR/NOT) operators treat any float as 0."
What do you mean? I believe both values are casted to integer and then the operator is applied. All logical operators are also binary operators by the way. That's why "true" is -1 (all bits set).
"VAL("0X"+NUMERALS$) converts hex."
This one is not on purpose. I just used the C function internally. I didn't know it does this :O
"Figure out display order of PRINT vs BG 0/1 and PRIO 0/1, but not yet..."
PRINT uses the current WINDOW. And the window command lets you set the BG.
nathanielbabiak 2020-11-27 00:23
Yep - my mistake. I only ever tested floats between 0 and 0.9999, never 1.0 or higher.
qwaffe 2020-11-27 05:28 (Edited)
this post can really benefit from some markdown formatting.
i thought they were random numbers at first.
nathanielbabiak 2020-11-27 16:03 (Edited)
How do I add monospace? I tried double back-quotes (``) but it didn't work :-(
qwaffe 2020-11-28 02:36 (Edited)
put the text inside the back-quotes like normal quotes on single lines. (can be in-line.)
but code blocks are also possible. this can be quite helpful (:
nathanielbabiak 2020-11-28 03:34 (Edited)
Timo, I had to look up that emoji! Can you keep the hex conversion feature and the TRACE CHR$(10) feature? It'd be really nice to make them official...
was8bit 2020-11-28 05:28
I kinda would like to see VAL("%10") =2 and not 0... but its prolly not worth the effort to put it in...
Timo 2020-11-28 08:50 (Edited)
CHR$(10) is as expected so it’s official.
VAL(“0xFF”) doesn’t really make sense in the BASIC world as 0x is not the way here to indicate hex (it’s #).
I will probably not remove it, but I won’t add a binary version.
The only reason I see for it would be doing binary operations using strings instead of bit operators (AND OR XOR), but this is bad for performance and I won’t do this favor for was8bit ;)
was8bit 2020-11-28 09:12
oh my poor bit-brain 0o0 ;)
qwaffe 2020-11-28 11:19
isn't the way to indicate hex in nx "$" and not "#"?
was8bit 2020-11-28 12:25 (Edited)
Corerect, # and $ are side by side on the keyboard, i am sure that was just a typo... just for the record....
Hexadecimal and binary notation can be used for number values:
$FF02
%11001011
Timo 2020-11-28 12:59
Sorry, yes it's $. Wasn't a typo, I really don't know how my language works :O
was8bit 2020-11-28 15:08
You are busy with real life, its ok :)