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

clock.rs — Kinetic Network Timekeeping (The Crystal Lexicon)

Cratekinetic-types
Stage1 of N
Reading time~10 minutes
Depends onNothing — this module is self-contained

What Is This?

clock.rs defines how Kinetic measures and expresses time on its own terms. Rather than leaning on Unix timestamps (seconds since January 1, 1970), Kinetic has its own named time units called The Crystal Lexicon: Kyn, Facet, Prism, Matrix, Lattice, and Apex. The KineticTime struct holds a decoded snapshot of exactly where the network is in that hierarchy at any given moment.

-> See: kinetic-types/src/clock.rs — Lines 1–12 (module-level doc comment listing all six units)


Why Kinetic Needs This

Without a custom time system, every frontend, explorer, and monitoring tool would independently interpret raw beacon numbers differently. You’d get one dashboard showing Unix timestamps, another showing block heights, and a third showing something else entirely. That confusion is a support nightmare and it makes Kinetic look like a generic chain rather than a purpose-built network.

There is also a practical reason: Kinetic’s consensus engine works in beacon slots — absolute counters that tick every 3 seconds. Those raw counters mean nothing to a human. The Crystal Lexicon translates them into something a user can read: “Prism 14, Facet 3 (Kyn 401)” instead of “412,601 beacons since genesis.” The time system is also a guard against confusion with wall-clock time — a Prism is not a “day” in the POSIX sense; it is exactly 28,800 Kyns, which happens to equal 24 hours of real time. Naming it differently keeps Kinetic network time and system time clearly separated in code and in conversation.


How It Works

The Six Units — sizes and rationale

The Crystal Lexicon is a strict hierarchy. Each unit is a fixed multiple of the one below it:

UnitIn KynsIn Real Time
Kyn13 seconds
Facet1,2001 hour
Prism28,8001 day
Matrix201,6001 week (7 Prisms)
Lattice864,0001 month (30 Prisms)
Apex10,512,0001 year (365 Prisms)

The Kyn is the heartbeat. Every time the network’s consensus engine produces a beacon, one Kyn has passed. At 3 seconds per Kyn, this is a deliberate engineering choice: it is long enough for a message to propagate across the globe, but short enough that the network feels responsive. Everything above a Kyn is just grouping those heartbeats into human-friendly containers.

-> See: kinetic-types/src/clock.rs — Lines 7–12 (the hierarchy comment block)


The KineticTime struct

-> See: kinetic-types/src/clock.rs — Lines 26–36

KineticTime has four fields:

  • total_kyns — the raw count of Kyns elapsed since the network’s genesis point. This is the authoritative number; all other fields are derived from it.
  • prism — how many complete Prisms (days) have passed since genesis. Not the prism-of-the-year; the total prism count from day zero.
  • facet — how many complete Facets (hours) have passed within the current incomplete Prism. This is always in the range 0–23.
  • kyn — how many complete Kyns have passed within the current incomplete Facet. Always in the range 0–1199.

Think of it like a digital clock. The hours hand doesn’t keep counting past 23; it resets at midnight. Same principle here: facet resets every Prism, kyn resets every Facet. The only field that never resets is total_kyns.

The #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] line above the struct tells Rust to auto-generate several standard behaviours. See RUST_CONCEPTS.md — #[derive] for what each of those means.


from_kyn() — converting a raw beacon number into human time

-> See: kinetic-types/src/clock.rs — Lines 44–68

This is the only constructor. It takes two arguments:

  • current_kyn — the absolute beacon number the network is at right now.
  • genesis_kyn — the beacon number at which this particular chain’s counting started. (More on why this matters in a moment.)

Important

The function has a safety check first: if current_kyn is somehow less than genesis_kyn (the clock hasn’t reached genesis yet, or the inputs are mismatched), the function returns a zeroed-out KineticTime rather than panicking or underflowing. This matters because u64 cannot go negative — subtracting a larger number from a smaller one would wrap around to an enormous number and silently corrupt the time. The guard prevents that entirely.

The arithmetic uses basic integer division (/) and remainders (%) in two passes to extract the full Prisms, Facets, and Kyns from the total elapsed kyns.

Concrete example — total_kyns = 50,000:

  • 50,000 / 28,800 = 1 complete Prism.
  • 21,200 Kyns remain.
  • 21,200 / 1,200 = 17 complete Facets.
  • 800 Kyns remain.
  • Display: “Prism 1, Facet 17 (Kyn 800)”

In real time, that 50,000 Kyn mark is 1 day, 17 hours, and 40 minutes after genesis (800 Kyns × 3 seconds = 2,400 seconds = 40 minutes).


Why genesis_kyn exists

The consensus engine counts beacons from the very start of the software’s life. But Kinetic may have multiple environments: the main network, a testnet that launched later, a devnet that launched later still. Each of those networks has its own genesis point within the global beacon stream. Without genesis_kyn, every environment would have to pretend it started at beacon zero, which would make a testnet launched at beacon 1,000,000 appear to be almost 35 days old from the very first moment. By subtracting genesis_kyn, the clock always reads “how long since this network started” rather than “how long since the software was first compiled.”

-> See: kinetic-types/src/clock.rs — Lines 44–52 (the guard and the subtraction)


matrix(), lattice(), apex() — computed, not stored

-> See: kinetic-types/src/clock.rs — Lines 70–83

These three methods return the week, month, and year counts respectively. They are not fields in the struct. Instead, they are calculated on demand from self.prism:

#![allow(unused)]
fn main() {
matrix()  ->  self.prism / 7
lattice() ->  self.prism / 30
apex()    ->  self.prism / 365
}

The reason they are methods instead of stored fields is that storing them would be redundant. If you know prism, you can always derive the others with one integer division. Storing them alongside prism would mean four numbers that must all be kept in sync — and every place that creates or modifies a KineticTime would have to update all four correctly. One source of truth (prism) is simpler, safer, and uses less memory. This pattern — “store the minimum, derive the rest” — is a common design discipline in Rust and in systems programming generally.

These methods will mainly be used by analytics layers: “how many Apexes has this validator been active?” or “show me all events within Lattice 3.”


to_display_string() — human-readable output

-> See: kinetic-types/src/clock.rs — Lines 85–91

This method produces a String formatted as:

#![allow(unused)]
fn main() {
"Prism 1, Facet 17 (Kyn 800)"
}

It uses the format!() macro — Rust’s way of building strings with embedded values. See RUST_CONCEPTS.md — format!() for details.

This string is designed for UIs: block explorers, node dashboards, wallet history screens. It shows the three most human-relevant units (Prism = which day, Facet = which hour, Kyn = sub-hour precision) without overwhelming the user with total_kyns or matrix/lattice/apex counts. Those larger and smaller values are available on the struct and via methods if a UI wants them.


Key Pieces

NameTypeLocationWhat it does
KineticTimestructclock.rs:27–36Holds a decoded snapshot of Kinetic network time in Crystal Lexicon units
from_kyn()methodclock.rs:44–68Primary constructor — converts raw beacon number + genesis offset into a KineticTime
prismfield (u64)clock.rs:29Total Prisms (days) elapsed since genesis — the anchor field everything else derives from
facetfield (u64)clock.rs:31Facets (hours) within the current Prism, always 0–23
kynfield (u64)clock.rs:33Kyns (3-second beats) within the current Facet, always 0–1199
total_kynsfield (u64)clock.rs:35Raw elapsed Kyn count — the single authoritative number
matrix()methodclock.rs:71–73Returns completed weeks since genesis (prism / 7)
lattice()methodclock.rs:76–78Returns completed months since genesis (prism / 30)
apex()methodclock.rs:81–83Returns completed years since genesis (prism / 365)
to_display_string()methodclock.rs:86–91Formats time as branded, human-readable string for UIs

How This Connects to the Rest of Kinetic

KineticTime is purely a data type — it does not talk to the network, read files, or produce side effects. Its job is to be created, carried around, and read. That means it will appear as a field or return value in many other crates.

FORWARD DEPENDENCY: kinetic-kid — the KID node will produce the raw current_kyn value from its consensus engine and pass it to from_kyn() to build a KineticTime for inclusion in block headers and status responses.

FORWARD DEPENDENCY: kinetic-verify — the verifier will likely use KineticTime to timestamp proofs and check whether events fall within expected time windows.

FORWARD DEPENDENCY: Explorer and frontend tooling — to_display_string() exists specifically for this layer. Any dashboard that shows “current network time” will call this method and render its output directly.

The Serialize and Deserialize derives (from the serde crate) mean that KineticTime can be converted to and from JSON automatically. This is how it will travel over the network — a node serializes it to JSON, sends it over HTTP or WebSocket, and the receiving frontend deserializes it back into a KineticTime struct without any custom parsing code.


Quick Reference

1 Kyn     = 3 seconds       (network heartbeat)
1 Facet   = 1,200 Kyns      (1 hour)
1 Prism   = 28,800 Kyns     (1 day)
1 Matrix  = 7 Prisms        (1 week)   — computed via .matrix()
1 Lattice = 30 Prisms       (1 month)  — computed via .lattice()
1 Apex    = 365 Prisms      (1 year)   — computed via .apex()

Constructor: KineticTime::from_kyn(current_kyn, genesis_kyn)
Display:     my_time.to_display_string()  ->  "Prism N, Facet N (Kyn N)"

Fields stored:   prism, facet, kyn, total_kyns
Fields derived:  matrix(), lattice(), apex()

Guard: if current_kyn < genesis_kyn -> returns all zeros, never panics

Open Questions / Things to Revisit

Note

  • Leap seconds and clock drift: Kinetic time is defined purely by beacon count. If the real-world 3-second interval ever drifts (node clock skew, consensus delays), Kinetic time and wall-clock time will diverge silently. There is no correction mechanism in this module. Worth flagging for production monitoring.
  • Lattice is 30 Prisms, not a calendar month: Real months have 28–31 days. Kinetic Lattices are always exactly 30 Prisms. This simplifies math but means “Lattice 1” does not map cleanly to “February 2027.” Explorers should clarify this distinction in their UI copy.
  • apex() denominator is 365, not 365.25: Real years have leap years. Kinetic Apexes are exactly 365 Prisms. Over four years, Kinetic time will fall roughly one day behind the Gregorian calendar. Likely intentional (simplicity), but it should be documented in the network spec.
  • No locale support in to_display_string(): If frontends want to localize (e.g., “Day 2, Hour 5, Beat 800” in another language), they will need to build their own formatter on top of the raw struct fields.
  • #[allow(clippy::absurd_extreme_comparisons)] on line 43: This lint suppression is needed because Clippy misreads the current_kyn < genesis_kyn guard as impossible (both are u64). The guard is meaningful and correct — it prevents a silent underflow wrap. Keep an eye on this if the argument types ever change.