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

A 24-hour chart with three bands. Crystal: morning from 04:00 to 10:00, day from 10:00 to 18:00, night from 18:00 to 04:00. Kiradex: morning from 06:00 to 09:00, day from 09:00 to 17:00, evening from 17:00 to 20:00 and night from 20:00 to 06:00, with the lamps lit from 17:00 to 06:00, 13 hours. Stardew Valley: the day from 06:00 to 02:00, 14 real minutes long, with nightfall marked at 18:00 in winter, 19:00 in fall and 20:00 in spring and summer.

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

Grouped bars of mean luma for each of Crystal's eight background palettes, the base blocks of its palette file, at morning, day, night and dark; for the water a town displays, the file's overworld pair; and for Kiradex's town atlas under its morning, day and night tints. In the base blocks every world palette but YELLOW falls to between 0.206 and 0.321 at night and to 0.009 in the dark; the town's water is 0.528 by day and 0.248 at night; YELLOW falls to 0.460 at night and 0.224 in the dark; TEXT stays at 0.549 at every time. Kiradex's atlas is 0.432, 0.449 and 0.264.

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.

Horizontal bars of luma at night. Crystal: the lightest night color shared by GRAY, RED, GREEN, BROWN and ROOF, 0.498; WATER's lightest night color as a town displays it, 0.490; YELLOW's lightest color, 0.898. Kiradex: the atlas mean by day, 0.449; under the night tint, 0.264; the brightest atlas pixel under the night tint, 0.562; a lamp pool's center added to the night mean, weighted by the picture's peak, 0.785; the lit window and sconce pixels, drawn untinted, 0.901.

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 targetColorMapIndex 3 and a colorMapStepDelay of 20 frames in its InitVars.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) / 16 per 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

A chart of output against input for a 5-bit channel, 0 to 31. A dashed identity line. The rain map, map 3, runs below it, about 0.81 of the input: 31 to 25, 16 to 13, 8 to 6. The lightning map, map 19, follows the identity for inputs under 12 (0 to 1, 8 to 8, 11 to 10) and jumps to 31 for every input of 12 and above.

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 / 256 pixels a frame, the index stepping every 6 frames through 0x20 to 0x5F. Replayed over 6,000 frames against the sine table in src/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

Horizontal bars of how fast each layer of air moves as a share of its game's walking speed, with the walk at 1. Emerald: the sandstorm at 3.60, the clouds at 0.50, the horizontal fog at 0.25. Kiradex, against today's 60-pixel-a-second walk (16 pixels in 16 ticks of a 60-a-second clock): the cloud sheet as proposed, 16 pixels a second, at 0.27, and the cloud sheet today at 0.027.

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_CHANGE jumps to THUNDER_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, or EnqueueThunder(100) as the long bolt strikes. EnqueueThunder sets a timer to Random() % waitFrames, and UpdateThunderSound, called at the top of each frame’s Thunderstorm_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, UpdateThunderSound returns 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 timeline of an Emerald thunderstorm cycle replayed from its state machine with every timer at its minimum, for a cycle after the first, drawn to scale in frames: a wait of 361 frames from the cycle's start, a short bolt lit 6 frames, 61 dark frames, a second short bolt of 6, 61 dark frames, and a long bolt lit 30 frames before its fade begins. One short bolt alone is also allowed, so this is the longest pattern at its tightest, not the shortest cycle. A ruler under the first gap marks one second, 59.73 frames, which ends before the second bolt begins.

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

Two stacked bars of the header weather of every outdoor map. Emerald, 68 maps: sunny 51, none 13, shade 2, horizontal fog 1, volcanic ash 1. FireRed, 76 maps: sunny 66, none 8, shade 2. Neither game has an outdoor map whose header is rain. A note: Emerald's rain comes from triggers; of 86 coordinate weather events on 5 maps, 4 are RAIN and 24 the Route 119 and 123 cycles, and of 24 scripted setweather calls, 1 is RAIN and 2 DOWNPOUR; FireRed has no coordinate weather event.

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

A schematic of the 240 by 160 pixel screen on a 16-pixel grid, dark, with two circles centered on it: an inner one of radius 24 pixels, 1.5 cells, the cave without Flash; an outer one of radius 72 pixels, 4.5 cells, with Flash. The radii table, 200, 72, 64, 56, 48, 40, 32, 24 and 0 pixels, is printed beside it, with the shares of the screen, 4.7% and 42.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.53
  • Light places one of nine light types: lantern, window, sconce, cauldron, indoor window, projector, fish tank, winter town tree lights and pinpoint.53
  • WindowLight adds lights “only lit during the day when it is not raining”, the light that comes in through a window from outside.53
  • NightTiles “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.swift picks 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.png is “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.py when 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 paint never 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 to Calendar.current as Daylight.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’s Daylight.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 as zone and clock in welcome and 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 the LightClock, PlazaClient on welcome and 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 its now is 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, and Daylight.light(at:calendar:) and Weather.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 first welcome or 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); a LightClock keeps the last response’s zone and stamp until the next one arrives, and its state names which it runs on: the room’s clock by welcome, the room’s clock by the quiet response, or the phone’s. On October 5 the server’s welcome carried no zone or clock and PlazaClient’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, the LightClock runs on the phone’s clock and says so in its state.432176
  • Change at the boundaries, not a minute late. LightClock, in a new Kiradex/World/LightClock.swift, is given a time source (the monotonic uptime and the wall clock, both replaceable), a NotificationCenter and 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 observes UIApplication.significantTimeChangeNotification and NSSystemTimeZoneDidChange on 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. PlazaStage owns one, built with the system’s time source and NotificationCenter.default, in place of its clock state 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.forcedHour reads -plazaHour from 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

A line chart of the town atlas's mean luma over 24 hours. Today, a dashed step line: 0.264 through the night, a cut to 0.432 at 06:00, to 0.449 at 09:00, down to 0.372 at 17:00 and to 0.264 at 20:00. The proposal, a solid line that follows the same levels but slopes over the 30 minutes after each boundary instead of stepping.

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) / 2 per 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 new Kiradex/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, and walkers, the day’s light lifted halfway to white, which takes no weather factor. PlazaRig owns one in place of its single light. The place materials take place: the ground, the surroundings and the objects as build(tiles:collector:arrival:) first makes them, in light(_:lampsOn:) and in the ripple’s frame step, and the door’s drawing in showDoor(at:sheet:column:). The walkers take walkers: an arrival’s material in add(other:figure:tile:facing:) and each frame’s in animate(_:).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 DirectionalLight for 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, from PlazaRig.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 emotes PlazaRig.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 .task writes 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’s shown (the facing’s row times the sheet’s 43 columns plus the drawing’s column, so 0 is idle_a facing down), rewritten whenever it changes, and the view frames of the lit cells wholly in view, by their cells in town.json. A debug argument, -plazaHoldStill (my naming), read from the launch arguments and fixed for the launch as -plazaHour is, holds a standing collector on idle_a: no breath, no blink and no idle emote, which animate(_:) 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 argument Air.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 -airProbe the stage draws check 3’s pipeline swatch and reports its frame.2230 All of these are proposed additions, for check 3.

Swatches of Kiradex's own four lights. Top row, the place: morning (1, 0.96, 0.86), luma 0.961; day (1, 1, 1), 1.000; evening (1, 0.78, 0.6), 0.825; night (0.5, 0.58, 0.9), 0.593. Bottom row, the walkers as proposed, each lifted halfway to white: morning luma 0.980; day 1.000; evening (1, 0.89, 0.8), 0.913; night (0.75, 0.79, 0.95), 0.796.

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 -airProbe the 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 to SpriteSheet.material(light:) directly and never through PlaceLight or 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_b to 0.3161 in think, a correct build captured in think by day and in doze_b at 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 22 with a proposed debug argument, -plazaNoTint, fixed for the launch as -plazaHour is, under which PlaceLight.place returns 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 ignored PlaceLight would 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 22 with another proposed debug argument, -plazaLiftAgain (my naming), under which SpriteSheet.material(light:) lifts every tint it is given halfway to white again, must fail too. That is a build whose PlaceLight gives 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 PlaceLight adds the exact part: at the night tint, in summer and clear weather, walkers is (0.75, 0.79, 0.95) and place the 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, and snow in winter only. Morning mist, autumn leaves and night fireflies stay what they are, layered on top. Weather is outdoors only: where Air finds no sky (its outdoors), 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 LightClock schedules 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.swift for 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.swift for rain, splash, snow and overcast (item 4); the forge’s air.py for rain.png, splash.png and snow.png, in cells of 8 by 8 to 8 by 16; PlazaStage.swift, whose relight() reads the state and passes its factor to the rig and the state to Air, and whose PlazaView.words adds the state’s words to the status line; and QuestBoardView.swift, which the plaza view (PlazaView) and the Me tab present as a sheet, for the board’s line.129019 Daylight.swift takes no weather: its light stays a function of the time alone, and the weather’s factor is applied in PlaceLight. The dossier put the weather factor in Daylight and the board line in PlazaStage.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. -plazaWeather with a state name forces the day’s state for one launch, read from the launch arguments and fixed for the launch as -plazaHour is; the checks below use it. This is my addition.

Four stacked bars, one per season, of a year of the proposed weather roll for one zone in 2027. Spring, 92 days: clear 53, overcast 22, rain 17. Summer, 92: clear 61, overcast 12, rain 13, heavy rain 6. Autumn, 91: clear 40, overcast 30, rain 17, heavy rain 4. Winter, 90: clear 36, overcast 29, snow 25.

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. Air gains a RainField, 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, and Air.set(phase:season:weather:reduceMotion:) replaces today’s set(phase:season:) to turn the drops, the flakes and the overcast sheet on and off. The drops are Air’s, and nothing in Air takes 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. Air also keeps the drop count in its report, and under -airProbe the stage copies the report into its value once a second instead of once, in the .task that prepares the air, until the player’s first step; from then on rig.onStep writes 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 where place has changed it assigns the three place materials, as light(_:lampsOn:) does once a relight, and the door’s while one is shown. The stage’s relight() 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 through PlaceLight as rain’s does; the sheet’s opacity is set by Air.set(phase:season:weather:reduceMotion:) at once, because the state changes only at midnight, when the sheet is hidden (Air.set hides the cloud at night).22
  • Snow. Winter only, drawn by the forge: slow flakes with the leaves’ sway, kept by Air as 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 RainField holds 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:) in Weather.swift gives the day’s velocity in pixels a second; Air.update adds it, over 384, to the cloud’s offset in place of today’s fixed SIMD2(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’s paint, 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

A schematic of Kiradex's 40 by 30 town from its exported letter grid, buildings in purple, blocked cells and props in gray, the pond in blue, walkable ground plain. The 33 cells that take the shadow, one per building row where the cell east of that row's last building cell is ground, are outlined in orange, each with a solid orange strip over the 4 of its 16 pixel columns nearest the building; every strip touches its own wall, and the rows whose east cell holds a prop have none.

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.png and 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 Daylight or Weather equals a pokecrystal palette 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 (in Daylight.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


  1. Author’s measurement, October 5, 2026: measure_gen2_daylight.py in the author’s evidence folder for this post, output gen2_daylight.txt (and gen2_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.asm and the TimesOfDay table in engine/rtc/rtc.asm); B, the background palettes by time in gfx/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, and LoadMapPals’s roof copy from gfx/tilesets/roofs.pal per 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 in gfx/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, _UpdateTimePals and the four fades in engine/tilesets/timeofday_pals.asm, the overworld loop, a census of what calls each fade (each map setup script’s fades in data/maps/setup_scripts.asm, following a script without db -1 into the next; which setup constant a stepped-on warp, Fly and the script command warp load with; the menu fades in home/map.asm; every special, farcall or mapsetup of a fade in any .asm outside timeofday_pals.asm, counted by file, with the black pair’s eleven map scripts and their line numbers), the six tilesets whose background palettes LoadSpecialMapPalette takes over from LoadMapPals, counted by environment from the map headers, and the maps whose palettes it does load (the Hall of Fame, an INDOOR map on TILESET_ICE_PATH, excepted: 74 INDOOR maps, every one PALETTE_DAY, 57 of them on TILESET_HOUSE, and 5 CAVE), the Start menu’s own lines (StartMenu, its .Exit, its UpdateTimePals calls, which of its items call FadeToMenu) and every call FadeToMenu outside mobile/ and home/map.asm, by file; F, the 388 map headers of data/maps/maps.asm by environment and palette mode, each map directive 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: pret pokecrystal at 5beda23. 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. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  2. pret, pokecrystal/constants/misc_constants.asm at 5beda23 (MORN_HOUR EQU 4, DAY_HOUR EQU 10, NITE_HOUR EQU 18, lines 36 to 38) and pokecrystal/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. ↩↩↩↩↩

  3. pret, pokecrystal/engine/tilesets/timeofday_pals.asm at 5beda23, read October 5, 2026: _TimeOfDayPals (line 13), _UpdateTimePals (line 109, ld c, $9 ; normal, then GetTimePalFade and DmgToCgbTimePals), FadeInFromWhite (115), FadeOutToWhite (122), FadeInFromBlack (146), FadeOutToBlack (153), and ReplaceTimeOfDayPals (189, testing STATUSFLAGS_FLASH_F at line 206), https://github.com/pret/pokecrystal/blob/master/engine/tilesets/timeofday_pals.asm. ↩↩↩↩↩

  4. Author’s arithmetic from the measured values in the text: arith.py in the author’s working folder for this post, output arith.txt, reading the outputs in the author’s evidence folder for this post and, read only, Kiradex’s exported town.png and glow.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 from gen2_daylight.txt section 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, from gen3_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 from Motion.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 saved MaterialParameterTypes.BlendMode page, read from its topic list, which parse_apple.py does 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’s Motion.swift rather than carry 64 pixels a second; the earlier scripts and outputs are kept beside it. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  5. Author’s measurement, October 5, 2026: measure_gen3_weather.py in the author’s evidence folder for this post, output gen3_weather.txt. Sections: A, the constants of include/constants/weather.h; B, each weather’s InitVars and blend targets in src/field_weather_effect.c; C, BuildColorMaps in src/field_weather.c replayed in Python, sBasePaletteColorMapTypes, and the palette slots of include/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, and Rain_InitAll’s loop within one frame); E, cloud, fog and sandstorm drift (the sandstorm replayed 6,000 frames over gSineTable in src/trig.c); F, thunder timings; G, the header weather of every data/maps/*/map.json in Emerald and FireRed by map type, coordinate weather events, the two route cycles, scripted setweather calls in data/**/*.inc, and requires_flash maps; H, sFlashLevelToRadius and the default flash level; I, a case-insensitive name search for “timeofday” and “time_of_day” in src/*.c and include/**/*.h. Inputs: pret pokeemerald at 731ad5b and pokefirered at 037335f. Re-run before publishing: every section prints as before except the last lines of D, whose label had printed rainSpriteVisibleDelay itself 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. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  6. pret, pokeemerald/src/field_weather.c at 731ad5b, read October 5, 2026: sWeatherFuncs (line 85, each weather’s initVars, main, initAll and finish), sBasePaletteColorMapTypes[32] (line 114), StartWeather (154), SetNextWeather (183), Task_WeatherInit (216, calling the weather’s initAll at 222 once the screen is ready), Task_WeatherMain (227: while the current and next weather differ, the old weather’s finish() each frame until it returns false, at 231, then the new weather’s initVars() at 235, colorMapStepCounter = 0 at 236 and WEATHER_PAL_STATE_CHANGING_WEATHER at 237; otherwise the weather’s main() at 244; the palette state function after either, at 247), BuildColorMaps (271), UpdateWeatherColorMap (344, stepping the color map when ++colorMapStepCounter >= colorMapStepDelay at 354) and ApplyColorMap (459); and include/constants/field_weather.h line 4, MAX_RAIN_SPRITES 24, https://github.com/pret/pokeemerald/blob/master/src/field_weather.c. ↩↩↩↩↩↩

  7. pret, pokeemerald/src/field_weather_effect.c at 731ad5b, 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 until weatherGfxLoaded), Rain_Main (491: its three states, the sheet at 496, CreateRainSprite at 500 until it returns false, UpdateVisibleRainSprites at 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) with UpdateThunderSound() first (1094), its setup states (1097 to 1111) and its THUNDER_STATE_WAIT_CHANGE case (1112), which jumps to THUNDER_STATE_INIT_CYCLE_1 (1114) past the wait, THUNDER_STATE_NEW_CYCLE (1116, the timer (Random() % 360) + 360 at 1118 falling through into THUNDER_STATE_NEW_CYCLE_WAIT at 1121, whose --thunderTimer == 0 is at 1123), THUNDER_STATE_INIT_CYCLE_1 and _2 (1126 and 1131, falling into THUNDER_STATE_SHORT_BOLT at 1135, the strike at 1137 and the hold (Random() % 3) + 6 at 1142), THUNDER_STATE_TRY_NEW_BOLT (1145, the bolt’s end at 1149 and the gap timer (Random() % 16) + 60 at 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) + 30 at 1183), THUNDER_STATE_FADE_BOLT_LONG (1187, the gradual fade at 1191), EnqueueThunder (1242, the timer Random() % waitFrames at 1246) and UpdateThunderSound (1251), FogHorizontal_Main (1392), Sandstorm_Main (1975), Shade_InitVars (2247), sWeatherCycleRoute119 (2581), sWeatherCycleRoute123 (2588) and UpdateWeatherPerDay (2622), https://github.com/pret/pokeemerald/blob/master/src/field_weather_effect.c. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  8. Author’s measurement, October 5, 2026: measure_shadows.py in the author’s evidence folder for this post, output shadows.txt: size, opaque pixels and distinct tones of Emerald’s shadow_small.png, shadow_medium.png, shadow_large.png and shadow_extra_large.png (pret pokeemerald at 731ad5b, graphics/field_effects/pics/) and of Kiradex’s Kiradex/Resources/World/shadow.png, with each drawn as a text mask of its opaque pixels. Re-run before publishing with identical output. ↩↩↩↩↩↩↩↩↩↩

  9. Author’s measurement, October 5, 2026, on a Mac, not an iPhone: measure_kiradex_light.py in the author’s evidence folder for this post, output kiradex_light.txt. It reads, as text, the Kiradex repository’s Kiradex/World/Daylight.swift, PlazaStage.swift, Air.swift and every Swift file in Kiradex/World/, and scripts/forge/town.py and buildings.py; and, with Pillow, the exported Kiradex/Resources/World/town.png (the tints applied per channel to its opaque pixels), cloud.png, mist.png, glow.png, shadow.png and town.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 commit cf3beb1e, from a working tree being edited that day. Re-run before publishing, at 18:11 at commit 66c116b0: 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. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  10. 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 10 and NITE_HOUR 18 and the TimesOfDay comment; Kiradex’s Daylight.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. ↩↩↩↩↩↩↩↩↩↩↩↩↩

  11. Author’s measurement and proposal arithmetic, October 5, 2026, on a Mac: measure_kiradex_proposals.py in the author’s evidence folder for this post, output kiradex_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 of Motion.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 in town.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 commit 66c116b0, reading the town export and pictures in the working tree. Section 2 was revised to read the walk from Motion.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. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  12. 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 commit cf3beb1e), 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). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  13. pret, pokecrystal/gfx/tilesets/bg_tiles.pal at 5beda23, 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/day and RGB 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. ↩↩

  14. pret, pokecrystal/gfx/overworld/npc_sprites.pal at 5beda23, read October 5, 2026: the object palettes by time of day, https://github.com/pret/pokecrystal/blob/master/gfx/overworld/npc_sprites.pal. ↩↩

  15. pret, pokeemerald/include/event_object_movement.h at 731ad5b, read October 5, 2026: the object palette slots from PALSLOT_PLAYER (line 12) to PALSLOT_NPC_SPECIAL (line 22) and its reflection slot, https://github.com/pret/pokeemerald/blob/master/include/event_object_movement.h. ↩↩

  16. Stardew Valley Wiki, “Day Cycle,” saved October 5, 2026 as sdv-day-cycle.html and sdv-day-cycle.txt in 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. ↩↩↩↩↩↩↩↩↩

  17. Stardew Valley Wiki, “Weather,” saved October 5, 2026 as sdv-weather.html and sdv-weather.txt in 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. ↩↩↩↩↩↩↩↩↩

  18. Stardew Valley Wiki, “Modding:Weather data,” saved October 5, 2026 as sdv-weather-data.html and sdv-weather-data.txt in 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. ↩↩↩↩↩↩↩↩

  19. Kiradex source, Kiradex/World/PlazaStage.swift, read October 5, 2026, last at 17:42 at commit 5a344e3f, 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 commit cf3beb1e, had the lines from 469 on lower by 4 to 8, the file having gained an accessibility representation since): daylight(at:) (line 69, using Daylight.forcedHour when 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 by StageAccessibility at 361), content.renderingEffects.depthOfField = .disabled (133), the -airProbe report written to the stage’s value once, after rig.air.prepare(), in the view’s .task (190 to 193), onChange(of: clock) calling relight() (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 a PlazaStage at 946 with fitsWhole: false at 975); the clock @State (57); PlazaView.words, the status line’s words (819); the SceneEvents.Update subscription that steps rig.update each frame (176 to 182); rig.onStep setting stepTrace to the player’s step (156 to 160); bubbles(of:in:), the speech bubbles over the heads, under a TimelineView(.animation(minimumInterval: 0.1)) (447 to 463); and PlazaView (536), which presents QuestBoardView as 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 its PlazaStage with client: 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 of significantTimeChangeNotification or NSSystemTimeZoneDidChange was in Kiradex/World/ at that reading. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  20. Kiradex source, Kiradex/World/PlazaRig.swift, read October 5, 2026 at 16:51 at commit cf3beb1e, 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’s DirectionalLight under the comment “For the card alone: nothing else in the world is lit.” (lines 161 and 162), the rig’s light (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 with material(light: .white) (383), and each walker frame’s material from sheet.material(column:row:light:) with the rig’s light (512, in add(other:figure:tile:facing:) at 508, and 1081, in animate(_:)); animate(_:) itself (1038), in which a standing walker shows the idle breath, the blink and, now and then, one of the idleEmotes, 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’s shown (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 given material(light:) in light(_: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 in showDoor(at:sheet:column:) (617; the materials at 627 and 634); build(tiles:collector:arrival:) (362), which first gives the ground, the surroundings and the objects material(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), with tick() (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); and lockCamera() (1171 to 1200), which sets the camera on the player within bounds that CameraLock.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. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  21. Kiradex source, Kiradex/World/Daylight.swift, dated October 2, 2026 and read October 5 at 16:51 at commit cf3beb1e, 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:) with calendar: Calendar = .current (line 86), light(phase:season:) (line 92) and forcedHour, read from -plazaHour (line 101). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  22. Kiradex source, Kiradex/World/Air.swift, dated October 3, 2026 and read October 5 at 16:51 at commit cf3beb1e, 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 with lightingModel = .unlit and blendMode = .add and 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 in update at 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 -airProbe shows (built at lines 105 and 114); and the per-frame cloud offset, cloudOffset += SIMD2(0.004, 0.0015) * dt plus camera / 384 (lines 266 to 268); the depths, cloudDepth 2.4, driftDepth 2.45 and poolDepth 2.5 (lines 19 to 21), with the leaves, the fireflies and the smoke at driftDepth (237, 251, 287 and 314); TileMap.depth(atWorldY:), 1 plus up to 0.5 down the map (Kiradex/World/TileMap.swift line 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); and update(_:camera:view:) (line 262). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  23. 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’s NightTiles and WindowLight, https://blakecrosley.com/blog/pixel-art-structures-on-iphone. ↩↩↩↩↩↩↩↩

  24. Kiradex forge, scripts/forge/buildings.py, dated October 3, 2026 and read October 5 at 16:51 at commit cf3beb1e, 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 is gable and 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, in finish (lines 195 to 198). ↩↩↩↩

  25. pret decompilations, shallow clones in the author’s evidence folder for the series: pokecrystal at 5beda23 (committed September 29, 2026), pokeemerald at 731ad5b (October 1), pokefirered at 037335f (September 26) and pokered at d2704a6 (September 22), https://github.com/pret. ↩

  26. Author’s reading, October 5, 2026: parse_apple.py in the author’s evidence folder for this post, output apple_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. ↩↩↩

  27. Author’s measurement, October 5, 2026, on a Mac: measure_capture_walk.py in the author’s evidence folder for this post, output capture_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, commit cf3beb1e, 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.swift lines 234 to 241); a tap’s world point (PlazaRig.worldPoint, lines 459 to 463); the walkable letters (TileMap.swift lines 35 to 53), which the app takes from the export’s rows (WorldMap.swift lines 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 from characters.json); and the camera set on the player and clamped to the map (PlazaRig.lockCamera, lines 1171 to 1200, with CameraLock.bounds in Motion.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 that testTheChimneysSmoke’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 of house-dawn (column 18, rows 16 to 20) and of house-moss rows 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 brings house-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. ↩↩↩

  28. Author’s measurement, October 5, 2026, on a Mac: measure_palette_match.py in the author’s evidence folder for this post, output palette_match.txt. It reads every color in pret pokecrystal’s gfx/tilesets/bg_tiles.pal (168 colors, 57 distinct), gfx/overworld/npc_sprites.pal (128, 23 distinct) and gfx/tilesets/roofs.pal (108, 91 distinct) at 5beda23, scales each 5-bit channel by 1/31, and compares the 8 color constants of Kiradex’s Daylight.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. ↩↩↩

  29. Author’s replay, October 5, 2026: replay_gen3_rain_thunder.py in the author’s evidence folder for this post, output gen3_replay.txt. It first asserts, against the text of pret pokeemerald at 731ad5b, the idioms it rests on: Rain_Main’s three states, CreateRainSprite creating one sprite a call and returning false on the call that makes the 24th, UpdateVisibleRainSprites’s ++rainSpriteVisibleCounter > rainSpriteVisibleDelay, Rain_Finish keeping the sprites when the next weather is a rain kind, Sunny_Finish returning false at once, Task_WeatherMain’s swap (the old weather’s finish() until false, then initVars(), the step counter reset and the palette state set to changing, in src/field_weather.c) and UpdateWeatherColorMap’s ++colorMapStepCounter >= colorMapStepDelay, the thunderstorm’s setup states and THUNDER_STATE_WAIT_CHANGE, and every timer and transition of Thunderstorm_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 frame Task_WeatherMain swaps 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 calls Rain_InitAll loops 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 from THUNDER_STATE_NEW_CYCLE with 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). ↩↩↩↩

  30. Author’s measurement, October 5, 2026, on a Mac: measure_collector_coverage.py in the author’s evidence folder for this post, output collector_coverage.txt, reading the 18:07 copy of the figure sheets and characters.json at commit 66c116b0 and following CollectorComposer as 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 (think by day, doze_b at night, the one pair under 0.75) to 0.8336 under the lift, and up to 0.6099 under the full tint; held in idle_a, 0.7876 and 0.5752. E, the ground over every opaque pixel of the town atlas (the 16:51 copy at commit cf3beb1e) and the mask held in idle_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 of capture_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. ↩↩↩↩↩↩↩↩↩↩↩↩↩

  31. pret, pokecrystal/constants/ram_constants.asm at 5beda23, read October 5, 2026: wTimeOfDay and its values MORN, DAY, NITE and DARKNESS (lines 142 to 147), https://github.com/pret/pokecrystal/blob/master/constants/ram_constants.asm. ↩

  32. 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. ↩↩↩↩↩↩↩

  33. pret, pokecrystal/data/maps/environment_colors.asm at 5beda23, 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 $28 by morning and day and $29 at night, the file’s overworld water pair, with the other seven the block’s own; .IndoorColors (21 to 25), the indoor block $20 to $26 with TEXT $07 by morning and day, the night and dark blocks after; .DungeonColors and .Env5Colors (27 to 37), the blocks as they are, https://github.com/pret/pokecrystal/blob/master/data/maps/environment_colors.asm. ↩↩

  34. pret, pokecrystal/engine/gfx/color.asm at 5beda23, read October 5, 2026: LoadMapPals (from line 1197: first farcall LoadSpecialMapPalette at 1198 and jr c, .got_pals at 1199, which skips the background copy when that routine set carry; otherwise eight background palettes of 8 bytes; from .got_pals at 1257 the eight object palettes, from MapObjectPals at 1261; and, after cp TOWN at 1269, in a TOWN or ROUTE the map group’s roof colors from RoofPals at 1280, the night pair for NITE and DARKNESS by cp NITE_F at 1284, 4 bytes into wBGPals1 palette PAL_BG_ROOF color 1 at 1290 to 1291, that is ROOF’s colors 1 and 2; the eight background indices come from EnvironmentColorsPointers at 1206, by environment and time of day), and pokecrystal/engine/tilesets/tileset_palettes.asm, LoadSpecialMapPalette (lines 1 to 53: carry set after loading a table of its own for TILESET_POKECOM_CENTER, TILESET_BATTLE_TOWER_INSIDE, TILESET_ICE_PATH unless the environment is INDOOR, the Hall of Fame, TILESET_HOUSE, TILESET_RADIO_TOWER and TILESET_MANSION; .do_nothing at 51 clears it), https://github.com/pret/pokecrystal/blob/master/engine/gfx/color.asm. ↩↩↩↩↩↩

  35. pret, pokecrystal/gfx/tilesets/johto_palette_map.asm at 5beda23, 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. ↩

  36. pret, pokecrystal/gfx/tilesets/roofs.pal at 5beda23, 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. ↩

  37. pret, pokecrystal/engine/overworld/events.asm at 5beda23, read October 5, 2026: HandleMapTimeAndJoypad (line 193), which returns when wMapEventStatus is MAPEVENTS_OFF (lines 194 to 196) and otherwise calls UpdateTime, GetJoypad and TimeOfDayPals (lines 198 to 200), https://github.com/pret/pokecrystal/blob/master/engine/overworld/events.asm. ↩↩

  38. pret, pokecrystal/data/maps/setup_scripts.asm at 5beda23, read October 5, 2026: the twelve map setup scripts (table at lines 4 to 15); MapSetupScript_Fly (line 29, mapsetup FadeOutToWhite at 30, falling through MapSetupScript_Warp at 32, whose mapsetup FadeInFromWhite is at 52, two commands before its end); MapSetupScript_Door (102, mapsetup FadeOutToWhite at 103, falling through MapSetupScript_Train at 104, whose mapsetup FadeInFromWhite is at 121); MapSetupScript_Connection (81) has no fade, and no script names FadeInFromBlack or FadeOutToBlack. Also engine/overworld/events.asm line 1005, WarpToNewMapScript’s newloadmap MAPSETUP_DOOR, the setup a warp the player steps on runs; engine/events/overworld.asm line 619, newloadmap MAPSETUP_FLY; engine/overworld/scripting.asm line 2074, MAPSETUP_WARP for the script command warp; home/map.asm, FadeToMenu (1910, farcall FadeOutToWhite at 1914), CloseSubmenu (1919) and ExitAllMenus (1927), which call ReloadTilesetAndPalettes (1966, whose farcall UpdateTimeOfDayPal is at 1979) and end in FinishExitMenu (1933, farcall FadeInFromWhite at 1938); the Start menu, engine/menus/start_menu.asm: StartMenu (line 13), with no Fade call of its own and call UpdateTimePals at 39, 44 and 92 (home/time_palettes.asm line 20, UpdateTimePals, calls _UpdateTimePals), its .Exit (80), and its six items that call FadeToMenu, StartMenu_Option (447), StartMenu_Status (455), StartMenu_Pokedex (466), StartMenu_Pokegear (475), StartMenu_Pack (482) and StartMenu_Pokemon (501), returning through CloseSubmenu, ExitAllMenus or the menu’s .Clear (call FinishExitMenu at 155); FadeToMenu is called 21 times in 8 files outside mobile/ and home/map.asm, 6 of them in the Start menu and the others in engine/events/specials.asm (8), engine/pokemon/mail.asm (2) and one each in engine/items/mart.asm, engine/events/pokecenter_pc.asm, engine/overworld/select_menu.asm, engine/events/move_tutor.asm and engine/events/battle_tower/battle_tower.asm, counted by gen2_daylight.txt section E; and the eleven map scripts that call the black pair by special, each once out and once in: the three the body names, maps/SproutTower3F.asm (lines 55 and 59), Route24 (29, 33) and TeamRocketBaseB2F (110, 118), and eight more, each listed with its two lines in gen2_daylight.txt section E, https://github.com/pret/pokecrystal/blob/master/data/maps/setup_scripts.asm. ↩↩

  39. pret, pokecrystal/data/maps/maps.asm at 5beda23, read October 5, 2026: the map macro (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 expression RADIO_TOWER_MUSIC | MUSIC_GOLDENROD_CITY; among the 27 PALETTE_NITE maps, IlexForest (line 130) and TohjoFalls (line 161) are CAVE, BurnedTower1F DUNGEON (line 91), DragonShrine INDOOR (line 160) and UndergroundPath GATE (line 164), https://github.com/pret/pokecrystal/blob/master/data/maps/maps.asm. ↩↩

  40. pret, pokered/home/overworld.asm at d2704a6, read October 5, 2026: the warp code’s test for ROCK_TUNNEL_1F and ld [wMapPalOffset], a (lines 497 to 500), https://github.com/pret/pokered/blob/master/home/overworld.asm. ↩↩↩

  41. pret, pokered/home/fade.asm at d2704a6, read October 5, 2026: LoadGBPal reading from FadePal4 less the offset, and FadePal2:: 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. ↩

  42. pret, pokered at d2704a6, read October 5, 2026: every write of wMapPalOffset. home/overworld.asm line 500 (the 6, on a warp from an outdoor map to ROCK_TUNNEL_1F), line 535 (0, on the warp back outside; the indoor branch, .indoorMaps at line 508, does not write it) and line 790 (0, in HandleFlyWarpOrDungeonWarp); engine/menus/start_sub_menus.asm line 187 (0, Flash); engine/events/black_out.asm line 7 (0); engine/battle/init_battle_variables.asm line 18 (0 for the battle), restored from the value engine/battle/core.asm pushed at line 6667 and pops at line 6768; and data/maps/objects/RockTunnelB1F.asm, whose four warp_event lines (15 to 18) all lead to ROCK_TUNNEL_1F, https://github.com/pret/pokered. ↩

  43. Blake Crosley, “A Pixel Town Alive When Nobody Is Online: NPCs and Ghosts,” blakecrosley.com, October 5, 2026: Emerald’s UpdatePerDay and the functions it calls, UpdateWeatherPerDay among 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 in welcome and 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.now today reading Calendar.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. ↩↩↩↩↩↩↩↩↩

  44. pret, pokeemerald/include/constants/weather.h at 731ad5b, read October 5, 2026: the 18 WEATHER_* values from WEATHER_NONE (line 4) to WEATHER_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 13 COORD_EVENT_WEATHER_* values, https://github.com/pret/pokeemerald/blob/master/include/constants/weather.h. ↩

  45. pret, pokeemerald/src/trig.c at 731ad5b, read October 5, 2026: gSineTable (line 5), https://github.com/pret/pokeemerald/blob/master/src/trig.c. ↩

  46. 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.html and .txt in 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. ↩↩↩↩↩↩↩

  47. pret, pokeemerald/data/maps/*/map.json at 731ad5b, read October 5, 2026 by measure_gen3_weather.py: 518 maps, each header’s weather, map_type, requires_flash and coord_events, for example https://github.com/pret/pokeemerald/blob/master/data/maps/Route119/map.json. ↩

  48. pret, pokefirered/data/maps/*/map.json at 037335f, read October 5, 2026 by measure_gen3_weather.py: 425 maps, https://github.com/pret/pokefirered/tree/master/data/maps. ↩

  49. pret, pokeemerald/src/field_screen_effect.c at 731ad5b, read October 5, 2026: sFlashLevelToRadius and gMaxFlashLevel (lines 53 and 54), and the flash effect centered at DISPLAY_WIDTH / 2, DISPLAY_HEIGHT / 2 (line 980), https://github.com/pret/pokeemerald/blob/master/src/field_screen_effect.c. ↩

  50. pret, pokeemerald/src/overworld.c at 731ad5b, read October 5, 2026: SetDefaultFlashLevel (lines 971 to 979: level 0 unless gMapHeader.cave is set, 1 with FLAG_SYS_USE_FLASH, otherwise gMaxFlashLevel - 1), with include/global.fieldmap.h line 180, the header’s u8 cave, which tools/mapjson/mapjson.cpp line 164 writes from each map’s requires_flash, https://github.com/pret/pokeemerald/blob/master/src/overworld.c. ↩

  51. pret, pokeemerald/graphics/field_effects/pics/ at 731ad5b (shadow_small.png, shadow_medium.png, shadow_large.png, shadow_extra_large.png), read October 5, 2026 by measure_shadows.py; not reproduced, https://github.com/pret/pokeemerald/tree/master/graphics/field_effects/pics. ↩

  52. 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. ↩↩↩

  53. Stardew Valley Wiki, “Modding:Maps,” saved October 5, 2026 as sdv-modding-maps.html and .txt in the author’s evidence folder for this post (the Lighting properties AmbientLight, Light with its nine light types, WindowLight; and NightTiles, “Changes the tile after 7pm”, “mostly used for lamps in the game”), https://stardewvalleywiki.com/Modding:Maps. ↩↩↩↩↩↩

  54. 80.lv, “Moonlighter: Building Pixel Art & Preparing for Switch,” interview with Digital Sun, saved October 5, 2026 as moonlighter-80lv.html and .txt in the author’s evidence folder for this post, https://80.lv/articles/moonlighter-building-pixel-art-preparing-for-switch. ↩

  55. Noel Berry, “Remaking Celeste’s Lighting,” September 25, 2017, saved October 5, 2026 as celeste-lighting.html and .txt in the author’s evidence folder for this post, https://noelberry.ca/posts/celeste_lighting/. ↩↩

  56. 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.html and .txt in the author’s evidence folder for this post, https://www.gamedeveloper.com/art/eastward-s-creators-share-insights-on-making-pixel-art-adventures. ↩↩

  57. Nintendo Everything, translation of the Nintendo Dream interview with Octopath Traveler’s developers, November 3, 2018, saved October 5, 2026 as octopath-ne.html and .txt in 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.html is the 403 page). ↩↩

  58. Wikipedia, “HD-2D,” saved October 5, 2026 as hd2d-wiki.html and .txt in 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. ↩

  59. 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.html and .txt in 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. ↩↩

  60. Sabotage Studio, Sea of Stars press kit, saved October 5, 2026 as sos-presskit.html and .txt in the author’s evidence folder for this post, https://sabotagestudio.com/presskits/sea-of-stars/. ↩

  61. TechRaptor, “Sea of Stars Gameplay Clip Shows Day & Night Cycle,” saved October 5, 2026 as sos-techraptor.html and .txt in the author’s evidence folder for this post, https://techraptor.net/gaming/news/sea-of-stars-gameplay-clip-shows-day-night-cycle. ↩

  62. Raymond Schlitter, “Pixelblog 21: Top Down Objects,” SLYNYRD, saved October 5, 2026 as slynyrd-objects.html and .txt in the author’s evidence folder for this post, https://www.slynyrd.com/blog/2019/9/18/pixelblog-21-top-down-objects. ↩↩↩

  63. Raymond Schlitter, “Pixelblog 43: Top Down Tiles Part 2,” SLYNYRD, saved October 5, 2026 as slynyrd-tiles2.html and .txt in the author’s evidence folder for this post, https://www.slynyrd.com/blog/2023/3/26/pixelblog-43-top-down-tiles-part-2. ↩↩↩↩↩↩↩↩

  64. Raymond Schlitter, “Pixelblog 44: Top Down Trees,” SLYNYRD, saved October 5, 2026 as slynyrd-trees.html and .txt in the author’s evidence folder for this post, https://www.slynyrd.com/blog/2023/5/22/pixelblog-44-top-down-trees. ↩

  65. Shmuplations, translated interview on Seiken Densetsu 3 with Koichi Ishii, saved October 5, 2026 as sd3-shmuplations.html and .txt in the author’s evidence folder for this post, https://shmuplations.com/seikendensetsu3/. ↩↩↩↩

  66. Kiradex source, Kiradex/World/Glow.metal, dated October 3, 2026 and read October 5 at 16:51 at commit cf3beb1e, 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). ↩↩↩↩↩↩

  67. Kiradex forge, scripts/forge/air.py, dated October 3, 2026 and read October 5 at 16:51 at commit cf3beb1e (private repository): the docstring’s pictures, glow.png (“a radial pool, white at the centre falling to black”) and cloud.png (“a tileable mask of cloud shadow: alpha where a cloud passes”), lines 9 to 13. ↩↩

  68. Kiradex tests, KiradexUITests/KiradexUITests.swift, read October 5, 2026 at 16:51 at commit cf3beb1e, from a working tree being edited that day (private repository): testThePlazaKeepsTime (line 502), launched with -plazaHour 21 and -airProbe (503), and testTheChimneysSmoke (523), launched with -plazaHour 12 (524) and waiting for the air’s report to contain smoke 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 helpers wait(for:toRead:) (line 1171), launch (line 1179, which sets launchArguments before app.launch()) and save (line 1212, a screenshot of the screen). The file is dated October 5, 2026, 10:32, and the line numbers are that copy’s. ↩↩↩↩↩↩↩

  69. Kiradex source, Kiradex/World/HallView.swift, read October 5, 2026 at 16:51 at commit cf3beb1e, 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 the PlazaStage it builds (57), with client: nil (58). The file was the same bytes at 18:07, when the commit was 66c116b0. ↩↩↩↩

  70. Kiradex design plan, docs/research/design/01-art-direction-v2.md, dated October 3, 2026 and read October 5 at 16:51 at commit cf3beb1e (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”. ↩↩

  71. Apple Developer Documentation, UnlitMaterial (RealityKit), page JSON saved October 5, 2026 as apple-unlitmaterial.html in 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; and init(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. ↩↩↩

  72. Kiradex source, Kiradex/World/SpriteSheet.swift, dated October 3, 2026 and read October 5 at 16:51 at commit cf3beb1e (private repository; the line numbers are that copy’s): the frame material’s textureCoordinateTransform with 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 with semantic: .color (37); and material(light:), which builds a plain UnlitMaterial() (75 and 76). Also Air.swift line 223, the smoke frames, set the same way. ↩↩

  73. Kiradex source, Kiradex/World/Motion.swift, read October 5, 2026 at 17:18 at commit cf3beb1e, 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 was 66c116b0): 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), and Stride.ticks(diagonal:running:) (67 to 74): 16 ticks for a straight walking step, 8 running, 23 and 12 on a diagonal; and Stepping.tick() (215), which ends a step when its count of ticks reaches the stride’s. measure_kiradex_proposals.py and arith.py read the three numbers from this copy. ↩↩↩↩

  74. Kiradex forge, scripts/forge/town.py, dated October 3, 2026 and read October 5 at 16:51 at commit cf3beb1e (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); in paint (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). ↩↩↩↩

  75. Kiradex source, Kiradex/World/FigurePreview.swift, dated October 4, 2026 and read October 5 at 16:51 at commit cf3beb1e (private repository): @Environment(\.accessibilityReduceMotion) at line 25, the only read of the setting in Kiradex/World/. ↩

  76. Kiradex source, Kiradex/World/PlazaClient.swift, dated October 3, 2026 and read October 5 at 17:42 at commit 5a344e3f, from a working tree being edited that day (private repository): handle(_:)’s case "welcome": (line 214), which takes you, room, lines and players from the server’s welcome and nothing else; endpoint (lines 87 to 91), which reads -plazaServer from the launch arguments, “points a run at a local” server in its comment’s example ws://127.0.0.1:8300/v1/plaza, and otherwise uses the production wss:// endpoint; no zone or clock is read anywhere in the file. Read again at 18:19, at commit 66c116b0: the same bytes. ↩↩↩

  77. 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. ↩↩

  78. Apple Developer Documentation, MaterialParameterTypes.BlendMode (RealityKit), page JSON saved October 5, 2026 as apple-mpt-blendmode.html in the author’s evidence folder for this post: “Modes that describe how materials should be blended with content behind them”; enumeration cases add and alpha; 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. ↩↩↩↩↩↩

  79. Apple Developer Documentation, MaterialParameterTypes.BlendMode.add, page JSON saved October 5, 2026 as apple-blendmode-add.html in 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. ↩↩

  80. Apple Developer Documentation, CustomMaterial (RealityKit), page JSON saved October 5, 2026 as apple-custommaterial.html in 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. ↩

  81. Apple Developer Documentation, TimelineView (SwiftUI), page JSON saved October 5, 2026 as apple-timelineview.html in 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. ↩

  82. Apple Developer Documentation, TimelineSchedule.everyMinute (SwiftUI), page JSON saved October 5, 2026 as apple-everyminute.html in 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. ↩

  83. Apple Developer Documentation, UIApplication.significantTimeChangeNotification (UIKit), page JSON saved October 5, 2026 as apple-significanttime.html in 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. ↩↩↩↩↩↩

  84. Apple Developer Documentation, NSNotification.Name.NSSystemTimeZoneDidChange (Foundation), page JSON saved October 5, 2026 as apple-systemtimezonechange.html in 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. ↩↩↩

  85. Apple Developer Documentation, EnvironmentValues.timeZone (SwiftUI), page JSON saved October 5, 2026 as apple-timezone.html in 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. ↩

  86. Apple Developer Documentation, EnvironmentValues.accessibilityReduceMotion (SwiftUI), page JSON saved October 5, 2026 as apple-reducemotion.html in 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”; declared var accessibilityReduceMotion: Bool { get }; iOS 13.0+, https://developer.apple.com/documentation/swiftui/environmentvalues/accessibilityreducemotion. ↩↩↩↩

  87. Apple Developer Documentation, WeatherKit, page JSON saved October 5, 2026 as apple-weatherkit.html in 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. ↩↩↩

  88. Kiradex source, Kiradex/World/CollectorComposer.swift, read October 5, 2026 at 18:07 at commit 66c116b0, from a working tree being edited that day (private repository; the file is dated October 4, 07:40), with Kiradex/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); and indexPlane(for:), the layers laid in the manifest’s order for each facing, the last opaque index winning (153). ↩↩

  89. Kiradex repository, read October 15, 2026 at 23:53 without running git: .git/refs/heads/main read 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, roomClock and roomZone (79 to 83), roomDate, “The room’s time now, from the welcome’s clock” (109 to 113), and the welcome handler reading json["clock"] and json["zone"] (265 to 267); server/app/main.py, the welcome that carries "zone" and "clock" (21, 22 and 228) and ZONE, “one for the whole world until rooms are sharded by region”, UTC unless PLAZA_ZONE is set (77 to 79); Kiradex/World/PlazaStage.swift, the clock “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”, and server/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’s Kiradex/ and KiradexTests/ for significantTimeChange and NSSystemTimeZoneDidChange found nothing. The reader script and the copies are in the author’s evidence folder for this post. ↩↩↩

  90. 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 from PlazaView in PlazaStage.swift (line 690 of the file dated 17:22, read at 17:42 at commit 5a344e3f) and from Kiradex/Views/Me/MeView.swift (line 65 of the file dated 07:52). ↩

  91. 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. ↩

  92. 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’s scripts/forge/interiors.py (dated October 3), whose ROOM grid (lines 57 to 70) places a curtained window, K, at (3, 1) and a window, N, at (12, 1) of the ground floor, drawn by window (line 259) and curtained_window (line 278). The exported room.json and room-up.json carry no list of window cells. ↩↩

  93. 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. ↩

Artigos relacionados

Pixel-Art Structures: Houses, Halls and Interiors on iPhone

How Pokémon, the SNES RPGs and Stardew build houses, interiors and floors, measured from source, and how Kiradex's town …

80 min de leitura

Pixel-Art Routes: Map Connections and Districts on iPhone

How Pokémon, Stardew and Animal Crossing build the land between towns, measured from source: routes, seams, gates, map s…

114 min de leitura

What App Review Looks For, as Far as We Know: October 2026

Apple's App Review as of October 2026, from the public record: who reviews, how long it takes, what gets apps rejected a…

54 min de leitura