Jump to content
NHL'94 Forums

Recommended Posts

Posted (edited)

I'm making this thread to help me focus this project-- I find writing things down like this brings me some clarity for my direction.

As you may or may not know, the PC version of NHL 94 (aka NHL Hockey) and NHL 95 uses sprites with many more color zones, which allow for much more accurate jerseys.

Comparison:

image.png

Not only is the jersey striping more accurate, but it even includes the crest.

The reason this is possible, is while the Sega Genesis supports only 64 colors (of which 16 colors are dedicated to the home team, and 16 colors are dedicated to the away team; one color has to be dedicated to transparency, so its actually really 15 colors), the PC version of NHL is a 256 color game.

image.png

image.png

Each team has 64 colors that can be defined for it's palette (although for some reason, the PC version adds 5 colors to the end of the Away Team Palette-- which means you are really limited to 59 colors per team).

So on NHL94 PC, the Home team palette is using 17 colors, and could easily be squashed down to use 15 colors. So why do the PC sprites look so much more detailed? The answer is color zone mapping.

The Genesis sprite has 15 color zones, and you are directly mapping the palette to each color zone.

image.png

image.png

There is actually a two step process with palettes on the PC version. One step is defining the palettes, the next step maps the palettes to the 90+ color zones on the sprite

image.png

image.png

If you notice-- in the sprite palette mapping, its dedicating close approximately 100 colors or so to mapping colors to various color zones

Now that being said-- to display a sprite like the PC version doesn't actually require being able to support displaying 100 colors. Here is the sprite above crunched down into 16 colors:

image.png

Maybe could use some tweaking to preserve certain colors over others, but doesn't look half bad, does it? It looks a lot closer to the original PC version than the original Genesis version.

There is a caveat here-- but the above sprite is in 8-bit color. Sega Genesis can only produce 3-bit color, so the above needs to be downcoverted to 3-bit color. I don't have time right now to create a hand-tweaked 3-bit color palette, so I just automated it for now. This resulted in a loss in some color detail that I think with hand-tweaked values could still be preserved a little better. In particular, the darker skin color under the chin doesn't look right, and there's some loss of shadow detail around the shoulders that I think could be kept:

image.png

For a better example of this 3-bit down conversion process done better, I did it to the Leafs sprite set earlier:

image.png

 

Now theoretically-- this could be done. We could render out every jersey as its own sprite set, put it in the rom, and then when rendering the frame, based on the team, we could point to a different sprite set. The issue with this is size.

Normally a Sega Genesis ROM's maximum size is 4MB.

The NHL94 animation sprites are around 270kb. If we just took this into account for 36 teams (32 + 4 for all star) thats:

36 teams * 2 jerseys = 72 jersey sets * 270 kb = 19.5MB. Which is way bigger than a normal sized Genesis ROM.

However, Sega Genesis supports Bank Switching thanks to Super Street Fighter 2, which used it ship with a 5MB ROM. My understanding is there has since been a home brew game which used Bank Switching to get up to 10MB, and theoretically Bank Switching should support up to 32MB.

So it definitely could fit on a cartridge, but the issue is, it's still very limiting because once you throw in third jerseys, classic jerseys, international teams, etc, that space quickly balloons and gets filled up. And also doesn't leave room for other cool stuff that could be done on the ROM in terms of enhanced sound effects, etc.

There is also the option of using the Sega CD as storage. You could fit 2000+ jersey sets on a CD. However, Sega CD's memory becomes an issue. It only has 512kb of memory, and since the jersey sprites would be on a CD, it would need to be loaded into RAM, and 270kb*2 won't fit into 512kb of memory. Now, maybe there is some trickery you could do where you put the first 256kb of the jersey sprite on the CD, and the remaining 14kb you put on the cart-- that would possibly make it work and still allow a lot more jersey sets than only relying on the Cartridge space.

However, what if instead of rendering out an entire jersey set, we rendered out jersey templates? I will explore this in my next post but just a small teaser: This would involve grouping jerseys into groups of common designs, and rendering jerseys WITHOUT the crest tile, but then dynamically inserting a crest upon rendering the frame.

This would also require having several crest variations stored in the game tiles to work depending on the orientation of the sprite (note: some of these seem like they are unnecessary because they could just be mirrored, but consider the case of a non symmetrical logo, like the Penguins):

image.png

OK that's all I have time for right now! Things that need to be tackled in the future:

- Jersey Templates

- positioning Crest tile correctly on Player sprites

- Getting these sprites in the correct format for importing into Sprites.anim

- Importing the Sprites into Sprites.anim

- Importing Jersey Templates into the game

- Updating the player sprite rendering logic to handle picking the correct jersey template and rendering the crest

Edited by bcrt2000
  • Wow 2
Posted

This is promising.

3-bit colour makes things a bit trickier. The colours tend to be a bit dull and muddy, and the colours that are vibrant, stand out dramatically from their next step in chroma.

You could get away with only ONE jersey, but palette swap. That would further reduce jersey sets. 

Some of us have modified the colour zones on each individual sprite and experimented that way. If we could isolate the specific 8x8 tiles in the sprites that display the logo and make those individual to each team, would that work? And have the command grab those specific team tiles rather than the base tiles it is already using to build the sprite to build the animation?

Let me know if that doesn't make sense to you...

Posted (edited)
5 hours ago, Drezz said:

This is promising.

3-bit colour makes things a bit trickier. The colours tend to be a bit dull and muddy, and the colours that are vibrant, stand out dramatically from their next step in chroma.

You could get away with only ONE jersey, but palette swap. That would further reduce jersey sets. 

Some of us have modified the colour zones on each individual sprite and experimented that way. If we could isolate the specific 8x8 tiles in the sprites that display the logo and make those individual to each team, would that work? And have the command grab those specific team tiles rather than the base tiles it is already using to build the sprite to build the animation?

Let me know if that doesn't make sense to you...

I think I can do a way better job with the 3-bit version of the chicago jersey. I will attempt to do a hand tuned version when I get a chance.

There is no need to isolate an existing tile. Chaos' thread has more details but tiles are all different sizes and aren't align to any sort of grid, there is no universal way to locate where the crest should go as is on the existing Genesis sprites.

We literally need to insert a new crest tile into each animation frame that should have a crest visible (which will serve to position the crest correctly). And then dynamically replace that default crest tile with the right crest tileId for the current team when rendering. The game already kind of has similar logic when rendering the away team-- any time it renders an animation frame, it checks if the team is home or away, and then uses the correct palette as a result. The crest swap logic would live in a similar place, and swap the tileId with the correct team's tileId.

Edited by bcrt2000
Posted (edited)

cover the pants more, at least one pixel, maybe two

test02.pngtest01

test03.pngtest02

Edited by 77
Posted (edited)

What you ask (changing a few pixels on 700+ frames of animation) is A LOT of work. The goal of this project, at least at this initial stage is not to change the existing sprites made by EA, just to take advantage of the higher color sprites and some clever techniques to get more accurate jerseys technically working on Genesis. I have ideas to make this better in the future, but it involves a lot of pixel art work... I think we can turn every pixel into a color zone, essentially allowing us to use textures to paint every pixel on the jersey,pants,etc. Some of the more unique jerseys could be supported (like Pittsburgh's old road uniform from the late 90's, Ducks Wild wing, etc), and there would be the ability to do custom masks, gloves, skates, etc... but again, thats far off and not the initial goal here.

Edited by bcrt2000
Posted (edited)
6 hours ago, Soitanen said:

And another idea - real jersey numbers on the back, like it is done in NBA Live series of the same year.

Yes, its part of my concept for this, and I was originally inspired by NBA Live (although I have not looked at technically how they did it). It would be similar to the crest concept (including the 6 variations of the numbers like how I did with the crest above)-- insert a dummy number tile for the number, and then when it goes to render that tileId, render the right number tile for that player. Its all up to whether or not it would be performant-- that's also the question on the Crest as well. Would adding a tileId check on every render of every tile slow down the performance of the game?

I think to keep things performant, we'd need to do tiles for every number including double digit numbers... so 0-99. And then likely there needs to be a number set for every jersey template.

 image.png

edit: with the assistance of grok I have this estimate

There are approx 128k CPU cycles available per frame (at 60FPS)

16-bit comparison (eg: cmp.w d0, d1) takes 4 CPU cycles.

We have up to 11 players on the screen (very unlikely both goalies will be on the screen), each player frame is made up of usually less than 10 tiles.

So 11 players * 10 tiles * 4 cycles = 440 cycles. When there are 128k cycles available, hopefully there are that many cycles to spare per frame. I'm sure this cost goes up a little with potentially needing to move data around registers as well, etc.

Edited by bcrt2000
  • Love 1
Posted (edited)

So on Jersey Templates-- I decided to try to group jerseys together based on what I think I could create a common template for. These jerseys are representative of the 2024-25 season, the 91-92 to 94-95 season, plus the third jerseys from the 97 season.

image.png

Things to note:

- there is a big section of unique jerseys (21 of them) where I'm not sure if a common template could be created at all

- I'm pretty sure within each section, there will be sub sections-- for example, even though the general jersey template is the same, one jersey might match the helmet color with the pants vs match with the base jersey color. One jersey might patch the pant stripe with the jersey color accent 1 or jersey color accent 2, etc

 

So I tried creating the first template, which I'm calling the sandwich stripe jersey

image.pngimage.pngimage.png

Just creating this template, immediately things started to get messy. With Chicago, they need an extra color for the shoulder patch and another extra color for the crest, so the equipment, skate blades & pant stripes share colors with other parts of the jersey. Those things made it very difficult to reuse. I was able to slightly modify the template to make Dallas work, by making the pant stripe share the same color as the base jersey color. However, when I got around to San Jose, I rearranged things to be more flexible-- got rid of the brown eyes/mouth, now that becomes black. And dedicated a palette color just for equipment. And another one for the Skate blade. I also want to keep the stick as an independent color too so that we have flexibility to change it to a dark grey for a more modern stick color.

One thing I wanted to do is really be uncompromising-- meaning, if I can make the jersey more accurate by creating separate templates, I will.

But even just flipping between these 3 jerseys I felt like things became very confusing in keeping track of what template did what.

Instead, I've started to ideate on creating a dedicated Jersey editing tool which can automate the process of grouping together team palettes and creating common jersey templates for them.

I think what I can do is create a broader template that works for 90% of jerseys and also make things simpler by automating 3-bit color conversion, automating shadow/highlight details, so you only have to pick the base color of a broader color zone, and the tool will automatically shade it correctly. And ultimately the automation will take that 1 broad template, and break it down into sub-templates to be used in game.

If I didn't prioritize making the jerseys as accurate as 15 colors allows us, we probably could get away with having 5-10 templates. I think many people who have been editing ROMs for years would be happy with that...

NOT ME 🤣

For me, I'm used to the much better accuracy the PC version gives you-- so I want to squeeze every ounce of accuracy we can. That might mean to cover all current jerseys thats 40-50 templates, which is a lot. But the upside of that is that I don't think I see the amount of templates inflating beyond that amount if we start adding historical jerseys, international jerseys, other leagues, etc. And with the automation I mentioned above, it would be a much smaller amount of templates that are being created and managed manually, and the automation does the rest.

I don't want to get bogged down creating a jersey template editor just yet when there are so many unknowns in terms of technical feasibility-- the worst case would be to spend all this effort, only for the approach to be a complete failure. I think I need to just move forward using the Chicago/Dallas jersey examples for now, and I can circle back to making the jersey editor/automation later if I can prove out this approach works.

Next steps:

- Create Crests & variations for Chicago/Dallas

- Create number sprites & variations

- Figuring out the most efficient way to place the dummy Crest & Number tiles on the animation frames

- Updating player sprite tiles

- Import Chicago/Dallas jersey templates & palettes into ROM

- Update animation frame rendering logic to select correct Jersey Template, Crest & Number tiles.

Edited by bcrt2000
Posted

Alright, so I half lied when I said I wouldn't make a jersey editor-- I'm not going to make the UI, but I am going to build some scripts around automating palette conversion processes as its going to make it so much easier to create & edit jerseys for me. First, I created a standard jersey template that will work for MOST jerseys. I used this tool called JED (v3.01) which is a jersey editor for NHL95PC:

image.png

One thing you may notice is the jersey is flat shaded-- it's intentional. I plan to automate the shading bit to make it even easier/quicker to put together jerseys. I've spent time analyzing the palette:

image.png

and have mapped out what each palette entry means:

Colors 0-127: Rink Palette (aka Game Palette)

  • Static rink/ice colors from rinkpal.act

Colors 128-143: Skin Tones (16 colors)

  • Dedicated flesh/skin tone colors

Colors 144-191: Jersey Components (48 colors)
Mapped as follows:

  • 144-146: forearm (dark, medium, light)
  • 147-149: armStripe3 (light, medium, dark)
  • 150-152: armStripe2 (dark, medium, light)
  • 153-155: armStripe1 (light, medium, dark)
  • 156-158: armUpper (light, medium, dark)
  • 159-163: yolk components (yolkCorner, shoulderPatch, yolk3, yolk1, yolk2)
  • 164-166: jersey (goalieMask, light, medium, dark)
  • 167-169: waist1 (odd, even, hidden)
  • 170-172: waist2 (light, medium, dark)
  • 173-175: waist3 (light, medium, dark)
  • 176-179: pants (dark, pantsStripe2, pantsStripe1, medium)
  • 180-182: socks (light, medium, dark)
  • 183-185: socksStripe1 (light, medium, dark)
  • 186-188: socksStripe2 (light, medium, dark)
  • 189-191: helmet (medium, dark, unused)

Colors 192-255: Crest and Logo Data (64 colors)
Organized in 4 rows of 16 colors each:

Each 16-color row contains:

  • 5 colors: Crest row pixels (5×4 crest grid)
  • 6 colors: Ice logo colors
  • 5 colors: Unused/padding

Row breakdown:

  • 192-207: Crest row 1 + ice logo + padding
  • 208-223: Crest row 2 + ice logo + padding
  • 224-239: Crest row 3 + ice logo + padding
  • 240-255: Crest row 4 + ice logo + padding

 

So as you may have noticed, this template has 23 color zones, and if you include shading it can be up to 48 colors. Now the idea is:

  • define jerseys using this template
  • use automation to fit each jersey into 16 colors (some teams might just fit as is with using full advantage of shading, other teams, the automated script would reduce shading in some areas to fit it into 16 colors) and also create the minimal amount of sprite sets required to support all of the jerseys.

So I've created the following data structure to be able to define color jerseys:

const jerseyDef = {
    "global": {
        "palette": {
            "brown": "144 108 0",
            "black": "0 0 0",
            "darkBrown": "108 72 36",
            "flesh": "220 148 104",
            "flesh2": "200 132 92",
            "flesh3": "164 108 72",
            "flesh3bit": "216 144 108",
            "flesh3bit2": "180 108 72",
            "white": "252 252 252"
        },
        "mapping": {
            "stick": "brown",
            "equipment": "black",
            "eyes": "darkBrown",
            "skin1": "flesh3bit",
            "skin2": "flesh3bit2",
            "skin3": "flesh3bit2",
            "skateBlade": "white"
        }
    },
    "0": {
        "name": "Chicago Blackhawks",
        "palette": {
            "red": "144 0 0",
            "yellow": "180 180 0"
            // "chiWhite": "216 216 216"
        },
        "home": {
            "template": "classic",
            "helmet": "black",
            // "yolk1": "0 0 0",
            // "yolk2": "0 0 0",
            // "yolk3": "0 0 0",
            // "yolkCorner": "0 0 0",
            "shoulderPatch": "yellow",
            "jersey": "red",
            "waist1": "white",
            "waist2": "black",
            "waist3": "white",
            "pants": "black",
            "pantsStripe1": "white",
            "pantsStripe2": "red",
            // "socks": "0 0 0",
            "socksStripe1": "white",
            "socksStripe2": "black",
            // "armUpper": "0 0 0",
            "armStripe1": "white",
            "armStripe2": "black",
            "armStripe3": "white",
            // "forearm": "0 0 0",
            "goalieMask": "white",
        }, crest: [
            "red",   "black", "black", "black",     "red",
            "red",   "brown", "black", "black",     "brightRed",
            "brown", "brown", "brown", "brightRed", "yellow",
            "red",   "brown", "black", "brown",     "red",
        ],
    }
}

Next steps:

  • create a script that will convert this jersey definition into the NHL 95 jersey format, including expanding the flat shaded color zones to be properly shaded to have multiple color tones, whilist also staying within 3-bit and 16 color restrictions.
  • create a script that can then render the amount of needed sprite sets to support all of the defined jerseys.
  • create a script that can render crest definition to crest + crest variation tiles
  • Figuring out the most efficient way to place the dummy Crest & Number tiles on the animation frames
  • Updating player sprite tiles
  • Import Chicago/Dallas jersey templates & palettes into ROM
  • Update animation frame rendering logic to select correct Jersey Template, Crest & Number tiles.
  • Love 1
  • 2 weeks later...
Posted
On 7/24/2025 at 10:03 AM, AdamCatalyst said:

This is insane. I will watch with great interest. :)

same. Being a big fan of the PC versions, i like what's going on here. 
There are a bunch of jersey files in the NHL95PC sections in this forum if you need them @bcrt2000

they could help you with your project.

  • Like 1
  • 4 weeks later...
Posted (edited)

I'm back! Took a month break for summer from this stuff.

I quickly whipped up a script that takes the jersey definitions above and puts it into the NHL95 palette format. As you can see, its flat shaded for now. In my next update the plan would be to add shading support.

image.png

Next steps:

  • add shading support to script that generates NHL95 palette from template definition
  • create a script that can then render the amount of needed sprite sets to support all of the defined jerseys.
  • create a script that can render crest definition to crest + crest variation tiles
  • Figuring out the most efficient way to place the dummy Crest & Number tiles on the animation frames
  • Updating player sprite tiles
  • Import Chicago/Dallas jersey templates & palettes into ROM
  • Update animation frame rendering logic to select correct Jersey Template, Crest & Number tiles.

Yes, already have taken a look. Although I think for V1 of this it would be just taking the NHL94 jerseys and porting them over to the stock NHL94 ROM.

On 8/6/2025 at 9:57 PM, PenguinFan1985 said:

same. Being a big fan of the PC versions, i like what's going on here. 
There are a bunch of jersey files in the NHL95PC sections in this forum if you need them @bcrt2000

they could help you with your project.

 

Edited by bcrt2000
  • Love 1
  • Like 1
Posted

Shading support has been added to the script:

image.png

I think I probably should define more jerseys as the next step, and then I can properly create/test the template rendering script.

  • Love 6
  • 2 weeks later...
  • 1 month later...
Posted

OK, I'm going to try to actively work on this again after a bit if a hiatus.

Since the last update I did go through and create more jersey templates and also finalized the designs for the NHL94 default jerseys (based them off the NHL 95 PC jerseys but some of them needed to be tweaked to be more accurate). They still need to be converted to 3bit color & auto shaded (which I believe I've already written a script to do-- its been a while I've looked at all of this and need to remember what scripts do what 😅)

image.pngimage.pngimage.pngimage.png

 

BTW I realize the palette zones are complete broken on the cross checking sprite on the arms and torso-- thats how they are in NHL 95. Whoever worked on them didn't know what they were doing and/or didn't verify they drew those sprites correctly. If this project is successful, I definitely will come back around and fix those sprites.

 

Next steps:

  • create a script that can then render the jerseys out to template sprite sets + per team palettes
  • Import templates & palettes into ROM
  • Update animation frame rendering logic to select correct Jersey Template
  • if above works, continue with work for crest & numbers:
    • create a script that can render crest definition to crest + crest variation tiles & numbers out to number + number variation tiles
    • Figuring out the most efficient way to place the dummy Crest & Number tiles on the animation frames
    • reimport templates into rom
    • update animation frame rendering logic to select correct crest variation & number variation

 

  • Like 1
Posted
On 9/1/2025 at 7:12 PM, bcrt2000 said:

Shading support has been added to the script:

image.png

I think I probably should define more jerseys as the next step, and then I can properly create/test the template rendering script.

already looks 10000x better.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Who's Online   0 Members, 0 Anonymous, 31 Guests (See full list)

    • There are no registered users currently online
×
×
  • Create New...