add clock sync option

This commit is contained in:
DramaticShape
2026-07-29 22:50:54 -04:00
parent 6d27d91411
commit 284172db7b
5 changed files with 89 additions and 25 deletions
+10 -7
View File
@@ -57,13 +57,16 @@
horizon above the frame, so the arena keeps exactly the flat sky it had.
- **A day/night cycle**, on a new **DAYTIME** options row: `DAY`, `NIGHT`,
`DUSK`, `DAWN`, `CYCLE`. One twenty-minute clock underneath all five -- ten
minutes of sun, ten of moon -- where the four named settings are PINS on
that dial (noon, mid-night, sunset, sunrise) and `CYCLE` lets it run,
picking up from whichever pin the player was just looking at. Everything is
a pure function of the clock, so the pinned DUSK is exactly the running
cycle stopped at sunset. Setting **VOXEL** to `FULL` switches DAYTIME to
`CYCLE` along with the rest of the preset.
`DUSK`, `DAWN`, `SYNC`, `CYCLE`. One twenty-minute clock underneath all of
them -- ten minutes of sun, ten of moon -- where the four named settings
are PINS on that dial (noon, mid-night, sunset, sunrise), `CYCLE` lets it
run, picking up from whichever pin or SYNC sky the player was just looking
at, and `SYNC` -- the DEFAULT -- lays the machine's own clock onto the
dial: local noon is the DAY pin, midnight is NIGHT, six and eighteen the
twilights, an hour of the real day is fifty seconds of dial. Everything is a pure function of
the clock, so the pinned DUSK is exactly the running cycle stopped at
sunset. Setting **VOXEL** to `FULL` switches DAYTIME to `CYCLE` along with
the rest of the preset.
**The sun and the moon are in the sky**, and their positions are honest:
the disc is the light's own direction projected through the same matrix the
+9 -5
View File
@@ -34,7 +34,7 @@ menu.
| `6`, or the **T-SHIFT** options row | OFF → 1 → 2 → 3 → OFF (miniature blur) |
| `7`, or the **V-CURVE** options row | OFF → 1 → 2 → 3 — bend the world over the horizon |
| `8`, or the **3D-BTL** options row | ON / OFF — fight on the map instead of on a white field |
| the **DAYTIME** options row | DAY / NIGHT / DUSK / DAWN / CYCLE — what time it is outdoors |
| the **DAYTIME** options row | DAY / NIGHT / DUSK / DAWN / SYNC / CYCLE — what time it is outdoors |
**3D-BTL** is on by default and is independent of **VOXEL**: battles draw
on the world whether or not the free-roam camera is pitched over.
@@ -71,10 +71,14 @@ to the clock:
The **DAYTIME** row is one twenty-minute clock — ten minutes of sun, ten of
moon. `DAY`, `NIGHT`, `DUSK` and `DAWN` are pins on that dial (noon,
mid-night, sunset, sunrise) and `CYCLE` lets it run, picking up from
whichever pin was showing. Everything is a pure function of the clock, so
the pinned DUSK is exactly the running cycle stopped at sunset. Setting
**VOXEL** to `FULL` switches DAYTIME to `CYCLE` with the rest of the preset.
mid-night, sunset, sunrise), `CYCLE` lets it run, picking up from whichever
pin (or SYNC sky) was showing, and `SYNC` — the default — lays your
machine's own clock onto the dial: local noon is the DAY pin, midnight is
NIGHT, six and eighteen the twilights, so out of the box Kanto's evening
falls when yours does.
Everything is a pure function of the clock, so the pinned DUSK is exactly
the running cycle stopped at sunset. Setting **VOXEL** to `FULL` switches
DAYTIME to `CYCLE` with the rest of the preset.
The **sun and moon hang in the sky**, projected through the same matrix the
geometry is drawn with, so each stands over the point on the horizon its
+30 -6
View File
@@ -63,11 +63,14 @@ DayNight.T = { dawn = 0, day = 300, dusk = 600, night = 900 }
DayNight.KEY = "daytime"
DayNight.LABEL = "DAYTIME"
-- "day" first: an unset or unreadable value is DAY, per the row's contract
-- (ModSetting values[1] is the default)
-- "sync" first: an unset or unreadable value follows the machine's own
-- clock, per the row's contract (ModSetting values[1] is the default).
-- "cycle" stays LAST: the FULL preset reaches for it by position.
DayNight.setting = ModSetting.new(DayNight.KEY, DayNight.LABEL,
{ "day", "night", "dusk", "dawn", "cycle" },
{ "DAY", "NIGHT", "DUSK", "DAWN", "CYCLE" })
{ "sync", "day", "night", "dusk",
"dawn", "cycle" },
{ "SYNC", "DAY", "NIGHT", "DUSK",
"DAWN", "CYCLE" })
DayNight.clock = DayNight.T.day -- the running cycle's own position
@@ -311,10 +314,28 @@ local function mode()
return DayNight.setting:get() or "day"
end
-- The effective time: the pin, or the running clock under CYCLE.
-- Where SYNC reads the real clock: local hours, 0..24 with the minutes as
-- fraction. A named seam rather than a bare os.date call, so the suite can
-- hand it a fixed hour.
function DayNight.hours()
local d = os.date("*t")
return d.hour + d.min / 60 + d.sec / 3600
end
-- SYNC: the machine's own time of day laid onto the dial. Local noon is
-- the DAY pin, midnight the NIGHT pin, six and eighteen the twilights --
-- an hour of the real day is fifty seconds of dial, and Kanto's evening
-- falls when the player's does.
function DayNight.syncTime()
return ((DayNight.hours() - 6) * (DayNight.CYCLE / 24)) % DayNight.CYCLE
end
-- The effective time: the pin, the running clock under CYCLE, or the wall
-- clock under SYNC.
function DayNight.time()
local m = mode()
if m == "cycle" then return DayNight.clock end
if m == "sync" then return DayNight.syncTime() end
return DayNight.T[m] or DayNight.T.day
end
@@ -326,8 +347,11 @@ end
function DayNight.update(dt)
local m = mode()
if m ~= lastMode then
if m == "cycle" and DayNight.T[lastMode] then
if m == "cycle" then
-- from a pin, its time; from SYNC, wherever the real sky already was
DayNight.clock = DayNight.T[lastMode]
or (lastMode == "sync" and DayNight.syncTime())
or DayNight.clock
end
lastMode = m
end
+3 -2
View File
@@ -321,8 +321,9 @@ local SETTINGS = {
.. "shot over the shoulder with a slow parallax drift." },
{ DayNight.setting,
"What time it is outdoors: pin the sky to DAY, NIGHT, DUSK or DAWN, "
.. "or let CYCLE run it -- ten minutes of sun, ten of moon, with the "
.. "shadows, the sky and the light following." },
.. "let CYCLE run it -- ten minutes of sun, ten of moon, with the "
.. "shadows, the sky and the light following -- or SYNC it to the "
.. "clock on the wall, so Kanto's evening falls when yours does." },
}
local schema = {}
+37 -5
View File
@@ -297,8 +297,8 @@ T.eq(#hookedRows, 5, "the options hook added a row per setting")
local grid, curve, battles = hookedRows[2], hookedRows[3], hookedRows[4]
local daytime = hookedRows[5]
T.eq(daytime.label, "DAYTIME", "the day/night row carries its label")
T.eq(daytime.value(), "DAY",
"and starts pinned to DAY -- no time set means it is day")
T.eq(daytime.value(), "SYNC",
"and defaults to SYNC -- no value set follows the clock on the wall")
T.eq(grid.label, "V-GRID", "the grid row carries its label")
T.eq(grid.value(), "OFF", "the grid starts off")
T.eq(curve.label, "V-CURVE", "the curve row carries its label")
@@ -1055,6 +1055,11 @@ local skyFor = VoxelScene._skyFor
local skyStrength = VoxelScene._skyStrength
T.check(type(skyFor) == "function", "the scene exposes its sky resolve")
-- pinned to DAY for every sky assertion below: the row ships defaulting to
-- SYNC -- the machine's own clock -- which would hand these tests whatever
-- palette the hour of the test run happened to be
run.loader.exports.DRAMATIC_SHAPE.lib.require("DayNight").setting:sync("day")
local outside = { def = { id = "PALLET_TOWN", tileset = "OVERWORLD" } }
local inside = { def = { id = "REDS_HOUSE_1F", tileset = "HOUSE" } }
local TOP = math.rad(Voxel.ANGLES_DEG[Voxel.MAX_LEVEL + 1])
@@ -1928,7 +1933,10 @@ local Voxel3D = run.loader.exports.DRAMATIC_SHAPE.lib.require("Voxel3D")
local Voxel = run.loader.exports.DRAMATIC_SHAPE.lib.require("VoxelState")
-- the dial and its pins
T.eq(DayNight.setting:get(), "day", "no time set means DAY: the default pin")
T.eq(DayNight.setting.values[1], "sync",
"no value set means SYNC: values[1] is the row's contract default, and "
.. "it follows the machine's clock")
DayNight.setting:sync("day")
T.eq(DayNight.time(), 300, "and DAY is noon on the dial")
local PINS = { day = 300, night = 900, dusk = 600, dawn = 0 }
for name, t in pairs(PINS) do
@@ -2087,6 +2095,30 @@ modApi.save:set(DayNight.SAVE_KEY, nil)
DayNight.restore()
T.eq(DayNight.clock, 300, "a save with no clock in it starts at day")
-- SYNC lays the machine's own clock onto the dial: local noon is the DAY
-- pin, midnight is NIGHT, six and eighteen the twilights
local hoursWas = DayNight.hours
DayNight.setting:sync("sync")
DayNight.hours = function() return 12 end
T.eq(DayNight.time(), 300, "local noon is the DAY pin")
DayNight.hours = function() return 18 end
T.eq(DayNight.time(), 600, "six in the evening is DUSK")
DayNight.hours = function() return 0 end
T.eq(DayNight.time(), 900, "midnight is mid-night")
DayNight.hours = function() return 6.5 end
T.check(math.abs(DayNight.time() - 25) < 1e-9,
"half past six in the morning is just after dawn")
-- and stepping from SYNC onto CYCLE picks up from the sky already showing
DayNight.update(0)
DayNight.setting:sync("cycle")
DayNight.update(0)
T.check(math.abs(DayNight.clock - 25) < 1e-9,
"CYCLE picks up from wherever SYNC's sky already was")
DayNight.hours = hoursWas
T.eq(DayNight.setting.values[#DayNight.setting.values], "cycle",
"cycle stays the ladder's last rung -- the FULL preset reaches for it "
.. "by position")
-- arriving at FULL sets the clock going
local Game = require("src.core.Game")
local hadSave = Game.save
@@ -2098,8 +2130,8 @@ T.eq(DayNight.setting:get(), "cycle",
"FULL switches DAYTIME to CYCLE -- the diorama gets its running sky")
Game.save = hadSave
-- put the room back the way it was found
DayNight.setting:sync("day")
-- put the room back the way it was found (SYNC is the shipped default)
DayNight.setting:sync("sync")
DayNight.clock = 300
DayNight.applyRig(false)
Voxel3D.tint = { 1, 1, 1 }