User:Stkptr/sandbox
Paradigm(s) | imperative, event-driven |
---|---|
Designed by | Eiko, Ori randomizer community |
Appeared in | 2020 |
Memory system | variable-based |
Dimensions | one-dimensional |
Computational class | Finite state automata |
Reference implementation | Ori WOTW randomizer |
File extension(s) | .wotwr , .wotwrh |
The Ori and the Will of the Wisps Randomizer is a configurable game randomizer for Ori and the Will of the Wisps. The randomizer supports a seed language, which is capable of performing event-driven behavior depending on the player's progress.
Syntax
Seed files are made up of lines. Lines can include comments which begin with //
. Commands are in the general form:
a|b|x|y|z|...
The a and b parameters together form a|b
which is the uberstate specification. Uberstates are values that are tracked by the game. When an uberstate is changed or reached, commands corresponding to the uberstate are evaluated. The uberstate specification's first element is the uberGroup and the second is the uberState uberGroup|uberState
. The other parameters form the pickup.
Semantics
Uberstates can have one of several different types, with types being either boolean-valued or number-valued. Bare uberstate specifications determine evaluation by determining if the value changed, and if the value is now truthy. Positive number values are considered truthy, with negative and zero values being falsy.
The uberstate can also be compared to produce a different coercion. The comparison operators are =
, >
, >=
, <
, <=
. An example compared uberState is 14019|26318>=5
(taken from the documentation). This compares the Hand-to-Hand skill uberState 14019|26318
with 5. When this uberState reaches a value which meets the condition, it will trigger the pickup. Subsequent changes which meet the condition will not trigger the pickup. However, a change which does not meet the condition, followed by one which does, will trigger the pickup.
Most uberStates correspond to either locations which can be reached by the player, skills the player can increase, or other types of data which are kept track of by the save system. There are other additional "pseudo-location" uberStates which activate on different conditions, including certain key combinations, actions, general locations, etc.
Pickups have the form group|param|param|...
. The group determines the type of change which happens when the pickup is triggered.
Group ID | Parameters | Behavior |
---|---|---|
0 | Spirit Light ID | places a Spirit Light |
1 | resource ID | places a resource pickup |
2 | item ID | places an item pickup |
3 | shard ID | grants shard |
4 | command, ... | miscellaneous commands |
5 | teleporter ID | places a teleporter |
6 | text/flags... | displays the text in a textbox, formatted according to the flags |
7 | none | implicit multipickup group, automatically created when multiple pickups are defined for a single uberState |
8 | uberGroup, uberState, type, value, skip? | writes a typed value to an uberState |
9 | event | triggers world events |
10 | bonus ID | places bonus items |
11 | bonus ID | places bonus upgrades |
12 | hint | deprecated zone hint |
13 | hint, ... | deprecated key hint |
14 | relic ID | places a relic |
15 | category, ... | progress for relics |
16 | wheel, position, ... | creates a wheel |
17 | command, ... | alters shop items |
The uberState update pickup is perhaps the most versatile. With it, chains of execution can be triggered, conditionally so. By default, an uberState update will allow for updates down the chain to occur. Using the skip parameter, the next n changes of a variable will not trigger updates. For instance:
a|b|8|x|y|bool|true|skip=1 x|y|6|State xy was triggered.
If we trigger the ab state, then the xy state will also be triggered. Since the uberState pickup triggered by the ab state is set to skip, the state triggered by xy will not be affected. If the skip is removed, then triggering ab will trigger xy, which displays the text.
The value parameter is quite versatile, supporting a basic expression syntax.
Form | Example | Result |
---|---|---|
constant |
7 |
sets the uberState to the given constant |
+expr |
+5 , +$(9|15) |
increments the uberState by an amount |
-expr |
-5 , -$(9|15) |
decrements the uberState by an amount |
$(uberGroup|uberState) |
$(9|15) |
expands to the value of the given uberState |
[min, max] |
[0, 10] , [$(9|14), $(9|15)] |
expands to a random value between min and max, inclusive |
The group 4 pickup has a range of functions:
Command | Parameters | Effect |
---|---|---|
0 | autosaves the game | |
1 | resource, quantity | sets a resource to a quantity |
2 | saves the game as a checkpoint | |
3 | "the magic has vanished from the forest" | |
4 | deprecated stop if equal | |
5 | deprecated stop if greater than | |
6 | deprecated stop if less than | |
0 | flag | allow/disallow the player to open the Kwolok door before Hollow |
8 | x, y | teleports Ori to the given x, y coordinates |
12 | health | sets Ori's health to the given amount |
13 | energy | sets Ori's energy to the given amount |
14 | light | sets Ori's spirit light to the given amount |
15 | slot, ability | equips an ability in a slot |
16 | keybind | presses a keybind |
17 | uberGroup, uberState, value, pickup | grant pickup if the uberState is equal to value |
18 | uberGroup, uberState, value, pickup | grant pickup if the uberState is greater than value |
19 | uberGroup, uberState, value, pickup | grant pickup if the uberState is less than value |
20 | uberGroup, uberState | disable syncing the given uberState in multiplayer |
21 | uberGroup, uberState | enable syncing the given uberState in multiplayer |
22 | ID, x, y, label? | creates a warp icon with optional label |
23 | ID | removes a warp icon |
24 | x1, y1, x2, y2, pickup | grant pickup if Ori is in the bounding box |
25 | value, pickup | grant pickup if the triggering uberState is equal to value |
26 | value, pickup | grant pickup if the triggering uberState is greater than value |
27 | value, pickup | grant pickup if the triggering uberState is less than value |
28 | ability | unequip an ability |
29 | ID, text | assign the string builder with the given ID to the specified text |
30 | ID, text | append the text to the specified string builder |
31 | set icon override | |
32 | clear icon override |
String builders can be accessed in message pickups (group 6) with the syntax ${builder ID}
, e.g. ${0}
will expand to the content in string builder 0.
uberStates can be set as timers using the syntax timer: uberGroup|uberState|uberGroup|uberState
. The first uberState is the boolean toggle, which causes the second float uberState to increase each frame by the amount of seconds since the last frame.
Pickups with the same triggering state are grouped together into multipickups. The execution of these multipickups occurs like so:
- Sort the pickups such that the general triggers (no condition) occur first and the conditional triggers occur last, without altering the order within each type
- Execute the pickups top to bottom
- If the pickup alters an uberState which is triggered on, handle those triggers before continuing
Computational class
The chain behavior of group 8 allows for a mostly straightforward implementation of counter machines. At the start, several uberStates are initialized to 1. These are the counters, one for each original counter, with an additional one for testing the state. Another state is initialized to 1, the program counter.
This implementation uses the notation when uberState = value set uberState as expression (type)
corresponding to uberState|8|uberState|type|expression
. There are three instruction forms, unconditional increment and decrement, and conditional jump. In this construction, the uberState p|c
is the program counter, c|0
is counter 0, c|1
counter one, t|0
test counter zero, etc. state
shall be the state corresponding to this instruction, n
being the counter to alter, next state
being the target state.
Start
when Init set counter0 as 1 (bigint) when Init set counter1 as 1 (bigint) ... when Init set countern as 1 (bigint) when Init set temporary as 1 (bigint) when Init set PC as 1 (int) 3|0|8|c|0|bigint|1 3|0|8|c|1|bigint|1 ... 3|0|8|c|n|bigint|1 3|0|8|t|0|bigint|1 3|0|8|p|c|int|1
Increment
when PC = state set countern as +1 (bigint) when PC = state set PC as next state (int) p|c=state|8|c|n|bigint|+1 p|c=state|8|p|c|int|next state
Decrement
when PC = state set countern as -1 (bigint) when PC = state set PC as next state (int) p|c=state|8|c|n|bigint|-1 p|c=state|8|p|c|int|next state
Since the language only has conditional execution on state change, in fact only execution ever on state change, testing the value of a counter is somewhat tricky using only the group 8 pickup. The most apparent method is to use a temporary counter. The counter is set to 0, then the counter to test is copied into it. Since counters start and only ever can go as low as 1, there is a definite state change which occurs. Two additional pickups then affect the jump. Since pickups on a given uberState will always execute when that condition occurs, two auxiliary variables are used to store the jump targets before the jump occurs.
// at start of the program, appears once when temporary = 1 set PC as $(zero) (int) when temporary > 1 set PC as $(nonzero) (int) t|0=1|8|p|c|int|$(j|0) t|0>1|8|p|c|int|$(j|1)
// prepare temp and targets when PC = state set temporary as 0 (bigint) when PC = state set zero as zero state (int) when PC = state set nonzero as nonzero state (int) // copy over counter, triggering the jump when PC = state set temporary as $(countern) (bigint) p|c=state|8|t|0|bigint|0 p|c=state|8|j|0|int|zero state p|c=state|8|j|1|int|nonzero state p|c=state|8|p|c|bigint|$(c|n)
This scheme can translate any counter machine into the seed language. Note, however, the use of the fictional bigint
type. As stated previously, the only types available are int
, byte
, boolean
, float
, and teleporter
. Since int
is limited to 32 bits, and there are a limited amount of uberStates, this means that the language is in the class of finite state automata.
As noted by the example, the addition of an arbitrary sized integer type would realize Turing completeness. This is not the only way Turing completeness could be achieved. The string builder system would be able to realize a queue if starting characters could be read and removed, or a stack if ending characters could be read and removed. Reading here referring to the ability to transition state on. An arbitrary sized queue is Turing complete by implementing tag, with a pair of unbounded stacks being Turing complete by constructing a Turing machine. Adding the ability to directly compare the lengths of strings with no intermediate operations would allow for the implementation of incrementing machines.