Skip to content

orbitaldecay/euler8

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Euler8 Documentation


Preface

This is the documentation for Euler8 0.1. This software was designed and developed by Bob Forder (bob@forder.cc) for the Make a Console Jam during September of 2022. It is free software under the zlib license. All documentation and source code was written in Notepad.



What is this?

Perhaps most directly, Euler8 is a fantasy console. Think of a fantasy console as an emulator for a computer that was never built. More specifically, Euler8 is a stack based fantasy console which uses an extremely compact bytecode and features complex numbers as the native internal value representation. Euler8 doesn't support branching, and as such is not properly Turing complete. It is, however, a fully fledged finite state machine. It is capable of expressing more than simple combinational logic. If you don't know what this means, don't worry. It actually turns out not to matter much. Technically speaking, you're reading this on a finite state machine.

There are two seperate, but interdependent programs involved in using Euler8. The Euler8 assembler is a standalone console program that is used to convert Euler8 assembly code into Euler8 cartridges. The Euler8 virtual machine loads cartridges and executes them. This version of Euler8 only supports graphics. Input, sound, and other features may or may not be added at a later time.


Why did you do this?

My goal in designing Euler8 was twofold. First, I wanted to design a fantasy console that facillitated the creation of size optimized graphical programs. Second, I wanted to explore new classes and styles of size optimized programs. Traditionally, extremely small graphical programs have a small set of arithmetic and logical operations to perform on the available data. XOR patterns, feedback effects, and tunnel animations have been done many times by many people. I wanted to create a machine that encouraged the exploration of new ideas hitherto unseen in programs under 256 bytes in size.

I chose complex numbers as the internal representation because, mathematically speaking, the complex plane seemed to me to be the most natural choice for representing a two dimension viewport and is typically very space consuming on traditional architectures. Additionally, many beautiful visualizations can be expressed succinctly and efficiently in terms of operations on complex numbers. I chose to make Euler8 a stack machine because stack based operations are well known for having remarkably compact code density. This shoehorns in nicely with the already compact representation of many visuals in terms of complex numbers.

The final design decision which may seem peculiar is why I omitted branching and looping constructs from the language. There is an implicit pixel loop around each Euler8 program. Introducing Turing completeness to the language would create the possibility that some programs may never exit. Naively, this would cause the implicit loop to hang. As most small graphical effects don't require looping aside from the pixel loop, I decided to simply omit jumps and branches all together. This also allows the language to be more compact.


How does it work?

The Euler8 virtual machine is written in C++ and compiled for Windows. It is a viewport that takes a single command line parameter which specifies the cartridge to load. The cartridge is then executed in a loop. The Euler8 assembler is a console program that takes two arguments. The first argument is the filename of the source code. The second argument is the cartridge to be written to. On the Windows command line, e8asm program.asm cartridge.bin where program.asm is your source code and cartridge.bin is the name of the cartridge file that will be output.

The Euler8 assembly language can be learned in a short amount of time. Euler8 assembly code is written in reverse Polish notation (sorry, maybe one day I'll write an infix converter). That means, rather than writting add 3 2 or 3 + 2 to represent the sum of three and two, you'd write 3 2 add. The operation follows the operands. The language supports five constants, integer values, and 19 operations for a total of 26 operation codes.

A Euler8 program is a function which is called once per pixel per frame. The function is given access to three complex parameters and returns up to three complex values (but may return two, one, or none in which case the absent values are assumed to be zero). The function is given access to the parameters z, t, and s.

The z parameter is a complex number representing the coordinates of the current pixel. The real part encodes the X coordinate and the imaginary part encodes the Y coordinate. X and Y range between -1 and 1. The t parameter (time) is a purely real value that is the number of seconds the program has been running. Finally, the s parameter (state) is a value that was returned by the last iteration of the program on this pixel. This allows us to store some state for various purposes. The values z, t, and s are not passed on the stack, but they can be pushed to the stack by using the respective instructions. The argument stack is always empty when each iteration of the program begins.

The return value of an Euler8 program, however, is left on the stack. The top of the stack, when the function exits, encodes the red and green intensity to be written to the current pixel in its real and imaginary parts, respectively. The second number on the stack encodes the blue intensity to be written to the current pixel in it's real part (the imaginary component of the second number on the stack is ignored). RGB values are clamped to the range [0, 1]. Finally, the third number on the stack can encode anything and can be used by the next iteration of the function on this pixel (it is accessed via the s parameter mentioned previously).


Instruction listing

What follows is a complete listing of instructions for version 0.1. The argument stack contains complex numbers and each operation manipulates the argument stack. As mentioned previously, the program is executed once per pixel, per frame. The hexidecimal bytecode encodings have the most significant nibble first. The link text below contains descriptions of each instruction with some useful information. Note that many commands are only four bits in size. Eight bit commands need not be byte aligned.

InstructionAssembly codeBytecodeSize (bits)
No operationnop$04
Z parameterz$14
T parametert$24
S parameters$34
Pipi$44
Imaginary uniti$54
Immediate[-8,7]$X68
Complex conjugatecon$74
Complex argumentarg$84
Absolute valueabs$94
Additionadd$A4
Subtractionsub$B4
Multiplicationmul$C4
Divisiondiv$D4
Duplicatedup$E4
Imaginary componentim$0F8
Real componentre$1F8
Sinesin$2F8
Cosinecos$3F8
Arcsineasin$4F8
Arccosineacos$5F8
Exponentiationexp$6F8
Natural logarithmln$7F8
Powerpow$8F8
Square rootsqrt$9F8
Cosine plus i times sinecis$AF8

No operation

Does nothing.


Z Parameter

Push the z parameter onto the stack. The X and Y coordinates of the current pixel are stored in the real and imaginary parts, respectively. X and Y coordinates range from -1 to 1.


T Parameter

Push the t parameter onto the stack. The t parameter represents the amount of time in seconds since the program started.


S Parameter

Push the s parameter onto the stack. The s parameter is the third value left on the stack the last time the program executed on this pixel.


Pi

Push the mathematical constant Pi onto the stack (3.1415...).


Imaginary unit

Push the imaginary unit onto the stack.


Immediate

Push the four bit immediate xxxx following this instruction onto the stack as a signed integer. It is encoded as a purely real number.


Complex conjugate

Pop A. Push the complex conjugate of A onto the stack.


Complex argument

Pop A. Push the complex argument of A onto the stack.


Absolute value

Pop A. Push the absolute value of A onto the stack.


Addition

Pop B. Pop A. Push A + B onto the stack.


Subtraction

Pop B. Pop A. Push A - B onto the stack.


Multiplication

Pop B. Pop A. Push A * B onto the stack.


Division

Pop B. Pop A. Push A / B onto the stack.


Duplicate

Pop A. Push A. Push A.


Imaginary component

Pop A. Push the imaginary component of A onto the stack.


Real component

Pop A. Push the real component of A onto the stack.


Sine

Pop A. Push sin(A) onto the stack.


Cosine

Pop A. Push cos(A) onto the stack.


Arcsine

Pop A. Push asin(A) onto the stack.


Arccosine

Pop A. Push acos(A) onto the stack.


Exponentiation

Pop A. Push e^A onto the stack where ^ represents exponent and e is the base of the natural logarithm.


Natural logarithm

Pop A. Push ln(A) onto the stack.


Power

Pop B. Pop A. Push A^B onto the stack where ^ represents exponent.


Square root

Pop A. Push the positive square root of A onto the stack.


Cosine plus i times sine

Pop A. Push cos(A) + i*sin(A) onto the stack where i is the imaginary unit.


Copyright Bob Forder 2022. Released under Creative Commons CC0.

About

The Euler8 fantasy console

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C++ 65.6%
  • C 23.1%
  • Batchfile 6.1%
  • NSIS 5.0%
  • Assembly 0.2%