RayBlast Games
Home Spirit Drop About
Spirit Drop

Back

Custom Versus Rulesets

NOTE: The reference here is in progress and may expand later as rulesets become more powerful. This documentation is auto-generated from the game.

Spirit Drop has a gameplay loop that can refer to ruleset objects. You can create these rulesets from JSON files that are formatted appropriately. When v0.2.1 comes out, an in-game editor will be included if you don't like writing in JSON; it will also be the recommended way to create rulesets as it is able to identify potential problems and expression errors. The parser interprets JSONC (JSON with Comments); however, if you do use JSONC comments, the in-game editor will not be able to preserve them! To add a proper comment, include a property with a key that starts with "_c".

To create a ruleset JSON from scratch, create a .json file with this template (JSON schema coming soon, but include the line for later):

{ "name": "Your Ruleset Name", "author": "your_username", "identifier": "unique_ruleset_id", "versionNumber": "1", "description": "<describe your ruleset here>", "$schema": "https://rayblastgames.com/spiritdrop/ruleset-schema.json", }

After this, you can append event names with array values. Each event contains multiple rule objects. Every rule is identified by "type".

Jump into the Discord server if you have any questions

1. Synchronization

To include a variable for synchronization in multiplayer and replays, append one of these to the ruleset:

"syncedModeVariables": [ // Array of mode variables (don't include "mode.") ]

"syncedGroupVariables": [ // Array of group variables (don't include "group.") ]

"syncedBoardVariables": [ // Array of board variables (don't include "board.") ]

Each time you set these variables (read: each time) the values require networking. Setting them every frame will cause several problems such as restricting the max number of players, slowing down the framerate, occupying gigabytes in replay size, or a combination of such. Worth noting is that these variables are only updated when time starts passing, so you may get unexpected behavior if you were to treat them as normal compute variables (using a variable immediately after changing it results in the variable returning its old value). Generally, you want to use the variables within the events that are called for them. These variables should be saved for values that do not change often, such as points rewarded to winners of versus rounds.

Name Who can write Event called Safe to control timings
syncedModeVariables The current host syncedModeVariableRules No
syncedGroupVariables The player with the lowest boardIndex in the group syncedGroupVariableRules No
syncedBoardVariables The player controlling the board syncedBoardVariableRules Yes

2. Events

To include a new event, simply append the name of the event, and then give it an array value. Note that "buttonPressRules" and "userDefinedRules" are structured differently.

Whenever one of these events start, the scope variables and the context are cleared (except in userDefinedRules). When a rule triggers another event that is not a user defined event, the scope variables and the context are saved for after the new event finishes, so you should not rely on scope variables assuming that they have been set by another event.

modeStartRules

Ran when the mode starts.
Context:

"modeStartRules": [ // Array of rules ]

boardCreateRules

Ran whenever a board is created.
Context:

"boardCreateRules": [ // Array of rules ]

modePassiveRules

Ran every frame (delta time varies, do not assume 60 fps).
Context:

"modePassiveRules": [ // Array of rules ]

boardPassiveRules

Ran every frame for each board (delta time varies, do not assume 60 fps).
Context:

"boardPassiveRules": [ // Array of rules ]

syncedModeVariableRules

Ran whenever a synced mode variable changes. Use the variable changedVariableName to branch.
Context:

"syncedModeVariableRules": [ // Array of rules ]

syncedGroupVariableRules

Ran whenever a synced group variable changes. Use the variable changedVariableName to branch.
Context:

"syncedGroupVariableRules": [ // Array of rules ]

syncedPlayerVariableRules

Ran whenever a synced player variable changes. Use the variable changedVariableName to branch.
Context:

"syncedPlayerVariableRules": [ // Array of rules ]

syncedBoardVariableRules

Ran whenever a synced board variable changes. Use the variable changedVariableName to branch.
Context:

"syncedBoardVariableRules": [ // Array of rules ]

attackCommitRules

Ran whenever a piece is placed, right before attack and cancel is used. This event allows calculating resultAttackPower, resultBaseAttack, and resultGarbageCancel.
Context:

"attackCommitRules": [ // Array of rules ]

pieceSpawnRules

Ran when a piece appears after spawn delay or a Hold.
Context:

"pieceSpawnRules": [ // Array of rules ]

placementRules

Ran after the attackCommit rules.
Context:

"placementRules": [ // Array of rules ]

attackPushRules

Ran whenever an attack is created by a board.
Context:

"attackPushRules": [ // Array of rules ]

attackReceiveRules

Ran immediately after a board receives an attack.
Context:

"attackReceiveRules": [ // Array of rules ]

beforeGarbageAcceptRules

Ran when an attack arrives at a board.
Context:

"beforeGarbageAcceptRules": [ // Array of rules ]

garbageAcceptRules

Ran after an attack pushes garbage onto a board's garbage meter.
Context:

"garbageAcceptRules": [ // Array of rules ]

beforeGarbageIntakeRules

Ran whenever the player allows garbage to enter into the Rise Meter.
Context:

"beforeGarbageIntakeRules": [ // Array of rules ]

garbageIntakeRules

Ran after garbage has been added to the Rise Meter.
Context:

"garbageIntakeRules": [ // Array of rules ]

riseLineRules

Ran after each line enters the board from the Rise Meter.
Context:

"riseLineRules": [ // Array of rules ]

beforeBreakdownRules

Ran before a player is about to be declared topped out. canContinue is initialized to false and countAsKO is initialized to true.
Context:

"beforeBreakdownRules": [ // Array of rules ]

playerBreakdownRules

Ran after a player is declared topped out, regardless of if they canContinue.
Context:

"playerBreakdownRules": [ // Array of rules ]

playerRespawnRules

Ran after a player that topped out finishes wiping their board to continue playing.
Context:

"playerRespawnRules": [ // Array of rules ]

buttonPressRules[0]

Ran when the player sends a General1 input.
In the JSON format, this is stored as a nested array of rules, where each value in the root array is another array of rules corresponding to each General button. General1 triggers index 0, General2 triggers index 1, etc. The number of arrays in here should range from 1 to 4. You cannot have more than 4 because only 4 General buttons can be used in the game.
Context:

"buttonPressRules": [ // General1 Rules [ // Array of rules ], // General2 Rules [ // Array of rules ] ]

userDefinedRules

Set of events that can be triggered with the userDefined rule.
In the JSON format, this is not an array, but an object. Each property inside the object has an array of rules and can be any key that is not an existing event name.
Context:

"userDefinedRules": { /* userFunction1 - An example event to demonstrate syntax */ "userFunction1": [ // Array of rules ], /* userFunction2 - Another example event to demonstrate syntax Variables used: variable1, board.variable2 */ "userFunction2": [ // Array of rules ] }

3. Rules

assign

Gives a variable a value.

If you want to assign something inside a collection, use the [[update rule]] instead.

add

Adds a value to a numeric variable or collection.

Typically, you want to deal with numbers, but when it comes to [[collections]], you can make them grow instead.

subtract

Subtracts a value from a variable or collection.

Typically, you want to deal with numbers, but when it comes to [[collections]], you can make them shrink instead.

multiply

Multiplies a number to a variable.

update

Updates a [[collection]] with a key-value pair.

if

Controls flow based on a truth [[statement]] For complex expression [[ternary]], you should use this rule instead, as ternary requires evaluating the values to pick from (may change later), while rules included here are skippable. If you want to use Lua to compute logic, use an [[assign]] rule beforehand.

If neither onTrue or onFalse are provided, a warning will be thrown.

while

Performs rules while a statement remains true If you want to use Lua to compute logic, use an [[assign]] rule beforehand and after each loop

Keep in mind there is also a 2 second time limit (50 ms during a board update), and then the loop will be terminated

base

Executes a subset of rules from the base ruleset in the same event. If an event is not defined in the current ruleset, this is implicitly used for that event, otherwise you need to use this rule to restore the base ruleset's functionality.

WARNING: if you use either of these arguments, careful consideration must be taken when you update the base ruleset.

lua

Calls a Lua script with a specific name and a specific function. If you want to use this function to compute values (likely), then you should use [[assign]], [[add]], [[multiply]], etc.

userDefined

Triggers a user defined event. Note that the context does not change, and the scope variables carry over to the new event.

groupBoardsRelative

Applies rules to all boards of the group in the [[context]].

This is a relative rule and thus modifies the context:

alivePlayersRelative

Applies rules to all alive players; be careful not to nest this rule inside more alivePlayersRelative rules.

This is a relative rule and thus modifies the context:

aliveGroupsRelative

Applies rules to all alive groups.

This is a relative rule and thus modifies the context:

playerIDRelative

Applies rules to one player.

groupIDRelative

Applies rules to one group.

This is a relative rule and thus modifies the context:

announce

Causes a large message to appear on the right side of the screen. Use the s+ operator to print variable values inside your messages.

createAttack

Creates an attack object and pushes it through the current board's [[targeting style]]; due to limitations, Lua calls must be handled beforehand.

This creates a form of data that can be sent to other boards; you can enact on the data through certain events that initialize it in the attack [[context]]. WARNING: heavy use of the data argument can make the ruleset ineligible for mass battles!

target

Causes the current board to change target styles and their target.

playSound

Plays a sound effect in the Sounds folder.

If a customSoundName is provided, soundType, ordinalA, and ordinalB are ignored.

createBoard

Creates a board for the player in the [[context]]; this will immediately trigger a [[boardCreate event]]

This is a relative rule and thus modifies the context:

If the player already has a board, this has no effect.

destroyBoard

Deletes the board assigned to the player in the [[context]], effective immediately. This allows the player to receive a new board in the same round.

resumePlaying

Lets the player continue playing with their existing board If the player was declared finished, they are no longer finished and the board will continue processing events If the player just topped out, the top out is no longer permanent If the player is having their board permanently wiped, it will restart and revive If the player has gotten a Game Over (or has already dropped out of view), their board is summoned again, revived, and their Game Over stack wiped

The breakdown state takes a constant 1.95 seconds, the wipe state takes 0.05 seconds times the board height, and the countdown will always be 1.5 seconds. A 21 tall board will always have a 4.5 seconds break.

stopPlaying

Causes the current board to halt and be put in the finished state.

startRound

Causes all players to jump to the next round. This triggers synchronization in multiplayer. No more ruleset events are ran until the mode completes the first part of its between-round transition and everyone in multiplayer is ready.

endMode

Allows the mode to end. No more ruleset events will occur afterward. Versus mode will trigger its ending transition.

4. Variables

Hardcoded Variables

Variables with get instead of read are considered more expensive to fetch. Variables with set instead of write have side effects or just take some time to process.

Name CLR Type Permissions Description
aliveEnemyCount uint16 Read Number of opponents that have not topped out
aliveGroupCount uint16 Read Number of groups that have at least one board that hasn't topped out
alivePlayerCount uint16 Read Number of boards that have not topped out
allClearCount uint32 Read Number of all clears or all clear minis that were performed
allowHardDrop bool Read / Write Allows the use of the Hard Drop input; if not allowed, the Hard Drop Fallback is used
allowPassthrough bool Read / Write When disabled, attacks will not only burn lines in the garbage meter, but lines that will be inserted in the garbage meter as well; note that if you send 0 line attacks to opponents that send attacks with lines when passthrough is disabled, the attack receive event will NOT trigger for the 0 line attacks
allowSonicDrop bool Read / Write Allows the use of the Sonic Drop input; if not allowed, Sonic Drop inputs will become Soft Drop inputs
asdMax double Read / Write Maximum allowed Auto-Shift Delay; default is infinity; is applied after the placement event
asdMin double Read / Write Minimum allowed Auto-Shift Delay; default is 0; is applied after the placement event
aspMax double Read / Write Maximum allowed Auto-Shift Period; default is infinity; is applied after the placement event
aspMin double Read / Write Minimum allowed Auto-Shift Period; default is 0; is applied after the placement event
attackerCount uint16 Read Number of boards that are targeting the current board
attackers List<int16> Get List of player IDs that are targeting the current board
garbageIntakeDelay double Read / Write Determines how long it sits in the intake section of the Garbage Meter before it can be taken in by the Rise Meter
attackTravelTime double Read / Write Determines how long it takes for the attack to travel to its destination; writing to this will reschedule the GarbageAccept event
autoDropInterval double Read / Write Board timing for how long it takes for the current piece to move down one row; writing to this will reschedule the AutoDrop event
backToBack int32 Read Current Back-to-Back chain; is negative when there is no chain
bag List<string> Get / Set A list representing the piece and orientation combinations the piece generator can use; to apply changes, you must set it back; WARNING: This will be empty in replays
boardHeight byte Read / Set Number of rows in the grid, or more specifically, the position of the spawn window
boardIsAlive bool Get If true, the current board is alive
boardIsLead bool Read If true, the current board is in focus in the view; should only be used for effects
boardStack List<byte>
Dictionary<uint16,byte>
Get / Set Returns a list of bytes that represent the stack of the current board; this is an expensive variable to retrieve and overwrite; data is read from bottom to top, left to right (for example, the 7th cell in the 3rd row would be index 26 (2 * 10 + 6) or Lua index 27) To apply changes, you must set it back; when providing a dictionary, the keys represent cell positions; this allows you to overwrite parts of the stack without having to retrieve the current data Refer to the data format for each byte For a nested list where each list represents a row, use boardStack2D
boardWidth byte Read / Set Number of columns in the grid
changedVariableName string Read The name of the variable that was changed in the syncedModeVariableRules, syncedGroupVariableRules, syncedPlayerVariableRules, and syncedBoardVariableRules events
combo int32 Read Current combo count; is negative when no line clears occurred
currentPieceName string Read Returns the name of the piece currently in control
enableGhost bool Read / Write Reveals or hides the location a piece will end up when Hard Drop or Sonic Drop is used
gameTime double Read Number of seconds that the current board has been active; stops progressing once the player tops out or when the stop playing rule is ran
garbageAsynchronousIntake bool Read / Write If true, the player will be able to play while the Rise Meter is pushing rows
garbageData object Read / Write Object transferred with an attack; if pushed to the Rise Meter, the garbageType will determine what the rows become using this data
garbageIntake string Read / Set Set to 'individual' to have the Rise Meter take in one garbage at a time or the garbageIntakeLimit, whichever is lower Set to 'conglomerate' to have the Rise Meter take in as much as the garbageIntakeLimit
garbageIntakeLimit byte Read / Set Determines how many lines of garbage can enter in every piece placement; setting this will immediately "unprime" pending garbage, so do not set to 0 if you're recalculating
garbageLimit int32 Read / Set Determines how many lines of garbage the Garbage Meter can hold; setting this will immediately toss any excess, so do not set to 0 if you're recalculating
garbageLineClearIntake bool Read / Write If true, the Garbage Meter will push garbage regardless even if the player clears a line
garbageRegisterA byte Read / Write One byte of information; if pushed to the Rise Meter, the garbageType will determine what the rows become using this data
garbageRegisterB byte Read / Write One byte of information; if pushed to the Rise Meter, the garbageType will determine what the rows become using this data
garbageRegisterC byte Read / Write One byte of information; if pushed to the Rise Meter, the garbageType will determine what the rows become using this data
garbageRowCount byte Read / Write The number of rows in the attack or garbage; alternatively can be used simply as a quantity
garbageSpawnDelay double Read / Write Determines how long the Rise Meter waits before applying rows from the Garbage Meter; has no effect if the Rise Meter already has rows
garbageType enum(GarbageType) Read / Write Determines how the Rise Meter interprets the data in the garbage
  • 0 / Holes
  • 1 / HolesUsingData
  • 2 / InverseHoles
  • 3 / InverseHolesUsingData
  • 4 / RightHanded
  • 5 / CustomRowsUsingData
groupCount uint16 Read Number of groups in the mode
holdCapacity byte Read / Write Maximum number of pieces in the hold queue
holdLimit uint16 Read / Write Maximum number of times the player can move pieces in and out of the Hold queue; when this is exhausted, Re-Hold can be used if enabled
koHistory List<List<object>> Get This is an expensive variable to translate; use latestKO if you are only interested in the latest KO to increase performance, otherwise use this to get a full list of the entries across all rounds in the same format
latestKO List<object> Get This translates the last KO that occurred into data you can use; if no one has been KO'd yet, this will be null, otherwise you will get a list that gives you the board index that was KO'd, the board index of the killer (or the KO'd if self-destructed), the round it occurred in, and the roundTime it occurred
lineClearDelayMax double Read / Write Board timing for how long it takes for the stack to compress after line clears; writing to this will reschedule the LineFall and Spawn events
lineClearDelayMin double Read / Write Board timing for how long it takes for the stack to compress after line clears; writing to this will reschedule the LineFall and Spawn events
localInputPresses Dictionary<InputCode,bool> Read Returns a dictionary of all the client's input codes that were pressed this frame; they are updated right before the modePassiveRules event; note that this is input data that is not recorded in a replay, try using the General buttons if you can
localInputState Dictionary<InputCode,bool> Read Returns a dictionary of all the client's input codes and their state; they are updated right before the modePassiveRules event; note that this is input data that is not recorded in a replay, try using the General buttons if you can
localPlayerID int32 Get The player ID that corresponds to the user of the client
lockDelay double Read / Write Board timing for how long it takes for the current piece to automatically lock down; writing to this will reschedule the Lock event
modeTime double Read Number of seconds since the mode started the first round
musicLevel int32 Read / Set When assigned to, the music will change. Set to -1 to turn off the music. If duel is set to true, setting this variable has no effect.
nextPieces List<string> Get / Set Returns a list representing the order of the pieces as their name and orientations in the Next Queue (formatted as 'name:orientation'); to apply changes, you must set it back; if the queue is too short, it will be filled up with the current bag
pieceX sbyte Read / Write The horizontal coordinate of the origin of the current piece; note that you can no-clip by setting it
pieceY sbyte Read / Write The vertical coordinate of the origin of the current piece; note that you can no-clip by setting it
playerCount uint16 Read Number of player definitions there are
playerIsLocal bool Read If true, the current board is controlled by the player of this client
previewCount byte Read / Write Controls the number of next piece previews visible to the player; currently the limit is 7
previousBackToBack int32 Read The Back-to-Back chain that existed before the placement; when this is greater than backToBack, it means a chain just ended
reaperState bool Read If true, the player escaped a top out
resonanceMax double Read / Write Restricts the resonance level; default is 1.0
resonanceMin double Read / Write Enforces a minimum resonance level; default is 0.0
resultAttackPower uint16 Read / Write (conditional) A number that can be used to create attacks; can only be written to in the attack commit rules
resultAttackSent uint16 Read A number that is equal to attack power minus garbage burned; cancel amount is added to this number when it is used
resultBaseAttack uint16 Read / Write (conditional) A number that is used for the base attack statistic which appears outside of 2-player, not used otherwise; can only be written to in the attack commit rules. There is no way to validate base attack calculation so do not forget to set this or it will show as "0.00 base atk/min"
resultBuildIdentifier enum(BuildIdentifier) Read Is one of the named builds that the player can make
  • 0 / None
  • 1 / TwinQuad
  • 2 / QuadFrenzy
  • 3 / FourWide
  • 4 / TwinQuintuple
  • 5 / QuintupleFrenzy
  • 6 / TwinSextuple
  • 7 / SextupleFrenzy
  • 8 / TwinSeptuple
  • 9 / SeptupleFrenzy
  • 10 / TwinOctuple
  • 11 / OctupleFrenzy
  • 12 / Goodtwist
  • 13 / Roofless
resultGarbageCancel uint16 Read / Write (conditional) A number that can be used to bypass garbage to send attack; can only be written to in the attackCommitRules event
resultGarbageCleared byte Read Number of line clears the piece placement formed involving rows that came from the Rise Meter
resultIsAerial bool Read Denotes if this was a line clear that was performed over a bunch of space right underneath
resultIsMajorTwist bool Read Denotes if a major twist was performed
resultIsMinorTwist bool Read Denotes if a minor twist was performed
resultIsReaper bool Read Denotes if this was a piece placement that was saved from a top out or involved a line clear at the top
resultLinesCleared byte Read Number of line clears the piece placement formed
resultPieceName string Read The name of the piece that was placed
resultSpecialType enum(SpecialResult) Read Denotes line clears with special conditions
  • 0 / None
  • 2 / AllClear
  • 3 / AllClearMini
  • 4 / FlatClear
  • 5 / SplitClear
risePeriod double Read / Write Board timing for how long it takes for each line in the Rise Meter to appear; writing to this will reschedule the GarbageIntake event
rotationResets uint16 Read / Write Number of times the player can kick a piece before it auto-locks; set to 0 to disable lock resets for rotation and 10000+ to give infinite lock resets
rotationState int16 Read / Write The orientation index of the current piece; note that you can no-clip by setting it
roundNumber int16 Read The current round in the game session; starts at 0 and goes up each time the startRound rule is used (which then starts at 1)
roundTime double Read Number of seconds since the round started
shiftResets uint16 Read / Write Number of times the player can shift a piece on the stack before it auto-locks; set to 0 to disable lock resets for shifts and 10000+ to give infinite lock resets
spawnDelayMax double Read / Write Maximum time to summon the next piece; writing to this will reschedule the Spawn events
spawnDelayMin double Read / Write Minimum time to summon the next piece; writing to this will reschedule the Spawn events
targetCount uint16 Read Number of boards targeted by the current board
targets List<uint16> Get / Set A list of player IDs that corresponding to who the current board is targeting
thereIsAPlayerInDanger bool Get Checks if any alive player is near the top; can be used to allow a different targeting style
sdmMax double Read / Write Maximum allowed Soft Drop Multiplier; default is infinity; is applied after the placement event
sdmMin double Read / Write Minimum allowed Soft Drop Multiplier; default is 0; is applied after the placement event
sdaMax double Read / Write Maximum allowed Soft Drop Addition; default is infinity; is applied after the placement event
sdaMin double Read / Write Minimum allowed Soft Drop Addition; default is 0; is applied after the placement event
boardStack2D List<List<int32>> Get / Set Returns a list of lists of bytes that represent each row of the stack of the current board; this is an expensive variable to retrieve and overwrite; rows are ordered from bottom to top, left to right (for example, the 7th cell in the 3rd row would be indexes 2 and 6, or Lua indexes 3 and 7) To apply changes, you must set it back; when providing a dictionary, the keys represent cell positions; this allows you to overwrite parts of the stack without having to retrieve the current data Refer to the data format for each byte For a single list of cells, use boardStack
groupID uint16 Get Returns the index of the group
playerID uint16 Read Returns the index of the player

Mode Settings

Mode settings are global constants. Mode settings are readonly and are initialized as a number set by the host before the mode begins. To expose this in the lobbies and in the AI Versus setup screen, you need a block of JSON like this in the settings portion. Supported types for min and max are numbers only. If you want the UI to display something other than numbers, you can include an enumValues attribute.
name is the ruleset variable name, accessible through mode.settings.name or simply settings.name
uiName is what appears in the menus
min is the minimum value it can be; can be a integer (up to 64-bit signed or unsigned), or a double-precision floating-point number
max is the maximum value it can be
default is the default value it will start with in the menus; this is also the value the AI use
enumValues is an array of enum strings; these strings can be referred to in the rules

"settings": [ { "name": "goalPoints", "uiName": "Goal Points", "min": 1, "max": 255, "default": 3 } ]

Board Preferences

Board preferences are unique to each player. Board preferences are readonly and are initialized as a number set by the player before the mode begins. They are structured in JSON the same way as mode settings. All board preferences must be prefixed with board.preferences. or simply preferences..

"preferences": [ { "name": "targetStyle" "uiName": "Target Style" "description": "(explanation on how this works)" "enumValues": [ "quickAccess", "cycle" ] } ]

{ // Check target style "type": "if", "statement": "$board.preferences.targetStyle == quickAccess", "onTrue": [ { "type": "target", "style": "AllAttackers" } ] }

Mode Variables

Mode variables are global variables and can be accessed anywhere. Mode variables are initialized as null before the mode begins. All mode variables must be prefixed with mode..

Name CLR Type Initial Value Description
goalPoints byte 1 A number that is used to determine the winner(s). When it is greater than 1, a bar at the bottom of the screen will describe which groups are winning (if anyone is by themselves, they are technically their own group). You will need to check if any group's points is at least this before using the endMode rule.
goalLead byte 1 A number that is used to describe how much of a lead a group needs to be winning by before they are declared the winner. This makes the middle section of the points bar wider, and the goal points will appear to be increasing if a lead is not maintained. Refer to the playerBreakdownRules in the Eliminations Ruleset to see how to implement.
duel bool false Changes the behavior of musicLevel.
button1Text string null Text that says what the General1 input does
button2Text string null Text that says what the General2 input does
button3Text string null Text that says what the General3 input does
button4Text string null Text that says what the General4 input does

Group Variables

Group variables are unique to each group and can be accessed by member boards. They persist across rounds. Group variables are initialized as null before the mode begins. All group variables must be prefixed with group..

Name CLR Type Initial Value Description
points byte 0 A number that is used to determine who is winning. This also rewards spirit points at the end, based on goalPoints.
koCount uint32 0 Number of KOs this group has accrued across all its members.

Board Variables

Board variables are unique to each player. They persist across rounds. Board variables are initialized as null before the mode begins. All board variables must be prefixed with board..

Name CLR Type Initial Value Description
displayText List<string> null Text that appears on the right side of the board; if you provide anything other than a list it will be formatted as one line of text; set to null to not add any text; for performance, only create the list when the number of lines change, and use the update rule or operator to change the text inside
modeBar1 float 0f A number that fills up the first meter on the right. -infinity is hidden, 0.0 is empty, 1.0 is filled once, 2.0 is filled twice, etc.
modeBar1Colors Color[] <the 12 rainbow colors but with rose replaced with white> An array of colors used for each fill. It is recommended not to set this very often. When a list of strings is provided it will be parsed as hex values on the next frame, and you cannot modify it afterward. If the value is null it will just be a single white.
modeBar2 float -infinity A number that fills up the second meter on the right. -infinity is hidden, 0.0 is empty, 1.0 is filled once, 2.0 is filled twice, etc.
modeBar2Colors Color[] <the 12 rainbow colors but with rose replaced with white> An array of colors used for each fill. It is recommended not to set this very often. When a list of strings is provided it will be parsed as hex values on the next frame, and you cannot modify it afterward. If the value is null it will just be a single white.
attackMultiplier double 1.0 A number shown at the top of the mode bars (if it's not a number, it will be used as a string).
level int32 0 This number is used to control the typeface of the attackMultiplier shown. If it's 0, the multiplier is shown as gray, otherwise it's one of modeBar1Colors. It gets bigger when level is at least 10.
koCount uint32 0 Number of KOs since the beginning of the first round.

Scope Variables

Scope variables are temporary variables that are used to assist in computing. Scope variables are cleared on every event except for the user defined events. Due to how easy it is to accidentally create scope variables, the game chooses to throw an exception if you try to use the assign rule instead of the assignNew rule when the variable is uninitialized.

Board Statistics

Statistics are unique to each board. They are automatically managed by the game and are readonly. To access them, either do $board.statistics[Lines] or $board.statistics;Lines;[]. You can also refer to them by ID. They are all of type uint32. If you try to add to, remove from, or update $board.statistics itself, it will clone itself and turn into a dictionary.

Enum Name ID Description
Attack 0 The total number of attack power created from the attack commit event
AttackSent 1 The total number of attack created from the attack commit event that did not get cancelled by the garbage meter
BaseAttack 12 The total number of base attack created from the attack commit event
GarbageClears 6 The total number of line clears that came from the Rise Meter
Inputs 4 The total number of key presses accepted by the game
Lines 5 The total number of line clears
Pieces 7 The total number of pieces placed
Power 8 The current power level of the board; currently unused
AccumulatedPower 9 The total power ever gained, not including losses
PeakPower 10 The highest power level ever achieved in the round

Supported CLR and Lua Types

NOTE: Need to jot down this section.

Collections

Three types of collections are supported. The add, subtract and update rules as well as the add, subtract and update operators can modify them.

5. Expressions

All expressions are in postfix. You can use a mix of constants, variables, and operators, all separated by semicolons (use \; to use actual semicolons). All variable references must be appended with $. All strings must be wrapped in single-quotes. The game will check for illegal expressions, such as when you pop more than you push.
At some point, infix expressions will be supported, but currently there's too many operators at the moment to make it worth implementing at the moment.
NOTE: Will need to work on operators involving collections

add

A;B;+
Consumes two numbers and pushes A plus B
If A is a list, then it adds B to the end of A and pushes A back
If A is a dictionary and B is a list of 2 items, the first item is used as the key and the second item is used as the value then A is pushed
If A and B are both dictionaries, all key-value pairs are added to A then A is pushed

If A is a color array, then it is cloned into a list
If A is a statistics object, it is cloned into a dictionary
For performance, do not create temporary lists just for them to be merged into another; use the update operator instead

subtract

A;B;-
Consumes two numbers and pushes A minus B
If A is a list, then it removes all instances of B from A and pushes A back
If A is a dictionary, B is used as a key to remove from A then A is pushed
If A and B are both dictionaries, all keys from B are removed from A then A is pushed

If A is a color array, then it is cloned into a list
If A is a statistics object, it is cloned into a dictionary

multiply

A;B;*
Consumes two numbers and pushes A times B

divide

A;B;/
Consumes two numbers and pushes A divided by B

integer divide

A;B;//
Consumes two numbers, converts them to 32-bit integers, and pushes A divided by B rounded down

modulo

A;B;%
Consumes two numbers, does A divided by B, and pushes the remainder

power

A;B;pow
Consumes two numbers and pushes A raised to the power of B

bitwise and

A;B;&
Consumes two numbers, converts them to 32-bit integers, and pushes a number where they share set bits

bitwise or

A;B;|
Consumes two numbers, converts them to 32-bit integers, and pushes a number where either have set bits

bitwise exclusive or

A;B;^
Consumes two numbers, converts them to 32-bit integers, and pushes a number where either, but not both, have set bits

bitwise complement

A;B;~
Consumes a number, converts it to a 32-bit integer, and pushes a number where all bits are flipped

For performance, 'A;~;~;~' is automatically converted to 'A;~'

bitshift left

A;B;<<
Consumes two numbers, converts them to 32-bit integers, and pushes a number where A's bits are shifted left B times

bitshift right

A;B;>>
Consumes two numbers, converts them to 32-bit integers, and pushes a number where A's bits are shifted right B times

logical and

A;B;&&
Consumes two values and pushes true if both values are truthy, or false if only one is truthy or neither are truthy

logical or

A;B;||
Consumes two values and pushes true if one or both values are truthy, or false if neither are truthy

logical exclusive or

A;B;^^
Consumes two values and pushes true if one value is truthy, or false if both or neither are truthy

not

A;!
Consumes one value and pushes true if it is not truthy, or false if it is truthy

For performance, 'A;!;!' is automatically converted to 'A;!!', 'A;!!;!' is automatically converted to 'A;!', and so on

truth

A;!!
Consumes one value and pushes true if it is truthy, or false if it is not truthy

For performance, 'A;!!;!!' is automatically converted to 'A;!!' and so on

equality

A;B;==
Consumes two values and pushes true if they are considered equal, or false if they are not equal

inequality

A;B;!=
Consumes two values and pushes true if they are not considered equal, or false if they are equal

greater than

A;B;>
Consumes two numbers and pushes true if A is greater than B, or false if A is less than or equal to B

less than

A;B;<
Consumes two numbers and pushes true if A is less than B, or false if A is greater than or equal to B

at least / greater than or equal to

A;B;>=
Consumes two numbers and pushes true if A is greater than or equal to B, or false if A is less than B

no more than / less than or equal to

A;B;<=
Consumes two numbers and pushes true if A is less than or equal to B, or false if A is greater than B

string concatenation

A;B;s+
Consumes two strings and pushes a string where B is appended to A

string removal

A;B;s-
Consumes two strings and push a string where all instances of B are removed from A

number format

A;B;:
Consumes a number and a string and pushes a string representation of A based on the format provided by B

Valid formats are
:time - 0:00.000
:intTime - 0:00 (rounded to nearest second)
:floorTime - 0:00 (rounded down)
:ceilTime - 0:00 (rounded up)
any valid .NET format such as ':N3'

index

A;B;[]
Consumes a collection and pushes an object from A at index/key B

update

A;B;C;@
Replaces an item C in collection A at index/key B

new list

A;l[]
Pushes a new list object

new dictionary

A;d[]
Pushes a new dictionary/table object

countof

A;countof
Consumes a collection and pushes the number of properties

ternary

A;B;C;?
Consumes a boolean and two objects and pushes B if A is truthy or C if A is not truthy

Due to the nature of postfix expressions, B and C are fully evaluated before A is checked, which can result in wasted processing; keep B and C simple or consider using an if rule

insert into index

A;B;C;[]+
Inserts an item C in collection A at index/key B
If A is a dictionary that already has key B, this operator leaves A unaffected

When A is a list, all items after index B are moved

remove at index

A;B;[]-
Removes an item in collection A at index/key B

When B is a dictionary, this operator explicitly removes the dictionary instead of the items inside, unlike the subtract operator

6. Statements

Statements are in infix and produce booleans. At first glance, they seem to have the same operators as expressions, but they operate fundamentally differently. No assignment or manipulation can be performed, and thus no Lua is allowed. You can control evaluation order using parentheses.

If you want to use postfix expressions, surround the postfix in curly braces (not technically always required but highly recommended).
$variable1 < {$board.variable2;5;+}

equality

A=BA==B
Returns true if A and B are considered equal, else returns false
When comparing a string and a number, the length of the string is used

inequality

A!=B
Returns false if A and B are considered equal, else returns true
When comparing a string and a number, the length of the string is used

greater than

A>B
Returns true if A is considered greater than B, else returns false
When comparing strings, this returns true if A comes after B lexically
When comparing a string and a number, the length of the string is used

less than

A<B
Returns true if A is considered greater than B, else returns false
When comparing strings, this returns true if A comes before B lexically
When comparing a string and a number, the length of the string is used

at least / greater than or equal to

A>=BA≥B
Returns true if A is considered greater than or equal to B, else returns false
When comparing strings, this returns true if A comes after B lexically or is in the same position lexically
When comparing a string and a number, the length of the string is used

no more than / less than or equal to

A<=BA≤B
Returns true if A is considered less than or equal to B, else returns false
When comparing strings, this returns true if A comes before B lexically or is in the same position lexically
When comparing a string and a number, the length of the string is used

and

A&BA and B
Returns true if both A and B are truthy, else returns false

or

A|BA or B
Returns true if A or B are truthy, else returns false

exclusive or

A^BA xor B
Returns true if A or B are truthy but not both, else returns false

not

!Anot A
Returns false if A is truthy, else returns true

truth

A
Returns true if A is truthy, else returns false
A value is considered truthy if it is not null/nil/undefined, not zero, not false, not an empty string, and not an empty collection

7. Event Context

The event context is a special state that is shared across rules. The nature of the values set depends on the events and rules being ran.

NOTE: Need to clarify what each contains.

8. Board Data

The board stack is a highly specialized collection of bytes. When getting and setting board data, it will be in this format, from bottom to top, left to right:

00 05 00 00 00 00 03 03 00 00
05 05 05 00 00 03 03 09 09 09
2E 2E 2E 10 2E 2E 2E 2E 2E 2E
2E 2E 2E 2E 2E 2E 2E 2E 2E 10

The right digit is the mino color. When it is 0, the cell is interpreted as being empty. 1 is red, 12 is rose, 13 is white, 15 is black, etc.
The left digit is the metadata, which is a set of 4 flags. Add numbers together to get desired cell state:

2E,2E,2E,2E,2E,2E,2E,2E,2E,10,2E,2E,2E,10,2E,2E,2E,2E,2E,2E,
05,05,05,00,00,03,03,09,09,09,00,05,00,00,00,00,03,03,00,00

[46,46,46,46,46,46,46,46,46,16,46,46,46,16,46,46,46,46,46,46,5,5,5,0,0,3,3,9,9,9,0,5,0,0,0,0,3,3,0,0]

The above array is what you'll receive when getting the board stack.

You can also send attacks that contain specialized rows. Here's an example postfix that creates a row:

l[];l[];1;+;2;+;3;+;4;+;5;+;6;+;7;+;8;+;9;+;10;+;+

9. Lua

There are several rules that can call Lua 5.2 functions. These reside in Lua scripts that exist in the Lua folder in the game directory.
Lua support is powered by the MoonSharp plugin. You can define multiple functions in a Lua script and be able to reference them directly in rulesets.

Supported CLR Types (used by the game):

Supported Lua Types:

NOTE: Will need to experiment more with Lua scripting to give more information.

There are some differences in this interpretation of Lua, as it is sandboxed:

10. Tips

Because the game is not framerate-based, it is important that everyone is in sync. All board events are time-driven, so if you change a variable that reschedules an event, a desync might occur and can potentially cause a "Time cannot go backwards" exception (shouldn't happen by the way so report it if it does). The game will try its best to prevent these situations but there's a lot of places to plug up. Your best opportunities to change board timings are by changing them in:

If you change them outside of these situations, replays and multiplayer will visually desync but will fix themselves up afterward. It's kinda quantum-like.

If performance is low, or players are disconnecting in the middle of your custom ruleset, consider any synchronized variables that are constantly being assigned to. If synchronized board variables are used to control board timings, check if the value is actually different, since the ruleset is allowed to use synchronized variables as a sort of event-type value (think of a switch statement). The reason frequent assignment causes performance issues is because of the replay protocol causing all boards to savestate for playback. You can get a visual indicator of how bad it is by viewing a replay and seeing how many markers appear on the seek bar.

Adding a list to another list will make a multi-dimensional jagged list. If you want to append the contents of a list to another you must add each element individually.

When declaring the winner in an eliminations style battle, you need to account for latency. First you check if alivePlayerCount is no more than 1. If only 1 person has not topped out, wait until their gameTime exceeds everyone else's. If everyone is topped out, loop through all the boards and solve for the one with the longest gameTime.

Discord - AgStarRay

Click here for AgStarRay's YouTube channel

Twitter - @AgStarRay