Bedrock is a portable 8-bit computer system that is designed to be straightforward to implement on a wide variety of computer architectures and form factors. The core architecture can be implemented by a solo programmer in a day, and the devices are designed to be implemented and connected as needed.
Programs written for Bedrock will be able to run on any platform for which the Bedrock system has been implemented.
All devices are optional, and can be omitted if the host system does not provide the required facilities.
0x0System device Provides information about the Bedrock system, and the ability to sleep, fork, and reset the system.
0x1Memory device Provides access to up to 16 MiB of additional working memory.
0x2Math device Provides access to multiplication, division, and coordinate-space conversion operations.
0x3Clock device Provides access to the current date, current time, and four countdown timers.
0x4Input device Provides access to a pointer device, a keyboard device, and up to four game controllers.
0x5Screen device Provides access to a two-layer screen with a sixteen colour palette.
0x6Tone device Provides a mechanism for synthesizing audio tones (unavailable, reserved for future revision).
0x7Sampler device Provides a mechanism for playing short audio samples (unavailable, reserved for future revision).
0x8Stream device Provides communications with local and networked systems.
0x9File device Provides access to a hierarchical file system.
0xAClipboard device Provides access to the system clipboard, and the ability to drag and drop data between programs.
0xBRegistry device Provides access to program arguments and a persistent key-value store.
The device slots 0xC, 0xD, 0xE, and 0xF are reserved for the private use of each Bedrock system implementation. Programs that access a custom device will not be portable across Bedrock system implementations.
A double is a 16-bit value, stored as a pair of bytes in big-endian byte order. All double values are treated as unsigned integers unless otherwise specified, and range from 0 to 65535.
When treated as a signed integer, the value of a double is encoded using two’s compliment and ranges from -32768 to 32767.
A port alias is a device port that acts as a copy of the previous port in the port table. Port aliases are listed with the name ‘aliased’ in the port table of a device. Accessing a port alias will have identical behaviour to accessing the aliased port.
A port group is a contiguous group of device ports that acts as a single larger port, exposing a value larger than a byte. The ports belonging to the group are listed with the name ‘grouped’ in the port table of the device, and are owned by the previous port in the table. Values are exposed using big-endian byte order, with the lowest-addressed port of the group exposing the highest-order byte of the value.
Some port groups perform atomic reads of the exposed value. Reading from the lowest-addressed port of the group will cache the current value of the port group, and reading from any port in the group will return the corresponding byte of the cached value. The initial cached read value of each port group is zero.
Some port groups perform atomic writes to the exposed value. Writing to any port in the group will set the corresponding byte of a cached value, and writing to the highest-addressed port of the group will commit the cached value, overwriting the exposed value. The initial cached write value of each port group is zero.
An implementation defined value is a value that depends on the behaviour or design of the underlying system. The value used is determined independently by each implementation.
Undefined behaviour is behaviour that occurs when performing an unsupported action, and can include any behaviour at all, including corruption of the system state. The behaviour exhibited is determined independently by each implementation.
The program memory is a 65536-byte block of readable and writeable memory that holds the current program. Each byte of memory is addressed by a 16-bit memory address. The initial value of each byte is zero.
Systems with fewer than 65536 bytes of program memory are partially supported. Reading from or writing to an unimplemented memory address will cause undefined behaviour.
To load a program into program memory, the value of each byte of memory is set to zero, and then each byte of the program is written sequentially to program memory starting from address zero. The program is truncated to fit the available memory.
Reading a byte from program memory will return the byte at the given address.
Reading a double from program memory will read the high byte of the double from the given address and the low byte from the following address. Reading a double from address 0xFFFF will cause undefined behaviour.
Writing a byte to program memory will write that byte to the given address.
Writing a double to program memory will write the high byte of the double to the given address and the low byte to the following address. Writing a double to address 0xFFFF will cause undefined behaviour.
A stack is a 256-byte block of readable and writeable memory that holds temporary data, and an associated 8-bit stack pointer. Each byte of stack memory is addressed by an 8-bit memory address. The initial value of each byte of stack memory is unspecified, and the initial value of each stack pointer is zero.
There are two stacks, called the working stack and the return stack.
Systems with fewer than 256 bytes of memory per stack are partially supported. Reading from or writing to an unimplemented memory address will cause undefined behaviour.
Pushing a byte to a stack will write that byte to the address referenced by the stack pointer, and then will increment the stack pointer by 1. Overflowing the stack pointer will cause undefined behaviour.
Pushing a double to a stack will push the high byte of the double to the stack, followed by the low byte.
Popping a byte from a stack will decrement the stack pointer by 1, and then will read and return the byte at the address referenced by the stack pointer. Underflowing the stack pointer will cause undefined behaviour.
Popping a double from a stack will pop the low byte of the double from the stack, followed by the high byte.
The device bus is an array of 256 readable and writeable ports that interface with external devices. Each port is addressed by an 8-bit port number. Each contiguous group of sixteen ports is called a slot. Each slot is addressed by a 4-bit slot number, and can connect to a single device.
Reading a byte from the device bus will send a read request to the given port, and then the device connected to that port will process the request and return a byte, with the system pausing until the request returns. The maximum duration of a read request is unlimited. If the port is not connected to a device, or if the connected device does not allow reading from that port, the request will return immediately with a value of zero.
Reading a double from the device bus will read the high byte of the double from the given port and then the low byte from the following port. Reading a double from port 0xFF will cause undefined behaviour.
Writing a byte to the device bus will send a write request containing that byte to the given port, and then the device connected to that port will process the request and return, with the system pausing until the request returns. The maximum duration of a write request is unlimited. If the port is not connected to a device, or if the connected device does not allow writing to that port, the request will return immediately.
Writing a double to the device bus will write the high byte of the double to the given port and then the low byte to the following port. Writing a double to port 0xFF will cause undefined behaviour.
The processor executes instructions from program memory using an associated 16-bit instruction pointer. The initial value of the instruction pointer is zero.
Each instruction is stored as an 8-bit value. The upper three bits of the value represent the three mode flags, and the lower five bits represent the operation to be performed.
To execute an instruction, a byte is read from the program memory address referenced by the instruction pointer, then the instruction pointer is incremented by 1, and then the instruction represented by that byte is performed. Overflowing the instruction pointer will cause undefined behaviour.
Bit 0x80 of the instruction byte represents the return mode flag. When set, the operation will use the return stack in place of the working stack, and the working stack in place of the return stack.
Bit 0x40 of the instruction byte represents the wide mode flag. When set, each value listed in the operation description that is neither a byte or a double will be a double, otherwise each such value will be a byte.
Bit 0x20 of the instruction byte represents the immediate mode flag. When set, the first byte or double that would be popped from a stack by the operation will instead be read from program memory, from the program memory address referenced by the instruction pointer. The instruction pointer will be incremented by 1 for each byte read. Overflowing the instruction pointer will cause undefined behaviour.
The lower five bits of an instruction represent the operation to be performed:
0x00 (HLT) If none of the mode flags are set, the system is halted. If only the immediate mode flag is set, no operation is performed. The remaining six instruction variants will trigger implementation defined debug behaviour.
0x01 (PSH) The value x is popped from the return stack, and then x is pushed to the working stack.
0x02 (POP) The value x is popped from the working stack.
0x03 (CPY) The value x is popped from the return stack, then x is pushed to the return stack, and then x is pushed to the working stack.
0x04 (DUP) The value x is popped from the working stack, then x is pushed to the working stack, and then x is pushed to the working stack.
0x05 (OVR) The value y is popped from the working stack, then the value x is popped from the working stack, then x is pushed to the working stack, then y is pushed to the working stack, and then x is pushed to the working stack.
0x06 (SWP) The value y is popped from the working stack, then the value x is popped from the working stack, then y is pushed to the working stack, and then x is pushed to the working stack.
0x07 (ROT) The value z is popped from the working stack, then the value y is popped from the working stack, then the value x is popped from the working stack, then y is pushed to the working stack, then z is pushed to the working stack, and then x is pushed to the working stack.
0x08 (JMP) The double a is popped from the working stack, and then a is written to the instruction pointer.
0x09 (JMS) The double a is popped from the working stack, then the double b is read from the instruction pointer, then b is pushed to the return stack, and then a is written to the instruction pointer.
0x0A (JCN) The double a is popped from the working stack, and then the value t is popped from the working stack. If t is not zero, a is written to the instruction pointer.
0x0B (JCS) The double a is popped from the working stack, and then the value t is popped from the working stack. If t is not zero, the double b is read from the instruction pointer, and then b is pushed to the return stack, and then a is written to the instruction pointer.
0x0C (LDA) The double a is popped from the working stack, then the value v is read from the program memory address referenced by a, and then v is pushed to the working stack.
0x0D (STA) The double a is popped from the working stack, then the value v is popped from the working stack, and then v is written to the program memory address referenced by a.
0x0E (LDD) The byte p is popped from the working stack, then the value v is read from the device bus port referenced by p, and then v is pushed to the working stack.
0x0F (STD) The byte p is popped from the working stack, then the value v is popped from the working stack, and then v is written to the device bus port referenced by p.
0x10 (ADD) The value y is popped from the working stack, then the value x is popped from the working stack, then the value r is calculated as the wrapped addition of y to x, and then r is pushed to the working stack.
0x11 (SUB) The value y is popped from the working stack, then the value x is popped from the working stack, then the value r is calculated as the wrapped subtraction of y from x, and then r is pushed to the working stack.
0x12 (INC) The value x is popped from the working stack, then the value r is calculated as the wrapped addition of 1 to x, and then r is pushed to the working stack.
0x13 (DEC) The value x is popped from the working stack, then the value r is calculated as the wrapped subtraction of 1 from x, and then r is pushed to the working stack.
0x14 (LTH) The value y is popped from the working stack, and then the value x is popped from the working stack. If x is less than y, the byte 0xFF is pushed to the working stack, otherwise the byte 0x00 is pushed to the working stack.
0x15 (GTH) The value y is popped from the working stack, and then the value x is popped from the working stack. If x is greater than y, the byte 0xFF is pushed to the working stack, otherwise the byte 0x00 is pushed to the working stack.
0x16 (EQU) The value y is popped from the working stack, and then the value x is popped from the working stack. If x is equal to y, the byte 0xFF is pushed to the working stack, otherwise the byte 0x00 is pushed to the working stack.
0x17 (NQK) The value y is popped from the working stack, then the value x is popped from the working stack, then x is pushed to the working stack, and then y is pushed to the working stack. If x is not equal to y, the byte 0xFF is pushed to the working stack, otherwise the byte 0x00 is pushed to the working stack.
0x18 (SHL) The byte y is popped from the working stack, then the value x is popped from the working stack, then the value r is calculated by shifting the bits of x leftwards y times, and then r is pushed to the working stack. To shift the bits of a value leftwards, each bit of the value is set to the original state of the next lower bit, and the lowest bit is unset.
0x19 (SHR) The byte y is popped from the working stack, then the value x is popped from the working stack, then the value r is calculated by shifting the bits of x rightwards y times, and then r is pushed to the working stack. To shift the bits of a value rightwards, each bit of the value is set to the original state of the next higher bit, and the highest bit is unset.
0x1A (ROL) The byte y is popped from the working stack, then the value x is popped from the working stack, then the value r is calculated by rotating the bits of x leftwards y times, and then r is pushed to the working stack. To rotate the bits of a value leftwards, each bit of the value is set to the original state of the next lower bit, and the lowest bit is set to the original state of the highest bit.
0x1B (ROR) The byte y is popped from the working stack, then the value x is popped from the working stack, then the value r is calculated by rotating the bits of the value x rightwards y times, and then r is pushed to the working stack. To rotate the bits of a value rightwards, each bit of the value is set to the original state of the next higher bit, and the highest bit is set to the original state of the lowest bit.
0x1C (IOR) The value y is popped from the working stack, then the value x is popped from the working stack, then the value r is calculated by setting each bit only if at least one of the corresponding bits of x and y is set, and then r is pushed to the working stack.
0x1D (XOR) The value y is popped from the working stack, then the value x is popped from the working stack, then the value r is calculated by setting each bit only if exactly one of the corresponding bits of x and y is set, and then r is pushed to the working stack.
0x1E (AND) The value y is popped from the working stack, then the value x is popped from the working stack, then the value r is calculated by setting each bit only if both of the corresponding bits of x and y are set, and then r is pushed to the working stack.
0x1F (NOT) The value x is popped from the working stack, then the value r is calculated by setting each bit only if the corresponding bit of x is not set, and then r is pushed to the working stack.
This section specifies the standard assembler used to assemble Bedrock programs.
The assembler takes a textual source file as input and returns a binary program file as output. If the program is invalid according to the rules of the assembler, no program file will be created.
A source file is a sequence of zero or more characters representing the source code of a Bedrock program, where each character is a Unicode scalar value. The character encoding and line-ending format are implementation defined. The file extension used for source files is .brc.
A token is a sequence of one or more characters representing a language element. Each token assembles to a sequence of zero or more bytes. The address of a token is equal to the number of bytes assembled before it in the program file.
The source file is parsed as a list of zero or more tokens by iterating over the characters in the file, with characters being consumed from the sequence to form tokens according to the following rules:
If no token is in progress, characters in the range U+0000 to U+0020 are ignored.
The characters ', ", and ( begin a new span-style token. Characters are consumed up to and including the matching terminator character, which ends the token. The terminators for the characters ', ", ( are ', ", ), respectively. The program is invalid if the end of the source file is reached before the matching terminator is found.
All other characters begin a new word-style token. If the character is ), [, ], {, }, ;, or :, no further characters are consumed by this token. Otherwise, characters are consumed up to and including the next : character, or up to and excluding the next (, ), [, ], {, }, ;, or character in the range U+0000 to U+0020, or until the end of the source file is reached, whichever comes first.
The list of tokens is then assembled sequentially into program bytes according to the description of the matching language element for each token. If the program is valid, the assembled bytes are returned as the program file.
An opening block delimiter token is a single { character. A closing block delimiter token is a single } character. Each closing block delimiter is matched with the closest previous unmatched opening block delimiter. An opening block delimiter assembles to a double with value equal to the address of the matching closing block delimiter. A closing block delimiter assembles to nothing.
The program is invalid if it contains an unmatched block delimiter, or if a block delimiter inside a macro definition is matched with a block delimiter outside that definition, or if a closing block delimiter has an address greater than 0xFFFF.
A global label definition token is an @ character, followed by zero or more characters called the label identifier. The name of a global label definition is given by the label identifier. It assembles to nothing.
A local label definition token is an & character, followed by zero or more characters called the label identifier. The name of a local label definition is the name of the closest previous global label definition (if any), followed by a / character, followed by the label identifier. It assembles to nothing.
The program is invalid if a label definition shares a name with another label or macro definition, or if the name of a label definition is longer than 63 characters, or if a label definition has an address greater than 0xFFFF.
The maximum number of label definitions allowed in a program is implementation defined.
A macro definition token is a % character, followed by zero or more characters called the macro identifier. The name of a macro definition is given by the macro identifier. The body of a macro definition is the list of zero or more tokens that follows the macro definition token, up to and excluding the next macro definition terminator token. These body tokens are collected into the macro definition for later use, and are not assembled. The macro definition terminator is matched with the macro definition. The macro definition assembles to nothing.
A macro definition terminator token is a single ; character. It assembles to nothing.
The program is invalid if it contains an unmatched macro definition or macro definition terminator, or if a macro definition shares a name with another label or macro definition, or if the name of a macro definition is longer than 63 characters, or if the body of a macro definition contains a label or macro definition token.
The maximum number of macro definitions allowed in a program is implementation defined.
A padding token is a # character, followed by zero or more characters called the pad value. It assembles to a number of zero bytes equal to the pad value as interpreted as a hexadecimal value.
The program is invalid if the pad value does not contain exactly two or four characters, or if the pad value contains any character that is not in the range 0 to 9, A to F, and a to f.
A raw string token is a ' character, followed by zero or more characters called the string content, followed by a ' character. It assembles to the string content as a UTF-8 encoded byte sequence.
A terminated string token is a " character, followed by zero or more characters called the string content, followed by a " character. It assembles to the string content as a UTF-8 encoded byte sequence, followed by a zero byte.
A byte literal token is exactly two characters long, where each character is in the range 0 to 9, A to F, and a to f. It assembles to one byte with value equal to the token as interpreted as a hexadecimal value.
A double literal token is exactly four characters long, where each character is in the range 0 to 9, A to F, and a to f. It assembles to a double with value equal to the token as interpreted as a hexadecimal value.
A symbol token is an optional ~ character, followed by zero or more characters called the symbol identifier. Any token that doesn’t already represent another language element is a symbol token. If the token begins with a ~ character, the name of the symbol is the name of the closest previous global label definition (if any), followed by a / character, followed by the symbol identifier. Otherwise, the name of the symbol is given by the symbol identifier. The name of a symbol in the body of a macro definition is determined once it is collected into the macro definition.
If the symbol name is equal to the name of a label definition, the symbol assembles to a double with value equal to the address of that label definition. The label definition can come either before or after the symbol token in the source file.
If the symbol name is equal to the name of a macro definition, the symbol token is replaced with a copy of the body of the macro definition, which is then assembled recursively. The maximum recursion depth is implementation defined. The macro definition must come before the symbol token in the source file.
The program is invalid if the name of a symbol is longer than 63 characters, or if the name is not equal to the name of a label or macro definition, or if the name is equal to the name of a macro definition that comes after the symbol token in the source file.
The presence of the 10-byte identifier E8 00 18 42 45 44 52 4F 43 4B at the beginning of a file indicates that the file is a Bedrock program with metadata, and that the following 14 bytes of the file can be parsed as a list of pointers to metadata values.
Each pointer in the list is a double that represents the address of the first byte of the associated metadata value. An address of zero indicates that no value has been provided.
The name value is a UTF-8 encoded string containing the name and version of the program, followed by a zero byte. The string format is the program name, followed by the forward-slash character U+002F, followed by the program version. The program name and version must not contain the forward-slash character U+002F or any character in the range U+0000 to U+001F. The maximum length of the name value is 255 bytes, excluding the zero byte.
The name value is addressed by the pointer at address 0x000A.
The author list is a UTF-8 encoded string containing a list of names of the authors of the program, followed by a zero byte. The string format is a concatenated list of names, with every name but the last followed by the newline character U+000A. The name of each author must not contain any character in the range U+0000 to U+001F. The maximum length of each name is 255 bytes, excluding the newline character and the zero byte, and the maximum number of names in the list is 16.
The author list is addressed by the pointer at address 0x000C.
The description value is a UTF-8 encoded string containing a short description of the program, followed by a zero byte. The program description must not contain any character in the range U+0000 to U+001F. The maximum length of the description value is 255 bytes, excluding the zero byte.
The description value is addressed by the pointer at address 0x000E.
A colour is a double that represents a 12-bit RGB colour value.
The highest four bits of the double are ignored, the next four bits represent the intensity of the red channel, the next four bits represent the intensity of the green channel, and the lowest four bits represent the intensity of the blue channel.
A sprite is a sequence of 8 bytes that represents an 8x8 pixel monochrome image.
Each byte in the sequence represents a row of the sprite, ordered top to bottom. Each bit of the byte represents a pixel in the row, ordered left to right, from highest bit to lowest. If a bit is set, the corresponding pixel will be drawn using the foreground colour, otherwise it will be drawn using the background colour. If a foreground or background colour hasn’t been provided, the colour to use as a replacement is implementation defined.
This section specifies the system device of the Bedrock computer system.
The system device connects to slot 0x0 of the device bus. It provides information about the Bedrock system, and the ability to sleep, fork, and reset the system.
Writing a value to this port group will perform an atomic write, putting the system to sleep on commit. The value written is a device list representing the list of devices that are allowed to wake the system from sleep. When a wake flag associated with one of those devices has been set, that flag will be cleared, the value of the wake port will be set to the slot number of that device, the system will wake from sleep, and then this write request will return. While the system is asleep, no further instructions will be executed.
If more than one wake flag associated with a device in the list is set, the flag associated with the device that least recently woke the system from sleep will be chosen. The system device will be chosen only if no other flag is set.
Reading from this port will return the slot number of the device that most recently woke the system from sleep. The initial value of this port is zero.
Writing a non-zero value to this port will fork the Bedrock system. A new instance of the Bedrock system will be created with separate devices and with all values set to their initial states, and then the program memory of the current instance will be copied over to the new instance. If this system does not support multiple instances, or if a new instance could not be created, then the current instance will be reset as if by writing a zero value to this port.
Writing a zero value to this port will reset the Bedrock system. The instruction pointer and stack pointers will be set to zero, and all devices will be reset to their initial states. The program memory will not be affected.
Each device name port is associated with a custom slot on the device bus (slots 0xC to 0xF), and a text buffer containing the name of the device connected to that slot. If no device is connected, the device name will be an empty string. The maximum length of the text buffer string is 255 bytes, excluding the zero byte.
Reading from one of these ports will read and return a byte from the associated text buffer. Writing any value to one of these ports will set the pointer of the associated text buffer to zero.
This port is associated with a text buffer containing the name and version of this Bedrock system implementation. The format of the text buffer string is the system name, followed by the forward-slash character U+002F, followed by the system version. The system name and version must not contain the forward-slash character U+002F or any character in the range U+0000 to U+001F. The maximum length of the text buffer string is 255 bytes, excluding the zero byte.
Reading from this port will read and return a byte from the associated text buffer. Writing any value to this port will set the pointer of the associated text buffer to zero.
This port is associated with a text buffer containing a list of names of the authors of this Bedrock system implementation. The format of the text buffer string is a concatenated list of names, with every name but the last followed by the newline character U+000A. The name of each author must not contain any character in the range U+0000 to U+001F. The maximum length of each name is 255 bytes, excluding the newline character and the zero byte, and the maximum number of names in the list is 16.
Reading from this port will read and return a byte from the associated text buffer. Writing any value will set the pointer of the associated text buffer to zero.
A device list is a 16-bit value representing a list of devices. Each bit of the value (from highest bit to lowest) represents a slot on the device bus (from lowest slot to highest), with a bit being set only if the list contains the device connected to the corresponding slot.
A wake flag is a 1-bit value associated with a slot on the device bus. A wake flag is set when a wake request is received from the device connected to that slot. A device will send a wake request to the system device according to the ‘wake behaviour’ section in the specification for that device. The initial state of each wake flag is unset.
This device contains sixteen wake flags: one for each of the sixteen slots on the device bus.
A text buffer is a UTF-8 encoded string terminated by a zero byte, and an associated pointer to a byte in the string. The initial value of the pointer is zero.
Reading a byte from a text buffer will return the byte of the string referenced by the pointer, and then will increment the pointer by 1. Reading past the terminating zero byte will cause undefined behaviour.
This device contains six text buffers: one for the name port, one for the authors port, and one for each of the four device name ports.
Reading from this port group will return the number of pages currently allocated.
Writing to this port group will perform an atomic write, requesting on commit that the number of pages allocated be changed to the value written. Pages will be sequentially allocated or deallocated from the end of allocated memory until the requested value is reached or until an implementation defined minimum or maximum value has been reached.
Each page offset port group is associated with a read-write head.
Reading from one of these port groups will return the page offset of the associated head. Writing to one of these port groups will set the page offset to the value written.
Each address offset port group is associated with a read-write head.
Reading from one of these port groups will return the address offset of the associated head. Writing to one of these port groups will set the address offset to the value written.
Reading from one of these ports will read and return a byte from the memory address referenced by that read-write head, and then will increment the address offset by 1, wrapping to zero on overflow. Reading from an unallocated memory address will cause undefined behaviour.
Writing to one of these ports will write the byte to the memory address referenced by that read-write head, and then will increment the address offset by 1, wrapping to zero on overflow. Writing to an unallocated memory address will cause undefined behaviour.
Writing to this port group will perform an atomic write, requesting on commit that a number of pages be copied equal to the value written. The initial source page is addressed by the page offset of the second read-write head. The initial destination page is addressed by the page offset of the first read-write head.
To copy pages of memory, the contents of the source page will be copied to the destination page, the page directly following the source page will become the new source page, and the page directly following the destination page will become the new destination page, repeating until the requested number of pages has been copied. Copying to or from an unallocated page will cause undefined behaviour.
A page is a 256-byte block of readable and writeable memory that must be allocated before it can be accessed. Memory is allocated as a continuous array of bytes, starting from page zero. Each page is addressed by a 16-bit page address, and each byte of memory is addressed by a 24-bit memory address. The value of each byte on an unallocated page is unspecified. Each time a page is allocated, the value of each byte on that page becomes zero.
The initial number of pages allocated is implementation defined. The minimum and maximum number of pages that can be allocated is implementation defined. The maximum number of pages allocated cannot exceed 65535.
A read-write head is a pointer used to access allocated memory. Each head has an associated 16-bit page offset and 16-bit address offset. The initial value of each offset is zero. The memory address referenced by a head is calculated by multiplying the page offset by 256 and then adding the address offset, causing undefined behaviour if the result exceeds the range of an unsigned 24-bit integer.
This device contains two read-write heads: one head is controlled using the first half of the device, and the other head is controlled using the second half.
Reading from this port group will return the x coordinate of the polar point after conversion to the cartesian coordinate system. If the coordinate exceeds the range of a signed 16-bit integer, the value returned will be zero. The x coordinate is calculated by taking the signed floor of cos(\frac{2 \pi t}{65536}) \times r, where the cosine function takes an angle in radians.
Writing to this port group will set the x coordinate of the cartesian point to the value written.
Reading from this port group will return the y coordinate of the polar point after conversion to the cartesian coordinate system. If the coordinate exceeds the range of a signed 16-bit integer, the value returned will be zero. The y coordinate is calculated by taking the signed floor of sin(\frac{2 \pi t}{65536}) \times r, where the sine function takes an angle in radians.
Writing to this port group will set the y coordinate of the cartesian point to the value written.
Reading from this port group will return the r coordinate of the cartesian point after conversion to the polar coordinate system. The r coordinate is calculated by taking the unsigned floor of \sqrt{x^2 + y^2}.
Writing to this port group will set the r coordinate of the polar point to the value written.
Reading from this port group will return the t coordinate of the cartesian point after conversion to the polar coordinate system. If the x and y coordinates are both zero, the value returned will be zero. The t coordinate is calculated by taking the unsigned floor of atan2(y,x) \times \frac{65536}{2\pi}.
Writing to this port group will set the t coordinate of the polar point to the value written.
Reading from this port group will return the quotient of the division of the x operand by the y operand. If the y operand is zero, the value returned will be zero.
Reading from this port group will return the remainder of the division of the x operand by the y operand. If the y operand is zero, the value returned will be zero.
The cartesian point is a pair of coordinates representing a two-dimensional point in the cartesian coordinate system, where each coordinate is a signed 16-bit integer. The x coordinate represents the signed distance from the origin along the horizontal axis, and the y coordinate represents the signed distance from the origin along the vertical axis. The initial value of each coordinate is zero.
The polar point is a pair of coordinates representing a two-dimensional point in the polar coordinate system, where each coordinate is an unsigned 16-bit integer. The r coordinate represents the unsigned distance from the origin, and the t coordinate represents the angle anti-clockwise around the origin from the positive horizontal axis. The angle unit is \frac{2\pi}{65536} radians. The initial value of each coordinate is zero.
Reading from this port will return the number of full years elapsed since the start of the year 2000. The value returned after the year 2255 is implementation defined.
Writing to this port will set the year on the clock to the value written, if supported by the system. Invalid values are ignored.
Reading from this port will return the number of full seconds elapsed since the start of the minute. The maximum value returned by this port is 0x3B. The value returned during a leap second is implementation defined.
Writing to this port will set the second on the clock to the value written, if supported by the system. Invalid values are ignored.
Reading from this port group will perform an atomic read, returning the number of full 1/256 second durations elapsed since the system was last reset. The value will wrap to zero on overflow.
Reading from one of these port groups will perform an atomic read, returning the current value of the associated timer. Writing to one of these port groups will perform an atomic write, setting the timer to the value written on commit.
A countdown timer is an unsigned 16-bit integer. The value of a timer will decrement by 1 for each full 1/256 second duration elapsed, stopping at zero. The initial value of each timer is zero.
This device contains four countdown timers: one for each of the four timer port groups.
Reading from this port will return 0xFF if the pointer is active, or 0x00 otherwise.
The pointer is active only while the pointer position is within screen bounds, or while a pointer button is still being held that was pressed while within screen bounds. If the pointer device is a touchscreen, it is only active while touched.
Reading from this port will return a button list representing the list of buttons that are currently held down on any pointer device, mapped according to the following table:
Bit
Button
0x80
Primary
0x40
Secondary
0x20
Tertiary
0x10
Auxiliary 1
0x08
Auxiliary 2
0x04
Auxiliary 3
0x02
Auxiliary 4
0x01
Auxiliary 5
If the pointer device is a right-handed mouse, primary is the left-mouse button, secondary is the right-mouse button, and tertiary is the middle-mouse button. For a left-handed mouse, primary and secondary will be reversed. If the pointer device is a touchscreen, primary is the touchscreen surface.
Reading from this port will remove and return a byte from the front of the character queue. If the character queue is empty, the value returned will be zero.
Writing to this port will clear the character queue. If a non-zero byte was written and the system uses an on-screen keyboard for text entry, the on-screen keyboard will be made available. If a zero byte was written, the on-screen keyboard will be hidden.
Reading from this port will return a button list representing the list of modifier keys that are currently held down on any keyboard device, mapped according to the following table:
Bit
Button
0x80
Control
0x40
Shift
0x20
Alt
0x10
Super
0x08
Auxiliary 1
0x04
Auxiliary 2
0x02
Auxiliary 3
0x01
Auxiliary 4
The auxiliary modifier keys are implementation defined.
Each controller port is associated with a game controller, ordered from least-recently to most-recently connected. On systems where a physical keyboard is the primary input device, the first controller will also be emulated by the keyboard, with the port returning the union of the emulated and physical controller states. The mapping of keyboard buttons to controller buttons is implementation defined.
Reading from one of these ports will return a button list representing the list of buttons that are currently held down on the associated game controller, mapped according to the following table:
Bit
Button
0x80
Up
0x40
Down
0x20
Left
0x10
Right
0x08
Confirm
0x04
Cancel
0x02
Primary
0x01
Secondary
All analog sticks and directional pads on a game controller map to the up, down, left, and right buttons.
The affirmative face button maps to confirm, the negative face button maps to cancel, the primary action face button maps to primary, and the secondary action face button maps to secondary. The face buttons used by popular game consoles map to controller buttons according to the following table:
The pointer position is a pair of coordinates representing the last known position of the pointer on the screen, where each coordinate is a signed 16-bit integer. The initial value of each coordinate is zero. The pointer position will update only while the pointer is active.
The pointer position uses the same coordinate space as the screen device: the distance unit is one pixel, the horizontal coordinate increases rightwards, the vertical coordinate increases downwards, and the origin is the top-left pixel of the screen device. The pointer position is within screen bounds when the horizontal position is at least zero and is less than the screen width, and when the vertical position is at least zero and is less than the screen height.
Each scroll delta is a signed 16-bit integer representing the distance scrolled along the respective axis since the delta was last read. The initial value of each delta is zero.
The horizontal scroll delta increases when scrolling rightwards (the action that would slide content leftwards) and decreases when scrolling leftwards. The vertical scroll delta increases when scrolling downwards (the action that would slide content upwards) and decreases when scrolling upwards. If a value would overflow or underflow, it will instead saturate at bounds.
For a device with discrete scrolling, such as a traditional scroll wheel, the scroll unit is equal to one increment of the scroll device. For a device with continuous scrolling, such as a touchpad, the scroll unit is implementation defined.
A button list is an 8-bit value representing a list of held buttons. Each bit of the value represents a button on an input device, with a bit being set only if the corresponding button is currently held down.
The character queue is a first-in first-out byte queue that buffers received characters. The maximum length of the queue is implementation defined.
When a character is received from a keyboard device, the character is encoded as a UTF-8 byte sequence and then all bytes are pushed to the back of the queue. If the queue has insufficient capacity for the full sequence, no bytes will be pushed.
The enter key (called return on Apple keyboards) is represented by the Unicode code point U+000A. The backspace key (called delete on Apple keyboards) is represented by the Unicode code point U+0008. The delete key is represented by the Unicode code point U+007F.
Reading from this port group will return the horizontal coordinate of the screen cursor. Writing to this port group will set the horizontal coordinate to the value written.
Reading from this port group will return the vertical coordinate of the screen cursor. Writing to this port group will set the vertical coordinate to the value written.
Reading from this port group will perform an atomic read, returning the width of the screen in pixels. If no screen is available, the value returned will be zero.
Writing to this port group will perform an atomic write, requesting that the screen width be changed and then locked to the value written on commit. If the screen is resizeable, the screen width will be set to the requested value, or to the closest value supported by the system, and will be prevented from being resized other than by a write to this port. The values of the screen pixels after a resize are implementation defined.
Reading from this port group will perform an atomic read, returning the height of the screen in pixels. If no screen is available, the value returned will be zero.
Writing to this port group will perform an atomic write, requesting that the screen height be changed and then locked to the value written on commit. If the screen is resizeable, the screen height will be set to the requested value, or to the closest value supported by the system, and will be prevented from being resized other than by a write to this port. The values of the screen pixels after a resize are implementation defined.
Writing to this port group will perform an atomic write, writing a colour to the screen palette on commit. The highest four bits of the value represents the palette index to be overwritten, and the remaining 4-bit groups each represent the intensities of the red, green, and blue colour channels:
Reading from this port will return a 16-bit value representing the palette indices of each of the four selected colours. Writing to this port will change the selected colours to the palette indices represented by the value written.
Each 4-bit group in the value represents the palette index of one of the four selected colours:
Writing to this port will perform a draw operation according to the byte written. The upper four bits determine the operation to be performed, according to the following table:
Value
Operation
0x0_
Set the colour of a pixel in the background layer.
0x1_
Draw a 1-bit sprite to the background layer.
0x2_
Fill the background layer with a solid colour.
0x3_
Draw a 2-bit sprite to the background layer.
0x4_
Draw a solid line to the background layer.
0x5_
Draw a 1-bit textured line to the background layer.
0x6_
Draw a solid rectangle to the background layer.
0x7_
Draw a 1-bit textured rectangle to the background layer.
0x8_
Set the colour of a pixel in the foreground layer.
0x9_
Draw a 1-bit sprite to the foreground layer.
0xA_
Fill the foreground layer with a solid colour.
0xB_
Draw a 2-bit sprite to the foreground layer.
0xC_
Draw a solid line to the foreground layer.
0xD_
Draw a 1-bit textured line to the foreground layer.
0xE_
Draw a solid rectangle to the foreground layer.
0xF_
Draw a 1-bit textured rectangle to the foreground layer.
If bit 0x10 of the byte is unset, the lower four bits of the byte will represent the palette index of the colour to use for the operation. Otherwise, the lower four bits will represent the transformation value to use for the operation.
Each bit of the transformation value represents a transformation to apply to the sprite used by the operation, with a transformation being applied only if the corresponding bit is set. The ‘flip x’ and ‘flip y’ transformations are applied before the ‘flip diagonal’ transformation.
Bit
Name
Description
0x_1
Flip X
Flip sprite across vertical center-line.
0x_2
Flip Y
Flip sprite across horizontal center-line.
0x_4
Flip diagonal
Flip sprite across top-left bottom-right diagonal.
0x_8
Transparent
Don’t draw sprite pixels that use sprite colour 0.
The current cursor position is the current position of the screen cursor. The previous cursor position is the position of the screen cursor when this port was last written to, or the origin if this port had not previously been written to.
When setting the colour of a pixel on a layer, the pixel at the current cursor position on that layer is changed to the given colour.
When filling a layer with a solid colour, each pixel on that layer is changed to the given colour.
When drawing a sprite to a layer, the sprite is read from the sprite buffer, then the given transformations are applied to the sprite, and then each pixel of the sprite is drawn to that layer using the selected colours, with the top-left pixel of the sprite being placed at the current cursor position.
When drawing a line to a layer, each pixel on that layer that belongs to the shortest and straightest line connecting the previous and current cursor positions is affected. Line pixels can connect diagonally or orthogonally. If there are multiple possible lines, the choice is implementation defined.
When drawing a rectangle to a layer, each pixel on that layer that belongs to the smallest axis-aligned rectangle containing both the previous and current cursor positions is affected.
When drawing a solid line or rectangle, each pixel belonging to the line or rectangle is changed to the given colour.
When drawing a textured line or rectangle, a 1-bit sprite is read from the sprite buffer, then the given transformations are applied to the sprite, and then each pixel belonging to the line or rectangle is drawn as the corresponding sprite pixel, as if the sprite were tiled outwards from the top-left corner of the screen.
Writing to this port will move the screen cursor a number of pixels in a given direction according to the byte written. The upper two bits determine the direction to move, and the lower six bits determine the distance:
Bit
Name
0x80
Negative
0x40
Vertical
0x3F
Distance
If bit 0x40 is set, the vertical coordinate of the screen cursor will be modified, else the horizontal coordinate will be modified. If bit 0x80 is set, the distance will be subtracted from the coordinate, else the distance will be added. The coordinate value wraps on overflow.
The screen has a palette of sixteen colours, with each colour in the palette assigned an index from zero to fifteen. Each colour in the palette is selected from a 12-bit RGB colour space. Changing the colour stored at a palette index will change the colour of all pixels on the screen that use that index.
The initial value of each colour in the palette is implementation defined.
If the system has a fixed-palette screen with two colours, every second palette index will map to the on colour and every other index will map to the off colour. For three colours, every fourth index will map instead to the grey colour. For four colours, the third and four index of every four will map instead to the dim and bright colours. For more than four colours, the mapping is implementation defined.
The screen contains two overlapping layers of pixels, called the foreground layer and the background layer. The colour of each pixel on each layer is represented by a palette index. When rendering the screen, each pixel of the screen is rendered using the colour of the foreground pixel if it is not palette index zero, otherwise it is rendered using the colour of the background pixel.
The initial colour of each pixel on each layer of the screen is palette index zero.
The screen cursor is a pair of coordinates representing the position of a pixel on the screen, where each coordinate is a signed 16-bit integer. The horizontal coordinate increases rightwards, the vertical coordinate increases downwards, and the origin is the top-left pixel of the screen. The initial value of each coordinate is zero.
The sprite buffer is a 16-byte block of readable and writeable memory and an associated 4-bit pointer. The initial value of each byte of buffer memory is zero, and the initial value of the pointer is unspecified.
Pushing a byte into the sprite buffer will write that byte to the buffer address referenced by the pointer, and then will increment the pointer by 1, wrapping to zero on overflow.
The sprite buffer is treated as a circular buffer that is split into two 8-byte planes by the current pointer value. The high plane consists of the eight bytes starting from the address referenced by the pointer, and the low plane consists of the remaining eight bytes.
A sprite is an 8x8 pixel image. A 1-bit sprite is encoded as a sequence of eight bytes, and a 2-bit sprite is encoded as a sequence of sixteen bytes.
Sprites are read from the sprite buffer. Each plane of the sprite buffer contains 1 bit of data per sprite pixel. Each byte of a plane maps to a row of the sprite, ordered top to bottom, and each bit of a byte maps to a pixel in the row, ordered left to right, from highest bit to lowest.
A 1-bit sprite is drawn using the low plane of the sprite buffer, with each pixel corresponding with a 1-bit colour value.
A 2-bit sprite is drawn using both planes of the sprite buffer, with each pixel corresponding with a 2-bit colour value. The high plane determines the high bit of each colour value and the low plane defines the low bit.
Each pixel of a 1-bit or 2-bit sprite is drawn using the selected colour that corresponds with the colour value of that pixel:
Each input connection port is associated with the input channel of a bytestream.
Reading from one of these ports will return 0xFF if the associated input channel is connected to another system, or 0x00 otherwise. Writing a byte to the remote input connection port will write that byte to the remote path buffer.
Each output connection port is associated with the output channel of a bytestream.
Reading from one of these ports will return 0xFF if the associated output channel is connected to another system, or 0x00 otherwise. Writing a byte to the remote output connection port will write that byte to the remote path buffer.
Each input transmission port is associated with the input channel of a bytestream.
Reading from one of these ports will return 0xFF if the transmission control flag of the associated input channel is set, or 0x00 otherwise. Writing any value to one of these ports will clear the buffer queue of the associated input channel and then will set the transmission control flag of that channel.
Each output transmission port is associated with the output channel of a bytestream.
Reading from one of these ports will return 0xFF if the transmission control flag of the associated output channel is set, or 0x00 otherwise. Writing any value to one of these ports will clear the transmission control flag of the associated output channel.
Each input queue port is associated with the input channel of a bytestream.
Reading from one of these ports will return the number of bytes waiting in the buffer queue of the associated input channel. If the queue contains more than 255 bytes, the value returned will be 0xFF.
Writing any value to one of these ports will drop the incoming transmission. This will remove all bytes from the buffer queue of the associated input channel, and then will drop all newly received bytes on that channel until the transmission control flag has been cleared.
Each output queue port is associated with the output channel of a bytestream.
Reading from one of these ports will return the number of bytes of free space in the buffer queue of the associated output channel. If the queue has more than 255 bytes of free space, the value returned will be 0xFF.
Reading from one of these ports will remove and return a byte from the front of the input queue of the associated bytestream. If the queue is empty, the value returned will be zero.
Writing a byte to one of these ports will push that byte to the back of the output queue of the associated bytestream. The byte will be dropped instead if the output channel is disconnected, the output queue is full, or the transmission control flag of the output channel is not set.
A bytestream is a bi-directional interface that connects this system to another system. This other system is called the connected system. Each bytestream contains two channels, called the input channel and the output channel. The input channel receives transmissions from the connected system, and the output channel sends transmissions to the connected system.
A transmission is the sequence of bytes that are sent through a channel from when the transmission control flag of that channel is set to when it is cleared.
This device contains two bytestreams: the local bytestream is controlled using the first half of the device, and the remote bytestream is controlled using the second half.
A channel is a uni-directional interface that connects a transmitting system to a receiving system. Each channel contains a buffer queue and a transmission control flag.
Bytes are sent through a channel by the transmitting system, are buffered in the buffer queue, and then are received by the receiving system. Bytes can only be sent through a channel while the transmission control flag is set.
A buffer queue is a first-in first-out byte queue that buffers bytes sent through a channel. The maximum length of each queue is implementation defined. While a queue is full, all newly received bytes will be dropped.
A transmission control flag is a 1-bit value that determines whether bytes can be sent through a channel. The initial state of each transmission control flag is unset.
The receiving system on a channel can set the transmission control flag. The transmitting system can send bytes through the channel while the flag is set, and can clear the flag to end the transmission.
The remote bytestream will attempt to connect to a remote system in either server mode or client mode after a network address is written to the remote path buffer via either of the remote connection ports. The connection will be made in server mode if the terminating zero byte is written to the remote input connection port, or in client mode if the terminating zero byte is written to the remote output connection port.
When the remote bytestream connects to an address in server mode, the bytestream will bind to that address and listen for incoming connections. This system will act as a server, and other systems that connect to the bound address will act as clients. Each incoming transmission represents an incoming request from a remote system, and each outgoing transmission represents a response to that remote system. The remote system will be disconnected after writing to either the remote input transmission, output transmission, or input queue port. This system will remain bound to the address after a remote system is disconnected. The remote input connection state represents whether a remote system is currently connected, and the remote output connection state represents whether this system is still bound to a network address.
When the remote bytestream connects to an address in client mode, the stream device will connect to the remote system at that address. Both systems can send and receive transmissions until either system disconnects.
The remote path buffer is a 256-byte block of readable and writeable memory and an associated 8-bit pointer. The initial value of each byte of buffer memory is zero, and the initial value of the pointer is zero.
Writing a non-zero byte to the remote path buffer will write that byte to the buffer address referenced by the pointer, and then will increment the pointer by 1, wrapping to zero on overflow.
Writing a zero byte to the remote path buffer will disconnect the current remote connection, then will attempt to open a connection only if the final byte of the buffer is zero, and then will set the pointer and every byte of buffer memory to zero. The connection will be made in server mode if the zero byte was written to the remote input connection port, or in client mode if the zero byte was written to the remote output connection port.
When attempting to open a connection, the buffer content up to and excluding the first zero byte will be interpreted as a UTF-8 encoded string representing the network address to connect to. The connection fails if this buffer content is empty, is not a valid UTF-8 string, or contains any character in the range U+0000 to U+001F. The address format is implementation defined.
Reading from this port will return 0xFF if a filesystem entry is currently open, or 0x00 otherwise.
Writing a non-zero byte to this port will write that byte to the associated path buffer.
Writing a zero byte to this port will read a filesystem path from the associated path buffer, then will close the current entry, and then will attempt to open the entry referenced by the path read. If the path is invalid or the entry could not be opened, no entry will be opened and the error flag will be set.
Reading from this port will return 0xFF if the error flag is set, or 0x00 otherwise. The error flag will then be cleared.
Writing a non-zero byte to this port will write that byte to the associated path buffer.
Writing a zero byte to this port will read a filesystem path from the associated path buffer, then will attempt to perform a filesystem action, and then will close the current entry. The source path is the path of the current entry, and the destination path is the path that was read from the path buffer. The source path is not provided if no entry is open, and the destination path is not provided if the path buffer was empty. If the provided destination path addresses an existing entry or is invalid, no action will be performed and the error flag will be set.
Source
Destination
Action
✗
✗
No action.
✗
✓
Create empty file at destination.
✓
✗
Delete source.
✓
✓
Move source to destination.
If only the destination path has been provided, an empty file will be created at the destination path, with all parent directories being created as needed. If the file or any directory could not be created, the error flag will be set.
If only the source path has been provided, the entry at the source path will be deleted. If the entry could not be deleted and still exists, the error flag will be set.
If both paths have been provided, the entry at the source path will be moved to the destination path, with all parent directories being created as needed. If the entry could not be moved, or if any directory could not be created, the error flag will be set.
Reading from this port while the current entry is a file will return the byte of the file referenced by the entry address, and then will increment the entry address by 1. If no byte could be read from that address, the entry address will not be incremented, the error flag will be set, and the value returned will be zero. Overflowing the entry address will cause undefined behaviour.
Reading from this port while the current entry is a directory or while no entry is open will return zero.
Writing a byte to this port while the current entry is a file will write that byte to the byte of the file referenced by the entry address, and then will increment the entry address by 1. If the entry address does not reference a byte of the file, the file will first be resized by appending a number of zero bytes such that the file length is one greater than the entry address. If the file could not be resized or written to, the error flag will be set. Overflowing the entry address will cause undefined behaviour.
Writing to this port while the current entry is a directory or while no entry is open will have no effect.
This port is associated with a path buffer. When an entry is opened, the buffer will be populated with the path of that entry. When an entry is closed, the buffer will be cleared.
Reading from this port will read and return a byte from the associated path buffer.
Writing a non-zero value to this port will set the pointer of the associated path buffer to the address of the byte following the final path separator 0x2F, or otherwise to zero.
Writing a zero value to this port will set the pointer of the associated path buffer to zero.
Reading from this port will return 0xFF if the current entry is a directory, or 0x00 otherwise.
Writing to this port while an entry is open will close that entry and then will attempt to open the parent directory of the entry. If the entry has no parent directory or if that directory could not be opened, the original entry will be opened and the error flag will be set.
Writing to this port while no entry is open will close the current entry and then will attempt to open the default directory. If the default directory could not be opened, the original entry will be reopened and the error flag will be set. The path of the default directory is implementation defined.
This port is associated with a path buffer. When a child entry is selected, the buffer will be populated with the path of the child entry. When a child entry is deselected, the buffer will be cleared.
Reading from this port will read and return a byte from the associated path buffer.
Writing a non-zero value to this port will set the pointer of the associated path buffer to the address of the byte following the final path separator 0x2F, or otherwise to zero.
Writing a zero value to this port will set the pointer of the associated path buffer to zero.
Reading from this port will return 0xFF if the selected child is a directory, or 0x00 otherwise.
Writing to this port while a child entry is selected will close the current entry and then will attempt to open the child entry. If the child entry could not be opened, the original entry will be reopened and the error flag will be set.
Reading from this port group will perform an atomic read, returning the entry address.
Writing to this port group will perform an atomic write, setting the entry address to the value written on commit. If the current entry is a directory and the value written is the index of a child entry, that child entry will become the selected child, otherwise the current selected child will be deselected.
Reading from this port group will perform an atomic read, returning the length of the current entry, or zero if no entry is open. The length of a file is the number of bytes stored in the file. The length of a directory is the number of child entries in that directory. If the value exceeds the range of an unsigned 32-bit integer, the value returned will be 0xFFFFFFFF.
Writing to this port group will perform an atomic write, requesting that the current entry be resized to the value written on commit. If the current entry is a file, it will be truncated or zero-padded such that the file length is equal to the value written. If the current entry is a directory, or if no entry is open, or if the file could not be resized to the requested size, the error flag will be set.
A filesystem entry is an object stored in the filesystem that can be addressed by at least one filesystem path. Each entry is either a file or a directory.
A file is a block of zero or more bytes. Whether a file can be read from or written to is implementation defined. The maximum length of a file is implementation defined.
A directory is an ordered list of zero or more filesystem entries, called child entries. Child entries are indexed starting from zero. A directory can contain itself. The sort order of a directory is implementation defined. The maximum length of a directory is implementation defined.
A filesystem path is a UTF-8 encoded string representing the address of a filesystem entry.
A path separator is a single forward-slash character U+002F. A path component is a string that is at least one character long, and that does not contain the character U+002F or any character in the range U+0000 to U+001F.
A path must begin with a path separator, followed by zero or more alternating path components and path separators. A path containing a path component cannot end with a path separator. A path cannot exceed 255 bytes in length. A path must be a valid UTF-8 string. If a path does not meet all of these requirements, it is invalid, and it cannot address a filesystem entry.
The case sensitivity of a path is implementation defined. The set of additional characters that are not allowed to be used in a path is implementation defined.
A path buffer is a 256-byte block of readable and writeable memory and an associated 8-bit pointer. The initial value of each byte of buffer memory is zero, and the initial value of the pointer is zero.
Reading a byte from a path buffer will return the byte referenced by the pointer, and then will increment the pointer by 1, wrapping to zero on overflow.
Writing a byte to a path buffer will write that byte to the buffer address referenced by the pointer, and then will increment the pointer by 1, wrapping to zero on overflow.
When a path buffer is populated with a filesystem path, the buffer will be cleared, then each byte of the path will be written to the buffer, and then the pointer will be set to zero.
When a path buffer is cleared, the pointer and each byte of buffer memory will be set to zero.
When a filesystem path is read from a path buffer, the buffer content up to and excluding the first zero byte will be interpreted as the path. If the buffer contains no zero bytes, the path is invalid.
This device contains four path buffers: one for each of the open, action, path, and child path ports.
The entry address is a 32-bit value that represents the address of a byte in a file or the index of a child entry in a directory. The initial value of the entry address is zero.
The error flag is a 1-bit value that indicates when set that an error occurred while accessing the filesystem. The initial state of the error flag is unset.
This section specifies the clipboard device of the Bedrock computer system.
The clipboard device connects to slot 0xA of the device bus. It provides access to the system clipboard, and the ability to drag and drop data between programs.
Each read entry port is associated with a clipboard.
Writing to one of these ports will remove all bytes from the read queue of the associated clipboard, and then will read the current entry on the clipboard into that queue. If a non-zero value was written to the port, a text entry will be read. If a zero value was written to the port, a binary entry will be read. If the clipboard could not be accessed, or if the clipboard does not contain an entry of the chosen type, or if the entry is larger than the read queue, or if the entry is a text entry that is not a valid UTF-8 string, no entry will be read.
Each write entry port is associated with a clipboard.
Writing to one of these ports will remove the current entry from the associated clipboard, then will write the contents of the associated write queue as an entry to that clipboard, and then will remove all bytes from that queue. If a non-zero value was written to the port, a text entry will be written. If a zero value was written to the port, a binary entry will be written. If the clipboard could not be accessed, or if the entry is a text entry that is not a valid UTF-8 string, no entry will be written.
Each read queue port is associated with a clipboard.
Reading from one of these ports will return the number of bytes waiting in the read queue of the associated clipboard. If the queue contains more than 255 bytes, the value returned will be 0xFF.
Each write queue port is associated with a clipboard.
Reading from one of these ports will return the number of bytes of free space in the write queue of the associated clipboard. If the queue has more than 255 bytes of free space, the value returned will be 0xFF.
Writing to one of these ports will remove all bytes from the write queue of the associated clipboard.
Reading from one of these ports will remove and return a byte from the front of the read queue of the associated clipboard. If the queue is empty, the value returned will be zero.
Writing a byte to one of these ports will push that byte to the back of the write queue of the associated clipboard. If the queue is full, no byte will be pushed.
Reading from this port will return 0xFF if the drop flag is set, or 0x00 otherwise. The drop flag will then be cleared.
Writing a byte to this port will indicate that the entry on the secondary clipboard has been dropped by this system, and then the drag flag will be cleared.
A clipboard is a shared data structure that holds at most one entry. Each clipboard is shared across all Bedrock instances.
Each clipboard contains two access queues, a read queue and a write queue. The read queue is used to read an entry from the clipboard, and the write queue is used to write an entry to the clipboard.
This device contains two clipboards: the primary clipboard is controlled using the first half of the device, and the secondary clipboard is controlled using the second half.
An entry is a sequence of zero or more bytes, and can be either a text entry or a binary entry. A text entry represents a UTF-8 encoded string. A binary entry represents raw data. The maximum length of an entry is implementation defined.
The drag flag is a 1-bit value that indicates when set that the entry on the secondary clipboard is currently being dragged by this system. The initial value of the drag flag is unset.
The drop flag is a 1-bit value that indicates when set that the entry on the secondary clipboard has been dropped on this system. The initial value of the drag flag is unset.
When the entry on the secondary clipboard is dropped on this system, the drop flag will be set.
When the entry on the secondary clipboard is dragged over this system, or when an entry is written to the secondary clipboard, the drop flag will be cleared.
This device will send a wake request to the system device when the entry on the secondary clipboard starts being dragged over this system, stops being dragged over this system, or is dropped on this system.
Writing a non-zero byte to this port will write that byte to the associated name buffer.
Writing a zero byte to this port will read a name from the associated name buffer, and then will clear that buffer. If the name is valid, that name will become the selected namespace, otherwise the null namespace will become the selected namespace.
Writing a non-zero byte to this port will write that byte to the associated name buffer.
Writing a zero byte to this port will read a name from the associated name buffer, and then will clear that buffer. If the name is valid, that name will become the selected key, otherwise the null key will become the selected key.
Writing a value to this port will remove all bytes from the read queue, and then if an entry of the correct type exists in the selected key of the selected namespace, it will be read into the read queue. If a non-zero value was written to the port, the entry must be a text entry. If a zero value was written to the port, the entry must be a binary entry. If a text entry is invalid, no entry will be read.
Writing a value to this port will write an empty entry to the selected key of the selected namespace, then will write the contents of the write queue to that key, and then will remove all bytes from the write queue. If a non-zero value was written to the port, a text entry will be written. If a zero value was written to the port, a binary entry will be written. If attempting to write an invalid text entry, or if writing the entry would exceed the maximum number of registered namespaces or keys, no entry will be written.
Reading from this port will return the number of namespaces in the registry (excluding the null namespace).
Writing a value to this port will perform an atomic write, selecting a namespace on commit. If the value written is the index of a namespace in the registry, that namespace will become the selected namespace, otherwise the null namespace will become the selected namespace.
Writing a value to this port will perform an atomic write, selecting a key on commit. If the value written is the index of a key in the selected namespace, that key will become the selected key, otherwise the null key will become the selected key.
This port is associated with a name buffer. When the selected namespace changes, the buffer will be populated with the new name. The name buffer will initially contain the name of the null namespace.
Reading from this port will read and return a byte from the associated name buffer.
Writing a value to this port will set the pointer of the associated name buffer to zero.
This port is associated with a name buffer. When the selected key changes, the buffer will be populated with the new name. The name buffer will initially contain the name of the null key.
Reading from this port will read and return a byte from the associated name buffer.
Writing a value to this port will set the pointer of the associated name buffer to zero.
The registry is an ordered list of zero or more namespaces. The registry is shared across all Bedrock instances, and the contents persist after the system is halted. The null namespace is not shared or persisted.
A namespace is a name that is associated with an ordered list of zero or more keys.
Each registered namespace is addressed by a 16-bit index, starting from zero. The sort order is implementation defined. The maximum number of namespaces that can be registered is implementation defined, but must be no greater than 65535. The null namespace does not count as a registered namespace.
Registering a key to a namespace will register that namespace, and unregistering all keys from a namespace will unregister that namespace.
A key is a name that is associated with a namespace and at most one entry.
Each registered key is addressed by a 16-bit index, starting from zero. The sort order is implementation defined. The maximum number of keys that can be registered to a namespace is implementation defined, but must be no greater than 65535. The null key does not count as a registered key.
Writing a non-empty entry to a key will register that key, and writing an empty entry to a key will unregister that key.
An entry is a sequence of zero or more bytes associated with a key in a namespace, and can be either a text entry or a binary entry. A text entry represents a UTF-8 encoded string, and does not contain any character in the range U+0000 to U+001F. If a text entry does not meet these requirements, it is invalid, and cannot be read or written. A binary entry represents raw data. The maximum length of an entry is 255 bytes.
The null namespace is a special namespace that is used to pass named values into a program on launch. The name of the null namespace is the empty string. The initial contents of the null namespace are implementation defined.
The contents of the null namespace are not shared across Bedrock instances. They will be copied over to the new instance if the system is forked and will persist after the system is reset, but will not persist after the system is halted.
The null key is a special key in every namespace that cannot contain an entry. When an entry is written to the null key, that entry is discarded. The name of the null key is the empty string.
A name represents a key or a namespace. A name is a UTF-8 encoded string that does not contain the character U+002F or any character in the range U+0000 to U+001F. The maximum length of a name is 127 bytes. If a name does not meet these requirements, it is invalid, and cannot represent a key or a namespace.
A name buffer is a 128-byte block of readable and writeable memory and an associated 7-bit pointer. The initial value of each byte of buffer memory is zero, and the initial value of the pointer is zero.
Reading a byte from a name buffer will return the byte referenced by the pointer, and then will increment the pointer by 1, wrapping to zero on overflow.
Writing a byte to a name buffer will write that byte to the buffer address referenced by the pointer, and then will increment the pointer by 1, wrapping to zero on overflow.
When a name buffer is populated with a name, the buffer will be cleared, then each byte of the name will be written to the buffer, and then the pointer will be set to zero.
When a name buffer is cleared, the pointer and each byte of buffer memory will be set to zero.
When a name is read from a name buffer, the buffer content up to and excluding the first zero byte will be interpreted as the name. If the buffer contains no zero bytes, the name is invalid.