Definitions

"Internal" call here means a call from Coyote code to Coyote code.

"External" call refers to calls from Coyote code to "native code".

Internal is used instead of native to avoid confusion with calls to native code, which are external.

Plan

Game

Language

VM

Bytecode

Bytecode is a stream of u32s. A partial SSI form is used. In normal SSA, registers can only be assigned once. In our bytecode, registers can be reassigned within a block, but not between blocks. A full SSA form can be trivially constructed from this.

Bytecode is encoded as a stream of u32s. Instructions consist of 8-bit opcodes, 8-bit flags, and a 16-bit argument count. Arguments consist of 8-bits of flags and 24 of data.

The upper three bits of the opcode indicate the opcode class, which is one of the following:

Opcode classes four and five are not yet defined.

Order of operations

We are not supposed to hack on the language until the jam, so we won't. Instead, we will fully finish designing the subset of Coyote to be used during the language (this document), and the APIs it exposes to C (we'll write the headers and comment them, but won't work on real code).

By the end of August 3rd, we make all important decisions on syntax. These can be changed until three days before the jam starts (the seventh) with mutual agreement, but cannot be changed after that until the jam is over (this applies for all decisions made during the pre-jam period). This is to avoid bike shedding.

Between the 3rd and the tenth, we write as much of the Coyote portions of the roguelike as possible, without implementing any of the language. We also write parts of the C portions which interact with the mocked up VM API and the Coyote portions. By the beginning of the jam, we should have a (completely untested) large chunk of (hopefully) playable game. We will commit regularly, and before any new language feature is introduced. This will allow for rapid iteration during the jam.

Over this period, we should also design and implement a test harness to be run by the Makefile. The test harness should consist of a folder of tests, which define a script, an init function (to inject functions, for instance), and a post-execution function (to verify concluding state). One design would have them all #include a common base with a main() function.

During jam

First, we write the full lexer. Then we parse uint, int, returns, function declarations, and function calls into an AST. Then we implement semantic analysis for external calls, integers, and int#2 (which is implemented as two separate integers for now). At this point, we implement enough of the VM API to initialize Coyote and inject the rendering layer and input functions from the game's main.c.

From there, we build up the VM so that it can handle external calls with integer parameters, returns, and int#2 indexing. This should be enough to get some basic testing of the first commit of the game done. At this point, we fix any issues with the game and / or Coyote. If there are any issues with the game when testing commits, we fix them and commit in-place in history, rebasing the rest of the game over it. Commits to Coyote will be entirely separate from game commits; we won't commit when this happens, as we should have already committed a number of times.

We should also have a few tests now, to catch regressions.

At this point, we ignore the game for a bit, and add support for the following to every stage of the pipeline: local variables, basic arithmetic, and assignments. This will all be extremely important for pretty much everything, and will make it possible to write tests entirely separate from the game (which is critical, as we need to be able to partly test features). In the same vein, a few basic debugging opcodes should be added at this stage.

Tests for each bit should also be added immediately.