Deep Buffering
Introduction
Deep Buffering is a slightly more advanced, more fine-grained approach to handling command buffering in the M.U.G.E.N engine co-developed by Vans and Jesuszilla. It is a direct descendant of the Tiny Buffering system by Vans. What makes Deep Buffering unique is that it completely bypasses M.U.G.E.N's command sequence interpreter in favor of its own implementation through use of bitwise arithmetic. It is based on Capcom vs. SNK 2's command buffering and interpreter.
Differences from Tiny Buffering
The main difference between Deep Buffering and Tiny Buffering is that Deep Buffering assigns an internal timer to each cardinal direction or button rather than one for the entire command. This means that the entire input window for the command is extended with each button or direction that is added to the sequence. This is accomplished by interpreting only cardinal directions and button presses individually, rather than relying on M.U.G.E.N's command interpreter to indicate when the command sequence has been completed. For that reason, Deep Buffering can be seen as more of a complete command interpreter rather than strictly a buffering implementation.
Structure
There are three variables for both directions and buttons, adding up to a total of 6 variables to indicate the various states for each of the two "primitives": Press, Release, and Hold.
Primitives
In the Deep Buffering system, directions and buttons are separated and considered their own "primitives." Due to lack of analog support in M.U.G.E.N (and 2D fighters in general), each button and direction can be implemented as one bit indicating whether the input is on or off. This makes for a compact, memory efficient and logically efficient system.
States
There are three states for each primitive: Press, Hold, and Release. Each state is calculated as follows:
- Press - The initial state. The corresponding bit is set to 1 as soon as M.U.G.E.N interprets the press for the button. This uses the button press/hold directly provided by M.U.G.E.N.
- Hold - Calculated at the same time as the initial press. This is determined by taking the bitwise OR of the last command in the Hold variable after its elements have been shifted by 1 tick (4 bits right for directions, and 7 bits right for buttons) and the current Hold state. This also uses the button press/hold directly provided by M.U.G.E.N.
- Release - Calculated 1 tick later than the last hold. This is calculated by checking the bits of the last command in the Hold variable after its elements have been shifted by 1 tick (4 bits right for directions, and 7 bits right for buttons) and the current Hold state.
As an example, consider the following scenario of a player pressing the a
button. Contrary to the structure and calculations, Hold is actually calculated first.
Assumptions:
- Button buffering time is 2 ticks.
- Direction buffering time is 2 ticks.
Tick 1: No input Tick 2 Tick 1 Var(1) - 0000000 0000000 ; Hold Var(0) - 0000000 0000000 ; Press Var(2) - 0000000 0000000 ; Release
Tick 2: User presses 'a' Tick 2 Tick 1 Var(1) - 0000000 0000100 ; Hold Var(0) - 0000000 0000100 ; Press Var(2) - 0000000 0000000 ; Release
Tick 3: No input Tick 2 Tick 1 Var(1) - 0000100 0000000 ; Hold Var(0) - 0000100 0000000 ; Press Var(2) - 0000000 0000100 ; Release
Note that the XOR for the Release is only calculated for the upper 7 bits, which corresponds to the current state of the command. This is done so that it doesn't interfere with the lower bits, which only store the previous state exactly as it was the tick before.
Buttons
Vars 0-2 are used for button states. Var(0) is used to indicate the initial press, Var(1) is used to indicate hold, and Var(2) is used to indicate release.
The binary structure for the button variables is as follows:
Button: xyzsabc Bits: 0000000 Bit #: 1234567
Due to the storage mechanism, it is only possible in this system to hold up to 4 ticks of buffering data for all buttons.
Directions
Vars 3-5 are used for direction states. Var(3) is used to indicate the initial press, Var(4) is used to indicate hold, and Var(5) is used to indicate release.
The binary structure for the direction variables is as follows:
Button: UDBF Bits: 0000 Bit #: 1234
Due to the storage mechanism, it is only possible in this system to hold up to 8 ticks of buffering data for all directions.
Algorithm
The algorithm involved in interpreting a command is very straightforward and uses only bitwise operations (or approximations of them, due to lack of bitwise shift operators in the CNS language).
- Let represent the number of ticks to buffer buttons.
- Let represent the number of ticks to buffer directions.
- Let represent the number of ticks to buffer each element.
The basic algorithm operates every tick as follows:
- Store the current hold state in the upper bits of the hold variables.
- Store the current press state in the upper bits of the press variables.
- Compare the lower bits of the hold variable with the upper bits in the release variable to get the current release state. A button is considered to be released when the uppermost bits corresponding to the command are 0 bits, and any of the lower bits are non-zero.
- Divide every button variable by to shift the bits down by 7 bits.
- Divide every direction variable by to shift the bits down by 4 bits.
-
Check if any of the inputs in any of the variables are active for the current sequence in the full command, then:
- If it's active, zero out the current sequence bit in the command check, set the next sequence bit to 1, and set the lower 4 bits to .
- If the time has expired for the current sequence (all the first 4 bits are 0), then reset the entire command check to 0.
- Check for whether or not the uppermost required sequence bit is set. If it is, then the command is complete.
Command Interpretation
An element of a command is defined as an individual component normally separated by commas. in /D,DF,F+x
, there are 3 elements: /D
, DF
, and F+x
. Conversely, /D,DF,F,x
contains 4 elements
Each command sequence is stored and checked in its own variable with the first 4 bits reserved for , which can store an element for up to 15 ticks. The binary structure of a command is as follows:
T: Element buffer time (t_e) E: Element to check TTTTEEEEEEEEEEEEEEEEEEEEEEEEEEEE 00000000000000000000000000000000
With that said, it is possible in this system to store and interpret any command sequence with 28 or fewer elements with each up to 15 ticks of buffer time.
The sequence for interpreting a Hadouken (/D,DF,F,x
) could go as follows, if the buffering times for buttons and directions are both 2 ( and ) and the element buffering time is 9 (). All bits go from least significant bit on the left to most significant bit on the right. Thus, the first bit is 1, the second is 2, the third is 4, etc.
Tick 1: No input Var(0) (Button Press): 0000000 0000000 Var(1) (Button Hold): 0000000 0000000 Var(2) (Button Release): 0000000 0000000 Var(3) (Direction Press): 0000 0000 Var(4) (Direction Hold): 0000 0000 Var(5) (Direction Release): 0000 0000 Var(20) (Check): 0000 0000000000000000000000000000
Tick 2: User inputs D Var(0) (Button Press): 0000000 0000000 Var(1) (Button Hold): 0000000 0000000 Var(2) (Button Release): 0000000 0000000 Var(3) (Direction Press): 0000 0100 Var(4) (Direction Hold): 0000 0100 Var(5) (Direction Release): 0000 0000 Var(20) (Check): 1001 1000000000000000000000000000
Tick 3: No input Var(0) (Button Press): 0000000 0000000 Var(1) (Button Hold): 0000000 0000000 Var(2) (Button Release): 0000000 0000000 Var(3) (Direction Press): 0100 0000 Var(4) (Direction Hold): 0100 0000 Var(5) (Direction Release): 0000 0100 Var(20) (Check): 0001 1000000000000000000000000000
Tick 4: No input Var(0) (Button Press): 0000000 0000000 Var(1) (Button Hold): 0000000 0000000 Var(2) (Button Release): 0000000 0000000 Var(3) (Direction Press): 0000 0000 Var(4) (Direction Hold): 0000 0000 Var(5) (Direction Release): 0100 0000 Var(20) (Check): 1110 1000000000000000000000000000
Tick 5: User inputs DF Var(0) (Button Press): 0000000 0000000 Var(1) (Button Hold): 0000000 0000000 Var(2) (Button Release): 0000000 0000000 Var(3) (Direction Press): 0000 0101 Var(4) (Direction Hold): 0000 0101 Var(5) (Direction Release): 0000 0000 Var(20) (Check): 1001 0100000000000000000000000000 ; Reset to 9, next element check
Tick 6: User inputs F Var(0) (Button Press): 0000000 0000000 Var(1) (Button Hold): 0000000 0000000 Var(2) (Button Release): 0000000 0000000 Var(3) (Direction Press): 0101 0001 Var(4) (Direction Hold): 0101 0001 Var(5) (Direction Release): 0000 0100 Var(20) (Check): 1001 0010000000000000000000000000 ; Reset to 9, next element check (command considered complete)
Tick 7: User inputs x Var(0) (Button Press): 0000000 1000000 Var(1) (Button Hold): 0000000 1000000 Var(2) (Button Release): 0000000 0000000 Var(3) (Direction Press): 0001 0000 Var(4) (Direction Hold): 0001 0000 Var(5) (Direction Release): 0100 0000 Var(20) (Check): 0001 0010000000000000000000000000 ; Still counting down
Tick 8: No input Var(0) (Button Press): 1000000 0000000 Var(1) (Button Hold): 1000000 0000000 Var(2) (Button Release): 0000000 1000000 Var(3) (Direction Press): 0000 0000 Var(4) (Direction Hold): 0000 0000 Var(5) (Direction Release): 0000 0000 Var(20) (Check): 1110 0010000000000000000000000000 ; Still counting down
Note how in this system, the last press is not interpreted by the system itself. This is so individual buttons can still buffer as usual without any special cases given to the last possible command. The final input in the sequence is used as a trigger to perform the next action (stage change, animation change, etc.) in conjunction with the command check variable. This means that the base command need only be defined once, with the only necessary change being the trigger to detect the final input. This allows motions to be reused and interpreted as one, which means fewer variables are necessary to interpret each command.
The logic for the Hadouken example (helper side) can thus be constructed as follows:
[State 10380, QCF Init] type = Null trigger1 = !Var(20) && ((var(4)&240) = 32 || (var(4)&15) = 2) trigger1 = e||(var(20) := 9 + (2**4)) trigger2 = (Var(20)&(2**5 - 1)) > (2**4) && ((var(3)&240) = 160 || (var(3)&15) = 10) trigger2 = e||(var(20) := 9 + (2**5)) trigger3 = (Var(20)&(2**6 - 1)) > (2**5) && ((var(3)&240) = 128 || (var(3)&15) = 8) trigger3 = e||(var(20) := 9 + (2**6)) trigger4 = Var(20) && (Var(20)&15) = 0 trigger4 = e||(var(20) := 0) ignorehitpause = 1
... and this can be used along with the following trigger:
triggerall = (helper(10371), Var(20)&(2**7 - 1)) > (2**6) triggerall = (helper(10371), var(0)&129)) > 0
... to detect the whole command. In most cases, this detection would be done in the .CMD for the ChangeState:
[State -1, Hadouken] type = ChangeState value = 1000 triggerall = !AILevel triggerall = numHelper(10371) triggerall = (helper(10371), Var(20)&(2**7 - 1)) > (2**6) ; Command detect triggerall = (helper(10371), Var(0)&16383) > 0 || ((helper(10371), Var(2)&16383) > 0 && !Var(30)) ; Button detect (x,y,z,~x,~y, or ~z) triggerall = statetype != A triggerall = roundstate = 2 trigger1 = ctrl || (StateNo = 100 && AnimElemTime(2) >1) || StateNo = 101 || StateNo = 40 || (StateNo = 52 && Anim = 47 && Time >= 2) ignorehitpause = 0
Note how the second triggerall
was changed from var(0)&129
to var(0)&16383
. This was done in order to show how bit masks can be utilized to check for any of x,y,z,~x,~y,~z
rather than checking each one individually.
Bitmasking
As shown in the example code, a mask can be used with the bitwise AND operator to check for several buttons at once. The mask depends on which buttons should be checked as well as and . The more ticks that are utilized to buffer a button or direction, the larger the number in the mask will be.
For example, with and , these common masks could be used:
- 14448 - All kicks (all ticks)
- 903 - All punches (all ticks)
- 16383 - All punches & kicks (all ticks)
- 2193 - x+a (all ticks)
Advantages & Disadvantages
The main advantage of Deep Buffering is that it provides a complete implementation for both command interpretation as well as buffering. Users have control over each individual element and how much time should be given to complete it, and as mentioned before, the overall command window expands with each element added to the sequence. However, this can be hard for users to read, and as such, tools exist such as Jesuszilla's online Deep Buffering Converter to make defining commands in this system trivial. However, some knowledge of bitwise functions is still required in order to use it to its fullest potential.
Another disadvantage is that some games (particularly older ones) may not use this algorithm for interpreting commands, opting instead to use a full window check. For such use cases, Tiny Buffering is recommended.
Because only cardinal directions and buttons need to be checked, only the most basic commands need to be defined in the .CMD, making Deep Buffering a prime candidate for use in a tag system.
Like all buffering helpers, this trivializes moves that require a target to go back to their own set of states when they press a button, such as Felicia's Cat Ride.
Simplifying Triggers
By using one extra variable, readability of characters that utilize Deep Buffering can be improved. The idea is to store each command as its own bit in a separate variable, that way only one single variable needs to be checked for the command itself in the .CMD. Bitwise operators are still required, but access is much simpler than creating large masks of arbitrary command lengths. The following example is taken from Jesuszilla's PotS-style Felicia's buffering.vns:
[State 10380, Command Active] type = Null trigger1 = e||(var(59) := 0) ; INIT trigger1 = e||(var(59) := (var(59)|((2**cond(P2Dist X < 0, 1, 0))*((Var(20)&(2**7 - 1)) > (2**6))))) ; QCF trigger1 = e||(var(59) := (var(59)|((2**cond(P2Dist X < 0, 0, 1))*((Var(21)&(2**7 - 1)) > (2**6))))) ; QCB trigger1 = e||(var(59) := (var(59)|((2**cond(P2Dist X < 0, 3, 2))*((Var(22)&(2**7 - 1)) > (2**6))))) ; DP trigger1 = e||(var(59) := (var(59)|((2**cond(P2Dist X < 0, 2, 3))*((Var(23)&(2**7 - 1)) > (2**6))))) ; rDP trigger1 = e||(var(59) := (var(59)|((2**cond(P2Dist X < 0, 5, 4))*((Var(24)&(2**9 - 1)) > (2**8))))) ; HCB trigger1 = e||(var(59) := (var(59)|((2**cond(P2Dist X < 0, 4, 5))*((Var(25)&(2**9 - 1)) > (2**8))))) ; HCF trigger1 = e||(var(59) := (var(59)|((2**cond(P2Dist X < 0, 7, 6))*((Var(40)&(2**10 - 1)) > (2**9))))) ; HCB,F trigger1 = e||(var(59) := (var(59)|((2**cond(P2Dist X < 0, 6, 7))*((Var(41)&(2**10 - 1)) > (2**9))))) ; HCF,B trigger1 = e||(var(59) := (var(59)|((2**cond(P2Dist X < 0, 9, 8))*((Var(42)&(2**9 - 1)) > (2**8))))) ; HCFx2 trigger1 = e||(var(59) := (var(59)|((2**cond(P2Dist X < 0, 8, 9))*((Var(43)&(2**9 - 1)) > (2**8))))) ; HCBx2 trigger1 = e||(var(59) := (var(59)|((2**10)* ((Var(53)&(2**6 - 1)) > (2**5))))) ; DD trigger1 = e||(var(59) := (var(59)|((2**29)* ((Var(54)&(2**6 - 1)) > (2**5))))) ; DU trigger1 = e||(var(59) := (var(59)|(cond(P2Dist X < 0, -2147483648, 2**30)*((Var(55)&(2**6 - 1)) > (2**5))))) ; FF trigger1 = e||(var(59) := (var(59)|(cond(P2Dist X < 0, 2**30, -2147483648)*((Var(56)&(2**6 - 1)) > (2**5))))) ; BB ignorehitpause = 1
Each special command is given its own bit and reversed as necessary to indicate flipped commands. The QCF command can be checked with one simple trigger with this:
triggerall = (helper(10371), Var(59)&(2**0)) > 0
As usual, 32 bits can be utilized, and thus, up to 32 unique commands can be detected with one variable. This is an addition to Deep Buffering, not a complete replacement, so the base system is still required for this to function.
Commerical Games
Deep Buffering is heavily inspired by Capcom vs. SNK 2's command interpreter and buffer, and in fact, uses the exact same algorithm to perform its calculations. Given the industry standard of reusing code, many other games utilize this same method.
- Vampire Savior (all editions including Vampire Savior 2, Vampire Hunter 2, and Vampire Chronicle)
- Street Fighter Zero 3 (all editions)
- Capcom vs. SNK (regular and PRO)
- Capcom vs. SNK 2 (regular and EO)
- Capcom Fighting Jam