Palette fade: Difference between revisions

From ZAMN Hacking
Content deleted Content added
No edit summary
No edit summary
(One intermediate revision by the same user not shown)
Line 11: Line 11:
== Details ==
== Details ==


The process used to fade between the palettes is as follows. The palette fade starts at palette 0, and on each iteration will update all 16 colors in the background and sprite palettes at the current index. After each iteration, the current palette index is incremented. When all 8 palettes have been updated, the palette fade will complete if there were no colors updated during the last 8 iterations. If there were updated colors, then current palette is reset to 0 and the process repeats.
The process used to fade between the palettes is as follows. The palette fade starts at palette 0, and on each iteration will update all 16 colors in the background and sprite palettes at the current index. After each iteration, the current palette index is incremented. When all 8 palettes have been updated, the palette fade will complete if there were no colors updated during the last 8 iterations. If there were updated colors, then current palette is reset to 0 and the process repeats. Background palette 7 is skipped during this process, but sprite palette 7 is updated as normal.


Before each iteration, there is a delay. The delay starts at 64 frames, and this value is decremented between each iteration. The delay will never go below 1 frame.
Before each iteration, there is a delay. The delay starts at 64 frames, and this value is decremented between each iteration. The delay will never go below 1 frame.
Line 23: Line 23:
{| class="wikitable"
{| class="wikitable"
|-
|-
! Offset !! Length !! [[Data types|Type]] !! Description
! Offset !! Length !! [[Data types|Type]] !! Name !! Description
|-
|-
| 0x00 || 4 || pointer32 || Pointer to target background palette
| 0x00 || 4 || pointer32 || targetBGPalette || Pointer to target background palette
|-
|-
| 0x04 || 4 || pointer32 || Pointer to target sprite palette
| 0x04 || 4 || pointer32 || targetSpritePalette || Pointer to target sprite palette
|}
|}


Line 36: Line 36:
{| class="wikitable"
{| class="wikitable"
|-
|-
! Address !! Length !! [[Data types|Type]] !! Description
! Address !! Length !! [[Data types|Type]] !! Name !! Description
|-
|-
| $00 || 4 || pointer32 || Pointer to [[#Additional data format|additional data]]
| $00 || 4 || pointer32 || additionalData || Pointer to [[#Additional data format|additional data]]
|}
|}


Line 45: Line 45:
{| class="wikitable"
{| class="wikitable"
|-
|-
! Address !! Length !! [[Data types|Type]] !! Description
! Address !! Length !! [[Data types|Type]] !! Name !! Description
|-
|-
| $0A || 2 || boolean || Any palette has been updated in current 8-palette cycle
| $0A || 2 || boolean || updated || Any palette has been updated in current 8-palette cycle
|-
|-
| $0C || 2 || uint16 || Index of current palette (in bytes)
| $0C || 2 || uint16 || curIndex || Index of current palette (in bytes)
|-
|-
| $0E || 2 || uint16 || Current delay between iterations
| $0E || 2 || uint16 || curDelay || Current delay between iterations
|-
|-
| $10 || 4 || pointer32 || Pointer to target background palette
| $10 || 4 || pointer32 || targetBGPalette || Pointer to target background palette
|-
|-
| $14 || 4 || pointer32 || Pointer to target sprite palette
| $14 || 4 || pointer32 || targetSpritePalette || Pointer to target sprite palette
|}
|}


Line 197: Line 197:
| Mines || Pyramid || 22 || 36.60638815
| Mines || Pyramid || 22 || 36.60638815
|}
|}

== Pseudocode ==

updateColor(targetColor, currentColor) {
if (currentColor == targetColor) {
return false
}
targetB = targetColor & 0x7C00
targetG = targetColor & 0x03E0
targetR = targetColor & 0x001F
newB = currentColor & 0x7C00
if (newB > targetB) {
newB -= 0x400
} else if (newB < targetB) {
newB += 0x400
}
newG = currentColor & 0x03E0
if (newG > targetG) {
newG -= 0x20
} else if (newG < targetG) {
newG += 0x20
}
newR = currentColor & 0x001F
if (newR > targetR) {
newR -= 1
} else if (newR < targetR) {
newR += 1
}
newColor = newR | newG | newB
return true, newColor
}
updateBGPalette(targetPalette, index) {
if (index >= 0x70) {
return false
}
colorUpdated = false
for (i = index; i < index + 0x10; i += 1) {
updated, newColor = updateColor(targetPalette[i], {{RAM name|$7E:5428}}[i])
if (updated) {
colorUpdated = true
{{RAM name|$7E:5428}}[i] = newColor
{{RAM name|$7E:5628}}[i] = newColor
}
}
return colorUpdated
}
updateSpritePalette(targetPalette, index) {
colorUpdated = false
for (i = index; i < index + 0x10; i += 1) {
updated, newColor = updateColor(targetPalette[i], {{RAM name|$7E:5528}}[i])
if (updated) {
colorUpdated = true
{{RAM name|$7E:5528}}[i] = newColor
}
}
return colorUpdated
}
main() {
this.targetBGPalette = args.additionalData.targetBGPalette
this.targetSpritePalette = args.additionalData.targetSpritePalette
this.curIndex = 0
this.updated = false
this.curDelay = 64
while (true) {
{{ROM name|$80:8353}}(this.curDelay)
if (updateBGPalette(this.targetBGPalette, this.curIndex)) {
this.updated = true
}
if (updateSpritePalette(this.targetSpritePalette, this.curIndex)) {
this.updated = true
}
this.curIndex += 0x10
if (this.curIndex >= 0x80) {
if (!this.updated) {
{{RAM name|$7E:1F94}} = true
return
}
{{ROM name|$80:83AE}}({{ROM name|$80:9FDF}}) // Transfer palette to CG-RAM
this.updated = false
this.curIndex = 0
if (this.curDelay >= 2) {
this.curDelay -= 1
}
}
}
}


[[Category:Boss monster]]
[[Category:Boss monster]]

Revision as of 02:13, 15 July 2024

Monster data
HP N/A
Points N/A
Special {{{special}}}
Resists {{{resists}}}
Weak to {{{weak_to}}}
Entity data
Entity pointer $82:AB95

Palette fade is a boss monster. Though implemented in the same way as other boss monsters, this is not a monster in the literal sense.

Overview

A palette fade will cause the background and sprite palettes to transition from their starting values to the specified palettes over time at the start of a level. This is generally used to make the level appear to go from day to night.

Once the palette fade is completed, two additional effects are activated. Werewolves will be allowed to spawn, and tourists will transform into werewolves. If either of these effects is desired without having a visible palette fade, then the post-fade palettes can be set to the same values as the starting palettes. This will cause the fade to complete quickly, essentially activating werewolves at the beginning of the level.

Details

The process used to fade between the palettes is as follows. The palette fade starts at palette 0, and on each iteration will update all 16 colors in the background and sprite palettes at the current index. After each iteration, the current palette index is incremented. When all 8 palettes have been updated, the palette fade will complete if there were no colors updated during the last 8 iterations. If there were updated colors, then current palette is reset to 0 and the process repeats. Background palette 7 is skipped during this process, but sprite palette 7 is updated as normal.

Before each iteration, there is a delay. The delay starts at 64 frames, and this value is decremented between each iteration. The delay will never go below 1 frame.

To update a color, compare each of the red, green, and blue components of the current color and the target color. If the current value is less than the target value, increment it, if it is greater, decrement it, and if the values are the same, do not alter it. This process means that the number of iterations required to complete the fade depends on the largest difference between starting and target color components. See timing data for details.

Additional data format

The format for the additional data in the level is:

Offset Length Type Name Description
0x00 4 pointer32 targetBGPalette Pointer to target background palette
0x04 4 pointer32 targetSpritePalette Pointer to target sprite palette

RAM map

Entity arguments

Address Length Type Name Description
$00 4 pointer32 additionalData Pointer to additional data

Entity memory

Address Length Type Name Description
$0A 2 boolean updated Any palette has been updated in current 8-palette cycle
$0C 2 uint16 curIndex Index of current palette (in bytes)
$0E 2 uint16 curDelay Current delay between iterations
$10 4 pointer32 targetBGPalette Pointer to target background palette
$14 4 pointer32 targetSpritePalette Pointer to target sprite palette

Bugs

If the high bit is set on any of the colors in the target palettes, then the palette fade will never be considered complete. This happens because the color update logic checks if the color values are exactly equal when determining if the fade is complete, but will never set this bit on the updated colors. Since the high bit is unused in SNES colors, this can be completely avoided by simply leaving the bit unset on all colors. None of the colors in the base game ever have this bit set, so this bug can only occur in modified ROMs.

Timing Data

Largest component
difference
Duration (frames) Duration (seconds)
0 484 8.053405392
1 904 15.04189768
2 1260 20.96547685
3 1552 25.82414291
4 1780 29.61789586
5 1944 32.34673571
6 2044 34.01066244
7 2080 34.60967607
8 2088 34.74279021
9 2096 34.87590434
10 2104 35.00901848
11 2112 35.14213262
12 2120 35.27524676
13 2128 35.4083609
14 2136 35.54147504
15 2144 35.67458918
16 2152 35.80770332
17 2160 35.94081745
18 2168 36.07393159
19 2176 36.20704573
20 2184 36.34015987
21 2192 36.47327401
22 2200 36.60638815
23 2208 36.73950229
24 2216 36.87261642
25 2224 37.00573056
26 2232 37.1388447
27 2240 37.27195884
28 2248 37.40507298
29 2256 37.53818712
30 2264 37.67130126
31 2272 37.8044154

Highlighted rows in the table below are palette combinations that occur in the original game.

Tileset Palette 1 Palette 2 Largest component
difference
Duration (seconds)
Grass Autumn Night 24 36.87261642
Autumn Normal 18 36.07393159
Autumn Winter 28 37.40507298
Night Normal 30 37.67130126
Night Winter 30 37.67130126
Normal Winter 23 36.73950229
Castle Bright Dark 24 36.87261642
Bright Night 24 36.87261642
Bright Normal 24 36.87261642
Dark Night 17 35.94081745
Dark Normal 14 35.54147504
Night Normal 21 36.47327401
Mall+Factory Factory Mall 27 37.27195884
Office+Cave Dark Fire Cave Dark 31 37.8044154
Dark Fire Cave Fire Cave 31 37.8044154
Dark Fire Cave Light 31 37.8044154
Dark Fire Cave Normal 31 37.8044154
Dark Fire Cave 31 37.8044154
Dark Light 24 36.87261642
Dark Normal 31 37.8044154
Fire Cave Light 31 37.8044154
Fire Cave Normal 18 36.07393159
Light Normal 31 37.8044154
Sand Beach Dark Beach 23 36.73950229
Beach Mines 22 36.60638815
Beach Pyramid 22 36.60638815
Dark Beach Mines 18 36.07393159
Dark Beach Pyramid 23 36.73950229
Mines Pyramid 22 36.60638815

Pseudocode

updateColor(targetColor, currentColor) {
	if (currentColor == targetColor) {
		return false
	}

	targetB = targetColor & 0x7C00
	targetG = targetColor & 0x03E0
	targetR = targetColor & 0x001F

	newB = currentColor & 0x7C00
	if (newB > targetB) {
		newB -= 0x400
	} else if (newB < targetB) {
		newB += 0x400
	}

	newG = currentColor & 0x03E0
	if (newG > targetG) {
		newG -= 0x20
	} else if (newG < targetG) {
		newG += 0x20
	}

	newR = currentColor & 0x001F
	if (newR > targetR) {
		newR -= 1
	} else if (newR < targetR) {
		newR += 1
	}
	
	newColor = newR | newG | newB
	return true, newColor
}

updateBGPalette(targetPalette, index) {
	if (index >= 0x70) {
		return false
	}

	colorUpdated = false
	for (i = index; i < index + 0x10; i += 1) {
		updated, newColor = updateColor(targetPalette[i], currentBGPalette[i])
		if (updated) {
			colorUpdated = true
			currentBGPalette[i] = newColor
			animatedBGPalette[i] = newColor
		}
	}
	return colorUpdated
}

updateSpritePalette(targetPalette, index) {
	colorUpdated = false
	for (i = index; i < index + 0x10; i += 1) {
		updated, newColor = updateColor(targetPalette[i], currentSpritePalette[i])
		if (updated) {
			colorUpdated = true
			currentSpritePalette[i] = newColor
		}
	}
	return colorUpdated
}

main() {
	this.targetBGPalette = args.additionalData.targetBGPalette
	this.targetSpritePalette = args.additionalData.targetSpritePalette
	this.curIndex = 0
	this.updated = false
	this.curDelay = 64

	while (true) {
		waitFrames(this.curDelay)
		if (updateBGPalette(this.targetBGPalette, this.curIndex)) {
			this.updated = true
		}
		if (updateSpritePalette(this.targetSpritePalette, this.curIndex)) {
			this.updated = true
		}

		this.curIndex += 0x10
		if (this.curIndex >= 0x80) {
			if (!this.updated) {
				paletteFadeComplete = true
				return
			}
			runOnVBLANK($80:9FDF) // Transfer palette to CG-RAM
			this.updated = false
			this.curIndex = 0
			if (this.curDelay >= 2) {
				this.curDelay -= 1
			}
		}
	}
}