Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Environment attributes and timelines

Verified against Minecraft 26.3 · Part IV · dusk falls over a taiga, and one value is resolved through a stack of layers — on the server for a mob, and again on the client for the sky.

Dusk on the overworld clock is a stretch and not an instant. Between tick 11867 and tick 13670 the sky over a taiga slides from its pale blue towards black, and the sky over a pale garden slides from its grey towards black by the same proportion; part-way through, at tick 12542, every mob that burns in daylight stops being in danger of burning, and is not in danger again until tick 23460. A player would never connect the three, and in 26.3 they are one mechanism. An environment attribute is a named, typed, registered property of the world — EnvironmentAttributes puts 51 of them in BuiltInRegistries.ENVIRONMENT_ATTRIBUTE — and the world answers one for a position and an instant by running a short stack of layers over the attribute’s default value: the dimension, the biome, the timelines, the weather. The surprise is in what a timeline holds. The day timeline does not know what colour a taiga sky is and never learns: its sky track’s keyframes are not values but modifier arguments, and the night segment is a multiply. Night does not set the sky’s colour — it multiplies whatever the biome produced, which is how one data-driven curve darkens every overworld biome correctly without being told about any of them.

The cast

Eight classes carry a value from the data pack to the sky. The first two thirds of this page is the machinery, object by object; the last third runs dusk through it twice, once on each side.

classwhat it decidesthread
EnvironmentAttributethe key: a type, a default, an AttributeRange, and four flags — positional, spatially interpolated, syncable, and the full-resolution biomes that EnvironmentAttributes.NATURAL_MOB_SPAWNS sets. It holds no value and no state— (registry constant)
EnvironmentAttributeMapwhat one dimension or one biome contributes — a modifier and an argument per attribute, never a bare value— (loaded from data)
EnvironmentAttributeSystemthe baked per-level resolver: one EnvironmentAttributeSystem.ValueSampler for each attribute some layer mentionsbuilt in the level constructor, read on that level’s thread; a generating region builds its own, on the worker
Timelinea clock, an optional period, one AttributeTrack per attribute, and the named instants on that clock— (loaded from data)
AttributeTrackSamplerone track baked against a clock, with a one-tick cache of the sampled argumentServer or Render
ServerClockManagerthe owner of day time — one ServerClockManager.ServerClockInstance per registered WorldClock, saved as world_clocksServer
ClientClockManagerthe client’s copy: free-runs each clock forward between packetsRender
EnvironmentAttributeProbethe client’s smoothing layer, living on Camera: 216 biome samples a tick, a lerp every frameRender

The stack a value falls through

flowchart TD
    DEF["EnvironmentAttribute.defaultValue enters here"]
    DIM["1 — dimension: DimensionType.attributes, a Constant folded into the baked base"]
    BIO["2 — biome: one Positional layer per attribute some biome mentions"]
    TLS["3 — timelines: DimensionType.timelines, one EnvironmentAttributeLayer.TimeBased per track"]
    WEA["4 — weather: WeatherAttributes.addBuiltinLayers, only where Level.canHaveWeather"]
    ASK{"who built this stack"}
    FLASH["two client-only layers, one attribute each"]
    SAN["EnvironmentAttribute.sanitizeValue clamps to the AttributeRange"]
    DEF --> DIM
    DIM --> BIO
    BIO --> TLS
    TLS --> WEA
    WEA --> ASK
    ASK -- "the server" --> SAN
    ASK -- "the client" --> FLASH
    FLASH --> SAN

The four rungs a level’s value falls through, and the one branch that is not on every value’s path: weather is the last word on the server, and the client stacks two more layers above it, each bolted to one attribute — one lerps the sky colour toward the lightning flash, the other pins the sky light factor to 1. A client asking for anything else falls the same four rungs a server does, and a region being generated builds only the first two.

EnvironmentAttributeSystem.Builder.addDefaultLayers stacks those four in that order and only that order. There is no priority number anywhere and no ordering data: a biome cannot run before its dimension, and weather is the last word on the server. The client’s two extra layers are not a fifth rung: ClientLevel adds each with EnvironmentAttributeSystem.Builder.addTimeBasedLayer against one named attribute — EnvironmentAttributes.SKY_COLOR and EnvironmentAttributes.SKY_LIGHT_FACTOR — so they are two entries in two attributes’ stacks and are absent from the other forty-nine. Each is a no-op unless ClientLevel.getSkyFlashTime is above zero. The stack is per attribute, too — an attribute nothing in the level mentions has no EnvironmentAttributeSystem.ValueSampler at all, and EnvironmentAttributeSystem.getValue hands back its default. And it is baked once: the level’s whole stack is built in the ServerLevel and ClientLevel constructors and never rebuilt, the only writer being ServerLevel.setEnvironmentAttributes, which is deprecated, marked for testing and called only from TestEnvironmentDefinition. A data-pack reload does not touch it.

Each rung earns its shape. The dimension’s is an EnvironmentAttributeLayer.Constant, and EnvironmentAttributeSystem.bakeLayerSampler walks off the front of the list folding every leading constant into one baked base value, so a dimension costs nothing at read time. The biome’s is an EnvironmentAttributeLayer.Positional, added once per attribute that any biome in the whole registry mentions — in vanilla, thirteen attributes across sixty-seven biome files, with visual/sky_color in fifty-seven of them. Timeline layers are EnvironmentAttributeLayer.TimeBased, one per track, and so is weather: one for each of the nine attributes named by WeatherAttributes.RAIN or WeatherAttributes.THUNDER, blending rain in first and thunder second. Rain is weighted at Level.getRainLevel minus the thunder level, so a thunderstorm never counts twice. Level.canHaveWeather wants sky light, no ceiling and not the End, so a rain-free dimension does not carry a weather layer that does nothing: it carries none.

ClientLevel adds two more after those four, both keyed on the lightning flash that LightningBolt sets through Level.setSkyFlashTime. One lerps EnvironmentAttributes.SKY_COLOR a fixed 22% toward a pale blue-white, the other pins EnvironmentAttributes.SKY_LIGHT_FACTOR to 1 outright, and both read the flash through the accessibility option Hide Sky Flashes, which reports a flash time of zero while it is switched on — so the two layers are still there and simply never fire. (The End’s sky flash is a different thing entirely — EndFlashState, read by the renderers rather than through the stack; see lightmap, fog and sky.)

An attribute is positional unless its builder says otherwise — positional meaning its answer is allowed to differ from block to block — and that flag is what polices who may write it. Biome.getAttributes is read through EnvironmentAttributeMap.CODEC_ONLY_POSITIONAL, so naming a non-positional attribute in a biome file is a load error, and no biome can locally change sky light level or lava speed. WorldGenRegion.environmentAttributes shuts the door from the other side: a region being generated builds a stack of the two static rungs alone, dimension and biome, so a feature or a spawn that asks about the environment during generation gets the place and never the hour, because generation must not depend on the hour.

Arguments, not values

EnvironmentAttributeMap is not a map of values. It is a map of EnvironmentAttributeMap.Entry, and an entry is an argument plus an AttributeModifier. EnvironmentAttributeMap.Builder.set is sugar for EnvironmentAttributeMap.Builder.modify with AttributeModifier.override; the interesting entries multiply, blend, maximise or or into whatever the layer below produced. AttributeTrack is the same shape — a modifier plus a KeyframeTrack of arguments — which is why Timeline.Builder.addTrack is only the override case and Timeline.Builder.addModifierTrack is the general one, and why the day timeline can say multiply sky light by 0.267 at night rather than sky light is 4 at night. In a data pack the shorthand shows: an entry written as the value itself means override, even for an attribute whose value is an object, and any other entry is an object carrying a modifier and an argument.

What a type allows

AttributeType is a record of a value codec, an AttributeType.modifierLibrary of the operations legal on that type and a codec for them, four separate LerpFunctions, one for each way two values of it can meet, and two converters.

lerp slotused when
AttributeType.keyframeLerpbetween two keyframes of a timeline track that overrides the value — a track of modifier arguments uses the modifier’s own AttributeModifier.argumentKeyframeLerp instead
AttributeType.stateChangeLerpfading weather in and out
AttributeType.spatialLerpacross a biome boundary
AttributeType.partialTickLerpbetween two client ticks, inside a frame

AttributeTypes registers fifteen of them: two boolean kinds, three numeric, two colour, six a data pack sets whole — a moon phase, a villager activity, a bed rule, a particle, a piece of background music, the ambient sounds — and two that combine, the ambient particles, a list that cross-fades, and the mob-spawn settings, which overlay. One built by AttributeType.ofNotInterpolated gets a step function in all four slots, each with its own threshold, which is how a MoonPhase snaps while a colour slides; AttributeType.toFloat and AttributeType.toInt are nullable, and their presence decides whether a loot table can read an attribute as a number, which the trace below reaches. Each type publishes a small, closed library of operations — six logic gates for a boolean, six arithmetic ones for a float, five for a colour, four of them ways to combine two colours — and a track or an entry that asks for one the type does not publish fails where it is built, AttributeType.checkAllowedModifier in the game’s own builders and the type’s modifier codec for a data pack, so an illegal combination is a load error rather than a runtime surprise. Three codecs then decide who may write what:

codecused byeffect
EnvironmentAttributeMap.CODECDimensionType.attributesanything
EnvironmentAttributeMap.CODEC_ONLY_POSITIONALBiome.getAttributesrejects non-positional attributes
EnvironmentAttributeMap.NETWORK_CODECDimensionType.NETWORK_CODEC, Biome.NETWORK_CODECdrops every non-syncable entry before the wire

Who owns the clock

WorldClock is a unit record. It holds nothing at all: it is an identity token in the Registries.WORLD_CLOCK registry, and vanilla registers two, WorldClocks.OVERWORLD and WorldClocks.THE_END. Every piece of state lives in ServerClockManager.ServerClockInstance — a total tick count, a fractional partial tick, a rate and a paused flag — and the manager holding those is a SavedData under ServerClockManager.TYPE, saved once for the whole server as world_clocks.

The four numbers, and the two records that copy them

The live instance is mutable, so its numbers exist twice more as records. ClockState is the saved form and carries all four; PackedClockStates is the map of them a save file holds. ClockNetworkState is the wire form and carries three — total ticks, partial tick and rate — and the one it leaves behind, the paused flag, is the whole of what the client does not get. ClockManager is the one thing the two managers share, an interface with a single method, ClockManager.getInstance: this clock’s ClockInstance — its total tick count, partial tick, rate and pause. Everything a reader of an attribute needs from a clock is behind that method, which is why AttributeTrackSampler can be the same class on both sides, and why ServerClockManager is the owner of day time in this book.

What moves a clock

MinecraftServer calls ServerClockManager.tick once per server tick, inside the clocks profiler zone and only while the tick-rate manager runs normally; the GameRules.ADVANCE_TIME check sits inside the method itself, and gates every clock at once, whereas ServerClockManager.setPaused gates one. Each unpaused instance then gains its rate, accumulating the fraction, so a clock at rate 0.5 gains a tick every other server tick and one at rate 1000 gains a thousand — /time rate accepts anything from 0.00001 to 1000.

Naming an instant on a clock

A ClockTimeMarker is a named instant on a clock: ClockTimeMarkers.DAY, NOON, NIGHT, MIDNIGHT, WAKE_UP_FROM_SLEEP, ROLL_VILLAGE_SIEGE. Markers are declared inside timelines and collected onto the clock by ServerClockManager.init, and Timeline.validateRegistry fails the whole registry load if two timelines on one clock declare the same one. The subset /time suggests is the one flagged ClockTimeMarker.showInCommands, though it accepts any marker on the clock; ServerClockManager.isAtTimeMarker is how VillageSiege asks whether the siege roll is due, and ServerLevel.tick calls ServerClockManager.moveToTimeMarker to jump the clock when enough players are asleep. Everything TimeCommand offers is offered twice, once against the source level’s DimensionType.defaultClock and once under /time of against a clock the player names, because a command that says set the time has to be told whose time it means.

The four timelines

timelineperiodwhat it carries
Timelines.OVERWORLD_DAY24000the whole day/night curve — sun, moon and star angles, sky and fog colours, sky light, and the gameplay flags that flip at dusk
Timelines.MOON192000 — eight daysthe moon phase, and the surface slime spawn chance riding the same steps
Timelines.VILLAGER_SCHEDULE24000EnvironmentAttributes.VILLAGER_ACTIVITY and EnvironmentAttributes.BABY_VILLAGER_ACTIVITY
Timelines.EARLY_GAMEnoneone ramp that ands EnvironmentAttributes.CAN_PILLAGER_PATROL_SPAWN with false until tick 120000

Three of the four have a period and are cycles. The fourth is the interesting one: a timeline with no period is not a cycle at all — its track runs once against the clock’s total ticks and then holds its last value forever, which is what makes Timelines.EARLY_GAME a switch, thrown when the overworld clock passes 120000 ticks and closed again only if /time sets the clock back below it, rather than something that comes round again.

All four run on WorldClocks.OVERWORLD; no timeline is bound to the End’s clock, which the End’s dimension type names as its default only, the clock /time sets there and the End’s flash reads. Which of them a dimension runs is a tag on DimensionType.timelines: TimelineTags.IN_OVERWORLD names the day, moon and early-game timelines on top of TimelineTags.UNIVERSAL, while TimelineTags.IN_NETHER and TimelineTags.IN_END name only the universal one, which holds the villager schedule. That schedule is the clearest case of a timeline driving something that is not a colour: Brain.setSchedule takes an EnvironmentAttribute of Activity, Villager is the only caller — adults read EnvironmentAttributes.VILLAGER_ACTIVITY and babies EnvironmentAttributes.BABY_VILLAGER_ACTIVITY, two tracks on the one timeline — and Brain.updateActivityFromSchedule samples it at the villager’s own position, but only when more than twenty game ticks have passed since it last looked. Where the activity it reads then sends a villager is in points of interest.

What crosses the wire

The rules travel, never the resolved values. Registries.TIMELINE and Registries.WORLD_CLOCK are in RegistryDataLoader.SYNCHRONIZED_REGISTRIES — the timeline through Timeline.NETWORK_CODEC, so only syncable tracks go — while Registries.ENVIRONMENT_ATTRIBUTE and Registries.ATTRIBUTE_TYPE are built-in code registries that never go out at all.

Syncable is the third flag, and it draws the line between the two sides. Thirty-three of the 51 carry it: every one of the 24 visual/ attributes and all four audio/ ones, and five of the gameplay flags — sky_light_level, fast_lava, water_evaporates, piglins_zombify, creaking_active. The eighteen left behind are gameplay decisions the server makes alone: whether monsters burn, whether a raid can start, what a villager should be doing now. Dropping their entries and tracks before the wire costs the client almost nothing: the one place it asks for them is the bed rules, read through the sleeping code it shares with the server, and there it gets the defaults. So the client’s stack is shorter than the server’s, and identical everywhere else the client looks.

Clock state rides ClientboundSetTimePacket: a game time — the world’s own tick count, which no clock owns — plus a ClockNetworkState — total ticks, partial tick, rate — per clock in its map. ServerClockManager.createFullSyncPacket fills that map on join, respawn and a change of dimension, and on a GameRules.ADVANCE_TIME change. Every mutator broadcasts a one-clock update. The routine broadcast from MinecraftServer.forceGameTimeSynchronization, once every twenty ticks, sends an empty map and nothing but the game time. ClientClockManager.handleUpdates adopts what arrives and ClientClockManager.tick free-runs the rest — which is why a paused clock travels as rate 0: the client has no paused flag to receive.

Dusk, and the same question asked twice

A mob asks whether it should be burning, and the camera asks what colour the sky is. Both go through the stack above, and this is the machinery running.

sequenceDiagram
    participant SL as ServerLevel
    participant EAS as Environment<br/>AttributeSystem
    participant EVS as Environment<br/>AttributeSystem.<br/>ValueSampler
    participant Mob as Mob
    participant ATS as Attribute<br/>TrackSampler
    participant KTS as Keyframe<br/>TrackSampler

    Note over SL,KTS: one server tick
    SL->>EAS: invalidateTickCache — before the world border, before the weather
    EAS->>EVS: the cached value is dropped, the cache tick id bumped
    Mob->>EAS: getValue(MONSTERS_BURN, position)
    EAS->>EVS: getValue — is any layer of this attribute positional?
    EVS->>EVS: none is — start from the baked base, the default false
    EVS->>ATS: applyTimeBased(value, cache tick id)
    ATS->>KTS: sample at the overworld clock's ClockInstance.totalTicks
    KTS-->>ATS: the argument — false at 12542, true again at 23460
    ATS-->>EVS: the modifier is BooleanModifier.OR, applied to the value
    EVS->>EVS: the weather layer is on nine attributes, not this one
    EVS-->>Mob: false, clamped and cached for the tick

One attribute resolved once, on the server, inside one tick: the level’s only contribution is the first two arrows, which throw last tick’s answer away, and every arrow after them is the stack being walked because a mob asked. Nothing here is pushed — a value exists because something wanted it.

Read the arrows as decisions. EnvironmentAttributeSystem.invalidateTickCache computes nothing: it drops each sampler’s cached value and bumps a counter, and that counter is the identity every track sampler compares against. AttributeTrackSampler.applyTimeBased keeps a one-entry cache of the sampled argument and reuses it for every reader arriving with the same tick id, so a thousand mobs asking EnvironmentAttributes.MONSTERS_BURN cost one keyframe sample between them.

The step that reads oddly is the one where the sampler asks whether any layer of the attribute is positional and finds that none is. EnvironmentAttributes.MONSTERS_BURN is a positional attribute, and yet its stack is one layer deep, because in vanilla nothing but the day timeline mentions it: no dimension type, no biome. EnvironmentAttributeSystem.ValueSampler decides by layers, not by the flag, so with no positional layer present the position is ignored and the whole answer is memoised for the tick. The flag still governs where the attribute may be read: it makes EnvironmentAttributeCheck and the two EnvironmentAttributeValue number providers declare LootContextParams.ORIGIN among the parameters they read, and the default answers when a context has none, and it makes EnvironmentAttributeSystem.getDimensionValue throw in a development build if asked for a positional attribute at all. Only two attributes are built EnvironmentAttribute.Builder.notPositional — sky light level and lava speed — and three call sites read them that positionless way: Level.updateSkyBrightness for EnvironmentAttributes.SKY_LIGHT_LEVEL, and LavaFluid.isFastLava and Entity for EnvironmentAttributes.FAST_LAVA, the two sites that between them decide how fast lava flows and how hard it shoves. A fourth site names no attribute at all: EnvironmentAttributeReader sends any non-positional attribute down this road when a loot context asks for one.

The period matters when a KeyframeTrackSampler is built: for a periodic track it bakes two extra segments, last keyframe to first on either side of the loop, so a value interpolates across the wrap — tick 0, where the day timeline’s period closes and ClockTimeMarkers.WAKE_UP_FROM_SLEEP sits — instead of snapping, and KeyframeTrackSampler.sample reduces the clock’s total ticks with a floor-mod before choosing one. EasingType supplies the curve, and the day timeline’s sun, moon and star angles share one symmetric cubic Bézier whose two keyframes both sit at tick 6000, so the baked segment runs noon to noon. The sun therefore turns slowest at its zenith — two thirds of the linear rate — and fastest at midnight, at about six fifths of it. That is why a Minecraft day is not two equal halves: the sun spends roughly 13,560 ticks above the horizon against 10,440 below.

The same value on the client

The client resolves the same stack and then does two things to it the server never does. Both of them are in the picture: one is about space, the other about time.

sequenceDiagram
    participant Camera as Camera
    participant EAP as Environment<br/>AttributeProbe
    participant GS as GaussianSampler
    participant SAI as SpatialAttribute<br/>Interpolator
    participant EAS as Environment<br/>AttributeSystem
    participant SR as SkyRenderer

    Note over Camera,SR: one client tick
    Camera->>EAP: tick(level, position) — once per client tick
    EAP->>GS: sample around the camera
    GS->>SAI: accumulate(weight, that biome's attributes) — 216 times
    Note over Camera,SR: between ticks, once per frame
    SR->>EAP: getValue(SKY_COLOR, partialTicks)
    opt only on the first frame of the tick that asks for this attribute
        EAP->>EAS: getValue(attribute, position, interpolator)
        EAS->>SAI: applyAttributeLayer — weighted blend of every biome in range
    end
    EAP-->>SR: AttributeType.partialTickLerp between last tick's value and this one

The same stack, on the other side of the wire, with the client’s two kinds of smoothing around it. Read the two note bars as the boundary they mark: the 216 biome samples happen once a tick whatever the frame rate, and the opt box is the part of the frame nobody pays for twice — the stack is resolved by the first frame that asks and every later frame in the same tick only lerps.

The client resolves the same stack from the same data — it is never sent a resolved value. What it adds is two kinds of smoothing the server never does. In space, EnvironmentAttributeProbe.tick prunes, clears, then runs GaussianSampler.sample over a 6×6×6 neighbourhood of quart-resolution biome cells — the 4×4×4 blocks a biome is stored per — for 216 samples in all. The weights come from a seven-entry kernel, 0-1-4-6-4-1-0, with each of the six taps along an axis lerped between two neighbouring entries by the camera’s sub-cell offset: the weights slide as the camera moves instead of snapping when it crosses a cell. Those weights accumulate into a SpatialAttributeInterpolator, whose SpatialAttributeInterpolator.applyAttributeLayer applies each contributing biome’s modifier to the base value and lerps the results together by weight. Only the 21 attributes flagged EnvironmentAttribute.isSpatiallyInterpolated are resolved that way; anything else takes the single biome under the position. But that flag is not a gate on the sampling: the probe runs its 216 samples every tick regardless, and the test is applied afterwards, when the layer is applied. The server pays none of it — it passes no interpolator at all. In time, each probed value keeps last tick’s answer beside this tick’s and returns AttributeType.partialTickLerp between them — and prunes itself, dropping any value nobody read during a tick. The resolve itself is once a tick, not once a frame: EnvironmentAttributeProbe.ValueProbe.tick nulls this tick’s answer, the first frame that asks for the attribute fills it in by walking the whole stack, and every frame after that in the same tick does nothing but the lerp. Only the first frame in a tick that asks pays for the walk.

The probe lives on Camera, ticked from Camera.tick and emptied by Camera.reset, and everything that draws the sky goes through it — the sky, the lightmap, the two fog environments, the clouds and the music. That is why lightmap, fog and sky never touches EnvironmentAttributeSystem directly. The probe is not a wall, though: the clock item reads sun_angle and moon_phase off ClientLevel.environmentAttributes directly, and so does ClientLevel itself for ambient particles.

The last difference is a tick wide, and it is the reason the two sides can disagree about the sky for one tick at dusk. They invalidate at opposite ends of the tick: ServerLevel.tick calls EnvironmentAttributeSystem.invalidateTickCache before the world border and the weather, then runs Level.updateSkyBrightness later in the same method, once sleeping and weather have resolved; ClientLevel.tick does the reverse, Level.updateSkyBrightness first and invalidation last, so the client’s sky-darken value comes from the previous tick’s clock.

Questions players ask

Why does the nether have no night? It has no day timeline: the nether’s and the End’s DimensionType.timelines both resolve to TimelineTags.UNIVERSAL alone. Its environment is constants instead — the dimension type pins gameplay/sky_light_level, gameplay/fast_lava, gameplay/water_evaporates and twelve more, which EnvironmentAttributeSystem.bakeLayerSampler folds into a base value that never changes again.

Does setting the time in the overworld move the End? No: each clock keeps its own ServerClockManager.ServerClockInstance. Every mutator does invalidate the cache on every level at once, though — ServerClockManager walks MinecraftServer.getAllLevels on each change, because a time jump must not leave half a tick of stale sky behind.

Why do pillager patrols not show up on day one? Because Timelines.EARLY_GAME is the timeline with no period, and its single modifier track ands EnvironmentAttributes.CAN_PILLAGER_PATROL_SPAWN with false until tick 120000 — a hundred minutes of play at the normal rate, sooner for every night slept through — and with true after. Nothing counts your days; the switch is one keyframe on a track that never comes round again.

For a 1.21-era reader. Three sets of names moved. The gameplay booleans on DimensionType — ultrawarm, bed_works, piglin_safe, respawn_anchor_works — are entries in DimensionType.attributes now, and ultrawarm has become several, EnvironmentAttributes.FAST_LAVA and EnvironmentAttributes.WATER_EVAPORATES chief among them. BiomeSpecialEffects keeps only the water, foliage and grass tints: sky and fog are Biome.getAttributes. And the villager Schedule class is Timelines.VILLAGER_SCHEDULE, a data-pack Timeline like any other.

Where to look

EnvironmentAttributes · EnvironmentAttribute.Builder · AttributeTypes · EnvironmentAttributeMap.Entry · EnvironmentAttributeSystem.Builder · EnvironmentAttributeSystem.bakeLayerSampler · EnvironmentAttributeSystem.invalidateTickCache · WeatherAttributes.addBuiltinLayers · Timelines · Timeline.createTrackSampler · AttributeTrackSampler.applyTimeBased · KeyframeTrackSampler.sample · ServerClockManager.tick · ClientClockManager.handleUpdates · EnvironmentAttributeProbe.tick · SpatialAttributeInterpolator.applyAttributeLayer · GaussianSampler.sample · TimeCommand


Rules: names, never code · how the system works, not how the code reads · newest version only · every backticked name passes tools/verify_names.py.