aboutsummaryrefslogtreecommitdiff
path: root/time
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-04-22 07:51:18 +0530
committerBobby <[email protected]>2026-04-22 07:51:18 +0530
commit6e8adbaabedba12e81bf0fdfe3dc42108255bd11 (patch)
tree6d0cb2f5d6d029ea93432a1f906ab74bcdfb7917 /time
parent22912c4af19b9055ed95779c4d16020fe3a449eb (diff)
downloadhollowdark-6e8adbaabedba12e81bf0fdfe3dc42108255bd11.tar.xz
hollowdark-6e8adbaabedba12e81bf0fdfe3dc42108255bd11.zip
Migrate remaining relative imports to @hollowdark/*; strip //-comments and doc references, JSDoc-only
Diffstat (limited to 'time')
-rw-r--r--time/calendar.ts67
-rw-r--r--time/gameTime.ts21
-rw-r--r--time/granularity.ts20
-rw-r--r--time/speed.ts12
4 files changed, 72 insertions, 48 deletions
diff --git a/time/calendar.ts b/time/calendar.ts
index ce61de4..e2f229f 100644
--- a/time/calendar.ts
+++ b/time/calendar.ts
@@ -1,8 +1,7 @@
/**
- * The Hollowdark calendar: 12 months × 30 days + a 5-day year-end festival,
- * 365 days per year, 7-day weeks.
+ * Number of days in a regular month.
*
- * Month order (per docs/01-world.md and style-bible/00-style-bible.md):
+ * Month order:
*
* spring 1 Thawing 2 Greening 3 Blossomtide
* summer 4 Highsun 5 Amberhaze 6 Harvestmark
@@ -10,20 +9,30 @@
* winter 10 Rainfall 11 Hollowdark 12 Rimefrost
* festival 13 Year's End Festival (5 days)
*
- * The year begins with Thawing (spring) and closes with the festival,
- * which sits between Rimefrost and the next year's Thawing. This matches
- * the style bible's cold-half / warm-half grouping and the "year-end
- * festival" phrasing in world lore.
+ * The year begins with Thawing and closes with the festival, which sits
+ * between Rimefrost and the next year's Thawing.
*/
-
export const DAYS_PER_MONTH = 30
+
+/** Twelve named months — all except the festival. */
export const REGULAR_MONTH_COUNT = 12
+
+/** Days in the Year's End Festival — the thirteenth "month". */
export const FESTIVAL_DAYS = 5
+
+/** Month index for the festival. */
export const FESTIVAL_MONTH = 13
-export const MONTHS_PER_YEAR = REGULAR_MONTH_COUNT + 1 // 12 regular + festival
-export const DAYS_PER_YEAR = REGULAR_MONTH_COUNT * DAYS_PER_MONTH + FESTIVAL_DAYS // 365
+
+/** Total month slots in the calendar cycle (12 regular + festival). */
+export const MONTHS_PER_YEAR = REGULAR_MONTH_COUNT + 1
+
+/** Days in a year: 12 × 30 + 5 = 365. */
+export const DAYS_PER_YEAR = REGULAR_MONTH_COUNT * DAYS_PER_MONTH + FESTIVAL_DAYS
+
+/** Seven-day weeks. */
export const DAYS_PER_WEEK = 7
+/** Month names in calendar order. Index 0 is Thawing; index 12 is the festival. */
export const MONTH_NAMES = [
'Thawing',
'Greening',
@@ -40,23 +49,25 @@ export const MONTH_NAMES = [
"Year's End Festival"
] as const
+/** The named-month type derived from `MONTH_NAMES`. */
export type MonthName = (typeof MONTH_NAMES)[number]
+/** Four regular seasons plus a distinct `festival` tag for the year-end. */
export type Season = 'spring' | 'summer' | 'autumn' | 'winter' | 'festival'
const SEASON_BY_MONTH: readonly Season[] = [
- 'spring', // Thawing
- 'spring', // Greening
- 'spring', // Blossomtide
- 'summer', // Highsun
- 'summer', // Amberhaze
- 'summer', // Harvestmark
- 'autumn', // Firstfall
- 'autumn', // Stormturn
- 'autumn', // Ashfall
- 'winter', // Rainfall
- 'winter', // Hollowdark
- 'winter', // Rimefrost
+ 'spring',
+ 'spring',
+ 'spring',
+ 'summer',
+ 'summer',
+ 'summer',
+ 'autumn',
+ 'autumn',
+ 'autumn',
+ 'winter',
+ 'winter',
+ 'winter',
'festival'
]
@@ -66,21 +77,33 @@ function validateMonth(monthIndex: number): void {
}
}
+/**
+ * Return the named month for a 1-based month index. Throws on invalid input.
+ * @param monthIndex 1 (Thawing) through 13 (Year's End Festival).
+ */
export function monthName(monthIndex: number): MonthName {
validateMonth(monthIndex)
return MONTH_NAMES[monthIndex - 1] as MonthName
}
+/**
+ * Return the season label for a 1-based month index. The festival has its
+ * own tag ('festival') rather than reusing one of the four seasons.
+ */
export function monthSeason(monthIndex: number): Season {
validateMonth(monthIndex)
return SEASON_BY_MONTH[monthIndex - 1] as Season
}
+/**
+ * Days in the given month. 30 for regular months, 5 for the festival.
+ */
export function daysInMonth(monthIndex: number): number {
validateMonth(monthIndex)
return monthIndex === FESTIVAL_MONTH ? FESTIVAL_DAYS : DAYS_PER_MONTH
}
+/** True when the supplied month index is the festival slot. */
export function isFestival(monthIndex: number): boolean {
return monthIndex === FESTIVAL_MONTH
}
diff --git a/time/gameTime.ts b/time/gameTime.ts
index 2f2fed7..adebc4f 100644
--- a/time/gameTime.ts
+++ b/time/gameTime.ts
@@ -8,16 +8,15 @@ import {
REGULAR_MONTH_COUNT,
daysInMonth,
monthName
-} from './calendar'
+} from '@hollowdark/time/calendar'
/**
* A position in game time. Immutable — all arithmetic returns a new value.
*
- * year integer (negative allowed for pre-1111 historical events)
+ * year integer (negative allowed for pre-epoch historical events)
* month 1..12 for regular months, 13 for year-end festival
* day 1..30 for regular months, 1..5 for festival
* tickOfDay 0 outside crisis mode; crisis mode subdivides the day
- * (docs/22-crisis-mode.md)
*/
export interface GameTime {
readonly year: number
@@ -61,23 +60,21 @@ function absoluteDays(time: GameTime): number {
}
function fromAbsoluteDays(abs: number, tickOfDay: number): GameTime {
- // Guard against non-integer arithmetic drift — time is whole days only,
- // tickOfDay handles sub-day resolution in crisis mode.
if (!Number.isFinite(abs)) {
throw new Error(`Non-finite absolute day count: ${abs}`)
}
const year = Math.floor(abs / DAYS_PER_YEAR)
- const rem = abs - year * DAYS_PER_YEAR // 0..364
- const doy = rem + 1 // 1..365
- const festivalStart = REGULAR_MONTH_COUNT * DAYS_PER_MONTH + 1 // 361
+ const rem = abs - year * DAYS_PER_YEAR
+ const doy = rem + 1
+ const festivalStart = REGULAR_MONTH_COUNT * DAYS_PER_MONTH + 1
let month: number
let day: number
if (doy >= festivalStart) {
month = FESTIVAL_MONTH
- day = doy - festivalStart + 1 // 1..5
+ day = doy - festivalStart + 1
} else {
- month = Math.floor((doy - 1) / DAYS_PER_MONTH) + 1 // 1..12
- day = ((doy - 1) % DAYS_PER_MONTH) + 1 // 1..30
+ month = Math.floor((doy - 1) / DAYS_PER_MONTH) + 1
+ day = ((doy - 1) % DAYS_PER_MONTH) + 1
}
return { year, month, day, tickOfDay }
}
@@ -105,7 +102,6 @@ export function addMonths(time: GameTime, months: number): GameTime {
if (!Number.isInteger(months)) {
throw new Error(`addMonths requires an integer (got ${months})`)
}
- // Zero-based cycle arithmetic: months are 1..13, so we work in 0..12.
const totalCycles = (time.month - 1) + months
const yearDelta = Math.floor(totalCycles / MONTHS_PER_YEAR)
const monthIndex = ((totalCycles % MONTHS_PER_YEAR) + MONTHS_PER_YEAR) % MONTHS_PER_YEAR
@@ -171,7 +167,6 @@ export function toAbsoluteDays(time: GameTime): number {
return absoluteDays(time)
}
-// Re-exports for convenience at the 'time' import.
export {
DAYS_PER_MONTH,
DAYS_PER_WEEK,
diff --git a/time/granularity.ts b/time/granularity.ts
index 0643c47..1acb122 100644
--- a/time/granularity.ts
+++ b/time/granularity.ts
@@ -1,12 +1,7 @@
/**
- * Tick granularity by life stage.
- *
- * One tick represents a different span of time depending on the character's
- * age — infancy advances in years because there isn't weekly texture worth
- * resolving, adulthood in weeks because that's the rhythm the design lives
- * at. See docs/05-time-system.md and ARCHITECTURE.md §5.
+ * The eight life stages a character moves through. Used to pick how much
+ * wall-clock time one simulation tick represents at a given age.
*/
-
export type LifeStage =
| 'infancy'
| 'early_childhood'
@@ -17,8 +12,15 @@ export type LifeStage =
| 'late_adult'
| 'elderly'
+/** How much game time one tick advances. */
export type TickUnit = 'year' | 'season' | 'month' | 'week'
+/**
+ * Tick granularity per life stage. Infancy advances in years because
+ * there isn't weekly texture worth resolving; adulthood in weeks because
+ * that's the rhythm the design lives at; elderly in months as the pace
+ * slows again.
+ */
export const TICK_UNIT_BY_LIFE_STAGE: Readonly<Record<LifeStage, TickUnit>> = {
infancy: 'year',
early_childhood: 'season',
@@ -30,6 +32,9 @@ export const TICK_UNIT_BY_LIFE_STAGE: Readonly<Record<LifeStage, TickUnit>> = {
elderly: 'month'
}
+/**
+ * Bucket an age in whole years into its life stage. Throws on negative age.
+ */
export function lifeStageForAge(ageYears: number): LifeStage {
if (ageYears < 0) throw new Error(`Invalid age: ${ageYears}`)
if (ageYears < 3) return 'infancy'
@@ -42,6 +47,7 @@ export function lifeStageForAge(ageYears: number): LifeStage {
return 'elderly'
}
+/** Shortcut: map an age directly to its tick unit. */
export function tickUnitForAge(ageYears: number): TickUnit {
return TICK_UNIT_BY_LIFE_STAGE[lifeStageForAge(ageYears)]
}
diff --git a/time/speed.ts b/time/speed.ts
index 9a9bb11..d5cba2e 100644
--- a/time/speed.ts
+++ b/time/speed.ts
@@ -1,11 +1,11 @@
/**
- * Player-controlled simulation speed. Only three states ever exist
- * (docs/05-time-system.md): time is stopped, running at reading pace,
- * or running fast with compressed flow.
- *
- * Scenes auto-set the effective speed to 'paused'; the intended speed is
- * preserved so the simulation returns to it when the scene resolves.
+ * Player-controlled simulation speed. Only three states ever exist: time
+ * is stopped, running at reading pace, or running fast with compressed
+ * flow. Scenes auto-set the effective speed to `paused`; the intended
+ * speed is preserved so the simulation returns to it when the scene
+ * resolves.
*/
export type Speed = 'paused' | 'play' | 'fast'
+/** The three speeds, in canonical order. */
export const SPEEDS: readonly Speed[] = ['paused', 'play', 'fast']