TimeOfDay

TimeOfDay is an environment object that drives the level day/night cycle.

It calculates seasonal sun elevation and azimuth from normalized time, latitude, date, and axisTilt, then broadcasts updates to environment systems. The current astronomical sky also reads this object as the level’s single source of clock, latitude, longitude, date, and UTC offset.

TimeOfDay is commonly used with advanced lighting and a compatible sky object, such as ScatterSky. The core_celestial runtime reads TimeOfDay just before rendering and pushes the live sun, moon, stars, meteors, and sky profile data into ScatterSky.

TimeOfDay only has a visible lighting effect when other environment objects listen to it or use its sun/time data. By itself, it is a global time controller, not a sky renderer.

For sky authoring, see Sky and Weather , Sky, Atmosphere, and Night Sky , and Night Lighting .

The Workbench Time of Day tool is the recommended live editor for this object. It can scrub the level clock, toggle playback, edit day length, and apply real-world place/date/time data used by the astronomical sky.

For most levels, author startTime, play, dayLength, latitude, longitude, year, month, day, and utcOffset. Use celestialProfile only when the level needs a custom sky profile.


Basic example

{
  "class": "TimeOfDay",
  "name": "tod",
  "axisTilt": 23.44,
  "dayLength": 1800,
  "startTime": 0,
  "time": 0,
  "play": false,
  "latitude": 47,
  "longitude": 8,
  "year": 2026,
  "month": 6,
  "day": 20,
  "utcOffset": "2",
  "celestialProfile": "earth",
  "position": [0, 0, 0],
  "rotationMatrix": [1,0,0,0,1,0,0,0,1],
  "scale": [1, 1, 1]
}

Important fields

Field Type Description
class string Must be "TimeOfDay".
name string Scene object name, commonly "tod".
axisTilt number Sun path tilt in degrees for TimeOfDay’s engine sun calculation. The current celestial runtime primarily uses time, latitude, longitude, date, and UTC offset for real sky placement.
dayLength number Length of a full virtual day in real-world seconds.
startTime number Time used when the level starts.
time number Current normalized local day time. Hidden in inspectors.
play bool Whether time advances automatically.
latitude number Observer latitude in degrees north. Used by the engine sun and astronomical sky.
longitude number Observer longitude in degrees east. Used by the astronomical sky and civil time conversion.
year number UTC calendar year used for moon, stars, and meteor shower positions.
month number UTC calendar month, 1-12.
day number UTC calendar day of month, 1-31.
utcOffset string/number Optional dynamic field for exact civil UTC offset in hours, including DST.
dstRule string Optional dynamic field: auto, eu, us, au, or none.
celestialProfile string Optional dynamic field selecting a sky profile by bare name or path.

Time values

TimeOfDay uses normalized local civil day time. In the current sky path, 0 is local noon:

0.00 -> 12:00 / local noon
0.25 -> 18:00 / evening
0.50 -> 00:00 / midnight
0.75 -> 06:00 / morning
1.0  -> wraps to 0.0

When assigned through setTimeOfDay, time is wrapped into the 0..1 range:

mTimeOfDay = time - floor(time);

Examples:

Input Stored time
0.15 0.15
1.15 0.15
-0.1 0.9

A common fixed noon setup is:

"startTime": 0

The exact visual result depends on latitude, longitude, date, UTC offset, sky setup, and terrain orientation.


startTime

"startTime": 0

startTime is copied into the current time when the object is added.


time

"time": 0.15

time is the current normalized local day time.

It is a protected field and hidden in inspectors. Setting it calls setTimeOfDay(), which wraps it and immediately updates sun position.

For level files, prefer startTime over time, because onAdd() resets time from startTime.


Playback

play

"play": true

When play is true, the time advances every simulation tick.

The update uses:

deltaTime = tickSeconds * simulationTimeScale
time += deltaTime / dayLength

If play is false, the time remains fixed unless changed by script/editor or animation.


dayLength

"dayLength": 1800

dayLength is the length of one full virtual day in real-world seconds.

Examples:

dayLength Meaning
600 10-minute day
1800 30-minute day
3600 1-hour day
86400 Real-time 24-hour day

Use longer values for slow natural progression and shorter values for visible day/night cycles.


Sun position

TimeOfDay calculates:

  • Sun elevation
  • Sun azimuth
  • Normalized elevation for color/day-night calculations

These values are broadcast through TimeOfDay::smTimeOfDayUpdateSignal.


axisTilt

"axisTilt": 23.44

axisTilt is the angle in degrees between the global equator and tropic. It controls the amplitude of seasonal solar declination in TimeOfDay’s engine sun calculation.

The default Earth-like value is:

23.44

Together with latitude and date, it changes sun height and day length through the year for systems that use the TimeOfDay sun values. The current core_celestial sky path reads the same clock, latitude, longitude, date, and UTC offset data and pushes the visible sun and night sky into ScatterSky.


Location and date

latitude and longitude

"latitude": 45.0,
"longitude": 9.0

latitude and longitude define the observer location for the current sky systems.

  • latitude affects the engine sun path and astronomical sky.
  • longitude affects astronomical sky orientation and local civil time conversion.

Use approximate real-world coordinates if the level represents a real place. For fictional levels, choose values that give the desired sun path and night sky orientation. Do not leave longitude at 0 unless that is intentional; it changes the apparent sky and derived timezone behavior.

year, month, and day

"year": 2026,
"month": 6,
"day": 23

The date is used by the astronomical sky for moon position, moon phase, star orientation, and meteor shower state.

It is also used by TimeOfDay to calculate seasonal solar declination.

The procedural night sky uses TimeOfDay, latitude, longitude, date, and UTC offset together.

utcOffset and dstRule

"utcOffset": "2",
"dstRule": "eu"

utcOffset and dstRule are optional dynamic fields used by core_celestial.

  • utcOffset pins the exact civil UTC offset in hours, including daylight saving time.
  • dstRule can be auto, eu, us, au, or none.
  • If neither is set, the runtime derives an approximate standard offset from longitude and applies an automatic DST rule by latitude/longitude.

Set utcOffset when the level should match a specific real-world local time. Longitude and UTC offset are related but not the same: longitude positions the observer on Earth, while utcOffset says how the level’s local civil clock maps to UTC.

celestialProfile

"celestialProfile": "earth"

celestialProfile is an optional dynamic field used by core_celestial.

Bare names resolve to:

/art/skies/profiles/<name>.json

For example, earth resolves to /art/skies/profiles/earth.json. You can also set a direct JSON path, or add a celestial.json file next to the level’s mission file.


Color targets

TimeOfDay initializes an internal sun color curve.

The curve maps normalized elevation to colors for:

  • Day
  • Dawn
  • Dusk
  • Night

The built-in color targets range from:

0 radians -> high noon
PI radians -> midnight

The default curve gradually transitions from white daylight to warm dawn/dusk colors and darker bluish night colors.

These targets are internal in this implementation and not exposed as persistent fields in the shown code.


Lifecycle behavior

When a TimeOfDay object is added:

  1. It initializes time from startTime.
  2. It calculates sun position.
  3. It sets global bounds.
  4. It adds itself to the scene.
  5. It executes its onAdd script callback.
  6. It enables processing.
  7. It stores the last signaled time.

When removed, it is removed from the scene.


Animation

TimeOfDay supports animated transition toward a target angle.

The method:

animate(f32 time, f32 speed)

takes:

Parameter Meaning
time Target time angle in degrees, clamped to 0..360.
speed Animation speed in degrees per second.

Internally, current normalized time is converted to degrees:

currentDegrees = timeOfDay * 360

If the target is behind the current time, it wraps forward by 360 degrees so animation always proceeds forward through the day.

Example concept:

current = 90 degrees
target = 45 degrees
actual target = 405 degrees

The animation stops when it reaches the target.


Example setups

Static noon lighting

{
  "class": "TimeOfDay",
  "name": "tod",
  "axisTilt": 23.44,
  "dayLength": 1800,
  "startTime": 0,
  "time": 0,
  "play": false,
  "latitude": 47,
  "longitude": 8,
  "year": 2026,
  "month": 6,
  "day": 20,
  "utcOffset": "2"
}

Use this for a fixed local noon setup.


Active day/night cycle

{
  "class": "TimeOfDay",
  "name": "tod",
  "axisTilt": 23.44,
  "dayLength": 2400,
  "startTime": 0.75,
  "time": 0.75,
  "play": true,
  "latitude": 47,
  "longitude": 8,
  "year": 2026,
  "month": 6,
  "day": 20,
  "utcOffset": "2"
}

This creates a 40-minute full cycle starting around local morning.


Earth atmosphere profile

{
  "class": "TimeOfDay",
  "name": "tod",
  "axisTilt": 23.44,
  "dayLength": 1800,
  "startTime": 0,
  "time": 0,
  "play": false,
  "latitude": 0,
  "longitude": 0,
  "year": 2026,
  "month": 6,
  "day": 20,
  "celestialProfile": "earth"
}

Use this pattern when a level should load a named Earth-based sky profile from /art/skies/profiles/. Most levels can omit celestialProfile and use the built-in Earth defaults.


Fast preview cycle

{
  "class": "TimeOfDay",
  "name": "tod_preview",
  "axisTilt": 23.44,
  "dayLength": 120,
  "startTime": 0,
  "time": 0,
  "play": true
}

Useful for quickly checking lighting transitions.


Interaction with other objects

TimeOfDay does not render the sky or sun by itself.

It is intended to work with environment systems that read or listen to its time data, such as:

  • ScatterSky
  • core_celestial
  • Sun/lighting objects
  • Advanced lighting environment systems
  • Exposure/tonemapping systems
  • Other objects connected to TimeOfDay::smTimeOfDayUpdateSignal
If the sky or lighting does not react to time changes, check that a compatible sky object exists and that core_celestial is enabled.

Best practices

  • Use one primary TimeOfDay object per level.
  • Use startTime for the level’s initial time.
  • Keep time and startTime consistent in saved files for clarity.
  • Disable play for fixed-lighting maps.
  • Enable play only when the level should have a moving day/night cycle.
  • Use realistic dayLength values unless you intentionally want fast cycling.
  • Confirm a compatible sky/lighting object exists, such as ScatterSky.
  • Set latitude, longitude, date, and UTC offset when the real sky, moon, and meteor showers should matter.
  • Use celestialProfile or level-local celestial.json for custom sky presets.

Common issues

TimeOfDay has no visible effect

Possible causes:

  • No compatible sky object exists.
  • The level is not using compatible advanced lighting setup.
  • core_celestial is disabled or not loaded.
  • play is false and the time is not being changed.
  • The level uses static lighting or another system overrides sun direction.

Level starts at the wrong time

Check:

"startTime": 0

Remember that onAdd() initializes current time from startTime.

Also remember that the current convention uses 0 as local noon, 0.25 as evening, 0.5 as midnight, and 0.75 as morning.


Time changes too quickly

Increase:

"dayLength": 3600

Also check the simulation time scale.


Sun path looks wrong

Check:

"latitude": 47,
"longitude": 8,
"year": 2026,
"month": 6,
"day": 20

Also check axisTilt, longitude, and utcOffset. The current sky calculates a seasonal sun path instead of using a fixed azimuth override.


Summary

TimeOfDay is the level day/night controller.

It stores normalized local day time, advances it when play is enabled, calculates seasonal sun elevation and azimuth, and provides latitude, longitude, date, UTC offset, and profile data to the astronomical sky. Use it with a compatible sky object such as ScatterSky for visible day/night lighting changes.

Last modified: July 23, 2026

Any further questions?

Join our discord
Our documentation is currently incomplete and undergoing active development. If you have any questions or feedback, please visit this forum thread.