clock.rs — Kinetic Network Timekeeping (The Crystal Lexicon)
| Crate | kinetic-types |
| Stage | 1 of N |
| Reading time | ~10 minutes |
| Depends on | Nothing — 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:
| Unit | In Kyns | In Real Time |
|---|---|---|
| Kyn | 1 | 3 seconds |
| Facet | 1,200 | 1 hour |
| Prism | 28,800 | 1 day |
| Matrix | 201,600 | 1 week (7 Prisms) |
| Lattice | 864,000 | 1 month (30 Prisms) |
| Apex | 10,512,000 | 1 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_kynis somehow less thangenesis_kyn(the clock hasn’t reached genesis yet, or the inputs are mismatched), the function returns a zeroed-outKineticTimerather than panicking or underflowing. This matters becauseu64cannot 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,200Kyns remain.21,200 / 1,200= 17 complete Facets.800Kyns 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
| Name | Type | Location | What it does |
|---|---|---|---|
KineticTime | struct | clock.rs:27–36 | Holds a decoded snapshot of Kinetic network time in Crystal Lexicon units |
from_kyn() | method | clock.rs:44–68 | Primary constructor — converts raw beacon number + genesis offset into a KineticTime |
prism | field (u64) | clock.rs:29 | Total Prisms (days) elapsed since genesis — the anchor field everything else derives from |
facet | field (u64) | clock.rs:31 | Facets (hours) within the current Prism, always 0–23 |
kyn | field (u64) | clock.rs:33 | Kyns (3-second beats) within the current Facet, always 0–1199 |
total_kyns | field (u64) | clock.rs:35 | Raw elapsed Kyn count — the single authoritative number |
matrix() | method | clock.rs:71–73 | Returns completed weeks since genesis (prism / 7) |
lattice() | method | clock.rs:76–78 | Returns completed months since genesis (prism / 30) |
apex() | method | clock.rs:81–83 | Returns completed years since genesis (prism / 365) |
to_display_string() | method | clock.rs:86–91 | Formats 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 thecurrent_kyn < genesis_kynguard as impossible (both areu64). The guard is meaningful and correct — it prevents a silent underflow wrap. Keep an eye on this if the argument types ever change.