Deep Buffering

From mugen-net
Revision as of 21:07, 7 August 2021 by 136.144.43.105 (talk) (→‎States)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Introduction

Deep Buffering (originally known as "Buffering Step 2") 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 hold directly provided by M.U.G.E.N. and the direction hold/press 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:

  1. Store the current hold state in the upper bits of the hold variables.
  2. Store the current press state in the upper bits of the press variables.
  3. 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.
  4. Divide every button variable by to shift the bits down by 7 bits.
  5. Divide every direction variable by to shift the bits down by 4 bits.
  6. 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, set the upper 28 bits to the new sequence number, 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.
  7. Check for whether or not the sequence number matches the final element number. If it does, 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 268,435,455 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 1100000000000000000000000000 ; 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 1100000000000000000000000000 ; 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 1100000000000000000000000000 ; 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 10372, QCF Init]
type = Null
trigger1 = !Var(20)  && ((var(4)&240) = 32 || (var(4)&15) = 2)
trigger1 = e||(var(20) := 9 + (2**4)*1)

trigger2 = (Var(20)/(2**4)) = 1 && ((var(3)&240) = 160 || (var(3)&15) = 10)
trigger2 = e||(var(20) := 9 + (2**4)*2)

trigger3 = (Var(20)/(2**4)) = 2 && ((var(3)&240) = 128 || (var(3)&15) = 8)
trigger3 = e||(var(20) := 9 + (2**4)*3)

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(10372), Var(20)/(2**4)) = 3
triggerall = (helper(10372), 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(10372)
triggerall = (helper(10372), Var(20)/(2**4)) = 3                                                  ; Command detect
triggerall = (helper(10372), Var(0)&16383) > 0 || ((helper(10372), 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 CvS2 Eagle buffering.vns:

[State 10372, Command Active]
type = Null
trigger1 = e||(var(59) := 0)												; INIT
trigger1 = e||(var(59) := (var(59)|((2**29)*((Var(54)/(2**4)) = 1))))		; DU
trigger1 = e||(var(59) := (var(59)|((2**30)*((Var(55)/(2**4)) = 2))))		; FF
trigger1 = e||(var(59) := (var(59)|(-2147483648*((Var(56)/(2**4)) = 2))))	; BB
trigger1 = !root, numProjID(131035)											; Chizuru seal
trigger1 = e||(var(59) := (var(59)|((2**0)*((Var(20)/(2**4)) = 3))))		; QCF
trigger1 = e||(var(59) := (var(59)|((2**1)*((Var(21)/(2**4)) = 3))))		; QCB
trigger1 = e||(var(59) := (var(59)|((2**2)*((Var(22)/(2**4)) = 3))))		; DP
trigger1 = e||(var(59) := (var(59)|((2**3)*((Var(23)/(2**4)) = 3))))		; rDP
trigger1 = e||(var(59) := (var(59)|((2**4)*((Var(24)/(2**4)) = 5))))		; HCB
trigger1 = e||(var(59) := (var(59)|((2**5)*((Var(25)/(2**4)) = 5))))		; HCF
trigger1 = e||(var(59) := (var(59)|((2**6)*((Var(40)/(2**4)) = 5))))		; QCFx2
trigger1 = e||(var(59) := (var(59)|((2**7)*((Var(41)/(2**4)) = 5))))		; QCBx2
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(10372), 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.

This is completely optional, but is recommended for Street Fighter II characters as the original game uses something similar to indicate and randomize specials.

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. The history of this implementation goes as far back as Street Fighter II: The World Warrior.

  • Street Fighter II Series
  • Vampire Series
  • Street Fighter Zero Series
  • Marvel Series
  • Street Fighter III Series
  • Capcom vs. SNK Series
  • Capcom Fighting Jam

See Also