Leaderboard
Popular Content
Showing content with the highest reputation on 01/12/2026 in all areas
-
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?) 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 reset1 point