bcrt2000 Posted January 6 Report Posted January 6 (edited) (Through this remember that SNES is little endian, in many cases you need to byte swap the data to get something readable) 94 SNES has 976 frames. Seems like they put the frame data ahead of each frame's tile data in SNES. Frame data header is 0x10000-0x10F40 (4 bytes per frame, 976 frames) These seem to be 32-bit offsets that are relative from the current code segment, which in this case starts at 0x10000. The first offset is F40, so the first frame's data should be at 0x10F40. Frame data for first frame is 0x10F40-0x10F6B -- which points to tile Data from 0x10F6B-0x1108B Frame 1 uses 9 tiles. 0x120 bytes of tile data. Each tile is 0x20 bytes or 32 bytes of data. All Tile data is approximately 0x10F4C-0x75880 (4bpp planar composite). Here is the Tile data for frame 1 in TM: frame 1 assembled by hand in Photoshop: I don't fully understand how the tiles are being placed on the canvas yet. Here is a look at the Frame Data for a few frames: Frame 1 frame data starts 0x10F40-0x10F6A -- 9 tiles, 2B in length (43) -- assuming tile data = 9*4bytes=36bytes -- 7 bytes extra Frame 1 tile data starts 0x10F6B 03 80 09 00 FA FF 07 00 E2 FF 04 00 A0 00 80 00 2B 00 02 E4 00 00 FA FF E2 FF 00 08 FF FA FF F2 FF 02 08 FF FE FF 02 00 04 08 00 Frame 2 frame data starts 0x1108B-0x110C3 -- 11 tiles, 39 in length (57) - assuming tile data = 11*4bytes=44bytes -- 13 bytes extra Frame 2 tile data starts 0x110C4 05 80 0B 00 F7 FF 09 00 E3 FF 05 00 C0 00 A0 00 39 00 01 E5 00 00 FD FF E3 FF 00 08 FF F7 FF F3 FF 02 08 FF 07 00 FB FF 04 08 00 F8 FF 03 00 05 08 00 00 00 03 00 14 08 00 So a couple of things above threw me off. One-- the frame data is an uneven length. I've since found out that for SNES it doesn't matter as much to align the data to even lengths as it is on Genesis since the CPU can handle unaligned data accesses. And when they can shorten 32-bit data to 16-bit, they do, since the SNES CPU doesn't natively support 32-bit data reads (which can lead to the misalignment). Secondly, there is not outright pattern to why the second frame's data is so much bigger than the first frame-- its only two tiles bigger. It doesn't account for the extra space in the header. Now what I think I do know is that byte 3 = Number of tiles Frame 1 Byte 3 - 0x09 = 9 tiles Frame 2 Byte 3 - 0x0B = 11 tiles I also believe the 17th byte indicates the length of the frame data Frame 1 Byte 17 - 0x2B = 43 bytes Frame 2 Byte 17 - 0x39 = 57 bytes I wanted to see how Frame 2 was assembled: (the above is an approximation-- might be off a pixel on one of the shadow tiles or pants tiles but I believe this mostly right) So this shows me that the tile laying system is sufficiently complex, its similar to how sprites are laid in the frame in the Genesis version-- seems like tiles on SNES can be laid on a per pixel basis. There's a couple of options to figure this out-- one is to find the addframe function in the code. Another is to just mess around with changing values in the frame data for frame 1 and see what happens in the game. I also wanted to find the animation data. The easiest thing to do was to search for SPApflip as it has a bunch of -4096 values which will stand out (below is frame data from NHLPA93 Gen): In NHLPA 93's rom for Genesis (I've been referencing this since this is what I've been recently working with disassembling) you can find the animation data for PFLIP direction 4-7 at 0x52AA-52B9: I asked grok to write me a node script to find `00 F0 ?? ?? 00 F0 ?? 00 F0` and voila, the script found me this at 0xEEA0A-0xEEA19: the puck frames are slightly offset between Genesis and SNES here Genesis references frame 0x18B (395) and 0x18F (399) SNES references frame 0x18D (397) and 0x191 (401) I actually had found the animation frames much easier on 94/95PC because the animation header format was the same between Genesis and PC. Here there is a difference: Genesis animation header is on the left, SNES animation header is on the right. On both, you've got eight 16-bit offsets to direction 0-7's animation data. However, on Genesis its relative from the start of the Animation Header. On SNES I believe its relative from the start of the code segment, which starts at 0xE0000. I traced the data back to the start and end and it looks like the animation data for SNES 94 lives at 0xEE0FC-EFF6E. OK, that's all that's known for now! Edited January 6 by bcrt2000 1 Quote
bcrt2000 Posted January 6 Author Report Posted January 6 I found I believe the equivalent of GetHot-- this is one place in the code that I've found accesses the Frame Data Header. I ran this through Grok and this is what it tells me it does: .9B:C002 read_frame_header_and_adjust_hotspot: ; CODE XREF: sub_9E8C35+43↓P .9B:C002 ; sub_9EEB2E+66↓P .9B:C002 ; Reads frame data pointer from table, extracts signed byte hotspot/offset values at +$12/+$13, .9B:C002 ; applies flip negations based on orientation flags, and optional 90-degree rotation transform. .9B:C002 ; Input: A = index (transferred to Y for RAM lookup) .9B:C002 ; Output: Adjusted X/Y hotspots in word_7E00A5 (X) and word_7E00A9 (Y) .9B:C002 ; Uses: X (preserved), Y (temp), word_7E000C/000E (temp long pointer) .9B:C002 .9B:C002 PHX ; Preserve original X .9B:C003 TAY ; Y = input index .9B:C004 LDA word_7E0D97,Y ; Load frame number or state from RAM table .9B:C007 DEC ; frame_index - 1 .9B:C008 ASL ; \ .9B:C009 ASL ; / *4 for table offset (assuming 4-byte entries) .9B:C00A TAX ; X = table index .9B:C00B LDA frame_data_header,X ; Load low 16-bit offset to frame data .9B:C00F CLC .9B:C010 ADC #$8000 ; Add base address low .9B:C013 STA D,word_7E000C ; Store to temp pointer low .9B:C015 LDA word_828002,X ; Load high/bank offset .9B:C019 ADC #$82 ; Add base bank .9B:C01C STA D,word_7E000E ; Store to temp pointer bank (forms 24-bit pointer) .9B:C01E TYX ; Y = original X (unused here?) .9B:C01F LDY #$12 ; Y = $12 (offset 18 decimal into frame data) .9B:C022 LDA [D,word_7E000C],Y ; Load byte at frame_data + $12 (likely X hotspot/offset) .9B:C024 AND #$FF ; Mask to byte .9B:C027 CMP #$80 ; Check if signed negative (>= $80) .9B:C02A BCC sign_extend_x_done ; If < $80, positive - skip sign extend .9B:C02C ORA #$FF00 ; Else, sign extend to negative word .9B:C02F .9B:C02F sign_extend_x_done: ; CODE XREF: read_frame_header_and_adjust_hotspot+28↑j .9B:C02F STA D,word_7E00A5 ; Store adjusted X hotspot/offset .9B:C031 INY ; Y = $13 (offset 19 decimal) .9B:C032 LDA [D,word_7E000C],Y ; Load byte at frame_data + $13 (likely Y hotspot/offset) .9B:C034 AND #$FF ; Mask to byte .9B:C037 CMP #$80 ; Check if signed negative .9B:C03A BCC sign_extend_y_done ; If < $80, positive - skip .9B:C03C ORA #$FF00 ; Else, sign extend .9B:C03F .9B:C03F sign_extend_y_done: ; CODE XREF: read_frame_header_and_adjust_hotspot+38↑j .9B:C03F STA D,word_7E00A9 ; Store adjusted Y hotspot/offset .9B:C041 LDA word_7E0E03,X ; Load orientation flags for this frame .9B:C044 BIT #$800 ; Test bit 11 (horizontal flip/mirror flag?) .9B:C047 BEQ no_hflip ; If clear, skip negate .9B:C049 LDA #0 .9B:C04C SEC .9B:C04D SBC D,word_7E00A5 ; Negate X .9B:C04F STA D,word_7E00A5 .9B:C051 .9B:C051 no_hflip: ; CODE XREF: read_frame_header_and_adjust_hotspot+45↑j .9B:C051 LDA word_7E0E03,X ; Reload flags (redundant, but original) .9B:C054 BIT #$1000 ; Test bit 12 (vertical flip/mirror flag?) .9B:C057 BNE no_vflip ; If set, skip negate (inverted logic: clear = apply flip) .9B:C059 LDA #0 .9B:C05C SEC .9B:C05D SBC D,word_7E00A9 ; Negate Y .9B:C05F STA D,word_7E00A9 .9B:C061 .9B:C061 no_vflip: ; CODE XREF: read_frame_header_and_adjust_hotspot+55↑j .9B:C061 LDA word_7E15DA ; Load potential rotation/mode flag .9B:C064 BEQ no_rotation ; If zero, skip transform .9B:C066 LDA D,word_7E00A5 ; Load current X .9B:C068 LDY D,word_7E00A9 ; Load current Y to Y reg .9B:C06A EOR #$FFFF .9B:C06D INC ; Negate X (+1 for two's complement) .9B:C06E STA D,word_7E00A9 ; Store -old_X as new Y .9B:C070 STY D,word_7E00A5 ; Store old_Y as new X (90° clockwise rotation) .9B:C072 .9B:C072 no_rotation: ; CODE XREF: read_frame_header_and_adjust_hotspot+62↑j .9B:C072 PLX ; Restore original X .9B:C073 RTL ; Return long .9B:C073 ; End of function read_frame_header_and_adjust_hotspot The function (read_frame_data_header, which prepares frame hotspot/offset values) only reads two specific bytes from the provided frame data: the signed byte at offset $12 (decimal 18, likely a horizontal/X hotspot or attachment offset) and the signed byte at offset $13 (decimal 19, likely a vertical/Y hotspot or attachment offset). These are sign-extended to 16-bit words before further processing (conditional negations for flips and an optional 90° rotation transform, based on external flags not provided here). Frame 1 Byte at offset $12 (18): $02 (sign-extended to $0002 or +2 decimal). Byte at offset $13 (19): $E4 (sign-extended to $FFE4 or -28 decimal). Frame 2 Byte at offset $12 (18): $01 (sign-extended to $0001 or +1 decimal). Byte at offset $13 (19): $E5 (sign-extended to $FFE5 or -27 decimal). --- So at least that's potentially 2 more values from the header that we know. 1 Quote
bcrt2000 Posted January 6 Author Report Posted January 6 (edited) A couple of things-- apparently that wasn't the full GetHot function. It was originally detected as data but when analyzing it it seems like it reads bytes 0x12-0x15. 0x14-15 is another X,Y pair-- potentially a secondary hotspot or some sort of bounding box. It doesn't matter in frame1/2 case as its zero'd out on both. Secondly, I think I was rash to dismiss the idea that SNES didn't use a sprite system like Genesis. I think that explains why the frame data is different lengths. Frame 2 uses more sprites than Frame 1. I think I might have figured out how the data is laid out-- I think the first byte in the Frame Data indicates how many sprites are used, and then after that its 7 bytes per Sprite. Frame 1 Data (Sprite Header): 03 80 09 00 FA FF 07 00 E2 FF 04 00 A0 00 80 00 2B 00 02 E4 00 00 Frame 1 Sprite 1: FA FF E2 FF 00 08 FF Frame 1 Sprite 2: FA FF F2 FF 02 08 FF Frame 1 Sprite 3: FE FF 02 00 04 08 00 Frame 2 Data (Sprite Header): 05 80 0B 00 F7 FF 09 00 E3 FF 05 00 C0 00 A0 00 39 00 01 E5 00 00 Frame 2 Sprite 1: FD FF E3 FF 00 08 FF Frame 2 Sprite 2: F7 FF F3 FF 02 08 FF Frame 2 Sprite 3: 07 00 FB FF 04 08 00 Frame 2 Sprite 4: F8 FF 03 00 05 08 00 Frame 2 Sprite 5: 00 00 03 00 14 08 00 I'll have to look at more frames to see if this pattern holds up. I think byte 1-2 are the X coordinate of the sprite, and byte 3-4 are the Y coordinate of the sprite. And then the remaining bytes must have to do with tile selection, and potentially flip bytes and/or palette selection Frame 1 Data (Sprite Header): 03 80 09 00 FA FF 07 00 E2 FF 04 00 A0 00 80 00 2B 00 02 E4 00 00 Frame 1 Sprite 1: FA FF E2 FF 00 08 FF <- -6, -30 Frame 1 Sprite 2: FA FF F2 FF 02 08 FF <- -6, -14 Frame 1 Sprite 3: FE FF 02 00 04 08 00 <- -2, 2 Frame 2 Data (Sprite Header): 05 80 0B 00 F7 FF 09 00 E3 FF 05 00 C0 00 A0 00 39 00 01 E5 00 00 Frame 2 Sprite 1: FD FF E3 FF 00 08 FF <- -3, -29 Frame 2 Sprite 2: F7 FF F3 FF 02 08 FF <- -9, -13 Frame 2 Sprite 3: 07 00 FB FF 04 08 00 <- 7, -5 Frame 2 Sprite 4: F8 FF 03 00 05 08 00 <- -8, 3 Frame 2 Sprite 5: 00 00 03 00 14 08 00 <- 0, 3 Edited January 6 by bcrt2000 1 Quote
bcrt2000 Posted Saturday at 10:53 PM Author Report Posted Saturday at 10:53 PM (edited) I want to detail the rest of what I know about the sprite format before I put a pause on this for now. At some point I will write a sprite extractor, but I want to focus on 93/94 disassembly to get something compilable out ASAP. Here is the file format for the Frame Data/Sprite Header for NHL 94 SNES that is known so far: I think the one thing that can be confusing is how the tiles are picked-- First is Size-- FF seems to indicate a 2x2 tiled Sprite, and 00 seems to indicate a 1x1 tiled Sprite If its a 1x1 tiled sprite, its simple, the First Tile gets selected. If its a 2x2 tiled sprite though, it selects First Tile, First Tile + 1, (First Tile +1) + Stride, (First Tile + 1) + Stride + 1 So for Frame 1: Frame X Data (Sprite Header): [Number of Sprites] 80 [Number of Tiles] 00 E2 FF 07 00 E2 FF [Stride] 00 A0 00 80 00 [Length of Frame Data] 00 02 E4 [Hostpot 2 X] [Hotspot 2 Y] Frame 1 Data (Sprite Header): 03 80 09 00 FA FF 07 00 E2 FF 04 00 A0 00 80 00 2B 00 02 E4 00 00 Frame X Sprite X: [X offset] [Y offset] [First Tile] [Palette?] [Size] Frame 1 Sprite 1: FA FF E2 FF 00 08 FF <- -6, -30 Frame 1 Sprite 2: FA F2 FF FF 02 08 FF <- -6, -14 Frame 1 Sprite 3: FE FF 02 00 04 08 00 <- -2, 2 Frame 1 has a Stride of 4. Sprite 1: Tile 0, 1, 5, 6 Sprite 2: Tile 2, 3, 7, 8 Sprite 3: Tile 4 Frame 2: Frame X Data (Sprite Header): [Number of Sprites] 80 [Number of Tiles] 00 E2 FF 07 00 E2 FF [Stride] 00 A0 00 80 00 [Length of Frame Data] 00 02 E4 [Hostpot 2 X] [Hotspot 2 Y] Frame 2 Data (Sprite Header): 05 80 0B 00 F7 FF 09 00 E3 FF 05 00 C0 00 A0 00 39 00 01 E5 00 00 Frame X Sprite X: [X offset] [Y offset] [First Tile] [Palette?] [Size] Frame 2 Sprite 1: FD FF E3 FF 00 08 FF <- -3, -29 Frame 2 Sprite 2: F7 FF F3 FF 02 08 FF <- -9, -13 Frame 2 Sprite 3: 07 00 FB FF 04 08 00 <- 7, -5 Frame 2 Sprite 4: F8 FF 03 00 05 08 00 <- -8, 3 Frame 2 Sprite 5: 00 00 03 00 14 08 00 <- 0, 3 Frame 2 has a Stride of 5. Sprite 1: Tile 0,1,6,7 Sprite 2: Tile 2,3,8,9 Sprite 3: Tile 4 Sprite 4: Tile 5 Sprite 5: This is where it gets weird and I don't fully understand-- the value is 20 which is double the value of 10. Could it have something to do with flipping the tile perhaps? I haven't investigated enough. I have to look at more frames to figure this out, and also closely compare how the frame is rendered in the game to how I would compose the frame. Frame X Sprite X: [X offset] [Y offset] [First Tile] [Palette?] [Size] Frame 2 Sprite 1: FD FF E3 FF 00 08 FF <- -3, -29 Frame 2 Sprite 2: F7 FF F3 FF 02 08 FF <- -9, -13 Frame 2 Sprite 3: 07 00 FB FF 04 08 00 <- 7, -5 Frame 2 Sprite 4: F8 FF 03 00 05 08 00 <- -8, 3 Frame 2 Sprite 5: 00 00 03 00 14 08 00 <- 0, 3 So 95%+ of the NHL94 frames format is known at this point IMO. There may be some weird things that pop up that should be easy to figure out given by the limited number of bytes used per sprite. Edited Saturday at 10:57 PM by bcrt2000 1 Quote
vancecookcobxin Posted Monday at 01:18 AM Report Posted Monday at 01:18 AM (edited) I'm not sure if this helps, but I have been using Claude Opus 4.5 to parse through interesting regions of code and had it basically read this post, outlining some information about the animations. I think a lot of it summarizes your stuff, but there might be some interesting tidbits here. As always, cross-reference this before taking it at face value...these assembly banks are massive in terms of tokens, and it could have hallucinated on some of this.: 1. Animation System Overview The NHL '94 SNES animation system consists of three distinct layers: Layer Purpose ROM Location Animation Scripts Sequence of frame IDs + timing $0EE0FC - $0EFF6E (Bank 9E) Frame Header Table 4-byte pointers to Frame Data $010000 - $010F40 (976 × 4 bytes) Frame + Tile Data Sprite composition + 4bpp tiles $010F4C - $075880 1.1 ROM Layout Details $010000 - $010F3F : Frame Header Table (976 entries × 4 bytes = 3904 bytes) Each entry: [Low Word] [High Word] → Relative offset from $010000 $010F40 : First Frame Data begins (Frame 1) $010F6B : First Frame Tile Data begins ... $075880 : End of Tile Data (approximate) $0EE0FC - $0EFF6E : Animation Script Data (relative to code segment $0E0000) 1.2 Key Differences: Genesis vs SNES Aspect Genesis (NHLPA 93) SNES (NHL 94) Animation Header Offsets Relative to header start Relative to code segment ($0E0000) Puck Frame IDs 0x18B (395), 0x18F (399) 0x18D (397), 0x191 (401) Data Alignment Must be even Unaligned OK (CPU handles) Tile Format 4bpp linear 4bpp planar composite 2. RAM Address Map (Per-Player, Indexed by X) Address Name Purpose $11A3,X Script Base Pointer to animation script start within Bank 9D $11C3,X Script Offset Current position within script (advances by 4) $11E3,X Animation Timer Counts down; triggers frame advance when negative $0D97,X Current Sprite ID Frame/Sprite index (1-976) for rendering $14E3,X Action State High-level action (0=None, 1=KO/Pass, 8=Slapshot, etc.) $0ADB Global Speed Timer decrement rate (affects all players) $1163,X Player Speed Factor Per-player speed modifier 3. Core Animation Routine: CODE_9EA425 Location: bank_9E.asm Lines 4305-4408 (Address $9EA425) 4. Animation Script Format 4.1 Script Entry Structure (4 Bytes) Based on the ASM analysis of CODE_9EA425 lines 4356-4400: Byte Offset Size Description 0-1 [$89],Y Word Sprite/Frame ID (1-976). Stored to $0D97,X 2-3 [$89],Y+2 Word Duration/Timer Value. Loaded to $11E3,X 4.2 Special Opcodes Value Meaning Action Negative Duration End of Script Reset $11C3,X to 0, clear $14E3,X $FDFE - $FEC2 Special Actions Triggers alternate behavior (e.g., falling animation) 4.3 Evidence from ASM ; Line 4356: Load SpriteID (Bytes 0-1) LDA.B [$89],Y ; 9EA480 | Load word at script+offset STA.B $AD ; Store to temp ; Line 4393: Load Duration (Bytes 2-3) LDY.B $A5 ; 9EA4C6 INY ; Y = offset + 2 INY LDA.B [$89],Y ; Load Duration word ... STA.W $11E3,X ; Reload timer 5. Frame Data Structure (Per bcrt2000 Research) 5.1 Frame Header Table Location: $010000 (Bank 82 relative: $828000) Size: 976 frames × 4 bytes = 3904 bytes Format: [Low Word] [High Word] → 24-bit pointer to Frame Data 5.2 Sprite Header Layout (22 Bytes) Offset Size Name Description $00 1 NumSprites Number of sprites in this frame $01 1 Flag Always $80 $02 1 NumTiles Total tile count for DMA $03 1 Padding Always $00 $04-$05 2 Unknown (Often $FFxx) $06-$07 2 Unknown $08-$09 2 Unknown $0A 1 Stride Row stride for 2×2 tile calculation $0B 1 Padding Always $00 $0C-$0D 2 Unknown $0E-$0F 2 Unknown $10-$11 2 DataLength Total length of Frame Data in bytes $12 1 HotspotX Signed X offset (sign-extended) $13 1 HotspotY Signed Y offset (sign-extended) $14 1 Hotspot2X Secondary X (often zero) $15 1 Hotspot2Y Secondary Y (often zero) 5.3 Sprite Entry Format (7 Bytes Each) After the 22-byte header, sprite entries follow: Bytes Name Description 0-1 X Offset Signed 16-bit, Little Endian 2-3 Y Offset Signed 16-bit, Little Endian 4 First Tile Starting tile index 5 Palette/Flags Usually $08 6 Size $FF = 2×2 tiles, $00 = 1×1 tile 5.4 Tile Selection Algorithm For 1×1 sprites (Size = $00😞 Use First Tile directly For 2×2 sprites (Size = $FF😞 Tile[0] = First Tile Tile[1] = First Tile + 1 Tile[2] = First Tile + 1 + Stride Tile[3] = First Tile + 1 + Stride + 1 5.5 Concrete Examples Frame 1 (Stride = 4, 3 Sprites, 9 Tiles) Header: 03 80 09 00 FA FF 07 00 E2 FF 04 00 A0 00 80 00 2B 00 02 E4 00 00 ^NumSpr ^NumTile ^Stride ^Hot Sprite 1: FA FF E2 FF 00 08 FF → X=-6, Y=-30, Tile 0, 2×2 → Tiles 0,1,5,6 Sprite 2: FA FF F2 FF 02 08 FF → X=-6, Y=-14, Tile 2, 2×2 → Tiles 2,3,7,8 Sprite 3: FE FF 02 00 04 08 00 → X=-2, Y=+2, Tile 4, 1×1 → Tile 4 Frame 2 (Stride = 5, 5 Sprites, 11 Tiles) Header: 05 80 0B 00 F7 FF 09 00 E3 FF 05 00 C0 00 A0 00 39 00 01 E5 00 00 ^NumSpr ^NumTile ^Stride ^Hot Sprite 1: FD FF E3 FF 00 08 FF → X=-3, Y=-29, Tile 0, 2×2 → Tiles 0,1,6,7 Sprite 2: F7 FF F3 FF 02 08 FF → X=-9, Y=-13, Tile 2, 2×2 → Tiles 2,3,8,9 Sprite 3: 07 00 FB FF 04 08 00 → X=+7, Y=-5, Tile 4, 1×1 → Tile 4 Sprite 4: F8 FF 03 00 05 08 00 → X=-8, Y=+3, Tile 5, 1×1 → Tile 5 Sprite 5: 00 00 03 00 14 08 00 → X=0, Y=+3, Tile 20,1×1 → Tile 20 (flipped?) Note: Sprite 5's First Tile = $14 (20) is unusual. May indicate tile flipping via high bits. 5.5 Hotspot Extraction Routine: CODE_9BC002 Location: bank_9B.asm Lines 5494-5557 (Address $9BC002) This function reads the X/Y hotspot values from Frame Data and applies flip/rotation transforms. Pseudocode CODE_9BC002 (read_frame_header_and_adjust_hotspot): PHX ; Save X TAY ; Y = player index A = RAM[$0D97 + Y] ; Load current SpriteID A = (A - 1) * 4 ; Table index (4 bytes/entry) X = A // Build 24-bit pointer to Frame Data $0C = DATA8_828000[X] + $8000 $0E = DATA8_828002[X] + $82 X = Y ; Restore player index Y = $12 ; Offset to Hotspot X A = [$0C + Y] & 0xFF ; Read byte at offset $12 IF A >= $80 THEN A |= $FF00 ; Sign extend if negative $A5 = A ; Store Hotspot X Y++ ; Y = $13 A = [$0C + Y] & 0xFF ; Read byte at offset $13 IF A >= $80 THEN A |= $FF00 ; Sign extend if negative $A9 = A ; Store Hotspot Y // Apply H-Flip IF (RAM[$0E03 + X] & $0800) THEN $A5 = 0 - $A5 ; Negate X // Apply V-Flip (note: inverted logic) IF !(RAM[$0E03 + X] & $1000) THEN $A9 = 0 - $A9 ; Negate Y // Apply 90° Rotation IF RAM[$15DA] != 0 THEN temp = $A5 $A5 = $A9 ; X = old Y $A9 = -temp ; Y = -old X PLX ; Restore X RTL Key RAM Addresses Address Purpose $0D97,Y Current Sprite ID (used to index Frame Header Table) DATA8_828000 Frame Header Table (4 bytes × 976 entries) $0E03,X Orientation Flags (bit 11 = H-flip, bit 12 = V-flip) $15DA Rotation Mode (non-zero = 90° clockwise rotation) $A5 Output: Adjusted Hotspot X $A9 Output: Adjusted Hotspot Y Callers (from bank_9E.asm) This function is called 8 times during rendering: Line 1624 ($9E8C78) Line 3047 ($9E982C) Line 3227 ($9E99B8) Line 5769 ($9EAFD8) Line 11095 ($9EDCF7) Line 12489 ($9EE805) Line 12935 ($9EEB94) Line 13403 ($9EEFCD) 6. Animation Header Table (SPAflip / SPAlist) 6.1 Location SNES: $EE0FC (Bank 9E, Address 0xEE0FC relative to code segment) Genesis (NHLPA 93): $0052AA (for comparison) 6.2 Format [Direction 0 Offset] [Direction 1 Offset] ... [Direction 7 Offset] Each is a 16-bit word pointing to the animation sequence for that direction. 6.3 Example (from bcrt2000's screenshot) 000EEA00: 04 00 96 01 04 00 91 01 FC FF 8D 01 ... ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ Dur0 ID0 Dur1 ID1 Loop Target Interpretation: 04 00 = Duration 4 96 01 = Frame ID 0x0196 (406) FC FF = Loop Opcode (0xFC) with target 0xFF 7. Key Differences from My Previous Implementation Aspect Previous Code Authentic Behavior SpriteID Size 8-bit ( byte) 16-bit (ushort) - Supports 976 frames Byte Order [SpriteID] [Duration] [VelX] [VelY] [SpriteID Low][SpriteID High] [Duration Low][Duration High] Bank $9D0000 + offset Correct, but must use long pointer via $89/$8B Timer Init +1 on negative Actually ~A + 1 (two's complement) for sign handling Loop Opcode 0xFC byte Negative Duration value triggers reset Edited Monday at 01:21 AM by vancecookcobxin 1 Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.