Palette fade: Difference between revisions
Piranhaplant (talk | contribs) No edit summary |
Piranhaplant (talk | contribs) No edit summary |
||
(7 intermediate revisions by the same user not shown) | |||
Line 11:
== 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|timing data]] for details.
== Additional data format ==
The format for the additional data in the level is:
{| class="wikitable"
|-
! Offset !! Length !! [[Data types|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 ===
{| class="wikitable"
|-
! Address !! Length !! [[Data types|Type]] !! Name !! Description
|-
| $00 || 4 || pointer32 || additionalData || Pointer to [[#Additional data format|additional data]]
|}
=== Entity memory ===
{| class="wikitable"
|-
! Address !! Length !! [[Data types|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 ==
{| class="wikitable"
Line 86 ⟶ 133:
|}
Highlighted rows in the table below are palette combinations that occur in the original game.
{| class="wikitable"
|-
! Tileset !! Palette 1 !! Palette 2 !! Largest component<br/>difference !! Duration (seconds)
|-
| rowspan="6"|Grass || Autumn || Night || 24 || 36.87261642
|-
|
|-
| Autumn || Winter || 28 || 37.40507298
|- class="highlightrow"
| Night || Normal || 30 || 37.67130126
|-
| Night || Winter || 30 || 37.67130126
|
| Normal || Winter || 23 || 36.73950229
|-
| rowspan="6"|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
|- class="highlightrow"
| Night || Normal || 21 || 36.47327401
|-
| Mall+Factory || Factory || Mall || 27 || 37.27195884
|-
| rowspan="10"|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
|- class="highlightrow"
| Dark || Light || 24 || 36.87261642
|-
| Dark || Normal || 31 || 37.8044154
|-
| Fire Cave || Light || 31 || 37.8044154
|-
| Fire Cave || Normal || 18 || 36.07393159
|- class="highlightrow"
| Light || Normal || 31 || 37.8044154
|-
| rowspan="6"|Sand || Beach || Dark Beach || 23 || 36.73950229
|-
| Beach || Mines || 22 || 36.60638815
|-
|
|-
|
|-
| Dark Beach || Pyramid || 23 || 36.73950229
|-
|
|}
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:
|
Latest revision as of 02:13, 15 July 2024
Monster data | |
---|---|
![]() | |
HP | N/A |
Points | N/A |
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 Edit
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 Edit
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 Edit
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 Edit
Entity arguments Edit
Address | Length | Type | Name | Description |
---|---|---|---|---|
$00 | 4 | pointer32 | additionalData | Pointer to additional data |
Entity memory Edit
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 Edit
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 Edit
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 Edit
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 } } } }