Pixel-Art Light: Day, Night, Weather and Shadows
This post has not been translated yet, so you are reading the English original. Read it at the English URL
Pokémon Crystal draws no night town. It keeps three parts of a day on the clock, 6 hours of morning, 8 of day and 10 of night, and at the hour it reloads its palettes in one frame, with no fade: in the palette file’s blocks the seven world palettes keep 0.41 to 0.68 of their daytime brightness, 0.51 on average (a mean of 0.49 to 0.54 by map group as a town displays them, with its own water and roof colors), and turn blue, while one palette, YELLOW, keeps its lightest color at 0.90, and the people’s palettes do not change on screen.1234 Emerald has no clock for light. It has 18 weather values instead, and rain takes the scenery’s palettes to about 13/16 of their value over one second while the player’s and the townspeople’s palettes stay exactly as drawn; no outdoor map in Emerald or FireRed has rain in its header, and Emerald’s rain comes from triggers and scripts on a few maps, two of them routes that keep a four-day calendar.567 Its shadows are one tone and five pixel rows tall at every size, and its darkness is a lit circle of radius 1.5 cells without Flash and 4.5 with it.85 My town, Kiradex World, already keeps four phases of its own, and read from its source on a Mac (nothing here was measured on an iPhone), its night leaves 0.588 of the atlas’s daytime brightness, its lit windows stand at 0.901 against a brightest tinted pixel of 0.562, and the change is a cut read once a minute.9 It has no weather, its walkers darken with the ground, and no building throws a shadow on the ground. So the brief keeps the four phases the homage post settled, ramps the light over 30 minutes, lifts the walkers halfway to white, rolls one weather a day per zone with rain at Emerald’s density and darkness, draws 33 cells of building shadow into the ground, refuses real local weather, moving sun shadows and any lightning flash, and gives each item a check that a unit test, a UI test, a forge check, a script or a capture can run, naming only what the app has today or what the brief proposes to add.101112
TL;DR
- A day is a palette set, not a second town. Crystal splits the day 6, 8 and 10 hours at 4:00, 10:00 and 18:00, holds five blocks of background palettes (morning, day, night, dark, indoor), and at night keeps, in those blocks, a mean 0.511 of each world palette’s daytime brightness (0.509 as a town displays them, with its own water and roof colors) while blue rises by 6.44 in 5-bit units (7.27 as displayed); morning changes only 5 of 32 colors, all the lightest one.12134
- Lit things refuse the dark, and people keep their color. Crystal’s YELLOW keeps its lightest color at luma 0.90 at night and in the dark when every other world color falls below 0.6; its five people palettes change only their transparent color. Emerald’s rain darkens 14 of 16 background palettes and leaves exactly the player’s and the five townspeople slots’ palettes on an identity map.114515
- The hour cuts; weather fades. Crystal swaps its palettes on the frame the hour changes and keeps its 8-frame fades to white for doors, warps, Fly, the screens its menus open and some scripted scenes (its fades to black are called only by eleven map scripts; its fades to white also by 16); Emerald fades into rain in 3 steps of 20 frames, 1.00 second; Stardew Valley’s night “gradually becomes darker over time.”1516
- Weather belongs to places and days. No outdoor map’s header is rain in Emerald (68 maps) or FireRed (76); Emerald’s rain comes from triggers and scripts on a few maps (of 86 coordinate weather events on 5 maps, 4 are RAIN and 24 start the two route cycles; of 24 scripted weather calls, 1 is RAIN and 2 a downpour), and Routes 119 and 123 step through a four-day cycle, one stage a day. Stardew rolls weather once a day by season and forecasts it the day before.571718
- Rain is sparse and fast; lightning is short and rare. 10 drops a screen at 868 pixels a second, 26.6 degrees from vertical, 18 frames each and a 7-frame splash; bolts held 6 to 8 frames, at least 61 dark frames apart, cycles 6 to 12 seconds apart; a change to rain during play has its drops in place about two seconds after the change, the palettes a second sooner.54
- Kiradex’s day works and cuts. Its four phases (3, 8, 3 and 10 hours) take the atlas at night to 0.588 of its day brightness, an untinted lit mesh stands at 0.901, and additive pools add light without ever darkening; but the light is a cut read every 60 seconds, the walkers take the night tint, nothing reads Reduce Motion in
Air, and buildings throw no ground shadow. All of it is read from source and exported images on a Mac, not measured on an iPhone.91920 - What this gives Kiradex. A brief: the homage post’s four phases kept, a 30-minute ramp computed from the clock (steps of 0.0036 instead of a cut of 0.108), walkers lifted halfway to white (luma 0.796 at night against the place’s 0.593), five weather states rolled once a day per zone, rain at 15 drops a view darkened to 13/16 with a one-second fade, cloud shadows at one tile a second, 33 cells of baked building shadow, rooms whose windows show the outside’s phase, a Reduce Motion mode, no lightning flash, and eleven checks. Its Kiradex numbers are computed on a Mac from the app’s source and exported pictures, not measured on an iPhone.101112
Kiradex is my card-collecting app for iPhone, and Kiradex World is the small pixel town inside it where collectors walk, meet and show cards. The town already keeps a day. Daylight.swift reads the hour from the phone and names four phases; the rig multiplies everything unlit by the phase’s tint; the lit windows and the hall’s sconces are a second mesh drawn untinted; and since build 29 the lamps and windows throw additive pools on the ground, a cloud sheet drifts over the town by day, mist sits in the park at morning, leaves fall in autumn, fireflies wander at night; and six chimneys smoke, which the design plan puts in build 28.212022239 The file that does the air says what it is for in a line: “none of it takes the day’s tint: light is light.”22
What the town does not have is weather, a change of light that anyone would call gradual, a reason for its people to stay legible at night, or a single building shadow on the ground. The forge that composes the buildings put that last one off in its own docstring: “The ground shadow stays off the building: it belongs to the ground layer.”24 So this post does for light, weather and time what the earlier posts in this series did for buildings and motion. It measures how Crystal keeps its day (the hours, the palettes, how the change is shown and on which maps), how Emerald does weather (its constants, its darkening, its rain, fog, sand, clouds and thunder, and where each one happens), how both handle darkness and shadow, what Stardew Valley’s wiki records of its day and weather, and what the makers of modern lit pixel games say about their light. Then it reads Kiradex’s own light from its source and writes the brief as a diff against what ships.
How this was measured
The Pokémon numbers come from shallow clones of the pret decompilations, the community projects that rebuild each game from C and assembly with its constants named: pokecrystal at 5beda23 (committed September 29, 2026), pokeemerald at 731ad5b (October 1), pokefirered at 037335f (September 26) and pokered at d2704a6 (September 22).25 Four Python scripts measure them and Kiradex, a fifth carries the canon’s numbers into Kiradex’s units, and a sixth reads the saved Apple documentation pages; each is saved with its output in the author’s evidence folder for this post, each re-runs in seconds. I re-ran all of them before publishing. Two gave their saved outputs back byte for byte, and four were corrected along the way, each with its earlier versions and outputs kept beside it: the proposals script, to place the building shadow row by row, to count the weather’s wet spells as section 10 defines them, and to read Kiradex’s walk from the app’s Motion.swift instead of carrying 4 tiles a second (read from its source, the app’s walk is 60 pixels a second, and sections 2, 7 and 10 carry it); the Emerald script, whose rain label had printed the spawn counter’s limit as the spawn period (section 2 carries the corrected figures); the Crystal script, which gained a census of what calls each of its four fades, whose parser of the map headers had taken only single-word fields and so dropped the five Radio Tower floors, whose music field is an expression (388 headers, not 383, and section 1 carries every count that moved), which now also resolves the palette set a map displays, from environment_colors.asm and the roof table, and counts which screens go through the menu fades and which indoor maps take a palette table of their own; and the Kiradex script, whose cloud line had carried the same 4 tiles a second as a label. Their other sections print as before.1215891126 A seventh replays on paper where the camera settles in two of the brief’s captures, an eighth, arith.py, prints every derived figure the text uses, from frame times to the ratios of night to day, a ninth compares the app’s color constants with Crystal’s palettes, a tenth replays Emerald’s rain setup and its thunder cycle call by call from the state machines in its source, and an eleventh counts how much of the collector’s frame the collector covers and computes the night’s ratios for each standing drawing and each way a renderer may treat the tint, for the brief’s check 3; the seventh, the ninth and the tenth re-ran to their saved outputs too.274282930
Brightness here is luma: Rec. 601 weights (0.299 red, 0.587 green, 0.114 blue) applied to the stored channel values scaled to 0 to 1, with no gamma and no model of the screen. It is a reading aid for comparing palettes, not colorimetry, and the Game Boy Color’s colors are as stored, not as its LCD showed them.121 A cell is the handhelds’ 16-by-16-pixel map square and a tile is Kiradex’s, 16 texels on a side, so they compare one for one. Seconds at the handhelds’ rate use 59.7275 frames a second, which is 2^24 ÷ 280,896 for the Game Boy Advance and 2^22 ÷ 70,224 for the Game Boy Color, the same rate.124
One limit runs through the whole post. Nothing in it was measured on an iPhone. Every Kiradex number is read from the app’s source as text, or computed from its exported pictures on a Mac: the tints applied by script to the shipped town atlas, the cloud picture’s alpha, the town’s export. Where the text gives a Kiradex brightness, it is that computation, which assumes the rig’s multiply tint acts per channel on the texture as SpriteSheet.material uses UnlitMaterial’s tint; where a cost on the device would matter, it is marked unmeasured.129
1. Crystal’s day, measured from source
Gold, Silver and Crystal keep time on a real-time clock, and Crystal is the one of the three this post reads.2 Everything in this section comes from measure_gen2_daylight.py, which reads the clock’s constants and table, the three palette files that change with the hour, the routine that applies them, the table that says which of the file’s palettes a map of each environment displays, and the 388 map headers that say which maps take the clock.1
Three parts of a day
The constants are MORN_HOUR 4, DAY_HOUR 10 and NITE_HOUR 18, and the TimesOfDay table that turns an hour into a part of the day carries its own summary as a comment: “0400-0959 morn | 1000-1759 day | 1800-0359 nite”.21 That is 6 hours of morning, 8 of day and 10 of night, so night is the longest part, 41.7% of the day.14 The homage post in this series read the same constants and the same comment, and they agree.10
A fourth state exists for places without a sky. The variable that holds the time of day, wTimeOfDay, takes MORN, DAY, NITE and DARKNESS, and the background palette file bg_tiles.pal holds five blocks of eight palettes, one each for morning, day, night, dark and indoors, plus a pair for overworld water.31131 The eight palettes of a block are the eight named ones every Johto tile takes: GRAY, RED, GREEN, WATER, YELLOW, BROWN, ROOF and TEXT.132
Three days on one clock, from Crystal’s constants, Kiradex’s Daylight.swift and the Stardew Valley Wiki; drawn by figures/make_figures.py.
Night is half the light, and blue
Brightness, not color: the mean luma of each palette’s four colors by time of day, the file’s base blocks and, beside them, the water a town displays, from gen2_daylight.txt; and Kiradex’s atlas from kiradex_light.txt, computed on a Mac from the shipped atlas, not measured on an iPhone. No palette color is reproduced.
In the palette file’s five blocks, measured as the mean luma of each palette’s four colors, the night block keeps a fraction of the day block’s brightness that runs from 0.406 for RED to 0.684 for YELLOW, a mean of 0.511 over the seven world palettes. TEXT, the palette of the interface, is identical at every hour.1 In the dark block the world palettes fall to luma 0.009, except YELLOW at 0.224.1
Those are the blocks as the file holds them, and a town does not display them unchanged. environment_colors.asm names, for each environment and each time of day, which eight of the file’s 42 palettes to load: a TOWN or ROUTE takes the block’s own palettes except WATER, which comes from the file’s overworld pair, one palette for morning and day and one for night, and LoadMapPals then writes the map group’s two roof colors over ROOF’s two middle colors. An INDOOR or GATE map, by morning and day, takes the file’s indoor block, unless its tileset loads a table of its own (below); a CAVE or DUNGEON takes the blocks as they are.33341 Resolved that way, a town’s water has a mean luma of 0.528 by day and 0.248 at night, 0.470 of the day against the base block’s 0.448, and its ROOF, over the 25 map groups that hold a town or a route, keeps 0.369 (Blackthorn’s) to 0.688 of its day; the seven world palettes as a town displays them keep a mean of 0.494 to 0.539 of their day brightness, by group, 0.509 over the 25, against the blocks’ 0.511.14 The figure draws the blocks and, beside them, the displayed water; the numbers below say which set they are from.
Night also changes hue. Over the world colors other than YELLOW and TEXT, blue minus the mean of red and green is −0.73 by day and +5.71 at night in the base blocks, in the Game Boy Color’s 5-bit units, a rise of 6.44, about a fifth of the full scale of 31; with the town’s water in place of the block’s, −1.17 and +6.10, a rise of 7.27.14 The mechanism is plain in the file: in the night block the lightest color of every world palette but YELLOW is the same (15, 14, 24), a blue gray of luma 0.498, against (27, 31, 27), luma 0.947, for most of them by day. As a town displays it, five of the seven share that color, GRAY, RED, GREEN, BROWN and ROOF (whose lightest color the roof table leaves alone); the water’s lightest night color is the overworld pair’s own, (15, 13, 27), luma 0.490, one step off in green and three in blue.14 On my reading, a shared lightest color, with the water a step from it, is what makes a Crystal town at night read as one place under one sky rather than six palettes each dimmed on its own.
Morning is a warmer white
Morning is not a scene of its own. It differs from day in 5 of the 32 world colors, 15.6%, and all five are color 0, the lightest, of GRAY, RED, YELLOW, BROWN and ROOF: (28, 31, 16) in the morning against (27, 31, 27) by day, the blue channel down by 11.14 Everything darker in each palette is the day’s. The morning of the canon is the daylight with its whites warmed, and nothing else moves.
One palette refuses the dark
At night and in darkness, YELLOW’s color 0 is (30, 30, 11), luma 0.90. It is the only world color above luma 0.6 in either block, and in the set a town displays; every other palette’s lightest color sits at 0.498 at night (the town’s water at 0.490) and near black in the dark.14 In the Johto tileset, johto_palette_map.asm gives YELLOW to 3 of its 192 tiles, 1.6%: bank 0, tiles 38, 55 and 56. GRAY has 36, BROWN 99, RED 26, GREEN 12, ROOF 9 and WATER 7.3514
To see what those three tiles are, the script renders them at four times their size for looking; the render is derived from copyrighted art and is kept for analysis only, not published.1 To me, tile 38 looks like a window with two panes. That is my reading of a picture, not a label in the source, which names only the palette. If it is right, Crystal’s lamp is a color, not a light: the game marks the few tiles that should glow at night by giving them the one palette whose lightest color does not fall, and the window stays lit while the town around it goes blue.
What is lit stays bright: Crystal’s YELLOW against its other night colors, and Kiradex’s lit cells and pools against its tinted town; from gen2_daylight.txt, kiradex_light.txt and arith.txt. Kiradex’s values are computed on a Mac from its exported pictures, not measured on an iPhone.
The people do not darken
The object palettes, in npc_sprites.pal, follow the time of day too, but not in the way the background does. The five people palettes (red, blue, green, brown and pink) change only color 0 between day and night or darkness, and color 0 of an object palette is transparent on the Game Boy Color, so on screen they do not change at all. SILVER does not change in any color. Only TREE and ROCK, the palettes of objects that are scenery, change all four colors.141 At night in Crystal, the town goes blue and the people walking in it are drawn exactly as they are at noon.
Roofs keep their own night
Roofs have a table of their own. roofs.pal holds 27 entries, the first unused, one per map group, and each entry has a pair of colors for morning and day and a pair for night. Over the groups, the night pair’s luma over the day pair’s runs from 0.247 to 1.000, a mean of 0.556.361 The structures post in this series described the same table and how one tileset makes every Johto town its own color.23
What the reload loads
LoadMapPals applies all of it when a map loads or the time changes. It copies eight background palettes of 8 bytes each for the time of day, the eight that environment_colors.asm names for the map’s environment, except on six indoor and cave tilesets (TILESET_POKECOM_CENTER, TILESET_BATTLE_TOWER_INSIDE, TILESET_ICE_PATH outside the Hall of Fame, TILESET_HOUSE, TILESET_RADIO_TOWER and TILESET_MANSION), whose background palettes LoadSpecialMapPalette loads first from tables of their own, so that copy is skipped; then eight object palettes for the time of day, on every map; and then, in a TOWN or a ROUTE only, the map group’s two roof colors, 4 bytes, into the ROOF palette.341 No TOWN or ROUTE map uses one of the six tilesets (75 INDOOR maps and 5 CAVE maps do, and of those INDOOR maps all but the Hall of Fame, on TILESET_ICE_PATH, take a table of their own: 74), so outdoors that is 64 bytes of background palette, 64 of object palette and 4 of roof, 132 bytes in all.14
That corrects a sentence of this series. The first post, on worlds, wrote that “time of day in Johto is a 64-byte palette reload”.32 It is true of the background palettes. Measured against LoadMapPals, the reload also copies 64 bytes of object palettes and, outdoors, 4 bytes of roof color; the people’s palettes look the same afterward, because only their transparent color changed, but the bytes are reloaded.341 That is a finding of this post. I have not edited the earlier one.
The change is a cut
The overworld loop calls UpdateTime, GetJoypad and TimeOfDayPals on every pass while map events are on; HandleMapTimeAndJoypad returns first when wMapEventStatus is MAPEVENTS_OFF.371 _TimeOfDayPals returns at once when a forced palette set is in effect (FORCED_PALSET_F) or when the time of day has not changed. When it has, it swaps the palette set, calls _UpdateTimePals once, which is ld c, $9 ; normal followed by DmgToCgbTimePals, and waits one frame. There is no stepped fade between morning and day, day and night, or night and morning: the palette set changes on one frame.31
The game does have fades, and keeps them for something else. FadeInFromBlack, FadeOutToBlack, FadeInFromWhite and FadeOutToWhite each step through 4 palettes at 2 frames a step, 8 frames, 134 milliseconds.314 The white pair is the one the map setup scripts use. A warp the player steps on, a door or a cave mouth, loads the map with the Door script, which fades out to white and, falling through the Train script, fades in from white; the Warp script, which a scripted warp runs, fades in from white two commands before its end; Fly’s script fades out to white and falls through the Warp script; and the screens that open through FadeToMenu and return through FinishExitMenu fade out to white and back: six of the Start menu’s items do (StartMenu_Pokedex, StartMenu_Pokemon, StartMenu_Pack, StartMenu_Status, StartMenu_Option and StartMenu_Pokegear), and 15 more calls in seven other files, the mart’s, the PC’s and the Select item’s among them. The Start menu itself has no fade: StartMenu draws over the map and calls UpdateTimePals, which applies the current time’s palettes, as it opens and as it closes. No setup script names the black pair. It is reached only by special in eleven map scripts, each with a fade out and a fade back, among them Sprout Tower’s third floor, Route 24 and the Team Rocket base’s second basement.381 The map scripts call the white pair too, and more often: 27 fades out to white and 12 back in, by special, in 16 map scripts, among them scenes in which nobody goes anywhere, Daisy grooming a Pokémon in Blue’s house and the scenes on the lighthouse’s sixth floor; and one of the black pair’s eleven, the rest house on Route 26, is a rest and a heal, not a trip.1 So the canon’s grammar, as the census shows it: a fade marks a change of scene, a trip, a menu or a scripted moment, and the hour is none of them.
Whether a player saw the cut depended on where they were at 4:00, 10:00 or 18:00. Outdoors on a map that follows the clock, it was on screen on that frame: the overworld loop applies the palettes while the player walks. Indoors, the palette that changed was not the one displayed. In the screens that open through FadeToMenu, the return reloads the time-of-day palette before its fade in from white, so the new set arrived under a fade; in the Start menu itself, UpdateTimePals applied it as the menu closed.37381 How often each of those happened is not something the source can say. A phone that someone has left open on a plaza is a different case, and section 7 comes back to it.
Only the outdoors keeps time
Each map header names an environment and a palette mode. Over the 388 headers, all 23 TOWN maps and all 54 ROUTE maps are PALETTE_AUTO, the mode that follows the clock, and one CAVE map is too: 78 maps of 388, 20.1%.3914 Indoors the clock stops. 207 of the 208 INDOOR maps and 24 of the 25 GATE maps are PALETTE_DAY, fixed at daylight whatever the hour. The daylight 133 of those INDOOR maps show is the file’s indoor block; the other 74, on the HOUSE, RADIO_TOWER, MANSION, BATTLE_TOWER_INSIDE and POKECOM_CENTER tilesets, show the tables LoadSpecialMapPalette loads for those tilesets (What the reload loads, above).13334 13 maps are PALETTE_DARK: the eight maps of the Whirl Islands, Silver Cave’s first room, Dark Cave’s two entrances and Rock Tunnel’s two floors. 27 are PALETTE_NITE, permanent night. 24 of those are CAVE maps, Ilex Forest and Tohjo Falls among them; the other three are Burned Tower’s first floor (a DUNGEON map), the Dragon Shrine (INDOOR) and the Underground Path (GATE).1394
So in Crystal, time of day is a property of the open air. A house at 22:00 is lit like a house at noon, a forest under its canopy is always night, and the dark is a third thing again.1
Flash is a palette; Red’s dark is an offset
Crystal’s Flash does not draw a circle. On a PALETTE_DARK map, ReplaceTimeOfDayPals uses the DARKNESS set unless the status flag STATUSFLAGS_FLASH_F is set, and then uses the NITE set for every time slot.3 Lighting a cave with Flash turns it from black to night, the same blue palettes the towns wear after 18:00.
Red, the generation before, has one dark place and handles it in one line. When the destination of a warp is ROCK_TUNNEL_1F, the warp code sets wMapPalOffset to 6, and LoadGBPal subtracts that offset from FadePal4, landing on FadePal2, whose background entry is dc 3,3,3,2: almost all black.4041 That warp, from an outdoor map, is the only place pokered sets the offset to anything but 0 or the value a battle saved and puts back. A warp between two indoor maps leaves it as it was, and the floor below, ROCK_TUNNEL_B1F, has four warps, all to the first floor, so it is reached only from there and stays as dark; Flash, a blackout, a fly or dungeon warp and the warp back outside each set it to 0.4042 Red has no clock, and Rock Tunnel, both floors of it, is its one dark place.
2. Emerald’s weather, measured from source
No clock for the light
The Game Boy Advance games went the other way. Neither Emerald nor FireRed has a time-of-day system for light: no file in src/*.c or include/**/*.h of either decompilation matches “timeofday” or “time_of_day”.5 Emerald does keep a real-time clock, and the homage post measured what it drives: berries that grow in real hours and the eleven daily updates that run when the date changes, the weather among them (the alive post names the function, UpdateWeatherPerDay).1043 None of it touches the palettes by hour. Where Crystal spent its color on the time, Emerald spent it on the sky.
Eighteen weathers, three kinds
include/constants/weather.h names 18 WEATHER_* values: NONE, SUNNY_CLOUDS, SUNNY, RAIN, SNOW, RAIN_THUNDERSTORM, FOG_HORIZONTAL, VOLCANIC_ASH, SANDSTORM, FOG_DIAGONAL, UNDERWATER, SHADE, DROUGHT, DOWNPOUR, UNDERWATER_BUBBLES, ABNORMAL and two route cycles. The header marks three of them “// Unused” (SNOW, FOG_DIAGONAL and UNDERWATER), and SHADE carries a comment of its own: “Original name was closer to WEATHER_CLOUDY/OVERCAST”. Thirteen more COORD_EVENT_WEATHER_* values exist for map triggers.445
Grouped by what they do to the screen, they fall into three kinds.57
- Weathers that darken the palettes. Four in use, RAIN, RAIN_THUNDERSTORM, DOWNPOUR and SHADE, take the palettes to color map 3; so does the unused SNOW. Each sets
targetColorMapIndex3 and acolorMapStepDelayof 20 frames in itsInitVars.5 - A weather with its own tables. DROUGHT uses precomputed color tables of its own, reached through negative map indices, −6 after its fade-in, in
src/field_weather.c.612 - Weathers that leave the palettes alone. SUNNY_CLOUDS, FOG_HORIZONTAL, FOG_DIAGONAL, VOLCANIC_ASH and SANDSTORM keep color map 0 and instead blend a layer of sprites over the scene, with the hardware’s alpha blend,
(eva × target1 + evb × target2) / 16per channel: clouds toward 12 and 8, horizontal fog toward 12 and 8 and later 4 and 16, ash and sand at 16 and 0.5
Rain darkens the place to 13/16
The darkening comes from BuildColorMaps, which the script replays in Python. It builds 19 maps, each a table from a 5-bit channel value to another. The first three subtract the value divided by 16, one step at a time, so map 3, the one rain and shade use, maps a channel value v to about 0.81 v: 31 becomes 25, 16 becomes 13, 8 becomes 6. Over the values 1 to 31, output over input never exceeds 0.8125, reached at 16.65 Rain reaches it in 3 steps of 20 frames, 60 frames, 1.00 second.54
That makes 13/16, 0.8125, the canon’s number for rain: every channel of every darkened palette at about four fifths. It is a gentle darkening against Crystal’s night, which keeps half the light.14
Emerald’s rain and lightning maps, replayed from BuildColorMaps by figures/make_figures.py, which asserts the points measure_gen3_weather.py printed. Curves, not art.
Rain leaves people alone
Which palettes take the darkened map is a table too, sBasePaletteColorMapTypes, 32 entries, one per hardware palette.6 Background palettes 0 to 13 take the darkened map; 14 and 15, the interface, take none. Of the 16 object palettes, 10 take the darkened map and 6 take the plain “contrast” map, whose map 3 is the identity: exactly what is drawn stays what is drawn.5
Read against the palette slots named in include/event_object_movement.h, those six are object palettes 0, 2, 3, 4, 5 and 10, which are PALSLOT_PLAYER, PALSLOT_NPC_1 to PALSLOT_NPC_4 and PALSLOT_NPC_SPECIAL: the player and the five townspeople slots. Every reflection slot, the player’s and each townsperson’s, takes the darkened map.155 In the rain, Emerald’s ground, buildings, water and even the reflections on that water go to 13/16, and the people walking through it keep their colors.
Two generations, two mechanisms, and one rule. Crystal’s people palettes change only their transparent color at night; Emerald’s people palettes sit on the identity map in rain. The light belongs to the place, and the figures stay readable in it.15 The source states the mapping and not the reason. On my reading it is legibility on a small, dim screen, where a darkened sprite loses its outline first; the effect, either way, is that a person is never the thing you have to squint at.
Lightning is the same tables at the far end
The far end of the same tables is the flash. Map 19, the last, sends every channel value of 12 and above to 31 and leaves values under 12 near where they were: 0 becomes 1, 4 stays 4, 8 stays 8, 11 becomes 10.56 It is a high-contrast flash rather than a white screen: the light half of every palette goes white, and the dark half stays dark.
Rain, measured
Rain is 10 drop sprites; a thunderstorm 16 and a downpour 24, which is also the cap. Each drop is a 16-by-32 sprite. UpdateVisibleRainSprites activates one drop when ++rainSpriteVisibleCounter > rainSpriteVisibleDelay, a counter that fires on the frame after its delay: with a delay of 8 for rain and 4 for the two heavier kinds, a new drop joins every 9 frames for rain and every 5 for a thunderstorm or a downpour, until the count is met. That activation phase alone, counted from zero active drops, is 90 frames, 1.51 seconds, for rain, 80 frames, 1.34 seconds, for a thunderstorm and 120 frames, 2.01 seconds, for a downpour.754 It is not the whole change. Replayed call by call from the state machine, a change of weather during play from a clear sky (SUNNY, whose finish returns at once) begins on the frame Task_WeatherMain swaps the weather; from the next frame, Rain_Main loads the drop sheet on its first call, creates all 24 drop sprites one a call on the next 24, and only then starts activating them, so rain’s last drop is active 115 frames after the swap, 1.93 seconds; a thunderstorm’s 105, 1.76; a downpour’s 145, 2.43. The palettes finish first: three steps of 20 frames take them to map 3 on frame 59, before any kind’s last drop, and a thunderstorm’s first bolt can strike on frame 109. Between two rain kinds the sprites are kept and only the count changes, one drop every 5 or 9 frames: rain to a thunderstorm activates its six more by call 32. When a map loads already raining, Rain_InitAll runs the same steps in a loop inside one frame, 116 calls for rain, and the screen starts full. When the rain ends, the same call retires one drop every 9 or 5 frames.29764
Each drop moves at a fixed velocity stored in Q28.4 fixed point, where 16 is one pixel. Rain falls (−6.5, +13.0) pixels a frame, 14.5 pixels a frame, 868 pixels a second, 26.6 degrees from vertical, for 18 frames, 262 pixels, before it splashes. A downpour drop falls (−10, +20) pixels a frame, 22.4 a frame, 1,336 a second, at the same angle, for 12 frames, 268 pixels.57 Both fall about 16 cells’ worth of distance before they land, in 0.301 and 0.201 seconds.4 The splash animation is 3 + 2 + 2 = 7 frames, 0.117 seconds; the heavy splash 3 + 3 + 4 = 10, 0.167.54
So rain in the canon is sparse and fast: ten streaks on a 240-by-160 screen, crossing it in about a fifth of a second, each living 25 frames, 0.42 seconds, from the top to the end of its splash.4 It reads as rain because of its angle, speed and splash, not its density.
Fog, sand and clouds
The weathers that do not darken move a layer instead.57
- Horizontal fog scrolls 1 pixel every 4 frames, 14.9 pixels a second, a quarter of the walking speed, blended toward 12/16 over 8/16.54
- Diagonal fog, unused, moves 1 pixel every 3 frames across and every 5 down: 19.9 and 11.9 pixels a second.5
- Sandstorm moves its layer by
gSineTable[index] × 4 / 256pixels a frame, the index stepping every 6 frames through 0x20 to 0x5F. Replayed over 6,000 frames against the sine table insrc/trig.c, that is 2.83 to 4.00 pixels a frame, a mean of 3.59 left (215 pixels a second) and 0.90 up: 3.6 times the walking speed, the only weather layer faster than the player.454 - SUNNY_CLOUDS is three 64-by-64 cloud sprites, placed on map cells that the source says are “for the lower half of Route 120”, blended toward 12 and 8, and moved by
UpdateCloudSprite, whose comment reads “Move 1 pixel left every 2 frames.” That is 29.9 pixels a second, half the walk’s 59.7.7511
Air against the walk: Emerald’s layers from gen3_weather.txt, Kiradex’s cloud today and as proposed from kiradex_proposals.txt and arith.txt, with Kiradex’s walk read from its Motion.swift. Kiradex’s speeds are read from its source on a Mac, not measured on an iPhone.
Thunder: short, rare, and heard with the flash or after it
A thunderstorm runs a small state machine, which its source summarizes as a pattern: “(SHORT_BOLT){1,2}(LONG_BOLT)?”.7 Measured:54
- Each cycle after the first waits on a timer of 360 to 719 frames and strikes two frames after it runs out (the next call sets up the bolts, and the one after that strikes): 361 to 720 frames, 6.04 to 12.05 seconds, from the cycle’s start. The first does not wait: once the fade into the storm is done,
THUNDER_STATE_WAIT_CHANGEjumps toTHUNDER_STATE_INIT_CYCLE_1, past the wait.5297 - It strikes 1 or 2 short bolts, each holding map 19 for 6 to 8 frames, 0.10 to 0.13 seconds, then returning to the rain map.57
- Between bolts its timer is 60 to 75 frames, and the dark gap from one bolt’s end to the next strike is a frame longer, 61 to 76 frames, 1.02 to 1.27 seconds; the long bolt’s gap is the same.5294
- In half the cycles a long bolt follows, held 30 to 45 frames, 0.50 to 0.75 seconds, which then fades back to the rain map gradually rather than cutting.57
- The thunder sound is queued on the frame the cycle’s last bolt strikes:
EnqueueThunder(20)as the last short bolt strikes when no long bolt follows, orEnqueueThunder(100)as the long bolt strikes.EnqueueThundersets a timer toRandom() % waitFrames, andUpdateThunderSound, called at the top of each frame’sThunderstorm_Main, counts it down and plays the sound when it reaches 0 and no other sound effect is playing: 1 to 20 frames after the strike, up to 0.33 seconds, or 1 to 100 frames, up to 1.67 seconds, for the long bolt, when nothing else is sounding as the timer runs out. If another sound effect is playing then,UpdateThunderSoundreturns and tries again the next frame, so the thunder waits for that sound to end, and the ranges are not upper bounds. A short bolt holds 6 to 8 frames, so the sound can begin while the bolt is still lit. The source notes that “Thunder only plays on the final bolt of the cycle.”74
A minimum-wait three-bolt cycle after the first (which skips the wait), replayed from the state transitions in gen3_replay.txt: each gap is its timer plus the one frame the next state takes to act. The one-second ruler is arith.py’s.
The arithmetic matters for anyone copying it. The dark gap between two bolts is at least 61 frames, which at 59.7275 frames a second is 1.0213 seconds, longer than a second. So no one-second window of Emerald’s storm holds the end of one bolt and the start of another, and at most one flash falls in any second.4 That is under the W3C’s success criterion 2.3.1, a Level A requirement that content not contain “anything that flashes more than three times in any one second period”.46 Section 10 still refuses a flash for Kiradex, and says why there.
3. Where the weather is: maps, triggers and calendars
Weather is a property of the map
Every Gen 3 map’s map.json names a weather in its header. The script reads all of them, 518 in Emerald and 425 in FireRed, and groups them by map type.547
Emerald’s 68 outdoor maps (9 cities, 7 towns, 41 routes and 11 ocean routes) carry SUNNY 51 times, NONE 13, SHADE 2, FOG_HORIZONTAL 1 and VOLCANIC_ASH 1. FireRed’s 76 outdoor maps carry SUNNY 66, NONE 8 and SHADE 2.54 In both games, three quarters or more of the outdoor maps are sunny by default (75.0% and 86.8%), and no outdoor map’s header is rain.4
Header weather by map, from gen3_weather.txt section G.
Rain comes from triggers, scripts and a calendar
Emerald’s rain is placed, not rolled. The maps carry 86 coordinate weather events, cells that change the weather when the player steps on them, on 5 maps: Jagged Pass and Routes 113, 119, 120 and 123. By kind they are 34 SUNNY, 12 SUNNY_CLOUDS, 12 VOLCANIC_ASH, 4 RAIN, and 12 each for the two route cycles.54 The scripts add 24 setweather calls: NONE 5, SUNNY 4, ABNORMAL 3, DOWNPOUR 2, SANDSTORM 2 (both on Route 111), SUNNY_CLOUDS 2, VOLCANIC_ASH 2, FOG_HORIZONTAL 2, RAIN once (Route 120) and DROUGHT once.5 FireRed has no coordinate weather event at all.5
Two routes keep a calendar. Route 119 cycles SUNNY, RAIN, RAIN_THUNDERSTORM, RAIN, and Route 123 SUNNY, SUNNY, RAIN, SUNNY, one stage a day, advanced by UpdateWeatherPerDay.75 On Route 119 three days of four are wet; on Route 123 one.4 Because the stage moves once a day, a player who walks Route 119 twice on the same day meets the same weather twice, and the weather on a given day is settled before anyone arrives.
Indoors is weather too
The header field is not only for the sky. Emerald gives 2 indoor maps horizontal fog and 1 indoor map shade; underground, 11 maps shade and 7 fog. FireRed gives 5 indoor maps fog and 4 shade, and 14 underground maps fog and 1 shade. Emerald’s 14 underwater maps all carry UNDERWATER_BUBBLES.548 SHADE, the weather whose comment says its original name meant cloudy or overcast, is the canon’s way of saying that a room or a cave is dim.
What this says about weather
Put together, the measurements describe weather as a scheduled state of the world. It belongs to a place (a route, a cave, a room), it changes on a step or a day, it is known in advance by anyone who has walked the route before, and it is the same for everyone who is there.5 Section 5 shows Stardew Valley doing the same thing with different machinery: one roll for the whole valley each day, announced the day before.17 Neither game shows the player’s real weather, and on my reading that is not a limitation of the hardware but the design: a world’s weather is something the world has, not something the player brings into it.
4. Darkness and shadow in Gen 3
A dark cave is a lit circle
Emerald draws darkness as geometry. sFlashLevelToRadius in src/field_screen_effect.c is {200, 72, 64, 56, 48, 40, 32, 24, 0} pixels, nine levels from fully bright to fully black.495 When a map’s header sets requires_flash, which the map tool writes into the header byte the C code reads as gMapHeader.cave, SetDefaultFlashLevel starts it at level 7, gMaxFlashLevel - 1, a radius of 24 pixels, 1.5 cells; once Flash has been used it starts at level 1, 72 pixels, 4.5 cells. Every other map, cave or not, starts at level 0, fully bright.505 The lit area is a Game Boy Advance window whose horizontal bounds are rewritten on every scanline by DMA, centered on the screen at half its width and height, not on the player.5
On the 240-by-160 screen, the circle without Flash covers 4.7% of the picture and the circle with it 42.4%, nine times the area.4 From level 1 to level 7 each level takes 8 pixels off the radius, half a cell; level 8 is black.4
Darkness as a radius in cells: a schematic from sFlashLevelToRadius as printed in gen3_weather.txt. No map is drawn.
Seven Emerald maps set requires_flash, so seven of its 518 start dark: three of them unused maps left over from Ruby and Sapphire (CaveOfOrigin_UnusedRubySapphireMap1 to 3), with Granite Cave’s two lower floors and Victory Road’s two; FireRed has two, Rock Tunnel’s floors.5 Set beside Crystal, the three generations draw a dark place three ways: Red by a palette offset, Crystal by a palette set that Flash swaps for night, and Emerald by a circle of light whose radius Flash multiplies by three.4035
Shadows are one tone and five rows tall
Emerald draws its shadows from four field-effect sprites, read by measure_shadows.py. Small is 8 by 8 pixels, medium 16 by 8, large 32 by 8 and extra-large 64 by 32. Each is one color index, and each is a flattened ellipse in the bottom five rows of its frame: 24 of 64 pixels opaque for the small, 64 of 128 for the medium, 144 of 256 for the large and 304 of 2,048 for the extra-large.8514 Whatever the size of the walker, the shadow is five rows tall and one tone. The series’ elevation post measured the other half of the rule: when a walker hops a ledge, the shadow stays on the ground.52
Kiradex’s walker shadow, shadow.png, follows the same plan: 12 by 4 pixels, one tone, (18, 17, 23) at alpha 153, 40 of its 48 pixels opaque, read from the shipped picture on a Mac, not measured on an iPhone.89 Alpha 153 is 60% coverage, and the tone, luma 0.071, is a near-black with a lean to blue.4
5. Stardew Valley’s day and weather
Stardew Valley is the modern game this series keeps returning to for a town that runs on a calendar, and its wiki documents the day and the weather in detail. These are the wiki’s words and numbers, saved on October 5, 2026; the game’s code was not read for this post, so its night tint values and the speed of its darkening, which the pages read do not give, are not here.12
A day of fourteen minutes, unpaused
The day runs from 6am to 2am, 20 in-game hours, and “Time passes at 0.7 real seconds per in-game minute”, which makes a full day 840 real seconds, 14 minutes, at the wiki’s own two conditions: outside the Skull Cavern, where a minute takes 0.9 seconds (“except the Skull Cavern where it’s 0.9”), and “Ignoring pauses”, since in single-player the clock stops for cutscenes, menus and dialog.164 Nightfall moves with the season: “In Spring and Summer , it is 8pm, while in Fall it is at 7pm and Winter is 6pm.” (the space before the comma is the wiki’s). At nightfall, “indoors the windows turn dark and lamps and sconces light up”, and outdoors “it does not immediately become full dark, but gradually becomes darker over time.”16 From 6am, nightfall comes about 9.8 real minutes into a spring day and 8.4 into a winter one.4
So Stardew does what Crystal does not: the dark arrives as a gradient, and the hour it begins follows the season. It also darkens its interiors, where Crystal fixes 207 of 208 indoor maps at day, with one exception the wiki records: “if nightfall happens while you are indoors it stays light until you exit and re-enter.”161
Lights are map properties
The wiki’s modding page for maps lists the lighting as properties a map carries.53
AmbientLight“Sets the RGB color that is subtracted from white (255,255,255)”, so a map’s darkness is a color taken away, not a color multiplied in.53Lightplaces one of nine light types: lantern, window, sconce, cauldron, indoor window, projector, fish tank, winter town tree lights and pinpoint.53WindowLightadds lights “only lit during the day when it is not raining”, the light that comes in through a window from outside.53NightTiles“Changes the tile after 7pm”, and the page adds that it “is mostly used for lamps in the game.”53
The structures post used NightTiles as the model for Kiradex’s lit windows.23 WindowLight is the other half, and it is the one Kiradex does not have yet: a room whose window shows the light outside.
Weather by the day, announced the day before
Stardew’s weather is rolled once a day and announced: the wiki’s weather page says that “Weather changes daily and with the seasons”, and that the next day’s weather “can be checked by viewing the TV screen”, under the TV’s weather report.17 The modding page’s rates, for dates without forced weather:18
- Spring: “an 18.3% base chance of rain”, and “in spring there’s a 66.4% chance of sunny weather.”18
- Summer: “The chance of sunny weather diminishes steadily per day from 87.4% on summer 1 to 79.9% on summer 28”, and “There’s an 85% chance that rain becomes storms.”18
- Winter: “a 63% chance of precipitation”, and, of rain itself, “Winter: never rains.”18
- Debris (wind): “approximately 16.6% chance of debris weather” in spring and “approximately 49.8%” in fall.18
During a storm, “There is a chance for a lightning strike to occur every 10 minutes”, and the game lets the player turn snow down: “Snow transparency can be changed in the Options Menu”.17 That last line is a small accessibility setting for weather, and section 10 takes the hint.
6. The modern lit pixel games, in their makers’ words
The first post in this series set out a tier list for lighting pixel art, summarized here: Moonlighter’s overlaid sprites at the bottom, Celeste’s light masks, Eastward’s hand-painted bump maps, and at the top Sea of Stars and HD-2D’s lit 3D scenes; it also places Animal Well and Dead Cells.32 This section goes back to the makers for what each says its light is for. None of them gives numbers for a day cycle or for weather; the only numbers among them are Celeste’s texture and light budget, and no Eastward or Sea of Stars statement with lighting numbers was found.12
Moonlighter: no lights at all
Digital Sun told 80.lv that “There is no dynamic lighting”, and that “we use the superposition of sprites to give the impression of light (for example in Rynoka during the night).”54 Night in its town is drawn, not computed.
Celeste: light that hits walls
Noel Berry’s account of remaking Celeste’s lighting, from September 25, 2017, begins with a requirement: simple spotlights “wouldn’t cut it”, because “the lights needed to actually hit walls.” His method draws each light’s spotlight into a mask first, and then every wall surface draws “their shadow projection (each just a single quad)” on top, cutting the shadow out of the spotlight. Each light takes one color channel of a 2048-by-2048 texture divided into 256-by-256 slots, 64 slots of four channels, so “we can have a total of 256 lights on a single, big texture.”554
Eastward: a 3D game that looks 2D
Tommo Zhou of Pixpil told Game Developer on October 11, 2021: “Interestingly Eastward is a 3D game with a 2D perspective!” The studio uses “shader textures and lighting systems in our custom engine (Maoi) to help convey atmosphere, and things like the weather and the time”, with “intricate fog layers and sunlight beams breaking through the landscape during dawn and dusk”, and its assets are rebuilt in 3D and “hand painted in the bump map one by one”.56
Octopath Traveler and HD-2D: where the shadows fall
In a Nintendo Dream interview translated by Nintendo Everything on November 3, 2018, Octopath Traveler’s Shizuka Morimoto said: “At first, I started using some techniques to add dramatic lighting to the 2D art, but Asano told me that wasn’t enough.” Tomoya Asano: “I wanted to make use of lighting mixed with pixels in a way that emphasized how light shines on objects and how shadows fall.”57 Wikipedia, a secondary source, describes the technique the series came to be known by, HD-2D: “Typically, a point light source is placed into the scene to make characters and objects cast shadows”, with tilt-shift, depth of field, bloom, volumetric lighting and fog added on top.58 Asano’s own verdict on the cost, from his 4Gamer interview as Nintendo Life reported it in May 2022, is the line under the article’s headline: “It costs more than you’d think”.59 Unreal Engine’s two Octopath Traveler pages returned HTTP 403 to the fetcher and to curl, so the Nintendo Dream translation stands in for them.12
Sea of Stars: lighting as a feature, and a clock in play
Sabotage’s press kit for Sea of Stars lists “Full-on dynamic lighting. Our custom-made render pipeline allows the creation of a breathtaking world coming to life by pushing the limits of 2D pixel-art games.”60 TechRaptor’s report on a gameplay clip shows its day and night change driving play: it “influences gameplay as well, specifically some interesting little puzzles that materialize bridges at the right time of day.”61 Sabotage Studio’s Thierry Boulanger told Screen Rant in August 2023, as the elevation post in this series quotes him, that its levels had “to all read well with different levels of height, with the light and everything”.52
Shadows drawn by hand
Two artists give rules for shadows that need no engine at all: three articles by one, a line from the other.
Raymond Schlitter, in his Pixelblog on top-down objects, on cast shadows: “It’s best to keep them subtle and not cast out long in any particular direction”, and “in most cases I make the shadows on separate tiles and place them on a different layer than the sprites.”62 In the second part of his top-down tiles post, on the drop shadow under a wall: “it’s best to limit them to one or two wall faces, minding light source, and they should not exceed a tile in length. No matter how tall the rock wall all shadows are the same length.”63 And in his top-down trees post, on where a tree’s shadow goes: “It’s most practical for game design to center the shadow directly under the tree, so that it doesn’t run into other assets”.64
Koichi Ishii, on Seiken Densetsu 3, in Shmuplations’ translation: “for shadows and the like, we’re using deep blues and purples instead of shades of black, to impart a sense of softness.”65
The tiers, read for a phone
The makers’ words add one thing to the tier list. Every tier above Moonlighter exists to make light interact with something: Celeste’s light hits walls, Eastward’s falls on bump-mapped surfaces, Octopath’s shows “how shadows fall.”555657 Light that only adds, never blocked and never shaped by what it lands on, is Moonlighter’s tier, however it is blended.
Kiradex’s pools are that: light added to the ground with real additive blending, interacting with nothing (section 7).6622 That is not a fault, on my reading. It is the tier a phone town can afford, and the hand-drawn rules above say how far it can be taken without an engine: shadows on their own layer, short, on one or two faces, the same length whatever the height, and tinted toward blue rather than black.626365
7. Kiradex’s light today, read from source on a Mac
Everything in this section was read on October 5, 2026 from the Kiradex repository’s working tree, from 16:51 to 18:19, at commits cf3beb1e to 66c116b0, while the tree was being edited that day, without running git or the forge, each note naming its reading, among them: Kiradex/World/Daylight.swift, Motion.swift, Air.swift, Glow.metal, PlazaStage.swift, PlazaRig.swift and HallView.swift, the forge’s scripts/forge/air.py, town.py and buildings.py, and the exported town, Kiradex/Resources/World/town.json (40 by 30 tiles, the file dated October 3, 2026 at 21:34) with its atlas and air pictures.912 measure_kiradex_light.py reads the Swift and Python as text and applies the tints to the shipped pictures with Pillow. None of it ran on an iPhone, and no number here is a capture: where the text says the town’s night is 0.588 of its day, that is the shipped atlas multiplied by the shipped tint on a Mac.9
Four phases on the phone’s calendar
Daylight.Phase maps the hour to morning from 6 to 9, day from 9 to 17, evening from 17 to 20 and night otherwise: 3, 8, 3 and 10 hours.219 The lamps are lit in evening and night, lampsOn being self == .evening || self == .night, 13 hours a day, 54.2% of it.214 These are not Crystal’s hours, on purpose. The homage post set Kiradex’s phases beside Crystal’s 4, 10 and 18 and decided that they stay Kiradex’s own; this post keeps that decision.10
Each phase is a tint, “a multiplier on the art’s own colours”, and the file declares the four as an art surface, “four fixed lights for a pixel world”.21 With their luma:9
| Phase | Tint (red, green, blue) | Luma | Blue minus the mean of red and green |
|---|---|---|---|
| Morning | 1, 0.96, 0.86 | 0.961 | −0.12 |
| Day | 1, 1, 1 | 1.000 | 0.00 |
| Evening | 1, 0.78, 0.6 | 0.825 | −0.29 |
| Night | 0.5, 0.58, 0.9 | 0.593 | +0.36 |
The season adds a cast, multiplied in: spring (0.97, 1, 0.95), luma 0.985; summer white, 1.000; autumn (1, 0.93, 0.84), 0.941; winter (0.9, 0.94, 1), 0.935. Over the 16 combinations of phase and season, the darkest light is an autumn night, (0.5, 0.539, 0.756), luma 0.552, and the brightest a summer day at 1.000.921 The season is chosen by month, “in the north; the south is a setting for later”, in the file’s own words.21
The shape matches the canon’s. Night is the longest phase in Crystal and in Kiradex, 10 hours of 24; Kiradex’s morning is the daylight warmed, a tint of luma 0.961 over every color, where Crystal warms only its whites; and Kiradex’s night leans blue as Crystal’s does.194 Kiradex adds an evening, warm and lamp-lit, which Crystal does not have.
What the tint does to the town
Applied to the shipped atlas, town.png (256 by 352 pixels, 81,926 of them opaque), the four tints give a mean luma of 0.432 in the morning, 0.449 by day, 0.372 in the evening and 0.264 at night: 0.962, 1.000, 0.829 and 0.588 of the day.94 Kiradex’s night keeps a little more light than Crystal’s average, 0.588 against 0.511, and a little less than its night tint’s own luma, 0.593, because the atlas’s colors lean to the warm channels that the night tint cuts.4 On my reading that is the right side to err on for a phone held at arm’s length.
Lit things refuse the tint
Kiradex arrived at Crystal’s answer by another road. The lit windows and the hall’s sconces are a second mesh whose material is drawn with light: .white, untinted, enabled when the lamps come on; the forge exports for each lit cell only the pixels that change when it is lit.2023 In the export those are 24 cells in three atlas indices: 20 windows (index 200, 54 opaque pixels each), 2 gable windows (index 201, also 54: the window buildings.py sets at the middle of a gable’s triangle, on house-mine and the hall; the dormers are a roof piece with no lit variant) and the hall’s 2 sconces (index 257, 12 pixels, at cells (21, 5) and (23, 5)).9
Their brightest pixel is luma 0.901, drawn untinted. Under the night tint, the brightest pixel anywhere in the atlas is 0.562. The lit cells are 1.60 times the brightest night-tinted atlas pixel; that leaves out the additive pools, whose lamp centers reach 0.785 to 0.806 (below).94 Crystal’s YELLOW, at 0.90 against the 0.498 of the other palettes’ lightest night color (the town’s water a step off it, at 0.490), is the same idea in a palette: what is lit keeps its brightness, and the town around it falls.14
That count corrects another post in this series. The structures post describes “nineteen windows and the hall’s two sconces” and says the “21 lit cells dedupe to two”.23 The export of October 3 at 21:34 has 24 lit cells in three atlas indices: 20 windows, 2 gable windows and the 2 sconces.94 Either the town changed after that post was written or the post counted differently; against the shipped export it is out of date by three cells and one atlas index. That is a finding of this post, and I have not edited the earlier one.
Pools of light that only add
Since build 29, lamps and lit windows throw pools of light on the ground at night. Glow.metal is a RealityKit surface shader that writes the picture’s color times the tint as both the base and the emissive color, with opacity 1, and the material blends by adding; its comment gives the reason: “black adds nothing, and a pool of light never darkens what it lies on, which an alpha halo did.”6622 In Air.swift the program’s descriptor sets blendMode = .add.22
The pools are measured from the code and the picture:922
- Lamp pools. 56 by 56 pixels, 3.5 tiles, under each of the town’s 7 lights (the lamp heads and the kiosk), tint (0.7, 0.52, 0.24).922
- Window pools. 40 by 28 pixels, 2.5 by 1.75 tiles, before a lit ground-story window or sconce, tint (0.5, 0.38, 0.17). Only a lit cell on a building’s base row or the row above it throws one, so 17 of the 24 lit cells do: 24 pools in all.4
- The picture.
glow.png, 64 by 64, a radial pool whose center is 0.961 and which is above zero on 0.713 of its pixels.967
What a pool does to the ground can be computed. The atlas’s mean color at night is (0.228, 0.264, 0.358), luma 0.264; adding a lamp’s full tint at the center lifts it to (0.928, 0.784, 0.598), luma 0.806, above the day’s 0.449.9 The script adds the tint in full; weighted by the picture’s own peak of 0.961, the center comes to 0.785.4 Either way, a lamp at night makes the ground under it brighter than noon, which is what a lamp does to the ground under it.
The walkers take the night
The walkers do not keep their color. PlazaRig.light(_:lampsOn:) sets the new light on the ground, the surroundings and the objects, and marks every walker to be “redrawn in the new light on the next frame”; each walker frame’s material is sheet.material(column:row:light:) with the same light, the same multiply as the ground.20 At night the walkers are drawn at the night tint, three channel multipliers (0.5, 0.58, 0.9) whose own luma is 0.593, which on the stored values keep 0.575 of the collector’s figure’s luma (its mask, held in idle_a),30 exactly where Crystal and Emerald leave their people alone.915
The one exception is the card a collector holds. The rig adds a DirectionalLight with the comment “For the card alone: nothing else in the world is lit.”20
This is the second correction. The world post’s checklist reads: “Dusk and night as one multiply tint over the world plus hand-drawn emissive variants for lamps, windows and the kiosk sign.”32 The code does that, and it is accurate about the code. This post’s brief changes it for the walkers, who should not take the full tint (section 10, item 2). Measured against the canon, one multiply over everything, walkers included, is the rule Crystal and Emerald both decline. That is a finding of this post; the world post is unedited.
A cut, once a minute
The light is a function of clock, a date the stage holds. A task re-reads it every 60 seconds, under the comment “The light follows the clock, a minute at a time.”, and onChange(of: clock) calls relight(), which computes the phase and season and assigns the new materials at once.199 There is no animation between lights, and no observer of significantTimeChangeNotification anywhere in World/. The hour comes from Calendar.current, the phone’s own zone, through Daylight.now, and the launch argument -plazaHour forces one, which the UI tests use to see the plaza at night.92168 The stage does run a TimelineView(.animation(minimumInterval: 0.1)), but that is the speech bubbles’ clock, in bubbles(of:in:), which keeps what was just said over the heads that said it for a few seconds; it is not the light’s, and not the walk’s either, which rig.update steps from the scene’s update event.199
So the change of phase is a cut, and it can come up to 60 seconds after the boundary. On the atlas’s mean luma the cuts are, from arith.py:411
- 06:00, night to morning: a step of 0.168, the largest of the day.4
- 09:00, morning to day: 0.017.4
- 17:00, day to evening: 0.077.11
- 20:00, evening to night: 0.108.11
Crystal cut too, and a player outdoors at 18:00 saw it on the frame it happened (section 1); how often anyone was looking, the source cannot say.1 Kiradex’s plaza, on my reading, is an always-open screen: someone may leave it open on a desk and look up at 20:00 to see the town’s mean luma drop from 0.372 to 0.264, more than a quarter of its brightness, in a frame. A cut on a screen being watched is an event, and the canon had no need to solve that.
Rooms and the hall keep the phone’s clock
RoomView, in PlazaStage.swift, builds a PlazaStage of its own, and HallView builds one too, so rooms and the hall are relit by the same code as the plaza, from the same clock, the phone’s, lamps and all.1969 Each is entered through the black veil, in their own comments “A room is entered through the veil” and “The show is entered through the veil”, and each is laid over the plaza rather than replacing it.1969 That is my reading of the code, not a capture. It is Stardew’s choice rather than Crystal’s: Stardew darkens its interiors at nightfall and lights their lamps, while Crystal fixes 207 of its 208 indoor maps at day.161 Both are canon. Kiradex has already picked one, and goes a step past it: Stardew leaves a room the player is in at nightfall light until they leave and come back, while a Kiradex room relights on the minute with the player in it, which is Kiradex’s own choice, not the canon’s.1619
The air
Air.swift is the build 29 layer of “Light and air over a place”, following the design plan’s section 4.2270 Measured from the code and the forge’s pictures:9
- Cloud shadow. A sheet wider than any view, following the camera, whose picture
cloud.png(256 by 256) repeats every 384 world pixels. Its texture offset advances (0.004, 0.0015) per second, which across 384 pixels is (1.536, 0.576) pixels a second, 1.64 pixels a second in all.229 That conversion assumes RealityKit adds the offset after the scale, so that an offset of 1 is one whole repeat of the picture. The Apple pages saved for this post say only that the property is “A two-dimensional transformation to apply to the entity’s primary texture coordinates”, not in which order.71 The app’s own sprite sheets rely on the same order:SpriteSheet.swiftpicks a frame with an offset of the column over the columns and a scale of one over the columns, which lands on the frame only if the offset comes after the scale, and its comment records a measurement of the offset’s convention on the simulator.72 So the 1.64, and the cloud’s bar in the chart in section 2, rest on that order. The offset also adds the camera’s position over 384 each frame, so the sheet’s movement on screen is the drift plus whatever that term does as the view moves; I have not captured it.229 The cloud is shown in every phase but night. - Cloud shade.
cloud.pngis “a tileable mask of cloud shadow: alpha where a cloud passes”, covering 0.499 of its area, with alpha up to 110, drawn at material opacity 0.55. The darkest shade multiplies the ground by 0.763, and the picture’s mean multiplies it by 0.950.679 - Mist. Three banks over the park’s pond at cells (5, 24), (9, 26) and (4, 27), each 128 by 64 pixels with alpha up to 93 at opacity 0.7, in the morning only.922
- Leaves and fireflies. 14 leaves in autumn, in every phase but night; 10 fireflies at night, kept to the park’s rectangle.922
- Smoke. Three puffs over each of the 6 chimneys the forge recorded; a UI test waits for the air’s report to say
smoke 18.2268
Against the canon, the cloud is slow and its shade is gentle. At 1.64 pixels a second it is 0.027 of Kiradex’s walk and crosses one tile in 9.8 seconds, where Emerald’s clouds move at half the walk; on a phone, on my reading, that is slow enough to read as still.114 The walk it is set against is 60 pixels a second: Motion.swift’s tick clock runs 60 ticks a second, Stride.ticks gives 16 ticks to a straight walking step of 16 pixels, and PlazaRig.update(_:) runs the ticks through tick(), which steps the player.732011 The cloud’s darkest shade, 0.763, is darker than Emerald’s rain map, about 0.81 of every channel, but it covers only half of the picture.95
Shadows
Three shadows are drawn by rule (a lamp post on paving also carries its own), and one is missing.9874
- The walker’s.
shadow.png, the 12-by-4 one-tone blob of section 4, drawn at 12 by 4 under every walker.98 - The wall’s. The forge darkens the top 4 pixel rows of whatever stands at the foot of a wall cell, one step darker on its own ramp; the comment names the reference, “the way the light falls at Leene.”74
- The eaves’. Three pixel rows of shadow on a building’s wall under its roof, drawn by
buildings.pywhen it finishes a building.24 - The ground’s, missing. No shadow falls from any building onto the ground. The docstring that defers it is quoted at the top of this post, and the forge’s
paintnever draws it.249
The wall’s shadow already follows Schlitter’s rule (one face, under a tile long, the same length whatever stands above it), and the walker’s follows Emerald’s (one tone, a few rows).638 The buildings are the gap.
Motion and the system setting
accessibilityReduceMotion is read in exactly one file in World/, FigurePreview.swift. Air.swift does not read it, so the cloud drift, the mist, the leaves, the fireflies and the smoke all move whatever the setting.975 The stage does set renderingEffects.depthOfField = .disabled, although the design plan’s build 29 had called for “a vignette and tilt-shift depth of field as the camera’s rendering effects”; the vignette was built, and the depth of field is off.1970
What each post in the series says, checked
Three statements in this series’ earlier posts do not match what this post measured, and each is stated above with its measurement: the structures post’s 21 lit cells against the export’s 24; the world post’s 64-byte reload against LoadMapPals’s 132 bytes outdoors; and the world post’s one multiply tint over the world, which this brief changes for the walkers.2332934 What matched: the homage post’s hours for Crystal and for Kiradex; the alive post’s account of the clock today, the phone’s calendar and a server that names no zone, beside its contract for a welcome and a quiet plaza’s response that carry the room’s zone and clock, which section 10 builds on and the client did not read on October 5; the structures post’s account of NightTiles and of roofs.pal; and the elevation post’s shadow kept on the ground under a hop.1043762352 The characters, motion, rooms and routes posts say nothing this post contradicts, and the motion post’s walk of 16 ticks a tile at 60 ticks a second is what the app now runs.127773
8. The craft, as rules with numbers
Each rule below is a measured fact from sections 1 to 7 with the design reading I take from it. The numbers are the canon’s and the app’s; every Kiradex number is read from source or computed from its exported pictures on a Mac, not measured on an iPhone; the readings are mine.
| Element | Rule | Source |
|---|---|---|
| Parts of the day | A day is three or four parts, and night is the longest. Crystal: 6 + 8 + 10 hours. Kiradex: 3 + 8 + 3 + 10. | Sections 1 and 719 |
| Night | Night is about half the light, and bluer. Crystal’s world palettes keep 0.41 to 0.68 of their day luma in the palette file’s blocks, a mean of 0.51 (0.49 to 0.54 by map group as a town displays them, with its own water and roof colors), with blue bias rising by about 6 to 7 in 5-bit units; Kiradex’s night keeps 0.59 of the atlas’s luma (read on a Mac). | Sections 1 and 719 |
| Morning | Morning is a warmer white, not a different scene. 5 of Crystal’s 32 world colors change for morning, all of them the lightest one. | Section 11 |
| Lit things | Lit things refuse the tint. Crystal’s YELLOW stays at luma 0.90 at night; Kiradex’s lit cells are untinted at 0.901 against a night town whose brightest pixel is 0.562 (read on a Mac). | Sections 1 and 719 |
| People | The place darkens; the people do not. Crystal’s people palettes and Emerald’s player and townspeople palettes are untouched by night and by rain. | Sections 1 and 215 |
| Changes | The hour cuts; weather fades. Crystal: one frame on the hour, and its 8-frame fades to white are for doors, warps, Fly, the screens its menus open and some scripted scenes. Emerald’s rain: 3 steps of 20 frames, 1.00 second, with the drops in place about a second later. A phone left open on a town is watched through the cut, so Kiradex ramps (section 10). | Sections 1, 2 and 715 |
| Rain’s light | Rain takes the place to 13/16. Emerald’s map 3, about 0.81 of every channel. | Section 25 |
| Rain’s drops | Rain is sparse and fast. 10 drops on a 240-by-160 screen, 868 pixels a second at 26.6 degrees from vertical, 18 frames a drop and a 7-frame splash; a downpour, 24 drops at 1,336 pixels a second. | Section 25 |
| Air | Clouds and fog move slower than you walk. Emerald’s clouds at 0.50 of the walk, its fog at 0.25; only the sandstorm, at 3.6, is faster. | Section 2511 |
| Weather’s schedule | Weather is scheduled by place and day, and known ahead. No outdoor header is rain in Emerald or FireRed; Routes 119 and 123 keep four-day cycles; Stardew rolls once a day and forecasts the next. | Sections 3 and 5517 |
| Lightning | Lightning is short and rare. 6 to 8 frames a bolt, at most two short bolts at least 61 dark frames apart, cycles 6 to 12 seconds apart; no second holds two bolts, under W3C’s three flashes a second. | Section 25446 |
| Interiors | Interiors keep the day or follow it; pick one. Crystal fixes 207 of 208 indoor maps at day; Stardew darkens interiors at nightfall and lights their lamps, though a room the player is in at nightfall stays light until they leave and come back; Kiradex already follows Stardew, and relights a room with the player in it. | Sections 1, 5 and 711619 |
| Darkness | Darkness is a radius in cells. Emerald: 1.5 cells without Flash, 4.5 with it. | Section 45 |
| Cast shadows | A cast shadow is one tone, under a tile long, on one or two faces, and the same length whatever the height. Emerald’s shadow sprites, Schlitter’s rules, Kiradex’s wall. | Sections 4, 6 and 786374 |
| Additive light | Additive light never darkens; a shadow needs alpha or the art. A RealityKit program’s blend modes are add and alpha; the unlit material blends by transparency. |
Sections 7 and 97866 |
9. The Apple way: blend modes, a clock that jumps, and Reduce Motion
The engine under Kiradex World is the one the first post in this series set out: a RealityKit scene used as a 2D renderer, an orthographic camera at a whole number of device pixels per texel, every sprite material unlit.32 Light, weather and time need no new framework. They need additive and alpha blending, both of which the app already uses (the first as the pools’ BlendMode.add, the second as UnlitMaterial’s transparent blending), two notifications it does not yet observe, and one environment value it reads in only one file. The availability below is what each documentation page listed on October 5, 2026, read by parse_apple.py from the saved pages.26
Two blend modes, and what each can draw
MaterialParameterTypes.BlendMode is documented as “Modes that describe how materials should be blended with content behind them”, and its page lists exactly two enumeration cases, add and alpha.784 Of add, Apple writes: “Blend by adding the material’s color to the background color, ignoring alpha”, “also known as additive blending or linear dodge.”79 Both are listed for iOS 18.0 and later, iPadOS 18.0, Mac Catalyst 18.0, macOS 15.0, tvOS 26.0 and visionOS 2.0.7879
The blend mode belongs to a material’s program, and the program is a CustomMaterial, “A material that works with custom Metal shader functions”, available from iOS 15.0 and macOS 12.0.80 Everything else in the town is an UnlitMaterial, “A material that doesn’t respond to lights in the scene”, from iOS 13.0 and macOS 10.15.71 Kiradex uses both exactly so: the pools are a CustomMaterial whose program’s descriptor sets blendMode = .add (with a comment worth keeping: “The program says how to blend; the material says that it blends at all.”), and the cloud sheet and the mist are UnlitMaterial with .transparent blending at opacities of 0.55 and 0.7.229
The consequence for shadows follows from the list. Additive light can only brighten: “black adds nothing”, in Glow.metal’s words.66 With add and alpha the only cases a program’s blend mode lists, and the unlit material blending by transparency, a shadow in this engine can be drawn in one of two ways. It can be alpha-blended dark over what is behind it, which is what the cloud sheet already does, or it can be baked into the art, which is what the wall’s shadow does.782274 There is no multiply blend to darken by a shadow’s color. A moving shadow is therefore an alpha sheet, and a fixed one belongs in the atlas.
What either costs on a phone is not measured. The structures post noted that the cost of its two new meshes on the GPU had not been profiled, and the 24 additive pools, the cloud sheet and the mist added since have not been profiled either.2312 The brief adds rain sprites, a splash, snow and a second alpha strength for the clouds; their cost is unmeasured too: item 4 says so of the rain, and items 5 and 6, snow, overcast and the clouds’ new speed, are not profiled either.
The clock, and when it jumps
The stage re-reads the clock every 60 seconds with a sleeping task.19 SwiftUI has a documented alternative for a view that changes with the time: TimelineView, “A view that updates according to a schedule that you provide”, with the schedule everyMinute, documented as “A schedule for updating a timeline view at the start of every minute”; both are available from iOS 15.0.8182 The stage’s task, on my reading of it, learns that the clock has jumped (past midnight, into daylight saving time, into another zone) only when it next wakes, up to a minute later. The system says when the time jumps.
UIApplication.significantTimeChangeNotification is posted, in Apple’s words, “when, for example, there’s a change to a new day (midnight), a carrier time update, or a change to, or from, daylight savings time.” Its page lists iOS, iPadOS, Mac Catalyst, tvOS and visionOS without a version.8326 NSSystemTimeZoneDidChange is “A notification posted when the time zone changes”, available from iOS 2.0.84 EnvironmentValues.timeZone, “The current time zone that views should use when handling dates”, from iOS 13.0, is SwiftUI’s way to carry a zone through a view tree.85
The alive post in this series sets a contract for a clock per room: every room runs on the server’s time in UTC, read in the room’s own zone, and the server sends both, as zone and clock, in welcome and in the quiet plaza’s response, so that everyone in a room sees the same day-part whatever their phones’ clocks say.43 The light must follow that clock, not the phone’s, while the plaza is open, and the notifications above cover the phone’s calendar, not the room’s: Apple’s page names a new day on the device among the times the first is posted, and says nothing of another zone’s midnight. Item 1 of the brief says how the two fit together. On October 5 neither the room clock nor any observer of a time jump was in the app; section 10 opens with what has changed since.983
Reduce Motion
accessibilityReduceMotion is the environment value for “Whether the system preference for Reduce Motion is enabled”, available from iOS 13.0. The page’s discussion is direct: “If this property’s value is true, UI should avoid large animations, especially those that simulate the third dimension.”86 Kiradex reads it in FigurePreview.swift and nowhere else in the world’s code.9 The air, which is all motion, does not read it today.
Real weather, and flashes
Apple ships a framework for the player’s real weather. WeatherKit is documented as “Deliver weather conditions and alerts to your users”, available from iOS 16.0.87 Section 10 refuses it, and gives the reason there.
Flashing has a published threshold. The W3C’s Understanding page for WCAG 2.2 success criterion 2.3.1, Three Flashes or Below Threshold, a Level A criterion, states that web pages “do not contain anything that flashes more than three times in any one second period, or the flash is below the general flash and red flash thresholds”, and gives the reason: “Flashing content can cause migraines, dizziness, nausea, and seizures.”46 Kiradex is an app, not a web page, and I am using the criterion as the closest published bar, not claiming it applies by law.
10. The brief: what Kiradex builds and the checks it must pass
Author synthesis. What follows is my proposal, derived from sections 1 to 9 and written as a diff against the app as read on October 5, 2026, from 16:51 to 18:19, at commits cf3beb1e to 66c116b0, while its tree was being edited, each note naming the reading it rests on, among them: Daylight.swift, Motion.swift, Air.swift, Glow.metal, PlazaStage.swift, PlazaRig.swift, PlazaClient.swift, HallView.swift, CollectorComposer.swift, the forge’s air.py, town.py, buildings.py and interiors.py, town.json of October 3, and the figure sheets with their characters.json.1297388 It names and draws nothing from any Pokémon game: no palette, sprite, weather name or sound of theirs. Where it borrows a mechanic, the rule in section 8 says from where, and the look is the forge’s. The homage post’s decision stands: Kiradex keeps its own four phases and its own hours.10 None of it has been built, and no number in it has been measured on an iPhone: every Kiradex number is read from source or computed from the exported pictures on a Mac, the costs on the device are unmeasured throughout, and the checks are written so that a build can measure them. Every type, method, file and launch argument a check names either exists today, cited by its symbol here and by its line in the notes, or is listed in an item as a proposed addition. Checks 2 to 8, 10 and 11 are my revisions of the dossier’s: as first written, some asked a UI test to do what only the app’s own process can do, changed a launch argument while the app ran, read a value from somewhere that does not carry it, or relied on a list the app does not have. A UI test runs in a process of its own and sees the app through its accessibility values, its labels and its screenshots; -plazaHour is read from the launch arguments, so it is fixed for the launch; and the one value the stage gives a test, its accessibility value, carries the player’s last step or, under -airProbe, the air’s report, which the UI tests read “since nothing else the simulator logs reaches them”, in the stage’s own comment.192168 Each revised check says what it needs.
The app has moved since October 5. Read again on October 15, 2026 at 23:53, from the commit the repository’s main names, fc9d4876, committed that day at 17:45, each file taken from the repository’s own objects without running git, and the working tree’s copy of each the same bytes: the server now sends zone and clock in every welcome, one zone for the whole world, UTC unless configured, and PlazaClient keeps them, the server’s milliseconds paired with the phone’s uptime as item 1 proposes, giving the room’s time as roomDate.89 The light does not read it yet: PlazaStage still keeps a clock re-read from the phone each minute and takes the day from it, and a quiet plaza still joins no room; and a search of the working tree’s Swift finds no observer of a time jump. A deny list now exists, scripts/forge/protected_names.txt, which server/tests/test_names.py reads against the world’s area names, preset lines and townspeople’s handles.89 Everything below is written against the October 5 reading, and its “today” is that day’s; item 1 and check 2 would now start from the client’s room clock, and item 10 and check 11 from that list.
1. The day clock: keep the hours, ramp the light, read the room’s clock
- Hours unchanged. Morning from 6 to 9, day from 9 to 17, evening from 17 to 20, night otherwise (section 8, Parts of the day).1021
- A ramp, computed from the clock. A new
Daylight.light(at:calendar:), taking a date and a calendar that defaults toCalendar.currentasDaylight.now(_:calendar:)already does, returns the tint interpolated linearly over the first 30 minutes after each boundary, from the previous phase’s tint to the new one, times the season’s cast. The stage already relights every minute, so the ramp is 30 steps. On the atlas’s mean luma, the evening’s change becomes 30 steps of 0.0026 instead of one of 0.077, and the night’s 30 steps of 0.0036 instead of one of 0.108.11 The morning’s change, the largest cut today at 0.168, becomes 30 steps of at most 0.0056, and the day’s, 0.017, steps of at most 0.0006.4 - The ramp needs no animation and no new timer. The day’s light is a pure function of the time, so two phones reading the same clock show the same light, and a phone that wakes at 17:12 shows the 17:12 light without having to have watched the ramp. Crystal’s cut survives only for a forced
-plazaHour, which the tests use to look at a phase directly; under it,relight()keeps calling today’sDaylight.light(phase:season:)(section 8, Changes).2168 The weather’s one-second fade (item 4) is the one change that animates, and it rides the rig’s existing per-frame update, not a timer of its own. - The room’s clock.
LightClock, a small type proposed in the next item, is the one clock the light reads, and in the plaza it runs on the room’s time as the alive post’s contract sets it: the server’s time in UTC, read in the room’s own zone, both sent aszoneandclockinwelcomeand in the quiet plaza’s response, the one request that post gives a player whose multiplayer is restricted, who opens no WebSocket. Whichever of the two arrives hands them to theLightClock,PlazaClientonwelcomeand the quiet plaza’s request on its answer, and the clock keeps the server’s stamp paired with the phone’s monotonic uptime at the moment it arrived, so that itsnowis the server’s time plus the uptime since, in the room’s calendar; a phone whose wall clock is hours wrong then shows the same light as every other phone in the room, andDaylight.light(at:calendar:)andWeather.today(zone:date:)(item 3) take the clock’s calendar and zone. The phone’s own wall clock and zone are read only before the plaza’s firstwelcomeor quiet response lands and outside the plaza, in a room or the hall, where the alive post keeps the collector on their own clock (item 8 says what that means at their doors); aLightClockkeeps the last response’s zone and stamp until the next one arrives, and its state names which it runs on: the room’s clock bywelcome, the room’s clock by the quiet response, or the phone’s. On October 5 the server’swelcomecarried no zone or clock andPlazaClient’s handler read none (the opening of this section says what has changed since); the quiet plaza’s response is that post’s proposal. Until a response carries them, theLightClockruns on the phone’s clock and says so in its state.432176 - Change at the boundaries, not a minute late.
LightClock, in a newKiradex/World/LightClock.swift, is given a time source (the monotonic uptime and the wall clock, both replaceable), aNotificationCenterand the stage’s relight. From its calendar it computes the next moment the light must change, the next phase boundary and the end of that boundary’s 30-minute ramp from item 1, and the next midnight in its zone from item 3, and schedules a wake for each explicitly, beside the once-a-minute wake the ramp needs; a wake calls the relight with the time it reads. It also observesUIApplication.significantTimeChangeNotificationandNSSystemTimeZoneDidChangeon its center and relights on either, for what Apple’s page names: a new day on the phone’s own clock, a carrier time update, a change to or from daylight saving time, and a change of the phone’s zone. Those cover the phone’s calendar, and the page does not say the first is posted at another zone’s midnight, so the room’s boundaries are never left to it.PlazaStageowns one, built with the system’s time source andNotificationCenter.default, in place of itsclockstate and the sleeping task that re-reads it each minute, so that midnight in the room’s zone, a phase boundary, a daylight saving change and a change of zone do not wait up to 60 seconds. The clock, the center and the time source are parameters so that a test can drive them; that design is mine.838419 - The forced hour stays a launch argument.
Daylight.forcedHourreads-plazaHourfrom the process’s launch arguments, so it holds for the whole run and cannot change while the app runs; no check below asks it to.21 - Cost. One material assignment per minute on the ground, the surroundings, the objects and each walker, as today, which is not profiled; the ramp adds no work beyond computing a blend of two colors.20
Proposal against today: the atlas’s mean luma under each minute’s light, computed by figures/make_figures.py from the shipped town.png and Daylight.swift’s tints. Read on a Mac, not captured on a phone.
Acceptance check 1, the ramp. A unit test of Daylight.light(at:calendar:), given a calendar in a fixed zone, at 16:59, 17:00, 17:15, 17:29, 17:30 and every minute from 20:00 to 20:30: luma monotone across the sampled times, no step above 0.004 on the atlas mean between any two samples one minute apart (16:59 and 17:00, 17:29 and 17:30, and each minute from 20:00 to 20:30; the script’s 0.0036; the test takes the mean of town.png’s opaque pixels from the app bundle), and equal to today’s tints at :30 and after.1211 The same test at 06:00 to 06:30 uses a bound of 0.006, because the morning’s ramp steps up to 0.0056 a minute.4
Acceptance check 2, the room’s clock and the time’s jumps. In process, in a new KiradexTests/LightClockTests.swift, because a UI test cannot post a notification inside the app, cannot set the phone’s clock, and -plazaHour cannot change while the app runs. Each test builds a LightClock with a NotificationCenter of its own, a time source it controls (an uptime and a wall clock it advances by hand) and a relight it records, with no real timer: the test advances the uptime itself and asks the clock to fire whatever it has scheduled up to that moment. (a) A skewed phone: the clock is given a welcome whose clock is 20:35:00 in the room’s zone, stamped with the source’s uptime, while the wall clock is set three hours wrong; the relight is called with 20:35 in the room’s calendar, which Daylight.now(_:calendar:) reads as night and for which Daylight.light(at:calendar:) gives the night’s light, item 1’s ramp having ended at 20:30 (a time inside the ramp, 20:05 say, would give a light one sixth of the way from the evening’s to the night’s, which a correct build could not match to the night’s), and moving the wall clock again changes nothing. (b) Two zones: the phone’s calendar in a zone 10 hours ahead of the room’s, both fixed offsets so that no daylight saving change moves them. At a server instant that is 20:35 in the room’s zone it is 06:35 of the next day on the phone, which Daylight.Phase(hour:) reads as morning, past item 1’s ramp, while the room’s 20:35 is night, past the evening’s. The relight gets the room’s light, the night tint times the season’s cast (luma 0.593 in summer), and not the phone’s, the morning tint times the cast (0.961 in summer); and Weather.today(zone:date:) is asked with the room’s zone and the room’s date, the day before the phone’s. A build that read the phone’s clock would fail on the light and on the date. (c) The boundaries, with no notification posted at all: from 19:59:30 by the room’s clock the test advances the uptime 60 seconds and asserts that one scheduled wake fired at 20:00:00 in the room’s calendar, not at the minute’s end; from 23:59:30 it advances 60 seconds and asserts a wake at 00:00:00 of the next day in the room’s zone, with Weather.today(zone:date:) for that time the new day’s roll. (d) The observers, as wiring only: with no room clock given, posting significantTimeChangeNotification on the test’s center relights with the wall clock’s time, and so does NSSystemTimeZoneDidChange; the test asserts the call, not that the system would post it, which no test can. (e) With no post, no wake due and no welcome, no relight is called. (f) The quiet plaza, in process: the clock is given the quiet plaza’s response instead of a welcome, with the same 20:35:00 and the wall clock three hours wrong, and the relight gets 20:35 in the room’s calendar and the night’s light, as in (a), with its state naming the quiet response. And in the app, a UI test launched with the multiplayer restriction stubbed on, as the alive post’s first safety check stubs it, -airProbe, and a local test server that answers the quiet plaza’s request with a fixed zone and a clock three hours off the simulator’s, reached through the app’s existing -plazaServer argument (PlazaClient.endpoint, whose comment gives -plazaServer ws://127.0.0.1:8300/v1/plaza for a local server): the quiet request opens no WebSocket, so it takes that URL in its HTTP form, ws to http and wss to https, with the quiet route’s own path, which mapping is my proposal: the stage’s value, which adds the LightClock’s state to the probe’s report, names the quiet response, and the test asserts, as that post’s check does, that no WebSocket is opened. That needs the server fixture and the probe line; both are my proposal. Files: LightClock.swift (new), PlazaStage.swift, which owns the LightClock, PlazaClient.swift, whose welcome handler would hand it zone and clock, which the server did not send on October 5, the quiet plaza’s request, which would do the same with its answer, and the new test file.7643 What the test cannot see is that PlazaStage passes NotificationCenter.default and the real time source; that is two lines to read in the source. As first written, the check had a UI test post the notification after changing -plazaHour, which no UI test can do. A clock built on the phone’s wall clock, with a hand-posted midnight notification standing in for the room’s, would not serve either: the alive post’s contract rules out the first and Apple’s page does not promise the second, which is why the check rests on the server’s stamp, the explicitly scheduled boundaries and the two-zone case.1221194383
2. People keep their color
- The lift. Walkers take a light lifted halfway to white,
(1 + tint) / 2per channel. At night that is (0.75, 0.79, 0.95), luma 0.796, against the place’s 0.593, 1.34 times as bright; in the evening 0.913 and in the morning 0.980.114 - Where it lives. A new value type,
PlaceLight, in a newKiradex/World/PlaceLight.swift, holds the day’s light (from item 1) and the weather’s factor (item 4) and gives two colors:place, the day’s light times the factor, andwalkers, the day’s light lifted halfway to white, which takes no weather factor.PlazaRigowns one in place of its singlelight. The place materials takeplace: the ground, the surroundings and the objects asbuild(tiles:collector:arrival:)first makes them, inlight(_:lampsOn:)and in the ripple’s frame step, and the door’s drawing inshowDoor(at:sheet:column:). The walkers takewalkers: an arrival’s material inadd(other:figure:tile:facing:)and each frame’s inanimate(_:).20 A pure value, so a unit test can drive it without RealityKit; the type is my proposal. - Why halfway. Section 8’s rule says untouched. Halfway is the proposal because Kiradex’s walkers stand in a blended scene, with additive pools and a vignette, not on a hardware palette, and fully untinted figures would, on my reading, look pasted onto the night. That is a judgment to make on the device, and the check below leaves room to make it.1219
- The held card. It already has its own light, a
DirectionalLightfor the card alone. No change.20 - The collector’s frame, figure and drawing on the probe. Under
-airProbe, the stage’s value also carries the collector’s frame in the view’s points, fromPlazaRig.viewPoint(of:in:displayScale:)applied to the corners of the player’s sprite, and the collector’s figure mask: the pixels of its 32-by-40 cell that are opaque in every drawing a standing collector shows, the idle breath, the blink and the idle emotesPlazaRig.animate(_:)picks from (tic, look, shrug, think and doze), taken from the figure’s composed index plane,CollectorComposer.indexPlane(for:), where a pixel is opaque when it is neither clear nor the eraser, and written as runs along each row. Both are added to the report the stage’s.taskwrites there today once the air is prepared. For the manifest’s default figure facing down the mask is 290 of the cell’s 1,280 pixels.20198830 The value also carries the frame the collector’s material shows, the walker’sshown(the facing’s row times the sheet’s 43 columns plus the drawing’s column, so 0 isidle_afacing down), rewritten whenever it changes, and the view frames of the lit cells wholly in view, by their cells intown.json. A debug argument,-plazaHoldStill(my naming), read from the launch arguments and fixed for the launch as-plazaHouris, holds a standing collector onidle_a: no breath, no blink and no idle emote, whichanimate(_:)otherwise starts at random 6 to 14 seconds apart, the player’s first at 7 seconds, its first blink at 2.5.20 Under the same argumentAir.set(phase:season:)leaves off the layers that move over the walkers, the cloud, the mist (morning only, at 2.41), the leaves, the fireflies and the chimneys’ smoke, all drawn at depths 2.4 to 2.45 above the walkers’ 1 to 1.5; the lamp pools stay, being the night’s light. The probe’s value also says whether any pool’s plane overlaps the collector’s frame, and under-airProbethe stage draws check 3’s pipeline swatch and reports its frame.2230 All of these are proposed additions, for check 3.
Kiradex’s tints from Daylight.swift and the proposed walker light; swatches drawn by figures/make_figures.py. These are the app’s own values, read from its source on a Mac; the lumas are computed there, not measured on an iPhone.
Acceptance check 3, people. Two captures on the iPhone 18 Pro Max simulator, each its own launch, one with -plazaHour 12 and one with -plazaHour 22, both with -plazaWeather clear (item 3), so that the day’s roll cannot change the light, -plazaHoldStill (item 2), so that both show the same drawing with nothing drawn over it, and -airProbe, saved by the UI tests’ save. The stage’s value carries the collector’s frame, figure mask and drawing and the lit cells’ frames (item 2’s proposed additions), so the test knows which pixels are the collector and which are lit windows. It reads the drawing just before and just after each capture and requires frame 0, idle_a facing down, all four times; if any of the four reads is not frame 0, the hold is broken and the check fails. It also requires the probe to report no pool over the collector’s frame in the night capture. The fixtures are frozen before any capture is judged: the four launch argument sets, the swatch’s picture, tint and place, the town export the captures are made against (town.json of October 3, by its hash, with its atlas), the crops (the collector’s mask from the probe, each of its pixels sampled at the center of its square in the frame; the ground as the view less the collector’s frame and the status line; the lit pixels below), the pipelines and the tolerances below are constants in the test, and a change to any of them is a change to the check, made in the open and never a reading taken from a capture.12206819
Luma is taken on the capture’s stored color values. How RealityKit turns the tint and the picture into those values, the Apple pages saved for this post do not say: the UnlitMaterial page lists an init(applyPostProcessToneMap:), “Creates an UnlitMaterial with the given tone mapping setting”, but not what the plain UnlitMaterial() that SpriteSheet.material(light:) builds does, nor how the tint and a texture made with the .color semantic are each converted.7172 So the band is set for each pipeline, all of it before any capture, from section E of the coverage computation:30
- The calibration. The lit windows and sconces are drawn untinted, so the night capture carries its own fixed reference: in each pipeline below, an untinted color comes out as stored. Of the 24 lit cells, 7 lie wholly in the arrival view, on the paper replay of check 8, with 294 lit pixels, all of them outside every pool’s plane and within 450 points of the view’s center, inside the half of the vignette’s 900-point radius where it is clear. Each is sampled at a point of its square that no scanline row covers, and the median, over those pixels, of the largest per-channel difference from the atlas’s stored value must be at most 0.02, 5 steps in 255. If it is not, a tone map or a color conversion is in the path and none of the pipelines below applies: the check fails there, before a band is read, and the band for what the windows show is computed from the atlas, as these were, and written into the test before a pair is judged again.201930
- The tint multiplied as stored, or tint and picture both in linear. The ground’s night over day computes to 0.588 as stored, and to 0.562 with the picture and the tint decoded from sRGB, multiplied and encoded back; per channel, red, green and blue, (0.500, 0.580, 0.900) and (0.469, 0.554, 0.892). The band is 0.46 to 0.69. The collector’s mask, held in
idle_a, computes to 0.788 and 0.764 under the lift and to 0.575 and 0.531 under the full night tint, and must reach 0.67, halfway between the lower of the two correct values and the higher of the two broken ones. - The tint’s numbers taken as linear. If the picture is decoded and the tint is not, the ground computes to 0.774, per channel (0.719, 0.772, 0.951), and the band is 0.67 to 0.87. The mask computes to 0.889 under the lift and to 0.756 under the full tint, and must reach 0.82, halfway between them.
-
Which one. A swatch names the pipeline, not the ground. Under
-airProbethe stage draws a fixed picture of two grays, stored 51 and 102 of 255, under a fixed tint of 0.25 on every channel, given toSpriteSheet.material(light:)directly and never throughPlaceLightor the day’s light, on a quad above the pools at a fixed place in the view, two 16-point patches side by side at x 24 to 56 and y 462 to 478 of the Pro Max’s 440 by 956 points, the left edge at mid-height, its farthest corner 197 points from the center and so inside the vignette’s clear half. The probe reports its frame, the ground’s crop leaves it out, and the place is frozen with the other fixtures. From the probe’s frames the test asserts that it overlaps neither the collector’s frame nor any of the seven reference windows; at the arrival they compute to 0 overlaps, the nearest window 267 points clear. It is sampled as the windows are, off the scanline rows, and its stored values are the same at both hours. Multiplied as stored they come to 13 and 26; with picture and tint in linear, 6 and 20; with the tint’s numbers taken as linear, 22 and 51; no two pipelines are closer than 6 steps on either patch. Each capture’s swatch must lie within 2 steps of one pipeline’s two values, the same pipeline in every capture, and that pipeline’s band and threshold apply. The swatch, not the ground, names the pipeline, so ground that happens to fit another pipeline’s band cannot pass under it; the test also records the named pipeline, with the SDK it was named on, and a later run that names another fails until the record is changed in the open. The swatch and its picture are my proposal.30 -
To pass. The calibration holds; the ground’s night over day lies in the named pipeline’s band; the swatch names one pipeline in every capture, the one the test recorded; the mask’s ratio reaches that pipeline’s threshold and is above the ground’s, which is item 2’s lift showing; the ground’s channel ratios pass the two assertions below; both controls fail; all four reads of the drawing are frame 0; and the night capture reports no pool over the collector. Anything else fails the check.
- The band. Each band is its pipeline’s computed ratios 0.1 either side, rounded, and that is a target set from the computation, not a measurement. The computed ratios are the atlas alone; a capture also holds the lamp pools, which can only brighten the night, and the 0.1 is the room left for them, chosen before the first capture. The scanlines and the vignette are drawn the same at both hours, so on my reading they scale both captures alike.1291922
- Why the same drawing. The thresholds hold for the same drawing in both captures, and only for it. Multiplied as stored, the lift’s smallest channel is 0.75, so no color keeps less than 0.75 of its day luma under it, and with the same drawing the mask’s mean cannot either. Across two drawings it can, because the mask fixes which pixels are sampled, not the colors they hold: its day luma runs from 0.2989 in
doze_bto 0.3161 inthink, a correct build captured inthinkby day and indoze_bat night computes to 0.746, and over the 121 ordered pairs of the 11 drawings a build that tinted the walkers reaches 0.610. Hence the hold. In linear, the sRGB curve’s straight foot lets the darkest colors keep less than 0.75, which is why each threshold is a computed mean, not that bound.3011 - What the air would do. Everything the air draws lies above every walker, the cloud at depth 2.4, the mist (morning only) at 2.41, the leaves, the fireflies and the smoke at 2.45, the pools at 2.5, against the walkers’ 1 to 1.5, and the cloud is shown in every phase but night. Its darkest shade multiplies what lies under it by 0.763, and over the mask by day it would lift a build that tinted the walkers to 0.754 as stored, 0.696 in linear and 0.991 with the tint’s numbers taken as linear, if the shade darkens the encoded values; if it darkens in linear light before encoding, 0.608 in linear and 0.865 with the tint’s numbers taken as linear. Which the renderer does, the saved pages do not say, and in every case but the one at 0.608 the broken build would pass its threshold.2230
- Why the hold. The hold keeps the air off the collector, which this check cannot do without. A probe flag with retakes until the collector stood clear would also serve, but the cloud drifts at 1.536 pixels a second across and 0.576 up or down (which way on either axis the code does not settle: the sprite sheets’ measured convention, an offset counted from the texture’s top left, is for their own hand-built quads, and the cloud is a generated plane), so a shade that crosses the collector’s frame stays on some of it for the 21 seconds (20.8) the drift takes to carry it 32 pixels across, the frame’s width, and longer the wider the shade, over a picture about half covered, while the hold settles it in every capture; I chose the hold. The pools cannot be held off, since they are the light under test, and they do not move: at the arrival the collector’s frame, 32 by 40 pixels, is 52 pixels clear of the nearest pool’s plane, so the probe’s pool flag is a guard on that computation, and the check fails rather than retakes when it is set.22930
- Why not the whole frame. The collector’s whole frame would not do as the crop: it is mostly ground. The default figure covers 408 of its 1,280 pixels in its first standing drawing (the body sheet alone 377, every figure sheet in the export at once 581), and with the rest at the atlas’s mean color, multiplied as stored, a correct build’s whole frame computes to 0.638, under the 0.67 the mask must reach and only 0.049 above the ground, where a build that tinted the walkers fully would give 0.584.30
- Two more assertions. They keep a pair from passing by being alike: the ground’s ratios of night to day, taken per channel, are ordered blue above green above red, as the night tint (0.5, 0.58, 0.9) orders them, and each lies within 0.15 of the named pipeline’s computed channel (of either set, in the first).
- The control. A third launch at
-plazaHour 22with a proposed debug argument,-plazaNoTint, fixed for the launch as-plazaHouris, under whichPlaceLight.placereturns the day’s light at every hour, must fail the band, its ground ratio landing near 1, which shows that the band rejects untinted output. A renderer that ignoredPlaceLightwould give the same picture at both hours; against this check its ground ratio of about 1 fails either band and its flat channel ratios fail the ordering. A second control, a fourth launch at-plazaHour 22with another proposed debug argument,-plazaLiftAgain(my naming), under whichSpriteSheet.material(light:)lifts every tint it is given halfway to white again, must fail too. That is a build whosePlaceLightgives the right values and whose materials do not: multiplied as stored, its ground computes to 0.794 of the day, its channels to (0.750, 0.790, 0.950) and the mask to 0.894, its white windows are unchanged, and its untinted control still reads 1. Its ground alone, 0.794 with a red ratio of 0.750, would sit inside the band for the tint’s numbers taken as linear, 0.67 to 0.87, and pass every assertion made on the ground, which is why the swatch decides. The swatch fails it: its tint reaches 0.625 and it reads 32 and 64, 13 steps from the nearest pipeline’s values.30 - The band is not moved. If the first real pair falls outside its pipeline’s band, the check fails and the cause is found in the renderer or in the computation; the band is not moved to meet it. A band taken from the first pair instead would pass a renderer with the tint disabled, which is why the fixtures and the bands are fixed in advance, and why the first pair only chooses between bands already written.
- The unit test. A unit test of
PlaceLightadds the exact part: at the night tint, in summer and clear weather,walkersis (0.75, 0.79, 0.95) andplacethe tint itself.11 - As first written. The check had one night capture and asked that the collector’s mean luma be at least 0.75 of its day capture while the ground’s was 0.55 to 0.65, with no crop, fixture, drawing, pipeline or control named; everything above is my revision.12
3. Weather: five states, one roll a day
- States.
clear,overcast,rain,heavy rain, andsnowin winter only. Morning mist, autumn leaves and night fireflies stay what they are, layered on top. Weather is outdoors only: whereAirfinds no sky (itsoutdoors), rooms and the hall take no drops and no weather factor; that choice is mine.1222 - One roll a day, the same for everyone in a zone.
Weather.today(zone:date:)takes the SHA-256 of the zone’s identifier and the date in that zone,"zone|yyyy-mm-dd", reads the digest’s first four bytes as one unsigned big-endian number, and takes it modulo 100. That roll is matched against the season’s table cumulatively: the states are taken in the order listed, each state’s weight is added to a running total, and the first state whose total exceeds the roll is the day’s. In spring, a roll of 0 to 54 is clear, 55 to 79 overcast and 80 to 99 rain.11 The table, which is mine: spring clear 55, overcast 25, rain 20; summer clear 65, overcast 20, rain 10, heavy rain 5; autumn clear 45, overcast 30, rain 20, heavy rain 5; winter clear 40, overcast 30, snow 30.12 Its shape follows Stardew’s seasonal rates (a wet spring, a dry summer whose rain turns heavy, no winter rain); the numbers are Kiradex’s (section 8, Weather’s schedule).18 - A year of it. Run for 2027 in one zone, the roll gives spring 92 days, clear 53 (58%), overcast 22 (24%), rain 17 (18%); summer 92, clear 61 (66%), rain 13 (14%), overcast 12 (13%), heavy rain 6 (7%); autumn 91, clear 40 (44%), overcast 30 (33%), rain 17 (19%), heavy rain 4 (4%); winter 90, clear 36 (40%), overcast 29 (32%), snow 25 (28%). Of the year’s 82 wet days, those of rain, heavy rain or snow, a wet spell here is a run of consecutive wet days, in any mix of the three, so a day of rain followed by a day of heavy rain is one spell of two days. There are 65 wet spells, 50 of one day, 13 of two and 2 of three, the longest 3 days. Two zones agree on 8 of 30 January days, because each zone rolls on its own, and the same zone and date always give the same state.114 Spring’s rain, 18%, lands on Stardew’s 18.3% base chance.184
- When a change reaches the stage. The roll’s date is read in the room’s zone, so the day’s state changes at that zone’s midnight. The stage reads it at every relight, and item 1’s
LightClockschedules a wake at the room’s midnight from its own calendar, so the new day’s state arrives on time rather than up to a minute later, and without relying on a system notification that Apple’s page promises only for the phone’s own day. A stage that appears reads the day’s state with its first relight and shows it without a fade. So the fade of item 4 is seen only by someone who has the plaza open at the room’s midnight, which falls in Kiradex’s night.832143 - Known ahead. The quest board carries tomorrow’s weather in a line of Kiradex’s own words, as Stardew’s TV does.17
- Files. A new
Kiradex/World/Weather.swiftfor the states, each state’s light factor and status words, the roll, the board’s line (Weather.boardLine(zone:date:), naming the next day’s state in the same zone), the clouds’ velocity (item 6) and the forced state;Air.swiftfor rain, splash, snow and overcast (item 4); the forge’sair.pyforrain.png,splash.pngandsnow.png, in cells of 8 by 8 to 8 by 16;PlazaStage.swift, whoserelight()reads the state and passes its factor to the rig and the state toAir, and whosePlazaView.wordsadds the state’s words to the status line; andQuestBoardView.swift, which the plaza view (PlazaView) and the Me tab present as a sheet, for the board’s line.129019Daylight.swifttakes no weather: its light stays a function of the time alone, and the weather’s factor is applied inPlaceLight. The dossier put the weather factor inDaylightand the board line inPlazaStage.swift; the fade cannot live in a function of the time, and the board is its own view, so both corrections are mine. - A forced weather, for tests.
-plazaWeatherwith a state name forces the day’s state for one launch, read from the launch arguments and fixed for the launch as-plazaHouris; the checks below use it. This is my addition.
Proposal: a year of the daily roll, from kiradex_proposals.txt section 6, computed on a Mac.
Acceptance check 4, the roll. A unit test computes Weather.today for the 365 days of 2027 in the zone America/Los_Angeles and matches what measure_kiradex_proposals.py section 6 prints: each season’s count of each state, 82 wet days in 65 wet spells as defined above, the longest 3 days, and 8 of 30 January days on which Europe/London agrees.1211 It also asserts that the same zone and date give the same state whatever zone the test sets as the device’s, because the date is read in the zone named. The board’s line is mine: the same test asserts that Weather.boardLine(zone:date:) names the state Weather.today returns for the next day in the same zone, and that the five states’ status words are distinct and none is empty. As first written, the check compared the year day for day with the script’s output, which prints counts, not days, and had a server test check a roll the server does not make; and the dossier’s wet runs (its section 4.3) counted runs of one state, so rain then heavy rain was two, a count of 67 that the script no longer prints.
4. Rain
- Drops. 15 drops in the iPhone 18 Pro Max view, which shows 165 by 358 world pixels (the characters post’s figure, not re-measured here): Emerald’s density of 2.60 drops per 10,000 square pixels carried over. Heavy rain, 37.1191
- Fall. A drop is a forge-drawn streak of Kiradex’s own shape, falling at about 868 pixels a second, 26.6 degrees from vertical, for 0.301 seconds (Emerald’s 18 frames), then a splash on the ground of 0.117 seconds (its 7).11 Positions on whole pixels (section 8, Rain’s drops). At 120 frames a second that is 7.2 pixels a frame, and the streak’s length covers the gap.114
- Where the drops live.
Airgains aRainField, a value that holds the drops’ positions for the view’s size and the day’s state and steps them by the frame’s time, rounding to whole pixels;Air.update(_:camera:view:)steps it and places one entity per drop, andAir.set(phase:season:weather:reduceMotion:)replaces today’sset(phase:season:)to turn the drops, the flakes and the overcast sheet on and off. The drops areAir’s, and nothing inAirtakes the day’s tint, in its header’s words, so a drop at night is drawn as the forge drew it; whether that sits right in the night is a judgment for the device.Airalso keeps the drop count in its report, and under-airProbethe stage copies the report into its value once a second instead of once, in the.taskthat prepares the air, until the player’s first step; from then onrig.onStepwrites the step trace there, as it does today, since the walks read it there. The checks that read the report, 5 and 10, take no step. All of this is proposed.2219 - The light. The place’s light times 13/16 (section 8, Rain’s light): the atlas’s day luma goes from 0.449 to 0.365, and a rainy night to 0.214.11 The walkers keep item 2’s lift and take no weather factor. The lit cells and the pools are unchanged.
- The one-second fade, and who runs it. Into and out of rain, the place’s light fades over 1 second (section 8, Changes).12
PlaceLight(item 2) holds the weather factor, the factor it is fading to and the time since the change; told of a new factor with a fade, it moves linearly from the factor it had to the new one over 1.0 second of the time it is stepped by.PlazaRig.update(_:), which already runs the world’s ticks (the walkers, the camera and the card) and then, each frame, turns the showcase, steps the ripple and updates the air, steps it, and on a frame whereplacehas changed it assigns the three place materials, aslight(_:lampsOn:)does once a relight, and the door’s while one is shown. The stage’srelight()passes each state’s factor (clear 1, overcast 0.9, rain 13/16, heavy rain 0.75, snow 1) with a fade, except at the stage’s first relight, which sets it at once. Nothing else animates the light, and no timer is added.2019 - Assumption. The carried numbers assume 60 hertz; a frame rate other than the handhelds’ changes the step per frame, not the speed.12
- Cost. 15 to 37 sprite entities and their splashes, not profiled.11 During a fade, the rig assigns the three place materials on every frame for one second, and the door’s while one is shown, 180 assignments at 60 hertz and 360 at 120 (240 and 480 with a door), where a relight makes 3 a minute (4 while a door is shown) and the ripple 12 a second where there is water; that is counted from the code, and its cost on the device is unmeasured.420
Acceptance check 5, rain. Two parts. In the app: a UI test launched with -plazaHour 12, -plazaWeather rain and -airProbe on the Pro Max simulator waits, as testTheChimneysSmoke waits for smoke 18, for the air’s report on the stage’s value to read a drop count of 15 ± 1; that needs item 4’s proposed report and its once-a-second copy, and the test takes no step, so the copy keeps the value.6819 In process, a unit test steps a RainField for the Pro Max view through 10 simulated seconds of rain at 120 frames a second and asserts every drop position is whole and the count is 15; steps a PlaceLight at the day’s light, told of rain with a fade, and asserts that place is still above 13/16 of the day’s light one frame before the 1.0-second mark and is at 13/16 within one frame after it; and asserts that walkers is the same with and without the weather factor. The wiring, PlazaRig.update stepping its PlaceLight and assigning the materials when place changes, is a few lines to read in the source. As first written, the check read all four from a rig log, which reaches the Mac’s console and not a test.12
5. Overcast, snow, and heavy rain without lightning
- Overcast. The cloud sheet’s opacity rises from 0.55 to 0.8 and the light takes a 0.9 gray.12 With the shipped
cloud.png, the darkest shade goes from 0.763 to 0.655, the picture’s mean from 0.950 to 0.927, and the atlas’s day luma under the gray to 0.404.4 The gray fades throughPlaceLightas rain’s does; the sheet’s opacity is set byAir.set(phase:season:weather:reduceMotion:)at once, because the state changes only at midnight, when the sheet is hidden (Air.sethides the cloud at night).22 - Snow. Winter only, drawn by the forge: slow flakes with the leaves’ sway, kept by
Airas its leaves are, and the ground keeps its winter cast; snow takes no weather factor, which is my choice.1222 - Heavy rain. The light darkens further, to 0.75: the atlas’s day luma 0.337, a heavy-rain night 0.198.124 The
RainFieldholds 37 drops.11 - No lightning flash. Heavy rain makes no flash. Emerald’s storm, measured in section 2, holds at most one bolt in any second, under the W3C’s limit of three flashes a second, so the canon would pass the criterion.446 The refusal is not about passing. It is that flashing content “can cause migraines, dizziness, nausea, and seizures”, in the W3C’s words, and my premise is that children may use the town for long sessions; a world used that way does not need it.4612
Acceptance check 6, no flash. In process: a unit test steps a PlaceLight at the day’s light through 10 simulated seconds at 120 frames a second, told of heavy rain with a fade at second 2 and of clear at second 6, and asserts that place, as luma on the atlas mean, never changes by more than 0.02 from one frame to the next, and that it reaches 0.75 of the day’s light and returns. The fade into heavy rain takes the day’s 0.449 to 0.337 over a second, 0.0019 a frame at 60 hertz and 0.0009 at 120, so the bound holds with room; a cut would be a step of 0.112.4 PlaceLight takes no Reduce Motion input, so the fade is the same with the setting on or off by construction (item 9); what the setting changes, check 10 asserts. A screen recording of the simulator, made from the Mac, can cross-check it, but a recording need not hold every frame the app draws, and the drops’ own motion moves a frame’s mean too, so the unit test is the check. As first written, the check asked for a capture of every frame, which a UI test cannot take.12
6. Cloud shadows that move
- Speed. The cloud drifts at a fixed 16 pixels a second instead of 1.64, one tile a second: 0.27 of today’s walk, 60 pixels a second, which the app’s tick clock gives as the motion post’s brief set it (one pixel a tick, 16 ticks a tile, 60 ticks a second). The fixed number is the proposal, and the chart in section 2 sets it against that walk; it is near Emerald’s fog ratio, 0.25, and slower than its clouds, 0.50 (section 8, Air). At that speed a cloud travels a distance equal to the Pro Max view’s height, 358 pixels, in 22.4 seconds; how long it takes to cross the view depends on the day’s direction, below: on January 1, 2027, heading 215.16 degrees from east, its vertical component is 9.21 pixels a second and its horizontal 13.08, so it crosses the view’s height in 38.9 seconds and its width in 12.6.1147773 That is 9.75 times today’s drift.4
- Shade. Keep the sheet’s darkest shade, 0.763, and its coverage of about half the picture.9
- Direction. Fixed for the day by the digest’s fifth byte, the first one the roll does not read, as that byte times 360/256 degrees counterclockwise from east, so everyone in a zone sees the clouds go the same way. Over the 30 January days of 2027 the script finds 30 different directions.11 The dossier said the roll’s “second byte”, which is one of the four the roll reads; the fifth is my correction.12
- Files.
Weather.cloudVelocity(zone:date:)inWeather.swiftgives the day’s velocity in pixels a second;Air.updateadds it, over 384, to the cloud’s offset in place of today’s fixedSIMD2(0.004, 0.0015)a second, and keeps the drift in pixels a second in its report. Both are proposed.22
Acceptance check 7, clouds. In process: a unit test asserts that Weather.cloudVelocity(zone:date:) is 16 pixels a second in magnitude, the same for the same zone and date, and different on at least one of the 29 days after a given date (the script finds all 29 of January 2 to 30, 2027 differing from January 1). On screen: two captures 5 seconds apart, with -plazaHour 12, -plazaWeather clear and the collector standing still, overlaid with the shift that best aligns the cloud’s shade, show 80 ± 3 world pixels of drift, which also measures whether RealityKit applies the offset after the scale, as section 7 assumes, and whether Air.update adds the velocity as item 6 says. As first written, the check read a rig log and asked two simulators to share a date, which a launch cannot set.122211
7. Building shadows on the ground
- The pass. A forge pass in
town.py’spaint, after the wall’s shadow. For each building, row by row from its top row to its base row, the cell immediately east of that row’s easternmost building cell takes the same treatment as the foot of the wall: a step darker on its own ramp, over the 4 pixel columns nearest the building. The cell is shaded only if it is ground, lawn, path or paving in the town’s letters; if it holds a prop, a tree, the kiosk, another building, a fence or water, that row takes no shadow, and the pass never steps past it to a cell further east. So every strip touches its own wall. The light comes from the top left; the shadow lies on one face, is under a tile long, and is the same width for a one-story and a two-story building (section 8, Cast shadows).126311 The rule for rows and props is mine; the dossier defined the pass by each building’s column, which its script took from the building’s bounding box, so a narrower roof or base row got a strip three cells from its wall. - Where it falls. The seven buildings have 39 rows between them. In 33 the cell to the east is ground, 28 of lawn and 5 of path, and takes the shadow; in 6 it holds a prop (5 that block the way and 1 bed of flowers, beside the base row of
house-mine) and keeps its own art.11 At 4 columns of 16 rows a cell, that is 2,112 shaded pixels.4 - Not below the base. The cell below each column’s lowest building cell would add 40 more, 35 of them ground (22 of path, 8 of lawn and 5 of paving); the brief leaves them unshaded.114 The façade faces the light’s side of the view, and a band there would, on my reading of Schlitter’s “one or two wall faces,” read as the building floating.1163
- Baked, not drawn at run time. The shadow goes into the ground atlas. It does not move with the hour (no game measured here moves one), and it costs nothing at night.12
- The docstring comes true.
buildings.py’s line, “The ground shadow stays off the building: it belongs to the ground layer.”, then describes something that exists.24
Proposal: where the building shadows fall, from town.json and kiradex_proposals.txt section 3; a plan, not a render.
Acceptance check 8, shadows. A forge check counts 33 shadowed cells and refuses any shadow cell whose neighbor to the west is not a cell of the same building in the same row, any shadow on a cell that is not lawn, path or paving, and any strip wider than 4 pixels.1211 A capture on the Pro Max simulator at arrival, before any tap, launched with -plazaHour 12, -plazaWeather clear and -airProbe, shows the strips east of all five rows of house-dawn, on the path at column 18, and of rows 3 and 4 of house-moss, with none east of its row 5, where a prop stands. That frame comes from a replay of the camera on paper, with the rig’s own geometry: the collector arrives at (19, 13), the camera settles on (312, 278) in world pixels, and the view spans 229.5 to 394.5 across, so each of those strips is in frame with 25 pixels or more to spare; the probe’s view and world sizes confirm the replay’s 165 by 358 before the capture is judged, and the probe’s frames confirm that check 3’s pipeline swatch, drawn under -airProbe, overlaps none of the strips named here, which at the arrival lie 144 points or more from it.276830 Following the walk of testTheChimneysSmoke to house-mine instead would not work: replayed the same way, that walk’s tap lands on a cell the export marks as a prop, so the collector does not move, and no tap a test could make from the arrival frame brings all of house-mine’s strips into a view 10.3 tiles wide. The adjacency rule and the arrival frame are my revisions of the dossier’s check.
8. Interiors
Kiradex already follows Stardew: rooms and the hall relight with their lamps, by the same code and from the same clock as the plaza, which today is the phone’s (section 7). Keep it (section 8, Interiors), with the difference section 7 names: Stardew holds an occupied room’s light until the player leaves and comes back, and Kiradex relights it on the minute, which is Kiradex’s choice.16 When the plaza moves to the room’s clock (item 1), rooms and the hall stay on the phone’s: outside the plaza the collector’s clock stays their own, as the alive post’s contract has it, and the hall is a place “off the plaza”, in its own comment, built with no client. Where the phone’s zone and the room’s differ, the light can change at the door, and the code already puts both crossings under the black veil: a room and the hall are entered through it, and the plaza returns “out of the door gone in by, the veil lifting”. The plaza stays under the place laid over it, its LightClock with it, so on the way back it still holds the last welcome’s or quiet response’s zone and stamp. That rule is mine.436919 Then add Stardew’s one refinement, its WindowLight: a window in a room shows the outside’s phase, so that a room at night is dark because its window is.125369 The forge’s interiors.py draws the room’s windows today, a plain one and a curtained one (window and curtained_window, placed by the letters N and K of its room grids); it would draw day, evening and night variants of each pane, and export each window’s cell with the room. The rig lays the variant for the phase over each window cell, as it lays the shelf and the desk over theirs (addQuad), and the stage’s relight() passes it the phase. All of it is proposed.9220
Acceptance check 9, rooms. A forge check finds the three variants of each pane in the room atlas and a window cell for every N and K in the room grids. Two captures on the Pro Max simulator, each its own launch, with -plazaHour 22 and with -plazaHour 12, enter the player’s own room by the Me tab’s me-room button, which needs no server, and show the night pane and the day pane at the curtained window, cell (3, 1) of the ground floor. On the same paper replay, from the room’s arrival at (7, 10) the view spans tiles 2.34 to 12.66 across and the room’s whole height, so that window is in frame and the plain one at (12, 1) is cut by the edge.9227 This check is mine, added so that every item has one.
9. Reduce Motion
PlazaStage reads accessibilityReduceMotion from its environment, ORs in -plazaReduceMotion, a debug launch argument fixed for the launch, as -plazaHour is, and passes the result to Air.set(phase:season:weather:reduceMotion:) at each relight and whenever the environment’s value changes. Both the argument and the parameter are proposed. When it is on, rain and snow drop to a third of their counts and fall at half speed (5 drops in the view for rain, 12 for heavy rain, at 434 pixels a second), leaves, fireflies, mist and smoke hold still, and the clouds stop, their velocity taken as zero. The light is not cut: the tints keep their 30-minute ramp, and weather keeps its 1-second fade, which PlaceLight runs with no Reduce Motion input. On my reading, a slow change of brightness is not the motion the setting is for, and cutting it would trade a slow change for a sudden one: the fade into rain takes the atlas’s day mean from 0.449 to 0.365, 0.0014 a frame at 60 hertz, where a cut is one step of 0.084, more than four times check 6’s bound of 0.02 a frame.12864 The dossier had weather cut instead of fading under Reduce Motion, and named only rain, snow, leaves, fireflies and clouds; keeping the fade, so that this item and check 6 agree, and stilling the mist and smoke, which section 7 found moving whatever the setting, are my changes. Stardew’s snow-transparency setting is the precedent for letting a player turn weather down.17
Acceptance check 10, Reduce Motion. SwiftUI’s environment value is read-only, declared var accessibilityReduceMotion: Bool { get }, so neither the app nor a test can set it; only the system’s setting changes it.86 So item 9 adds -plazaReduceMotion. A UI test launched with -plazaHour 12, -plazaWeather rain, -plazaReduceMotion and -airProbe waits for the air’s report, which carries the drop count (item 4) and the cloud’s drift (item 6), to read a drop count of 5 ± 1 and a drift of 0. Check 6’s unit test shows the fade kept under its bound, since the fade has no Reduce Motion input to change it. Once, by hand, with Reduce Motion turned on in the simulator’s Settings, the same report shows the same counts without the argument. As first written, the check read a rain log, and nothing in a test turns the setting on.1219
10. Names and art
On October 5 the app had no deny list. The dossier’s version of this item said the weather pictures pass “the deny list”, and the routes post, which calls for one, says “The deny list does not exist yet” and makes building it part of its own brief; a search of the Kiradex repository on October 5 found no such list.1293 So this item says what could be done on October 5 and what waited for that list; a list has since been added (the opening of this section).
- The strings. The new strings are the five states’ status words and the board’s line. They pass the deny list the routes brief calls for, which did not exist on October 5 and which the repository now carries as
scripts/forge/protected_names.txt. They are also checked against the one list that is published, the homage post’s “Never” list, whose second item names the franchise vocabulary no feature may use.10 - The pictures.
rain.png,splash.png,snow.pngand the window panes are drawn by the forge and reviewed against the same list’s first item, which refuses any tile, sprite or palette “taken, traced or redrawn from any Pokémon game”.10 - The palette. No color value in
DaylightorWeatherequals apokecrystalpalette color scaled to 0 to 1, except pure white. White is the multiply that leaves the art as drawn, not a color taken from anyone: today’s day tint and summer cast are white (inDaylight.swift), and white is also in Crystal’s background and people palettes, so the rule as the dossier wrote it would fail the app as it ships. The exception is my correction.122821
Acceptance check 11, names and art. A new unit test, KiradexTests/NamesTests.swift, carries the terms of the homage post’s “Never” item 2 as a list in the test target and asserts that no state’s status words and no board line contains one, ignoring case, with any word beginning “Poké” refused as that item refuses “Poké-anything”; the same test also reads the repository’s deny list, which did not exist on October 5, a half of the check that could not run that day, and which is now scripts/forge/protected_names.txt: the file is bundled into KiradexTests as a test resource, and its entries, one a line with # lines skipped, are matched as whole words or phrases without regard to case, as the file’s header says (“matched without regard to case as a whole word or phrase”); the bundling is my proposal.89 A script compares every color constant in Daylight.swift and Weather.swift against every color in bg_tiles.pal, npc_sprites.pal and roofs.pal scaled to 0 to 1, and finds no match but pure white; it runs in the author’s evidence folder, beside the decompilation clones, so no decompilation data enters the app or the forge, as the same “Never” list’s fourth item requires. Run on October 5 against the shipped Daylight.swift, its 8 color constants against the three files’ 404 colors, it finds two matches, both pure white, the day tint and the summer cast, and no other; the nearest other pair is a full 5-bit step apart in one channel. The review of the pictures is recorded with a capture of each.121028 As first written, the check had the pictures pass a deny list the app did not have on October 5.
Not in this brief
- The player’s real weather. WeatherKit would make the plaza a weather app and would need the player’s location. The canon’s weather is the world’s, scheduled and shared (section 8, Weather’s schedule).8712
- Shadows that move with the sun, point lights on sprites, and depth of field. The HD-2D tier “costs more than you’d think” in its own producer’s words, and its players turn the blur off, as the world post recorded.5932 No game measured here moves a shadow with the hour, and the stage already disables depth of field.19
- Lightning flashes (item 5), and a flicker on every lamp, unless a flicker passes check 6 and stops under Reduce Motion.12
- Crystal’s palettes, Emerald’s rain, fog and splash sprites, and their weather names. Mechanics only.1210
- A cut on the hour, except under a forced
-plazaHour.12
Key Takeaways
If you draw the art
- Draw night as a palette, not a second set of tiles. Crystal keeps about half the light (0.41 to 0.68 of each palette’s day luma in its palette blocks, and a mean of 0.49 to 0.54 by map group as a town displays them) and shares one blue-gray lightest color across five of its seven world palettes at night, with the town’s water a step off it.1
- Make morning a warmer white and nothing more. Crystal changes 5 of 32 colors for it, all the lightest one.1
- Give the lit things a color that refuses the dark. Crystal’s YELLOW stays at 0.90 when everything else falls below 0.6; Kiradex’s lit cells stand at 0.901 against a brightest tinted pixel of 0.562 (read on a Mac, not measured on an iPhone).19
- Keep cast shadows short, one tone, on one or two faces, and the same length whatever the height; tint them toward blue rather than black.86365
If you build the engine
- Leave the people out of the tint, or most of the way out. Crystal and Emerald both do, by two different mechanisms.15
- Compute the light from the clock and ramp it; do not cut it on a screen people leave open. A 30-minute ramp turns Kiradex’s 0.108 cut into steps of 0.0036 (on the atlas’s mean luma, read on a Mac).11
- Run the light on the room’s clock, schedule its own boundaries, and observe the system’s time-jump notifications for the phone’s; do not wait for the next minute.438384
- Additive light cannot darken. In RealityKit’s two blend modes, a moving shadow is an alpha sheet and a fixed one belongs in the atlas.7866
- Read Reduce Motion in every file that moves the air.869
If you design the loop
- Make weather a scheduled property of the world, rolled once a day and known ahead, not the player’s real sky. No outdoor map in Emerald or FireRed is born raining.517
- Fade weather in over about a second, and keep it gentle: Emerald’s rain is 13/16 of the light.5
- Leave out the lightning flash. The canon’s storm passes the W3C’s threshold; on my premise that children may spend long sessions in the town, it does not need to test it.446
- Decide whether interiors follow the day. Crystal says no for 207 of 208; Stardew says yes; choose one and keep it.116
FAQ
How does the day and night cycle work in Pokémon Gold, Silver and Crystal?
In Crystal, the one of the three this post reads, the real-time clock splits the day into morning from 4:00, day from 10:00 and night from 18:00, 6, 8 and 10 hours. Each part has its own set of eight background palettes, eight object palettes and, outdoors, roof colors, and when the part changes the game reloads them in one frame, with no fade. Only towns, routes and one cave follow the clock: 207 of 208 indoor maps are fixed at day, and 27 maps, 24 of them CAVE maps, are permanently night.2134
Did Pokémon Emerald have a day and night cycle?
Not for its light. Neither Emerald nor FireRed has a time-of-day system in its source; Emerald’s clock drives berries and daily events. What Emerald has is 18 weather values, set per map and by triggers: rain, thunderstorms, a downpour, shade, fog, ash, sandstorm and drought among them, with rain darkening the scenery to about 13/16 while the people keep their colors.510
How does rain work in Pokémon Emerald?
Rain is 10 drop sprites and a thunderstorm 16, each falling at 868 pixels a second, 26.6 degrees from vertical, for 18 frames before a 7-frame splash; a downpour’s 24 fall at 1,336 pixels a second for 12 frames before a 10-frame splash. The palettes fade to a color map about 0.81 of their values over 1.00 second, except the player’s and the townspeople’s; entered during play from a clear sky, the drops are all in place 115 frames after the change for rain, 1.9 seconds, after the sheet and the 24 drop sprites are set up and the drops activated one every 9 frames. No outdoor map’s header is rain: it comes from coordinate triggers on five maps, a few scripts, and Route 119’s and 123’s four-day cycles.57
How do you do day and night in a pixel-art game without redrawing tiles?
Multiply the scene by a tint per part of the day, or swap palettes, and keep the lit things out of it. Crystal’s night keeps about half the light and turns blue; Kiradex multiplies by four tints and draws its lit windows as an untinted second mesh, with additive pools of light on the ground. Ramp between tints rather than cutting, and leave the characters lighter than the place.196611
How do you draw shadows in top-down pixel art?
Short, one tone, on a layer of their own. Emerald’s shadow sprites are a single color in five rows at every size. Raymond Schlitter keeps cast shadows subtle, on separate tiles, on one or two faces, and the same length however tall the wall; Koichi Ishii used deep blues and purples instead of black. In an engine with only additive and alpha blending, a fixed shadow belongs in the art.862636578
Should a pixel-art game use the player’s real weather?
The canon says no. Emerald’s weather belongs to its maps and its days, and Stardew rolls one weather a day for the whole valley and announces it on the television the day before. Real weather needs the player’s location and turns a shared world into a private forecast; a daily roll keyed to a zone and a date gives everyone in a place the same sky, known in advance.5178711
Is lightning in games a seizure risk?
Flashing can be. The W3C’s success criterion 2.3.1 sets the bar at no more than three flashes in any one second, unless the flash is below its general and red flash thresholds, and says flashing content can cause migraines, dizziness, nausea and seizures. Emerald’s storm, measured, never holds two bolts in one second. Kiradex’s brief leaves the flash out entirely.464
How this post was made. The research dossier behind it was compiled on October 5, 2026 from shallow clones of four pret decompilations, measured by five Python scripts saved with their outputs, and from saved copies of the Stardew Valley Wiki’s day, weather and map pages, eight pages on modern lit pixel games (interviews, a developer’s post, a press kit, a news report and Wikipedia’s HD-2D article), three Pixelblogs, one translated interview on Seiken Densetsu 3, the W3C’s page on flashes and eleven Apple documentation pages, read by a sixth script. Before publishing I re-ran all of them: two gave their saved outputs back unchanged; the proposals script was corrected to place the building shadow row by row, to count wet spells as section 10 defines them, and to read Kiradex’s walk from the app’s source rather than carry 4 tiles a second; the Emerald script’s rain label was corrected, since it had printed the spawn counter’s limit as the period; the Crystal script gained a census of the fade routines’ callers, its parser of the map headers was corrected, since it had dropped five headers whose music field is an expression, and it now also resolves the palette set each environment displays; and the Kiradex script’s cloud line lost the same carried walk. Each changed script’s earlier versions and outputs are kept beside it. A seventh replays on paper where the brief’s capture checks would frame the town and a room, an eighth, arith.py, prints every derived figure, a ninth compares the app’s color constants with Crystal’s palettes, a tenth replays Emerald’s rain setup and thunder cycle call by call from its source, and an eleventh counts how much of the collector’s frame the collector covers. Every Pokémon number in the text is a script’s output, and each note names the script. Every Kiradex number is read from source or computed from exported pictures on a Mac; none was captured on an iPhone. The proposal in section 10 is mine and is labeled so; none of it has been built.
Related on this site: Pixel-Art Worlds on iPhone: What the 16-Bit Masters Knew is the first post in this series, with the RealityKit recipe, the lighting tiers and the legal line; Pixel-Art Structures: Houses, Halls and Interiors on iPhone built the lit windows and read roofs.pal and NightTiles; Honoring the Old Pokémon Games Without Borrowing Them settled Kiradex’s four phases against Crystal’s hours; Pixel-Art People: Characters and a Creator on iPhone measured the view the rain is sized for; Pixel-Art Motion: Walks, Cameras and Doors on iPhone has the walk the air is measured against; A Pixel Town Alive When Nobody Is Online: NPCs and Ghosts proposes the room clock the light would follow; Pixel-Art Rooms: Furniture Grids, Caps and Scoring is the room whose window item 8 would light by the hour; Pixel-Art Routes: Map Connections and Districts on iPhone is the world past the plaza that a daily weather would also cover, if it is built; and Pixel-Art Ledges, Stairs and Bridges in a Tile World measured the hop’s shadow kept on the ground and quotes Sabotage Studio’s Thierry Boulanger on reading height in light.
Sources
-
Author’s measurement, October 5, 2026:
measure_gen2_daylight.pyin the author’s evidence folder for this post, outputgen2_daylight.txt(andgen2_yellow_tiles.png, a 4× render of three tiles derived from copyrighted art, kept for analysis and not published). Sections: A, the hours (constants/misc_constants.asmand theTimesOfDaytable inengine/rtc/rtc.asm); B, the background palettes by time ingfx/tilesets/bg_tiles.pal, the file’s blocks as they are (mean luma of each palette’s four colors at morning, day, night, dark and indoor; night over day; color 0 by time; the colors morning changes; blue minus the mean of red and green; colors above luma 0.6 at night or dark); B2, the palette set a map displays:data/maps/environment_colors.asm’s eight indices per environment and time into the file’s 42 palettes, with the outdoor set’s WATER taken from the overworld pair, andLoadMapPals’s roof copy fromgfx/tilesets/roofs.palper map group (the outdoor set’s lumas and night over day; the displayed water, its lightest night color and its ratio; the five palettes sharing (15, 14, 24); morning against day; the blue bias; colors above 0.6; the 25 map groups holding a town or a route, the displayed ROOF’s ratio over them, and the seven world palettes’ mean ratio by group; the indoor set); C, the object palettes ingfx/overworld/npc_sprites.pal(which colors change from day to night and dark); D,gfx/tilesets/roofs.pal(27 entries, night over day by map group); E,_TimeOfDayPals,_UpdateTimePalsand the four fades inengine/tilesets/timeofday_pals.asm, the overworld loop, a census of what calls each fade (each map setup script’s fades indata/maps/setup_scripts.asm, following a script withoutdb -1into the next; which setup constant a stepped-on warp, Fly and the script commandwarpload with; the menu fades inhome/map.asm; everyspecial,farcallormapsetupof a fade in any.asmoutsidetimeofday_pals.asm, counted by file, with the black pair’s eleven map scripts and their line numbers), the six tilesets whose background palettesLoadSpecialMapPalettetakes over fromLoadMapPals, counted by environment from the map headers, and the maps whose palettes it does load (the Hall of Fame, an INDOOR map onTILESET_ICE_PATH, excepted: 74 INDOOR maps, every onePALETTE_DAY, 57 of them onTILESET_HOUSE, and 5 CAVE), the Start menu’s own lines (StartMenu, its.Exit, itsUpdateTimePalscalls, which of its items callFadeToMenu) and everycall FadeToMenuoutsidemobile/andhome/map.asm, by file; F, the 388 map headers ofdata/maps/maps.asmby environment and palette mode, eachmapdirective split on commas and every directive asserted consumed (an earlier parser matched eight single-word fields and dropped the five Radio Tower headers, whose music field is an expression, so it printed 383 headers, 202 of 203 INDOOR maps at day and 78 of 383 on the clock, 20.4%; now 388, 207 of 208 and 78 of 388, 20.1%); G, the Johto tileset’s palette map,gfx/tilesets/johto_palette_map.asm. Luma is Rec. 601 on 5-bit values scaled to 0 to 1, no gamma. Input: pretpokecrystalat5beda23. Re-run before publishing: sections A to D and G and the tile render print as before; section E gained the fade census, the menu census and the count of maps on the special tables, and B2 and F’s corrected parser were added, in later revisions, whose earlier scripts and outputs are kept beside it. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
pret,
pokecrystal/constants/misc_constants.asmat5beda23(MORN_HOUR EQU 4,DAY_HOUR EQU 10,NITE_HOUR EQU 18, lines 36 to 38) andpokecrystal/engine/rtc/rtc.asm(TimesOfDay, line 48, with the comment “0400-0959 morn | 1000-1759 day | 1800-0359 nite” at line 50), read October 5, 2026, https://github.com/pret/pokecrystal/blob/master/engine/rtc/rtc.asm. ↩↩↩↩↩ -
pret,
pokecrystal/engine/tilesets/timeofday_pals.asmat5beda23, read October 5, 2026:_TimeOfDayPals(line 13),_UpdateTimePals(line 109,ld c, $9 ; normal, thenGetTimePalFadeandDmgToCgbTimePals),FadeInFromWhite(115),FadeOutToWhite(122),FadeInFromBlack(146),FadeOutToBlack(153), andReplaceTimeOfDayPals(189, testingSTATUSFLAGS_FLASH_Fat line 206), https://github.com/pret/pokecrystal/blob/master/engine/tilesets/timeofday_pals.asm. ↩↩↩↩↩ -
Author’s arithmetic from the measured values in the text:
arith.pyin the author’s working folder for this post, outputarith.txt, reading the outputs in the author’s evidence folder for this post and, read only, Kiradex’s exportedtown.pngandglow.png. It prints the frame rates (2^24 ÷ 280,896 and 2^22 ÷ 70,224, both 59.7275 hertz); the hours and shares of the day (night 10 of 24, 41.7%; Kiradex’s lamps 13 hours, 54.2%); Stardew’s day (20 × 60 × 0.7 = 840 seconds, 14 minutes; nightfall 9.8, 9.1 and 8.4 real minutes after 6am); Crystal’s night over day per palette and their mean (0.511), the blue-bias change (6.44, 0.208 of 31), the luma of colors (15, 14, 24) 0.498, (30, 30, 11) 0.898, (27, 31, 27) 0.947 and (28, 31, 16) 0.916, the displayed outdoor water fromgen2_daylight.txtsection B2 (0.528 by day, 0.248 at night, 0.470; its lightest night color (15, 13, 27), 0.490), the seven world palettes’ night over day as a town displays them by map group (0.494 to 0.539, mean 0.509 over 25 groups), the displayed ROOF’s (0.369 to 0.688, mean 0.477) and the displayed blue bias (−1.17 to +6.10, a rise of 7.27), the morning’s 5 of 32 (15.6%), YELLOW’s 3 of 192 tiles (1.6%), the reload’s 64 + 64 + 4 = 132 bytes, the fades’ 8 frames (134 ms), the map headers parsed from section F (388; the maps on the clock, 78 of 388, 20.1%; indoors at day, 207 of 208; the 27 permanent-night maps by environment, CAVE 24, DUNGEON 1, GATE 1, INDOOR 1; the earlier 383, 78 of 383 and 202 of 203 noted beside them); Emerald’s 13/16 (0.8125), map 3 at 31, 16 and 8, the rain fade (60 frames, 1.005 seconds), rain and downpour speeds, falls and splashes (262 and 268 pixels, 0.301 and 0.201 seconds, 7 and 10 frames, 0.117 and 0.167 seconds, a drop’s life of 25 frames, 0.419 seconds), the spawn periods (delay + 1: 9 and 5 frames), the activation phase from zero active drops (90, 80 and 120 frames, 1.51, 1.34 and 2.01 seconds) and, fromgen3_replay.txt, the complete transitions from a clear sky during play (25 setup calls + the activation: 115, 105 and 145 frames, 1.93, 1.76 and 2.43 seconds; the palette at map 3 on frame 59; a thunderstorm’s first bolt on frame 109; the rain-to-rain counts), the cloud, fog and sandstorm against the 59.73-pixel-a-second walk (0.500, 0.250, 3.60), the thunder’s waits, holds and gaps in seconds, the dark gap as the gap timer plus one (61 to 76 frames, 1.021 to 1.272 seconds; 61 frames as 1.0213 seconds, bolt onsets at least 67 frames apart), the wait before a cycle’s first strike (361 to 720 frames), the thunder figure’s timeline, and the thunder sound’s delay after the strike (1 to 20 frames, up to 0.335 seconds; 1 to 100, up to 1.674); the outdoor header shares (51 of 68, 75.0%; 66 of 76, 86.8%) and the 86 coordinate events; the flash circles (1,810 and 16,286 square pixels, 4.7% and 42.4% of 38,400, nine times the area); the shadow sprites’ opaque shares and Kiradex’s (40 of 48, alpha 153 = 0.60, luma 0.071); Kiradex’s atlas under each tint (0.4318, 0.4488, 0.3719, 0.2639), the lit cells over the brightest tinted pixel (1.60), the four cuts (0.0170, 0.0769, 0.1079, 0.1678) and the largest minute step of a 30-minute ramp at each boundary (0.0006, 0.0026, 0.0036, 0.0056); a lamp pool’s center with the tint added in full (0.806) and weighted by the picture’s peak of 0.961 (0.785); 7 + 17 = 24 pools; today’s walk read fromMotion.swift(16 pixels in 16 ticks at 60 a second, 60 pixels a second; the run 120), the cloud’s 1.640 pixels a second (0.0273 of the walk, 9.8 seconds a tile, 9.75 times slower than 16), the proposed 16 pixels a second against the walk (0.2667), a distance equal to the view’s height in 22.4 seconds, and the January 1, 2027 direction recomputed from the digest’s fifth byte (153, 215.15625 degrees; a vertical component of 9.21293 pixels a second, crossing the 358-pixel height in 38.86 seconds, and a horizontal of 13.08136, crossing the 165-pixel width in 12.61), its shade at opacity 0.55 and 0.8 (0.763 and 0.655 darkest; 0.950 and 0.927 mean) and the overcast day (0.404); the view of 59,070 square pixels (1.538 screens) and the drops in it; rain and heavy rain on the atlas (0.365, 0.214; 0.337, 0.198); the walkers’ lifted light per phase (0.980, 1.000, 0.913, 0.796; 1.34 times the place at night); the weather year’s wet shares; Reduce Motion’s thirds and halves (5 drops, 12.3, 434 pixels a second); the building shadow’s 33 × 4 × 16 = 2,112 pixels (28 of lawn, 5 of path, 6 rows skipped for a prop) and the 40 cells below the bases, 35 of them ground; the fades into rain and heavy rain per frame (0.084 and 0.112 as cuts; 0.0014 and 0.0019 a frame at 60 hertz, 0.0007 and 0.0009 at 120); the place materials a one-second fade assigns, counted from the code (3 at a relight, 12 a second for the ripple, 180 at 60 hertz and 360 at 120 over the fade; with a door shown, 4 at a relight and 240 and 480 over the fade, a line added October 16, 2026); the weather year’s wet days and spells (82 days, 17 + 19 + 21 + 25; 65 spells, the longest 3); Celeste’s (2048 ÷ 256)^2 = 64 slots × 4 = 256; and the two enumeration cases of the savedMaterialParameterTypes.BlendModepage, read from its topic list, whichparse_apple.pydoes not print. Revised to read the header counts from the script rather than carry them, to add the displayed palettes, the complete rain transitions, the thunder gaps and the cloud’s ratios and crossing, and to read today’s walk from the app’sMotion.swiftrather than carry 64 pixels a second; the earlier scripts and outputs are kept beside it. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Author’s measurement, October 5, 2026:
measure_gen3_weather.pyin the author’s evidence folder for this post, outputgen3_weather.txt. Sections: A, the constants ofinclude/constants/weather.h; B, each weather’sInitVarsand blend targets insrc/field_weather_effect.c; C,BuildColorMapsinsrc/field_weather.creplayed in Python,sBasePaletteColorMapTypes, and the palette slots ofinclude/event_object_movement.h; D, rain’s Q28.4 velocities, fall durations, splash frames and the spawn period (UpdateVisibleRainSprites’s++rainSpriteVisibleCounter > rainSpriteVisibleDelay, a drop every delay + 1 frames, andRain_InitAll’s loop within one frame); E, cloud, fog and sandstorm drift (the sandstorm replayed 6,000 frames overgSineTableinsrc/trig.c); F, thunder timings; G, the header weather of everydata/maps/*/map.jsonin Emerald and FireRed by map type, coordinate weather events, the two route cycles, scriptedsetweathercalls indata/**/*.inc, andrequires_flashmaps; H,sFlashLevelToRadiusand the default flash level; I, a case-insensitive name search for “timeofday” and “time_of_day” insrc/*.candinclude/**/*.h. Inputs: pretpokeemeraldat731ad5bandpokefireredat037335f. Re-run before publishing: every section prints as before except the last lines of D, whose label had printedrainSpriteVisibleDelayitself as the spawn period (every 8 and 4 frames) and now prints delay + 1 (every 9 and 5 frames) with the fill times; the earlier script and output are kept beside it. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
pret,
pokeemerald/src/field_weather.cat731ad5b, read October 5, 2026:sWeatherFuncs(line 85, each weather’sinitVars,main,initAllandfinish),sBasePaletteColorMapTypes[32](line 114),StartWeather(154),SetNextWeather(183),Task_WeatherInit(216, calling the weather’sinitAllat 222 once the screen is ready),Task_WeatherMain(227: while the current and next weather differ, the old weather’sfinish()each frame until it returns false, at 231, then the new weather’sinitVars()at 235,colorMapStepCounter = 0at 236 andWEATHER_PAL_STATE_CHANGING_WEATHERat 237; otherwise the weather’smain()at 244; the palette state function after either, at 247),BuildColorMaps(271),UpdateWeatherColorMap(344, stepping the color map when++colorMapStepCounter >= colorMapStepDelayat 354) andApplyColorMap(459); andinclude/constants/field_weather.hline 4,MAX_RAIN_SPRITES 24, https://github.com/pret/pokeemerald/blob/master/src/field_weather.c. ↩↩↩↩↩↩ -
pret,
pokeemerald/src/field_weather_effect.cat731ad5b, read October 5, 2026: the cloud coordinates’ comment “These coordinates are for the lower half of Route 120.” (line 41),Clouds_InitVars(95),UpdateCloudSprite’s comment “Move 1 pixel left every 2 frames.” (222),sRainSpriteSplashAnimCmd(414),sRainSpriteMovement(449),sRainSpriteFallingDurations(458),Rain_InitVars(471),Rain_InitAll(484, a loop untilweatherGfxLoaded),Rain_Main(491: its three states, the sheet at 496,CreateRainSpriteat 500 until it returns false,UpdateVisibleRainSpritesat 504 until it does),Rain_Finish(513, returning false at once and keeping the sprites when the next weather is a rain kind, 518 to 524),CreateRainSprite(665: returning false when 24 exist, 670 to 671, creating one sprite a call, and returning false on the call that makes the 24th, 694 to 709),UpdateVisibleRainSprites(714, returning false when the active count meets the target, 716 to 717, and activating a drop when++rainSpriteVisibleCounter > rainSpriteVisibleDelay, line 719),Sunny_Finish(168, returning false), the thunder states’ enum (1014 to 1035),Thunderstorm_InitVars(1037),Downpour_InitVars(1066), the thunderstorm’s pattern comment “(SHORT_BOLT){1,2}(LONG_BOLT)?” (1089) and “Thunder only plays on the final bolt of the cycle.” (1091),Thunderstorm_Main(1092) withUpdateThunderSound()first (1094), its setup states (1097 to 1111) and itsTHUNDER_STATE_WAIT_CHANGEcase (1112), which jumps toTHUNDER_STATE_INIT_CYCLE_1(1114) past the wait,THUNDER_STATE_NEW_CYCLE(1116, the timer(Random() % 360) + 360at 1118 falling through intoTHUNDER_STATE_NEW_CYCLE_WAITat 1121, whose--thunderTimer == 0is at 1123),THUNDER_STATE_INIT_CYCLE_1and_2(1126 and 1131, falling intoTHUNDER_STATE_SHORT_BOLTat 1135, the strike at 1137 and the hold(Random() % 3) + 6at 1142),THUNDER_STATE_TRY_NEW_BOLT(1145, the bolt’s end at 1149 and the gap timer(Random() % 16) + 60at 1154),THUNDER_STATE_WAIT_BOLT_SHORT(1169),THUNDER_STATE_INIT_BOLT_LONG(1173, the same gap timer at 1174),THUNDER_STATE_WAIT_BOLT_LONG(1177,EnqueueThunder(100)at 1181, the strike at 1182 and the hold(Random() & 0xF) + 30at 1183),THUNDER_STATE_FADE_BOLT_LONG(1187, the gradual fade at 1191),EnqueueThunder(1242, the timerRandom() % waitFramesat 1246) andUpdateThunderSound(1251),FogHorizontal_Main(1392),Sandstorm_Main(1975),Shade_InitVars(2247),sWeatherCycleRoute119(2581),sWeatherCycleRoute123(2588) andUpdateWeatherPerDay(2622), https://github.com/pret/pokeemerald/blob/master/src/field_weather_effect.c. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Author’s measurement, October 5, 2026:
measure_shadows.pyin the author’s evidence folder for this post, outputshadows.txt: size, opaque pixels and distinct tones of Emerald’sshadow_small.png,shadow_medium.png,shadow_large.pngandshadow_extra_large.png(pretpokeemeraldat731ad5b,graphics/field_effects/pics/) and of Kiradex’sKiradex/Resources/World/shadow.png, with each drawn as a text mask of its opaque pixels. Re-run before publishing with identical output. ↩↩↩↩↩↩↩↩↩↩ -
Author’s measurement, October 5, 2026, on a Mac, not an iPhone:
measure_kiradex_light.pyin the author’s evidence folder for this post, outputkiradex_light.txt. It reads, as text, the Kiradex repository’sKiradex/World/Daylight.swift,PlazaStage.swift,Air.swiftand every Swift file inKiradex/World/, andscripts/forge/town.pyandbuildings.py; and, with Pillow, the exportedKiradex/Resources/World/town.png(the tints applied per channel to its opaque pixels),cloud.png,mist.png,glow.png,shadow.pngandtown.json(40 by 30, dated October 3, 2026, 21:34). Sections: A, Daylight’s phases, tints, casts and lamps; B, the tints on the atlas and the lit cells; C, the clock, its observers and Reduce Motion; D, the air; E, the town’s lights, lit cells, pools, chimneys and buildings; F, the shadows. Luma is Rec. 601 on 0 to 1 values, no gamma. Private repository, read without running git or the forge, at 16:54 on October 5 at commitcf3beb1e, from a working tree being edited that day. Re-run before publishing, at 18:11 at commit66c116b0: every line prints as before but one in D, whose label had carried a walking speed of 4 tiles a second that the line’s number, the 9.8 seconds a cloud takes to cross a tile, does not use; the earlier script and output are kept beside it. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Blake Crosley, “Honoring the Old Pokémon Games Without Borrowing Them,” blakecrosley.com, October 5, 2026: section 3 (Crystal’s
MORN_HOUR 4,DAY_HOUR 10andNITE_HOUR 18and theTimesOfDaycomment; Kiradex’sDaylight.Phase, “morning from 6 to 9, day from 9 to 17, evening from 17 to 20 and night otherwise”, and the statement that “the brief in section 14 keeps it that way”; Emerald’s berries on the real-time clock, and its list of Emerald’s eleven daily updates, “the weather” among them, which names no function) and section 14 (the brief, which keeps Kiradex’s phases, “which are already not Crystal’s 4, 10 and 18 and stay that way”; and its “Never” list, whose item 1 refuses any “tile, sprite, palette or screen layout taken, traced or redrawn from any Pokémon game”, item 2 names the franchise vocabulary no feature may use, from Pokédex and “Poké-anything” to Frontier, and item 4 refuses “code or data from the decompilations” in the app or the forge), https://blakecrosley.com/blog/honoring-the-old-pokemon-games. ↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Author’s measurement and proposal arithmetic, October 5, 2026, on a Mac:
measure_kiradex_proposals.pyin the author’s evidence folder for this post, outputkiradex_proposals.txt. Sections: 1, Emerald’s rain carried into the iPhone 18 Pro Max view of 165 by 358 world pixels (density per 10,000 square pixels, drops in the view, speed per frame at 60 and 120 hertz, fall and splash times); 2, Kiradex’s walk read from the 17:18 copy ofMotion.swift(16 pixels in 16 ticks at 60 ticks a second, 60 pixels a second; the run 120; a diagonal walking step 59.0 along the diagonal), drift speeds against each game’s walk (Emerald’s 59.7 pixels a second, Kiradex’s 60), and the proposed cloud at 16 pixels a second and at half the walk; 3, the building shadow’s cells intown.json: for each row of each of the seven buildings, the cell immediately east of that row’s easternmost building cell, by letter, shaded only where it is lawn, path or paving (33 of 39), and, for comparison, the cell below each column’s lowest building cell (40, 35 of them ground); 4, the cut and a 30-minute ramp on the atlas’s mean luma for day to evening and evening to night; 5, rain’s 13/16 on the atlas and the walkers’ light lifted halfway to white; 6, a year (2027) of the proposed daily weather roll for one zone,sha256("zone|yyyy-mm-dd"), first four bytes big-endian, modulo 100, against the proposal’s seasonal table walked cumulatively in the order listed (it prints each season’s range of rolls per state), the wet days and wet spells (maximal runs of consecutive days of rain, heavy rain or snow, in any mix: 82 days, 65 spells, the longest 3), two zones’ agreement over 30 January days, and the proposed cloud direction from the digest’s fifth byte. Everything labeled PROPOSAL in the output is the author’s. Section 3 was revised from each building’s bounding box to the row-by-row rule above. Section 6 was revised: it had counted runs of one wet state (67, the longest 3) and dropped a run ending on the year’s last day; it now counts wet spells as defined above and prints the roll’s ranges and the cloud direction. Re-run at 18:11 at commit66c116b0, reading the town export and pictures in the working tree. Section 2 was revised to read the walk fromMotion.swift: it had carried 4 tiles a second, 64 pixels a second, which put today’s cloud at 0.026 of the walk and the proposal at 0.25. The earlier scripts and outputs are kept beside it, and sections 1, 3, 4 and 5 and section 6’s season counts print as before. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Author’s research dossier for this post, “Light, weather and time: how pixel worlds keep a day, and what Kiradex should,” compiled October 5, 2026; Kiradex repository
docs/research/light/01-light.md(private; dated 07:25 that day and read at 16:51 at commitcf3beb1e), identical to the copy in the author’s evidence folder. Sections 1 (key facts), 2 (case studies), 3 (rules with numbers), 4 (the brief for Kiradex, from which section 10’s states, table, factors, files and acceptance checks 1 to 8 and the post’s 10 and 11 (the dossier’s 9 and 10) come, as first written; the post revises checks 2 to 8, 10 and 11 and says so at each; its check 10 says the weather pictures pass “the deny list”, and its cloud direction comes from “the weather roll’s second byte”) and 5 (what is measured and what is not: approximations, pages not reached, proposals, and the checks against the published posts). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
pret,
pokecrystal/gfx/tilesets/bg_tiles.palat5beda23, read October 5, 2026: five blocks of eight background palettes (morning, day, night, dark, indoor) and an overworld-water pair (lines 51 to 53:RGB 23,23,31, 18,19,31, 13,12,31, 07,07,07 ; morn/dayandRGB 15,13,27, 10,09,20, 04,03,18, 00,00,00 ; nite), https://github.com/pret/pokecrystal/blob/master/gfx/tilesets/bg_tiles.pal. ↩↩ -
pret,
pokecrystal/gfx/overworld/npc_sprites.palat5beda23, read October 5, 2026: the object palettes by time of day, https://github.com/pret/pokecrystal/blob/master/gfx/overworld/npc_sprites.pal. ↩↩ -
pret,
pokeemerald/include/event_object_movement.hat731ad5b, read October 5, 2026: the object palette slots fromPALSLOT_PLAYER(line 12) toPALSLOT_NPC_SPECIAL(line 22) and its reflection slot, https://github.com/pret/pokeemerald/blob/master/include/event_object_movement.h. ↩↩ -
Stardew Valley Wiki, “Day Cycle,” saved October 5, 2026 as
sdv-day-cycle.htmlandsdv-day-cycle.txtin the author’s evidence folder for this post (“Time passes at 0.7 real seconds per in-game minute”; nightfall “In Spring and Summer , it is 8pm, while in Fall it is at 7pm and Winter is 6pm”; “indoors the windows turn dark and lamps and sconces light up”; outdoors “it does not immediately become full dark, but gradually becomes darker over time”), https://stardewvalleywiki.com/Day_Cycle. ↩↩↩↩↩↩↩↩↩ -
Stardew Valley Wiki, “Weather,” saved October 5, 2026 as
sdv-weather.htmlandsdv-weather.txtin the author’s evidence folder for this post (“Weather changes daily and with the seasons”; the next day’s weather “can be checked by viewing the TV screen”; “There is a chance for a lightning strike to occur every 10 minutes”; “Snow transparency can be changed in the Options Menu”), https://stardewvalleywiki.com/Weather. ↩↩↩↩↩↩↩↩↩ -
Stardew Valley Wiki, “Modding:Weather data,” saved October 5, 2026 as
sdv-weather-data.htmlandsdv-weather-data.txtin the author’s evidence folder for this post (the probability of each weather type by season for dates without forced weather), https://stardewvalleywiki.com/Modding:Weather_data. ↩↩↩↩↩↩↩↩ -
Kiradex source,
Kiradex/World/PlazaStage.swift, read October 5, 2026, last at 17:42 at commit5a344e3f, from a working tree the app’s own agent was editing that day (private repository; the file is dated 17:22 that day, and the line numbers are that reading’s; a reading at 16:51, at commitcf3beb1e, had the lines from 469 on lower by 4 to 8, the file having gained an accessibility representation since):daylight(at:)(line 69, usingDaylight.forcedHourwhen set),relight()(77 to 81, called after the build at 152 and after the air is prepared at 194),stepTrace, the player’s last step or the air’s report, “as the stage’s accessibility value” (60 to 63, given to the stage’s accessibility element byStageAccessibilityat 361),content.renderingEffects.depthOfField = .disabled(133), the-airProbereport written to the stage’s value once, afterrig.air.prepare(), in the view’s.task(190 to 193),onChange(of: clock)callingrelight()(248 to 250), the task “The light follows the clock, a minute at a time.” sleeping 60 seconds (251 to 257), the scanlines, one shaded line every three points (344 to 349), the vignette, a radial gradient from clear, through clear, to black at opacity 0.28, over a radius of 900 points (353 to 355),RoomView(declared at 911, building aPlazaStageat 946 withfitsWhole: falseat 975); the clock@State(57);PlazaView.words, the status line’s words (819); theSceneEvents.Updatesubscription that stepsrig.updateeach frame (176 to 182);rig.onStepsettingstepTraceto the player’s step (156 to 160);bubbles(of:in:), the speech bubbles over the heads, under aTimelineView(.animation(minimumInterval: 0.1))(447 to 463); andPlazaView(536), which presentsQuestBoardViewas a sheet (690) and lays a room or the hall over the plaza, “laid over it, not pushed”, the plaza staying under the place (719 to 724);RoomView’s “A room is entered through the veil, and its floors change under it.” (936), and itsPlazaStagewithclient: nil(947); and the plaza’s return from a room, the show or a visit, “out of the door gone in by, the veil lifting.” (678 to 679). No observer ofsignificantTimeChangeNotificationorNSSystemTimeZoneDidChangewas inKiradex/World/at that reading. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Kiradex source,
Kiradex/World/PlazaRig.swift, read October 5, 2026 at 16:51 at commitcf3beb1e, from a working tree being edited that day (private repository; the file is dated 15:22, and the line numbers are that reading’s): the card’sDirectionalLightunder the comment “For the card alone: nothing else in the world is lit.” (lines 161 and 162), the rig’slight(211),light(_:lampsOn:)(254) and its comment that a walker is “redrawn in the new light on the next frame” (262), the lit windows’ mesh withmaterial(light: .white)(383), and each walker frame’s material fromsheet.material(column:row:light:)with the rig’slight(512, inadd(other:figure:tile:facing:)at 508, and 1081, inanimate(_:));animate(_:)itself (1038), in which a standing walker shows the idle breath, the blink and, now and then, one of theidleEmotes, tic, look, shrug, think and doze (136), the next one 6 to 14 seconds on (1062), and records the frame its material shows in the walker’sshown(1078 to 1080), the facing’s row times the sheet’s columns plus the drawing’s; the player’s first idle emote at 7 seconds and first blink at 2.5 (Walker, 33 to 35); the ground, surroundings and objects givenmaterial(light:)inlight(_:lampsOn:)(257 to 259) and again in the ripple, each time its frame changes, four a second where there is water (231, 240 to 250); the door’s drawing with the rig’s light inshowDoor(at:sheet:column:)(617; the materials at 627 and 634);build(tiles:collector:arrival:)(362), which first gives the ground, the surroundings and the objectsmaterial(light:)with the rig’s light (363, 370 and 376);addQuad, a picture laid flat over some tiles (346);worldPoint(at:in:displayScale:)(459);viewPoint(of:in:displayScale:), where a world point lands in the view (471);update(_:), the per-frame step, which runs the world’s ticks through a tick clock and then turns the showcase, steps the ripple and updates the air (952 to 966), withtick()(969) advancing the walkers and the camera;place(_:), which stands a walker’s feet 0.3 of a tile below its tile’s center on whole pixels (1088 to 1097); andlockCamera()(1171 to 1200), which sets the camera on the player within bounds thatCameraLock.bounds(Kiradex/World/Motion.swift, lines 258 to 262, dated 12:36) rounds inward to the map’s edges.follow(_:), which an earlier reading of the file cited for the camera, is no longer in it. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Kiradex source,
Kiradex/World/Daylight.swift, dated October 2, 2026 and read October 5 at 16:51 at commitcf3beb1e, from a working tree being edited that day (private repository; the line numbers are that copy’s):Phase.init(hour:)(line 13),tint(line 26, “a multiplier on the art’s own colours”, declared “ART SURFACE: four fixed lights for a pixel world”),lampsOn(line 36),Season.init(month:)(line 54, “By the month, in the north; the south is a setting for later.”),cast(line 75),now(_:calendar:)withcalendar: Calendar = .current(line 86),light(phase:season:)(line 92) andforcedHour, read from-plazaHour(line 101). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Kiradex source,
Kiradex/World/Air.swift, dated October 3, 2026 and read October 5 at 16:51 at commitcf3beb1e, from a working tree being edited that day (private repository; the line numbers are that copy’s): the header, “Light and air over a place, build 29 of the design plan”, ending “none of it takes the day’s tint: light is light.” (lines 6 to 12); the glow program’s descriptor withlightingModel = .unlitandblendMode = .addand the comment “The program says how to blend; the material says that it blends at all.” (lines 88 to 96); the pools (lines 144 to 153);set(phase:season:)(from line 160: the cloud hidden at night, mist in the morning, leaves in autumn in every phase but night, line 168, with the same test inupdateat line 276, fireflies at night);makeCloud(line 175, opacity 0.55, “repeats every 384 world pixels”);makeMist(line 192, opacity 0.7, three banks); 14 leaves (line 209); 10 fireflies (line 248); the report that-airProbeshows (built at lines 105 and 114); and the per-frame cloud offset,cloudOffset += SIMD2(0.004, 0.0015) * dtpluscamera / 384(lines 266 to 268); the depths,cloudDepth2.4,driftDepth2.45 andpoolDepth2.5 (lines 19 to 21), with the leaves, the fireflies and the smoke atdriftDepth(237, 251, 287 and 314);TileMap.depth(atWorldY:), 1 plus up to 0.5 down the map (Kiradex/World/TileMap.swiftline 122);outdoors, whether the place has a sky (lines 27 and 71); the cloud hidden at night (line 163); the leaves as drifters (lines 207 to 215); andupdate(_:camera:view:)(line 262). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Blake Crosley, “Pixel-Art Structures: Houses, Halls and Interiors on iPhone,” blakecrosley.com, October 3, 2026: the TL;DR’s “nineteen windows and the hall’s two sconces”; the section on lit windows as an untinted second mesh enabled when the lamps come on; the atlas paragraph, “the 21 lit cells dedupe to two”, and its note that the GPU cost of the two new meshes “is not profiled yet”; Crystal’s
roofs.pal; and Stardew’sNightTilesandWindowLight, https://blakecrosley.com/blog/pixel-art-structures-on-iphone. ↩↩↩↩↩↩↩↩ -
Kiradex forge,
scripts/forge/buildings.py, dated October 3, 2026 and read October 5 at 16:51 at commitcf3beb1e, from a working tree being edited that day (private repository; the line numbers are that copy’s): the module docstring, “The ground shadow stays off the building: it belongs to the ground layer.” (lines 13 to 16); the window set in a gable’s triangle,K.window(lit=night)at the middle of the roof block when the shape isgableand the width at least 5 (lines 91 to 93), recorded among the building’s windows (112 and 113), which the two gable-roofed buildings carry;dormer, a roof piece drawn with no lit variant and recorded as no window (lines 133 and 134); and the eave’s shadow, three rows under the roof, infinish(lines 195 to 198). ↩↩↩↩ -
pret decompilations, shallow clones in the author’s evidence folder for the series:
pokecrystalat5beda23(committed September 29, 2026),pokeemeraldat731ad5b(October 1),pokefireredat037335f(September 26) andpokeredatd2704a6(September 22), https://github.com/pret. ↩ -
Author’s reading, October 5, 2026:
parse_apple.pyin the author’s evidence folder for this post, outputapple_pages.txt, which prints the title, abstract, declaration and platforms of each of eleven Apple documentation JSON pages saved that day (apple-*.html). Re-run before publishing with identical output. ↩↩↩ -
Author’s measurement, October 5, 2026, on a Mac:
measure_capture_walk.pyin the author’s evidence folder for this post, outputcapture_walk.txt. A replay on paper, not a run on a simulator, of where the Pro Max camera settles, with the rig’s own geometry read from the Kiradex source (at 16:51 on October 5, commitcf3beb1e, a working tree being edited that day; the line numbers are that reading’s): the view of 440 by 956 points at 3x and 8 device pixels a texel, 165 by 358.5 world pixels (PlazaRig.fit, lines 433 to 456); tile centers and the tile under a point (TileMap.swiftlines 234 to 241); a tap’s world point (PlazaRig.worldPoint, lines 459 to 463); the walkable letters (TileMap.swiftlines 35 to 53), which the app takes from the export’s rows (WorldMap.swiftlines 99 to 110); the walker’s feet (PlazaRig.place, lines 1088 to 1097, with the sheet’s 32-by-40 cell and 2 rows under the feet fromcharacters.json); and the camera set on the player and clamped to the map (PlazaRig.lockCamera, lines 1171 to 1200, withCameraLock.boundsinMotion.swift, lines 258 to 262, which rounds the bounds inward to whole pixels; neither settle point the replay finds lies on a bound, so the rounding moves nothing). The script’s docstring was later brought to those lines, the earlier reading’s numbers kept in a copy beside it; its output is unchanged. It finds thattestTheChimneysSmoke’s tap lands on (18, 8), a prop in the export, so the collector stays at (19, 13) with the camera on (312, 278); that from there the strips ofhouse-dawn(column 18, rows 16 to 20) and ofhouse-mossrows 3 and 4 are in view with 25.2 pixels or more to spare, and its row 5, a prop, too; that no tap on a grid of normalized points from 0.02 to 0.98 across and 0.05 to 0.95 down bringshouse-mine’s strips into view; and that in the player’s ground-floor room, from its arrival at (7, 10), the view spans tiles 2.34 to 12.66 across and the room’s whole height, with the curtained window at (3, 1) whole in view and the window at (12, 1) not. ↩↩↩ -
Author’s measurement, October 5, 2026, on a Mac:
measure_palette_match.pyin the author’s evidence folder for this post, outputpalette_match.txt. It reads every color in pretpokecrystal’sgfx/tilesets/bg_tiles.pal(168 colors, 57 distinct),gfx/overworld/npc_sprites.pal(128, 23 distinct) andgfx/tilesets/roofs.pal(108, 91 distinct) at5beda23, scales each 5-bit channel by 1/31, and compares the 8 color constants of Kiradex’sDaylight.swift(read as text, lines 28 to 31 and 77 to 80): two exact matches, both pure white (31, 31, 31), at lines 29 and 78; none other; the nearest other pair, line 79’s (1, 0.93, 0.84) against (30, 28, 26), differs by 0.0323 in one channel, one 5-bit step. ↩↩↩ -
Author’s replay, October 5, 2026:
replay_gen3_rain_thunder.pyin the author’s evidence folder for this post, outputgen3_replay.txt. It first asserts, against the text of pretpokeemeraldat731ad5b, the idioms it rests on:Rain_Main’s three states,CreateRainSpritecreating one sprite a call and returning false on the call that makes the 24th,UpdateVisibleRainSprites’s++rainSpriteVisibleCounter > rainSpriteVisibleDelay,Rain_Finishkeeping the sprites when the next weather is a rain kind,Sunny_Finishreturning false at once,Task_WeatherMain’s swap (the old weather’sfinish()until false, theninitVars(), the step counter reset and the palette state set to changing, insrc/field_weather.c) andUpdateWeatherColorMap’s++colorMapStepCounter >= colorMapStepDelay, the thunderstorm’s setup states andTHUNDER_STATE_WAIT_CHANGE, and every timer and transition ofThunderstorm_Main’s cycle; then it replays them call by call. Sections: A, a rain kind entered during play from no rain sprites, counted from the frameTask_WeatherMainswaps the weather (the sheet on call 1, the 24 sprites on calls 2 to 25, the first drop active on call 34 for rain and 30 for the heavier kinds, the last on call 115, 105 or 145; the palette at map 3 on frame 59; a thunderstorm’s first bolt on frame 109; the callsRain_InitAllloops at map load, 116, 106 and 146); B, one rain kind entered from another, with the sprites kept and the count moved one drop every 5 or 9 calls; C, the thunderstorm’s cycle fromTHUNDER_STATE_NEW_CYCLEwith minimum draws, maximum draws and one short bolt alone (a first strike 361 to 720 frames after the cycle’s call; bolts lit 6 to 8 frames; dark gaps of 61 to 76 frames, the gap timer plus the one call the next state takes; the long bolt at full for 30 to 45; the sound 1 to 20 or 1 to 100 frames after the strike). ↩↩↩↩ -
Author’s measurement, October 5, 2026, on a Mac:
measure_collector_coverage.pyin the author’s evidence folder for this post, outputcollector_coverage.txt, reading the 18:07 copy of the figure sheets andcharacters.jsonat commit66c116b0and followingCollectorComposeras read. A, the 32-by-40 cell, 1,280 pixels, in frame 0 facing down: the body sheet alone opaque on 377, every one of the 123 sheets at once on 581 (counting the eraser’s pixels as opaque gives 705); B, the manifest’s default figure composed for each of the 11 drawings a standing collector shows (PlazaRig.animate(_:)): 408 opaque pixels in the first, 374 to 408 across them, 290 opaque in all 11; C, night over day on the stored color values, per channel: the default figure’s pixels 0.787 under the lifted light (0.788 on the 290) and 0.573 under the full night tint (0.575), the lift’s smallest channel 0.75; the ground at the atlas’s mean color, 0.588; the whole frame with the figure’s 408 pixels and 872 of ground, 0.638 for a correct build and 0.584 for one that tints the walkers fully, 0.633 with the body’s coverage and 0.662 with every sheet’s. Sections D and E were added October 15, 2026, leaving A to C as they were. D, the 290-pixel mask at each drawing’s own colors: day luma 0.2989 (doze_b) to 0.3161 (think); with the same drawing in both captures 0.7875 to 0.7884 under the lift and 0.5750 to 0.5768 under the full tint; across the 121 ordered pairs of drawings 0.7456 (thinkby day,doze_bat night, the one pair under 0.75) to 0.8336 under the lift, and up to 0.6099 under the full tint; held inidle_a, 0.7876 and 0.5752. E, the ground over every opaque pixel of the town atlas (the 16:51 copy at commitcf3beb1e) and the mask held inidle_a, under three pipelines: multiplied as stored, ground 0.5880, channels (0.500, 0.580, 0.900), mask 0.7876 under the lift and 0.5752 under the full tint; picture and tint decoded from sRGB, multiplied and encoded, 0.5623, (0.469, 0.554, 0.892), 0.7643 and 0.5312; the picture decoded and the tint’s numbers taken as linear, 0.7742, (0.719, 0.772, 0.951), 0.8885 and 0.7557; and the window reference, 7 of the 24 lit cells wholly in the Pro Max arrival view ofcapture_walk.txt, 294 lit pixels, all outside the planes of the 24 pools (Air.lightPools) and within 450 points of the view’s center. Section F, added the same day: the air’s depths, the cloud 2.4, the drifting things 2.45 and the pools 2.5, against the walkers’ 1 to 1.5 (TileMap.depth); the cloud’s darkest shade, 1 less its largest alpha, 110 of 255, times its opacity, 0.55, giving 0.7627; a build that tinted the walkers, with that shade over the mask by day, 0.7541 as stored, 0.6964 in linear and 0.9908 with the tint’s numbers as linear; and at the arrival, (19, 13), the collector’s frame, x 296 to 328 and y 258 to 298 in world pixels, overlapping none of the 24 pools’ planes, the nearest 52 pixels clear; and, for the cloud’s shade taken in linear light before encoding, 0.6076 in linear and 0.8645 with the tint’s numbers as linear. Section G, added the same day: the pipeline swatch, two grays stored 51 and 102 under a fixed tint of 0.25, which the three pipelines put at 13 and 26, 6 and 20, and 22 and 51, no two closer than 6 steps; and a build whose materials lift every tint halfway again, whose swatch reads 32 and 64, 13 steps from the nearest, and whose night, as stored, gives ground 0.794024, channels (0.750, 0.790, 0.950) and mask 0.893804. Section H, added after: the swatch’s place, x 24 to 56 and y 462 to 478 in the Pro Max view’s points, its farthest corner 196.7 points from the center; at the arrival, in view points, the collector’s frame x 177.3 to 262.7 and y 424.7 to 531.3, not overlapping it, the 7 reference windows 266.7 points or more from it and check 8’s 7 strips 144.0 or more. A computation from the shipped pictures, not a capture. ↩↩↩↩↩↩↩↩↩↩↩↩↩ -
pret,
pokecrystal/constants/ram_constants.asmat5beda23, read October 5, 2026:wTimeOfDayand its values MORN, DAY, NITE and DARKNESS (lines 142 to 147), https://github.com/pret/pokecrystal/blob/master/constants/ram_constants.asm. ↩ -
Blake Crosley, “Pixel-Art Worlds on iPhone: What the 16-Bit Masters Knew,” blakecrosley.com, October 3, 2026: the Gen 2 paragraph (“time of day in Johto is a 64-byte palette reload”; the eight named palettes), the lighting tier list (Moonlighter, Celeste, Eastward, HD-2D; HD-2D “costs more than you’d think”; players of Octopath Traveler II switch depth of field off), the RealityKit recipe, and the checklist’s “Dusk and night as one multiply tint over the world plus hand-drawn emissive variants for lamps, windows and the kiosk sign”, https://blakecrosley.com/blog/pixel-art-world-on-iphone. ↩↩↩↩↩↩↩
-
pret,
pokecrystal/data/maps/environment_colors.asmat5beda23, read October 5, 2026:EnvironmentColorsPointers(lines 1 to 12: TOWN and ROUTE to.OutdoorColors, INDOOR and GATE to.IndoorColors, CAVE and DUNGEON to.DungeonColors, ENVIRONMENT_5 to.Env5Colors), each table a row of eight palette indices per time of day;.OutdoorColors(15 to 19), whose WATER index is$28by morning and day and$29at night, the file’s overworld water pair, with the other seven the block’s own;.IndoorColors(21 to 25), the indoor block$20to$26with TEXT$07by morning and day, the night and dark blocks after;.DungeonColorsand.Env5Colors(27 to 37), the blocks as they are, https://github.com/pret/pokecrystal/blob/master/data/maps/environment_colors.asm. ↩↩ -
pret,
pokecrystal/engine/gfx/color.asmat5beda23, read October 5, 2026:LoadMapPals(from line 1197: firstfarcall LoadSpecialMapPaletteat 1198 andjr c, .got_palsat 1199, which skips the background copy when that routine set carry; otherwise eight background palettes of 8 bytes; from.got_palsat 1257 the eight object palettes, fromMapObjectPalsat 1261; and, aftercp TOWNat 1269, in a TOWN or ROUTE the map group’s roof colors fromRoofPalsat 1280, the night pair for NITE and DARKNESS bycp NITE_Fat 1284, 4 bytes intowBGPals1 palette PAL_BG_ROOF color 1at 1290 to 1291, that is ROOF’s colors 1 and 2; the eight background indices come fromEnvironmentColorsPointersat 1206, by environment and time of day), andpokecrystal/engine/tilesets/tileset_palettes.asm,LoadSpecialMapPalette(lines 1 to 53: carry set after loading a table of its own forTILESET_POKECOM_CENTER,TILESET_BATTLE_TOWER_INSIDE,TILESET_ICE_PATHunless the environment is INDOOR, the Hall of Fame,TILESET_HOUSE,TILESET_RADIO_TOWERandTILESET_MANSION;.do_nothingat 51 clears it), https://github.com/pret/pokecrystal/blob/master/engine/gfx/color.asm. ↩↩↩↩↩↩ -
pret,
pokecrystal/gfx/tilesets/johto_palette_map.asmat5beda23, read October 5, 2026: the palette of each of the Johto tileset’s 192 tiles, https://github.com/pret/pokecrystal/blob/master/gfx/tilesets/johto_palette_map.asm. ↩ -
pret,
pokecrystal/gfx/tilesets/roofs.palat5beda23, read October 5, 2026: 27 entries, a morning-and-day pair and a night pair per map group, https://github.com/pret/pokecrystal/blob/master/gfx/tilesets/roofs.pal. ↩ -
pret,
pokecrystal/engine/overworld/events.asmat5beda23, read October 5, 2026:HandleMapTimeAndJoypad(line 193), which returns whenwMapEventStatusisMAPEVENTS_OFF(lines 194 to 196) and otherwise callsUpdateTime,GetJoypadandTimeOfDayPals(lines 198 to 200), https://github.com/pret/pokecrystal/blob/master/engine/overworld/events.asm. ↩↩ -
pret,
pokecrystal/data/maps/setup_scripts.asmat5beda23, read October 5, 2026: the twelve map setup scripts (table at lines 4 to 15);MapSetupScript_Fly(line 29,mapsetup FadeOutToWhiteat 30, falling throughMapSetupScript_Warpat 32, whosemapsetup FadeInFromWhiteis at 52, two commands before its end);MapSetupScript_Door(102,mapsetup FadeOutToWhiteat 103, falling throughMapSetupScript_Trainat 104, whosemapsetup FadeInFromWhiteis at 121);MapSetupScript_Connection(81) has no fade, and no script namesFadeInFromBlackorFadeOutToBlack. Alsoengine/overworld/events.asmline 1005,WarpToNewMapScript’snewloadmap MAPSETUP_DOOR, the setup a warp the player steps on runs;engine/events/overworld.asmline 619,newloadmap MAPSETUP_FLY;engine/overworld/scripting.asmline 2074,MAPSETUP_WARPfor the script commandwarp;home/map.asm,FadeToMenu(1910,farcall FadeOutToWhiteat 1914),CloseSubmenu(1919) andExitAllMenus(1927), which callReloadTilesetAndPalettes(1966, whosefarcall UpdateTimeOfDayPalis at 1979) and end inFinishExitMenu(1933,farcall FadeInFromWhiteat 1938); the Start menu,engine/menus/start_menu.asm:StartMenu(line 13), with noFadecall of its own andcall UpdateTimePalsat 39, 44 and 92 (home/time_palettes.asmline 20,UpdateTimePals, calls_UpdateTimePals), its.Exit(80), and its six items thatcall FadeToMenu,StartMenu_Option(447),StartMenu_Status(455),StartMenu_Pokedex(466),StartMenu_Pokegear(475),StartMenu_Pack(482) andStartMenu_Pokemon(501), returning throughCloseSubmenu,ExitAllMenusor the menu’s.Clear(call FinishExitMenuat 155);FadeToMenuis called 21 times in 8 files outsidemobile/andhome/map.asm, 6 of them in the Start menu and the others inengine/events/specials.asm(8),engine/pokemon/mail.asm(2) and one each inengine/items/mart.asm,engine/events/pokecenter_pc.asm,engine/overworld/select_menu.asm,engine/events/move_tutor.asmandengine/events/battle_tower/battle_tower.asm, counted bygen2_daylight.txtsection E; and the eleven map scripts that call the black pair byspecial, each once out and once in: the three the body names,maps/SproutTower3F.asm(lines 55 and 59),Route24(29, 33) andTeamRocketBaseB2F(110, 118), and eight more, each listed with its two lines ingen2_daylight.txtsection E, https://github.com/pret/pokecrystal/blob/master/data/maps/setup_scripts.asm. ↩↩ -
pret,
pokecrystal/data/maps/maps.asmat5beda23, read October 5, 2026: themapmacro (lines 1 to 15, eight fields: the map, its tileset, environment, landmark, music, phone flag, palette mode and fishing group), the 26 map groups’ pointers (17 to 46) and the 388 map headers, each with an environment and a palette mode, among them the five Radio Tower floors (95 to 99), whose music field is the expressionRADIO_TOWER_MUSIC | MUSIC_GOLDENROD_CITY; among the 27PALETTE_NITEmaps,IlexForest(line 130) andTohjoFalls(line 161) are CAVE,BurnedTower1FDUNGEON (line 91),DragonShrineINDOOR (line 160) andUndergroundPathGATE (line 164), https://github.com/pret/pokecrystal/blob/master/data/maps/maps.asm. ↩↩ -
pret,
pokered/home/overworld.asmatd2704a6, read October 5, 2026: the warp code’s test forROCK_TUNNEL_1Fandld [wMapPalOffset], a(lines 497 to 500), https://github.com/pret/pokered/blob/master/home/overworld.asm. ↩↩↩ -
pret,
pokered/home/fade.asmatd2704a6, read October 5, 2026:LoadGBPalreading fromFadePal4less the offset, andFadePal2:: dc 3,3,3,2, 3,3,3,2, 3,3,2,0(line 66), https://github.com/pret/pokered/blob/master/home/fade.asm. ↩ -
pret,
pokeredatd2704a6, read October 5, 2026: every write ofwMapPalOffset.home/overworld.asmline 500 (the 6, on a warp from an outdoor map toROCK_TUNNEL_1F), line 535 (0, on the warp back outside; the indoor branch,.indoorMapsat line 508, does not write it) and line 790 (0, inHandleFlyWarpOrDungeonWarp);engine/menus/start_sub_menus.asmline 187 (0, Flash);engine/events/black_out.asmline 7 (0);engine/battle/init_battle_variables.asmline 18 (0 for the battle), restored from the valueengine/battle/core.asmpushed at line 6667 and pops at line 6768; anddata/maps/objects/RockTunnelB1F.asm, whose fourwarp_eventlines (15 to 18) all lead toROCK_TUNNEL_1F, https://github.com/pret/pokered. ↩ -
Blake Crosley, “A Pixel Town Alive When Nobody Is Online: NPCs and Ghosts,” blakecrosley.com, October 5, 2026: Emerald’s
UpdatePerDayand the functions it calls,UpdateWeatherPerDayamong them; and the brief’s room clock, in its section 7.1: “Every room runs on one clock: the server’s time in UTC, read in the room’s own time zone, one IANA zone per room in the server’s configuration”, sent inwelcomeand in the quiet plaza’s response as{"zone": "<IANA name>", "clock": <server ms>}, “and while the plaza is open the app takes its day-part from them rather than from the phone, so everyone in a room sees the same townspeople under the same light”, with “Outside the plaza the phone’s clock stays the collector’s“,Daylight.nowtoday readingCalendar.current, and its check (b), “a client test with the phone’s zone set to a different one from the room’s asserts that the plaza’s day-part follows the room’s clock”, https://blakecrosley.com/blog/pixel-art-town-alive-when-nobody-is-online. ↩↩↩↩↩↩↩↩↩ -
pret,
pokeemerald/include/constants/weather.hat731ad5b, read October 5, 2026: the 18WEATHER_*values fromWEATHER_NONE(line 4) toWEATHER_ROUTE123_CYCLE(line 21), with the comments marking SNOW, FOG_DIAGONAL and UNDERWATER unused and SHADE’s “Original name was closer to WEATHER_CLOUDY/OVERCAST”, and the 13COORD_EVENT_WEATHER_*values, https://github.com/pret/pokeemerald/blob/master/include/constants/weather.h. ↩ -
pret,
pokeemerald/src/trig.cat731ad5b, read October 5, 2026:gSineTable(line 5), https://github.com/pret/pokeemerald/blob/master/src/trig.c. ↩ -
W3C Web Accessibility Initiative, “Understanding Success Criterion 2.3.1: Three Flashes or Below Threshold,” WCAG 2.2 Understanding Docs, saved October 5, 2026 as
wcag-three-flashes.htmland.txtin the author’s evidence folder for this post (Level A; “Web pages do not contain anything that flashes more than three times in any one second period, or the flash is below the general flash and red flash thresholds”; “Flashing content can cause migraines, dizziness, nausea, and seizures”), https://www.w3.org/WAI/WCAG22/Understanding/three-flashes-or-below-threshold.html. ↩↩↩↩↩↩↩ -
pret,
pokeemerald/data/maps/*/map.jsonat731ad5b, read October 5, 2026 bymeasure_gen3_weather.py: 518 maps, each header’sweather,map_type,requires_flashandcoord_events, for example https://github.com/pret/pokeemerald/blob/master/data/maps/Route119/map.json. ↩ -
pret,
pokefirered/data/maps/*/map.jsonat037335f, read October 5, 2026 bymeasure_gen3_weather.py: 425 maps, https://github.com/pret/pokefirered/tree/master/data/maps. ↩ -
pret,
pokeemerald/src/field_screen_effect.cat731ad5b, read October 5, 2026:sFlashLevelToRadiusandgMaxFlashLevel(lines 53 and 54), and the flash effect centered atDISPLAY_WIDTH / 2, DISPLAY_HEIGHT / 2(line 980), https://github.com/pret/pokeemerald/blob/master/src/field_screen_effect.c. ↩ -
pret,
pokeemerald/src/overworld.cat731ad5b, read October 5, 2026:SetDefaultFlashLevel(lines 971 to 979: level 0 unlessgMapHeader.caveis set, 1 withFLAG_SYS_USE_FLASH, otherwisegMaxFlashLevel - 1), withinclude/global.fieldmap.hline 180, the header’su8 cave, whichtools/mapjson/mapjson.cppline 164 writes from each map’srequires_flash, https://github.com/pret/pokeemerald/blob/master/src/overworld.c. ↩ -
pret,
pokeemerald/graphics/field_effects/pics/at731ad5b(shadow_small.png,shadow_medium.png,shadow_large.png,shadow_extra_large.png), read October 5, 2026 bymeasure_shadows.py; not reproduced, https://github.com/pret/pokeemerald/tree/master/graphics/field_effects/pics. ↩ -
Blake Crosley, “Pixel-Art Ledges, Stairs and Bridges in a Tile World,” blakecrosley.com, October 5, 2026: its section 3, “The shadow stays on the ground”, the hop’s shadow kept on the ground in Red, Crystal and Emerald; and its quotation of Sabotage Studio’s Thierry Boulanger from Screen Rant’s interview of August 29, 2023, levels that had “to all read well with different levels of height, with the light and everything” (https://screenrant.com/sea-stars-interview-thierry-boulanger/), https://blakecrosley.com/blog/pixel-art-elevation-ledges-bridges. ↩↩↩
-
Stardew Valley Wiki, “Modding:Maps,” saved October 5, 2026 as
sdv-modding-maps.htmland.txtin the author’s evidence folder for this post (the Lighting propertiesAmbientLight,Lightwith its nine light types,WindowLight; andNightTiles, “Changes the tile after 7pm”, “mostly used for lamps in the game”), https://stardewvalleywiki.com/Modding:Maps. ↩↩↩↩↩↩ -
80.lv, “Moonlighter: Building Pixel Art & Preparing for Switch,” interview with Digital Sun, saved October 5, 2026 as
moonlighter-80lv.htmland.txtin the author’s evidence folder for this post, https://80.lv/articles/moonlighter-building-pixel-art-preparing-for-switch. ↩ -
Noel Berry, “Remaking Celeste’s Lighting,” September 25, 2017, saved October 5, 2026 as
celeste-lighting.htmland.txtin the author’s evidence folder for this post, https://noelberry.ca/posts/celeste_lighting/. ↩↩ -
Game Developer, “Eastward’s creators share insights on making pixel art adventures,” interview with Tommo Zhou of Pixpil, October 11, 2021, saved October 5, 2026 as
eastward-gamedev.htmland.txtin the author’s evidence folder for this post, https://www.gamedeveloper.com/art/eastward-s-creators-share-insights-on-making-pixel-art-adventures. ↩↩ -
Nintendo Everything, translation of the Nintendo Dream interview with Octopath Traveler’s developers, November 3, 2018, saved October 5, 2026 as
octopath-ne.htmland.txtin the author’s evidence folder for this post (Shizuka Morimoto and Tomoya Asano on lighting), https://nintendoeverything.com/octopath-traveler-devs-on-character-origins-visual-style-initial-hd-rumble-plans-much-more/. Unreal Engine’s two Octopath Traveler pages returned HTTP 403 to the fetcher and to curl (octopath-ue.htmlis the 403 page). ↩↩ -
Wikipedia, “HD-2D,” saved October 5, 2026 as
hd2d-wiki.htmland.txtin the author’s evidence folder for this post; a secondary source (“Typically, a point light source is placed into the scene to make characters and objects cast shadows”), https://en.wikipedia.org/wiki/HD-2D. ↩ -
Alana Hagues, “Triangle Strategy Producers Talk HD-2D And Why Other Devs Haven’t Used it,” Nintendo Life, May 30, 2022, reporting Tomoya Asano and Yasuaki Arai’s interview with 4Gamer, with the standfirst “It costs more than you’d think,” saved October 5, 2026 as
triangle-nlife.htmland.txtin the author’s evidence folder for this post, https://www.nintendolife.com/news/2022/05/triangle-strategy-producers-talk-hd-2d-and-why-other-devs-havent-used-it. ↩↩ -
Sabotage Studio, Sea of Stars press kit, saved October 5, 2026 as
sos-presskit.htmland.txtin the author’s evidence folder for this post, https://sabotagestudio.com/presskits/sea-of-stars/. ↩ -
TechRaptor, “Sea of Stars Gameplay Clip Shows Day & Night Cycle,” saved October 5, 2026 as
sos-techraptor.htmland.txtin the author’s evidence folder for this post, https://techraptor.net/gaming/news/sea-of-stars-gameplay-clip-shows-day-night-cycle. ↩ -
Raymond Schlitter, “Pixelblog 21: Top Down Objects,” SLYNYRD, saved October 5, 2026 as
slynyrd-objects.htmland.txtin the author’s evidence folder for this post, https://www.slynyrd.com/blog/2019/9/18/pixelblog-21-top-down-objects. ↩↩↩ -
Raymond Schlitter, “Pixelblog 43: Top Down Tiles Part 2,” SLYNYRD, saved October 5, 2026 as
slynyrd-tiles2.htmland.txtin the author’s evidence folder for this post, https://www.slynyrd.com/blog/2023/3/26/pixelblog-43-top-down-tiles-part-2. ↩↩↩↩↩↩↩↩ -
Raymond Schlitter, “Pixelblog 44: Top Down Trees,” SLYNYRD, saved October 5, 2026 as
slynyrd-trees.htmland.txtin the author’s evidence folder for this post, https://www.slynyrd.com/blog/2023/5/22/pixelblog-44-top-down-trees. ↩ -
Shmuplations, translated interview on Seiken Densetsu 3 with Koichi Ishii, saved October 5, 2026 as
sd3-shmuplations.htmland.txtin the author’s evidence folder for this post, https://shmuplations.com/seikendensetsu3/. ↩↩↩↩ -
Kiradex source,
Kiradex/World/Glow.metal, dated October 3, 2026 and read October 5 at 16:51 at commitcf3beb1e, from a working tree being edited that day (private repository):glowSurface, which sets the picture’s color times the tint as both base and emissive color with opacity 1, under the comment “Light that adds, as light does” and its line “black adds nothing, and a pool of light never darkens what it lies on, which an alpha halo did.” (lines 6 to 10). ↩↩↩↩↩↩ -
Kiradex forge,
scripts/forge/air.py, dated October 3, 2026 and read October 5 at 16:51 at commitcf3beb1e(private repository): the docstring’s pictures,glow.png(“a radial pool, white at the centre falling to black”) andcloud.png(“a tileable mask of cloud shadow: alpha where a cloud passes”), lines 9 to 13. ↩↩ -
Kiradex tests,
KiradexUITests/KiradexUITests.swift, read October 5, 2026 at 16:51 at commitcf3beb1e, from a working tree being edited that day (private repository):testThePlazaKeepsTime(line 502), launched with-plazaHour 21and-airProbe(503), andtestTheChimneysSmoke(523), launched with-plazaHour 12(524) and waiting for the air’s report to containsmoke 18(lines 531 and 532), then tapping the plaza at normalized (0.38, 0.3) (538) under the comment that the walk goes “Up the grand stair onto the terrace, left of the fountain” (lines 534 to 537); the helperswait(for:toRead:)(line 1171),launch(line 1179, which setslaunchArgumentsbeforeapp.launch()) andsave(line 1212, a screenshot of the screen). The file is dated October 5, 2026, 10:32, and the line numbers are that copy’s. ↩↩↩↩↩↩↩ -
Kiradex source,
Kiradex/World/HallView.swift, read October 5, 2026 at 16:51 at commitcf3beb1e, from a working tree being edited that day (private repository; the file is dated 14:59 that day): the comment that opens the file, “The show hall: off the plaza” (line 5), and that the hall “is not networked”, the plaza leaving the room on the way in (9 to 11); “The show is entered through the veil.” (34); and thePlazaStageit builds (57), withclient: nil(58). The file was the same bytes at 18:07, when the commit was66c116b0. ↩↩↩↩ -
Kiradex design plan,
docs/research/design/01-art-direction-v2.md, dated October 3, 2026 and read October 5 at 16:51 at commitcf3beb1e(private repository): section 4, “The plan, by build,” whose build 29 lists light pools, cloud shadows, mist, leaves and fireflies, and “a vignette and tilt-shift depth of field as the camera’s rendering effects”. ↩↩ -
Apple Developer Documentation,
UnlitMaterial(RealityKit), page JSON saved October 5, 2026 asapple-unlitmaterial.htmlin the author’s evidence folder for this post: “A material that doesn’t respond to lights in the scene”; iOS 13.0+, macOS 10.15+; and, in its topic list,textureCoordinateTransform, “A two-dimensional transformation to apply to the entity’s primary texture coordinates”, with no statement of the order of offset and scale; andinit(applyPostProcessToneMap:), “Creates an UnlitMaterial with the given tone mapping setting”, with no statement of what the plain initializer does about tone mapping, https://developer.apple.com/documentation/realitykit/unlitmaterial. ↩↩↩ -
Kiradex source,
Kiradex/World/SpriteSheet.swift, dated October 3, 2026 and read October 5 at 16:51 at commitcf3beb1e(private repository; the line numbers are that copy’s): the frame material’stextureCoordinateTransformwith offset (column ÷ columns, row ÷ rows) and scale (1 ÷ columns, 1 ÷ rows) (line 65), under a comment recording that the offset is measured from the texture’s top left, “measured on the simulator”; the sheet’s texture made withsemantic: .color(37); andmaterial(light:), which builds a plainUnlitMaterial()(75 and 76). AlsoAir.swiftline 223, the smoke frames, set the same way. ↩↩ -
Kiradex source,
Kiradex/World/Motion.swift, read October 5, 2026 at 17:18 at commitcf3beb1e, from a working tree being edited that day (private repository; the file is dated 12:36 that day, the same bytes at 18:07, when the commit was66c116b0): its header, which names the motion post’s brief as its source (lines 3 to 6);TickClock, which hands the world whole sixtieths of a second whatever the display’s rate,ticksPerSecond = 60(14 to 52, the constant at 17);Stride.pixels, 16 (57), andStride.ticks(diagonal:running:)(67 to 74): 16 ticks for a straight walking step, 8 running, 23 and 12 on a diagonal; andStepping.tick()(215), which ends a step when its count of ticks reaches the stride’s.measure_kiradex_proposals.pyandarith.pyread the three numbers from this copy. ↩↩↩↩ -
Kiradex forge,
scripts/forge/town.py, dated October 3, 2026 and read October 5 at 16:51 at commitcf3beb1e(private repository; the line numbers are that copy’s): the legend’s “a lamp post on paving (as l, with its shadow in the stone’s shade)” (line 29); inpaint(from line 403), the wall’s shadow, “the top four rows of whatever stands at its foot”, one step darker, “the way the light falls at Leene.” (lines 426 to 436). ↩↩↩↩ -
Kiradex source,
Kiradex/World/FigurePreview.swift, dated October 4, 2026 and read October 5 at 16:51 at commitcf3beb1e(private repository):@Environment(\.accessibilityReduceMotion)at line 25, the only read of the setting inKiradex/World/. ↩ -
Kiradex source,
Kiradex/World/PlazaClient.swift, dated October 3, 2026 and read October 5 at 17:42 at commit5a344e3f, from a working tree being edited that day (private repository):handle(_:)’scase "welcome":(line 214), which takesyou,room,linesandplayersfrom the server’swelcomeand nothing else;endpoint(lines 87 to 91), which reads-plazaServerfrom the launch arguments, “points a run at a local” server in its comment’s examplews://127.0.0.1:8300/v1/plaza, and otherwise uses the productionwss://endpoint; nozoneorclockis read anywhere in the file. Read again at 18:19, at commit66c116b0: the same bytes. ↩↩↩ -
Blake Crosley, “Pixel-Art Motion: Walks, Cameras and Doors on iPhone,” blakecrosley.com, October 5, 2026: the brief’s item 1, “The walk is 1 pixel a tick, 16 ticks a tile; the run is 2 pixels a tick, 8 ticks”, on a 60-hertz tick, which is 60 pixels a second for the walk; the app runs that tick and stride today, in
Motion.swift(section 7), https://blakecrosley.com/blog/pixel-art-motion-on-iphone. ↩↩ -
Apple Developer Documentation,
MaterialParameterTypes.BlendMode(RealityKit), page JSON saved October 5, 2026 asapple-mpt-blendmode.htmlin the author’s evidence folder for this post: “Modes that describe how materials should be blended with content behind them”; enumeration casesaddandalpha; iOS 18.0+, iPadOS 18.0+, Mac Catalyst 18.0+, macOS 15.0+, tvOS 26.0+, visionOS 2.0+, https://developer.apple.com/documentation/realitykit/materialparametertypes/blendmode. ↩↩↩↩↩↩ -
Apple Developer Documentation,
MaterialParameterTypes.BlendMode.add, page JSON saved October 5, 2026 asapple-blendmode-add.htmlin the author’s evidence folder for this post: “Blend by adding the material’s color to the background color, ignoring alpha”; “also known as additive blending or linear dodge”; the same availability, https://developer.apple.com/documentation/realitykit/materialparametertypes/blendmode/add. ↩↩ -
Apple Developer Documentation,
CustomMaterial(RealityKit), page JSON saved October 5, 2026 asapple-custommaterial.htmlin the author’s evidence folder for this post: “A material that works with custom Metal shader functions”; iOS 15.0+, iPadOS 15.0+, Mac Catalyst 15.0+, macOS 12.0+, tvOS 26.0+, https://developer.apple.com/documentation/realitykit/custommaterial. ↩ -
Apple Developer Documentation,
TimelineView(SwiftUI), page JSON saved October 5, 2026 asapple-timelineview.htmlin the author’s evidence folder for this post: “A view that updates according to a schedule that you provide”; iOS 15.0+, https://developer.apple.com/documentation/swiftui/timelineview. ↩ -
Apple Developer Documentation,
TimelineSchedule.everyMinute(SwiftUI), page JSON saved October 5, 2026 asapple-everyminute.htmlin the author’s evidence folder for this post: “A schedule for updating a timeline view at the start of every minute”; iOS 15.0+, https://developer.apple.com/documentation/swiftui/timelineschedule/everyminute. ↩ -
Apple Developer Documentation,
UIApplication.significantTimeChangeNotification(UIKit), page JSON saved October 5, 2026 asapple-significanttime.htmlin the author’s evidence folder for this post: posted “when, for example, there’s a change to a new day (midnight), a carrier time update, or a change to, or from, daylight savings time”; the page lists iOS, iPadOS, Mac Catalyst, tvOS and visionOS without a version, https://developer.apple.com/documentation/uikit/uiapplication/significanttimechangenotification. ↩↩↩↩↩↩ -
Apple Developer Documentation,
NSNotification.Name.NSSystemTimeZoneDidChange(Foundation), page JSON saved October 5, 2026 asapple-systemtimezonechange.htmlin the author’s evidence folder for this post: “A notification posted when the time zone changes”; iOS 2.0+, https://developer.apple.com/documentation/foundation/nsnotification/name-swift.struct/nssystemtimezonedidchange. ↩↩↩ -
Apple Developer Documentation,
EnvironmentValues.timeZone(SwiftUI), page JSON saved October 5, 2026 asapple-timezone.htmlin the author’s evidence folder for this post: “The current time zone that views should use when handling dates”; iOS 13.0+, https://developer.apple.com/documentation/swiftui/environmentvalues/timezone. ↩ -
Apple Developer Documentation,
EnvironmentValues.accessibilityReduceMotion(SwiftUI), page JSON saved October 5, 2026 asapple-reducemotion.htmlin the author’s evidence folder for this post: “Whether the system preference for Reduce Motion is enabled”; “If this property’s value is true, UI should avoid large animations, especially those that simulate the third dimension”; declaredvar accessibilityReduceMotion: Bool { get }; iOS 13.0+, https://developer.apple.com/documentation/swiftui/environmentvalues/accessibilityreducemotion. ↩↩↩↩ -
Apple Developer Documentation, WeatherKit, page JSON saved October 5, 2026 as
apple-weatherkit.htmlin the author’s evidence folder for this post: “Deliver weather conditions and alerts to your users”; iOS 16.0+, https://developer.apple.com/documentation/weatherkit. ↩↩↩ -
Kiradex source,
Kiradex/World/CollectorComposer.swift, read October 5, 2026 at 18:07 at commit66c116b0, from a working tree being edited that day (private repository; the file is dated October 4, 07:40), withKiradex/Resources/World/characters.json(dated October 4, 07:20, the same bytes as at the 16:51 reading) and the 123 figure sheets,fig-*.png, copied at the same time: the header, every sheet “an 8-bit plane whose value is an index × 2 (0 clear, 63 the eraser” (lines 5 to 14);sparse(_:), a plane’s opaque pixels, those neither clear nor the eraser (66);layers(for:), which always adds the body, the eyes and the held card (131); andindexPlane(for:), the layers laid in the manifest’s order for each facing, the last opaque index winning (153). ↩↩ -
Kiradex repository, read October 15, 2026 at 23:53 without running git:
.git/refs/heads/mainread as a file,fc9d4876(committed October 15 at 17:45), and each file below taken from that commit’s objects with zlib and compared byte for byte with the working tree’s copy, the same in every case (private repository; the line numbers are that commit’s):Kiradex/World/PlazaClient.swift,roomClockandroomZone(79 to 83),roomDate, “The room’s time now, from the welcome’s clock” (109 to 113), and thewelcomehandler readingjson["clock"]andjson["zone"](265 to 267);server/app/main.py, thewelcomethat carries"zone"and"clock"(21, 22 and 228) andZONE, “one for the whole world until rooms are sharded by region”,UTCunlessPLAZA_ZONEis set (77 to 79);Kiradex/World/PlazaStage.swift, theclock“re-read each minute: the light follows it” (64 and 65),relight()reading it (86), and “A quiet plaza joins no room” (919);scripts/forge/protected_names.txt, “Names the world never uses”, andserver/tests/test_names.py, “The deny list: no franchise word and no canon place in anything the world names”. A search of the working tree’sKiradex/andKiradexTests/forsignificantTimeChangeandNSSystemTimeZoneDidChangefound nothing. The reader script and the copies are in the author’s evidence folder for this post. ↩↩↩ -
Kiradex source, read October 5, 2026 at 16:51 at commit
cf3beb1e, from a working tree being edited that day (private repository):Kiradex/World/QuestBoardView.swift(dated October 2), the quest board, a view of its own, presented as a sheet fromPlazaViewinPlazaStage.swift(line 690 of the file dated 17:22, read at 17:42 at commit5a344e3f) and fromKiradex/Views/Me/MeView.swift(line 65 of the file dated 07:52). ↩ -
Blake Crosley, “Pixel-Art People: Characters and a Creator on iPhone,” blakecrosley.com, October 3, 2026: on an iPhone 18 Pro Max, “a 440-by-956-point view at 3× shows 165 by 358 world pixels”, https://blakecrosley.com/blog/pixel-art-characters-on-iphone. ↩
-
Kiradex source, read October 5, 2026 at 16:51 at commit
cf3beb1e, from a working tree being edited that day (private repository):Kiradex/Views/Me/MeView.swift(dated 07:52 that day), the Me tab’s “Your Room” button,me-room(lines 272 to 277), which opens the player’s own room without the plaza’s server; and the forge’sscripts/forge/interiors.py(dated October 3), whoseROOMgrid (lines 57 to 70) places a curtained window,K, at (3, 1) and a window,N, at (12, 1) of the ground floor, drawn bywindow(line 259) andcurtained_window(line 278). The exportedroom.jsonandroom-up.jsoncarry no list of window cells. ↩↩ -
Blake Crosley, “Pixel-Art Routes: Map Connections and Districts on iPhone,” blakecrosley.com, October 5, 2026: its brief’s names and art, “The deny list does not exist yet: the dossier promised one in the repository, and the repository has none”, with building the list and its test made part of that brief, https://blakecrosley.com/blog/pixel-art-world-beyond-the-plaza. ↩