SUBLIME TO GOD
A tutorial mini-site by Choon_2i
Based roughly on "Simply FPU" by Raymond Filiatreault

Chap. 1
Prerequisite Knowledge

Throughout this section, I will be using certain characters


Internal Notes


State Processing/Branching

Whenever a player is processed, a small flowchart is referred to for determining which state to execute.

  1. If the player isn’t a helper or it has helpertype=player, as long as the player isn’t thrown, execute Def -3.
  2. If the player isn’t a helper or it has helpertype=player, execute Def -2.
  3. If the player has keyctrl=1 (non-helper players have this by default), execute Def -1.
  4. Execute the Def corresponding to the player’s current stateno. Whenever a state branch occurs, repeat this step. If this step happens 2500 times consecutively, the game will crash.
  5. If the player has hitpausetime<=0, stateno=140, and statetime>=animlength, ChangeState to a statetype-dependent state and return to step 4S sends to 0. C sends to 11. A sends to 51. Any other statetype will not branch, but the engine will still treat it as if it did, which can result in an infinite loop if not handled..
  6. If the player has physics=A and they’re about to hit the ground, ChangeState to 52 and return to step 4As you’d expect, this can result in an infinite loop if physics isn’t changed in Def 52, or if Def 52 doesn’t exist..

“Branching” refers to the process of changing one’s state, or restarting state processing somehow. Using only standard state controllers, there are either 2 or 4 ways to execute it, depending on version, which are:


IgnoreHitPause, Persistent, and the 512 State Controller Limit

There are 2 non-expressiveThe parser will only read the first number present after persistent= parameters that every state controller can have, which are ignorehitpause and persistent. When ignorehitpause is omitted/0 and the player’s hitpausetime value is greater than 0, that state controller is completely ignored. In all other cases, it’s executed as normal.

persistent is a parameter that allows a state controller to be executed every Nth time until a state is branched to, or just once if N happens to be 0. It defaults to 1 if unspecified. This is implemented as each controller having a byte corresponding to it that stores its “persistence” value within player memory. The flowchart of processing these bytes is as follows:

  1. If the persistence byte is non-zero, skip to step 4.
  2. If all triggeralls (if they exist) are truthy, and all of any trigger# are also truthy, execute the state controller. Otherwise, skip to step 4.
  3. Set the persistence byte to the controller’s persistent valueAs this is a byte, the value written is actually (value&255)., or 1 if the value is zero.
  4. If a state branch occurred, ignore steps 5-6.
  5. If the controller’s persistent value is non-zero, decrease the value of the persistence byte by 1.
  6. If the resulting byte is less than 0, set it to 0.

Because there are only 512 persistent bytes allocated, there is a maximum of 512 state controllers that can be used per state, as any more would result in unintended memory regions being modified by the persistence system. This check is performed on initially branching to a state without hitpausetime (in other words, if (hitpausetime<=0 && time=0)), right as every byte is cleared. In other words, if this check isn’t performed, you can have more than 512 state controllers in a state. Exploiting this oversight is referred to as “State Controller Overflow,” “Null OverflowAs it’s commonly accomplished by using the Null state controller, which does nothing on execution.,” and “512 Overflow.”
By extension, since persistent bytes aren’t reset when a character has hitpausetime and state changes don’t decrement their respective persistence byte, keeping a state branch’s persistence byte at 0 is incredibly important to prevent weird inconsistencies involving state controllers simply not being executed at all. Since persistent=0 will still write 1, persistent=256 is used to accomplish this(256&255) = 0, and since 256 != 0, it will properly write 0.

Since states -3~-1 are meant to (almost) always be executed, they do not(except for when your StateNo happens to be one of these special values) rely on any persistence-based system, and are exempt from this limit.


HitAttr

This lists the types of attacks belonging to a particular HitDef/Projectile, reversible by a particular ReversalDef, a character can/can’t be hit by when a HitBy/NotHitBy is executed, and are overriden by a particular HitOverride. These are written in the format of PRIMARY,SECONDARY.

The primary field can be any combination of the following:

The secondary field can be any comma-delimited combination of the following:

When 2 HitAttrs need to interact with one another for whatever reason, if both attrs can “mask each other,” the interaction is considered a success. In other words, if the combination of a component in each field is present in both attrs, it’s successful.

For example, a HitOverride with attr S,NA will override Projectiles with attrs SC,NA and SCA,AA,AT,AP because each of the latter contains both S in their primary fields and NA in their secondary fields, but not A,NA or S,NT as one field contains something missing from the other.
Similarly, a ReversalDef with reversal.attr SCA,AA,AT will reverse HitDefs with attrs C,HA and S,AA,AT because each field is contained within the former, but not SCA,AP because the ,AP component is not.
Because of this masking property, if either field is empty, any interaction relying on attrs will always fail. Keeping the primary field empty is the mechanism behind “unreversible HitDefs” and is part of the reason why NotHitBy with a value of SCA is actually optimal.


RETURN TO
SUBLIME TO GOD
CONTENTS