A new potential side project led me back to my old PG notes, they in turn led me more properly into the PG.MAC source code, and while I didn't find answers to the raw-mode questions I had, I did conclusively find the section related to parsing the input stream for custom joypad processing. Nocash got remarkably close in his analysis; I can only really provide confirmation.
Omitting the very first header byte, packet length, which (perhaps naturally) does not appear to be part of the TEMPLATE data proper, the structure does appear to be a header byte preceding N 16-bit gesture definitions, followed by a second header byte and N 8-bit gesture-logic statements.
Specifically,
Line 2041 of PG.MAC begins what is likely the parsing section for what were previously termed the “16-bit opcodes”, referring to a “gesture header byte” and a “gesture data byte”
I am assuming TEMPLATE is the address of the start of the received message structure.
First, an initial gesture address GADDR is computed (1 byte after TEMPLATE) and cached.
Then, the contents of address TEMPLATE are loaded, masked with 0x0F, and interpreted as the count of gestures to follow; consistent with earlier reverse-engineering of the “16-bit Block Header.”
If >0, this count is stored in NYREG and gesture loop counter GNUM is initialized to 1.
Looping over an incrementing GADDR, 2 bytes are read (cached by address) for each gesture entry:
The first byte is a header and gets masked by 0x3F prior to processing. It would appear this mask is used to ignore “last state” and “1-shot” flag bits.
The second byte is a data byte; cached in X.
If the entire gesture header (after the 3F mask) is 0x00, this is a noop (“A gesture type of 00 would be flex with no fingers set in the bitmap”).
otherwise, the header byte is masked with 0x03 and is identified as one of
00b = flex-only
01b = position
10b = orientation
11b = “D. POSITION” (unknown term)
Values of 0 and 2 (00b and 10b) are explicitly tested to jump to respective GFLEX and GORIENT subroutines; 1 and 3 (01b and 11b) fall through, caching some convenience variables before entering the position subroutine GPOSITION.
GPOSITION interprets the cached X data byte as a “vector” lookup. It RLCs the value (presumably arithmetic rotate-left with carry) to double it, then masks it with 0x06, apparently to isolate vales (in the 2 masked-in bits) of
00b = X-axis
01b = Y-axis
10b = Z-axis
11b = don’t-care
before adding the masked value to a base address, using the resulting address (by storing it in B and then loading from the addresses contained in “B+” (postincrement) and B) to look up what appears to be the current and old position vectors (on that axis?)
Flow then JSRs into GADB, with a comment indicating that the data pointer in B is to the gesture header byte (presumably upon return, as it is not so going in).
The header (data at address B) is bit-tested (REL = bit 2, according to PGCON.INC) to determine if an absolute or relative gesture is being specified; relative jumps to subroutine RELATIVE, absolute falls through.
ABSOLUTE evaluation logic appears, in a nutshell, to:
- check whether the gesture data byte is direction-consistent with the current ongoing direction vector (CURR) sign, aborting/resetting detection otherwise
- take the absolute value of the current vector (CURR)
- shift-mask the data byte (X) down to 6 bits even: 2 arithmetic right rotations followed by a mask with 0x3E, retaining 2 of the top 4 and 3 of the bottom 4 bits
- test/return if the processed data value is greater than the CURR ongoing gesture vector
subsequent comments/subroutines indicate that only the largest single-axis (x/y/z) gesture so-detected will return as true/triggered, and only if it was previously false.
RELATIVE evaluation logic is trickier, and appears to
- look up a “turning point” (cached in TURN) by first masking the data byte (X) by 0x03 to get an axis code (as before) then adding that to TURNX (presumably a table address), and reading the data in the resultant address (via B)
- subtract the TURN point from the CURR current vector (cacheing the result in CMT)
- GADB-lookup the header and shift-mask it into a “RESET” value: 1 arithmetic right rotate, mask with 0x1C to “Keep 5 bit magnitude, 3 bit resolution” and cache it in CREG. So apparently the original header was a 2-bit requested data type and a 3-bit reset value of still-ambiguous function.
- perform a sophisticated walk down a table of tristate gesture component definitions, continuing to track the gesture I believe so long as the current gesture is positively matched (PTRUE) and the next gesture is not yet evaluated (RFALSE) as opposed to definitively nonmatched (NTRUE); PTRUE and NTRUE being determined by +/- difference from SET (X data value, processed to 6-bit similarly to before)
- reset the turn point for detection if the current vector (C===CURR) and turn point (T===CMT===CURR-TURN) both exceed the last vector(?); fail if the difference between the current vector and turn point exceeds the reset threshold CREG as previously computed. Meaning the original header was a 2-bit requested data type and a 3-bit reset sensitivity, or to borrow the format of subsequent comments:
Code: Select all
;
; Motion gesture Header byte:
; Last /1 shot / RESET (3) /RELATIVE (1) / 0 / 1 ;
;
; Data byte:
; SET (5) / DIRECTION / AXIS (2) /
;
Finger gesture processing is probably best summarized by the big comment
Code: Select all
;
; Flex gesture Header byte:
; <-- second finger <-- first finger <--
; Last /1 shot /THUMB /INDEX /MIDDLE /RING / 0 / 0 ;
;
; Data byte - Set bitmap: 2nd finger / first finger
; 3 / 2 / 1 / 0 / 3 / 2 / 1 / 0 /
;
; Finger map specifies 1 or 2 fingers. If both are true,
; gesture becomes true. If either are false, gesture becomes false.
indicating a system of looking for “bitmap” levels on either 1 or 2 fingers and returning an AND match
likewise with orientation
Code: Select all
; Orientation gesture Header byte:
;
; Last /1 shot / / / / / 1 / 0 ;
;
; Data byte:
;
; End position / Start position ;
;
which match nocash’s analysis rather precisely.
The only mystery is whether the single 0x0800 entry present in the gesture block of the “raw mode” packet is anything more significant than a vacuous middle finger.
Parsing then proceeds to a “Gesture logic” (line 2499) statement parser/processor which begins with a check of bit 7 of (presumably) what we’ve been calling the 8-bit section header, aborting back to the main loop if true (meaning the “extra 6 bytes” nocash observed are doubly confusing…)
From here (line 2506), things begin to get crazy, appearing (as nocash surmised) to be a whole metalanguage of “logic statements,” “indent states,” truth tables, processes, etc., implemented in assembly, to act on bytecode, all for the purpose of chaining the defined gestures (16-bit opcodes) into evaluation sequences to yield true/false button states.
The 8-bit opcodes appear to reduce to control codes in the low 3 bits of the high nybble as defined
Code: Select all
LOGJMP: ; Logic statement jump table
.BYTE L(LTMPN) ; 0
.BYTE L(LIFG) ; 1
.BYTE L(LTIFG) ; 2
.BYTE L(LEIFG) ; 3
.BYTE L(LANDG) ; 4
.BYTE L(LORG) ; 5
.BYTE L(LENDIF) ; 6
.BYTE L(LELSEDO) ; 7
Those 8 control codes jump to handlers with header comments as follows:
LTMPN
Code: Select all
; Load template N now, then restart.
editorial note: “template” appears to be the internal term for a set of gestures + logic, or in our nomenclature, 16-bit + 8-bit opcodes
LIFG
Code: Select all
;
; "IF (G)"
; evaluates the state of gesture G and sets the state
; of the current indent level accordingly if the state of the
; previous indent level is true.
;
LTIFG
Code: Select all
;
; "THEN IF (G)"
; increments the indent level if the indent flag
; is clear and then performs an IF (G)
;
LEIFG
Code: Select all
;
; "ELSE IF (G)"
; decrements the indent level and clears the indent flag.
;
LANDG
Code: Select all
;
; "AND (G)"
; evaluate the state of gesture G and perform a logical
; AND with the state of the current indent level
;
LORG
Code: Select all
;
; "OR (G)"
; evaluate the state of gesture G and perform a logical
; OR with the state of the current indent level
;
LENDIF
Code: Select all
;
; "ENDIF"
; decrements the indent level
;
LESLEDO
Code: Select all
;
; "ELSE DO"
; pre-decrement the indent level, set current state,
; post-increment the level and set the indent flag.
;
For standard statements (high nybble 0-7), the low nybble is parsed by the EVAL subroutine, which farms the actual parsing off down another rabbit hole, but it looks like it contains a 1-based (1-16 range) index for the 16-bit gesture entry to operate on. Actual parsing process is
- load the byte into A
- mask in the low nybble (by AND with 0x0F)
- subtract 1
- load into B the address of either a (presumably) gesture evaluation state array (GSTAT1) if A<=7 or status flags (GSTAT2) if A>7
- mask down to the low 3 bits (AND 0x07)
- use headache-inducing indirect lookup magic to compute 2^A and store it back in A
- use A as a mask into the data at B (AND A,
) and return whether that result is true (0xFF) or false (0x0)
A value of 0 in the low nybble appears to just set a flag to trigger a beep.
A value of 8 or higher in the high nybble indicates THEN (0x8_-0xB_) or ELSE (0xC_+) commands, which appear at first glance to conditionally increment the “indent” level and proceed based on the truthiness of the previous statement or decrement the “indent” level and update the current truthiness consistent with prior and current level results. They also both flow through to a further DoAction handler which calls out that 0xB_ or 0xF_ statements invert the referenced gesture. Subsequent lines seem to indicate this is an inversion of the gesture’s result status or corresponding output bit/byte.
The low nybble, if nonzero, triggers actions based on previously defined gestures.
If >0x08, special handling (DoSpecial) is invoked. This turns out to be another straightforward jump table:
Code: Select all
SPECJP: ; Jump table for special actions
.BYTE L(EndAction) ; 9
.BYTE L(CTR1CE) ; 10
.BYTE L(CentAll) ; 11
.BYTE L(CentX) ; 12
.BYTE L(CentY) ; 13
.BYTE L(CentZ) ; 14
.BYTE L(ResAct) ; 15
for noop (EndAct), conditionally resetting everything if it hasn’t been done before (CTR1CE), centering All/X/Y/Z axes (Cent*), or resetting the flex, center-once and beep flags (ResAct).
Otherwise, the corresponding button-bit of the NES joypad is selected (modulo a swap flag which can cause A and B (0x07 and 0x08) to be swapped) and cached on the stack. The original instruction’s bits masked by 0x30 are then checked to see if the action being defined should be “pulsed 2” (0x20), “pulsed 1” (0x10) or continuous (0x0). I.e., out of the original high nybble, which by now we have determined to be on the 0x8_-0xF_ range, the codes 0x8_ and 0xC_ are continuous actions, 0x9_ and 0xD_ are “pulsed 1”, and 0xA_ and 0xE_ are “pulsed 2”. The remaining 0xB_ and 0xF_ were covered earlier in the inversion clause.
So basically, again in a representation akin to other source comments,
Code: Select all
;
; Logic Statement
; EXT / STATEMENT (3) / OPSET / OPERAND (3) /
;
If EXT is 0 then STATEMENT is a basic control command, and based on OPSET, the command is evaluated with the current value of either the gesture (OPSET=0) or status flag (OPSET=1) indexed by OPERAND.
if EXT is 1 then STATEMENT is an action command as
000b = THEN continuous trigger
001b = THEN 1-pulse trigger
010b = THEN 2-pulse trigger
011b = THEN inverted trigger
100b = ELSE continuous trigger
101b = ELSE 1-pulse trigger
110b = ELSE 2-pulse trigger
111b = ELSE inverted trigger
For further help interpreting, it may be useful to consult a COP888 datasheet. I found one at
http://www.engineering.uiowa.edu/sites/ ... 010830.pdf