Calendar: convert ticks to an in-game date, and fix the units of time #104
No reviewers
Labels
No labels
assets
bug
duplicate
enhancement
help wanted
invalid
question
testing
ui
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
liamjd/UntitledColonyBuilder!104
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "87-game-calendar-conversations"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds the time substrate the threat timeline and forecast UI will be built on: a calendar that projects a tick onto a moon-date, the pacing constants as documented decisions, and a HUD panel showing both the date and contract progress.
GameClock.advance()is unchanged. Nothing here adds state to the simulation — the calendar is a pure function of the tick counter from #65.Design
The calendar is a projection, never an accumulator. All arithmetic happens in ticks;
GameDateis the picture taken of the result, is read-only, and has no "add a day" method. Two code paths for "what time is it" is how a calendar stops agreeing with itself, so there is only one — which is what makes the round-trip tests meaningful rather than decorative.Absolute and run-relative ticks are different numbers. Absolute counts from the calendar epoch (Y1 M1 D1 00:00) and is what the threat timeline will be placed on. Run-relative is
GameClock.now, zeroed at landing, and is what contract progress counts in. GDScript types both asintand will happily let them be mixed, so the defence is structural:run_epoch_tick + GameClock.nowappears exactly once, inGameState.absolute_tick().To keep that visible at call sites,
GameClock.tick's payload is renamednow→run_tickand documented as run-relative. The calendar panel takes it as_run_tick— the underscore marks that ignoring it is a choice, since it converts viaGameStateinstead.debug_layergenuinely wants the raw run tick and still uses the payload.TICKS_PER_DAYis not derived fromticks_per_second. Design pacing and simulation fidelity are different questions; deriving one from the other means tuning the tick rate for pawn smoothness silently rewrites every authored duration in the game.test_the_day_length_does_not_depend_on_the_simulation_rateasserts the separation directly, andTICKS_PER_DAYhas moved offGameClockontoGameCalendarso there is one copy rather than two (knock-on for #88: readGameCalendar.TICKS_PER_DAY).Bug found and fixed
Contract progress was computed as
date.month - contract_start_date.month. That reads "Month 0 of 6" on landing day, and breaks outright across a year boundary — a contract landing in M10 computes1 - 10 = -9in its second year. Now counted from elapsed ticks (GameClock.now / TICKS_PER_MONTH), which is both correct across the wrap and the right tick space for the question.GameStatewas also hand-building aGameDateviaGameDate.new(0, 1, 1, 1, 1, 0, 1, 1), which the calendar explicitly warns against — a constructed date can hold a field combination the calendar cannot produce. Now projected throughdate_at().AC coverage (#87)
GameDateconvertingnow→ day / hour / phase, andticks_until_day()backticks_per_secondsettable only before the first tickGameClock.reset()zeroes_nowand_accumulatorticks_untilagreeing with the forward conversiondocs/colony-game-time-and-calendar.mdVerification
451 tests pass (
--headless -s addons/gut/gut_cmdln.gd), 1 pending — the pre-existing#85placeholder intest_task_tree.gd, unrelated.Six new clock tests cover the two AC bullets that had none. The one worth calling out is
test_reset_discards_the_banked_fraction: everywhere else in that file the accumulator is preserved on purpose, soreset()has to opt out explicitly, and a leftover part-tick makes the next colony's first tick land early.now == 0does not catch it.The two rejection tests use GUT's
assert_push_error(), so they pin the error text rather than suppressing it.Visual check, headed.
gl_compatibilityrenders nothing headless, so the panel was verified by running the game and reading the values back. At tick 525, with the run seeded to Y1 M3 D1 12:00 (run_epoch_tick= 24,600):22:0001/03/1DUSK_HOUR→ night ✓Month 1 of 6The last row is the load-bearing one: the date reports month 3 (seeded epoch) while progress reports month 1 (run-relative), from the same tick. If either read the wrong tick space they would agree, and they do not. Real-time pacing also checks out — 525 ticks is ~52 s at 1x, and 10.5 in-game hours have passed since a 12:00 landing, matching the documented 5 s per hour.
Caveats and follow-ups
PanelContainer, which sizes both children to the same rect, so the label draws over the bar — intended. But at month 1 of 6 the fill is 0.7% of the width, so there is little to see for the first twenty real minutes.GameState.contract_start_dateis now unread, since the progress fix removed its only consumer. Left in place as it is likely wanted by the forecast UI; cut it if not.ticks_per_secondin source, which is the wrong knob — it is an engineering constant, rejected mid-run, and pinned by a test. That test caught exactly this during development.Closes #87
🤖 Generated with Claude Code