See the question and my original answer on StackOverflow

I quote an extract of the definitive answer here from Why is there a limit of 15 shell icon overlays? Raymond Chen 2019 post

The value 15 came from the corresponding limit for image lists. The Image­List_Set­Overlay­Image function supports up to 15 image list overlays per image list. (Hey, it used to be worse. The limit used to be only 3!)

Okay, but why only 15? Why not more?

The overlay image is one of the pieces of information used when drawing an image from an image list. The options are encoded in the fStyle parameter, and when the bits were divided up for various purposes, four bits were available to be used to specify the overlay image. (You get 15 overlay images instead of 16 because you lose one of the values in order to specify “no overlay.”)

Okay, but the values in the fStyle parameter use only the bottom 16 bits. What about the upper 16 bits? There’s plenty of room there.

The 16-bit limit was carried over from the 16-bit version of the common controls (which still needed to be supported in Windows 95). Of course, nowadays, nobody cares about the 16-bit version of the common controls, so why not start using the upper bits?

There’s an unsatisfying explanation: The code internally that manages the fStyle still uses a WORD in some places, so all the code that manages the fStyle would have to be revised. This occurs in multiple modules across Windows, so a synchronized change would have to be made across multiple components. This is a breaking change at the binary level because the interfaces are no longer compatible. Breaking changes are procedurally difficult to coordinate: The affected code may not be visible to the shell team because they are sitting in a far-away leaf branch that has not yet RI’d to the trunk. It might be that expanding fStyle from a WORD to a DWORD has far-reaching consequences for some component.

Like I said, this is unsatisfying. Basically it boils down to “It would be a lot of work and we are lazy.”