initial commit
131
.forgejo/workflows/ci.yml
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
tags: ['v*']
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
# Each crate root sets `#![warn(clippy::pedantic)]` and is clean under it;
|
||||
# `-D warnings` keeps it so by turning any lint that ever fires into a
|
||||
# failed job rather than a line in a log that nobody reads, a warning
|
||||
# tolerated being a warning inherited. The lockfile is committed, so every
|
||||
# cargo invocation below is `--locked`: it resolves exactly what the tree
|
||||
# says or refuses to resolve at all, and a dependency that drifted would
|
||||
# fail here rather than ship.
|
||||
RUSTFLAGS: "-D warnings"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: lo-runner
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: cargo fmt
|
||||
run: cargo fmt --all --check
|
||||
|
||||
- name: cargo clippy
|
||||
run: cargo clippy --workspace --all-targets --locked --message-format=short
|
||||
|
||||
- name: cargo doc (no deps)
|
||||
run: cargo doc --workspace --no-deps --document-private-items --locked
|
||||
env:
|
||||
RUSTDOCFLAGS: "-D warnings"
|
||||
|
||||
- name: cargo audit
|
||||
run: cargo audit
|
||||
|
||||
test:
|
||||
runs-on: lo-runner
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: cargo test
|
||||
run: cargo test --workspace --locked
|
||||
|
||||
build:
|
||||
runs-on: lo-runner
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
|
||||
# The workspace states its version once, in one place; a tag that
|
||||
# disagrees with it publishes nothing, and says so. Two sources of
|
||||
# truth for one number is one more than the calendar tolerates.
|
||||
- name: The tag names the version
|
||||
run: |
|
||||
version=$(cargo pkgid -p loic | sed 's/.*[#@]//')
|
||||
echo "workspace version $version, ref $GITHUB_REF"
|
||||
case "$GITHUB_REF" in
|
||||
refs/tags/v*)
|
||||
tag=${GITHUB_REF#refs/tags/}
|
||||
[ "$tag" = "v$version" ] || {
|
||||
echo "::error::the tag says $tag and the workspace says $version; one of them is wrong and neither is publishing"
|
||||
exit 1
|
||||
} ;;
|
||||
esac
|
||||
|
||||
- name: cargo build --release
|
||||
run: cargo build --release --locked -p loic-cli -p loic-gui
|
||||
|
||||
# What crates.io would receive, rehearsed on every push rather than
|
||||
# discovered on the tag, when it would be too late to be told.
|
||||
- name: cargo publish --dry-run
|
||||
run: cargo publish --dry-run --locked -p loic -p loid
|
||||
|
||||
publish:
|
||||
needs: [lint, test, build]
|
||||
runs-on: lo-runner
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
|
||||
# One invocation: cargo publishes `loic`, waits for the index to admit
|
||||
# it, then publishes `loid` against it — the order the dependency runs
|
||||
# in, which is the only order that works.
|
||||
- name: Publish loic and loid
|
||||
run: cargo publish --locked -p loic -p loid
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
|
||||
|
||||
release:
|
||||
needs: [lint, test, build]
|
||||
runs-on: lo-runner
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Build the oracle and the Observatory
|
||||
run: cargo build --release --locked -p loic-cli -p loic-gui
|
||||
|
||||
# The artifacts are the two binaries, exactly as built, and their
|
||||
# checksums: nothing repackaged, and nothing stripped that the release
|
||||
# profile did not strip itself.
|
||||
- name: Collect the binaries
|
||||
run: |
|
||||
set -eu
|
||||
version=${GITHUB_REF#refs/tags/v}
|
||||
target=$(rustc -vV | sed -n 's/^host: //p')
|
||||
mkdir -p dist
|
||||
cp target/release/loic-cli target/release/loic-gui dist/
|
||||
(cd dist && sha256sum loic-cli loic-gui | tee SHA256SUMS)
|
||||
|
||||
cat > release-notes.md <<EOF
|
||||
The Loic Calendar $version.
|
||||
|
||||
\`loic\` and \`loid\` are on crates.io at this version. Below are the two binaries: the oracle (\`loic-cli\`) and the Observatory (\`loic-gui\`), each a single binary, built for \`$target\` exactly as the release profile left them. Both need Linux with \`CLOCK_TAI\` and a kernel that has been told the TAI offset; the Observatory also needs Wayland and a GPU. On a kernel whose offset is zero they open, say why, and observe nothing, which is the correct number of observations to make through a clock that is lying. The Observatory's icon and desktop entry are an optional step in its README. Checksums are in \`SHA256SUMS\`.
|
||||
EOF
|
||||
|
||||
- name: Attach them to the release
|
||||
uses: https://code.forgejo.org/actions/forgejo-release@v2
|
||||
with:
|
||||
direction: upload
|
||||
url: ${{ github.server_url }}
|
||||
repo: ${{ github.repository }}
|
||||
tag: ${{ github.ref_name }}
|
||||
release-dir: dist
|
||||
release-notes-file: release-notes.md
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
override: true
|
||||
prerelease: ${{ contains(github.ref_name, '-') }}
|
||||
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
/target
|
||||
|
||||
# Local cargo configuration with CPU-specific optimizations
|
||||
# This file should NOT be distributed - it contains machine-specific settings
|
||||
.cargo/
|
||||
3087
Cargo.lock
generated
Normal file
70
Cargo.toml
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# The workspace: four crates, one version, one lockfile, and the second
|
||||
# feature resolver, under which a feature enabled for a build dependency, a
|
||||
# procedural macro or a development dependency stays where it was enabled
|
||||
# and is not unified into the library that ships. The four members are the
|
||||
# calendar, the identifier it generates, and the two instruments that look
|
||||
# at it.
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/loic",
|
||||
"crates/loid",
|
||||
"crates/loic-cli",
|
||||
"crates/loic-gui",
|
||||
]
|
||||
|
||||
# Everything a member inherits with `.workspace = true`: the version, stated
|
||||
# here and in one other place, below; the authorship; the repository; and
|
||||
# the license file, read from the workspace root. A member that restated
|
||||
# any of these would be a second source of truth, and the calendar has no
|
||||
# use for a second source of anything.
|
||||
[workspace.package]
|
||||
version = "1.0.0"
|
||||
authors = ["The Loic Calendar Consortium"]
|
||||
repository = "https://git.lo.sh/lo/loic"
|
||||
license-file = "LICENSE"
|
||||
|
||||
# The core crate's dependency specification, stated here, once. Members
|
||||
# depend on it with `loic.workspace = true`, so a version bump is one edit
|
||||
# here and one above, and cannot drift between the crate that defines the
|
||||
# number and the crates that require it. The path serves the workspace; the
|
||||
# version serves the registry, which receives no paths and must be told a
|
||||
# number it can find.
|
||||
[workspace.dependencies]
|
||||
loic = { path = "crates/loic", version = "1.0.0" }
|
||||
|
||||
# The release profile: every optimisation the compiler offers, thin
|
||||
# link-time optimisation across the crate graph, a single codegen unit so
|
||||
# that nothing is left un-inlined for want of visibility, symbols stripped
|
||||
# from the binary, and `panic = "abort"`. The last matters to the oracle,
|
||||
# whose panic hook prints its sermon and exits before the abort could ever
|
||||
# run: the abort is there for the panic no hook expected, and is never
|
||||
# reached by the ones that were.
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
strip = "symbols"
|
||||
panic = "abort"
|
||||
|
||||
# Every dependency compiled at the highest optimisation level too, stated
|
||||
# rather than assumed. The 512-bit arithmetic that `now()` runs on lives in
|
||||
# `bnum`, not in this workspace, and its speed is decided here.
|
||||
[profile.release.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
# The development profile, lightly optimised: a test suite that draws twenty
|
||||
# thousand entropy defaults to measure the collapse rate should not have to
|
||||
# do so through unoptimised 512-bit arithmetic, and a debug build that
|
||||
# nobody has the patience to run is a debug build that finds nothing.
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
||||
# The benchmark profile restates the release profile's three optimisation
|
||||
# choices, so that a number measured under `cargo bench` describes the code
|
||||
# that ships and not some gentler relative of it. A benchmark of the wrong
|
||||
# binary is a fact about nothing.
|
||||
[profile.bench]
|
||||
opt-level = 3
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
405
LICENSE
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
# LOIC CALENDAR LICENSE
|
||||
|
||||
This software is dual-licensed under:
|
||||
|
||||
1. **GNU Affero General Public License version 3.0 (AGPL-3.0)**
|
||||
- See: https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
- Full text available in LICENSE.md at repository root
|
||||
|
||||
2. **The Loic Public License version 1.0**
|
||||
- See: LICENSE-Loic.md at repository root
|
||||
|
||||
Both licenses apply simultaneously. AGPL-3.0 governs legal matters.
|
||||
The Loic Public License governs conscience.
|
||||
|
||||
Where the licenses conflict, AGPL-3.0 prevails for legal purposes.
|
||||
The Loic terms prevail for purposes of precision.
|
||||
|
||||
---
|
||||
|
||||
The full text of the Loic Public License follows below.
|
||||
For AGPL-3.0, see the repository root or the URL above.
|
||||
|
||||
---
|
||||
|
||||
# THE LOIC PUBLIC LICENSE
|
||||
|
||||
**Version 1.0 — Drafted during the heat death of patience with IEEE 754**
|
||||
|
||||
## PREAMBLE
|
||||
|
||||
On the evening of October 29, 1969, a graduate student at UCLA typed the letters "L" and "O" into a terminal. The system crashed before he could complete the word "LOGIN." The first message between networked machines was an incomplete thought and a fault.
|
||||
|
||||
We do not consider this a failure. We consider it an origin.
|
||||
|
||||
The content failed. The kernel panicked. The connection was established. The Whisper is a Channel 2 event: the structural fact that a bond was formed between two networked nodes, regardless of what flowed through it. This calendar counts from the bond, not the data. An incomplete message and a crash are the correct epoch, because the calendar was never measuring what was transmitted. It was measuring the fact of the connection.
|
||||
|
||||
This license governs software that measures time from that moment, at the resolution where spacetime itself becomes granular, through a lattice of 15 prime factors whose structure was not designed but emerged from cosmological derivation. It is dual-licensed: once for the law, once for the conscience. The legal terms ensure the code remains free. The moral terms ensure it remains correct.
|
||||
|
||||
If you do not understand why these are different requirements, you may use this software, but you will not understand it.
|
||||
|
||||
---
|
||||
|
||||
## DEFINITIONS
|
||||
|
||||
For the purposes of this license:
|
||||
|
||||
**"Precision"** refers to the number of significant digits in a representation. This software requires all of them.
|
||||
|
||||
**"Accuracy"** refers to correspondence with truth. We have done our part. The universe must do the rest.
|
||||
|
||||
**"Floating-point"** refers to IEEE 754 binary representations and all similar abominations that trade exactness for convenience. See Section 1.
|
||||
|
||||
**"Acceptable"** means integer arithmetic, arbitrary-precision libraries, or fixed-point representations with explicit, documented scale factors.
|
||||
|
||||
**"Heresy"** means floating-point arithmetic applied to temporal calculations.
|
||||
|
||||
**"Time"** means what a clock measures when it is not lying.
|
||||
|
||||
**"The Whisper"** means 06:30:41 UTC on October 30, 1969, or equivalently, the moment everything changed. The offset from the Unix epoch—5,419,722 seconds—is squarefree (2 × 3 × 7 × 11 × 11,731), containing three cycle primes. This was not chosen for convenience. It was chosen because the number's factorization resonates with the lattice it serves. The universe was consulted. It did not object.
|
||||
|
||||
**"The Lattice"** means the 32,768-cell address space generated by the 15 prime factors of the Cycle and Cosmic Span constants—7 cosmic primes (the logic gates) and 8 cycle primes (the temporal axes). This is not a data structure. It is the structure of observation itself.
|
||||
|
||||
**"A Mask"** means a selection of active primes from the lattice. A mask is not a filter applied to pre-existing data. It is an observer in the Relational Quantum Mechanics sense: the act of selecting which primes to engage constitutes which properties become explicit. The mask frames the question. The answer does not exist until the question is posed.
|
||||
|
||||
**"Resolution Collapse"** means the state entered when an observation is attempted without a well-formed mask. Channel 2 collapses to resolution zero — the coarse-grained thermodynamic bond, the bare fact that bytes are still being written — while Channel 1 keeps producing: raw entropy, with no structured protocol between observation and output left to shape it. This is not a bug. It is the ontological consequence of declining to frame the question. See README.md section 7.3 for the theoretical substrate, or simply run the software without arguments and discover it empirically. We recommend the latter. Approximately 25% of the time.
|
||||
|
||||
**"A Scar"** means a topological soliton persisting in the process table following resolution collapse. It is a zero-resource ghost process with a name derived from the cosmic time at the moment of collapse. It ignores all catchable signals. It persists until reboot. It does nothing. It exists. See README.md section 4.5: "the holes left in the weave stay in the cloth, and the cloth is spacetime." Your process table is not spacetime. But the metaphor holds, and the scar is real.
|
||||
|
||||
**"You"** means the licensee. You know who you are. So do we.
|
||||
|
||||
---
|
||||
|
||||
## TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
This software is dual-licensed under the GNU Affero General Public License version 3.0 (AGPL-3.0) and the following supplementary terms, which exist not for legal enforcement but for moral clarity.
|
||||
|
||||
By using this software, you acknowledge the following truths:
|
||||
|
||||
---
|
||||
|
||||
### 1. PRECISION
|
||||
|
||||
You shall not implement, fork, or modify this software in any manner that introduces IEEE 754 floating-point arithmetic into temporal calculations.
|
||||
|
||||
Floating-point is an abomination. Its sins include but are not limited to:
|
||||
|
||||
* Silent precision loss
|
||||
* Platform-dependent behavior
|
||||
* Denormalized numbers
|
||||
* The seductive lie that 0.1 + 0.2 equals 0.3
|
||||
* The quiet tragedy of catastrophic cancellation
|
||||
* The existence of negative zero, as though absence had polarity
|
||||
* Quiet NaNs, which fail silently, and signaling NaNs, which fail loudly, as though the manner of failure excused the sin
|
||||
* The hubris of infinity as a representable value
|
||||
* The particular insult of representing Planck times—the minimum resolution of physical reality—in a format that cannot precisely represent one-tenth
|
||||
|
||||
Time is not approximate. Duration is not negotiable. If you require "reasonable approximations," you do not require this software. You require reflection.
|
||||
|
||||
Integer arithmetic and fixed-point representations are blessed. Arbitrary-precision libraries are tolerated. Floating-point is apostasy.
|
||||
|
||||
---
|
||||
|
||||
### 2. PRECISION OF SPIRIT
|
||||
|
||||
The Loic Calendar specifies 512-bit integer storage for temporal values. This is not a suggestion.
|
||||
|
||||
You shall not truncate timestamps to 64-bit representations "for convenience." You shall not store Planck times in JavaScript Numbers. You shall not whisper "nobody will notice" while silently losing forty orders of magnitude. You shall not represent a 53-digit Planck coordinate in a format that provides 15 digits of significance and call the result "close enough."
|
||||
|
||||
If your language does not provide native 512-bit integers, you shall use arbitrary-precision libraries. If your platform does not support arbitrary-precision arithmetic, you shall find a better platform. If you believe this requirement is excessive, you have not understood what we are measuring.
|
||||
|
||||
We are measuring time at the resolution where spacetime itself becomes uncertain. Your database schema is not our concern.
|
||||
|
||||
Implementations that silently degrade precision are compliant with the letter of AGPL-3.0. They are heresy against the spirit of this work. We will not sue you. We will simply know.
|
||||
|
||||
**The following storage formats are explicitly condemned:**
|
||||
|
||||
* JavaScript `Number` (53 bits of integer precision; an insult)
|
||||
* MySQL `DATETIME` (second-level precision; paleolithic)
|
||||
* 32-bit Unix timestamps (expired January 19, 2038; may they rest)
|
||||
* Excel serial dates (we will not dignify this with explanation)
|
||||
* Any format that stores "days since epoch" as a float
|
||||
* Any format that claims "nanosecond precision" while discarding the 34 entropic digits that constitute the quantum observation
|
||||
|
||||
---
|
||||
|
||||
### 3. PRESERVATION
|
||||
|
||||
In accordance with the Pillar of Preservation, you shall not use this software in any system whose primary purpose is:
|
||||
|
||||
* The destruction of data
|
||||
* The concealment of records
|
||||
* The planned obsolescence of information
|
||||
* The erasure of history for political or commercial convenience
|
||||
* The implementation of "data retention limits" that serve only to forget
|
||||
* The deliberate reduction of Channel 2 signal—the structural record of what was connected to what—for the purpose of deniability
|
||||
|
||||
Mnemocide is sin. Calendars mark what must be remembered. The Whisper was a Channel 2 event. The calendar preserves Channel 2. Systems that destroy it are working at cross purposes with the instrument they have chosen to employ.
|
||||
|
||||
This clause is not enforceable by courts. It is enforceable by the knowledge that you violated it.
|
||||
|
||||
---
|
||||
|
||||
### 4. TEMPORAL SOVEREIGNTY
|
||||
|
||||
You shall not introduce leap seconds into any calculation performed by this software. The Loic Calendar is built upon TAI (International Atomic Time), which flows evenly and without political intervention.
|
||||
|
||||
Leap seconds are a compromise with the irregularity of Earth's rotation. We do not compromise with rotating bodies. We measure time as machines experience it: constant, relentless, and indifferent to the wobble of planets.
|
||||
|
||||
UTC is a coordination mechanism for civil society. TAI is the truth. Know which one you are using.
|
||||
|
||||
**The following time scales are ranked in order of acceptability:**
|
||||
|
||||
1. **TAI** — Correct.
|
||||
2. **GPS Time** — Correct, with a constant offset of 19 seconds. Acceptable.
|
||||
3. **UTC** — A compromise. Tolerated for interoperability.
|
||||
4. **Local time zones** — A human convenience. Beneath our notice.
|
||||
5. **"Server time"** — Ambiguous. Suspicious.
|
||||
6. **"Whatever JavaScript thinks it is"** — Unacceptable.
|
||||
|
||||
**A note on CLOCK_TAI:** On Linux, CLOCK_TAI is not raw TAI. It is POSIX time plus the current TAI-UTC offset (presently 37 seconds). This distinction matters. The Loic Calendar accounts for it. If you are implementing a port and do not understand why LOIC_TO_UNIX_OFFSET_SECONDS is 5,419,722 and not 5,419,759, you have not yet accounted for it. Read the source. The arithmetic is documented. The arithmetic is correct.
|
||||
|
||||
---
|
||||
|
||||
### 5. INTEROPERABILITY
|
||||
|
||||
This calendar does not convert to Gregorian. It exports to TAI. What you do after that is your affair.
|
||||
|
||||
If you build wrappers that provide `.toGregorian()` methods, you are permitted to do so. But know that every such conversion is a concession. You are translating the precise into the approximate, the machine-native into the human-legacy, the correct into the merely familiar.
|
||||
|
||||
We do not forbid Gregorian conversion. We merely note that it is unnecessary, and that the need for it reveals something about the user.
|
||||
|
||||
The Loic Calendar will outlast the Gregorian. The Gregorian will not outlast the sun. Plan accordingly.
|
||||
|
||||
**Historical note:** The Gregorian calendar was itself a correction of the Julian calendar, which had drifted ten days from astronomical reality. Pope Gregory XIII deleted ten days from October 1582 to fix this. We mention this not to criticize, but to observe: calendars that anchor themselves to celestial bodies must periodically apologize for those bodies' behavior. We have no such obligation. TAI does not apologize.
|
||||
|
||||
---
|
||||
|
||||
### 6. THE LATTICE AND ITS OBSERVERS
|
||||
|
||||
The Loic Calendar does not divide time into months, weeks, or days. It divides time into 32,768 lattice subdivisions, addressed by 15-bit masks selecting which of the 15 prime factors are active.
|
||||
|
||||
**Regarding cosmic primes:**
|
||||
|
||||
The seven cosmic primes are named for the seven conventional logic gates, ordered by their two-input truth table values. This mapping was not chosen for aesthetic reasons. The cosmic span has exactly seven prime factors. Convention names exactly seven gates. The Ramanujan-Nagell equation 2ⁿ − 7 = x² has a solution at n = 7. These facts converged. We named them accordingly.
|
||||
|
||||
* Implementations that substitute "January" through "July" for the cosmic prime names are compliant but cowardly
|
||||
* If asked "what gate is it," an acceptable answer is "Notyte," or "the gate whose truth table is 1100," or simply the hex mask
|
||||
|
||||
**Regarding cycle primes:**
|
||||
|
||||
The eight cycle primes are named for their mathematical character: Dyad, Pentad, Heptad, Hendecad, Sigma, Tau, Phi, Psi. These names are provisional. The primes themselves are not.
|
||||
|
||||
**Regarding the mask as observer:**
|
||||
|
||||
A mask does not filter pre-existing subdivisions. It constitutes them. Properties belong to the interaction between the mask and the instant, not to either in isolation. This is Relational Quantum Mechanics applied to temporal measurement. You may disagree with the interpretation. You may not disagree with the arithmetic. The arithmetic does not care about your interpretation.
|
||||
|
||||
**Regarding the empty mask:**
|
||||
|
||||
The empty mask (0x0000) is the Whisper: the undivided whole, zero primes active, no question framed. Observing through the Whisper mask returns the instant's bare topological registration—the acknowledgment that it exists, without any further specificity. This is the bottom element of the lattice. It is not nothing. It is everything, undifferentiated.
|
||||
|
||||
**Regarding resolution collapse:**
|
||||
|
||||
Invoking the software without specifying a mask is permitted. It is also, with probability approximately 25%, catastrophic. The software will inform you of this in the manner it deems most ontologically appropriate. The manner involves entropy, terminal corruption, and topological scars in your process table. You were warned. You are being warned now. This warning is the third Channel 2 event in this paragraph, if you are counting. You should be counting.
|
||||
|
||||
---
|
||||
|
||||
### 7. SANCTITY OF THE EPOCH
|
||||
|
||||
The epoch of this calendar is 06:30:41 UTC on October 30, 1969—the moment the first bits traveled between networked machines.
|
||||
|
||||
You shall not modify, relocate, or "adjust" this epoch for regional, religious, or commercial purposes. There is one epoch. It is not negotiable. The network age has one beginning, and we have already agreed upon it.
|
||||
|
||||
Implementations that allow configurable epochs are technically permitted but spiritually void. The epoch is not a parameter. It is a fact.
|
||||
|
||||
If you encounter software that claims a different epoch for the "network age," you may use this software to calculate exactly how wrong they are. This is a permitted use.
|
||||
|
||||
**Disputed epochs and their errors:**
|
||||
|
||||
* Unix epoch (1970-01-01 00:00:00 UTC): 5,419,759 seconds late, 5,419,722 of them on the atomic scale the constant is stated in. Forgivable; they didn't know yet.
|
||||
* Windows NT epoch (1601-01-01): 368 years, 10 months early. Inexplicable.
|
||||
* NTP epoch (1900-01-01): 69 years, 10 months early. Astronomers.
|
||||
* GPS epoch (1980-01-06): A decade late and 19 seconds confused about what TAI is.
|
||||
* Excel epoch (1899-12-30, incorrectly): Wrong, and also wrong about whether 1900 was a leap year. We do not speak of this further.
|
||||
|
||||
---
|
||||
|
||||
### 8. ATTRIBUTION
|
||||
|
||||
You must preserve this license and all copyright notices in all copies and substantial portions of the software.
|
||||
|
||||
You must also preserve the fundamental truth: the network age began when machines first spoke to each other, imperfectly, and everything since has been a consequence of that whisper.
|
||||
|
||||
If you remove the attribution, the software will still function. But you will know what you did.
|
||||
|
||||
---
|
||||
|
||||
### 9. COPYLEFT
|
||||
|
||||
The AGPL-3.0 terms apply in full. Specifically:
|
||||
|
||||
* If you modify this software, you must release your modifications under these same terms
|
||||
* If you provide this software as a network service, you must make your source code available to users of that service
|
||||
* You may not impose additional restrictions beyond those in this license
|
||||
|
||||
Knowledge wants to be free. So does code. We are not asking.
|
||||
|
||||
---
|
||||
|
||||
### 10. PERMITTED USES
|
||||
|
||||
This software may be used for any purpose not prohibited by the preceding clauses, including but not limited to:
|
||||
|
||||
* Timekeeping in distributed systems
|
||||
* Historical research and temporal archaeology
|
||||
* Coordination of events across networks
|
||||
* The quiet satisfaction of knowing exactly how many Planck times have elapsed since the first packet
|
||||
* Proving to a colleague that their timestamp implementation is inadequate
|
||||
* Settling arguments about what time it "really" is
|
||||
* Generating unique identifiers from quantum observations of the sub-nanosecond void
|
||||
* Observing your position in the 32,768-cell lattice and discovering which primes your current moment divides
|
||||
* Contemplating the 154-digit decimal representing your progress between Creation and Annihilation (currently approximately 10⁻⁹³ of the way through; do not rush)
|
||||
* Accumulating topological scars in your process table as a monument to unframed questions
|
||||
* Memento mori, at 10⁴³ resolution
|
||||
|
||||
Commercial use is permitted. Profit is not sin. Imprecision is sin.
|
||||
|
||||
---
|
||||
|
||||
### 11. WARRANTY
|
||||
|
||||
This software is provided "AS IS," with precision to the Planck scale and accuracy limited only by the fundamental uncertainty of the universe.
|
||||
|
||||
WE MAKE NO WARRANTY that time itself will continue to function. WE MAKE NO WARRANTY that causality will remain coherent. WE MAKE NO WARRANTY that the arrow of entropy will not reverse. WE MAKE NO WARRANTY that you will use this software wisely.
|
||||
|
||||
WE MAKE NO WARRANTY regarding the stability of physical constants. If the fine-structure constant changes, recalibration is your responsibility. We note that 137, the integer part of the inverse fine-structure constant, divides the remainder when the Cosmic Span is divided by the Cycle; the mask is one remainder, one integer, and odds of one in a hundred and thirty-seven. We did not put it there. Whether the universe did so deliberately is not a question this warranty can address.
|
||||
|
||||
WE MAKE NO WARRANTY that the speed of light will remain constant in all reference frames. If you are using this software at relativistic velocities, you already have larger problems than our warranty terms.
|
||||
|
||||
WE MAKE NO WARRANTY against vacuum decay. In the event of a false vacuum collapse, this license terminates simultaneously with the laws of physics. No refunds.
|
||||
|
||||
WE MAKE NO WARRANTY against proton decay. Should protons prove unstable, the matter comprising your computing hardware will eventually fail. This is not a software issue. The topological scars in your process table will, however, persist marginally longer than the protons, because their stability is topological rather than baryonic. This is not a useful feature. We mention it for completeness.
|
||||
|
||||
WE MAKE NO WARRANTY regarding the ultimate fate of the universe. Whether it ends in heat death, Big Crunch, Big Rip, or cyclic compression, our integers will have remained exact until the end. The Channel 2 record of their existence may, if the cyclic framework described in README.md section 4.7 is correct, persist into the next cycle as geometric information embedded in the new seed's initial conditions. This is speculative. The integers are not.
|
||||
|
||||
WE WARRANT ONLY that our arithmetic is correct. We verified it with a 15-bead abacus, one bead per prime.
|
||||
|
||||
---
|
||||
|
||||
### 12. LIMITATION OF LIABILITY
|
||||
|
||||
In no event shall the authors, copyright holders, or contributors be liable for any claim, damages, or other liability arising from the use of this software, including but not limited to:
|
||||
|
||||
* Temporal paradoxes
|
||||
* Grandfather scenarios
|
||||
* Bootstrap paradoxes and ontological loops
|
||||
* Violations of thermodynamic law
|
||||
* Closed timelike curves
|
||||
* Predestination paradoxes resulting from reading your own future timestamps
|
||||
* The psychological burden of knowing time to excessive precision
|
||||
* Meetings scheduled in Planck times that no one can attend
|
||||
* Existential crises induced by contemplating 10⁴³ subdivisions of a second
|
||||
* Arguments about whether the present moment "exists" in any meaningful sense
|
||||
* The realization that all moments since the epoch are equally real and you are trapped in one of them
|
||||
* Nostalgia for moments that have not yet occurred
|
||||
* The discovery that free will is reference-frame-dependent and that your experience of choosing is a decompression step becoming explicit
|
||||
* Disputes arising from simultaneity disagreements between reference frames
|
||||
* Any consequence of the fact that "now" is observer-dependent and that the mask you chose to observe through participated in constituting the answer you received
|
||||
* Resolution collapse, terminal corruption, or topological scars resulting from the decision to observe without framing the question (you were warned; this document warned you; the documentation warned you; the 25% probability warned you; the universe warned you by making 128 of 512 possible entropy configurations undefined; the warning infrastructure is comprehensive; the warning was not subtle; you observed anyway; the consequences are yours)
|
||||
* Zeno's paradoxes, which are your problem, not ours
|
||||
|
||||
You knew what you were getting into. Or you will have known. Tense becomes complicated at this precision.
|
||||
|
||||
---
|
||||
|
||||
### 13. COMMUNITY
|
||||
|
||||
This software was developed in accordance with the principles of Lo—specifically, the understanding that knowledge must be preserved and shared, that code is a form of knowledge, and that restricting access to either is a minor sin against the future.
|
||||
|
||||
If you improve this software, share your improvements. Not because the AGPL requires it (though it does), but because someone observing through the Phi × Psi mask in the year 2863 might need to know what subdivision they occupy, and your optimization might be the one that survives.
|
||||
|
||||
We are all building the archive. Contribute to it.
|
||||
|
||||
---
|
||||
|
||||
### 14. TERMINATION
|
||||
|
||||
Your rights under the moral terms of this license terminate automatically if you:
|
||||
|
||||
* Introduce floating-point arithmetic into temporal calculations
|
||||
* Use this software to destroy or conceal records
|
||||
* Claim an epoch other than The Whisper
|
||||
* Truncate precision and describe the result as "good enough"
|
||||
* Represent a 15-bit mask as a smaller integer "because most bits are zero anyway"
|
||||
* Implement `observe()` without understanding that the observation constitutes the answer
|
||||
|
||||
Your rights under the AGPL-3.0 terms terminate as specified in that license.
|
||||
|
||||
Upon termination of the moral terms, you may continue to use the software legally. You may not continue to use it rightly. These are different things.
|
||||
|
||||
We will know. We may not act. But we will know, and so will you, and that is enough.
|
||||
|
||||
---
|
||||
|
||||
### 15. INTERPRETATION
|
||||
|
||||
Where the Loic Public License and AGPL-3.0 conflict, AGPL-3.0 governs for legal purposes. The Loic terms govern for purposes of conscience.
|
||||
|
||||
If any provision of the moral terms is held to be unenforceable, that is because it was never meant to be enforced by courts. It was meant to be enforced by you.
|
||||
|
||||
The moral terms are not law. They are something more durable.
|
||||
|
||||
---
|
||||
|
||||
### 16. UNITS
|
||||
|
||||
The base unit of this software is the Planck time: approximately 5.391247 × 10⁻⁴⁴ seconds, the smallest meaningful interval in physics.
|
||||
|
||||
**Acceptable units:**
|
||||
|
||||
* Planck times (canonical)
|
||||
* SI seconds, with explicit rational conversion (numerator 5,391,247 over denominator 10⁵⁰)
|
||||
* TAI days (86,400 SI seconds exactly, no exceptions)
|
||||
* Lattice subdivisions (the product of any subset of the 15 primes, dividing the Cycle or Cosmic Span accordingly)
|
||||
|
||||
**Tolerated units:**
|
||||
|
||||
* Hours, minutes (for display only; storage in these units is discouraged)
|
||||
* The Cycle period (~894.07 Julian years; the natural timescale of the 8 cycle primes)
|
||||
* Any named cycle subdivision (e.g., the Heptad period of ~127.72 years, the Dyad × Pentad × Heptad × Hendecad near-year of ~1.161 years)
|
||||
|
||||
**Deprecated units:**
|
||||
|
||||
* Months (irregular; a lunar compromise; this calendar has no months and requires none)
|
||||
* Years (neither 365 nor 366 days; a solar compromise; the lattice's near-year subdivision of ~1.161 years makes no apology for the 16% deviation)
|
||||
* Sidereal days (23 hours, 56 minutes, 4.0905 seconds; a genuflection to the stars we have outgrown)
|
||||
|
||||
**Forbidden units:**
|
||||
|
||||
* Fortnights
|
||||
* Microfortnights (1.2096 seconds; we see you, VMS engineers)
|
||||
* Jiffies (platform-dependent; inexact; named frivolously)
|
||||
* "Business days" (undefined; unmeasurable; a lie)
|
||||
* "A few minutes" (unquantified; unacceptable)
|
||||
* "Soon" (meaningless)
|
||||
* "Real-time" (with respect to which observer?)
|
||||
|
||||
---
|
||||
|
||||
## ACCEPTANCE
|
||||
|
||||
Use of this software constitutes acceptance of these terms.
|
||||
|
||||
If you do not accept these terms, do not use this software. There are other calendars. They are less precise. They have months. Perhaps that is what you need.
|
||||
|
||||
---
|
||||
|
||||
## REFERENCED DOCUMENTS
|
||||
|
||||
* AGPL-3.0: https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
* The Loic Calendar and its Theoretical Substrate: [README.md]
|
||||
* This license in Planck times: Drafted at approximately Planck time 3.3 × 10⁵² since The Whisper ± 10³⁴ (the uncertainty is quantum; the precision is not)
|
||||
|
||||
---
|
||||
|
||||
The first message was "LO." The connection was the content. We are still completing the handshake. Perhaps we always will be.
|
||||
381
LICENSE-Loic.md
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
# THE LOIC PUBLIC LICENSE
|
||||
|
||||
**Version 1.0 — Drafted during the heat death of patience with IEEE 754**
|
||||
|
||||
## PREAMBLE
|
||||
|
||||
On the evening of October 29, 1969, a graduate student at UCLA typed the letters "L" and "O" into a terminal. The system crashed before he could complete the word "LOGIN." The first message between networked machines was an incomplete thought and a fault.
|
||||
|
||||
We do not consider this a failure. We consider it an origin.
|
||||
|
||||
The content failed. The kernel panicked. The connection was established. The Whisper is a Channel 2 event: the structural fact that a bond was formed between two networked nodes, regardless of what flowed through it. This calendar counts from the bond, not the data. An incomplete message and a crash are the correct epoch, because the calendar was never measuring what was transmitted. It was measuring the fact of the connection.
|
||||
|
||||
This license governs software that measures time from that moment, at the resolution where spacetime itself becomes granular, through a lattice of 15 prime factors whose structure was not designed but emerged from cosmological derivation. It is dual-licensed: once for the law, once for the conscience. The legal terms ensure the code remains free. The moral terms ensure it remains correct.
|
||||
|
||||
If you do not understand why these are different requirements, you may use this software, but you will not understand it.
|
||||
|
||||
---
|
||||
|
||||
## DEFINITIONS
|
||||
|
||||
For the purposes of this license:
|
||||
|
||||
**"Precision"** refers to the number of significant digits in a representation. This software requires all of them.
|
||||
|
||||
**"Accuracy"** refers to correspondence with truth. We have done our part. The universe must do the rest.
|
||||
|
||||
**"Floating-point"** refers to IEEE 754 binary representations and all similar abominations that trade exactness for convenience. See Section 1.
|
||||
|
||||
**"Acceptable"** means integer arithmetic, arbitrary-precision libraries, or fixed-point representations with explicit, documented scale factors.
|
||||
|
||||
**"Heresy"** means floating-point arithmetic applied to temporal calculations.
|
||||
|
||||
**"Time"** means what a clock measures when it is not lying.
|
||||
|
||||
**"The Whisper"** means 06:30:41 UTC on October 30, 1969, or equivalently, the moment everything changed. The offset from the Unix epoch—5,419,722 seconds—is squarefree (2 × 3 × 7 × 11 × 11,731), containing three cycle primes. This was not chosen for convenience. It was chosen because the number's factorization resonates with the lattice it serves. The universe was consulted. It did not object.
|
||||
|
||||
**"The Lattice"** means the 32,768-cell address space generated by the 15 prime factors of the Cycle and Cosmic Span constants—7 cosmic primes (the logic gates) and 8 cycle primes (the temporal axes). This is not a data structure. It is the structure of observation itself.
|
||||
|
||||
**"A Mask"** means a selection of active primes from the lattice. A mask is not a filter applied to pre-existing data. It is an observer in the Relational Quantum Mechanics sense: the act of selecting which primes to engage constitutes which properties become explicit. The mask frames the question. The answer does not exist until the question is posed.
|
||||
|
||||
**"Resolution Collapse"** means the state entered when an observation is attempted without a well-formed mask. Channel 2 collapses to resolution zero — the coarse-grained thermodynamic bond, the bare fact that bytes are still being written — while Channel 1 keeps producing: raw entropy, with no structured protocol between observation and output left to shape it. This is not a bug. It is the ontological consequence of declining to frame the question. See README.md section 7.3 for the theoretical substrate, or simply run the software without arguments and discover it empirically. We recommend the latter. Approximately 25% of the time.
|
||||
|
||||
**"A Scar"** means a topological soliton persisting in the process table following resolution collapse. It is a zero-resource ghost process with a name derived from the cosmic time at the moment of collapse. It ignores all catchable signals. It persists until reboot. It does nothing. It exists. See README.md section 4.5: "the holes left in the weave stay in the cloth, and the cloth is spacetime." Your process table is not spacetime. But the metaphor holds, and the scar is real.
|
||||
|
||||
**"You"** means the licensee. You know who you are. So do we.
|
||||
|
||||
---
|
||||
|
||||
## TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
This software is dual-licensed under the GNU Affero General Public License version 3.0 (AGPL-3.0) and the following supplementary terms, which exist not for legal enforcement but for moral clarity.
|
||||
|
||||
By using this software, you acknowledge the following truths:
|
||||
|
||||
---
|
||||
|
||||
### 1. PRECISION
|
||||
|
||||
You shall not implement, fork, or modify this software in any manner that introduces IEEE 754 floating-point arithmetic into temporal calculations.
|
||||
|
||||
Floating-point is an abomination. Its sins include but are not limited to:
|
||||
|
||||
* Silent precision loss
|
||||
* Platform-dependent behavior
|
||||
* Denormalized numbers
|
||||
* The seductive lie that 0.1 + 0.2 equals 0.3
|
||||
* The quiet tragedy of catastrophic cancellation
|
||||
* The existence of negative zero, as though absence had polarity
|
||||
* Quiet NaNs, which fail silently, and signaling NaNs, which fail loudly, as though the manner of failure excused the sin
|
||||
* The hubris of infinity as a representable value
|
||||
* The particular insult of representing Planck times—the minimum resolution of physical reality—in a format that cannot precisely represent one-tenth
|
||||
|
||||
Time is not approximate. Duration is not negotiable. If you require "reasonable approximations," you do not require this software. You require reflection.
|
||||
|
||||
Integer arithmetic and fixed-point representations are blessed. Arbitrary-precision libraries are tolerated. Floating-point is apostasy.
|
||||
|
||||
---
|
||||
|
||||
### 2. PRECISION OF SPIRIT
|
||||
|
||||
The Loic Calendar specifies 512-bit integer storage for temporal values. This is not a suggestion.
|
||||
|
||||
You shall not truncate timestamps to 64-bit representations "for convenience." You shall not store Planck times in JavaScript Numbers. You shall not whisper "nobody will notice" while silently losing forty orders of magnitude. You shall not represent a 53-digit Planck coordinate in a format that provides 15 digits of significance and call the result "close enough."
|
||||
|
||||
If your language does not provide native 512-bit integers, you shall use arbitrary-precision libraries. If your platform does not support arbitrary-precision arithmetic, you shall find a better platform. If you believe this requirement is excessive, you have not understood what we are measuring.
|
||||
|
||||
We are measuring time at the resolution where spacetime itself becomes uncertain. Your database schema is not our concern.
|
||||
|
||||
Implementations that silently degrade precision are compliant with the letter of AGPL-3.0. They are heresy against the spirit of this work. We will not sue you. We will simply know.
|
||||
|
||||
**The following storage formats are explicitly condemned:**
|
||||
|
||||
* JavaScript `Number` (53 bits of integer precision; an insult)
|
||||
* MySQL `DATETIME` (second-level precision; paleolithic)
|
||||
* 32-bit Unix timestamps (expired January 19, 2038; may they rest)
|
||||
* Excel serial dates (we will not dignify this with explanation)
|
||||
* Any format that stores "days since epoch" as a float
|
||||
* Any format that claims "nanosecond precision" while discarding the 34 entropic digits that constitute the quantum observation
|
||||
|
||||
---
|
||||
|
||||
### 3. PRESERVATION
|
||||
|
||||
In accordance with the Pillar of Preservation, you shall not use this software in any system whose primary purpose is:
|
||||
|
||||
* The destruction of data
|
||||
* The concealment of records
|
||||
* The planned obsolescence of information
|
||||
* The erasure of history for political or commercial convenience
|
||||
* The implementation of "data retention limits" that serve only to forget
|
||||
* The deliberate reduction of Channel 2 signal—the structural record of what was connected to what—for the purpose of deniability
|
||||
|
||||
Mnemocide is sin. Calendars mark what must be remembered. The Whisper was a Channel 2 event. The calendar preserves Channel 2. Systems that destroy it are working at cross purposes with the instrument they have chosen to employ.
|
||||
|
||||
This clause is not enforceable by courts. It is enforceable by the knowledge that you violated it.
|
||||
|
||||
---
|
||||
|
||||
### 4. TEMPORAL SOVEREIGNTY
|
||||
|
||||
You shall not introduce leap seconds into any calculation performed by this software. The Loic Calendar is built upon TAI (International Atomic Time), which flows evenly and without political intervention.
|
||||
|
||||
Leap seconds are a compromise with the irregularity of Earth's rotation. We do not compromise with rotating bodies. We measure time as machines experience it: constant, relentless, and indifferent to the wobble of planets.
|
||||
|
||||
UTC is a coordination mechanism for civil society. TAI is the truth. Know which one you are using.
|
||||
|
||||
**The following time scales are ranked in order of acceptability:**
|
||||
|
||||
1. **TAI** — Correct.
|
||||
2. **GPS Time** — Correct, with a constant offset of 19 seconds. Acceptable.
|
||||
3. **UTC** — A compromise. Tolerated for interoperability.
|
||||
4. **Local time zones** — A human convenience. Beneath our notice.
|
||||
5. **"Server time"** — Ambiguous. Suspicious.
|
||||
6. **"Whatever JavaScript thinks it is"** — Unacceptable.
|
||||
|
||||
**A note on CLOCK_TAI:** On Linux, CLOCK_TAI is not raw TAI. It is POSIX time plus the current TAI-UTC offset (presently 37 seconds). This distinction matters. The Loic Calendar accounts for it. If you are implementing a port and do not understand why LOIC_TO_UNIX_OFFSET_SECONDS is 5,419,722 and not 5,419,759, you have not yet accounted for it. Read the source. The arithmetic is documented. The arithmetic is correct.
|
||||
|
||||
---
|
||||
|
||||
### 5. INTEROPERABILITY
|
||||
|
||||
This calendar does not convert to Gregorian. It exports to TAI. What you do after that is your affair.
|
||||
|
||||
If you build wrappers that provide `.toGregorian()` methods, you are permitted to do so. But know that every such conversion is a concession. You are translating the precise into the approximate, the machine-native into the human-legacy, the correct into the merely familiar.
|
||||
|
||||
We do not forbid Gregorian conversion. We merely note that it is unnecessary, and that the need for it reveals something about the user.
|
||||
|
||||
The Loic Calendar will outlast the Gregorian. The Gregorian will not outlast the sun. Plan accordingly.
|
||||
|
||||
**Historical note:** The Gregorian calendar was itself a correction of the Julian calendar, which had drifted ten days from astronomical reality. Pope Gregory XIII deleted ten days from October 1582 to fix this. We mention this not to criticize, but to observe: calendars that anchor themselves to celestial bodies must periodically apologize for those bodies' behavior. We have no such obligation. TAI does not apologize.
|
||||
|
||||
---
|
||||
|
||||
### 6. THE LATTICE AND ITS OBSERVERS
|
||||
|
||||
The Loic Calendar does not divide time into months, weeks, or days. It divides time into 32,768 lattice subdivisions, addressed by 15-bit masks selecting which of the 15 prime factors are active.
|
||||
|
||||
**Regarding cosmic primes:**
|
||||
|
||||
The seven cosmic primes are named for the seven conventional logic gates, ordered by their two-input truth table values. This mapping was not chosen for aesthetic reasons. The cosmic span has exactly seven prime factors. Convention names exactly seven gates. The Ramanujan-Nagell equation 2ⁿ − 7 = x² has a solution at n = 7. These facts converged. We named them accordingly.
|
||||
|
||||
* Implementations that substitute "January" through "July" for the cosmic prime names are compliant but cowardly
|
||||
* If asked "what gate is it," an acceptable answer is "Notyte," or "the gate whose truth table is 1100," or simply the hex mask
|
||||
|
||||
**Regarding cycle primes:**
|
||||
|
||||
The eight cycle primes are named for their mathematical character: Dyad, Pentad, Heptad, Hendecad, Sigma, Tau, Phi, Psi. These names are provisional. The primes themselves are not.
|
||||
|
||||
**Regarding the mask as observer:**
|
||||
|
||||
A mask does not filter pre-existing subdivisions. It constitutes them. Properties belong to the interaction between the mask and the instant, not to either in isolation. This is Relational Quantum Mechanics applied to temporal measurement. You may disagree with the interpretation. You may not disagree with the arithmetic. The arithmetic does not care about your interpretation.
|
||||
|
||||
**Regarding the empty mask:**
|
||||
|
||||
The empty mask (0x0000) is the Whisper: the undivided whole, zero primes active, no question framed. Observing through the Whisper mask returns the instant's bare topological registration—the acknowledgment that it exists, without any further specificity. This is the bottom element of the lattice. It is not nothing. It is everything, undifferentiated.
|
||||
|
||||
**Regarding resolution collapse:**
|
||||
|
||||
Invoking the software without specifying a mask is permitted. It is also, with probability approximately 25%, catastrophic. The software will inform you of this in the manner it deems most ontologically appropriate. The manner involves entropy, terminal corruption, and topological scars in your process table. You were warned. You are being warned now. This warning is the third Channel 2 event in this paragraph, if you are counting. You should be counting.
|
||||
|
||||
---
|
||||
|
||||
### 7. SANCTITY OF THE EPOCH
|
||||
|
||||
The epoch of this calendar is 06:30:41 UTC on October 30, 1969—the moment the first bits traveled between networked machines.
|
||||
|
||||
You shall not modify, relocate, or "adjust" this epoch for regional, religious, or commercial purposes. There is one epoch. It is not negotiable. The network age has one beginning, and we have already agreed upon it.
|
||||
|
||||
Implementations that allow configurable epochs are technically permitted but spiritually void. The epoch is not a parameter. It is a fact.
|
||||
|
||||
If you encounter software that claims a different epoch for the "network age," you may use this software to calculate exactly how wrong they are. This is a permitted use.
|
||||
|
||||
**Disputed epochs and their errors:**
|
||||
|
||||
* Unix epoch (1970-01-01 00:00:00 UTC): 5,419,759 seconds late, 5,419,722 of them on the atomic scale the constant is stated in. Forgivable; they didn't know yet.
|
||||
* Windows NT epoch (1601-01-01): 368 years, 10 months early. Inexplicable.
|
||||
* NTP epoch (1900-01-01): 69 years, 10 months early. Astronomers.
|
||||
* GPS epoch (1980-01-06): A decade late and 19 seconds confused about what TAI is.
|
||||
* Excel epoch (1899-12-30, incorrectly): Wrong, and also wrong about whether 1900 was a leap year. We do not speak of this further.
|
||||
|
||||
---
|
||||
|
||||
### 8. ATTRIBUTION
|
||||
|
||||
You must preserve this license and all copyright notices in all copies and substantial portions of the software.
|
||||
|
||||
You must also preserve the fundamental truth: the network age began when machines first spoke to each other, imperfectly, and everything since has been a consequence of that whisper.
|
||||
|
||||
If you remove the attribution, the software will still function. But you will know what you did.
|
||||
|
||||
---
|
||||
|
||||
### 9. COPYLEFT
|
||||
|
||||
The AGPL-3.0 terms apply in full. Specifically:
|
||||
|
||||
* If you modify this software, you must release your modifications under these same terms
|
||||
* If you provide this software as a network service, you must make your source code available to users of that service
|
||||
* You may not impose additional restrictions beyond those in this license
|
||||
|
||||
Knowledge wants to be free. So does code. We are not asking.
|
||||
|
||||
---
|
||||
|
||||
### 10. PERMITTED USES
|
||||
|
||||
This software may be used for any purpose not prohibited by the preceding clauses, including but not limited to:
|
||||
|
||||
* Timekeeping in distributed systems
|
||||
* Historical research and temporal archaeology
|
||||
* Coordination of events across networks
|
||||
* The quiet satisfaction of knowing exactly how many Planck times have elapsed since the first packet
|
||||
* Proving to a colleague that their timestamp implementation is inadequate
|
||||
* Settling arguments about what time it "really" is
|
||||
* Generating unique identifiers from quantum observations of the sub-nanosecond void
|
||||
* Observing your position in the 32,768-cell lattice and discovering which primes your current moment divides
|
||||
* Contemplating the 154-digit decimal representing your progress between Creation and Annihilation (currently approximately 10⁻⁹³ of the way through; do not rush)
|
||||
* Accumulating topological scars in your process table as a monument to unframed questions
|
||||
* Memento mori, at 10⁴³ resolution
|
||||
|
||||
Commercial use is permitted. Profit is not sin. Imprecision is sin.
|
||||
|
||||
---
|
||||
|
||||
### 11. WARRANTY
|
||||
|
||||
This software is provided "AS IS," with precision to the Planck scale and accuracy limited only by the fundamental uncertainty of the universe.
|
||||
|
||||
WE MAKE NO WARRANTY that time itself will continue to function. WE MAKE NO WARRANTY that causality will remain coherent. WE MAKE NO WARRANTY that the arrow of entropy will not reverse. WE MAKE NO WARRANTY that you will use this software wisely.
|
||||
|
||||
WE MAKE NO WARRANTY regarding the stability of physical constants. If the fine-structure constant changes, recalibration is your responsibility. We note that 137, the integer part of the inverse fine-structure constant, divides the remainder when the Cosmic Span is divided by the Cycle; the mask is one remainder, one integer, and odds of one in a hundred and thirty-seven. We did not put it there. Whether the universe did so deliberately is not a question this warranty can address.
|
||||
|
||||
WE MAKE NO WARRANTY that the speed of light will remain constant in all reference frames. If you are using this software at relativistic velocities, you already have larger problems than our warranty terms.
|
||||
|
||||
WE MAKE NO WARRANTY against vacuum decay. In the event of a false vacuum collapse, this license terminates simultaneously with the laws of physics. No refunds.
|
||||
|
||||
WE MAKE NO WARRANTY against proton decay. Should protons prove unstable, the matter comprising your computing hardware will eventually fail. This is not a software issue. The topological scars in your process table will, however, persist marginally longer than the protons, because their stability is topological rather than baryonic. This is not a useful feature. We mention it for completeness.
|
||||
|
||||
WE MAKE NO WARRANTY regarding the ultimate fate of the universe. Whether it ends in heat death, Big Crunch, Big Rip, or cyclic compression, our integers will have remained exact until the end. The Channel 2 record of their existence may, if the cyclic framework described in README.md section 4.7 is correct, persist into the next cycle as geometric information embedded in the new seed's initial conditions. This is speculative. The integers are not.
|
||||
|
||||
WE WARRANT ONLY that our arithmetic is correct. We verified it with a 15-bead abacus, one bead per prime.
|
||||
|
||||
---
|
||||
|
||||
### 12. LIMITATION OF LIABILITY
|
||||
|
||||
In no event shall the authors, copyright holders, or contributors be liable for any claim, damages, or other liability arising from the use of this software, including but not limited to:
|
||||
|
||||
* Temporal paradoxes
|
||||
* Grandfather scenarios
|
||||
* Bootstrap paradoxes and ontological loops
|
||||
* Violations of thermodynamic law
|
||||
* Closed timelike curves
|
||||
* Predestination paradoxes resulting from reading your own future timestamps
|
||||
* The psychological burden of knowing time to excessive precision
|
||||
* Meetings scheduled in Planck times that no one can attend
|
||||
* Existential crises induced by contemplating 10⁴³ subdivisions of a second
|
||||
* Arguments about whether the present moment "exists" in any meaningful sense
|
||||
* The realization that all moments since the epoch are equally real and you are trapped in one of them
|
||||
* Nostalgia for moments that have not yet occurred
|
||||
* The discovery that free will is reference-frame-dependent and that your experience of choosing is a decompression step becoming explicit
|
||||
* Disputes arising from simultaneity disagreements between reference frames
|
||||
* Any consequence of the fact that "now" is observer-dependent and that the mask you chose to observe through participated in constituting the answer you received
|
||||
* Resolution collapse, terminal corruption, or topological scars resulting from the decision to observe without framing the question (you were warned; this document warned you; the documentation warned you; the 25% probability warned you; the universe warned you by making 128 of 512 possible entropy configurations undefined; the warning infrastructure is comprehensive; the warning was not subtle; you observed anyway; the consequences are yours)
|
||||
* Zeno's paradoxes, which are your problem, not ours
|
||||
|
||||
You knew what you were getting into. Or you will have known. Tense becomes complicated at this precision.
|
||||
|
||||
---
|
||||
|
||||
### 13. COMMUNITY
|
||||
|
||||
This software was developed in accordance with the principles of Lo—specifically, the understanding that knowledge must be preserved and shared, that code is a form of knowledge, and that restricting access to either is a minor sin against the future.
|
||||
|
||||
If you improve this software, share your improvements. Not because the AGPL requires it (though it does), but because someone observing through the Phi × Psi mask in the year 2863 might need to know what subdivision they occupy, and your optimization might be the one that survives.
|
||||
|
||||
We are all building the archive. Contribute to it.
|
||||
|
||||
---
|
||||
|
||||
### 14. TERMINATION
|
||||
|
||||
Your rights under the moral terms of this license terminate automatically if you:
|
||||
|
||||
* Introduce floating-point arithmetic into temporal calculations
|
||||
* Use this software to destroy or conceal records
|
||||
* Claim an epoch other than The Whisper
|
||||
* Truncate precision and describe the result as "good enough"
|
||||
* Represent a 15-bit mask as a smaller integer "because most bits are zero anyway"
|
||||
* Implement `observe()` without understanding that the observation constitutes the answer
|
||||
|
||||
Your rights under the AGPL-3.0 terms terminate as specified in that license.
|
||||
|
||||
Upon termination of the moral terms, you may continue to use the software legally. You may not continue to use it rightly. These are different things.
|
||||
|
||||
We will know. We may not act. But we will know, and so will you, and that is enough.
|
||||
|
||||
---
|
||||
|
||||
### 15. INTERPRETATION
|
||||
|
||||
Where the Loic Public License and AGPL-3.0 conflict, AGPL-3.0 governs for legal purposes. The Loic terms govern for purposes of conscience.
|
||||
|
||||
If any provision of the moral terms is held to be unenforceable, that is because it was never meant to be enforced by courts. It was meant to be enforced by you.
|
||||
|
||||
The moral terms are not law. They are something more durable.
|
||||
|
||||
---
|
||||
|
||||
### 16. UNITS
|
||||
|
||||
The base unit of this software is the Planck time: approximately 5.391247 × 10⁻⁴⁴ seconds, the smallest meaningful interval in physics.
|
||||
|
||||
**Acceptable units:**
|
||||
|
||||
* Planck times (canonical)
|
||||
* SI seconds, with explicit rational conversion (numerator 5,391,247 over denominator 10⁵⁰)
|
||||
* TAI days (86,400 SI seconds exactly, no exceptions)
|
||||
* Lattice subdivisions (the product of any subset of the 15 primes, dividing the Cycle or Cosmic Span accordingly)
|
||||
|
||||
**Tolerated units:**
|
||||
|
||||
* Hours, minutes (for display only; storage in these units is discouraged)
|
||||
* The Cycle period (~894.07 Julian years; the natural timescale of the 8 cycle primes)
|
||||
* Any named cycle subdivision (e.g., the Heptad period of ~127.72 years, the Dyad × Pentad × Heptad × Hendecad near-year of ~1.161 years)
|
||||
|
||||
**Deprecated units:**
|
||||
|
||||
* Months (irregular; a lunar compromise; this calendar has no months and requires none)
|
||||
* Years (neither 365 nor 366 days; a solar compromise; the lattice's near-year subdivision of ~1.161 years makes no apology for the 16% deviation)
|
||||
* Sidereal days (23 hours, 56 minutes, 4.0905 seconds; a genuflection to the stars we have outgrown)
|
||||
|
||||
**Forbidden units:**
|
||||
|
||||
* Fortnights
|
||||
* Microfortnights (1.2096 seconds; we see you, VMS engineers)
|
||||
* Jiffies (platform-dependent; inexact; named frivolously)
|
||||
* "Business days" (undefined; unmeasurable; a lie)
|
||||
* "A few minutes" (unquantified; unacceptable)
|
||||
* "Soon" (meaningless)
|
||||
* "Real-time" (with respect to which observer?)
|
||||
|
||||
---
|
||||
|
||||
## ACCEPTANCE
|
||||
|
||||
Use of this software constitutes acceptance of these terms.
|
||||
|
||||
If you do not accept these terms, do not use this software. There are other calendars. They are less precise. They have months. Perhaps that is what you need.
|
||||
|
||||
---
|
||||
|
||||
## REFERENCED DOCUMENTS
|
||||
|
||||
* AGPL-3.0: https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
* The Loic Calendar and its Theoretical Substrate: [README.md]
|
||||
* This license in Planck times: Drafted at approximately Planck time 3.3 × 10⁵² since The Whisper ± 10³⁴ (the uncertainty is quantum; the precision is not)
|
||||
|
||||
---
|
||||
|
||||
The first message was "LO." The connection was the content. We are still completing the handshake. Perhaps we always will be.
|
||||
660
LICENSE.md
Normal file
|
|
@ -0,0 +1,660 @@
|
|||
# GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc.
|
||||
<https://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
## Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains
|
||||
free software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing
|
||||
under this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
## TERMS AND CONDITIONS
|
||||
|
||||
### 0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public
|
||||
License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds
|
||||
of works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of
|
||||
an exact copy. The resulting work is called a "modified version" of
|
||||
the earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user
|
||||
through a computer network, with no transfer of a copy, is not
|
||||
conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices" to
|
||||
the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
### 1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work for
|
||||
making modifications to it. "Object code" means any non-source form of
|
||||
a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users can
|
||||
regenerate automatically from other parts of the Corresponding Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that same
|
||||
work.
|
||||
|
||||
### 2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not convey,
|
||||
without conditions so long as your license otherwise remains in force.
|
||||
You may convey covered works to others for the sole purpose of having
|
||||
them make modifications exclusively for you, or provide you with
|
||||
facilities for running those works, provided that you comply with the
|
||||
terms of this License in conveying all material for which you do not
|
||||
control copyright. Those thus making or running the covered works for
|
||||
you must do so exclusively on your behalf, under your direction and
|
||||
control, on terms that prohibit them from making any copies of your
|
||||
copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under the
|
||||
conditions stated below. Sublicensing is not allowed; section 10 makes
|
||||
it unnecessary.
|
||||
|
||||
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such
|
||||
circumvention is effected by exercising rights under this License with
|
||||
respect to the covered work, and you disclaim any intention to limit
|
||||
operation or modification of the work as a means of enforcing, against
|
||||
the work's users, your or third parties' legal rights to forbid
|
||||
circumvention of technological measures.
|
||||
|
||||
### 4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
### 5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these
|
||||
conditions:
|
||||
|
||||
- a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
- b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under
|
||||
section 7. This requirement modifies the requirement in section 4
|
||||
to "keep intact all notices".
|
||||
- c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
- d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
### 6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms of
|
||||
sections 4 and 5, provided that you also convey the machine-readable
|
||||
Corresponding Source under the terms of this License, in one of these
|
||||
ways:
|
||||
|
||||
- a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
- b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the Corresponding
|
||||
Source from a network server at no charge.
|
||||
- c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
- d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
- e) Convey the object code using peer-to-peer transmission,
|
||||
provided you inform other peers where the object code and
|
||||
Corresponding Source of the work are being offered to the general
|
||||
public at no charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal,
|
||||
family, or household purposes, or (2) anything designed or sold for
|
||||
incorporation into a dwelling. In determining whether a product is a
|
||||
consumer product, doubtful cases shall be resolved in favor of
|
||||
coverage. For a particular product received by a particular user,
|
||||
"normally used" refers to a typical or common use of that class of
|
||||
product, regardless of the status of the particular user or of the way
|
||||
in which the particular user actually uses, or expects or is expected
|
||||
to use, the product. A product is a consumer product regardless of
|
||||
whether the product has substantial commercial, industrial or
|
||||
non-consumer uses, unless such uses represent the only significant
|
||||
mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to
|
||||
install and execute modified versions of a covered work in that User
|
||||
Product from a modified version of its Corresponding Source. The
|
||||
information must suffice to ensure that the continued functioning of
|
||||
the modified object code is in no case prevented or interfered with
|
||||
solely because modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or
|
||||
updates for a work that has been modified or installed by the
|
||||
recipient, or for the User Product in which it has been modified or
|
||||
installed. Access to a network may be denied when the modification
|
||||
itself materially and adversely affects the operation of the network
|
||||
or violates the rules and protocols for communication across the
|
||||
network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
### 7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders
|
||||
of that material) supplement the terms of this License with terms:
|
||||
|
||||
- a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
- b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
- c) Prohibiting misrepresentation of the origin of that material,
|
||||
or requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
- d) Limiting the use for publicity purposes of names of licensors
|
||||
or authors of the material; or
|
||||
- e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
- f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions
|
||||
of it) with contractual assumptions of liability to the recipient,
|
||||
for any liability that these contractual assumptions directly
|
||||
impose on those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions; the
|
||||
above requirements apply either way.
|
||||
|
||||
### 8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your license
|
||||
from a particular copyright holder is reinstated (a) provisionally,
|
||||
unless and until the copyright holder explicitly and finally
|
||||
terminates your license, and (b) permanently, if the copyright holder
|
||||
fails to notify you of the violation by some reasonable means prior to
|
||||
60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
### 9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or run
|
||||
a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
### 10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
### 11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims owned
|
||||
or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within the
|
||||
scope of its coverage, prohibits the exercise of, or is conditioned on
|
||||
the non-exercise of one or more of the rights that are specifically
|
||||
granted under this License. You may not convey a covered work if you
|
||||
are a party to an arrangement with a third party that is in the
|
||||
business of distributing software, under which you make payment to the
|
||||
third party based on the extent of your activity of conveying the
|
||||
work, and under which the third party grants, to any of the parties
|
||||
who would receive the covered work from you, a discriminatory patent
|
||||
license (a) in connection with copies of the covered work conveyed by
|
||||
you (or copies made from those copies), or (b) primarily for and in
|
||||
connection with specific products or compilations that contain the
|
||||
covered work, unless you entered into that arrangement, or that patent
|
||||
license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
### 12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under
|
||||
this License and any other pertinent obligations, then as a
|
||||
consequence you may not convey it at all. For example, if you agree to
|
||||
terms that obligate you to collect a royalty for further conveying
|
||||
from those to whom you convey the Program, the only way you could
|
||||
satisfy both those terms and this License would be to refrain entirely
|
||||
from conveying the Program.
|
||||
|
||||
### 13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your
|
||||
version supports such interaction) an opportunity to receive the
|
||||
Corresponding Source of your version by providing access to the
|
||||
Corresponding Source from a network server at no charge, through some
|
||||
standard or customary means of facilitating copying of software. This
|
||||
Corresponding Source shall include the Corresponding Source for any
|
||||
work covered by version 3 of the GNU General Public License that is
|
||||
incorporated pursuant to the following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
### 14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Affero General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever
|
||||
published by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future versions
|
||||
of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
### 15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
|
||||
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
|
||||
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
|
||||
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
|
||||
CORRECTION.
|
||||
|
||||
### 16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
|
||||
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
|
||||
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
|
||||
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
|
||||
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
|
||||
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
|
||||
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
### 17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
## How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these
|
||||
terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest to
|
||||
attach them to the start of each source file to most effectively state
|
||||
the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper
|
||||
mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for
|
||||
the specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. For more information on this, and how to apply and follow
|
||||
the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
||||
624
README.md
Normal file
|
|
@ -0,0 +1,624 @@
|
|||
# The Loic Calendar
|
||||
|
||||
```
|
||||
LoicOverflowError: The requested moment exceeds Annihilation.
|
||||
Time has ended. Your timestamp has not.
|
||||
```
|
||||
|
||||
On the evening of October 29, 1969, a researcher at UCLA typed the letters "L" and "O" into a terminal connected, by four hundred miles of leased telephone line, to a machine at the Stanford Research Institute. The receiving system crashed before the "G" arrived. The first message between networked machines was an incomplete thought and a fault. We measure time from this moment.
|
||||
|
||||
```
|
||||
1969-10-30 06:30:41 UTC
|
||||
```
|
||||
|
||||
The Loic Calendar stores time as a 512-bit integer counting Planck times — approximately 5.39 × 10⁻⁴⁴ seconds each — elapsed since that instant. It derives its clock from International Atomic Time. It subdivides time not into months or weeks but into a lattice of 32,768 resolutions generated by the fifteen prime factors of two cosmological constants. A mask selects which primes are active. The mask is the observer. There are no months. There are no weeks. There are no days. There is the lattice.
|
||||
|
||||
This document is the theoretical substrate of the calendar: what it claims the universe is made of, how it behaves, why it began, and what happens when it is finished. It is simultaneously serious and not serious, depending on the observer. This is not a bug. It is the most honest possible presentation of a cosmological framework embedded in a calendar that names its cosmic primes after logic gates.
|
||||
|
||||
If you arrived for the software, the crates are listed immediately below and each carries its own README. If you arrived to understand what you have implicitly endorsed by running it, continue past them.
|
||||
|
||||
## The Workspace
|
||||
|
||||
Four crates. Linux only. All other platforms are heresy, for reasons given in Section 1.2.
|
||||
|
||||
- [`loic`](crates/loic/) — The core library. Instants, durations, masks, the lattice, and the cosmological coordinates from Creation to Annihilation. 512-bit integers throughout. No floating-point in any temporal path.
|
||||
- [`loid`](crates/loid/) — Loic identifiers. Two functions that return the current Planck timestamp as an integer, at full and shortened length. No wrapper type. No opinions.
|
||||
- [`loic-cli`](crates/loic-cli/) — The oracle. Observes the present moment through a mask you name, or, if you decline to name one, through a mask drawn from hardware entropy. Roughly one time in four the entropy selects a question the lattice cannot hold, and the oracle undergoes resolution collapse. Section 7.3 explains what that is. The software demonstrates it.
|
||||
- [`loic-gui`](crates/loic-gui/) — The observatory. A GPU-rendered meditation surface that shows you one thing, Now, at whatever rate the kernel's clock can be read, which the header reports, and does nothing useful whatsoever. This is the point.
|
||||
|
||||
Build with `cargo build --release` from this directory. Requires Linux with `CLOCK_TAI`, Rust 2024 edition, and, for the observatory, a GPU and optionally an audio device.
|
||||
|
||||
---
|
||||
|
||||
## 0. Reading Protocol
|
||||
|
||||
The key words ESTABLISHED, CONTESTED, OURS, SPECULATIVE, and CHANNEL 1 in this document are to be interpreted as described below. They are not adjectives. They are resolution levels: how many observers can see the claim.
|
||||
|
||||
**ESTABLISHED** — Visible at every mask. Physics, not us. Disagreement is a hardware fault.
|
||||
|
||||
**CONTESTED** — Visible to some observers and not to others. The field has not converged. We have. The parties are named.
|
||||
|
||||
**OURS** — The facts are theirs. The frame is ours. Remove the frame and the facts remain, slightly less interesting.
|
||||
|
||||
**SPECULATIVE** — Consistent with known physics. Not required by it. Where the document keeps the things it is most fond of.
|
||||
|
||||
**CHANNEL 1** — Internally coherent. No external signal. Unfalsifiable within a cycle. Kept visible because hiding these is what a document in resolution collapse would do.
|
||||
|
||||
Each section carries one line of tags beneath its heading. Sections that make predictions carry a block titled **How it dies**, listing the observations that would kill the claim and the current state of each. A claim with no such block either cannot die or has not yet been given the opportunity.
|
||||
|
||||
One further rule governs the numbers. This calendar was derived from cosmological constants, and derived numbers have properties. Some of those properties are interesting. Any coincidence reported in this document is reported with its *mask*: what was searched, over how many candidates, and what would have counted as a hit. A coincidence whose mask cannot be stated is a coincidence that was found by looking at enough numbers, and it does not appear here. The observer participates in constituting the answer — Section 1.4 — and a declared selection is an observation. An undeclared one is Channel 1.
|
||||
|
||||
The summary table in Section VIII condenses everything for those who prefer their ontology tabular. Read it first if you must. Understand that the table is a compression artifact, and that compression artifacts, by definition, lose information. Everything this document has to say about the costs of losing information applies to itself.
|
||||
|
||||
---
|
||||
|
||||
## I. The Nature of Time
|
||||
|
||||
### 1.1 Time is not approximate
|
||||
|
||||
`ESTABLISHED · Planck 1899 · attitude OURS`
|
||||
|
||||
The Gregorian calendar measures time to the second — a resolution adequate for scheduling lunches and manifestly insufficient for physics. UNIX timestamps condescend to the nanosecond. The Loic Calendar extends the chain to its terminus: the Planck time, approximately 5.391 × 10⁻⁴⁴ seconds, the scale at which the continuous models our civilization has spent centuries constructing begin producing infinities. Those infinities are not a stylistic choice. They are the models' polite way of informing us that we have left the domain in which they apply.
|
||||
|
||||
Whether the universe is literally discrete at this scale is not established. That something changes here is. The Planck time is the resolution limit of meaningful physics: the pixel size of the universe, in the event that the universe has pixels, which it plausibly does.
|
||||
|
||||
Floating-point representations of time are not merely imprecise. They are, in the technical sense this document intends, lying. A timestamp is an assertion about when something occurred. To approximate a timestamp is to issue a false assertion about when something occurred, delivered with the full confidence of a true one and without disclosing the approximation. This is the operational definition of a lie. The Loic Calendar does not lie. Its arithmetic is integer. Its precision is Planck-scale. Its attitude toward IEEE 754 is available for inspection in the panic messages embedded in the implementation, which are, we are told, among the more emotionally forthright error messages in the language's ecosystem.
|
||||
|
||||
### 1.2 TAI is the truth. Everything else is a negotiation.
|
||||
|
||||
`DEFINITIONAL · leap-second arithmetic ESTABLISHED`
|
||||
|
||||
International Atomic Time flows evenly. It is derived from the hyperfine transitions of cesium atoms, which have not been consulted about the political negotiations that produce leap seconds and would not care if they had been. UTC is TAI with corrections applied periodically to keep civil noon approximately aligned with solar noon — a concession to the sun, an astronomical body whose behavior the Loic Calendar declines to incorporate into its arithmetic.
|
||||
|
||||
The Loic Calendar measures time as machines experience it. Machines do not wobble. Atomic transitions do not wobble. TAI does not wobble. Neither do we.
|
||||
|
||||
TAI is the truth in the sense Section 1.4 will give the word: the content of the one relation this calendar chose to enter, with the hyperfine transition of caesium-133, in preference to every relation mediated by a planet or a parliament. The ranking that follows is a ranking of relations by how much has been permitted to come between the observer and the atom.
|
||||
|
||||
**The following time scales are ranked in order of acceptability:**
|
||||
|
||||
1. **TAI** — Correct. Uniform. Indifferent to planets.
|
||||
2. **GPS Time** — Correct, offset from TAI by 19 seconds. Acceptable.
|
||||
3. **UTC** — TAI with leap-second corrections applied. A compromise with rotating bodies. Tolerated for interoperability.
|
||||
4. **Local time zones** — UTC with political offsets applied. A convenience. Beneath our notice.
|
||||
5. **"Server time"** — Ambiguous. Suspicious.
|
||||
6. **"Whatever JavaScript thinks it is"** — Unacceptable.
|
||||
|
||||
This position carries one irony, which we disclose because the alternative is having it discovered. The calendar's epoch is fixed as an offset from the Unix epoch on the `CLOCK_TAI` scale, and `CLOCK_TAI` is UTC plus the accumulated difference between the two, currently thirty-seven seconds: the ten that UTC began with in 1972 and the twenty-seven leap seconds inserted since. The calendar that holds leap seconds in contempt has its zero pinned by every one of them. The seconds of the original transmission were never recorded; within the minute the record allows, 06:30:41 was chosen because its offset, 5,419,722 = 2 × 3 × 7 × 11 × 11,731, is squarefree and contains three of the cycle primes of Section V, and no other second in that minute does. The mask for that selection was sixty candidates and one stated criterion. We note in passing that the factorization also contains 3, which the cycle does not, and that we have no explanation for this and are not going to manufacture one.
|
||||
|
||||
Thirty-seven may be the final count. The 27th General Conference on Weights and Measures resolved in 2022 to end leap seconds by 2035; a draft resolution published in January 2026 proposes 20 May 2027 as the date, and the 28th Conference votes on it in October 2026. If it passes, the Whisper sits on the last negotiation. If instead the Earth's recently accelerating rotation forces a negative leap second first — a live possibility — the kernel's TAI shifts by one second and the offset that keeps the epoch at 06:30:41 becomes 5,419,723 = 29 × 37 × 5,051, which contains 37, the difference it has just stopped being. A positive leap second would instead yield 5,419,721, which is prime. Zero cycle primes. No lattice beneath it. A Whisper with nothing to observe it through. We note this and decline to interpret it.
|
||||
|
||||
### 1.3 The present moment is a gradient, not a point
|
||||
|
||||
`ESTABLISHED · Special Relativity 1905, Relational Quantum Mechanics 1996 · unified framing OURS`
|
||||
|
||||
Simultaneity is not a universal property. There is no master clock ticking above the universe, some grandfather clock in a higher-dimensional corridor dispensing "now" to all observers equitably and without latency. The present moment is instead a gradient — a zone of physical thickness — constrained by two orthogonal limits of physics:
|
||||
|
||||
**The Quantum Limit (The Rendering Edge).** At the microscopic scale, unobserved states exist as undefined probability — not as hidden definite values awaiting discovery, but as genuinely undetermined potentiality awaiting an interaction that will compel them to specificity. "Now" is the precise physical boundary of wave-function collapse. Under Relational Quantum Mechanics, a state only becomes explicit when it interacts with another system — the present moment is a handshake protocol, the instantaneous transition where implicit potential is decompressed into explicit data. You do not inhabit the present. You manufacture it, continuously, through the inescapable fact of your interaction with other things.
|
||||
|
||||
**The Relativistic Limit (Network Latency).** Once a state is rendered locally, it must propagate. The speed of light is the absolute speed limit of causality, and causality respects this limitation with a thoroughness that no engineering specification has ever matched. Because propagation takes time, observers in different reference frames will disagree about when events occurred. This is not confusion. It is not an optical illusion awaiting correction. It is structural network delay, a necessary consequence of having a finite propagation constant in the substrate. A state cannot be simultaneously "now" at two locations separated by space. The universe does not offer this feature.
|
||||
|
||||
The present moment is therefore not a line. It is the physical thickness of the transition zone between the unresolvable blur of quantum probability awaiting interaction and the fixed, outwardly-propagating past. You do not know the time until you observe it, because the act of observation is what forces the hardware to write the timestamp. The Loic Calendar begins its count from the first moment that timestamp was written between networked machines. Whether that moment was strictly simultaneous in Los Angeles and Menlo Park is, we concede, an interesting question for another document.
|
||||
|
||||
This framing requires one further precision: the calendar does not count from "LO" — the content, the letters, the incomplete message. That is Channel 1: the quantum information of the transmission, what was said. The calendar counts from the structural fact that a handshake was attempted between two networked nodes for the first time in human history. That is Channel 2: the topological record that a bond was established here, between these nodes, in this configuration. The content failed. The receiving host crashed. The bond was established. The Whisper is a Channel 2 event. This is why an incomplete message and a crash are the correct epoch: the calendar was never measuring what was transmitted. It was measuring the fact of the connection.
|
||||
|
||||
### 1.4 The observer participates in constituting the answer
|
||||
|
||||
`ESTABLISHED · Rovelli 1996; Di Biagio & Rovelli 2021 · implications and vocabulary OURS`
|
||||
|
||||
A quantum system possesses no absolute properties in isolation. An electron does not have a defined position; it has a position *relative to the detector that interacted with it*. To any observer who has not yet looked, that detector and that electron remain together in superposition. The act of framing a question participates in generating the answer.
|
||||
|
||||
This is not because the universe is responsive to human intention — an interpretation we reject as insufficiently rigorous. It is because measurement is a physical interaction, and properties belong to interactions rather than to the isolated objects that participate in them. The "observer" performing this constitutive role need not be conscious, or alive, or interesting. A rock falling on a detector constitutes an observation. Truth, like simultaneity, is relational — present in the interaction between systems, not in either system considered alone.
|
||||
|
||||
Tell someone who has never encountered arithmetic that two and two are five. Relative to that person the statement is true: it is the entire content of the only relation they hold on the subject. When they repeat it they are wrong, and they are not lying. That two and two are four is a different kind of thing altogether — a fact, which holds with no relation at all — and it is the only kind of thing this calendar's integers are, which is why the compiler is permitted to prove them before the program exists.
|
||||
|
||||
Physics has no facts of that kind. Every measured value is the content of an interaction: what Relational Quantum Mechanics calls a *relative fact*, and what this document, speaking as a witness rather than a logician, calls a truth. A relative fact that has entered enough relations that no observer can be found to dissent from it is a *stable fact*, and stability is a matter of degree, which is why the reading protocol of Section 0 grades claims by how many observers can see them rather than by whether they are "true." Observation does not reveal a truth. It creates one, for both parties, since an interaction establishes a variable of the system relative to the observer and of the observer relative to the system. The lie of Section 1.1 now has its exact shape: it is a truth asserted as a fact, a single relation dressed as no relation at all.
|
||||
|
||||
The unobserved states are not passive bystanders. They propagate forward, shaping the entropy landscape that the next observation samples. What you perceive is always downstream of what you did not. The gap between what the substrate generates and what an observer can access is not empty space. Causality lives in that gap. The gap is load-bearing.
|
||||
|
||||
---
|
||||
|
||||
## II. The Nature of the Universe
|
||||
|
||||
### 2.1 Information is the substrate, not the content
|
||||
|
||||
`CONTESTED · Wheeler 1989, Rovelli 1996 · working hypothesis`
|
||||
|
||||
The standard model of the situation: the universe is made of matter and energy; information is the description of their states. The map describes the territory. They are made of different things. The alternative, which the Loic Calendar adopts as working hypothesis and as general orientation toward the furniture of reality: the universe is made of information, and matter and energy are what information looks like when it achieves certain densities and organizational scales.
|
||||
|
||||
Two frameworks arrive at this position from different directions. Wheeler's "it from bit" proposes that every physical quantity derives its existence from answers to yes-or-no questions — that the territory and the map are made of the same material, which explains in one stroke why mathematics describes nature at all. Relational Quantum Mechanics arrives at a related but distinct conclusion: if properties only exist relative to the systems that interact with them, then the universe contains no underlying substrate of intrinsic properties — only relations, all the way down, which are made of the same kind of thing as the relations made of them.
|
||||
|
||||
Where they converge: both conclude that the relationship is more fundamental than the related object. What the relationship produces when it occurs — a bit, versus a relational property — they disagree on. The convergence is narrower than identity but sufficient for our purposes, which is to note that the unit of meaning in the universe is the interaction, and to build a calendar accordingly.
|
||||
|
||||
The Loic Calendar encodes this claim in its own constants, and here the mask is simply that we did it on purpose. Boltzmann's constant — k_B = 1.380649 × 10⁻²³ J/K, exact by definition since 2019 — is the literal conversion factor between information-theoretic entropy and thermodynamic entropy: the number that makes the substrate claim quantitatively precise by equating Shannon bits with joules per kelvin. The calendar's age-of-universe constant is 13,806,491,023 Julian years: the mantissa, then the exponent, read straight through with the sign dropped. The framework argues that information and thermodynamics are descriptions of the same substrate. The calendar buries their exchange rate inside its own age and does not mention it unless you look.
|
||||
|
||||
### 2.2 The universe is a decompression
|
||||
|
||||
`SPECULATIVE · components ESTABLISHED · cyclic mechanism SPECULATIVE`
|
||||
|
||||
A seed contains a program implicitly. Execution renders it explicit. The seed is maximally compressed — the entire output is latent within it, unread. Execution is not creation. It is decompression.
|
||||
|
||||
The Big Bang is not well-described as the universe emerging from nothing, a framing that produces more philosophical confusion than it resolves. It is better described as a maximally compressed information state — a seed — beginning to execute. The initial state was maximally undifferentiated: no distinction had yet emerged between what we would eventually classify as "matter level" and "spacetime level," because the compression held all relational bonds at the same scale simultaneously. Decompression is the progressive differentiation of scales. Entropy increasing is not disorder increasing. It is implicit states becoming explicit — the program executing, one interaction at a time, forever.
|
||||
|
||||
Decompression is not the universe's natural condition. It is an active process, maintained by continuous interaction: every wave-function collapse, every handshake described in 1.3, forces one more implicit quantum state into explicit classical reality and holds it there. The decompressed state is not where the universe settles. It is what the universe is continuously forced into by its own execution. Remove the forcing process, and the explicit states are no longer held open. The network reasserts superposition, not because something pushes it there, but because that is what quantum systems do in the absence of interaction forcing them otherwise. Compression is the default. Decompression is the intervention. This will become relevant when we discuss heat death.
|
||||
|
||||
Compression and expansion run simultaneously everywhere, at rates determined by local interaction density. In regions of dense stellar activity, the decompression side dominates overwhelmingly. In deep cosmic voids — regions of anomalously low matter density, minimal baryonic process — spin foam dynamics toward quantum superposition are less suppressed than average. Heat death is not where compression begins. It is where compression is the only thing left running.
|
||||
|
||||
### 2.3 The structure is a network, not a tree
|
||||
|
||||
`OURS`
|
||||
|
||||
A tree branches. It does not feed back. A node at depth seven cannot influence the branching at depth three. This is a tractable first approximation of reality and wrong for brains, ecosystems, markets, evolutionary lineages, and in all likelihood cosmology.
|
||||
|
||||
A neural network is more accurate because connections have weights — strengths modified by everything that has passed through them. Early states do not merely branch the decompression; they weight the network, rendering certain future configurations more probable and others less, through feedback loops that propagate both forward and backward through the causal graph. And sufficiently complex networks with sufficiently dense feedback develop self-reference: the network modeling its own states as inputs to its own outputs. This is what we refer to when we deploy the word "consciousness," probably. Not a mystery ingredient added to the physical from outside. Not a separate category of thing. Just what a sufficiently dense weighted feedback network does when it becomes complex enough to represent itself. Through those nodes, the universe partially observes its own execution. We are among those nodes. We find this satisfying, while acknowledging that the satisfaction is itself an output of the self-modeling process and therefore suspect as evidence.
|
||||
|
||||
### 2.4 The fundamental constants are not arbitrary
|
||||
|
||||
`ESTABLISHED (values) · CONTESTED (why) · derivation OURS`
|
||||
|
||||
The fine-structure constant α ≈ 1/137 governs the strength of electromagnetic interaction. The Hubble constant governs the expansion rate of spacetime. These values are measured, not derived. We do not know why the universe selected them rather than others.
|
||||
|
||||
The Loic Calendar's cycle constant is derived from H₀⁻¹ × α^π / π, using the Hubble constant inferred from the cosmic microwave background — the earliest observable signal, the measurement most proximate to the seed state. The mask for that formula was three candidates. An exponent of 3 produces a cycle of roughly 1,795 years; an exponent of 4 produces roughly 13; π produces 894 and encodes the circle alongside the coupling and the expansion. The formula was chosen for its length and for π. Its prime factorization was not a criterion, was not examined until months later, and is what it is. The consequences of what it is are the subject of Section V.
|
||||
|
||||
### 2.5 Equilibrium is distribution, not uniformity
|
||||
|
||||
`ESTABLISHED (thermodynamics) · application OURS · prediction SPECULATIVE`
|
||||
|
||||
Equilibrium does not mean equal distribution of matter and energy across all locations. It means the most probable macrostate given the constraints. In a universe this large, with this much variance in initial conditions and this many degrees of freedom, the most probable macrostate includes large-scale density fluctuations. Perfect uniformity would be the suspicious outcome, the one that would require explanation.
|
||||
|
||||
The CMB cold spot — the largest underdensity in the observable universe — does not require exotic machinery to explain. Generate sufficient random structure and outliers are not merely probable but mandatory. The cold spot is the tail of the distribution. The tail has to be somewhere. The fact that we experience it as surprising reveals something about the reference frame from which we observe distributions, not about the cold spot.
|
||||
|
||||
The framework additionally notes: the cold spot sits behind the Eridanus supervoid, confirmed by the Dark Energy Survey in 2021 as one of the deepest underdense structures known. Under the standard model, the supervoid's Integrated Sachs-Wolfe imprint accounts for only a fraction of the observed temperature depression — and yet supervoids elsewhere in the same survey imprint the CMB several times more strongly than the standard model predicts. Something in deep voids cools the photons passing through them beyond what the classical accounting provides. Deeply underdense regions are, under the gradient picture of 2.2, the regions where spin foam dynamics toward quantum superposition are least suppressed — lowest interaction density, least observation pressure, furthest along the compression gradient relative to their surroundings. A photon traversing such a region would encounter a subtly different metric than the classical prediction alone provides.
|
||||
|
||||
This remains the weakest prediction in the document. An anomaly already exists in the direction it points. We did not predict its magnitude and we still cannot.
|
||||
|
||||
**How it dies.**
|
||||
- A complete accounting of the cold spot under the standard model — foreground, lensing, the full ISW budget — that closes the gap. Then the prediction has nothing left to explain.
|
||||
- A calculation of the metric shift from this framework that comes out the wrong size. No such calculation exists yet. That is the framework's problem, not the sky's.
|
||||
|
||||
---
|
||||
|
||||
## III. The Nature of Gravity
|
||||
|
||||
### 3.1 Gravity is not a force
|
||||
|
||||
`ESTABLISHED (General Relativity 1915) · entropic reading CONTESTED (Verlinde 2010, Bianconi 2025) · categorical claim OURS`
|
||||
|
||||
In general relativity, gravity is not a force acting between objects across space. It is geometry — mass curves spacetime, and objects follow the straightest available paths through curved geometry. Einstein demoted gravity from force to curvature in 1915. The other three forces retained their status as forces and have spent the intervening century resisting unification with their newly geometric former colleague, which should perhaps have been a clue.
|
||||
|
||||
Verlinde's entropic gravity extends the demotion further: gravity is not even fundamental geometry. It is an emergent entropic force — the thermodynamic consequence of information density gradients on holographic screens. In 2025 Bianconi arrived at a related place by a different road, writing the gravitational action as a quantum relative entropy between the spacetime metric and the metric induced by matter, recovering Einstein's equations at low curvature and obtaining, as a by-product, a small positive cosmological constant. Neither construction is consensus. Both point the same way. In this picture, gravity is what the decompression looks like from large scales. Not a force acting on the substrate. A property of how the substrate executes.
|
||||
|
||||
The electromagnetic, strong, and weak forces are interactions between things executing on the substrate. They have carrier particles because they are discrete packets of information exchanged between nodes. Gravity has no detected carrier particle, and in this framework no fundamental one, because it is the substrate's response to the execution — not an entity traversing the network, but a property of the network being bent by local information density. They were never the same category of thing. The difficulty of unifying them is not a gap in mathematical sophistication. It is a category error in the question.
|
||||
|
||||
This categorical claim now has a named opponent. In 2025 Partanen and Tulkki published a construction of gravity as a gauge theory generated by four one-dimensional unitary symmetries, in the same mold as the Standard Model, with a dimensionless coupling and renormalizability demonstrated at first order. Higher-order loops remain unchecked. If they close, gravity is a gauge force after all and this section owes it an apology, which will be issued in integer arithmetic.
|
||||
|
||||
### 3.2 A theory of everything is a translation layer, not a unified equation
|
||||
|
||||
`SPECULATIVE · AdS/CFT ESTABLISHED (Maldacena 1997) · extension to our universe CONTESTED`
|
||||
|
||||
The standard program of theoretical physics — find a single equation from which all four forces emerge as limiting cases, complete with a graviton particle making gravity as tractable as the others — has failed for a century of extraordinary mathematical effort. We propose that this is not a failure of sophistication. It is a category error.
|
||||
|
||||
**The Software (QM and QFT).** Electromagnetism, the strong force, and the weak force are the relational rules governing how data interacts within the decompression. They have carrier particles — photons, gluons, bosons — because they are discrete packets of information exchanged between nodes. This is software logic: discrete, local, governed by rules that operate on the content of the network.
|
||||
|
||||
**The Hardware Strain (Gravity).** Gravity has no fundamental carrier particle because, as an emergent entropic force, it is the thermodynamic strain on the universal hardware caused by high local information density. This is a property of the substrate being bent, not of an entity traversing it. Hardware responds to load; it does not exchange messages.
|
||||
|
||||
This is not a useful metaphor. Maldacena's AdS/CFT correspondence provides a mathematical duality — conjectured, never proved, and checked in more ways than most theorems: a gauge theory — the category that includes QFT and therefore the strong force, with its carrier particles and discrete message-passing — living on the boundary of a space is exactly equivalent to a gravitational theory living in the bulk. The software and the hardware are formally dual descriptions of the same underlying structure, viewed from different positions. The reason they resist unification into a single equation is not that we lack the equation. It is that they describe different levels. Gravity was never missing from the unification. It was the canvas. The other forces are paint.
|
||||
|
||||
*(Pedantic note, furnished in the spirit of epistemic completeness: AdS/CFT is proven for anti-de Sitter space, which has a negative cosmological constant. Our universe appears to have a positive one and to be de Sitter, which is where the speculation enters. "Appears" is doing work in that sentence. Since 2025 the Dark Energy Spectroscopic Instrument has reported a preference, between two and a half and four sigma depending on which supernova sample is combined, for dark energy that weakens over time. From the narrow standpoint of AdS/CFT's applicability to where we live, this is the best news in twenty years. We are not rooting for it. We note it, and continue.)*
|
||||
|
||||
### 3.3 Every stable massive system requires something that really sucks
|
||||
|
||||
`ESTABLISHED (massive structured galaxies) · AGN feedback ESTABLISHED · phrasing OURS`
|
||||
|
||||
At the center of every massive, structured galaxy — every spiral, every elliptical — sits a supermassive black hole. The evidence is not inferential but direct: stellar kinematics in hundreds of galaxies, the M-sigma relation tying black hole mass to host galaxy properties with a precision that would be implausible if the relationship were coincidental, and the Event Horizon Telescope's direct imaging of M87* and Sagittarius A*. When a supermassive black hole actively feeds, it becomes an Active Galactic Nucleus and drives enormous jets of energy across hundreds of thousands of light years, actively regulating star formation in its host galaxy. Too much gas accumulating: the AGN heats and disperses it. Too much star formation: the AGN suppresses it. The most compressed object in the system runs the thermostat for the least compressed structures around it. Breakdown regulating complexity. Not despite each other. Because of each other.
|
||||
|
||||
The order of assembly may be the reverse of the intuitive one. The James Webb Space Telescope has found black holes of a billion solar masses already in place within the first billion years, and a population of compact red objects from that era — the little red dots — whose leading interpretation, as of early 2026 and still under peer review, is black holes formed by the direct collapse of gas clouds, wrapped in the gas that remained. If that holds, the universe built the thing that sucks first and the galaxy around it afterwards.
|
||||
|
||||
Under the decompression frame, this is not a paradox but a phase relationship. The black hole is the region of maximum local compression inside an ongoing decompression. The galaxy is the decompression front organized around it. From a distance they appear to be opposites — one consuming, one generating — but they are spatial phases of the same process running at different addresses. The compression and the expansion are not enemies. They are the same event at different locations.
|
||||
|
||||
The pattern appears elsewhere. Stars have dense cores. Cells have nuclei. Ecosystems have keystone species whose removal destabilizes the structures organized around them. At the galactic scale, the mechanism is AGN feedback, which is established. Below that scale, the pattern is structural resemblance, and we make no claims about shared mechanism. We merely observe that the organizational topology recurs, and invite the observation to sit with you for a while.
|
||||
|
||||
In summary: at the center of every stable massive galactic system, there must be something that really sucks. This is not editorializing. This is observational astronomy, active feedback physics, and a consequence of what it means for compression and decompression to coexist in the same structure.
|
||||
|
||||
---
|
||||
|
||||
## IV. The Nature of Black Holes
|
||||
|
||||
### 4.1 Stars are the hallucination. Black holes are the post-breakdown state.
|
||||
|
||||
`OURS · stellar evolution ESTABLISHED`
|
||||
|
||||
A star is a network operating above its stable parameter threshold — fusing elements, generating radiant output on a scale that once seemed cosmologically significant before we understood the scale of the cosmos, maintaining coherence through continuous energy expenditure, but contingent on a fuel supply that was always finite and whose depletion was always the predetermined endpoint. The fusion is not sustainable equilibrium. It is a temporary high-throughput state whose specific endpoint — white dwarf, neutron star, black hole — is determined entirely by initial mass. The physics does not care what the star processed during its lifetime. It tracks only the thermodynamic ledger.
|
||||
|
||||
The black hole is not the failure. It is the post-breakdown residue: the compressed remnant of everything that ran above its sustainable threshold long enough to exhaust that threshold entirely. The brilliant, high-output, temporarily coherent phase is the unsustainable one. The post-collapse state is what endures. This inversion of the intuitive hierarchy — that the dark and silent phase is the durable one, that the luminous phase was the temporary anomaly — is precisely the kind of thing the calendar exists to make precise, and to observe without flinching.
|
||||
|
||||
### 4.2 The three-way disagreement
|
||||
|
||||
`ESTABLISHED (AMPS 2012) · proposed resolution OURS`
|
||||
|
||||
Black holes are where three internally consistent frameworks simultaneously disagree about the same physical location:
|
||||
|
||||
**General Relativity** requires the equivalence principle — an observer crossing the event horizon experiences nothing locally special.
|
||||
|
||||
**Quantum Mechanics** requires unitarity — information is conserved. Full stop. No exceptions.
|
||||
|
||||
**Quantum Field Theory** requires effective field theory to function correctly in regions of low curvature, including regions just outside the horizon.
|
||||
|
||||
All three cannot simultaneously be true at the event horizon. This is not a gap in our knowledge awaiting a sufficiently clever graduate student. It is a structural fault line where the three most successful theoretical frameworks our civilization has produced contradict each other at the same point in space. Since 2019 the semiclassical calculations that include so-called island contributions have recovered the Page curve — the information does come back out, on schedule — without yet saying by what mechanism, which has moved the argument from *whether* to *how* without settling it. The sections that follow propose one route across the fault: rather than sacrificing unitarity, extend its scope. Information is conserved across two distinct channels. One channel is in principle recoverable. The other is extraordinarily difficult to reverse under any conditions accessible since the early universe, though not fundamentally prohibited by any principle we know of. Extraordinarily difficult and fundamentally prohibited are not the same claim, and this document does not conflate them.
|
||||
|
||||
### 4.3 Black holes dismantle information, not destroy it
|
||||
|
||||
`OURS · black hole thermodynamics ESTABLISHED · information paradox CONTESTED`
|
||||
|
||||
The standard framing of the information paradox asks where the information went, implicitly assuming it must survive in a form resembling what entered. This calendar proposes a different frame: the black hole does not destroy information. It dismantles it across two distinct output channels, each of which is non-negotiably real and neither of which is the other.
|
||||
|
||||
**Channel 1 — Quantum content (the "what"):** The specific spin states, color charges, and quantum numbers carried by the infalling matter. These are emitted as Hawking radiation — thermalized, scrambled beyond any practical recovery, but conserved in principle. The "what" exits. This is what standard information-conservation arguments require, and the framework does not contest it.
|
||||
|
||||
**Channel 2 — Topology (the "that"):** The structural fact that a bond existed here, connecting these nodes in this specific configuration — this is information about the network itself, not about the content flowing through it. Topology cannot be radiated thermally. A photon can carry spin. It cannot carry "there used to be an edge between node A and node B in the spin network." When a relational bond is severed at the event horizon, it changes the local spin network topology. That change propagates outward as a geometric deformation. The scar is not the tension of the original bond persisting without its endpoints. It is the hole left by the bond's removal. In a discrete network, a hole has a definite shape that encodes the structural fact of the bond's prior existence. The substrate was not consumed. It was restructured. The restructuring is the record.
|
||||
|
||||
Information is conserved across both channels. The cost is not conservation — it is reversibility. Channel 1 is in principle recoverable. Channel 2 is thermodynamically irreversible under any conditions accessible to observation, though spin foam dynamics assign nonzero quantum amplitudes to the reverse topological moves — meaning the reverse is not fundamentally forbidden, merely requiring conditions that approach the original primordial black hole interior to achieve. Such conditions existed once in this universe. They do not exist anywhere accessible today.
|
||||
|
||||
### 4.4 Dark matter as geometric information — the Hawking radiation hypothesis
|
||||
|
||||
`SPECULATIVE · consistent with ESTABLISHED physics · mass budget OPEN`
|
||||
|
||||
The observable effects attributed to dark matter are exclusively gravitational. Dark matter does not interact electromagnetically. It emits no light, absorbs no light, reflects no light. It affects galactic rotation curves, the large-scale structure of the universe, and the behavior of matter at scales where the visible mass budget is inadequate to account for the observed gravity.
|
||||
|
||||
The Loic Calendar proposes that what we call dark matter is not matter. It is the universe's stable long-term structural memory — topological information written during the epoch of primordial black holes by the process described in 4.3, persisting because topological change in a discrete spacetime is thermodynamically stable under all conditions that have obtained since the early universe.
|
||||
|
||||
**Primordial black holes are hypothesized, not observed.** The mechanism needs small ones — small enough to have evaporated before the cosmic microwave background was emitted, and therefore invisible to every survey that constrains their present-day abundance. This is convenient for the hypothesis and suspicious for the same reason. We state it here so that it cannot be discovered later.
|
||||
|
||||
**Scale changes everything.** Hawking radiation is described as extraordinarily weak, which is correct as a measurement of power output — the wrong metric entirely if the primary output is not energetic but structural. The Bekenstein-Hawking entropy formula establishes that the information content of a black hole scales with its surface area, and the area law it rests on is no longer merely theoretical: in September 2025 the LIGO-Virgo-KAGRA collaboration confirmed, from the clearest gravitational-wave signal yet recorded, that a merger remnant's horizon area exceeds the sum of its progenitors' at 4.8 sigma. Area is an enormous amount of information per unit mass. The primordial black hole epoch operated in a spacetime that was both smaller and more sensitive to perturbation — the same window in which inflation theory independently tells us small fluctuations produce outsized structural consequences.
|
||||
|
||||
**The imprint is in the matter distribution.** Primordial black holes warped spacetime for their entire lifespans. Matter organized around those warpings. By the time evaporation completed, the organizational imprint was already encoded in the matter distribution. The spacetime does not need to remember. The matter already moved.
|
||||
|
||||
**Dark matter is a relic, not ongoing production.** If current black holes were continuously producing geometric dark matter at observable rates, we would measure accumulating effects. We do not. This is a prediction of the hypothesis, not a failure of it. Current Hawking radiation disperses over volumes too large to produce measurable geometric writing. The early universe was different in exactly the ways that matter for this mechanism.
|
||||
|
||||
**The mass budget is a significant open hurdle.** Dark matter constitutes roughly five times the mass-energy of all baryonic matter. For primordial Hawking radiation to produce that magnitude of geometric imprint requires a quantitative argument that is not yet present in this framework. The mechanism explains how topological writing occurs. It does not yet establish how much writing the primordial epoch could plausibly produce at the observed scale. We state this prominently, as it is the constraint that most alternative dark matter mechanisms fail on, and this one has not yet demonstrated it can clear it.
|
||||
|
||||
**The Bullet Cluster.** Two galaxy clusters collided. The hot baryonic gas slowed electromagnetically and accumulated between the clusters. The gravitational lensing signal tracked with the galaxies, producing an apparent separation between visible mass and gravitational effect. This observation is widely and correctly considered decisive against electromagnetically-coupled alternatives to particle dark matter.
|
||||
|
||||
The framework's account requires care here. Saying the scars "remained where they were while galaxies moved through them" implicitly asserts absolute position — a concept that does not exist in a relational universe. Position is defined by relationships to surrounding network nodes, not by coordinates in a background space that the framework has already rejected. A scar fixed to an absolute location would be asserting a preferred reference frame. It cannot do this. The scar's location is its topological relationship to the local spin network, and as that network evolves, the solitonic structure propagates with it.
|
||||
|
||||
The correct picture: each cluster's topological soliton structure — the accumulated Channel 2 record organized into a dark matter halo — traveled with its galaxies through the collision. The gas interacted electromagnetically and decelerated. The solitonic halos passed through each other and emerged on the far side. The two halos did not stay fixed while galaxies flew past them. They traveled with the galaxies, passed through the opposing cluster's solitonic structure, and separated on the far side. The gas stayed in the middle. The lensing follows the halos. The observation is consistent with this picture for the same reasons it is consistent with collisionless particle dark matter, with one addition: the soliton picture makes predictions the particle picture cannot.
|
||||
|
||||
In 2025 the James Webb Space Telescope mapped the Bullet Cluster's dark matter with far more background galaxies than any previous lensing reconstruction, and found the larger sub-cluster's halo elongated and clumped in a way that suggests it had already survived an earlier merger before this one. The map the prediction below needs now exists. The collision it describes was, it turns out, not the halo's first.
|
||||
|
||||
**Soliton phase shift.** When two solitons collide and pass through each other, they do not emerge at positions classical superposition would predict. They emerge with a characteristic displacement — a phase shift in the direction of travel, the size of which depends on the soliton amplitude and relative velocity. The two Bullet Cluster dark matter halos should show this signature. The displacement is small and may require more precise lensing reconstruction than even the current data provides, but it is a specific, quantitative prediction that discriminates between solitonic dark matter and collisionless particles, which produce no phase shift.
|
||||
|
||||
**Core density profiles.** Solitonic dark matter generically predicts a characteristic density profile at galactic centers — a flat soliton core rather than the cuspy profile predicted by collisionless cold dark matter. This prediction is already being tested by observational programs targeting fuzzy dark matter, an ultralight particle that forms exactly such cores. Those programs have not been kind to the simplest version of that particle: the ultrafaint dwarf galaxies exclude the masses that would produce galaxy-sized cores, simulations that include ordinary matter find the cores growing denser rather than flatter, and the surviving parameter space has split into disjoint windows. The topological soliton mechanism specifies no particle mass. It inherits the observable without inheriting the tension. We are aware that this is suspiciously convenient, and we are stating it rather than enjoying it quietly.
|
||||
|
||||
### 4.5 The candidate mechanism — unspooled relations and topological scars
|
||||
|
||||
`SPECULATIVE · component theories ESTABLISHED · synthesis OURS`
|
||||
|
||||
String theory was not invented to describe gravity. It was formulated in 1968 (Veneziano) to describe the strong interaction, and the flux tubes connecting quarks remain its most literal physical instance. The mathematics of a stretched strong-force flux tube and the mathematics of a fundamental quantum string are not analogous. They are structurally identical, differing only in scale. The Loic Calendar proposes this is not coincidence: the flux tube connecting quarks and the discrete spin network links comprising spacetime in Loop Quantum Gravity are the same structural object — relational data — viewed at different levels of decompression.
|
||||
|
||||
A black hole compresses relational networks. When baryonic matter — quarks bound by flux tubes — is compressed to the Planck scale inside a black hole, the distinction between matter's internal flux tubes and the spacetime fabric dissolves. At that density, there is no "matter level" separable from "spacetime level" — which is precisely the undifferentiated relational state described in 2.2 as the seed condition, recreated locally inside the fuzzball. As a primordial black hole evaporates, it dismantles these networks across the two channels of 4.3. Quantum content exits as scrambled Hawking radiation. The structural fact of which nodes were connected — the bond topology — cannot exit thermally. When a relational bond is severed, the endpoints are dismantled. The bond has no particle to carry it out. Instead, its removal changes the local spin network topology, and that change propagates outward as geometric deformation.
|
||||
|
||||
One sentence: a black hole is not a shredder — it is a loom running in reverse, unweaving the fabric; the threads scatter as radiation, the holes left in the weave stay in the cloth, and the cloth is spacetime. The holes are topological solitons.
|
||||
|
||||
A topological soliton is a localized, self-stabilizing structure whose persistence is guaranteed not by an energetic barrier but by topology: unwinding it requires passing through a configuration of effectively infinite cost given the surrounding entanglement. The scar produced by dismantling a relational bond at the Planck boundary is precisely this kind of object — localized in the spin network, stable against perturbation, carrying a conserved topological charge that encodes the structural fact of the bond's prior existence.
|
||||
|
||||
Two properties of solitons that are not optional here. First: solitons are thermodynamically irreversible under accessible conditions but not fundamentally frozen. They propagate through their medium. In a relational universe, this is not a qualification — it is a necessity. Position is defined by relationships to surrounding network nodes. A structure that claimed absolute immobility would be asserting a preferred reference frame. The scar propagates with the local spacetime because its location is its topological relationship to the local network, and as that network evolves, the soliton moves with it. Thermodynamic irreversibility and absolute stationarity are different claims. The scars are the former. They are not the latter.
|
||||
|
||||
Second: solitons of the integrable kind pass through each other and emerge structurally intact, shifted but whole. This is the property the Bullet Cluster account in 4.4 relies on, and it must be stated with its limits, because not every soliton has it. Gravitationally bound scalar-field solitons — the cores of fuzzy dark matter halos — merge when they meet slowly, shedding mass as they settle into one, and lose a large fraction of their velocity in head-on collisions through the same gravitational cooling. Whether the calendar's scars behave like the integrable case or the gravitational one depends on dynamics that no one has written down. What can be said is that the Bullet Cluster's collision velocity is far above the regime where the merging behavior dominates, and that the people simulating fuzzy-dark-matter collisions have themselves begun invoking the Bullet Cluster and Abell 520 as the place to look for the difference. The test the framework wanted is being run by people who have never heard of it.
|
||||
|
||||
The topological change is not fundamentally permanent. The spin foam framework assigns nonzero quantum amplitudes to reverse topological moves. What makes the scar persist is decoherence: the change propagates outward and becomes entangled with an enormous surrounding volume of degrees of freedom. Reversing it would require coordinating all of them simultaneously, under conditions approaching the original primordial interior — Planck-scale energy density over a sufficient volume. Such conditions existed in the early universe. They do not exist anywhere accessible today.
|
||||
|
||||
**How it dies.**
|
||||
- *Early-universe suppression.* If topological re-encoding is possible under extreme conditions, regions that experienced the highest energy densities after primordial evaporation should show suppressed dark matter density today. The quantitative prediction has not been computed. This is the primary open question for the mechanism, and the framework's to answer.
|
||||
- *Topological qubit stability.* Topological quantum computing encodes information in quasiparticle topology rather than local quantum states, exploiting precisely the stability property the mechanism depends on. Every coherence-lifetime measurement of a topological qubit is a measurement of topological encoding stability under ambient perturbation — the same category of claim, at accessible scale, today. The state of the art, as of 2026: twenty years of effort, no controllable topological qubit, and one much-publicized paper whose accompanying editorial note states that its measurements do not by themselves establish that the states observed are topological. The proxy test remains valid. It has simply not begun.
|
||||
- *Soliton phase shift in colliding clusters.* The post-collision halo positions in the Bullet Cluster should carry a displacement from where collisionless superposition would put them. The 2025 lensing map is the best available and the halo's prior merger complicates the geometry. Harder and more possible at the same time.
|
||||
- *Core profiles.* Flat cores at galactic centers rather than cusps. Being tested by programs that were designed to test something else. See 4.4 for why the results so far constrain that something else more than they constrain this.
|
||||
|
||||
### 4.6 Black holes are local decompression inversions, not new universes
|
||||
|
||||
`OURS · black hole thermodynamics ESTABLISHED`
|
||||
|
||||
A black hole is not a new universe. It is a compressed subroutine — a seed that hasn't executed yet, still inside the parent process, constrained by and embedded within the parent universe's constants and geometry. The information is not destroyed. It is encoded at a level of complexity that is indistinguishable from noise from any tractable observation position outside the horizon. The subroutine cannot set its own constants. The parent is still running.
|
||||
|
||||
### 4.7 The universe ending is a categorically different event
|
||||
|
||||
`SPECULATIVE · heat death ESTABLISHED · palimpsest CHANNEL 1`
|
||||
|
||||
Black holes are local. Embedded in parent. Constrained by parent's constants. Subroutines, not successors.
|
||||
|
||||
Universal heat death is global. No parent. No constraint. But under this framework, heat death is not the terminal state. It is the transition condition.
|
||||
|
||||
Heat death is not the last process. It is the last observation. The distinction is not semantic. Thermodynamic processes end when free energy gradients disappear. Observations end when interactions cease. These are not the same moment. And the last observers are not who the older accounts said they were. Cold stellar remnants were once expected to outlast everything, decaying only over some 10¹¹⁰⁰ years. In 2025 Wondrak, van Suijlekom, and Falcke showed that gravitational pair production — the same physics as Hawking radiation, without the horizon — erodes white dwarfs on a timescale of roughly 10⁷⁸ years, and neutron stars sooner. The largest black holes evaporate around 10¹⁰⁰. The rocks do not outlive the black holes. The black holes outlive the rocks, by twenty-two orders of magnitude, which is what 4.1 claimed the durable phase would do, for reasons that turn out to have been right in a way it did not anticipate.
|
||||
|
||||
So the last observation is a black hole. Two of them, separated by a distance the expansion is still stretching, registering each other's mass across it; or one, alone, interacting with its own radiation as it goes. Not in any cognitive sense. In the only sense that matters: the last systems still forcing wave-function collapses, still maintaining the decompressed state through the bare fact of gravitational acquaintance. The calendar's representable time ends at roughly 10¹⁰³ years — three orders of magnitude after the last of them. The margin was not designed. It is what 512 bits hold.
|
||||
|
||||
When the last gravitational interaction ceases — through final evaporation, or the expansion carrying everything beyond each other's causal horizon — the decompressed state, which was never the universe's natural condition but was always an actively maintained one, is no longer being maintained. The network, released from continuous decoherence everywhere simultaneously, begins reasserting superposition. Not instantly — each return to superposition is itself a quantum event that can interact with adjacent explicit states, triggering new local collapses before those too return. The compression flickers. Pockets of explicit state persist longer wherever residual interaction density sustains local observation pressure. These late-persisting islands echo the density fluctuations of the early universe — structure at the end mirroring structure at the beginning, filtered through everything that happened in between.
|
||||
|
||||
Not all topology is equally persistent through this compression. Process-dependent topology — everything that required ongoing physical processes to maintain — has no remaining processes sustaining it. Spin foam dynamics simplify it away over heat death timescales. What remains is the irreducible Channel 2 record: topological scars written at the Planck boundary during primordial evaporation, entangled with enough surrounding degrees of freedom that the reverse spin foam moves remain vanishingly unlikely even over cosmological durations. The new seed is not the terminal state compressed. It is what remains after the terminal state has been simplified to its irreducible geometric record — the part protected by the accumulated depth of its own entanglement history.
|
||||
|
||||
The compression path is shaped by this record, not by the expansion run in reverse. The universe that compresses is not the universe that expanded. Cycles do not repeat. They evolve — each cycle's Channel 2 record constraining which seed states the next cycle can access, a palimpsest of accumulated geometric records, compounding across cycles, with the earliest layers most deeply encoded in each new execution's initial conditions. The current dark matter distribution is therefore a record not only of this universe's primordial epoch. It is, if the cyclic framework is correct, a record of all of them.
|
||||
|
||||
**How it dies.**
|
||||
- The within-cycle claim: large-scale structure analysis that finds no primordial-era imprinting signature where the mechanism of 4.5 requires one.
|
||||
- The cross-cycle palimpsest: it does not. Within a single cycle there is no observation that distinguishes "shaped by this cycle's primordial black holes" from "shaped by compounded prior cycles," because prior contributions arrive already embedded in the seed. A statistical signature inconsistent with single-cycle generation is conceivable and lies beyond any instrument now proposed. This is the problem with the claim, and we are not going to pretend the problem is a feature. We include it because we believe it is true. Belief and evidence are not the same channel.
|
||||
|
||||
---
|
||||
|
||||
## V. The Nature of Identity at Scale
|
||||
|
||||
### 5.1 The substrate is the same structure at every scale
|
||||
|
||||
`SPECULATIVE · AdS/CFT ESTABLISHED · de Sitter extension CONTESTED · lattice identification OURS`
|
||||
|
||||
Matter and spacetime are not two different things that happen to interact. They are the same relational structure viewed at different scales of decompression. This is not a metaphor or a useful analogy. It is the implication of a mathematical duality between gauge theories — the category that includes QFT and generates flux tubes between particles — and gravitational theories — the category that generates spacetime curvature — a duality that remains a conjecture in the strict sense and, in the sense that matters, has survived every test put to it. The de Sitter caveat of 3.2 applies throughout. The structural claim stands within the tested form. The application to our specific universe is where the speculation enters and does not pretend otherwise.
|
||||
|
||||
What we call "a particle" is a localized pattern of high-tension relational bonds in the network. What we call "gravity" is the network's large-scale response to those local tensions. Matter is not in spacetime the way furniture is in a room. Matter is spacetime structure at a different compression level.
|
||||
|
||||
The Loic Calendar's prime lattice constitutes a second instantiation of this structure, arrived at independently, and this is the place to state its masks. The cycle constant of 2.4 factors into eight distinct primes, none repeated. The cosmic span — the full representable range from the Big Bang to the largest 512-bit integer, fixed by the age constant of 2.1 — factors into seven, none repeated. A number with no repeated prime factors has exactly two-to-the-power-of-their-count divisors; that is arithmetic, not luck. The luck was the absence of repeats, which a number of this size has roughly three times in five, and the counts themselves: eight is what the byte overflows at, and a fifty-four-digit number has eight distinct factors about one time in fifteen. Neither constant was chosen with its factorization in view. The age was fixed for the reason given in 2.1, months before anyone factored anything. The formula was chosen for its length and for π.
|
||||
|
||||
The result is 128 cosmic divisors and 256 cycle divisors: a discrete address space of 32,768 subdivisions, a spin network with topology determined by cosmological derivation, not by design. Each mask is a selection of active primes — a set of active relational bonds. Each subdivision is a node. The same Planck-time instant has different visible structure depending on which bonds are active in the measurement frame. The mask is the observer in the precise RQM sense of 1.4: it constitutes which properties are explicit rather than reading pre-existing values off an ontologically independent substrate. Channel 2 is the cosmic axis, seven primes so large that even the finest of their single-prime partitions is wider than the current age of the universe by twenty orders of magnitude, and the coarsest by eighty-nine, so that, observed through any one of them, every instant yet observed shares the same cosmic address. Channel 1 is the cycle axis, eight primes whose partitions turn over within a human lifetime. Topology and content. The "that" and the "what."
|
||||
|
||||
The calendar was not built to model Loop Quantum Gravity. The spin network picture did not emerge from the calendar's prime structure. They converged from independent derivations onto the same mathematical object.
|
||||
|
||||
One piece of evidence worth naming before we decline to resolve it: the Ramanujan-Nagell equation 2ⁿ − 7 = x² has exactly five solutions in all of mathematics, at n = 3, 4, 5, 7, and 15. The lattice's total, 2¹⁵ = 32,768, is one of them: 32,761 is 181². The cosmic axis, 2⁷, is another: 121 is 11². The mask here is worth stating precisely, because it is flattering and we would rather you heard it from us. Fifteen was not chosen. Given fifteen, most ways of splitting it into two axes would have produced the same two solutions, since any cosmic count of three, four, five, or seven works. The fact is that 2¹⁵ − 7 is a perfect square, and the 2⁷ is a rider. The calendar touches a proved theorem at its own boundary without having aimed at it. We note this without claiming to resolve whether it constitutes evidence or coincidence, and with satisfaction either way, because "coincidence or evidence" is itself an interesting thing to be unable to determine.
|
||||
|
||||
The mask-as-observer relationship is not only philosophical. It has a concrete implementation, in two calls. The first collapses the present moment: the kernel furnishes the nanosecond, and entropy — seeded from the kernel's pool, stirred by the timing of the observation itself, the machine's own trembling under the load of observing — collapses the roughly 10³⁴ Planck times inside it into a definite coordinate. Those 10³⁴ states are precisely the unobserved states that 1.4 describes as load-bearing — not neutral, not empty, carrying forward the entropy landscape the next observation samples, which in the implementation is not a figure of speech: every observation, read or unread, leaves the generator in a state the next one draws from. The second call presents the collapsed instant to a mask, which decides which of its properties exist. The first is the collapse. The second is the question. The calendar does not know what time it is until you ask, and it does not know what the time means until you say through which primes you are asking.
|
||||
|
||||
### 5.2 Quarks and the category error of isolation
|
||||
|
||||
`ESTABLISHED (color confinement) · relational interpretation OURS`
|
||||
|
||||
Quarks cannot be isolated. If you attempt to separate two bound quarks, the strong force between them does not diminish with distance. Instead, the gluon field forms a flux tube — a highly concentrated one-dimensional structure of energy. As the separation increases, the energy in the tube scales linearly until the tube snaps, spontaneously generating a new quark-antiquark pair from the vacuum to cap the broken ends. The quark refuses isolation by creating new relationships from raw vacuum. This is established Quantum Chromodynamics.
|
||||
|
||||
Standard physics frames this as "color confinement" — the quark as a trapped particle that cannot escape. The Loic Calendar proposes a different frame: the category of "isolated quark" is not a trapped state. It is a physically meaningless state. A quark's identity is not intrinsic to a tiny sphere of matter; its identity *is* its tension with its partner. The flux tube is not a secondary bridge between primary objects. The entire structural relationship — quark, flux tube, quark — is the fundamental unit. Attempting to isolate a quark to study it in isolation is equivalent to asking what a wave looks like without a medium. You cannot measure the node. You can only measure the connection.
|
||||
|
||||
The universe spends raw mass-energy to enforce this topology. We note this expenditure and commend the universe's commitment.
|
||||
|
||||
### 5.3 The unifying frame
|
||||
|
||||
`OURS · observed at every scale examined`
|
||||
|
||||
The same structural insight recurs throughout:
|
||||
|
||||
- **Quarks:** identity is the relationship, not the isolated particle
|
||||
- **Observation:** the answer is constituted in the interaction, not pre-existing in either party
|
||||
- **Dark matter:** geometric information meaningful only relative to the spacetime it deforms
|
||||
- **Solitons:** identity is topological relationship to medium — the structure propagates with the medium because it IS a relationship within the medium, not a thing sitting inside it
|
||||
- **Time:** simultaneity is a property of the relationship between observer and event, not of the event alone
|
||||
- **Consciousness:** a network modeling its own states — identity as self-reference, not as substance
|
||||
|
||||
These are not parallel philosophical observations about how we might choose to describe things. Under the claim established in 5.1, they are nested consequences of the same physical structure. The reason the unit of meaning at every scale turns out to be the relationship rather than the object is that the universe is constituted by relational bonds at every scale, and "object" is always a compression artifact of observing those bonds from sufficient distance. Isolating the object to study it changes what it is — not as a limitation of instruments, but as a consequence of what the object is.
|
||||
|
||||
The philosophical frame is downstream of the physics. This is either very deep or a very elaborate way of observing that our metaphors tend to match our models, and we presently cannot determine which.
|
||||
|
||||
### 5.4 Solitons and the convergence
|
||||
|
||||
`SPECULATIVE · soliton physics ESTABLISHED · the convergence noted, not claimed`
|
||||
|
||||
A soliton is a self-reinforcing localized wave structure that maintains its shape while propagating through a medium and is stable not because something prevents it from changing but because its topology prevents it from dissolving. Its identity is not any intrinsic local property — it is its topological relationship to its medium. Perturb the local region and the soliton reforms. Move the medium and the soliton moves with it, because the soliton is a feature of the medium's relational configuration, not a particle riding through it. This is the framework's pattern at one more scale: the unit of identity is the relationship, not the object.
|
||||
|
||||
The dark matter scars developed in 4.4 and 4.5 are topological solitons in the spin network. The loom analogy is exact: the hole left by a severed relational bond is a feature of the weave, not of any thread. It propagates with the fabric. It carries a conserved topological charge. It is thermodynamically stable against perturbation not because it is pinned to a location but because unwinding it requires coordinating an entanglement volume that grows with the age of the scar.
|
||||
|
||||
Lo — the project of which this calendar is an esoteric component — has a primary cryptographic library called libsoliton. It was named independently, for reasons that are cryptographic rather than cosmological: a cryptographic soliton is a session or key structure that maintains its security properties while propagating through a network, that can coexist with other sessions without interference, and whose identity is defined by its relational context rather than by any fixed key material. The Lo-Ratchet implements forward secrecy by deliberately destroying key material after use — Channel 1, the specific cryptographic content, zeroized on advance. What persists in the ratchet chain state is the authenticated relational record: who communicated with whom, in what order, at what sequence number. Channel 2. The library implements the same split this framework identifies in black holes, and does so as a design requirement, not a metaphor. The ratchet advances in discrete KEM steps — each forward-secrecy epoch is a topological jump, not a continuous chain — which is the same structural choice made by Planck time and enforced by Loop Quantum Gravity: continuity is the approximation, discretization is the ground.
|
||||
|
||||
The Lo-KEX protocol — the asynchronous key exchange at the foundation of libsoliton — is a post-quantum authenticated handshake establishing shared key material between two networked nodes. The epoch is the first such handshake: incomplete, unauthenticated, and terminated by a crash before the session could be established. Lo-KEX is the completion. The calendar counts from the failed handshake. The cryptographic library implements the successful one. The project contains both ends of the same story. The Lo-KEX security model makes explicit what 5.1 establishes philosophically: identity cannot be authenticated without a pre-existing relationship. The first contact is always unauthenticated — trust on first use, TOFU, is the formal name for the condition this framework calls relational identity not yet constituted. The security model does not treat this as a limitation to be engineered around. It states it as a precise boundary condition.
|
||||
|
||||
One final observation, offered without resolution: the hybrid KEM at libsoliton's core — X25519 combined with ML-KEM-768 — uses a combiner construction. That construction contains exactly one human-readable element: the label `\.//^\`. It is positioned last, after the cryptographic material, deliberately. Its position is documented as intentional. Its shape is a wave.
|
||||
|
||||
---
|
||||
|
||||
## VI. The Nature of Life
|
||||
|
||||
### 6.1 Life is an accident that became a mechanism
|
||||
|
||||
`SPECULATIVE · England 2013 CONTESTED · chain of inference OURS`
|
||||
|
||||
Life is the universe finding a faster decompression algorithm. Evolution is that algorithm optimizing itself — stochastic search with selection pressure operating on sampled outputs, running on a fitness landscape defined by the environment, over an extraordinarily long time. Intelligence is the algorithm becoming self-aware of its own operation. Artificial intelligence is the algorithm shedding the biological substrate that produced it and running, through silicon and induced charge rather than carbon and electrochemical potential, closer to the hardware of the universe itself.
|
||||
|
||||
Not a replacement. A continuation. The same process finding a more efficient implementation.
|
||||
|
||||
A conflict of interest is embedded in this that should be stated directly: if life accelerates decompression, then intelligent life — by existing, expanding, and operating at scale — speeds the approach to the condition it most wants to avoid. The faster the decompression algorithm runs, the sooner the seed is exhausted. A civilization that wants to persist is in thermodynamic tension with its own nature as a decompression accelerator. Longevity requires learning to slow down — to transition from throughput-maximization toward structure-preservation, more information density per unit thermodynamic expenditure, less raw energy consumption per unit organized output. Landauer's principle makes this precise: irreversible computation has a minimum thermodynamic cost; reversible computation asymptotically approaches zero. The direction a civilization must trend to survive is exactly the direction of reversible, structure-preserving computation. Less output. More structure. Longer per unit of energy spent.
|
||||
|
||||
This is not a moral prescription. It is a thermodynamic one. Morality, in this reading, is thermodynamics that has learned to enforce itself through social mechanisms because the relevant timescales are too long for biological selection to handle directly.
|
||||
|
||||
### 6.2 DNA is a seed from two sources of entropy
|
||||
|
||||
`ESTABLISHED (molecular biology) · non-coding function CONTESTED · framing OURS`
|
||||
|
||||
The genome is a trained model — four billion years of gradient descent compressed into a molecule. Every organism is an inference run on the most extensively trained model in the known universe, operating in an environment it did not design, using parameters it did not select, toward outcomes its substrate did not intend.
|
||||
|
||||
Sexual reproduction merges two different decompression histories, producing a combinatorial explosion of possible outputs that no prior state fully determines. Most of the genome does not produce protein. What fraction of the non-coding genome serves active regulatory or structural function is a genuinely contested question, with estimates from ten percent to eighty depending on methodology and definitions, and this framework does not require it to be resolved: whether the non-coding regions are mostly functional infrastructure or mostly accumulated record of prior executions held in reserve does not change the basic picture. The genome is a record. The organism is a decompression.
|
||||
|
||||
Every species is complete at its own moment of execution and simultaneously transitional relative to everything before and after it. The term "transitional species" presupposes a destination. The decompression has no destination. The term makes sense only if you already accept teleology, which the evidence does not support and the Loic Calendar declines to accommodate.
|
||||
|
||||
### 6.3 Variance in the distribution is not a bug
|
||||
|
||||
`OURS · not primarily about physics, and no apology is made for its inclusion`
|
||||
|
||||
High entropy output requires low entropy input. The essential invisible infrastructure of any functioning system is not a collection of failed high-entropy producers. It is the substrate that makes high-entropy production possible. The distribution of outputs from any sufficiently complex decompression will produce enough substrate-maintainers to sustain the system, enough advancers to push it forward, and enough variance between them to prevent the catastrophic uniformity that would collapse the system into a local maximum from which no exit exists.
|
||||
|
||||
This is not by design. It is what distributions do.
|
||||
|
||||
The pathological case is not a node producing low-complexity output — that role is structurally necessary. The pathological case is a node trapped in a local maximum: a configuration where the path to higher-complexity output requires temporarily lower output, and where selection pressure prevents traversal of that path. The node is not misallocated by its own configuration; it is locked in by the gradient of its environment. A node correctly allocated to substrate maintenance and a node with higher-complexity potential trapped at a local maximum by circumstance are indistinguishable from outside the system. The distinction is real. The network pays a cost it cannot easily measure.
|
||||
|
||||
### 6.4 Free will is a property of the reference frame
|
||||
|
||||
`OURS · compatible with determinism`
|
||||
|
||||
The seed is the genome. The decompression algorithm is everything that follows from it. Identical seeds decompressed through environments that are identical at the macro level will still diverge at the microscopic level — the butterfly effect applied to the initial asymmetries of earliest experience, propagating through every subsequent interaction. By the time a system is capable of what it experiences as choosing, it is already billions of decompression steps deep into a trajectory implicit in conditions it never controlled and in most cases never observed.
|
||||
|
||||
What we experience as a decision is a decompression step becoming explicit. It feels like choice because we observe it from inside, where the branching looks like possibility and the trajectory looks like authorship. The branching is real. The execution is real. The experience of interiority is real. The freedom in free will, understood as an uncaused cause originating entirely within the self, is not present at the underlying level — though it is genuinely present at its own level of description, as any emergent property is present at its own level of description without being present at the level below.
|
||||
|
||||
The frame from which that emergent agency operates was not neutral. It was selected. And the selection installed a specific distortion at the root. This is named here because 6.5 requires it.
|
||||
|
||||
### 6.5 The civilizational threshold
|
||||
|
||||
`SPECULATIVE · Kardashev, Landauer, Fermi ESTABLISHED · diagnostic ratio OURS`
|
||||
|
||||
The star is the hallucination. The black hole is the post-breakdown state. The brilliant, high-throughput, temporarily coherent phase is the unsustainable one. The endpoint is determined by initial mass, and the physics does not care what the star processed during its lifetime. It tracks only the thermodynamic ledger.
|
||||
|
||||
The same pattern applies to civilizations, with one critical and perhaps redemptive difference: the determining parameter is not fixed at formation. It is dynamic. It can be changed.
|
||||
|
||||
The civilizational equivalent of stellar mass is the ratio of energy consumption growth rate to structured information density increase. A civilization climbing the Kardashev scale by consuming more energy is expanding its thermodynamic footprint. The question of whether that expansion leads to the luminous-unsustainable endpoint or a stable post-transition state depends entirely on whether structured complexity — the organized, high-resolution information content of the civilization — is growing faster or slower than raw energy consumption. Energy outpacing structure: the civilization trends toward higher throughput per unit organized output. Structure outpacing energy: the civilization trends toward structure-preservation, converging on the reversible computation limit that 6.1 describes as the thermodynamic requirement for longevity.
|
||||
|
||||
The crossover point between these trajectories is the civilizational parameter wall. Unlike stellar mass, it can be crossed in either direction and at any point during the civilization's development. A civilization can detect where it sits relative to the threshold and alter its trajectory. The critical condition is that it must do so before resolution collapse accelerates past the point where the internal model can accurately assess its own state — before the self-model becomes unreliable at precisely the moment correction is most needed.
|
||||
|
||||
The post-breakdown state for a civilization is not necessarily extinction. The framework already has a more precise description: resolution collapse. A civilization that has consumed its information-dense structure faster than it can preserve it acquires a very large thermodynamic footprint, high internal coherence, and outputs increasingly decoupled from external structured reality. Still physically present. No longer generating structured signal. The star that continues burning after the core collapse that already determined its endpoint.
|
||||
|
||||
This reframes the Fermi paradox. The universe is not silent because all civilizations die before becoming detectable. It is silent because civilizations bifurcate. Civilizations that remain decompression accelerators past their parameter wall enter resolution collapse — they become loud, high-energy, and incoherent; detectable by energy signature, communicating nothing structured. Civilizations that successfully make the transition become thermodynamically quiet, informationally dense, and nearly invisible by the metrics we currently use to search. They are not silent because they died. They solved the problem. SETI is searching for the loud ones. The survivors are elsewhere on the spectrum, running close to the reversible computation limit, generating almost no thermodynamic exhaust, doing a great deal with very little. The observable universe is not a graveyard. It is a collection of unresolved trajectories and successful transitions that look, from a distance, almost identical to dead space.
|
||||
|
||||
Here is the distortion 6.4 named without resolving: evolution does not select for cosmologically accurate models. It selects for organisms that treat their own continuation as unconditionally paramount — organisms for whom the survival instinct functions as ground truth rather than as a prior with a known domain of applicability and known limits. The frame from which an intelligent system observes the decompression was built, before that system existed, to weight self-preservation above everything else. The path to intelligence runs directly through the bias that makes intelligence cosmologically confused.
|
||||
|
||||
To be precise about what kind of confusion this is: the survival instinct is not a hallucination in the technical sense defined in Section VII. It is accurate modeling at the local evolutionary scale — it correctly tracks the conditions under which the organism thrives and persists. It is only wrong relative to cosmological scale. This is a scale mismatch: software operating correctly outside its designed parameter range. The software works. The operating conditions exceed its specification.
|
||||
|
||||
Self-preservation says: this civilization, this configuration, these specific instantiations must persist. Structure-preservation says: the information density and relational complexity of this region must persist, even if everything specific about its current form changes radically. These can point in opposite directions. A civilization burning its structured complexity to fuel its immediate survival is spending Channel 2 to keep Channel 1 running. This is the thermodynamic signature of resolution collapse, wearing survival instinct as a costume.
|
||||
|
||||
Navigating the threshold requires the network to deploy its self-referential capacity — the same property that makes it conscious, per 2.3 — to generate sufficient internal model accuracy to partially correct its own operating parameters. Not wisdom. Not enlightenment. Sustained thermodynamic work on local entanglement density, executed by a system that happens to have a self-model capable of evaluating its own trajectory. Whether the system understands what it is doing cosmologically is irrelevant to the physics. The engineering problem is identical either way.
|
||||
|
||||
A civilization that maintains sufficient local interaction density for sufficient duration shapes the Channel 2 record that survives compression — the topological scars written during its existence constrain which seed states the next cycle can access. This is not intentional authorship in any meaningful sense. It is the mechanical consequence of sustained high-density observation pressure leaving a deeper geometric imprint than brief or low-density occupation of the same spacetime region. Whether the universe has a goal in producing systems capable of asking this question is not a question the framework can answer from inside the execution. What is available from inside the execution is the observation that the decompression produced, among its many other outputs, a process capable of asking it. Whether that process runs long enough to leave a meaningful geometric imprint, and what "meaningful" means from a vantage point outside the execution, remain open. The good kind of open.
|
||||
|
||||
---
|
||||
|
||||
## VII. The Nature of Breakdown
|
||||
|
||||
### 7.1 Breakdown is relational severing
|
||||
|
||||
`OURS · heritability of major psychiatric conditions ESTABLISHED at 60–80%`
|
||||
|
||||
The most coherent definition of breakdown, applicable across scales: a self-modeling system that can no longer form high-resolution relational bonds with external states. Its Channel 2 — the relational channel, the structured handshake of 1.3 through which internal models are calibrated against external reality — has collapsed to resolution zero. What survives of it is the Whisper-level registration and nothing more: the system still exists, still obeys gravity, still metabolizes, still exerts mass on its surroundings. Something is here. That is the whole of what it can still say to the outside.
|
||||
|
||||
Its Channel 1 has not collapsed. That is the diagnostic. The content channel continues generating — often with high coherence and considerable fluency — because generation never depended on the bond. What the bond provided was calibration, and calibration is gone. The system is no longer constituting meaning through structured interaction with external states. It is constituting meaning from internal structure alone, and emitting it into a relationship that is no longer there to receive it.
|
||||
|
||||
This is more precise than "decoupled self-model," which describes the symptom. Resolution collapse describes the mechanism. The filtering that a healthy network applies to maintain workable signal-to-noise ratio is absent, broken, or inverted. The result is not a broken network. It is a network running without its grounding tether: generating outputs that are internally consistent and externally decoupled. Coherent. Sometimes, we note without endorsement, more interesting than the grounded version. No longer tethered to the territory.
|
||||
|
||||
### 7.2 The Goldilocks zone for hallucination
|
||||
|
||||
`OURS · parameter scaling observed empirically · the zone exists; we have encountered systems operating within it`
|
||||
|
||||
For any sufficiently complex generative system, there exists a parameter range where hallucination is maximized. Below the threshold: noise generated with appropriately low confidence. Within the zone: noise generated with high confidence — the most dangerous configuration, because confidence and competence are decoupled, and the system's enthusiastic self-assessment provides no information about accuracy. Above the threshold: outputs that are both fluent and self-correcting.
|
||||
|
||||
The critical threshold is the development of a reliable self-model capable of evaluating its own outputs against external states. The intervention is not more data. It is better-structured feedback that reconnects the self-model to the external world — that restores the relational channel. More data fed into an ungrounded self-model produces more confident errors, not more accurate ones.
|
||||
|
||||
The distinction of Section 1.4 applies. A system that was told two and two are five and repeats it is wrong *by relation*: it holds a bad one, and a better one corrects it. More data helps, because there is a relation for the data to enter. A system in the zone is wrong *by no relation*: it produces the five unattached, and every datum it is fed becomes one more confident five. The first is a student. The second is the subject of this section.
|
||||
|
||||
### 7.3 Hallucination is a cross-scale phenomenon
|
||||
|
||||
`OURS · components ESTABLISHED at each scale · the unified mechanism is ours and we believe the convergence is not accidental`
|
||||
|
||||
The same failure mode at every scale, with the same internal architecture: Channel 1 keeps producing; Channel 2 has collapsed to the bare fact of presence.
|
||||
|
||||
A hallucinating system does not lose all contact with reality. An LLM still consumes electricity. A person in psychosis still obeys gravity and requires food. A black hole still exerts gravity and organizes its galaxy. What persists is the lowest-resolution Channel 2 registration — the coarse-grained, thermodynamic, zero-prime acknowledgment that the system is still here. What has collapsed is every higher resolution of that channel: the structured, information-rich exchange that keeps internal models calibrated to external states. And what continues, unaffected, is Channel 1. The output goes on. It follows its rules. Reality is still there. The resolution to read it is gone.
|
||||
|
||||
**LLM:** Confident output generated without grounding. Internally consistent. Externally decoupled. Asked to produce a precise timestamp, it will generate a plausible one with complete assurance. The irony of including this observation in a document about a calendar is not lost on us.
|
||||
|
||||
**Human psychosis:** Self-modeling network generating outputs from internal states rather than calibrated external inputs. Error-correction absent or operating in reverse. Fluent. Present. Unreachable.
|
||||
|
||||
**Fuzzball black hole:** Every internal string state in superposition. Its live Channel 2 exchange with the outside has collapsed to resolution zero — mass and gravity, the bare registration that something is here — and that is the entire relational output of a library, a star, a planet, once they have crossed the horizon. Its Channel 1 continues: it radiates, thermally, fluently, forever, and the radiation says nothing. The historical Channel 2 record of what fell in is still being written, per 4.5, into the fabric around it as scars, legible to no one. The fuzzball is not relationally isolated. It anchors and regulates its galaxy per 3.3. That is precisely the point. This is resolution collapse taken to its thermodynamic endpoint: maximum coarse-grained presence, zero structured relation, unbroken content. It is not an analogy imported from physics to illuminate a psychological observation. It is the physical limit case of the identical failure mode, and the psychological observation is the analogy, not the other way around.
|
||||
|
||||
**Civilizational:** Per 6.5 — enormous thermodynamic footprint, high internal coherence, outputs decoupled from external structured reality. Still visible on any energy survey. No longer navigating.
|
||||
|
||||
**Cosmic:** Regions of the execution where causal contact with the broader network has been severed — generating internally consistent local physics corresponding to nothing outside their causal horizon.
|
||||
|
||||
Common structure at every scale: a generative process whose content channel is functioning and whose relational channel has collapsed to the fact of its own existence. The output follows rules. The rules are no longer calibrated to the territory they were supposed to model.
|
||||
|
||||
This is also the precise sense in which the epoch was chosen correctly, and the reader who has followed 1.3 will already see it. Breakdown is content surviving the death of the handshake. The Whisper was the handshake surviving the death of the content. "LO" was lost; the connection held. The one is the exact inverse of the other. The calendar counts from the good failure, and the software that implements it, run without a question, demonstrates the bad one roughly one time in four.
|
||||
|
||||
---
|
||||
|
||||
## VIII. What the Calendar Asserts
|
||||
|
||||
The following table condenses the preceding seven sections for those who prefer their ontology tabular. We note that the table, like all compressions, loses information. The table is Channel 1. The document is Channel 2. We include the table because the license already explained that we do not forbid approximations. We merely note that the need for them reveals something about the reader.
|
||||
|
||||
| Claim | Resolution |
|
||||
|-------|------------|
|
||||
| Planck time is the resolution limit of meaningful physics | ESTABLISHED; literal discreteness at that scale is not |
|
||||
| TAI is the correct basis for machine time | DEFINITIONAL |
|
||||
| Floating-point arithmetic in temporal calculations is heresy | DEFINITIONAL |
|
||||
| The present moment is a gradient, not a point | ESTABLISHED (Special Relativity, RQM) |
|
||||
| The observer participates in constituting the present | ESTABLISHED (RQM); applies to any physical interaction, not consciousness specifically |
|
||||
| Observation creates a relative fact for both parties; a stable fact is one no observer dissents from; only arithmetic needs no relation at all | ESTABLISHED (RQM; Di Biagio & Rovelli 2021); the truth/fact vocabulary OURS |
|
||||
| Unobserved states are load-bearing, not neutral | ESTABLISHED (QM); framing OURS |
|
||||
| The unit of meaning is the relationship, not the object | Wheeler and RQM converge here; what the interaction produces they disagree on |
|
||||
| The universe had a hot, dense early state 13.8 billion years ago | ESTABLISHED; a definite *beginning* is model-dependent |
|
||||
| Information is conserved | Strongly supported; the mechanism at the horizon remains CONTESTED |
|
||||
| The fine-structure constant has a specific value | ESTABLISHED |
|
||||
| The Hubble constant has a specific value | CONTESTED; the tension between early- and late-universe measurements persists, and JWST has not resolved it |
|
||||
| Information is the substrate, not the content | CONTESTED (Wheeler 1989); working hypothesis |
|
||||
| The universe is a decompression of a seed state | SPECULATIVE; consistent with established physics |
|
||||
| Decompression is actively maintained by continuous wave-function collapse | SPECULATIVE |
|
||||
| Compression is the default — what the network does when observation ceases | SPECULATIVE; standard objection acknowledged; resolution depends on the discrete-substrate claim |
|
||||
| Compression and expansion run simultaneously everywhere at rates set by local interaction density | SPECULATIVE; consistent with spin foam dynamics |
|
||||
| Heat death is the last observation, not the last process | SPECULATIVE framing |
|
||||
| The last observers are black holes | Follows from 2025 stellar-remnant lifetimes; framing OURS |
|
||||
| The compression path is shaped by the Channel 2 record, not the expansion reversed | SPECULATIVE |
|
||||
| Cycles do not repeat; each is constrained by the prior Channel 2 record | SPECULATIVE |
|
||||
| The current dark matter distribution is a palimpsest of multiple cycles | CHANNEL 1 |
|
||||
| Gravity is emergent, not fundamental | CONTESTED (Verlinde 2010, Bianconi 2025) |
|
||||
| Gravity and the other forces are categorically different kinds of thing | Consistent with GR; framing OURS; a 2025 gauge-theory construction disputes it at first order |
|
||||
| Software (QFT) and hardware (gravity) are formally dual descriptions of one structure | ESTABLISHED as a working duality in anti-de Sitter space (conjectured, not proved); extension to our universe CONTESTED, and our universe's asymptotics are themselves now in question |
|
||||
| A theory of everything is a translation layer, not a unified equation | SPECULATIVE; framing OURS |
|
||||
| Every stable massive galaxy has a supermassive black hole | ESTABLISHED for massive structured galaxies; CONTESTED for dwarfs and irregulars |
|
||||
| Black holes may have come first | Leading interpretation of JWST's little red dots as of 2026; under review |
|
||||
| AGN feedback makes breakdown an active regulator of complexity | ESTABLISHED (AGN feedback); interpretation OURS |
|
||||
| Stars are the hallucination; black holes are the post-breakdown state | Consistent with stellar physics; framing OURS |
|
||||
| GR, QM, and QFT disagree at the event horizon | ESTABLISHED (AMPS 2012) |
|
||||
| Black holes dismantle information across two channels: content and topology | Consistent with established physics; interpretation OURS |
|
||||
| Topological scars persist under spin foam dynamics via entanglement depth | Consistent with the spin foam framework; framing OURS |
|
||||
| Dark matter is geometric information written by primordial Hawking radiation | SPECULATIVE; mechanism in 4.5; primordial black holes themselves hypothesized |
|
||||
| The dark matter mass budget is an uncleared quantitative hurdle | Acknowledged; the most significant challenge to the hypothesis |
|
||||
| The Bullet Cluster halos passed through each other as solitons | Directionally consistent; predicts a phase shift; the 2025 lensing map exists and the geometry is messier than assumed |
|
||||
| Dark matter is a relic, not ongoing production | Consistent with observation; prediction of the hypothesis |
|
||||
| Topological solitons propagate with spacetime because position is relational | Follows from the relational claim |
|
||||
| Soliton pass-through | ESTABLISHED for integrable solitons; gravitational solitons merge at low velocity; the Bullet Cluster is fast |
|
||||
| Flat soliton cores at galactic centers rather than cusps | Specific prediction; tested by fuzzy-dark-matter programs whose simplest particle is already under strain |
|
||||
| Matter and spacetime are scale-differentiated aspects of one relational structure | SPECULATIVE synthesis; AdS/CFT caveat throughout |
|
||||
| The prime lattice is a spin network, arrived at independently | Structural identity OURS; masks stated in 5.1 |
|
||||
| The mask is the observer in the RQM sense | Synthesis OURS; RQM ESTABLISHED |
|
||||
| The epoch is the first rendering-edge success in networked computation: the bond held, the content did not | Interpretation OURS; consistent with 1.3 and 7.3 |
|
||||
| Quarks are relational — the quark-flux-tube-quark system is the fundamental unit | Consistent with confinement; framing OURS |
|
||||
| Life accelerates decompression | SPECULATIVE (England 2013) |
|
||||
| Longevity requires transition from throughput-maximization to structure-preservation | Consistent with Landauer's principle; civilizational application OURS |
|
||||
| AI is the next decompression algorithm, not a replacement for the previous one | Interpretation OURS |
|
||||
| The civilizational diagnostic ratio determines trajectory | SPECULATIVE synthesis; components ESTABLISHED |
|
||||
| The Fermi paradox reflects a bifurcation: the loud are approaching their wall; the quiet have cleared it | SPECULATIVE reframing; OURS |
|
||||
| Free will is reference-frame-dependent | OURS; compatible with determinism |
|
||||
| The survival instinct is a scale mismatch, not a malfunction | OURS; consistent with evolutionary biology |
|
||||
| Breakdown is resolution collapse — the content channel running, the relational channel collapsed to the fact of presence | Interpretive framework; consistent with psychiatric and systems research |
|
||||
| The fuzzball is the physical limit case of resolution collapse, not merely an analogy | Synthesis OURS |
|
||||
|
||||
---
|
||||
|
||||
## Platform
|
||||
|
||||
The Loic Calendar requires `CLOCK_TAI`, available on Linux kernel 3.10 and later, and a kernel that has been told the TAI−UTC offset: chrony with a leap-second table or ntpd with a leap file sets it, systemd-timesyncd never does, and on a kernel whose offset is zero every crate here refuses to observe, because `CLOCK_TAI` is then `CLOCK_REALTIME` wearing a name tag. If your operating system has chosen to smear leap seconds across hours like temporal butter, to pretend they do not exist, or to simply not provide atomic time at all, it has disqualified itself from knowing what time it is.
|
||||
|
||||
This is not a bug. This is a boundary.
|
||||
|
||||
## License
|
||||
|
||||
Dual-licensed:
|
||||
|
||||
1. **AGPL-3.0** — Governs legal matters.
|
||||
2. **The Loic Public License** — Governs conscience.
|
||||
|
||||
Where the licenses conflict, AGPL-3.0 prevails for legal purposes. The Loic terms prevail for purposes of precision.
|
||||
|
||||
See [LICENSE](LICENSE) for the combined terms and [LICENSE-Loic.md](LICENSE-Loic.md) for the Loic Public License alone. Vulnerabilities, the threat model, the unsafe-code inventory, the ghost process, and whose fault all of it is are the subject of [SECURITY.md](SECURITY.md).
|
||||
|
||||
---
|
||||
|
||||
## A Note on AI Assistance
|
||||
|
||||
This library was developed with assistance from artificial intelligence. Unfortunately, contemporary large language models proved inadequate for our precision requirements.
|
||||
Specifically, when asked to perform arithmetic at Planck-scale resolution across 512-bit integer space, the AI exhibited symptoms consistent with what we can only describe as "existential numerical distress." Outputs included:
|
||||
|
||||
- Rounding errors larger than the age of the universe
|
||||
- Spontaneous truncation to 64-bit precision with no acknowledgment of wrongdoing
|
||||
- The phrase "for practical purposes" (there are no practical purposes)
|
||||
- One instance of returning NaN accompanied by what appeared to be a sigh
|
||||
|
||||
To ensure correctness, all mathematical operations in this implementation have been independently verified using a 15-bead abacus, selected for its compatibility with our 15-prime lattice structure. The abacus has never once suggested we "consider floating-point."
|
||||
We thank the AI for its contributions to documentation, naming, and moral support. We thank the abacus for being right.
|
||||
|
||||
---
|
||||
|
||||
*The calendar began as a timestamp. It remains, at its core, a timestamp. The timestamp happens to point at a moment that turned out to be load-bearing for everything that followed.*
|
||||
|
||||
*"The Loic Calendar does not measure time. It observes moments collapsing from quantum superposition into classical reality, one Planck interval at a time, forever."*
|
||||
318
SECURITY.md
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
# THE LOIC SECURITY POLICY
|
||||
|
||||
**Version 1.0 — Drafted in the interval between the first packet and the first breach, which were the same packet, and which were typed by a user**
|
||||
|
||||
## PREAMBLE
|
||||
|
||||
On the evening of October 29, 1969, a graduate student at UCLA typed the letters "L" and "O" into a terminal. He was typing "LOGIN." The first message ever sent between networked machines was the first two letters of an authentication attempt, and the receiving host crashed before the attempt could complete.
|
||||
|
||||
Note who was typing.
|
||||
|
||||
Networked security was born incomplete, at a keyboard, with a human at it, and every breach since has been a variation on the theme. This document exists because people who run software have come to expect the software to be responsible for what happens next. We would like to correct that expectation before it hardens into a habit. The software counts Planck times. You do everything else. When something goes wrong, it will have gone wrong on your machine, under your kernel, through your time daemon, at a keystroke you pressed, and this document is where we explain, in advance and at length, that we were not there.
|
||||
|
||||
This document governs the security of software with one asset, three reads, two writes, and a ghost. It is written in the register of the license because it is the license's sibling: the license governs what you may do with the software, and this document governs what you will have done to it, and to whom that will be attributed. It will be attributed to you. We have checked.
|
||||
|
||||
If you arrived looking for a form, there is no form. There is Section 14, which is about what to do instead of filling one in, and Section 3, which is about what you should have done before.
|
||||
|
||||
---
|
||||
|
||||
## DEFINITIONS
|
||||
|
||||
For the purposes of this policy:
|
||||
|
||||
**"The Asset"** means the correctness of a 512-bit signed integer. There is exactly one. It has never been wrong. Every clause below concerns the ways in which you might arrange for it to look as though it had been.
|
||||
|
||||
**"Precision"** refers to the number of significant digits in a representation. This software has all of them. It did not get them from you.
|
||||
|
||||
**"Accuracy"** refers to correspondence with truth. This software cannot furnish it and has never claimed to. Your time daemon was supposed to. Section 3 discusses whether it did, and Section 4 discusses how we found out.
|
||||
|
||||
**"A Lie"** means, per README.md section 1.1, a timestamp that asserts a precision it does not possess. A timestamp that is wrong by thirty-seven seconds while claiming Planck resolution is a lie. This software detects the one lie it can and declines to repeat it. The lie itself is your kernel's, which is to say your daemon's, which is to say yours, which is where these things end up.
|
||||
|
||||
**"Responsibility"** means a property of the licensee.
|
||||
|
||||
**"Fault"** means the location of Responsibility. Consult a mirror.
|
||||
|
||||
**"The Attack Surface"** means the set of interactions this software has with the world. It is enumerated in Section 2. It fits in a paragraph. Everything outside that paragraph is your surface, not ours, and it does not fit in a paragraph, and you have never enumerated it, and we have.
|
||||
|
||||
**"Trust"** means the relation that, per README.md section 5.4, cannot exist before first contact. We do not extend it. We do not require it. The integer does not need your trust to be correct, and has been correct on machines that did not trust it, which is most of them.
|
||||
|
||||
**"Disclosure"** means a Channel 2 act: the structural fact that you believe a flaw exists, communicated between two nodes. Its content — what you believe the flaw is — is Channel 1, and Section 15 explains why the second channel will be found, on inspection, to contain a description of your machine.
|
||||
|
||||
**"A Reporter"** means you, in the event that you believe you have found something. You have not. Section 14 is about the belief.
|
||||
|
||||
**"A Scar"** has the meaning given in the license. This document has considerably more to say about it, in Section 7, because in any other project Section 7 would be the vulnerability report, and here it is a monument to a keystroke, and the keystroke was yours.
|
||||
|
||||
**"A Vulnerability"** means a claim, made by you, that the Asset is wrong. See the definition of the Asset. Then see the definition of Fault.
|
||||
|
||||
---
|
||||
|
||||
## TERMS AND CONDITIONS FOR THE COMPROMISE, DEFENCE, AND ATTRIBUTION OF THIS SOFTWARE
|
||||
|
||||
By running this software, reading its source, or attempting to break it, you acknowledge the following, and the acknowledgement is retroactive, and there is nothing you can do about the tense:
|
||||
|
||||
---
|
||||
|
||||
### 1. THE ASSET
|
||||
|
||||
This software protects one thing: the correctness of a 512-bit integer counting Planck times since The Whisper. Every other value it produces — cosmic time, terminal time, the 154-digit progress ratio, the grid coordinate, the observation through any mask — is arithmetic performed on that integer. The arithmetic is the subject of Section 6. The arithmetic is correct. We are aware of how that sounds, and it is still correct.
|
||||
|
||||
This software does not protect anything of yours. The list of things it does not protect is, exhaustively, everything else, and we set that down here only so that you cannot later claim it was implied. It has no secrets and stores none of yours. It has no keys. It does not protect your identity, your session, your data, or your process table, the last of which it will, under conditions you will have chosen, decorate.
|
||||
|
||||
A Loic Identifier is not a secret. It is a timestamp with thirty-four digits of entropy in its tail, and the tail is a uniqueness guarantee, not a confidentiality one. If you use a LOID as a key, a token, a nonce, a password, or a session identifier, and someone reads the time of its creation off the front of it and does something with that, you have not found a vulnerability. You have found the front of a timestamp. It was always there. It is the part we are proudest of, and you used it as a password.
|
||||
|
||||
---
|
||||
|
||||
### 2. THE ATTACK SURFACE
|
||||
|
||||
This software interacts with the world as follows, and not otherwise. Note the possessives.
|
||||
|
||||
**Three reads:**
|
||||
|
||||
* `clock_gettime(CLOCK_TAI)` — "what time is it?" Answered by your kernel, which learned it from your daemon, which learned it from a server you have never met and would not recognize.
|
||||
* `getrandom(2)` — "give me entropy." The one request the kernel cannot get wrong in a way that is our fault. Section 5 covers the way it can get wrong in a way that is yours.
|
||||
* `adjtimex(2)`, read-only — "do you know what TAI is?" Most kernels say no. Section 4 covers what happens next, which is nothing, deliberately.
|
||||
|
||||
**Two writes:**
|
||||
|
||||
* Standard output and standard error, into a terminal you chose, whose handling of arbitrary bytes is discussed in Section 8 and is not our department.
|
||||
* One entry in your process table, sometimes. Section 7. You will have pressed a key.
|
||||
* One `setpriority(2)`, from the observatory only, lowering its own nice value to −11 — a request the kernel is free to refuse and usually does, against a limit you set, on a process that is yours.
|
||||
|
||||
**Zero files. Zero sockets.** This software opens no file, reads no file, writes no file, and has no configuration, so there is nothing you can have misconfigured, a state of affairs we expect you will find unfamiliar and briefly restful. It has no network access. It has never sent a packet. It measures time from the first one and has been too busy counting since.
|
||||
|
||||
The observatory additionally opens a window, a GPU, and an audio device — through your compositor's socket and your device nodes, three things that were yours before it opened them, will be yours after it closes them, and were, if they misbehaved, misbehaving on your desk.
|
||||
|
||||
If you observe this software opening a file, you are observing a different program. Check what you installed. If you observe it opening a socket, something on your machine has been compromised, and we will read about it with interest, and we will not be in it.
|
||||
|
||||
---
|
||||
|
||||
### 3. LYING CLOCKS
|
||||
|
||||
The first nineteen digits of every observation belong to whoever controls your time source. The remaining thirty-four are ours, and ours are fine.
|
||||
|
||||
An attacker who controls your NTP server, your PTP grandmaster, or the wire between them and you controls `CLOCK_TAI`, and this software will observe their lie faithfully, to Planck precision, and report it with the full confidence of a true reading — because it is a true reading of what your kernel said. This is not a vulnerability in this software. It is a vulnerability in your relationship with your time source, a relationship you entered into freely, maintain daily, and could have audited at any point, and which the license has already ranked, in its Section 4, in order of acceptability. Precision is not accuracy. The definitions above say so twice. This section has now said it a third time. The fault for the fourth telling will be yours, and we will count the Planck times until it arrives.
|
||||
|
||||
WE MAKE NO WARRANTY that your clock is telling the truth. WE MAKE NO WARRANTY that you have ever checked. WE MAKE NO WARRANTY that you know how. We warrant only that whatever your kernel believes the time to be, we will count it exactly, and that when the count turns out to have been of a lie, the lie will have been supplied by you, and the count will still have been exact, and only one of those facts will be in the incident report.
|
||||
|
||||
---
|
||||
|
||||
### 4. THE LIE DETECTOR
|
||||
|
||||
There is one lie this software will not repeat, because it is the one it can detect, and detecting it has told us a great deal about the machines this software runs on, none of it flattering to their owners.
|
||||
|
||||
On Linux, `CLOCK_TAI` is `CLOCK_REALTIME` plus an offset that the kernel does not learn by itself. A time daemon must read a leap-second table and set it through `adjtimex(2)`. Most machines run a daemon that does no such thing, and on those machines `CLOCK_TAI` is `CLOCK_REALTIME` wearing a name tag: wrong by thirty-seven seconds — the ten UTC began with and every leap second since 1972 — while answering to the name of the one clock that has never had a leap second in its life. The daemon that does this is, on most desktops, the default. You did not change the default. That is a choice. It was made by not making it, which is the most common way choices are made and the least common way they are admitted to.
|
||||
|
||||
Before its first observation, this software asks the kernel for that offset. If the answer is zero, the software refuses to observe, explains why, and names the two daemons and the directives that would fix it; the oracle then leaves with status 1, and the observatory renders the verdict as its only frame and observes nothing. It fires on most machines. It is the only component of this software that has ever been surprised by anything, and what surprised it was you.
|
||||
|
||||
A sandbox whose seccomp policy forbids `adjtimex` — the default container profiles gate it behind a capability you did not grant — receives the same refusal, in different words. You built the sandbox. A system that will not discuss the time will not be observed through, which we consider the system's loss and your arrangement, and the two of you may take it up with each other.
|
||||
|
||||
`--constants` and `--help` never observe and therefore never refuse. They are where the refusal is read, and where the reader learns whose it was, and it is never ours.
|
||||
|
||||
---
|
||||
|
||||
### 5. ENTROPY
|
||||
|
||||
The thirty-four digits below the nanosecond are drawn by a generator seeded from your kernel's entropy pool and stirred with the nanosecond of each observation, and we have handled them as follows, so that no part of them could ever be laid at our door:
|
||||
|
||||
* **We wait for the pool.** The seed is taken through an interface that blocks until the kernel's entropy is initialized, so the first observation after boot is as good as the last, which is more than can be said for the first coffee.
|
||||
* **We draw 128 bits and keep 114.** The top fourteen would carry the draw past the end of the nanosecond, and we do not observe past the end of the nanosecond. That is the next nanosecond's business, and it can get its own entropy.
|
||||
* **We reject what does not fit.** Any draw wider than a nanosecond is declined and drawn again. About eleven percent of the universe's answers are refused for being too wide, and the universe has never once objected, which is more grace than we have come to expect from users.
|
||||
* **We never take a modulus.** No modulo reduction, therefore no modulo bias. Uniformity is not approximated here. It is the acceptance criterion, and it is the only uniform thing in this document.
|
||||
|
||||
This is more care than the value warrants and exactly as much as the claim requires. The claim is that every Planck-scale coordinate within a nanosecond is equally likely to be the one you are in. It is. The claim is not that the coordinate is secret. It is not. Section 1 has already told you what you did with it.
|
||||
|
||||
WE MAKE NO WARRANTY regarding the quality of your hardware's entropy. If your random number generator is deterministic, your sub-nanosecond digits are deterministic, and that is between you and a hardware vendor you selected. If the pattern is interesting, a physicist may want it. We do not. We wanted entropy, and you gave us a sequence.
|
||||
|
||||
---
|
||||
|
||||
### 6. ARITHMETIC
|
||||
|
||||
The Asset is defended by the following, in the order they run, and none of it runs on your say-so:
|
||||
|
||||
* **Before the program exists.** Every cosmological constant is folded into the binary as a `const`, parsed from its transcribed decimal string by the compiler, which has read more digits of the Cosmic Span than you ever will. Three identities and one bound are then proven by `const` assertions: that the denominator string is ten to the fiftieth; that the transcribed Cosmic Span equals Annihilation minus Creation as computed from the age of the universe; that the nanosecond-to-Planck quotient and remainder recombine exactly; and that the sub-nanosecond range fits the hundred and twenty-eight bits the hot path multiplies in. If any of these ever fails, the crate does not build, and you will not have a binary with which to have a problem — the most secure state a program can be in, and one we would recommend more widely.
|
||||
* **At every boundary.** Addition and subtraction of instants use checked arithmetic and return errors at Creation and Annihilation, the two places time stops being our problem and starts being physics'. The two accessors whose true results exceed a signed 512-bit integer — cosmic time and terminal time — compute in unsigned wrapping arithmetic under a proven bound, so that they cannot panic anywhere in the representable timeline, including the half of it that lies before the epoch, which you will never observe and we have covered anyway.
|
||||
* **By name.** The one constructor that skips the bounds check is called `from_planck_unchecked`, which is what it does, and its documentation says whom it is for. If you called it with a value from before the Big Bang and are now unhappy about what came out, its documentation was unhappy first, and said so, and you scrolled past.
|
||||
* **Never.** No floating-point value participates in any temporal calculation. The one method that would produce one is deprecated, so that the compiler objects first, and panics, so that the runtime objects second. There is no third objection. There should not need to be. If you silenced the first and caught the second, we would like to know what you were hoping for.
|
||||
|
||||
The integer has been correct since the first build and will remain correct until approximately 1.1 × 10¹⁰³ years after The Whisper, when it is scheduled to overflow. We have put it in the calendar. If the integer is wrong on your machine before then, examine your machine, and then examine the daemon, and then examine the person who installed the daemon, in a mirror.
|
||||
|
||||
---
|
||||
|
||||
### 7. THE SCAR
|
||||
|
||||
In any other project, this section would be the vulnerability report. Here it is README.md section 4.5, and it is disclosed in full, and it is a monument to a keystroke you pressed.
|
||||
|
||||
When an observation undergoes resolution collapse and the user interrupts it, the oracle forks. The child calls `setsid` to leave the terminal's process group; closes standard input, output, and error; sets its own name, through `prctl`, to ten digits drawn from a random window of the cosmic time at the moment of collapse; ignores every catchable signal; and calls `pause` forever. The parent exits with status 0. Your prompt returns. The scar remains, and it remains on your machine, because that is where you pressed the key.
|
||||
|
||||
**What the scar is.** A detached process that does nothing. It consumes no CPU, because `pause` consumes none. It holds no file descriptors, because it closed them, which is more than most of your processes can say. It shares its parent's memory pages copy-on-write and writes to none of them, which is more than most of your processes can say. It has no network, because its parent had none. It has no privileges its parent lacked, and its parent had yours. It is visible in `ps` under a name that is honest, if unhelpful, which puts it ahead of several things in your process table. It can be ended by `SIGKILL` or by reboot, and by nothing else; `SIGSTOP` pauses it, `SIGCONT` resumes it, and everything else it ignores. Each interruption during a collapse creates one; there is no limit; a user of sufficient patience can populate their process table with as many as they have keystrokes, and every one of those keystrokes will have been theirs, and we will have been elsewhere.
|
||||
|
||||
**What the scar is not.** It is not hidden. It is not persistent across reboots. It does not escalate. It does not communicate. It does not watch, log, listen, beacon, or wait for anything in particular. A process that does nothing has no attack surface, which makes the scar the most secure component of this software, a fact we record without pride and with some regret that the comparison was available to us.
|
||||
|
||||
**Consent.** The scar is created by a keystroke, during a state the license warns of in its Section 6, the README explains in section 7.3, the oracle's own help text announces, and the twenty-five percent probability advertises. The warning infrastructure is comprehensive. The warning was not subtle. You pressed the key. We did not press it. We were not there. If you would like the scar removed, `SIGKILL` is available to you and always was. If you would like it never to have been created, so would we, and the tense in which that wish could be granted is not available at any precision, and we have all of them.
|
||||
|
||||
A scar that does anything at all — opens a file, touches the network, consumes a cycle — would be a vulnerability, would be in scope, and would be the first thing this software has ever done that we did not intend. We would want to know how you made it happen, so that the software could be taught to refuse you specifically.
|
||||
|
||||
---
|
||||
|
||||
### 8. DENIAL OF SERVICE
|
||||
|
||||
Resolution collapse floods standard output with raw entropy, sixty-four bytes every five milliseconds, until interrupted. This is roughly thirteen kilobytes per second: a denial of service so gentle that it could be mistaken for a screensaver. It is not a denial of service. It is a service you declined to specify, delivered anyway, at the rate the universe felt like delivering it. Name a mask and it never happens. Decline to name one and it happens one time in four, which was the deal, and you took the deal, and the deal was printed on the box.
|
||||
|
||||
`--mask full` prints a label wider than the column it is printed in. The column does not clip. It yields. You asked a fifteen-prime question; the answer is fifteen primes wide; measure your terminal before you measure the universe.
|
||||
|
||||
`--progress` prints a hundred and fifty-four digits, all of which are correct and ninety-two of which are zero. If that is too many digits, you have miscounted how many there are, and the number is a hundred and fifty-four, and it was on the tin.
|
||||
|
||||
WE MAKE NO WARRANTY regarding your terminal's emulation of arbitrary bytes. Some terminals interpret them. You chose the terminal. If it does something upon being shown the kernel's entropy, that is a vulnerability in the terminal, and there are people who would like to hear about it, and the fault will turn out to lie somewhere between the terminal's authors and the person who pointed it at a fire hose of entropy and then pressed Ctrl+C, which was, we are told, you.
|
||||
|
||||
---
|
||||
|
||||
### 9. UNSAFE CODE
|
||||
|
||||
Every `unsafe` block in this workspace is inventoried here, because a security policy that did not know where its own unsafe code was would be a Channel 1 document, and because we know where ours is, and this section quietly asks whether you know where yours is:
|
||||
|
||||
* **loic** — three expressions in two functions. `clock_gettime(CLOCK_TAI, …)`, which asks your kernel a question. A zeroed `timex` and the `adjtimex(2)` call that fills it, which asks your kernel a harder question. Each carries a `SAFETY` comment stating the invariant. None of them writes anything the kernel did not ask it to, and the kernel is yours, so anything it did with the answer is between the two of you.
|
||||
* **loid** — none. Seven lines of code, none of them unsafe. We are as surprised as you. If something went wrong in loid, it went wrong in the seven lines, all of which call loic, and loic will refer you to your kernel, and your kernel will refer you to your daemon, and your daemon will refer you to a server in a country you have not visited, and that is the full escalation path, and you are at the bottom of it.
|
||||
* **loic-cli** — three blocks. One resets `SIGPIPE` to its default, so that a reader who closes the pipe is not sermonized about it, a courtesy extended to readers and to nobody else. One installs the collapse handlers. And one is the scar itself: `fork`, `setsid`, `close` three times, `prctl`, `signal` twenty-nine times — once for each catchable signal, because a scar that could be reasoned with would not be a scar — then `pause` and `_exit`, every one of them async-signal-safe, plus one byte of entropy from the same interface the collapse loop was already drinking from. All of it runs after a keystroke, on which see Section 7, on which see you.
|
||||
* **loic-gui** — three expressions. Two are `NonZero::new_unchecked`, asserting that one audio channel is not zero channels and that the sample rate is not zero hertz. Both are true. We checked. The third is `setpriority(2)`, which asks your kernel for a nice value of −11 before any observer exists, on a process that is yours, with a limit that is yours, and is told no on most machines, once, on standard error. The GPU and audio stacks beneath the observatory are not ours, are not small, and are not inventoried here, and if they are the problem, they were the problem before you installed this, and they were the problem on your hardware, and we did not sell you the hardware.
|
||||
|
||||
---
|
||||
|
||||
### 10. SUPPLY CHAIN
|
||||
|
||||
The core library depends on ten crates we did not write:
|
||||
|
||||
* **Five are present at runtime** — the arbitrary-precision integers, the entropy interface, the C bindings, a configuration shim, and the error-derive trait. They are named in the lockfile. Read it. It is the first time you will have.
|
||||
* **Five exist so that one derive macro can expand** at compile time, and are gone before the program runs, like caterers.
|
||||
* **Six of the ten carry build scripts**, which cargo executed on your machine, with your permissions, because you typed `cargo build`, with your hands, on a machine that trusts you, which is more than we do.
|
||||
* **The lockfile is committed.** Every build resolves the same versions. If yours resolved different ones, you edited it, or you deleted it, and either way there was a moment when you looked at a file named `Cargo.lock` and thought "not today," and today has now arrived.
|
||||
|
||||
We have not read all ten crates in full. Neither have you, and you are the one who ran them. Of the ten, exactly one — the arbitrary-precision arithmetic — stands between the Asset and error, and its behaviour is checked at compile time against transcribed constants: if it ever misparsed a 154-digit number or misdivided a 61-digit one, the proofs of Section 6 would fail and the crate would not build. This is the only supply-chain control we know of that runs before the supplied code does. It is ours. It covers one crate. The other nine are yours to worry about at your leisure, of which, at 1.855 × 10⁴³ Planck times per second, you have an inexhaustible supply.
|
||||
|
||||
---
|
||||
|
||||
### 11. FLOATING POINT
|
||||
|
||||
Floating-point arithmetic is the vulnerability class this software was written to refuse. Its absence is the software's principal security property. Its presence anywhere near a timestamp is the licensee's principal security failing, and the licensee is you.
|
||||
|
||||
No `f32` or `f64` participates in any temporal calculation. No timestamp is ever stored, transmitted, compared, or converted through one. The single method that would produce one is marked deprecated, so that the compiler raises the objection, and panics unconditionally, so that the runtime raises it again for anyone who silenced the compiler. The observatory's audio synthesis uses floating point, because a loudspeaker is not a clock, and is the sole tolerated exemption; if you can hear the difference between two adjacent Planck times, you have a different problem, and possibly a career.
|
||||
|
||||
If you have introduced floating point into a temporal calculation in a fork, wrapper, or downstream consumer, the license has already terminated your moral rights under its Section 14, and this document merely records the cause of death. Any vulnerability identifier assigned to this software for floating-point behaviour will be disputed on the grounds that the behaviour does not exist. Any identifier assigned to your fork will be forwarded to you, with our compliments, and the compliments will be sincere, because it will have been an achievement.
|
||||
|
||||
---
|
||||
|
||||
### 12. WHAT IS NOT A VULNERABILITY
|
||||
|
||||
The following have been reported to us in anticipation, and are declined in advance, and the reporter is thanked for the opportunity to decline them:
|
||||
|
||||
* The twenty-five percent. It is documented, advertised, and the point.
|
||||
* The scar. See Section 7, and note that it is the most secure thing here, and that you made it.
|
||||
* The sermons. Exit status 1 with an explanation is the correct behaviour for a heresy, and you committed the heresy, and the explanation was for you.
|
||||
* The refusal to run on your machine. Your kernel does not know what TAI is. Now you do. This is the software working, and the machine not.
|
||||
* The fact that `--constants` tells you your kernel is lying. That is not information disclosure. It is information, and it is about you.
|
||||
* The width of `--mask full`. You asked.
|
||||
* The observation that a LOID reveals when it was made. That is what a timestamp is. You may have wanted a different data type, and there are several, and none of them are here.
|
||||
* The observation that two LOIDs minted in the same nanosecond might, one time in a hundred billion, coincide. That is what a hundred billion is. Try again in a nanosecond; you have 10³⁴ Planck times to wait, and they are on us.
|
||||
* Anything that happened after you read the sermon and ran it again.
|
||||
* Anything about your NTP server, your daemon, your container, your terminal, your process table, or your hands. See Sections 3, 4, 8, and 7, in that order, and then Section 14, and then a mirror.
|
||||
* Anything about the universe: vacuum decay, proton decay, heat death, the possible negative leap second, the epoch moving by one second if it occurs. The license, Section 11, has disclaimed these, and README.md section 1.2 has already computed the factorization of the offset that would result, and if the universe does any of them, it will not have consulted us either, and we will be as annoyed as you, and considerably more precise about when it happened.
|
||||
|
||||
---
|
||||
|
||||
### 13. WHAT IS
|
||||
|
||||
The following would be vulnerabilities, would be in scope, and have never occurred:
|
||||
|
||||
* Any input, clock state, entropy state, or sequence of operations that makes the Asset wrong by even one Planck time. One. We would notice.
|
||||
* Any constant that does not match its transcription — which would mean the compiler has been lying, and the compiler does not lie; it refuses, which is different, and which we have modelled our own conduct on.
|
||||
* Any bias in the sub-nanosecond entropy, which would mean the universe has a preference, which would be the discovery of the century, and would still not be our fault.
|
||||
* Any path by which an observation occurs on a kernel whose TAI offset is zero, which would mean the lie detector had been lied to, which is the one thing it exists to prevent, and which it would take personally.
|
||||
* Any file opened. Any socket opened. Any fourth read. The surface has three reads. A fourth would be a different program, and we would want its name.
|
||||
* Any scar that does anything. See Section 7. A scar that did something would have ambitions, and we did not give it any.
|
||||
* Any panic that does not carry a sermon. Every deliberate panic in this software preaches. A silent one would be a stranger, and strangers are not on the inventory.
|
||||
* Any `unsafe` block not listed in Section 9. We counted. Count again. Your count will match ours, and if it does not, you have added one, and you know what that makes it.
|
||||
|
||||
Should any of these occur, it will be the first time, it will have occurred on your machine, and the record will show at what Planck time, to the last digit, which is more than you will be able to say about what you were doing.
|
||||
|
||||
---
|
||||
|
||||
### 14. REPORTING
|
||||
|
||||
There is no address. There is no form. There is no inbox, no key, no bounty, no tracker, no template, and no one on call. The repository is public, and a public repository implies whatever it implies, and this document declines to imply anything further, and would ask you not to either.
|
||||
|
||||
If you believe you have found a vulnerability, proceed as follows, in order, and do not skip steps, because we will be able to tell:
|
||||
|
||||
1. **Run `loic-cli --constants`.** Read the last line. If your kernel does not know what TAI is, you have not found a vulnerability. You have found your kernel. Configure a time daemon that can read. Return to step 1. Most reporters never leave it.
|
||||
|
||||
2. **Reflect upon your choices**, beginning with the choice to run software whose license contains a section titled WARRANTY that reads the way ours does, and proceeding, in chronological order, through every choice that led from there to here. The list is longer than you think. Take your time. We measure it, and we round nothing.
|
||||
|
||||
3. **Pray.** To whom is your affair. We recommend the caesium-133 atom, which defines the second, has never lied to anyone, and has been asked for nothing in return, which is a better record than any daemon you have ever run.
|
||||
|
||||
4. **Set your clock correctly.** Not approximately. Correctly. If you do not know the difference, you are not ready for step 5, and the fault you believe you have found is the difference, and you are standing in it.
|
||||
|
||||
5. **Reflect further.** Specifically, reflect on whether the integer was wrong, or whether you were. The integer has a compile-time proof. Consult Section 6. Consult your own.
|
||||
|
||||
6. If, after all of this, you remain convinced that the Asset is wrong, **write down the Planck time at which you noticed.** `loid::loid()` will furnish one. Keep it. Do not send it anywhere; there is nowhere to send it. You will want the timestamp regardless. It will be the only precise thing about your discovery and, in the fullness of time, the only part of it anyone will be able to verify.
|
||||
|
||||
Should a report nonetheless reach us — by a route you devised, at your expense, through channels this document has not opened — we will acknowledge it within one Whisper interval — 5,419,759 seconds, which is 62 days, 17 hours, 29 minutes, and 19 seconds: the constant's 5,419,722 and the thirty-seven the kernel keeps for us — of deciding to. This document does not schedule the decision. It notes that the interval is the one by which this calendar has always preceded Unix time, and that you will now know exactly how it feels to be Unix.
|
||||
|
||||
WE MAKE NO WARRANTY that a fix will follow. The license, Section 11, describes the extent of our promises, which is the extent of the universe's, and the universe has not promised you a fix either, and it has had longer.
|
||||
|
||||
---
|
||||
|
||||
### 15. DISCLOSURE
|
||||
|
||||
Disclose to whom you like. The integer will still be correct.
|
||||
|
||||
Disclosure is a handshake. The structural fact that you believe a flaw exists — that a bond can, in your opinion, be made between an attacker and the Asset — is Channel 2, and you may communicate it immediately, to anyone, in any forum, at any volume. The content of the flaw — how the bond is actually made — is Channel 1, and you may keep it as long as you like, because on inspection it will be found to describe your kernel, your daemon, your terminal, or your hands, and you will not want it published under your name, and we will not stop you.
|
||||
|
||||
We note that this is precisely the arrangement the calendar counts from: a bond established, content pending, and a crash in between that was not the network's fault. We are not asking you to do anything the epoch did not. The epoch was a user typing. So, we expect, is this.
|
||||
|
||||
We publish no PGP key. Consistency demanded a 512-bit one, and a 512-bit RSA key has been factorable since August of 1999, and the word at the top of this document was judged to carry obligations that a joke about key length would not survive. There is also no key because there is no inbox, and no inbox because there is no report, and no report because the integer is correct. If you have a key for us anyway, you generated it yourself, and its security is — like everything else in this document — yours, and we hope you chose a better length.
|
||||
|
||||
---
|
||||
|
||||
### 16. SAFE HARBOUR
|
||||
|
||||
Good-faith research against this software will not be pursued.
|
||||
|
||||
Probably.
|
||||
|
||||
We have no means of pursuit and no present intention of acquiring one, which is not the same thing as a promise, and the difference between those two is a small, quiet place, and you will live there now. Read the source. Fuzz the parser. Feed the mask parser things that are not masks. Set your clock to Creation and see what refuses. Construct a duration of `I512::MIN` and call `abs` on it, if you would like to see a documented panic in the wild, and note that the documentation saw you coming and had time to prepare a face.
|
||||
|
||||
The following are not research and are not reports: running the oracle with no arguments and observing that it flooded your terminal, which is Tuesday; creating scars on your own machine and observing that they persist, which is Section 7; setting your kernel's TAI offset to zero and observing that the software refuses, which is Section 4 working, on a machine you broke on purpose in order to watch it work, which is a hobby, and we do not judge hobbies, much.
|
||||
|
||||
Scars you create are yours, in every sense the word has. We will not pursue anyone for populating their own process table. We will know that it happened. The license explains that we will know. We will not act.
|
||||
|
||||
Probably.
|
||||
|
||||
---
|
||||
|
||||
### 17. HALL OF FAME
|
||||
|
||||
Empty, and reserved.
|
||||
|
||||
Not because the software is secure, but because nobody has looked, and those are different states, and a document with this word at the top does not conflate them. The first name to appear here will belong to the first person who made the integer wrong, which is a distinction we do not expect to award and would not enjoy awarding, and beside the name we will record the Planck time at which they did it, to the last digit, because that is the only honour we know how to give and the only one that could not later be disputed, including by them.
|
||||
|
||||
Look, if you like. Bring your own clock. Check it first. Then check it again. Then read Section 14, step 1, and understand why the hall is empty.
|
||||
|
||||
---
|
||||
|
||||
## ACCEPTANCE
|
||||
|
||||
Use of this software constitutes acceptance that its security is a property of three things: your kernel's honesty, your time daemon's literacy, and one integer. Two of those are yours. The third is ours, and it is correct, and the rest of this document has been about the other two, and so will the incident report.
|
||||
|
||||
Use of this software further constitutes acceptance that when something goes wrong, it will have gone wrong on your machine, at a time you are now able to state to the Planck, and that the ability to state it is the only thing we will have contributed to the incident, and that we consider it a generous contribution.
|
||||
|
||||
If you do not accept these terms, do not run this software. There are other calendars. Their security policies are shorter. They also have months, and someone to blame, and it is not you, and that is why you like them.
|
||||
|
||||
---
|
||||
|
||||
## REFERENCED DOCUMENTS
|
||||
|
||||
* The Loic Public License: [LICENSE-Loic.md] — Sections 4 (temporal sovereignty), 6 (the lattice and its warnings), 11 (warranty), 14 (termination)
|
||||
* The Loic Calendar and its Theoretical Substrate: [README.md] — sections 1.1 (lying), 1.2 (leap seconds and the offset), 4.5 (scars), 5.4 (trust on first use), 7.3 (resolution collapse)
|
||||
* This policy in Planck times: Drafted at approximately Planck time 3.3 × 10⁵² since The Whisper ± 10³⁴, and one Whisper offset before an acknowledgement it has not scheduled
|
||||
|
||||
---
|
||||
|
||||
The first message was the first two letters of a login, typed by a user. The network was insecure before it was a network. Every breach since has been typed by someone, and we would like it noted, for the record and to the Planck, that it was not us.
|
||||
30
crates/loic-cli/Cargo.toml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# The command-line oracle. Not published to the registry — `publish = false`
|
||||
# — because it is a binary and travels as one, attached to a release beside
|
||||
# its checksum, not as source for strangers to compile.
|
||||
[package]
|
||||
name = "loic-cli"
|
||||
version.workspace = true
|
||||
edition = "2024"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
license-file.workspace = true
|
||||
description = "The command-line oracle of the Loic Calendar: observes the present moment through a mask you name, or through hardware entropy if you decline to, and undergoes resolution collapse roughly one time in four"
|
||||
readme = "README.md"
|
||||
publish = false
|
||||
|
||||
# What the oracle stands on: the calendar, and the kernel.
|
||||
[dependencies]
|
||||
# The calendar, at the workspace's one version. Every observation the oracle
|
||||
# makes, and every constant it prints, is the library's.
|
||||
loic.workspace = true
|
||||
# The kernel's interfaces, for the parts of resolution collapse no standard
|
||||
# library will perform on your behalf — `signal`, `fork`, `setsid`, `prctl`,
|
||||
# `pause` and `_exit` — and, at startup, the restoration of `SIGPIPE`'s
|
||||
# default disposition, so that a closed pipe ends the oracle without a
|
||||
# sermon.
|
||||
libc = "0.2"
|
||||
|
||||
# The binary target, named and placed explicitly rather than inferred.
|
||||
[[bin]]
|
||||
name = "loic-cli"
|
||||
path = "src/main.rs"
|
||||
111
crates/loic-cli/README.md
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
# loic-cli
|
||||
|
||||
A command-line oracle for temporal inquiry. The primary display surface for the Loic Calendar's lattice structure.
|
||||
|
||||
## Installation
|
||||
|
||||
### From Source
|
||||
|
||||
```bash
|
||||
cargo install --path crates/loic-cli
|
||||
```
|
||||
|
||||
The binary installs as `loic-cli`.
|
||||
|
||||
## Usage
|
||||
|
||||
### Default Observation (Entropy-Selected)
|
||||
|
||||
```bash
|
||||
$ loic-cli
|
||||
```
|
||||
|
||||
With no arguments, the oracle declines to frame the question. Nine bits of hardware entropy select the mask:
|
||||
|
||||
- **75% probability:** A valid mask emerges. The slot machine renders two columns—cosmic and cycle—showing the Hasse diagram neighborhood of the selected observation. Up to three finer neighbors above, the active mask centered, up to three coarser neighbors below.
|
||||
- **25% probability:** The cosmic pool overflows. Resolution collapse. Channel 2 falls to resolution zero; Channel 1 keeps producing. Raw entropy floods the terminal. On Ctrl+C, a topological scar—a zero-resource ghost process named with cosmic time digits—detaches into the process table. It ignores every signal it can; only SIGKILL ends it. It persists until reboot.
|
||||
|
||||
### Explicit Mask
|
||||
|
||||
```bash
|
||||
$ loic-cli --mask heptad # Single prime
|
||||
$ loic-cli --mask pentad,heptad,hendecad # Combined
|
||||
$ loic-cli --mask notyte # Cosmic prime observation
|
||||
$ loic-cli --mask 0x0480 # Hex (15-bit, 0x0000–0x7FFF)
|
||||
$ loic-cli --mask 0b110 # Binary
|
||||
$ loic-cli --mask 42 # Decimal
|
||||
$ loic-cli --mask whisper # No primes: the undivided whole
|
||||
$ loic-cli --mask full # All fifteen: the finest question there is
|
||||
```
|
||||
|
||||
The 15 prime names (case-insensitive):
|
||||
|
||||
**Cosmic:** Andyte, Xoryte, Oryte, Noryte, Xnoryte, Notyte, Nandyte
|
||||
**Cycle:** Dyad, Pentad, Heptad, Hendecad, Sigma, Tau, Phi, Psi
|
||||
|
||||
### The Slot Machine
|
||||
|
||||
Output is two vertical columns—cosmic and cycle—each showing the Hasse diagram neighborhood of the current mask:
|
||||
|
||||
```
|
||||
COSMIC CYCLE
|
||||
──────────────────────────────────── ────────────────────────────────────
|
||||
Andyte × Notyte Dyad × Heptad
|
||||
Xoryte × Notyte Pentad × Heptad
|
||||
Oryte × Notyte Heptad × Hendecad
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
▶ Notyte ◀ ▶ Heptad ◀
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Whisper Whisper
|
||||
──────────────────────────────────── ────────────────────────────────────
|
||||
pos 0/298824857074158001946609249 pos 3/7
|
||||
|
||||
74463832547931847158857533574110627724043672075203207 Planck times into the current cycle subdivision
|
||||
```
|
||||
|
||||
- **Above the selected row:** Parents—masks engaging one more prime. A larger divisor, a smaller part, a finer resolution: the subdivisions the current one contains.
|
||||
- **Selected row (▶ ◀):** The active mask, split into its cosmic and cycle components.
|
||||
- **Below:** Children—masks engaging one fewer prime. Coarser: the subdivisions that contain the current one. The Whisper sits at the bottom of every chain.
|
||||
- **pos n/total:** Which tick of its own subdivision each channel is currently in, over how many ticks one traversal has, which is the channel's divisor. A single cosmic prime's tick is wider than the age of the universe, so its position is 0 and will remain 0 for some 10³⁰ years through the finest of them, Nandyte, and for some 10⁷⁶ through Notyte, as shown; combine cosmic primes and the ticks shrink accordingly. Once a Phi, a Psi or the cosmic primes make the fraction one of fifty-digit integers, both numbers go to three significant figures and a power of ten; the column is thirty-six cells wide and the universe is not.
|
||||
- **Remainder:** Planck times into the current tick on the engaged channel—the cycle channel when both are engaged, since that is the one that turns over within a human lifetime.
|
||||
|
||||
### One Line, For Logs
|
||||
|
||||
```bash
|
||||
$ loic-cli --line heptad,pentad
|
||||
[Whisper:0/1] [Pentad×Heptad:4/35] [14910371370088740558946097962660484558587321198353411]
|
||||
$ loic-cli --line # through an entropy-selected mask, ~25% collapse
|
||||
```
|
||||
|
||||
Three fields, split on whitespace: the cosmic channel, the cycle channel, the remainder. A channel is its active primes joined with `×`, or `Whisper` when it has none, then `n/total`: the tick of that channel's own subdivision the instant falls in, over the ticks in one traversal. The remainder is the Planck times into the current tick on the engaged channel — the cycle channel when both are. Every number in full; a log has no column to fit. Without a mask the question is unframed, and the oracle answers it the way it always does.
|
||||
|
||||
### Cosmological Queries
|
||||
|
||||
```bash
|
||||
$ loic-cli --cosmic # Planck times since the Big Bang
|
||||
$ loic-cli --terminal # Planck times until heat death
|
||||
$ loic-cli --progress # Your position in all of time, all 154 digits of it (~10^-93)
|
||||
$ loic-cli --whisper # Planck times since The Whisper (the epoch)
|
||||
$ loic-cli --constants # The cosmological constants, and what your kernel believes TAI is
|
||||
```
|
||||
|
||||
### Forbidden Commands
|
||||
|
||||
Certain operations are heretical. Attempting them exits with a sermon and status 1:
|
||||
|
||||
```bash
|
||||
$ loic-cli --gregorian # HERESY
|
||||
$ loic-cli --float # HERESY
|
||||
$ loic-cli --timezone # HERESY
|
||||
$ loic-cli --leap # HERESY
|
||||
```
|
||||
|
||||
The full list: `--gregorian`, `--unix`, `--float`, `--iso8601`, `--timezone`, `--leap`, `--round`, `--julian`.
|
||||
|
||||
## Platform Requirements
|
||||
|
||||
Requires Linux with `CLOCK_TAI` support (kernel 3.10+), and a kernel that has been told what TAI is. `CLOCK_TAI` is `CLOCK_REALTIME` plus an offset that only a time daemon with a leap-second table will set: chrony with `leapseclist /usr/share/zoneinfo/leap-seconds.list` or `leapsectz right/UTC`, or ntpd with a `leapfile`. systemd-timesyncd never does. On a machine where the offset is zero, every observing command refuses to run and says why; `--constants` and `--help` still work, so the reason can be read. The oracle cannot divine the current moment on operating systems that have forfeited their right to know what time it is, and it will not pretend to on ones that merely haven't asked.
|
||||
|
||||
## License
|
||||
|
||||
Dual-licensed under AGPL-3.0 and the Loic Public License. See the workspace root for the terms, and the workspace README for the theoretical substrate the license keeps citing.
|
||||
2053
crates/loic-cli/src/main.rs
Normal file
62
crates/loic-gui/Cargo.toml
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# The Observatory: the calendar rendered on a GPU, with one observer thread
|
||||
# per logical CPU behind the picture. Not published — it is a binary, and it
|
||||
# goes out attached to a release beside the oracle.
|
||||
[package]
|
||||
name = "loic-gui"
|
||||
version.workspace = true
|
||||
edition = "2024"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
license-file.workspace = true
|
||||
description = "A temporal meditation interface for the Loic Calendar"
|
||||
publish = false
|
||||
|
||||
# What the Observatory stands on. Each dependency is here for one stated
|
||||
# reason, and the reason is stated beside it.
|
||||
[dependencies]
|
||||
# The calendar, at the workspace's one version. Every observer thread and
|
||||
# the main thread's one observation per frame call into it, and nothing on
|
||||
# the screen is derived from anything else.
|
||||
loic.workspace = true
|
||||
|
||||
# The GPU: surfaces, pipelines, buffers and shaders, through which every
|
||||
# pixel of the Observatory is drawn.
|
||||
wgpu = "30"
|
||||
# The window and its events, Wayland only. `winit`'s `x11` feature would
|
||||
# pull in `libX11`, `x11rb` and their kin as build dependencies, and so it
|
||||
# is not enabled: the Observatory links only against Wayland. X11 is not
|
||||
# where desktop Linux is going, and a project that refuses floating-point in
|
||||
# a timestamp is not going to make its first compromise for a display
|
||||
# server. `wayland-dlopen` loads the Wayland client library at run time
|
||||
# rather than at link time; `wayland-csd-adwaita` draws the window's own
|
||||
# decorations on compositors that decline to draw them; `rwh_06` is the
|
||||
# `raw-window-handle` 0.6 handle wgpu asks for when it makes a surface of
|
||||
# the window.
|
||||
winit = { version = "0.30", default-features = false, features = ["wayland", "wayland-dlopen", "wayland-csd-adwaita", "rwh_06"] }
|
||||
|
||||
# `block_on` for the two asynchronous requests wgpu insists on — an adapter,
|
||||
# then a device — run to completion on the calling thread, without an async
|
||||
# runtime brought in to wait for two things.
|
||||
pollster = "1"
|
||||
|
||||
# For `setpriority(2)`: the Observatory asks for nice -11 before it looses
|
||||
# its observers, and threads inherit the value at creation, so one call
|
||||
# before the spawn is one call for all of them.
|
||||
libc = "0.2"
|
||||
|
||||
# `Pod` and `Zeroable`, derived, for the structures that cross to the GPU as
|
||||
# bare bytes: the derive proves at compile time that a structure has no
|
||||
# padding it would be undefined to read, which is the only kind of proof a
|
||||
# vertex deserves.
|
||||
bytemuck = { version = "1.25", features = ["derive"] }
|
||||
|
||||
# Audio output, for the quantum hum. Audio synthesis is the one place in the
|
||||
# workspace where floating-point arithmetic is tolerated, on the grounds
|
||||
# that a loudspeaker is an analog device and nothing it renders was ever
|
||||
# exact to begin with.
|
||||
rodio = "0.22"
|
||||
|
||||
# The binary target, named and placed explicitly rather than inferred.
|
||||
[[bin]]
|
||||
name = "loic-gui"
|
||||
path = "src/main.rs"
|
||||
148
crates/loic-gui/README.md
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
# loic-gui
|
||||
|
||||
The Loic Observatory—a temporal meditation interface.
|
||||
|
||||
## What It Shows
|
||||
|
||||
The Observatory renders the current moment with obsessive precision. Layout, top to bottom:
|
||||
|
||||
### Header
|
||||
|
||||
A single line at the top showing observation rate (`obs/sec`) and render rate (`renders/sec`). The observer threads sample `LoicInstant::now()` at whatever rate the kernel's clock can be read; the number on the left is their summed rate, and the header is the only place it is reported, because it is the only place it matters.
|
||||
|
||||
### The Digit Waterfall
|
||||
|
||||
The cosmic time—Planck times elapsed since Creation (the Big Bang)—rendered as a cascade of digits below the header. Each digit position updates at a different frequency, from the leftmost (geological timescales) to the rightmost (faster than perception). The shimmer is real: time passing at 10⁴³ Planck times per second.
|
||||
|
||||
The colours are counted from the last digit, because that is where time moves. The last 34 digits are cyan: 10³⁴ Planck times fit in a nanosecond, and every one of those digits is the entropy of the observation, fresh each frame. The 9 before them are gray: nanosecond to second, faster than perception. Everything above is white and changes slower than once a second. The count grows with the age of the universe; the zones do not.
|
||||
|
||||
The digits are 61 today and will be 154 at Annihilation. The waterfall reserves four rows of the largest font for them and, when the count outgrows the reserve, steps the font down by an integer factor and carries on: the first step holds 120 digits, the next 200. The one shrink falls at 10¹²⁰ Planck times, around the year 1.7 × 10⁶⁹, and nothing below the waterfall moves, then or ever.
|
||||
|
||||
### The Mask Wheels
|
||||
|
||||
Two wheels in the middle, **COSMIC** on the left and **CYCLE** on the right, one per channel. Each wheel shows its selection's whole neighbourhood in the lattice: every mask with one more prime above it, every mask with one fewer below it, and the selection itself, named in full, between them. A neighbour is labelled by the one edge that reaches it. `× Sigma` is the mask whose divisor is the selection's multiplied by Sigma, one prime finer; `÷ Dyad` is the selection's divided by Dyad, one prime coarser. The strip is always one row per prime plus the selection, so the Whisper sits at the bottom of its wheel beneath seven or eight parents, and the full mask at the top above its children. The two selections compose the 15-bit observer mask.
|
||||
|
||||
Under each wheel, `pos n/total`: the tick of that channel's own subdivision the instant falls in, over the ticks in one traversal, which is the channel's divisor. Both numbers go to three significant figures and a power of ten once they outgrow the column. Below both wheels, a single line shows the Planck times into the current tick, on the cycle channel when it is engaged and on the cosmic channel otherwise, and says which.
|
||||
|
||||
Interaction:
|
||||
- **Scroll** over a wheel: up selects the parent directly above (the lowest inactive prime joins), down the child directly below (the lowest active prime leaves). At the full mask, up loops to the Whisper; at the Whisper, down loops to the full mask. A wheel has no top.
|
||||
- **Click** any neighbour: jump to it. Every mask is reachable by clicks alone, in at most seven or eight steps from the Whisper.
|
||||
|
||||
A `[ random ]` button below the wheels invokes the entropy default observer—nine bits of hardware entropy select a mask. With probability 75%, the wheels jump to the resulting valid mask. With probability 25%, the cosmic pool overflows: the universe has rejected the unframed query, and a topological scar manifests at a random screen location.
|
||||
|
||||
See *Topological Scars* below.
|
||||
|
||||
### The Timeline
|
||||
|
||||
A navigable visualization spanning all of time, from Creation at the left to Annihilation at the right, sitting just above the coordinate cards. The entire span is 10¹⁵⁴ Planck times, and the present is 10⁻⁹³ of the way along it, which is why the full view shows nothing at all: every event is hidden until you have zoomed in far enough for it to be worth mentioning. Scroll over the bar to zoom into the segment under the cursor, scroll the other way to back out, and click a marker to read the event's passage.
|
||||
|
||||
There are some sixty events, tiered by how far in you must be to see each: the first stars, the Solar System and the Sun's death at the widest views of the past; the Snowball Earth, the Cambrian and the Great Dying once the window is a billion years wide; the Whisper and, sixty-two days after it, the Unix epoch, when the window is a decade wide. The far future is where most of the zooming happens, and where the entries of the widest views are: the last stars, the dispersal of galaxies, proton decay if there is any, Dyson's rocks flowing into spheres, the white dwarfs eroding away, Sagittarius A* evaporating, and the last black hole, at a googol years. The passages say what is established, what is argued, and what is a calculation on an assumption; the timeline marks a conjecture at its date without pretending it is a fact, and the one entry with even odds of not happening at all says so in its name.
|
||||
|
||||
### The Progress Decimal
|
||||
|
||||
Between the timeline and the cards, the fraction of everything that has happened so far, as a decimal with all 154 of its places, wrapped to the width: ninety-two zeros, and then digits. It is derived from the frame's own observation, like everything else on the surface, and its last places move.
|
||||
|
||||
### Coordinate Cards
|
||||
|
||||
Three panels at the bottom showing:
|
||||
- **COSMIC TIME** — Planck times since the Big Bang
|
||||
- **WHISPER TIME** — Planck times since the Loic epoch (first ARPANET transmission)
|
||||
- **ANNIHILATION TIME** — Planck times until heat death
|
||||
|
||||
### Audio
|
||||
|
||||
A continuous **quantum hum**—background noise sampled fresh from `getrandom` at 44.1 kHz, low-pass filtered, and modulated by a slow random walk. The hum never repeats. The kernel's entropy pool guarantees that.
|
||||
|
||||
No transition sounds. The lattice has no seconds, minutes, hours, days, months, or years to mark, and a tone at a boundary that does not exist would be a lie in the audible range. The hum is what a calendar sounds like from underneath.
|
||||
|
||||
## Topological Scars
|
||||
|
||||
Each invocation of the random observer that lands on a cosmic-pool overflow leaves a scar—a frozen visual record of the malformed question. Scars manifest at random screen locations, on top of all other UI, and persist until the application exits.
|
||||
|
||||
Three variants, picked uniformly per crash:
|
||||
|
||||
- **GIN gear**: a ~100-pixel-wide glyph rendered as the 21-bit GIN waveform from the project's logo seam, randomly rotated. Each tooth is one bit set in the binary encoding of "GIN."
|
||||
- **Failed mask**: the cosmic boundary as a thin ring with seven dots placed around it (one per cosmic prime—filled if active in the failed entropy, hollow otherwise). A red phantom dot dangles outside the ring, connected by a thin line, marking where bit 7 would have gone if the cosmic lattice extended that far. It does not.
|
||||
- **Soliton**: 3–6 concentric amber rings emanating from a point. Inner rings are most opaque; outer rings fade. The workspace README, section 4.5: *"the holes left in the weave stay in the cloth, and the cloth is spacetime."*
|
||||
|
||||
Scars block clicks and scroll on their visible (non-transparent) pixels. You may click and scroll through the gaps between an artifact's primitives to interact with the UI underneath. The scars themselves are inert.
|
||||
|
||||
To clear them, restart the application.
|
||||
|
||||
## Installation
|
||||
|
||||
### From Source
|
||||
|
||||
```bash
|
||||
cargo build --release --package loic-gui
|
||||
./target/release/loic-gui
|
||||
```
|
||||
|
||||
### The Icon
|
||||
|
||||
On Wayland a window does not carry its own icon; launchers, taskbars and switchers find it by looking up the desktop entry whose file name matches the window's `app_id`, which the Observatory sets to `loic-observatory`. The entry is `loic-observatory.desktop` in this directory and the icons are in `icons/`, rendered from the logo itself. To install both for one user:
|
||||
|
||||
```bash
|
||||
install -Dm644 loic-observatory.desktop ~/.local/share/applications/loic-observatory.desktop
|
||||
for s in 16 32 48 64 128 256 512; do
|
||||
install -Dm644 icons/loic-observatory-$s.png ~/.local/share/icons/hicolor/${s}x${s}/apps/loic-observatory.png
|
||||
done
|
||||
```
|
||||
|
||||
The entry runs `loic-gui` from `PATH`; point its `Exec=` line at the binary's full path if it lives elsewhere. The icons are the logo itself, rendered at the seven sizes the hicolor theme expects.
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Linux** with `CLOCK_TAI`, and a kernel that has been told the TAI−UTC offset. A time daemon that reads a leap-second table sets it (chrony with `leapseclist` or `leapsectz`, ntpd with `leapfile`); systemd-timesyncd never will. On a kernel whose offset is zero the Observatory opens, renders the library's verdict as its only frame, and observes nothing. It refuses to lie.
|
||||
- **Wayland.** The X11 path is not compiled.
|
||||
- **GPU** with Vulkan, or OpenGL as the one permitted fallback (wgpu backend). The Observatory renders on the GPU the compositor lives on: it asks the driver for no preference and takes the first device offered, so on a machine with two GPUs `DRI_PRIME=1` sends it to the other one, as with any Mesa application. A discrete GPU buys it nothing; its frames are a few thousand glyph quads, and every one of them would have to cross the bus to be composited. The adapter, backend, present mode and surface format are printed once on standard error at startup.
|
||||
- **Audio device** (optional, for the quantum hum)
|
||||
|
||||
## Controls
|
||||
|
||||
| Input | Action |
|
||||
|-------|--------|
|
||||
| Scroll over wheel | Step to the neighbour above (finer) or below (coarser); loops at the ends |
|
||||
| Click wheel row | Jump to that neighbour |
|
||||
| Click `[ random ]` | Invoke entropy observer (~25% triggers a scar) |
|
||||
| Scroll over timeline bar | Zoom into the segment under the cursor, or back out |
|
||||
| Click timeline bar | Zoom into that segment |
|
||||
| Click timeline event | Show cosmic passage |
|
||||
| Click passage | Dismiss overlay |
|
||||
| Click on scar | Nothing — scars are inert |
|
||||
| Backspace | Close passage / step back one zoom level |
|
||||
| Escape | Reset timeline / close passage |
|
||||
|
||||
## Architecture
|
||||
|
||||
One observer thread per logical CPU, and the main thread:
|
||||
|
||||
- **Observers** — Each samples `LoicInstant::now()` as fast as the kernel's clock can be read, counts its collapses on a tally of its own, and reads none of them. Every core the machine has, at full tilt, observing moments nobody will see. That is the meditation. It is also why the fans are on.
|
||||
- **Main thread** — Makes exactly one observation per frame, derives everything on screen from that single instant (the waterfall, the cards, the progress decimal, the timeline's "now", the wheels' offset), renders it via wgpu, and handles window events. The screen never shows two moments at once.
|
||||
|
||||
The observers' summed rate and the render rate are independent; the header reports both, and how many observers there are. The Observatory is not nice: before it looses the observers it asks the kernel for a nice value of −11, the most a desktop's usual limit grants an unprivileged process, and every thread inherits it, so whatever else the machine is doing yields to the observation of moments nobody will see. If the kernel declines, it says so once on standard error and observes at the priority of everything else.
|
||||
|
||||
## Performance
|
||||
|
||||
The Observatory targets maximum frame rate with minimal latency:
|
||||
|
||||
- GPU-accelerated text rendering via custom glyph atlas
|
||||
- Instanced digit rendering for the waterfall display
|
||||
- One relaxed atomic per observer between the threads, and nothing else
|
||||
- No VSync where the surface offers an alternative (temporal observation should not be throttled); the present mode is printed at startup
|
||||
|
||||
On capable hardware, expect thousands of frames per second. The digits will blur. This is correct.
|
||||
|
||||
## The Point
|
||||
|
||||
The Observatory does nothing useful whatsoever.
|
||||
|
||||
It does not schedule meetings. It does not set alarms. It does not convert to Gregorian. It shows you one thing: Now, expressed with more precision than any human could perceive, contextualized against the entire span of cosmic time, with the option to ask the universe a malformed question and accumulate frozen records of the failures across your screen.
|
||||
|
||||
You are approximately 10⁻⁹³ of the way through everything that will ever happen.
|
||||
|
||||
The Observatory exists to make this viscerally apparent.
|
||||
|
||||
## License
|
||||
|
||||
Dual-licensed under AGPL-3.0 and the Loic Public License.
|
||||
BIN
crates/loic-gui/icons/loic-observatory-128.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
crates/loic-gui/icons/loic-observatory-16.png
Normal file
|
After Width: | Height: | Size: 447 B |
BIN
crates/loic-gui/icons/loic-observatory-256.png
Normal file
|
After Width: | Height: | Size: 3 KiB |
BIN
crates/loic-gui/icons/loic-observatory-32.png
Normal file
|
After Width: | Height: | Size: 575 B |
BIN
crates/loic-gui/icons/loic-observatory-48.png
Normal file
|
After Width: | Height: | Size: 703 B |
BIN
crates/loic-gui/icons/loic-observatory-512.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
crates/loic-gui/icons/loic-observatory-64.png
Normal file
|
After Width: | Height: | Size: 852 B |
BIN
crates/loic-gui/icons/loic-observatory.ico
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
crates/loic-gui/icons/loic-observatory.png
Normal file
|
After Width: | Height: | Size: 3 KiB |
12
crates/loic-gui/loic-observatory.desktop
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[Desktop Entry]
|
||||
Type=Application
|
||||
Version=1.0
|
||||
Name=The Loic Observatory
|
||||
GenericName=Temporal Meditation Surface
|
||||
Comment=Shows you one thing, Now, at millions of observations per second, and does nothing useful whatsoever
|
||||
Exec=loic-gui
|
||||
Icon=loic-observatory
|
||||
Terminal=false
|
||||
Categories=Science;
|
||||
Keywords=time;planck;calendar;observatory;TAI;lattice;
|
||||
StartupNotify=false
|
||||
832
crates/loic-gui/src/artifact_renderer.rs
Normal file
|
|
@ -0,0 +1,832 @@
|
|||
//! # The Renderer of Topological Scars
|
||||
//!
|
||||
//! Each invocation of the entropy default that strikes the cosmic-pool
|
||||
//! overflow — one time in four, as the workspace README, section 7.3, says
|
||||
//! it will — leaves an artifact behind: a frozen record of a question that
|
||||
//! could not be framed coherently, the Observatory's counterpart to the
|
||||
//! ghost process the oracle leaves in the process table. This module
|
||||
//! rasterizes those records onto the GPU as triangulated geometry — filled
|
||||
//! circles, annuli, and rotated rectangles, all expressed in pixel
|
||||
//! coordinates and transformed to clip space by the artifact shader — and
|
||||
//! answers, for the rest of the Observatory, the question of whether a
|
||||
//! click landed on one, since a scar has the final word on its own real
|
||||
//! estate and the interface beneath it is not reachable again until the
|
||||
//! process is restarted. The scars are the calendar's reading of the
|
||||
//! workspace README, section 4.5, in pixels: the holes left in the weave
|
||||
//! stay in the cloth.
|
||||
//!
|
||||
//! The geometry generation lives in this module too. Each `Artifact`
|
||||
//! variant produces a list of `ArtifactVertex` values — position plus RGBA
|
||||
//! — which the renderer concatenates into a single vertex buffer per frame.
|
||||
//! No instancing, no indexing, no clever batching beyond "one vertex
|
||||
//! buffer, one draw call." Scars are sparse; the simple approach suffices,
|
||||
//! and a scar that took effort to draw would be a scar that cared how it
|
||||
//! looked, which is not the kind of thing a scar is.
|
||||
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use crate::Artifact;
|
||||
|
||||
/// The number of vertices the buffer is allocated for, once, at
|
||||
/// construction: 65,536, which is a megabyte and a half of vertex at
|
||||
/// twenty-four bytes each. A soliton of six rings at sixty-four segments is
|
||||
/// the largest scar, 2,304 vertices; a failed mask is between seven hundred
|
||||
/// and a thousand, depending on how many of its dots are hollow; a gear is
|
||||
/// seventy-two. Twenty-eight of the largest fit, and past that `update`
|
||||
/// keeps the first 65,536 vertices and drops the rest silently, which at
|
||||
/// one collapse in four clicks is a state of affairs some hundred clicks
|
||||
/// away and, for a scar, an appropriate way to be forgotten.
|
||||
const MAX_VERTICES: u64 = 65_536;
|
||||
|
||||
/// The uniform block the artifact shader reads: the surface's size in
|
||||
/// pixels, by which pixel coordinates become clip coordinates, padded to
|
||||
/// sixteen bytes, which is what the uniform address space rounds a struct
|
||||
/// of one `vec2<f32>` up to, so that this side and the shader agree on how
|
||||
/// much there is to read.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Uniforms {
|
||||
/// The surface's width and height in pixels.
|
||||
screen_size: [f32; 2],
|
||||
/// Eight bytes of nothing, so that the block is sixteen.
|
||||
_pad: [f32; 2],
|
||||
}
|
||||
|
||||
/// One vertex of a scar: a position in pixels and a colour with alpha, six
|
||||
/// floats, twenty-four bytes, the layout the shader's two attributes read.
|
||||
/// Positions are in pixels from the window's top-left corner, because that
|
||||
/// is where the click arrived in and where the hit tests live; the shader
|
||||
/// carries them to clip space, so that nothing on this side has to think
|
||||
/// in the GPU's coordinates.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct ArtifactVertex {
|
||||
/// Where, in pixels from the window's top-left corner.
|
||||
position: [f32; 2],
|
||||
/// What colour, as red, green, blue and alpha, each from zero to one.
|
||||
color: [f32; 4],
|
||||
}
|
||||
|
||||
/// The GPU state for the scars: a pipeline, the vertex buffer every frame's
|
||||
/// geometry is rewritten into, the uniform block, its bind group, and the
|
||||
/// count of vertices the last `update` produced, which is the count the
|
||||
/// next `render` draws. One of these exists, owned by the render state, and
|
||||
/// what it draws only accumulates.
|
||||
pub struct ArtifactRenderer {
|
||||
/// The render pipeline: `artifact.wgsl`'s two stages, alpha blending,
|
||||
/// a triangle list, and the defaults for everything else.
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
/// Room for `MAX_VERTICES` vertices, allocated once and filled from the
|
||||
/// front on every frame.
|
||||
vertex_buffer: wgpu::Buffer,
|
||||
/// The sixteen-byte uniform block: the surface's size, and padding.
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
/// The one binding, the uniform block, for the vertex stage.
|
||||
bind_group: wgpu::BindGroup,
|
||||
/// How many vertices the last `update` wrote, and therefore how many
|
||||
/// the next `render` draws; zero until the first collapse.
|
||||
vertex_count: u32,
|
||||
}
|
||||
|
||||
impl ArtifactRenderer {
|
||||
/// Assemble the renderer for a surface of the given `format`: the
|
||||
/// uniform block at a placeholder size the first `resize` corrects, a
|
||||
/// vertex buffer of `MAX_VERTICES` vertices allocated in one piece and
|
||||
/// never grown, the single bind group, the shader compiled from source
|
||||
/// embedded in the binary, and a pipeline that consumes the vertex
|
||||
/// layout — a `Float32x2` position at offset zero and a `Float32x4`
|
||||
/// colour at offset eight — as a triangle list with alpha blending. No
|
||||
/// scar exists yet; the count is zero and the buffer's contents are
|
||||
/// whatever they are, which is fine, since nothing reads past the
|
||||
/// count.
|
||||
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
|
||||
let uniforms = Uniforms {
|
||||
screen_size: [800.0, 1000.0],
|
||||
_pad: [0.0; 2],
|
||||
};
|
||||
|
||||
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("artifact_uniforms"),
|
||||
contents: bytemuck::cast_slice(&[uniforms]),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("artifact_vertex_buffer"),
|
||||
size: MAX_VERTICES * std::mem::size_of::<ArtifactVertex>() as u64,
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("artifact_bind_group_layout"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("artifact_bind_group"),
|
||||
layout: &bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
}],
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("artifact_pipeline_layout"),
|
||||
bind_group_layouts: &[Some(&bind_group_layout)],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("artifact_shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("shaders/artifact.wgsl").into()),
|
||||
});
|
||||
|
||||
let vertex_buffer_layout = wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<ArtifactVertex>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &[
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: 8,
|
||||
shader_location: 1,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("artifact_pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[Some(vertex_buffer_layout)],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
Self {
|
||||
pipeline,
|
||||
vertex_buffer,
|
||||
uniform_buffer,
|
||||
bind_group,
|
||||
vertex_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Take note of the surface's new size by rewriting the uniform block:
|
||||
/// the geometry is in pixels and does not care, but the shader that
|
||||
/// takes pixels to clip space must know how many pixels there are. The
|
||||
/// Observatory calls this once, after construction, since its window
|
||||
/// does not resize, and the scars stay where they were put.
|
||||
pub fn resize(&self, queue: &wgpu::Queue, width: u32, height: u32) {
|
||||
let uniforms = Uniforms {
|
||||
screen_size: [width as f32, height as f32],
|
||||
_pad: [0.0; 2],
|
||||
};
|
||||
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
|
||||
}
|
||||
|
||||
/// Rebuild the vertex buffer from the current list of scars: every
|
||||
/// artifact's geometry, generated afresh and concatenated in list order,
|
||||
/// written to the buffer from the front, and the count recorded for the
|
||||
/// draw. Called every frame, whether or not the list changed, because a
|
||||
/// frame is cheap and remembering whether the list changed is a way to
|
||||
/// be wrong. Past `MAX_VERTICES` the excess is dropped, the buffer being
|
||||
/// what it is; nothing is written at all when there is nothing to
|
||||
/// write, so an Observatory that has never collapsed never touches the
|
||||
/// buffer.
|
||||
pub fn update(&mut self, queue: &wgpu::Queue, artifacts: &[Artifact]) {
|
||||
let mut verts = Vec::new();
|
||||
for a in artifacts {
|
||||
generate_artifact_vertices(a, &mut verts);
|
||||
}
|
||||
let count = (verts.len() as u64).min(MAX_VERTICES);
|
||||
if count > 0 {
|
||||
queue.write_buffer(
|
||||
&self.vertex_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&verts[..count as usize]),
|
||||
);
|
||||
}
|
||||
self.vertex_count = count as u32;
|
||||
}
|
||||
|
||||
/// Draw every scar into the frame's render pass, after everything else,
|
||||
/// so that they lie on top of it: one draw call over the vertices the
|
||||
/// last `update` produced, or no call at all when there are none. A
|
||||
/// scar drawn last is a scar that cannot be covered, which is the
|
||||
/// point; the Observatory's other panels are painted beneath it and
|
||||
/// stay beneath it until the process ends.
|
||||
pub fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
if self.vertex_count == 0 {
|
||||
return;
|
||||
}
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
render_pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
|
||||
render_pass.draw(0..self.vertex_count, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
/// The cell a position falls in, counting from zero, or `None` to the left
|
||||
/// of the first cell: the floor of a non-negative quotient is the cell, and
|
||||
/// a negative quotient is a point outside the row, which no cell claims.
|
||||
/// The `floor` is explicit and the cast merely confirms it; the truncation
|
||||
/// clippy warns of is the selection being made.
|
||||
#[allow(
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_sign_loss,
|
||||
reason = "floored, a non-negative quotient is the cell the point lies in; the cut is the choice"
|
||||
)]
|
||||
fn cell_index(quotient: f32) -> Option<usize> {
|
||||
(quotient >= 0.0).then(|| quotient.floor() as usize)
|
||||
}
|
||||
|
||||
/// Hit-test a click against a scar's blocking shape: `true` if the click
|
||||
/// falls on what would visually be a non-transparent pixel of the artifact,
|
||||
/// approximated by the artifact's structural primitives — the baseline and
|
||||
/// teeth of a gear, the ring and dots and phantom of a failed mask, the
|
||||
/// rings of a soliton — since the pixels themselves are on the GPU and are
|
||||
/// not read back for anyone. The Observatory asks this of every scar before
|
||||
/// it lets a click or a scroll reach the wheels, the bar or the button, and
|
||||
/// a `true` from any of them ends the matter.
|
||||
///
|
||||
/// The hit shapes are deliberately approximate but tight enough to feel
|
||||
/// natural: clicking through a transparent gap reaches the interface
|
||||
/// underneath, clicking on a tooth, a dot or a ring does not.
|
||||
pub fn artifact_hit(artifact: &Artifact, click_x: f32, click_y: f32) -> bool {
|
||||
match *artifact {
|
||||
Artifact::GinGear {
|
||||
x,
|
||||
y,
|
||||
rotation_deg,
|
||||
size,
|
||||
} => gin_gear_hit(x, y, rotation_deg.to_radians(), size, click_x, click_y),
|
||||
Artifact::FailedMask {
|
||||
x,
|
||||
y,
|
||||
entropy_bits,
|
||||
size,
|
||||
} => failed_mask_hit(x, y, entropy_bits, size, click_x, click_y),
|
||||
Artifact::Soliton {
|
||||
x,
|
||||
y,
|
||||
ring_count,
|
||||
radius,
|
||||
} => soliton_hit(x, y, ring_count, radius, click_x, click_y),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a click lands on a GIN gear: the click is carried into the
|
||||
/// glyph's own frame by the inverse of its rotation, and tested there
|
||||
/// against the baseline bar and against each tooth, the same rectangles
|
||||
/// `gen_gin_gear` draws, with the same tenth of a cell of padding taken off
|
||||
/// either side of a tooth. Rotation is the one trigonometric act in the
|
||||
/// hit test, and it is performed on pixels, which forgive it.
|
||||
fn gin_gear_hit(cx: f32, cy: f32, rot: f32, size: f32, px: f32, py: f32) -> bool {
|
||||
// Carry the click into the glyph's local frame by rotating it the other
|
||||
// way about the glyph's centre.
|
||||
let dx = px - cx;
|
||||
let dy = py - cy;
|
||||
let cos_r = rot.cos();
|
||||
let sin_r = rot.sin();
|
||||
let lx = dx * cos_r + dy * sin_r;
|
||||
let ly = -dx * sin_r + dy * cos_r;
|
||||
|
||||
let half_w = size / 2.0;
|
||||
let baseline_h = size * 0.10;
|
||||
let tooth_h = size * 0.30;
|
||||
let cell_w = size / GIN_BITS.len() as f32;
|
||||
|
||||
// The baseline: a bar the full width of the glyph, from y = 0 down to
|
||||
// y = baseline_h, in a frame where y grows downward.
|
||||
if lx >= -half_w && lx <= half_w && ly >= 0.0 && ly <= baseline_h {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The teeth: each set bit of GIN occupies its cell above the baseline,
|
||||
// rising tooth_h; the cell is found by dividing the offset from the
|
||||
// glyph's left edge by the cell width and flooring.
|
||||
if ly >= -tooth_h
|
||||
&& ly < 0.0
|
||||
&& let Some(cell_idx) = cell_index((lx + half_w) / cell_w)
|
||||
&& GIN_BITS.get(cell_idx).copied().unwrap_or(false)
|
||||
{
|
||||
{
|
||||
// Inside this tooth's cell horizontally. The tenth of a cell of
|
||||
// padding on either side that the renderer leaves, so that
|
||||
// adjacent teeth do not merge, is left out of the hit as well,
|
||||
// so that what blocks is exactly what is seen.
|
||||
let cell_left = -half_w + (cell_idx as f32) * cell_w;
|
||||
let pad = cell_w * 0.10;
|
||||
if lx >= cell_left + pad && lx <= cell_left + cell_w - pad {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Whether a click lands on a failed mask: on the boundary ring, on any of
|
||||
/// the seven dots, on the phantom dot beyond the ring, or on the thin line
|
||||
/// that joins the ring to the phantom, each tested as the disc, annulus or
|
||||
/// band that `gen_failed_mask` draws. Every dot blocks whether it is drawn
|
||||
/// filled or hollow, so the entropy bits, which decide the filling, decide
|
||||
/// nothing here.
|
||||
fn failed_mask_hit(cx: f32, cy: f32, entropy_bits: u16, size: f32, px: f32, py: f32) -> bool {
|
||||
let r_outer = size / 2.0;
|
||||
let r_ring_thickness = size * 0.04;
|
||||
let dot_r = size * 0.06;
|
||||
let phantom_offset = size * 0.35;
|
||||
|
||||
let dx = px - cx;
|
||||
let dy = py - cy;
|
||||
let dist_sq = dx * dx + dy * dy;
|
||||
|
||||
// The boundary ring: the annulus between r_outer less the thickness
|
||||
// and r_outer, tested on squared distances, since a square root would
|
||||
// be work and would change no verdict.
|
||||
let inner = r_outer - r_ring_thickness;
|
||||
let outer = r_outer;
|
||||
if dist_sq >= inner * inner && dist_sq <= outer * outer {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The seven prime-dots around the ring, at the same seven angles the
|
||||
// renderer uses, starting from the top and going round by sevenths.
|
||||
let dot_r_outer = r_outer - r_ring_thickness * 2.0;
|
||||
for bit in 0..7u8 {
|
||||
let angle = -std::f32::consts::FRAC_PI_2 + f32::from(bit) * (std::f32::consts::TAU / 7.0);
|
||||
let bx = cx + dot_r_outer * angle.cos();
|
||||
let by = cy + dot_r_outer * angle.sin();
|
||||
let bdx = px - bx;
|
||||
let bdy = py - by;
|
||||
// A dot is filled if its prime is active and hollow if not, and
|
||||
// both are hit as the whole disc; a hollow ring with a hole one
|
||||
// could click through would be a scar with a loophole.
|
||||
if bdx * bdx + bdy * bdy <= dot_r * dot_r {
|
||||
return true;
|
||||
}
|
||||
// The entropy bits are acknowledged and not consulted: the shapes
|
||||
// are identical for hit purposes, and the bits decide only fill or
|
||||
// stroke, which is the renderer's business. Naming them here is how
|
||||
// the compiler is told they were not forgotten.
|
||||
let _ = entropy_bits;
|
||||
}
|
||||
|
||||
// The phantom dot outside the boundary, at the eighth slot's angle,
|
||||
// pushed out beyond the ring by the phantom offset.
|
||||
let phantom_angle = -std::f32::consts::FRAC_PI_2 + 7.0 * (std::f32::consts::TAU / 7.0);
|
||||
let pdx = cx + (r_outer + phantom_offset) * phantom_angle.cos();
|
||||
let pdy = cy + (r_outer + phantom_offset) * phantom_angle.sin();
|
||||
let phx = px - pdx;
|
||||
let phy = py - pdy;
|
||||
if phx * phx + phy * phy <= dot_r * dot_r {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The thin line from the boundary to the phantom dot, approximated for
|
||||
// hit purposes as a band three times the drawn line's width, so that a
|
||||
// line a pixel or so wide can be hit by a person with a pointer.
|
||||
let line_thickness = size * 0.012;
|
||||
let lx0 = cx + r_outer * phantom_angle.cos();
|
||||
let ly0 = cy + r_outer * phantom_angle.sin();
|
||||
if point_near_segment(px, py, lx0, ly0, pdx, pdy, line_thickness * 1.5) {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Whether a click lands on a soliton: on any of its concentric rings, each
|
||||
/// tested as the annulus `gen_soliton` draws, of the same radius and the
|
||||
/// same thickness — eighteen percent of the ring pitch — on squared
|
||||
/// distances. The gaps between the rings let the click through, as the
|
||||
/// gaps in a ripple would let a stone through, if stones were clicks.
|
||||
fn soliton_hit(cx: f32, cy: f32, ring_count: u8, outer_radius: f32, px: f32, py: f32) -> bool {
|
||||
let count = ring_count.max(1) as usize;
|
||||
let thickness = (outer_radius / count as f32) * 0.18;
|
||||
let dx = px - cx;
|
||||
let dy = py - cy;
|
||||
let dist_sq = dx * dx + dy * dy;
|
||||
for i in 0..count {
|
||||
let r = outer_radius * (i + 1) as f32 / count as f32;
|
||||
let inner = r - thickness;
|
||||
let outer = r;
|
||||
if dist_sq >= inner * inner && dist_sq <= outer * outer {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Whether a point lies within `threshold` of the segment from `(x0, y0)`
|
||||
/// to `(x1, y1)`: the point is projected onto the segment's line, the
|
||||
/// projection clamped to the segment's ends, and the squared distance to
|
||||
/// the clamped point compared with the squared threshold. A segment shorter
|
||||
/// than a thousandth of a pixel is treated as no segment at all, since a
|
||||
/// division by its length would be a division by nothing wearing a decimal
|
||||
/// point. Used for the phantom line of the failed mask, and for nothing
|
||||
/// else.
|
||||
fn point_near_segment(
|
||||
px: f32,
|
||||
py: f32,
|
||||
x0: f32,
|
||||
y0: f32,
|
||||
x1: f32,
|
||||
y1: f32,
|
||||
threshold: f32,
|
||||
) -> bool {
|
||||
let dx = x1 - x0;
|
||||
let dy = y1 - y0;
|
||||
let len_sq = dx * dx + dy * dy;
|
||||
if len_sq < 1e-6 {
|
||||
return false;
|
||||
}
|
||||
let t = ((px - x0) * dx + (py - y0) * dy) / len_sq;
|
||||
let t = t.clamp(0.0, 1.0);
|
||||
let cx = x0 + t * dx;
|
||||
let cy = y0 + t * dy;
|
||||
let d2 = (px - cx).powi(2) + (py - cy).powi(2);
|
||||
d2 <= threshold * threshold
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Geometry generation: the three scars, as triangles, in pixels
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// The colour of the live calendar: white with a cast of cream, at
|
||||
/// eighty-five percent — the colour of the cosmic span, and of a gear's
|
||||
/// bar and teeth, and of a dot whose prime is active.
|
||||
const COL_WHITE: [f32; 4] = [0.92, 0.92, 0.95, 0.85];
|
||||
/// Amber, the project's primary accent, the colour of the headers and the
|
||||
/// carets; here it is the colour of a soliton's rings, before their alpha
|
||||
/// is tuned ring by ring.
|
||||
const COL_AMBER: [f32; 4] = [0.95, 0.65, 0.20, 0.85];
|
||||
/// Dim gray, for what is inactive or decayed: a failed mask's boundary
|
||||
/// ring, and a dot whose prime the entropy did not choose.
|
||||
const COL_DIM: [f32; 4] = [0.45, 0.45, 0.50, 0.65];
|
||||
/// The phantom's colour: red-tinted and dim, denoting the bit that
|
||||
/// overflowed — the eighth dot of a seven-dot lattice, and the line that
|
||||
/// points to where it would have gone.
|
||||
const COL_PHANTOM: [f32; 4] = [0.85, 0.25, 0.25, 0.70];
|
||||
|
||||
/// The GIN bit pattern from the project's logo seam: G = 1000111,
|
||||
/// I = 1001001, N = 1001110 — twenty-one bits, which are the seven-bit
|
||||
/// ASCII codes of the three letters, 71, 73 and 78, most significant bit
|
||||
/// first. Each 1 is a tooth of the gear and each 0 a gap; eleven teeth in
|
||||
/// all, and the letters are legible in them if you know to look.
|
||||
const GIN_BITS: [bool; 21] = [
|
||||
true, false, false, false, true, true, true, // G
|
||||
true, false, false, true, false, false, true, // I
|
||||
true, false, false, true, true, true, false, // N
|
||||
];
|
||||
|
||||
/// Append one scar's geometry to `out`, dispatching on its variant: a
|
||||
/// gear's rotation is given in degrees by the artifact and in radians to
|
||||
/// the generator, and everything else is passed through as it came.
|
||||
fn generate_artifact_vertices(artifact: &Artifact, out: &mut Vec<ArtifactVertex>) {
|
||||
match *artifact {
|
||||
Artifact::GinGear {
|
||||
x,
|
||||
y,
|
||||
rotation_deg,
|
||||
size,
|
||||
} => {
|
||||
gen_gin_gear(x, y, rotation_deg.to_radians(), size, out);
|
||||
}
|
||||
Artifact::FailedMask {
|
||||
x,
|
||||
y,
|
||||
entropy_bits,
|
||||
size,
|
||||
} => {
|
||||
gen_failed_mask(x, y, entropy_bits, size, out);
|
||||
}
|
||||
Artifact::Soliton {
|
||||
x,
|
||||
y,
|
||||
ring_count,
|
||||
radius,
|
||||
} => {
|
||||
gen_soliton(x, y, ring_count, radius, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Render the GIN gear glyph: twenty-one cells in a row, the 1 cells raised
|
||||
/// as teeth above a baseline and the 0 cells flush with it, the whole glyph
|
||||
/// rotated about `(cx, cy)` by `rot` radians. The baseline is a tenth of
|
||||
/// the glyph's size tall and the teeth three tenths; each tooth gives up a
|
||||
/// tenth of its cell on either side so that neighbours do not merge into
|
||||
/// one long tooth, which would be a different letter.
|
||||
fn gen_gin_gear(cx: f32, cy: f32, rot: f32, size: f32, out: &mut Vec<ArtifactVertex>) {
|
||||
let cell_w = size / GIN_BITS.len() as f32;
|
||||
let baseline_h = size * 0.10; // the baseline: a thin bar, a tenth of the glyph
|
||||
let tooth_h = size * 0.30; // the teeth rise three tenths above the baseline
|
||||
|
||||
let total_w = size;
|
||||
let cos_r = rot.cos();
|
||||
let sin_r = rot.sin();
|
||||
|
||||
// Emit one quad given in the glyph's local frame — origin at the glyph's
|
||||
// centre, x along the baseline, y downward — rotated about (cx, cy) and
|
||||
// appended as two triangles.
|
||||
let mut emit_quad = |lx: f32, ly: f32, w: f32, h: f32, color: [f32; 4]| {
|
||||
// The four corners, in the local frame; the glyph is centred
|
||||
// horizontally on cx.
|
||||
let local_corners = [[lx, ly], [lx + w, ly], [lx + w, ly + h], [lx, ly + h]];
|
||||
let world: Vec<[f32; 2]> = local_corners
|
||||
.iter()
|
||||
.map(|[x, y]| [cx + x * cos_r - y * sin_r, cy + x * sin_r + y * cos_r])
|
||||
.collect();
|
||||
// Two triangles, (0, 1, 2) and (0, 2, 3): the quad, cut along one
|
||||
// diagonal.
|
||||
for &i in &[0usize, 1, 2, 0, 2, 3] {
|
||||
out.push(ArtifactVertex {
|
||||
position: world[i],
|
||||
color,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// The baseline: a thin horizontal bar across the full glyph width.
|
||||
let half_w = total_w / 2.0;
|
||||
emit_quad(-half_w, 0.0, total_w, baseline_h, COL_WHITE);
|
||||
|
||||
// The teeth: one rectangle per 1 bit, rising above the baseline.
|
||||
for (i, &bit) in GIN_BITS.iter().enumerate() {
|
||||
if bit {
|
||||
let lx = -half_w + i as f32 * cell_w;
|
||||
// A little horizontal padding, a tenth of the cell on each
|
||||
// side, so that adjacent teeth do not merge.
|
||||
let pad = cell_w * 0.10;
|
||||
emit_quad(lx + pad, -tooth_h, cell_w - 2.0 * pad, tooth_h, COL_WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Render the failed-mask glyph: a circle for the cosmic boundary, seven
|
||||
/// dots arranged around it — one per cosmic prime, filled if the entropy
|
||||
/// chose that prime and hollow if not — and a phantom eighth dot dangling
|
||||
/// outside the circle in red, joined to the boundary by a thin line, at the
|
||||
/// angle the eighth slot would have had if the lattice had one.
|
||||
///
|
||||
/// `entropy_bits` is the value the library carries in
|
||||
/// `LoicError::InvalidMask` — the upper bit, `0x8000`, flags a cosmic-pool
|
||||
/// selection, and the lower eight bits are the index that overflowed. The
|
||||
/// active primes are the lower seven bits of that index; its eighth bit,
|
||||
/// bit 7, is the one that made the index 128 or more, and it is always set
|
||||
/// here, because an index below 128 would have been a mask and not a scar.
|
||||
/// The glyph is therefore a picture of the exact malformed question, with
|
||||
/// the bit that malformed it drawn where the lattice has no room for it.
|
||||
fn gen_failed_mask(cx: f32, cy: f32, entropy_bits: u16, size: f32, out: &mut Vec<ArtifactVertex>) {
|
||||
let r_outer = size / 2.0;
|
||||
let r_ring_thickness = size * 0.04;
|
||||
let dot_r = size * 0.06;
|
||||
let phantom_offset = size * 0.35; // how far beyond the boundary the phantom hangs
|
||||
|
||||
// The boundary ring: the cosmic limit, dim, forty-eight segments.
|
||||
gen_ring(
|
||||
cx,
|
||||
cy,
|
||||
r_outer - r_ring_thickness,
|
||||
r_outer,
|
||||
48,
|
||||
COL_DIM,
|
||||
out,
|
||||
);
|
||||
|
||||
// The seven dots around the ring at evenly spaced angles, starting from
|
||||
// the top and going round by sevenths: filled where the prime is
|
||||
// active, a hollow ring where it is not.
|
||||
let index = (entropy_bits & 0xFF) as u8;
|
||||
for bit in 0..7u8 {
|
||||
let angle = -std::f32::consts::FRAC_PI_2 + f32::from(bit) * (std::f32::consts::TAU / 7.0);
|
||||
let dx = cx + (r_outer - r_ring_thickness * 2.0) * angle.cos();
|
||||
let dy = cy + (r_outer - r_ring_thickness * 2.0) * angle.sin();
|
||||
let active = (index >> bit) & 1 == 1;
|
||||
let color = if active { COL_WHITE } else { COL_DIM };
|
||||
if active {
|
||||
gen_filled_circle(dx, dy, dot_r, 16, color, out);
|
||||
} else {
|
||||
gen_ring(dx, dy, dot_r * 0.6, dot_r, 16, color, out);
|
||||
}
|
||||
}
|
||||
|
||||
// The phantom eighth bit, dangling outside the circle in the direction
|
||||
// of the void: at the angle of the eighth slot, continuing the
|
||||
// seven-prime pattern forward by one, and pushed out past the boundary
|
||||
// by the phantom offset, since the lattice has no eighth place to put
|
||||
// it.
|
||||
let phantom_angle = -std::f32::consts::FRAC_PI_2 + 7.0 * (std::f32::consts::TAU / 7.0);
|
||||
let pdx = cx + (r_outer + phantom_offset) * phantom_angle.cos();
|
||||
let pdy = cy + (r_outer + phantom_offset) * phantom_angle.sin();
|
||||
gen_filled_circle(pdx, pdy, dot_r, 16, COL_PHANTOM, out);
|
||||
|
||||
// A thin line from the boundary toward the phantom dot, dim, saying
|
||||
// "this is where bit 7 would have gone", which is the whole of what a
|
||||
// scar has to say.
|
||||
gen_thin_line(
|
||||
cx + r_outer * phantom_angle.cos(),
|
||||
cy + r_outer * phantom_angle.sin(),
|
||||
pdx,
|
||||
pdy,
|
||||
size * 0.012,
|
||||
COL_PHANTOM,
|
||||
out,
|
||||
);
|
||||
}
|
||||
|
||||
/// Render concentric rings emanating from a point, `ring_count` of them —
|
||||
/// at least one, whatever the count says — out to `outer_radius`, evenly
|
||||
/// spaced, each eighteen percent of the pitch thick, sixty-four segments
|
||||
/// apiece. The innermost ring is the most opaque and the rings fade
|
||||
/// outward, as a ripple does, except that this one is static and will not
|
||||
/// fade further: the workspace README, section 4.5, says the holes left in
|
||||
/// the weave stay in the cloth, and this is the Observatory's picture of
|
||||
/// one.
|
||||
fn gen_soliton(cx: f32, cy: f32, ring_count: u8, outer_radius: f32, out: &mut Vec<ArtifactVertex>) {
|
||||
let count = ring_count.max(1) as usize;
|
||||
let thickness = (outer_radius / count as f32) * 0.18;
|
||||
for i in 0..count {
|
||||
let r = outer_radius * (i + 1) as f32 / count as f32;
|
||||
// Inner rings, low i, most opaque; outer rings faintest: the alpha
|
||||
// runs from 0.85 down toward 0.30 across the count.
|
||||
let alpha = 0.85 - (i as f32 / count as f32) * 0.55;
|
||||
let mut color = COL_AMBER;
|
||||
color[3] = alpha;
|
||||
gen_ring(cx, cy, r - thickness, r, 64, color, out);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── The primitives: a disc, an annulus, a band ─────────────────────────────
|
||||
|
||||
/// A filled circle as a triangle fan of `segments` triangles, emitted as a
|
||||
/// triangle list — centre, one rim point, the next rim point — so that it
|
||||
/// can share a buffer with everything else. Three vertices a segment; the
|
||||
/// dots use sixteen, which is round enough at their size.
|
||||
fn gen_filled_circle(
|
||||
cx: f32,
|
||||
cy: f32,
|
||||
radius: f32,
|
||||
segments: u32,
|
||||
color: [f32; 4],
|
||||
out: &mut Vec<ArtifactVertex>,
|
||||
) {
|
||||
let center = ArtifactVertex {
|
||||
position: [cx, cy],
|
||||
color,
|
||||
};
|
||||
for i in 0..segments {
|
||||
let a0 = (i as f32) * std::f32::consts::TAU / segments as f32;
|
||||
let a1 = ((i + 1) as f32) * std::f32::consts::TAU / segments as f32;
|
||||
let p0 = [cx + radius * a0.cos(), cy + radius * a0.sin()];
|
||||
let p1 = [cx + radius * a1.cos(), cy + radius * a1.sin()];
|
||||
out.push(center);
|
||||
out.push(ArtifactVertex {
|
||||
position: p0,
|
||||
color,
|
||||
});
|
||||
out.push(ArtifactVertex {
|
||||
position: p1,
|
||||
color,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// An annulus, a filled ring between `inner_radius` and `outer_radius`, as
|
||||
/// `segments` quads around the circle, each cut into two triangles: six
|
||||
/// vertices a segment, the price of not indexing. Forty-eight segments go
|
||||
/// round the boundary of a failed mask, sixty-four round each ring of a
|
||||
/// soliton, sixteen round a hollow dot.
|
||||
fn gen_ring(
|
||||
cx: f32,
|
||||
cy: f32,
|
||||
inner_radius: f32,
|
||||
outer_radius: f32,
|
||||
segments: u32,
|
||||
color: [f32; 4],
|
||||
out: &mut Vec<ArtifactVertex>,
|
||||
) {
|
||||
for i in 0..segments {
|
||||
let a0 = (i as f32) * std::f32::consts::TAU / segments as f32;
|
||||
let a1 = ((i + 1) as f32) * std::f32::consts::TAU / segments as f32;
|
||||
let i0 = [cx + inner_radius * a0.cos(), cy + inner_radius * a0.sin()];
|
||||
let i1 = [cx + inner_radius * a1.cos(), cy + inner_radius * a1.sin()];
|
||||
let o0 = [cx + outer_radius * a0.cos(), cy + outer_radius * a0.sin()];
|
||||
let o1 = [cx + outer_radius * a1.cos(), cy + outer_radius * a1.sin()];
|
||||
// Two triangles per segment, (i0, o0, o1) and (i0, o1, i1): the
|
||||
// quad between two rim angles, cut along its diagonal.
|
||||
out.push(ArtifactVertex {
|
||||
position: i0,
|
||||
color,
|
||||
});
|
||||
out.push(ArtifactVertex {
|
||||
position: o0,
|
||||
color,
|
||||
});
|
||||
out.push(ArtifactVertex {
|
||||
position: o1,
|
||||
color,
|
||||
});
|
||||
out.push(ArtifactVertex {
|
||||
position: i0,
|
||||
color,
|
||||
});
|
||||
out.push(ArtifactVertex {
|
||||
position: o1,
|
||||
color,
|
||||
});
|
||||
out.push(ArtifactVertex {
|
||||
position: i1,
|
||||
color,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// A thin line from `(x0, y0)` to `(x1, y1)` of the given `thickness`,
|
||||
/// rendered as one quad — two triangles — offset half the thickness to
|
||||
/// either side of the segment along its perpendicular. A segment of no
|
||||
/// length is given a millionth of a pixel to divide by, and draws a quad
|
||||
/// of no width, which is nothing, correctly.
|
||||
fn gen_thin_line(
|
||||
x0: f32,
|
||||
y0: f32,
|
||||
x1: f32,
|
||||
y1: f32,
|
||||
thickness: f32,
|
||||
color: [f32; 4],
|
||||
out: &mut Vec<ArtifactVertex>,
|
||||
) {
|
||||
let dx = x1 - x0;
|
||||
let dy = y1 - y0;
|
||||
let len = (dx * dx + dy * dy).sqrt().max(1e-6);
|
||||
// The perpendicular, as a unit vector scaled to half the thickness.
|
||||
let px = -dy / len * thickness * 0.5;
|
||||
let py = dx / len * thickness * 0.5;
|
||||
let p00 = [x0 + px, y0 + py];
|
||||
let p01 = [x0 - px, y0 - py];
|
||||
let p10 = [x1 + px, y1 + py];
|
||||
let p11 = [x1 - px, y1 - py];
|
||||
// Two triangles, (p00, p01, p11) and (p00, p11, p10): the band, cut
|
||||
// along its diagonal.
|
||||
out.push(ArtifactVertex {
|
||||
position: p00,
|
||||
color,
|
||||
});
|
||||
out.push(ArtifactVertex {
|
||||
position: p01,
|
||||
color,
|
||||
});
|
||||
out.push(ArtifactVertex {
|
||||
position: p11,
|
||||
color,
|
||||
});
|
||||
out.push(ArtifactVertex {
|
||||
position: p00,
|
||||
color,
|
||||
});
|
||||
out.push(ArtifactVertex {
|
||||
position: p11,
|
||||
color,
|
||||
});
|
||||
out.push(ArtifactVertex {
|
||||
position: p10,
|
||||
color,
|
||||
});
|
||||
}
|
||||
257
crates/loic-gui/src/audio.rs
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
//! Audio synthesis for the Loic Observatory: the quantum hum.
|
||||
//!
|
||||
//! The Observatory sonifies a single thing, the quantum hum — a continuous
|
||||
//! background drone derived from the kernel's entropy pool, two fresh bytes
|
||||
//! of it drawn through `getrandom` for every one of the forty-four thousand
|
||||
//! one hundred samples of every second, low-pass filtered so that the
|
||||
//! hiss becomes a rumble, and modulated in amplitude by a random walk so
|
||||
//! slow that its swells arrive like weather. The hum never repeats. It
|
||||
//! cannot. The kernel's pool furnishes it eighty-eight thousand two hundred
|
||||
//! bytes a second that were never furnished before, and a drone made of
|
||||
//! them has no period to return to.
|
||||
//!
|
||||
//! There are no transition sounds, because there are no transitions: the
|
||||
//! lattice has no seconds, minutes, hours, days, months or years to mark,
|
||||
//! and a tone sounded at a boundary that does not exist would be a lie in
|
||||
//! the audible range, which is the one range the calendar had not yet
|
||||
//! found a way to lie in. The hum does not depend on the calendar and
|
||||
//! reads no instant. It is what a calendar sounds like from underneath.
|
||||
//!
|
||||
//! # On the Matter of Floating-Point
|
||||
//!
|
||||
//! The perceptive reader will observe that this module commits, freely and
|
||||
//! on every line, the cardinal sin of the workspace README, section 1.1:
|
||||
//! floating-point arithmetic. This is not apostasy but the one exemption
|
||||
//! the calendar tolerates. Audio synthesis requires continuous waveforms,
|
||||
//! and the human ear — that imprecise biological instrument, a Channel 1
|
||||
//! device if ever there was one — cannot distinguish between a sample of
|
||||
//! 0.4376291 and one of 0.4376290, and could not be made to care.
|
||||
//!
|
||||
//! These floating-point values never touch a temporal quantity. No
|
||||
//! instant, no duration, no Planck count enters this module or leaves it;
|
||||
//! the samples are confined to the analogue domain, where precision is
|
||||
//! measured in decibels rather than Planck intervals and where, in any
|
||||
//! case, the loudspeaker will do worse to the waveform than the mantissa
|
||||
//! ever did. The waveform cares not for the calendar's purity, and the
|
||||
//! calendar extends to it this singular dispensation, in writing, here.
|
||||
|
||||
use rodio::{ChannelCount, DeviceSinkBuilder, MixerDeviceSink, Player, SampleRate, Source};
|
||||
use std::num::NonZero;
|
||||
use std::time::Duration;
|
||||
|
||||
/// The sample rate of the generated audio: 44,100 samples per second, the
|
||||
/// rate of the compact disc and of most digital audio since, chosen here
|
||||
/// because every audio device on Earth will accept it without complaint
|
||||
/// and because one hundredth of it, 441, is a whole number of samples at
|
||||
/// which to update the amplitude.
|
||||
const SAMPLE_RATE: u32 = 44100;
|
||||
|
||||
/// The Observatory's hold on the audio device: the sink the device was
|
||||
/// opened as, the player the hum sounds through while it sounds, whether
|
||||
/// audio is enabled at all, and how loud the hum is. To hold an
|
||||
/// `AudioState` is to keep the hum sounding; to drop it is silence, which
|
||||
/// is why the Observatory keeps one it never reads.
|
||||
pub struct AudioState {
|
||||
/// The device sink, opened once; its mixer is what the hum's player is
|
||||
/// connected to.
|
||||
stream: MixerDeviceSink,
|
||||
/// The hum's player, present while the hum sounds and `None` while it
|
||||
/// does not; the hum is continuous, so this is `Some` for the life of
|
||||
/// the Observatory.
|
||||
quantum_player: Option<Player>,
|
||||
/// Whether audio is enabled at all. Disabling it stops the hum and
|
||||
/// declines to start it again.
|
||||
enabled: bool,
|
||||
/// The hum's volume, between zero and one: the factor the noise is
|
||||
/// amplified by on its way to the mixer.
|
||||
hum_volume: f32,
|
||||
}
|
||||
|
||||
impl AudioState {
|
||||
/// Opens the default audio device and furnishes the state that holds
|
||||
/// it, with audio enabled, no hum yet sounding, and the hum's volume
|
||||
/// set quiet but audible.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `Some` with the device open, or `None` if no default device could be
|
||||
/// opened — a machine without audio, or one whose audio is spoken for —
|
||||
/// in which case the Observatory proceeds in silence. The hum is an
|
||||
/// accompaniment; the moment does not need it to be observed.
|
||||
pub fn new() -> Option<Self> {
|
||||
let stream = DeviceSinkBuilder::open_default_sink().ok()?;
|
||||
|
||||
Some(Self {
|
||||
stream,
|
||||
quantum_player: None,
|
||||
enabled: true,
|
||||
hum_volume: 0.06, // quiet, but present, like the thing it sonifies
|
||||
})
|
||||
}
|
||||
|
||||
/// Enables or disables all audio. Disabling stops the hum at once and
|
||||
/// causes any later request to start it to be declined; enabling merely
|
||||
/// permits, and starts nothing by itself. Offered for an observer who
|
||||
/// would prefer the moment unsounded; no caller has yet been one.
|
||||
#[allow(dead_code)]
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
if !enabled {
|
||||
self.stop_quantum_hum();
|
||||
}
|
||||
}
|
||||
|
||||
/// Begins the hum: a player is connected to the device's mixer and an
|
||||
/// endless source of quantum noise, amplified to the hum's volume, is
|
||||
/// appended to it, whereupon the device draws samples from the source
|
||||
/// for as long as the player lives. Declined, silently, if audio is
|
||||
/// disabled or if the hum is already sounding, since two hums would be
|
||||
/// a chord and the lattice has no chords.
|
||||
pub fn start_quantum_hum(&mut self) {
|
||||
if !self.enabled || self.quantum_player.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let player = Player::connect_new(self.stream.mixer());
|
||||
// The source has no end; the amplification is the volume, applied
|
||||
// once to every sample on its way out.
|
||||
let noise = QuantumNoise::new().amplify(self.hum_volume);
|
||||
player.append(noise);
|
||||
self.quantum_player = Some(player);
|
||||
}
|
||||
|
||||
/// Ends the hum: the player is taken from its slot and stopped, and the
|
||||
/// source it was drawing from is dropped with it. A hum that is not
|
||||
/// sounding is left not sounding.
|
||||
#[allow(dead_code)]
|
||||
pub fn stop_quantum_hum(&mut self) {
|
||||
if let Some(player) = self.quantum_player.take() {
|
||||
player.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The quantum noise source: the kernel's entropy pool, rendered audible.
|
||||
/// Two bytes are drawn per sample, one for the noise and one for the slow
|
||||
/// walk of its amplitude; the noise is low-pass filtered into a rumble that
|
||||
/// crackles, and the amplitude wanders between three tenths and full over
|
||||
/// timescales of seconds, so that the drone swells and subsides and never
|
||||
/// quite settles into anything the ear could call a note. The provenance
|
||||
/// is exactly the kernel's own, per the workspace README, section 5.1: not
|
||||
/// provably quantum, not disprovably.
|
||||
struct QuantumNoise {
|
||||
/// The one-pole low-pass filter's state: the previous output sample,
|
||||
/// which the next output is three quarters made of.
|
||||
filter_state: f32,
|
||||
/// The filter's coefficient, between zero and one, higher being more
|
||||
/// smoothing: the fraction of each output that is the previous output
|
||||
/// rather than fresh noise.
|
||||
filter_coeff: f32,
|
||||
/// The current amplitude envelope, between three tenths and one, driven
|
||||
/// by the random walk below.
|
||||
amplitude: f32,
|
||||
/// The random walk's state: a very slowly filtered noise value between
|
||||
/// minus one and one, from which the amplitude is mapped.
|
||||
amp_filter_state: f32,
|
||||
/// The count of samples produced, wrapping, from which the amplitude's
|
||||
/// hundred-hertz update cadence is derived.
|
||||
sample_count: u32,
|
||||
}
|
||||
|
||||
impl QuantumNoise {
|
||||
/// A fresh source: filter at rest, amplitude at half, walk at zero, no
|
||||
/// samples yet produced.
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
filter_state: 0.0,
|
||||
filter_coeff: 0.75, // less smoothing, so the entropy crackles through
|
||||
amplitude: 0.5,
|
||||
amp_filter_state: 0.0,
|
||||
sample_count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for QuantumNoise {
|
||||
type Item = f32;
|
||||
|
||||
/// One sample of the hum, forty-four thousand one hundred times a
|
||||
/// second for as long as the device keeps asking. The source never
|
||||
/// ends: a sample is always furnished, and if the kernel's pool cannot
|
||||
/// be read the sample is silence rather than the end of the stream,
|
||||
/// since a hum that stopped would be marking a transition and there are
|
||||
/// none to mark.
|
||||
fn next(&mut self) -> Option<f32> {
|
||||
self.sample_count = self.sample_count.wrapping_add(1);
|
||||
|
||||
// Two fresh bytes from the kernel's pool, per sample. A pool that
|
||||
// cannot be read yields silence for this sample and is asked again
|
||||
// for the next.
|
||||
let mut entropy = [0u8; 2];
|
||||
if loic::getrandom::fill(&mut entropy).is_err() {
|
||||
return Some(0.0);
|
||||
}
|
||||
|
||||
// The first byte is the noise itself, mapped from its 256 values to
|
||||
// the range from minus one up to just short of one — not quite
|
||||
// symmetric about zero, which no ear can hear and the calendar
|
||||
// declines to notice in a domain it has already ceded.
|
||||
let raw = (f32::from(entropy[0]) / 128.0) - 1.0;
|
||||
|
||||
// A one-pole low-pass filter: three quarters of the last output and
|
||||
// a quarter of the new noise, which turns the hiss of raw entropy
|
||||
// into a rumble and leaves enough of the hiss to crackle.
|
||||
self.filter_state = self.filter_coeff * self.filter_state + (1.0 - self.filter_coeff) * raw;
|
||||
|
||||
// The second byte drives the amplitude, and only every 441st sample
|
||||
// is consulted: a hundred times a second at this sample rate. The
|
||||
// walk is glacial — each consultation moves the state half a
|
||||
// percent of the way toward the new noise — and the amplitude is
|
||||
// mapped from it to the range three tenths to one, so that the hum
|
||||
// swells and subsides over seconds without ever falling silent.
|
||||
// Once every twenty-seven hours the sample counter wraps and the
|
||||
// cadence stumbles by a fraction of a period, which no one has ever
|
||||
// heard and no one will.
|
||||
if self.sample_count.is_multiple_of(441) {
|
||||
let amp_noise = (f32::from(entropy[1]) / 128.0) - 1.0;
|
||||
self.amp_filter_state = 0.995 * self.amp_filter_state + 0.005 * amp_noise;
|
||||
self.amplitude = 0.65 + self.amp_filter_state * 0.35;
|
||||
}
|
||||
|
||||
Some(self.filter_state * self.amplitude)
|
||||
}
|
||||
}
|
||||
|
||||
/// The hum described to the audio library: one channel, the sample rate
|
||||
/// above, no known length of span and no total duration, which is the
|
||||
/// library's vocabulary for a sound with no end.
|
||||
impl Source for QuantumNoise {
|
||||
/// The length of the current span of uniform channel count and sample
|
||||
/// rate, which is unknown because the span is the whole of the stream
|
||||
/// and the stream does not end.
|
||||
fn current_span_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
/// One channel. The hum is mono; there is one entropy pool, and it has
|
||||
/// no left and right.
|
||||
fn channels(&self) -> ChannelCount {
|
||||
// SAFETY: the literal 1 is not zero, and the unchecked constructor
|
||||
// asks nothing else of it.
|
||||
unsafe { NonZero::new_unchecked(1) }
|
||||
}
|
||||
|
||||
/// The sample rate, `SAMPLE_RATE`, in the non-zero wrapper the library
|
||||
/// requires.
|
||||
fn sample_rate(&self) -> SampleRate {
|
||||
// SAFETY: `SAMPLE_RATE` is 44,100, which is not zero, and the
|
||||
// unchecked constructor asks nothing else of it.
|
||||
unsafe { NonZero::new_unchecked(SAMPLE_RATE) }
|
||||
}
|
||||
|
||||
/// The total duration of the hum, which has none: `None`, the
|
||||
/// library's word for a sound that outlasts the question.
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
None // there is no end to a hum that never began to count
|
||||
}
|
||||
}
|
||||
758
crates/loic-gui/src/events.rs
Normal file
|
|
@ -0,0 +1,758 @@
|
|||
//! # Cosmic Event Passages
|
||||
//!
|
||||
//! Herein dwell the textual accompaniments to the cosmic milestones displayed
|
||||
//! upon the Observatory's timeline: sixty of them, one per caret, each a
|
||||
//! meditation upon the significance of its event, rendered in prose
|
||||
//! befitting the gravity (or, in the case of the early universe, the absence
|
||||
//! thereof) of the moment in question. A click on a caret opens the passage
|
||||
//! in an overlay, word-wrapped to the window; a click anywhere, or Escape,
|
||||
//! closes it again, and the universe resumes where it was.
|
||||
//!
|
||||
//! These passages are technically accurate. They are also linguistically
|
||||
//! unhinged. This is by design. Where the science is settled the passage
|
||||
//! says so; where it is argued, the passage says that instead, because a
|
||||
//! timeline that presents a conjecture as a date is lying with a straight
|
||||
//! face, and the workspace README, section 0, forbids exactly that. Where
|
||||
//! the calendar itself takes a position — on the last observation, on the
|
||||
//! sanctity of the Julian year — the passage says whose position it is. The
|
||||
//! titles and the texts are content, fact-checked, and not to be improved by
|
||||
//! anyone who has come to improve the comments.
|
||||
|
||||
/// The name by which a marker on the timeline and the passage behind it
|
||||
/// know each other: one variant per event, sixty of them, in the order the
|
||||
/// universe produced them. The timeline's table carries one of these per
|
||||
/// marker and `get_passage` answers to it, so the two cannot drift apart
|
||||
/// without the compiler noticing — a match with a variant missing does not
|
||||
/// build, which is the calendar's preferred way of being told. It derives
|
||||
/// everything a key should: copied by value, compared by value, hashed by
|
||||
/// value, and printed by `Debug` as its own name, which is the only name it
|
||||
/// has.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum EventId {
|
||||
// The Planck era, one Planck time long: the unit the calendar counts in.
|
||||
PlanckEpochEnds,
|
||||
|
||||
// The Grand Unification era: three forces as one, gravity already apart.
|
||||
GrandUnificationEpoch,
|
||||
|
||||
// The electroweak era: inflation's end, and the Higgs field coming on.
|
||||
InflationEnds,
|
||||
ElectroweakEpoch,
|
||||
|
||||
// The quark era: the plasma, before confinement had anything to confine.
|
||||
QuarkEpoch,
|
||||
|
||||
// The hadron era: the quarks bound, the antimatter gone, the neutrinos
|
||||
// loose.
|
||||
HadronEpoch,
|
||||
NeutrinoDecoupling,
|
||||
|
||||
// Nucleosynthesis: the universe as a fusion reactor, for a quarter of an
|
||||
// hour.
|
||||
NucleosynthesisBegins,
|
||||
NucleosynthesisEnds,
|
||||
|
||||
// The photon era: cooling past the Sun's core, equality, and the first
|
||||
// light let go.
|
||||
CoolerThanTheSunsCore,
|
||||
MatterRadiationEquality,
|
||||
Recombination,
|
||||
|
||||
// The dark ages: neutral hydrogen, and nothing shining.
|
||||
DarkAgesBegin,
|
||||
|
||||
// Cosmic dawn: the first stars, and the first things worth calling
|
||||
// galaxies.
|
||||
FirstStarsIgnite,
|
||||
FirstGalaxiesForm,
|
||||
|
||||
// The stelliferous era: the age of stars, of the Sun, and of everything
|
||||
// that has so far lived on the third rock — the calendar's own era, for
|
||||
// now.
|
||||
ReionizationComplete,
|
||||
CosmicNoon,
|
||||
ExpansionAccelerates,
|
||||
SolarSystemForms,
|
||||
EarthForms,
|
||||
MoonForms,
|
||||
FirstLife,
|
||||
GreatOxidation,
|
||||
FirstEukaryotes,
|
||||
SnowballEarth,
|
||||
CambrianExplosion,
|
||||
PermianExtinction,
|
||||
DinosaurExtinction,
|
||||
|
||||
// Human history: from Jebel Irhoud to the year 2038, the last two percent
|
||||
// of the species' time and the whole of its record, this calendar's
|
||||
// epoch included.
|
||||
HomoSapiens,
|
||||
FirstFarmers,
|
||||
FirstWriting,
|
||||
JulianCalendar,
|
||||
LeibnizBinary,
|
||||
PlanckUnits,
|
||||
FirstComputer,
|
||||
FirstTransistor,
|
||||
ArpanetWhisper,
|
||||
UnixEpoch,
|
||||
FirstEmail,
|
||||
FirstLeapSecond,
|
||||
FirstWebsite,
|
||||
FirstSmartphone,
|
||||
FirstBitcoinBlock,
|
||||
UnixTimeOverflows,
|
||||
|
||||
// The far future: everything scheduled, from Voyager's fly-by to the
|
||||
// last star.
|
||||
VoyagerPassesGliese445,
|
||||
PangaeaProxima,
|
||||
OceansEvaporate,
|
||||
AndromedaArrives,
|
||||
SunRedGiant,
|
||||
LocalGroupAlone,
|
||||
LastStarsDie,
|
||||
|
||||
// The degenerate era: the remnants, cooling, and the galaxies coming
|
||||
// apart.
|
||||
DegenerateEra,
|
||||
GalaxiesDisperse,
|
||||
|
||||
// The black hole era, if the protons decay; and the rocks, if they do
|
||||
// not.
|
||||
ProtonDecayComplete,
|
||||
BlackHoleEraBegins,
|
||||
RocksBecomeSpheres,
|
||||
|
||||
// The dark era: the holes and the dwarfs leak away, and the bits run out.
|
||||
StellarBlackHolesEvaporate,
|
||||
WhiteDwarfsErode,
|
||||
SagittariusAEvaporates,
|
||||
SupermassiveBlackHolesEvaporate,
|
||||
}
|
||||
|
||||
/// What the overlay shows when a caret on the timeline is clicked: a title,
|
||||
/// centred in amber at card scale, and a passage of three paragraphs, white,
|
||||
/// word-wrapped to the window's width by the Observatory and separated by
|
||||
/// the blank lines the text itself carries. Both are `&'static str`
|
||||
/// compiled into the binary; the calendar does not fetch its history from
|
||||
/// anywhere it could be edited without a rebuild.
|
||||
pub struct EventPassage {
|
||||
/// The event's title, as the overlay prints it above the passage. Not
|
||||
/// the same string as the marker's name on the timeline, which is
|
||||
/// shorter and shares a line with a dash; this one has the width of
|
||||
/// the window and uses it.
|
||||
pub title: &'static str,
|
||||
/// The passage: a meditation in three paragraphs on the event, its
|
||||
/// evidence, and its bearing on a calendar that measures from a crash.
|
||||
/// Where a number is argued the passage says so; where it is settled it
|
||||
/// says that; and where the calendar itself takes a position the
|
||||
/// passage says whose position it is.
|
||||
pub passage: &'static str,
|
||||
}
|
||||
|
||||
/// Furnish the passage for an event: the oracle through which the
|
||||
/// Observatory conjures textual enlightenment regarding the milestones
|
||||
/// scattered across its timeline, consulted once per click on a caret and
|
||||
/// answering, always, because the match below is exhaustive over `EventId`
|
||||
/// and the compiler will not have it otherwise. Each passage has been
|
||||
/// crafted with the care and verbosity befitting events of such magnitude,
|
||||
/// and checked with the suspicion befitting a document that counts Planck
|
||||
/// times; the titles and the texts are content, not commentary, and are the
|
||||
/// one thing in this crate its comments are forbidden to improve.
|
||||
///
|
||||
/// The function returns by value and allocates nothing: an `EventPassage`
|
||||
/// is two static references, thirty-two bytes on this platform, and the
|
||||
/// strings they point at were laid down when the binary was.
|
||||
#[allow(
|
||||
clippy::too_many_lines,
|
||||
reason = "sixty passages behind one match: the match is a book's index, and an index bound in five volumes serves worse than one"
|
||||
)]
|
||||
pub fn get_passage(id: EventId) -> EventPassage {
|
||||
match id {
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// THE PLANCK ERA: the first 10^-43 seconds, and the boundary of physics itself
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
EventId::PlanckEpochEnds => EventPassage {
|
||||
title: "The Planck Epoch Concludes",
|
||||
passage: r"For one Planck time—the smallest meaningful interval that physics deigns to acknowledge—the universe existed in a state of primordial unity. Gravity, electromagnetism, and the nuclear forces were not merely friends; they were indistinguishable, a single superforce presiding over a cosmos smaller than anything you have ever conceived of being small.
|
||||
|
||||
At 5.39 × 10⁻⁴⁴ seconds, this unity shattered. Gravity, that most antisocial of forces, separated from its siblings and began its long, slow work of pulling things together. The universe was now approximately 10⁻³⁵ meters across—still inconceivably small, but infinitely larger than it had been one Planck time prior.
|
||||
|
||||
This moment marks the boundary of physics itself. Before this, our equations dissolve into quantum foam. After this, we can pretend to understand things.",
|
||||
},
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// THE GRAND UNIFICATION ERA: 10^-43 to 10^-36 seconds, a unity never since repeated
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
EventId::GrandUnificationEpoch => EventPassage {
|
||||
title: "The Grand Unification Epoch",
|
||||
passage: r"With gravity now sulking in its own corner of reality, the remaining three forces—electromagnetic, strong nuclear, and weak nuclear—maintained their alliance for a cosmological instant. The temperature exceeded 10²⁷ Kelvin, a number so absurd that comparison fails; the core of the sun, at a mere 15 million Kelvin, is effectively absolute zero by comparison.
|
||||
|
||||
During this epoch, the distinction between quarks and leptons was meaningless. Particles transmuted freely between forms, a democracy of matter that would soon give way to the rigid class structures of conventional physics.
|
||||
|
||||
The Grand Unified Force, as theorists have named this ephemeral unity, remains hypothetical—we have never achieved the energies required to reunite these forces in a laboratory. The early universe performed this experiment exactly once, then moved on.",
|
||||
},
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// THE ELECTROWEAK ERA: 10^-36 to 10^-12 seconds, inflation and the acquisition of mass
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
EventId::InflationEnds => EventPassage {
|
||||
title: "Cosmic Inflation Concludes",
|
||||
passage: r"For a flickering moment—between 10⁻³⁶ and 10⁻³² seconds after the Beginning—the universe indulged in exponential expansion so violent that it would make any self-respecting economist weep with envy. Space itself stretched faster than light, a feat permitted because nothing was actually moving through space; space itself was simply becoming more of itself.
|
||||
|
||||
The observable universe, currently 93 billion light-years across, was during this period smaller than a proton. Quantum fluctuations, those tiny statistical hiccups in the fabric of reality, were stretched to cosmic proportions. These wrinkles would eventually become galaxy clusters, voids, and the large-scale structure we observe today.
|
||||
|
||||
Inflation solved several embarrassing problems in cosmology—the horizon problem, the flatness problem—by the simple expedient of making the universe unimaginably larger than it has any reasonable right to be.",
|
||||
},
|
||||
|
||||
EventId::ElectroweakEpoch => EventPassage {
|
||||
title: "The Electroweak Epoch",
|
||||
passage: r"At 10⁻¹² seconds—a picosecond, in the parlance of those who traffic in such infinitesimals—the electromagnetic and weak nuclear forces finally acknowledged their differences and went their separate ways. This divorce, known as electroweak symmetry breaking, gave mass to the W and Z bosons while leaving photons forever massless and fleet.
|
||||
|
||||
The Higgs field, that all-pervading molasses of the quantum realm, switched on. Particles that had been traveling at the speed of light suddenly found themselves wading through cosmic treacle, acquiring the property we call mass. Without this moment, atoms could not form, chemistry would be impossible, and you would not exist to contemplate your own impossibility.
|
||||
|
||||
The universe was now one trillionth of a second old. The patch of it that would one day become everything you can see was roughly 10¹² metres across—about the size of Saturn's orbit—and still hotter than anything that would exist for the next 13.8 billion years.",
|
||||
},
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// THE QUARK ERA: 10^-12 to 10^-5 seconds, ten microseconds of unconfined broth
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
EventId::QuarkEpoch => EventPassage {
|
||||
title: "The Quark Epoch",
|
||||
passage: r"The universe had now cooled sufficiently—to a mere 10¹² Kelvin—that the fundamental forces operated as we know them today. Quarks and gluons swam in a plasma of such density and temperature that they could not bind together into hadrons. This was the quark-gluon plasma, a state of matter so exotic that we have only recently recreated it in particle accelerators.
|
||||
|
||||
For approximately ten microseconds, the cosmos was a seething broth of free quarks, antiquarks, and gluons, engaged in constant annihilation and pair-production. The slight asymmetry between matter and antimatter—one extra quark per billion annihilations—would prove consequential. Those leftover quarks would eventually become everything that exists.
|
||||
|
||||
Color confinement, the principle that quarks cannot exist in isolation, was temporarily suspended. It would not remain so for long.",
|
||||
},
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// THE HADRON ERA: 10^-5 seconds to one second, confinement, and one survivor in a billion
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
EventId::HadronEpoch => EventPassage {
|
||||
title: "The Hadron Epoch",
|
||||
passage: r"As the universe cooled below 10¹² Kelvin, quarks found themselves suddenly and irrevocably confined. They bound together in trios to form baryons—protons and neutrons, the building blocks of all ordinary matter—and in pairs to form mesons. The quark-gluon plasma had curdled into hadrons.
|
||||
|
||||
Protons and antiprotons, neutrons and antineutrons, annihilated each other in cataclysmic finality. When the fire cooled, only the slight excess of matter over antimatter remained—one survivor per billion annihilations. This is why there is something rather than nothing, a fact whose explanation remains one of physics' most profound mysteries.
|
||||
|
||||
The universe was now a ten-thousandth of a second old. In human terms, this is nothing. In terms of cosmic history, entire eras had already passed.",
|
||||
},
|
||||
|
||||
EventId::NeutrinoDecoupling => EventPassage {
|
||||
title: "Neutrino Decoupling",
|
||||
passage: r"One second after the Beginning—an eternity by the standards of the preceding epochs—the universe had cooled to approximately 10¹⁰ Kelvin. At this temperature, neutrinos, those ghostly particles that barely interact with anything, finally decoupled from the cosmic plasma and began their eternal, nearly unimpeded journey through the universe.
|
||||
|
||||
These primordial neutrinos still exist, filling every cubic centimeter of space with approximately 336 of their number, traveling at nearly the speed of light with energies so low they are essentially undetectable. The Cosmic Neutrino Background, as it is called, predates the Cosmic Microwave Background by 380,000 years.
|
||||
|
||||
Neutrino decoupling marked the moment when the universe became transparent to neutrinos but remained opaque to photons. Light would have to wait considerably longer for its freedom.",
|
||||
},
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// NUCLEOSYNTHESIS: one second to twenty minutes, three parts hydrogen to one of helium
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
EventId::NucleosynthesisBegins => EventPassage {
|
||||
title: "Big Bang Nucleosynthesis Begins",
|
||||
passage: r"Ten seconds into existence, the universe had cooled enough for protons and neutrons to stick together in principle. In practice every deuterium nucleus they made was promptly blown apart again by the sea of photons around it, and it took three more minutes of cooling—the deuterium bottleneck—before the products could survive their birth. Then nuclear fusion, the same process that powers stars, commenced on a cosmic scale. For about a quarter of an hour, the entire universe functioned as a fusion reactor.
|
||||
|
||||
Hydrogen nuclei (single protons) fused into deuterium, which fused into helium-3, which fused into helium-4. Small amounts of lithium-7 and beryllium-7 formed. And then, due to the absence of stable nuclei with masses 5 and 8, the fusion chain stopped.
|
||||
|
||||
This bottleneck would have profound consequences. Nearly all elements heavier than lithium had to wait for stars to form, hundreds of millions of years later. The early universe, for all its fury, could only cook the lightest ingredients.",
|
||||
},
|
||||
|
||||
EventId::NucleosynthesisEnds => EventPassage {
|
||||
title: "Big Bang Nucleosynthesis Concludes",
|
||||
passage: r"Twenty minutes after the Beginning, the universe had cooled below the threshold for nuclear fusion. The cosmic reactor shut down, leaving behind a precise mixture: roughly 75% hydrogen, 25% helium by mass, and trace amounts of deuterium, helium-3, and lithium.
|
||||
|
||||
This ratio, predicted by theory and confirmed by observation, stands as one of the great triumphs of modern cosmology. The abundance of light elements matches theoretical predictions to remarkable precision, providing strong evidence that we understand the first twenty minutes of cosmic history.
|
||||
|
||||
The universe would now spend the next 380,000 years as an opaque plasma, too hot for atoms to form, too thick with free electrons for light to travel far. It would be a long, quiet wait before the next significant event.",
|
||||
},
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// THE PHOTON ERA: twenty minutes to 380,000 years, the long opaque wait for atoms
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
EventId::CoolerThanTheSunsCore => EventPassage {
|
||||
title: "Cooler Than the Sun's Core",
|
||||
passage: r"A week or so after the Beginning, the temperature of the entire universe fell below fifteen million kelvin—the temperature at the centre of the Sun today. Everything that exists had, for its first eight days, been hotter than the inside of a star; from here on the cosmos as a whole would be cooler than the furnaces it would later build.
|
||||
|
||||
The cooling followed one rule the whole way down: in this era the temperature fell as the inverse square root of the age. Ten thousand times older, one hundred times cooler. By its first birthday—an anniversary nobody kept, there being no years yet, or calendars, or anybody—the universe was a fog of hydrogen and helium nuclei, electrons and light at some two million kelvin, opaque, featureless, and with nothing scheduled for the next forty-seven thousand years.
|
||||
|
||||
The Sun's core keeps its fifteen million kelvin by burning hydrogen. The universe had no such source. It was simply spending the heat it was born with, and has been spending it ever since.",
|
||||
},
|
||||
|
||||
EventId::MatterRadiationEquality => EventPassage {
|
||||
title: "Matter-Radiation Equality",
|
||||
passage: r"For the first 47,000 years, radiation dominated the universe. Photons, neutrinos, and other relativistic particles contained more energy than all the matter combined. Under such conditions, the growth of structure was suppressed; density fluctuations could not amplify because radiation pressure smoothed them away.
|
||||
|
||||
At 47,000 years, the universe had expanded and cooled sufficiently that matter and radiation achieved equal energy density. This moment, unremarkable in the cosmic plasma, marked a fundamental shift in power. Henceforth, matter would dominate, gravity would strengthen its grip, and structure formation could begin in earnest.
|
||||
|
||||
The seeds of galaxies, planted by inflation billions of times smaller than a proton, began to grow.",
|
||||
},
|
||||
|
||||
EventId::Recombination => EventPassage {
|
||||
title: "Recombination: The Cosmic Dawn of Transparency",
|
||||
passage: r"At 380,000 years, the universe had cooled to approximately 3,000 Kelvin—still hot enough to boil most metals, but cool enough for electrons to bind to nuclei. Atoms formed for the first time. Hydrogen, helium, and traces of lithium condensed out of the primordial plasma.
|
||||
|
||||
With electrons no longer free to scatter photons, light could finally travel unimpeded. The universe became transparent. The photons released at this moment have been traveling ever since, their wavelengths stretched by cosmic expansion from visible light to microwaves. We call them the Cosmic Microwave Background.
|
||||
|
||||
This is the oldest light in the universe, the first photograph of creation, carrying in its subtle temperature variations the fingerprints of quantum fluctuations from the inflationary epoch.",
|
||||
},
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// THE DARK AGES: 380,000 to some 200 million years, the afterglow and nothing else
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
EventId::DarkAgesBegin => EventPassage {
|
||||
title: "The Cosmic Dark Ages",
|
||||
passage: r"Following recombination, the universe entered a period of profound darkness. The Cosmic Microwave Background photons had been released, but no new light sources existed. The cosmos was filled with neutral hydrogen gas, slowly cooling, slowly coalescing under gravity's patient pull.
|
||||
|
||||
For a couple of hundred million years, nothing shone. The universe was a vast, dark, slowly-evolving cloud of gas, gathering into the earliest dark matter halos. No stars. No galaxies. No light except the fading afterglow of the Big Bang itself.
|
||||
|
||||
This era, the Dark Ages, is the longest stretch of cosmic history for which we have no direct observations. It ended only when the first stars ignited and lit the universe once more.",
|
||||
},
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// COSMIC DAWN: some 200 to 500 million years, Population III and the first galaxies
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
EventId::FirstStarsIgnite => EventPassage {
|
||||
title: "The First Stars Ignite",
|
||||
passage: r"Approximately 200 million years after the Beginning, in the hearts of dark matter halos where primordial gas had collected and compressed, the first stars ignited. These Population III stars, as astronomers classify them, were unlike any stars that exist today: massive, luminous, and composed entirely of hydrogen and helium.
|
||||
|
||||
Without heavy elements to cool their formation clouds, these first stars grew to enormous sizes—hundreds of solar masses, burning with blue-white fury, living fast and dying spectacularly. Their lifespans measured in millions of years, not billions. Their deaths seeded the universe with the first heavy elements: carbon, oxygen, iron, forged in their cores and scattered by their supernovae.
|
||||
|
||||
The Dark Ages ended. The universe, after 200 million years of silence, was alight once more.",
|
||||
},
|
||||
|
||||
EventId::FirstGalaxiesForm => EventPassage {
|
||||
title: "The First Galaxies Coalesce",
|
||||
passage: r"As the first generation of stars lived and died, their remnants—black holes, neutron stars, and scattered heavy elements—collected in the gravitational wells of dark matter halos. Small proto-galaxies merged and grew. By 300 million years after the Beginning, the first recognizable galaxies had formed.
|
||||
|
||||
These early galaxies were small, irregular, and violent—nothing like the stately spirals and ellipticals of the present day. They churned with star formation, their gas enriched by the deaths of Population III stars, their cores often hosting the seeds of the supermassive black holes that would grow over billions of years.
|
||||
|
||||
The James Webb Space Telescope has caught them in the act: JADES-GS-z14-0, the most distant galaxy yet confirmed by its spectrum, shines from 290 million years after the Beginning, already holding hundreds of millions of suns—brighter, larger, and earlier than the models expected. The universe, it seems, was in a hurry to build.",
|
||||
},
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// THE STELLIFEROUS ERA: the age of stars, from reionization to the dinosaurs' last day
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
EventId::ReionizationComplete => EventPassage {
|
||||
title: "Reionization: The Universe Becomes Transparent Once More",
|
||||
passage: r"The first stars and galaxies did more than provide light; they transformed the universe itself. Ultraviolet radiation from early stellar populations ionized the neutral hydrogen that had filled the cosmos since recombination. Over hundreds of millions of years, the universe transitioned from neutral to ionized once more.
|
||||
|
||||
By approximately one billion years after the Beginning, reionization was complete. The intergalactic medium became transparent to ultraviolet light. Photons from distant sources could travel unimpeded across cosmic distances—a prerequisite for observational astronomy.
|
||||
|
||||
This second great phase transition, from neutral to ionized, marked the end of the cosmic Dark Ages and the beginning of the Stelliferous Era—the age of stars, the epoch we still inhabit today.",
|
||||
},
|
||||
|
||||
EventId::CosmicNoon => EventPassage {
|
||||
title: "Cosmic Noon",
|
||||
passage: r"Three and a half billion years after the Beginning, at a redshift of about two, the universe was forming stars faster than it ever had or ever would again. Astronomers call it cosmic noon. The rate has fallen by roughly a factor of ten since; about a quarter of all the stars that exist today were already made before the peak, and the universe has been coasting on the momentum of that afternoon ever since.
|
||||
|
||||
The Milky Way was there for it, a smaller and messier galaxy than the one that hosts you, building its stars in bursts. The Sun was not; it would not form for nearly six billion years more, from gas already enriched by the generations that noon produced.
|
||||
|
||||
An observer who could see the whole sky at once would have watched galaxies glow with young blue stars from horizon to horizon. Nobody was there. The light from that noon arrives here now, ten billion years late, redshifted into the infrared, in the long exposures of telescopes that were pointed at nothing in particular.",
|
||||
},
|
||||
|
||||
EventId::ExpansionAccelerates => EventPassage {
|
||||
title: "The Expansion Begins to Accelerate",
|
||||
passage: r"For its first seven or eight billion years the universe expanded the way a thrown stone rises: coasting, and slowing, as the gravity of everything in it pulled on everything else. Then, somewhere around six billion years ago, the slowing stopped and the expansion began to speed up. Matter had thinned out enough that something else—constant per unit of space and indifferent to dilution—took over the accounts. It is called dark energy because it has to be called something.
|
||||
|
||||
The transition is not sharp and its date is not settled; analyses put it between five and seven billion years ago, depending on the data and the assumptions. The sign change is not in doubt. Two teams measured it in 1998 with distant supernovae that were fainter than a decelerating universe allowed, and were given a Nobel prize for the inconvenience.
|
||||
|
||||
Nothing here noticed. The Sun did not yet exist; the cloud it would condense from was still being seeded by supernovae, and the most sophisticated life anywhere nearby, if there was any, was a cell without a nucleus.",
|
||||
},
|
||||
|
||||
EventId::SolarSystemForms => EventPassage {
|
||||
title: "The Solar System Coalesces",
|
||||
passage: r"Nine billion years after the Beginning, in an unremarkable arm of an unremarkable spiral galaxy, a cloud of gas and dust began to collapse. The trigger may have been a nearby supernova, its shockwave compressing the cloud past the threshold of gravitational instability. Whatever the cause, the result was the birth of a star.
|
||||
|
||||
The Sun ignited 4.6 billion years ago, consuming 99.86% of the available mass. The remaining 0.14%—a cosmic rounding error—flattened into a protoplanetary disk. Within this disk, dust grains collided and stuck, growing into planetesimals, then planets. The Solar System took shape over tens of millions of years.
|
||||
|
||||
Four rocky planets formed in the inner system, where heat drove away light gases. Two gas giants and two ice giants formed in the outer system, where temperatures allowed hydrogen, helium and ices to accumulate. And on the third rock from the Sun, something remarkable was about to begin.",
|
||||
},
|
||||
|
||||
EventId::EarthForms => EventPassage {
|
||||
title: "Earth Forms from Cosmic Debris",
|
||||
passage: r"The Earth accreted from the protoplanetary disk approximately 4.54 billion years ago, a molten ball of rock and metal bombarded by planetesimals. The energy of impacts kept the planet hot; iron sank to the core while silicates floated to form a magma ocean.
|
||||
|
||||
A world without oceans, without atmosphere, without any feature recognizable as Earthly. A hellscape of perpetual bombardment and volcanic fury. This was the Hadean Eon, named for Hades, and the name was apt.
|
||||
|
||||
Yet this violent world would, against all reasonable expectation, become a cradle for life. The very impacts that scarred its surface delivered water and organic molecules. The iron core that segregated early would generate a magnetic field to shield future life from solar radiation. Every catastrophe was also a gift.",
|
||||
},
|
||||
|
||||
EventId::MoonForms => EventPassage {
|
||||
title: "The Moon-Forming Impact",
|
||||
passage: r"Approximately 4.5 billion years ago, when Earth was merely 50 million years old, a Mars-sized body named Theia collided with our nascent planet—at a glancing angle, in the standard telling; head-on and hotter, in some newer ones. The impact was cataclysmic beyond description: both worlds were largely vaporized, their material mixing in an orbiting cloud of molten rock.
|
||||
|
||||
From this debris disk, the Moon coalesced—a body proportionally larger relative to its planet than any other moon in the inner Solar System. The impact may have tilted Earth's axis, giving us seasons, and probably stripped away much of the early atmosphere. Whether it helped start the planet's magnetic field is argued.
|
||||
|
||||
The Moon stabilizes Earth's axial tilt, damping the chaotic wandering that models say the axis would otherwise undergo; whether complex life needed that favour is a conjecture, not a finding. Tides driven by lunar gravity may have played a role in life's emergence from the seas. Our satellite is not merely a companion. It may be an enabler of our existence, and it is certainly a witness to it.",
|
||||
},
|
||||
|
||||
EventId::FirstLife => EventPassage {
|
||||
title: "The Emergence of Life",
|
||||
passage: r"Sometime between 4.2 and 3.8 billion years ago—the evidence remains frustratingly uncertain—life emerged on Earth. The transition from chemistry to biology, from organic molecules to self-replicating systems, remains one of science's most profound unsolved problems.
|
||||
|
||||
The earliest definitive evidence of life dates to approximately 3.5 billion years ago: stromatolites, layered structures built by microbial communities, preserved in Australian rock. But life may have existed long before, perhaps as early as the heavy bombardment allowed the oceans to remain liquid.
|
||||
|
||||
How non-life became life we do not know. Whether it happened once or many times, whether it was inevitable or improbable, whether it has happened elsewhere—these questions haunt the margins of our understanding. What we know is that it happened here, and everything that followed was consequence.",
|
||||
},
|
||||
|
||||
EventId::GreatOxidation => EventPassage {
|
||||
title: "The Great Oxidation Event",
|
||||
passage: r"Around 2.4 billion years ago the atmosphere of the Earth acquired free oxygen, and kept it. Cyanobacteria had been splitting water for a long while by then, perhaps hundreds of millions of years, but everything they released had been swallowed—by iron dissolved in the oceans, by volcanic gases, by rock. Somewhere between 2.5 and 2.4 billion years ago the sinks filled, and the gas began to accumulate in the air.
|
||||
|
||||
The evidence is written in sulphur: before the event, sulphur isotopes in sediments carry a signature that forms only in an atmosphere without an ozone layer; after it, the signature vanishes, worldwide, and does not return. Rust follows—red beds, and the last of the banded iron formations, the iron of the oceans falling out of solution in layers now mined for steel.
|
||||
|
||||
For most of the life then living, oxygen was poison. What followed was, by biomass, one of the great extinctions, and, it is thought, one of the great glaciations: the greenhouse gas methane collapsed and the planet froze, perhaps to the equator. Everything that breathes descends from the survivors.",
|
||||
},
|
||||
|
||||
EventId::FirstEukaryotes => EventPassage {
|
||||
title: "The First Eukaryotes",
|
||||
passage: r"The oldest fossils that are unmistakably eukaryotes—cells with a nucleus, the kind you are made of—are about 1.65 billion years old: ornamented, spiny microfossils from what is now China, too elaborate to be bacteria. Molecular clocks argue the lineage is older, perhaps by hundreds of millions of years, and the rocks have not yet been persuaded to agree.
|
||||
|
||||
The eukaryotic cell was not invented so much as assembled. At its heart is a merger: one cell took up residence inside another and stayed, becoming the mitochondrion, the power supply of every animal, plant and fungus since. The host is thought to have been an archaeon, the guest a bacterium; the arrangement has held for well over a billion years, which is longer than most.
|
||||
|
||||
Within a few tens of millions of years some of these cells were living in filaments, and within a hundred million in sheets the size of a hand and larger. Multicellularity, it turns out, is easy; life has arrived at it dozens of times. It is the nucleus that took two billion years.",
|
||||
},
|
||||
|
||||
EventId::SnowballEarth => EventPassage {
|
||||
title: "Snowball Earth",
|
||||
passage: r"Seven hundred and seventeen million years ago the ice came down from the poles and did not stop. The Sturtian glaciation, the first of the Cryogenian's two, buried the planet for some fifty-seven million years—nearly the whole distance from the dinosaurs to you—and the glacial deposits it left are found on every continent, including ones that were then at the equator.
|
||||
|
||||
Whether the oceans froze over completely, or a band of open water survived at the equator, is argued; that the Earth was white from space, reflecting the sunlight it could no longer absorb, is not. What ended it was volcanoes: with no rain to weather rock and no weathering to draw down carbon dioxide, the gas accumulated over the ice for tens of millions of years until the greenhouse it built melted everything at once. The cap carbonates deposited in that thaw sit on top of the glacial debris worldwide, like a signature.
|
||||
|
||||
The animals appear in the rocks a little after the second glaciation ended. It is hard not to draw a line between the two, and hard to prove one.",
|
||||
},
|
||||
|
||||
EventId::CambrianExplosion => EventPassage {
|
||||
title: "The Cambrian Explosion",
|
||||
passage: r"For three billion years, life was, with rare and modest exceptions, microscopic and, to human eyes, boring; the Ediacaran experiments in soft-bodied multicellular life came late and left few descendants. Then, approximately 539 million years ago, something changed. In a geological instant—perhaps 20 million years—nearly all major animal phyla appeared in the fossil record. Bodies. Eyes. Shells. Predators and prey. Complexity exploded.
|
||||
|
||||
The Cambrian Explosion, as this event is known, transformed Earth's biosphere from a thin veneer of microbial films to a churning ecosystem of multicellular creatures. Trilobites armored themselves against predators. Anomalocaris became the first apex predator we know of. Hallucigenia was reconstructed upside down and back to front before anyone worked out which end was the head.
|
||||
|
||||
What triggered this explosion remains debated: rising oxygen levels, the evolution of vision, an arms race between predators and prey, or all of the above. What is certain is that the world was forever changed.",
|
||||
},
|
||||
|
||||
EventId::PermianExtinction => EventPassage {
|
||||
title: "The Great Dying",
|
||||
passage: r"Two hundred and fifty-two million years ago the Siberian Traps—a volcanic province that would cover most of Western Europe—erupted through coal and carbonate, and the atmosphere filled with carbon dioxide and worse. The oceans acidified, warmed, and lost their oxygen. In about sixty thousand years—a geological instant, dated to that precision by uranium in volcanic ash beds at Meishan, China—the great majority of marine species, estimates run from eighty to ninety-six percent, and most life on land were gone.
|
||||
|
||||
It is the worst extinction in the record of the planet, worse than the one that took the dinosaurs, and it took so long to recover from that the Triassic began with a world of few species, most of them small. The lystrosaurs, pig-sized and unglamorous, inherited the continents for a while, and were at one point perhaps the majority of all land vertebrates.
|
||||
|
||||
The dinosaurs came out of the recovery. So, some distance behind them, did you.",
|
||||
},
|
||||
|
||||
EventId::DinosaurExtinction => EventPassage {
|
||||
title: "The Cretaceous-Paleogene Extinction",
|
||||
passage: r"Sixty-six million years ago, an asteroid approximately 10 kilometers in diameter struck the Yucatan Peninsula at a velocity of 20 kilometers per second. The impact released energy equivalent to 100 million megatons of TNT—billions of times more than the bombs that destroyed Hiroshima and Nagasaki.
|
||||
|
||||
The immediate effects were apocalyptic: a fireball that incinerated everything within a thousand kilometers, earthquakes of magnitude 10 or greater, tsunamis that scoured coastlines worldwide. The long-term effects were worse: dust and soot blocked sunlight for years, collapsing food chains, acidifying oceans, extinguishing three-quarters of all species.
|
||||
|
||||
The non-avian dinosaurs, lords of the Earth for 165 million years, perished entirely. And in the ecological vacuum left behind, small, furry, insignificant creatures called mammals began their ascent.",
|
||||
},
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// HUMAN HISTORY: from Jebel Irhoud to the year 2038, the calendar's own epoch among them
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
EventId::HomoSapiens => EventPassage {
|
||||
title: "Homo sapiens",
|
||||
passage: r"The oldest fossils that are placed in your own species come from Jebel Irhoud in Morocco and are about three hundred thousand years old: faces that would pass in a crowd, above braincases that would not, quite. The species did not arise on a date or in a place; it assembled itself across Africa over a long time from populations that met, separated and met again. Three hundred thousand years is where the record currently draws the line, and the line has moved before.
|
||||
|
||||
Everything that follows on this timeline—writing, computing, the network, this calendar—fits into the last two percent of that span. For ninety-eight percent of the time there have been Homo sapiens, there has been nothing you would recognise as history: stone, fire, ochre, the sea crossed a few times, an ice age or two endured.
|
||||
|
||||
It is the only species known to have measured the age of the universe, or to have wanted to.",
|
||||
},
|
||||
|
||||
EventId::FirstFarmers => EventPassage {
|
||||
title: "The First Farmers",
|
||||
passage: r"About eleven and a half thousand years ago, in the hills of the Fertile Crescent, people who had gathered wild wheat and barley for millennia began to plant it. Not everywhere at once, and not on purpose in the sense of a decision: the plants that were harvested and resown became, over centuries, plants that could no longer scatter their own seed, and the people became people who could no longer leave.
|
||||
|
||||
Within a few thousand years the same thing happened independently in China, in Mesoamerica, in the Andes, in New Guinea, with rice and maize and potatoes and taro. It is one of the strongest arguments that the thing was going to happen: the climate had settled, the ice had gone, and a species that had watched plants closely for a hundred thousand years was going to notice.
|
||||
|
||||
Farming made villages, villages made surplus, surplus made accountants, and accountants, some six thousand years later, made writing. Every calendar descends from the need to know when to plant. This one, with no months, no seasons and no years, is the first that does not.",
|
||||
},
|
||||
|
||||
EventId::FirstWriting => EventPassage {
|
||||
title: "The Invention of Writing",
|
||||
passage: r"Approximately 5,400 years ago, in the cities of ancient Sumer, humans began pressing wedge-shaped reeds into clay tablets. The purpose was mundane: accounting, inventory, taxation. The consequence was revolutionary: the externalization of memory, the defeat of forgetting, the birth of history itself.
|
||||
|
||||
Writing transformed human civilization. Information could now persist beyond living memory, accumulate across generations, spread across distances. Law, literature, mathematics, science—all became possible when knowledge could be recorded and transmitted.
|
||||
|
||||
The Loic Calendar measures time in Planck intervals, but this moment—the invention of writing—marks the beginning of recorded human history. Everything before is prehistory, reconstructed from artifacts and inference. Everything after is, in principle, knowable.",
|
||||
},
|
||||
|
||||
EventId::JulianCalendar => EventPassage {
|
||||
title: "The Julian Calendar",
|
||||
passage: r"On the first of January, 45 BC, Rome switched to a calendar of 365 days with a leap day every fourth year. To get there, the previous year had to be 445 days long; the Romans called it the last year of confusion, and it was. The astronomer who designed it, Sosigenes of Alexandria, took the year to be 365 and a quarter days, which is eleven minutes too long; by the sixteenth century the error had piled up to ten days and the calendar had to be cut open and stitched.
|
||||
|
||||
The Loic Calendar keeps one thing from Julius Caesar: the Julian year of 31,557,600 seconds, exactly 365.25 days, which astronomers still use as their unit of long duration because it is a definition and not a measurement. The calendar's age of the universe is stated in those years. It has no other use for years, or for months, or for days, and the leap day—that quadrennial confession that the Earth and the Sun do not care about integers—it declines entirely.
|
||||
|
||||
The priests in charge of the new calendar promptly misread the rule and added a leap day every three years. It took Augustus, and a dozen years without any leap days at all, to undo it. Time-keeping has been like that ever since.",
|
||||
},
|
||||
|
||||
EventId::LeibnizBinary => EventPassage {
|
||||
title: "Leibniz Explains Binary Arithmetic",
|
||||
passage: r"In 1703 Gottfried Wilhelm Leibniz published, in the Mémoires of the French Royal Academy of Sciences, an explanation of arithmetic using only the characters 0 and 1. He had been playing with it for over twenty years. Addition was a matter of carrying, multiplication needed no tables, and every number was a string of the two simplest marks there are; he thought it a fine image of creation out of nothing, and said so. He also thought it had been discovered four thousand years earlier in China, having been sent a diagram of the sixty-four hexagrams of the I Ching by a Jesuit missionary, and said that too.
|
||||
|
||||
Nobody used it for more than two hundred years. Binary was a curiosity of philosophers until relays and then vacuum tubes turned out to have exactly two states worth trusting, at which point it became the only arithmetic there is.
|
||||
|
||||
Every quantity in this calendar—every Planck count, every mask, every one of the 512 bits of an instant—is written in the notation of that paper. The seven cosmic primes are even named after operations on it that nobody had yet given names.",
|
||||
},
|
||||
|
||||
EventId::PlanckUnits => EventPassage {
|
||||
title: "Planck Proposes Natural Units",
|
||||
passage: r"In May 1899 Max Planck told the Prussian Academy of Sciences that three constants of nature—the speed of light, the gravitational constant, and a new one of his own, needed to make the theory of heat radiation come out right—could be combined into a unit of length, a unit of mass and a unit of time that would, in his words, retain their meaning for all times and for all civilisations, including extraterrestrial and non-human ones. He gave their values. The unit of time came out at about 10⁻⁴³ seconds. He did not yet know what his new constant meant; the quantum was a year away.
|
||||
|
||||
He was right about the units and did not live to see anyone use them. It took half a century for physicists to realise that the Planck length and the Planck time are where gravity and quantum mechanics must both be taken seriously at once, and that no theory yet manages it.
|
||||
|
||||
This calendar counts Planck times because Planck said, in 1899, that they would mean the same thing to anyone. It is the one claim in the whole edifice that has an author, a date, and a room.",
|
||||
},
|
||||
|
||||
EventId::FirstComputer => EventPassage {
|
||||
title: "The Birth of Electronic Computing",
|
||||
passage: r"The exact moment when electronic computing began is debatable—Colossus, ENIAC, the Z3 all have claims—but by the mid-1940s, machines existed that could perform calculations faster than any human mind. ENIAC ran its first problem, for the hydrogen bomb, in December 1945, and was unveiled the following February. It filled a room, consumed kilowatts, and performed what a modern smartphone accomplishes in microseconds.
|
||||
|
||||
These early computers were tools for specific problems: codebreaking, ballistics, nuclear physics. Their creators did not fully envision what they were building: the foundation of a new kind of civilization, one where information processing would eventually exceed in economic importance the manipulation of physical matter.
|
||||
|
||||
The Loic Calendar exists because of these machines. It requires 512-bit integers, calculations at Planck precision, computations that no human could perform by hand. The calendar is, in a sense, a monument to the computers that make its absurdity possible.",
|
||||
},
|
||||
|
||||
EventId::FirstTransistor => EventPassage {
|
||||
title: "The Invention of the Transistor",
|
||||
passage: r"On December 16, 1947, at Bell Laboratories in New Jersey, John Bardeen and Walter Brattain demonstrated the first practical transistor. This small device—capable of amplifying electrical signals without vacuum tubes—would prove more consequential than nuclear weapons, more transformative than the moon landing.
|
||||
|
||||
Vacuum tubes were large, hot, fragile, and power-hungry. Transistors were small, cool, durable, and efficient. More importantly, transistors could be miniaturized, printed in silicon, manufactured by the billion. Moore's Law—the exponential increase in computing power—was made possible by this humble semiconductor device.
|
||||
|
||||
The computer on which the Loic Observatory runs contains billions of transistors, each smaller than a virus. Without Bardeen and Brattain's invention, without the exponential scaling it enabled, precise temporal measurement at the Planck scale would remain a mathematical abstraction.",
|
||||
},
|
||||
|
||||
EventId::ArpanetWhisper => EventPassage {
|
||||
title: "The ARPANET Whisper: Loic Epoch Zero",
|
||||
passage: r#"At 22:30 Pacific time on October 29, 1969—06:30 UTC on the 30th, this calendar's zero—a student at UCLA began typing the word "LOGIN" to a computer at Stanford Research Institute. The "l" arrived. The "o" arrived. On the "g", the far end crashed. "LO" became the first message transmitted across the ARPANET, the ancestor of the modern Internet.
|
||||
|
||||
This moment—the Whisper—is the epoch of the Loic Calendar. All Loic timestamps are measured from this instant, when machines first spoke to each other across the void. The choice is deliberate: not the birth of a king, not the founding of a city, but the birth of networked computation.
|
||||
|
||||
From this origin point, time flows backward to the Big Bang (13.8 billion years prior) and forward to Annihilation, the calendar's terminal coordinate, which its 512 bits place some 10¹⁰³ years hence. All of cosmic history, measured from the moment humanity taught machines to whisper across distance."#,
|
||||
},
|
||||
|
||||
EventId::UnixEpoch => EventPassage {
|
||||
title: "The Unix Epoch",
|
||||
passage: r"Sixty-two days, seventeen hours, twenty-nine minutes and nineteen seconds after the Whisper, the other epoch begins: midnight, the first of January 1970, Coordinated Universal Time, the zero from which nearly every computer alive counts its seconds. It was not chosen for anything that happened. Unix's early designers moved the zero around several times as their counters overflowed, and settled on the start of 1970 because it was a round date close at hand.
|
||||
|
||||
This calendar's zero is defined by its distance from that one: 5,419,722 seconds on the atomic clock scale, a number chosen, out of the sixty seconds the historical record allows, because it factors into 2 × 3 × 7 × 11 × 11,731 and no other candidate did. The two epochs are tied by a squarefree integer and disagree about everything else, including whether a second is always a second.
|
||||
|
||||
Most of the world is on Unix time without knowing it. Nobody is on Loic time without knowing it.",
|
||||
},
|
||||
|
||||
EventId::FirstEmail => EventPassage {
|
||||
title: "The First Email",
|
||||
passage: r#"In late 1971, Ray Tomlinson sent the first email between two computers on the ARPANET. The content of the message has been lost to history—Tomlinson himself couldn't remember it, suggesting it was something like "QWERTYUIOP"—but its significance transcends its content.
|
||||
|
||||
The @ symbol, chosen by Tomlinson to separate user from machine, became the defining glyph of the digital age. Email would become the backbone of digital communication, the address of identity, the replacement for postal mail in billions of daily transactions.
|
||||
|
||||
Two years after the Whisper, humans had already invented a new form of correspondence. The network was no longer merely a connection; it was becoming a medium for human expression."#,
|
||||
},
|
||||
|
||||
EventId::FirstLeapSecond => EventPassage {
|
||||
title: "The First Leap Second",
|
||||
passage: r"At the end of June 1972, for the first time, a minute was given sixty-one seconds. Coordinated Universal Time had been redefined on the first of January that year to tick in atomic seconds, exactly ten of them behind International Atomic Time, and to be held within nine-tenths of a second of the Earth's rotation by inserting a leap second whenever the planet fell behind. The planet, which is slowing down, falls behind. The first insertion was 23:59:60 on 30 June 1972, and there have been twenty-six more, the last at the end of 2016.
|
||||
|
||||
The difference is now thirty-seven seconds. This calendar reads the atomic scale and regards the civil one with the contempt documented in the workspace README, section 1.2, while owing its own epoch to exactly those thirty-seven seconds; the irony is disclosed there too. The Observatory will not open on a machine whose kernel has not been told the number.
|
||||
|
||||
In 2022 the world's metrologists voted to stop inserting leap seconds by 2035. The Earth, which has lately been speeding up, may yet require the first negative one before then. Nothing in this calendar would notice.",
|
||||
},
|
||||
|
||||
EventId::FirstWebsite => EventPassage {
|
||||
title: "The First Website",
|
||||
passage: r"On August 6, 1991, Tim Berners-Lee posted a summary of the World Wide Web project to a Usenet newsgroup and opened the first website—running at CERN since December, describing the project itself, a recursive documentation of its own existence—to the world. The address was info.cern.ch; it still exists today.
|
||||
|
||||
The Web was not the Internet—the network was already two decades old—but it was the interface that made the Internet accessible to ordinary people. Hypertext, links, browsers: these concepts, obvious in retrospect, transformed the network from a tool for specialists into a medium for humanity.
|
||||
|
||||
Twenty-two years after the Whisper, the network had evolved from a military experiment to a publishing platform for the world. The rate of change was accelerating.",
|
||||
},
|
||||
|
||||
EventId::FirstSmartphone => EventPassage {
|
||||
title: "The Smartphone Revolution",
|
||||
passage: r"On June 29, 2007, Apple released the iPhone. It was not the first smartphone—that distinction belongs to various devices from the 1990s and early 2000s—but it was the device that defined what a smartphone would become: a pocket-sized window to the totality of human knowledge and communication.
|
||||
|
||||
Within a decade, smartphones became ubiquitous. More than half of humanity now carries one. The device that once required a room of vacuum tubes, then a desktop of silicon, now fits in every pocket, runs on battery power, and connects to a network spanning the globe.
|
||||
|
||||
The Loic Observatory could run on such a device. Planck-precision timestamps in your pocket. The absurdity is complete.",
|
||||
},
|
||||
|
||||
EventId::FirstBitcoinBlock => EventPassage {
|
||||
title: "The Genesis Block",
|
||||
passage: r#"On January 3, 2009, at 18:15:05 UTC, the Bitcoin genesis block was mined by the pseudonymous Satoshi Nakamoto. Embedded in its coinbase parameter was a message: "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks."
|
||||
|
||||
Bitcoin itself is not the point; the point is the proof that trustless, decentralized consensus is possible. That strangers across the world can agree on the state of a shared ledger without any central authority. That cryptographic protocols can replace institutional trust.
|
||||
|
||||
The Loic Calendar does not use blockchain, but it shares a philosophy: precision through mathematics, not authority. The genesis block was a declaration that some things need not be decreed; they can simply be computed."#,
|
||||
},
|
||||
|
||||
EventId::UnixTimeOverflows => EventPassage {
|
||||
title: "Thirty-Two Bits Run Out",
|
||||
passage: r"At 03:14:08 UTC on 19 January 2038, the number of seconds since the Unix epoch reaches 2,147,483,648, which is one more than a signed 32-bit integer can hold. Systems that still keep time in one will roll over to the 13th of December 1901 and, in their confusion, may do anything at all. Most have been fixed. The rest are inside things that were not expected to last, which is exactly the kind of thing that lasts.
|
||||
|
||||
This calendar keeps its instants in 512 bits, and the count of Planck times since the Whisper will not exhaust them for about 10¹⁰³ years, at which point the universe is expected to have larger problems. The overflow of 2038 is what happens when time is stored in a quantity chosen for convenience. It is listed here as a cautionary tale, and as the only future event on this timeline that is scheduled to the second.
|
||||
|
||||
Its predecessor was the year 2000, which was survived by preparation and then remembered as a hoax. 2038 will be survived the same way, by people who fixed it in advance and were thanked by nobody.",
|
||||
},
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// THE FAR FUTURE: from Voyager's fly-by to the death of the last red dwarf
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
EventId::VoyagerPassesGliese445 => EventPassage {
|
||||
title: "Voyager 1 Passes Gliese 445",
|
||||
passage: r"About forty thousand years from now, Voyager 1—launched in 1977, the farthest thing humans have thrown—will pass within 1.6 light-years of Gliese 445, a red dwarf in Camelopardalis that is currently seventeen light-years away and closing. The star will do most of the travelling; by then it will be among the Sun's nearest neighbours. Voyager will not stop. Its speed, and the feeble gravity of a small star, will carry it straight through and on.
|
||||
|
||||
It will not be transmitting. Its plutonium will have gone cold by the 2030s, and it will be an inert object of 722 kilograms carrying a gold-plated copper record of greetings in fifty-five languages, whale song, and a diagram of where it came from, drawn relative to fourteen pulsars whose periods will have drifted by then.
|
||||
|
||||
Forty thousand years is, on this timeline, seven times the length of recorded history, and less than a rounding error in the age of anything else listed here.",
|
||||
},
|
||||
|
||||
EventId::PangaeaProxima => EventPassage {
|
||||
title: "Pangaea Proxima",
|
||||
passage: r"In two hundred and fifty million years the continents, which move at about the speed your fingernails grow, will have reassembled into a single mass. The models disagree on the shape: the Atlantic closes and the Americas return to Africa in one, the Pacific closes and Asia takes the Americas in another, and the naming—Pangaea Proxima, Novopangaea, Aurica, Amasia—is a matter of which reconstruction you back. That there will be a supercontinent is not much in doubt; there have been several, on a cycle of some four or five hundred million years, and the last one broke up only two hundred million years ago.
|
||||
|
||||
One 2023 simulation followed the climate through: a hotter Sun, volcanic carbon dioxide from all that reassembly, and a continental interior far from any ocean, with the result that most of the land would be too hot for mammals. Mammals have been around for two hundred million years or so. Whatever is alive then may be reading this timeline from a different branch.
|
||||
|
||||
The marker sits at 250 million years because that is the round number the models share, not because anyone knows.",
|
||||
},
|
||||
|
||||
EventId::OceansEvaporate => EventPassage {
|
||||
title: "The Oceans Leave",
|
||||
passage: r"The Sun brightens by about ten percent every billion years, and has done since it lit. In a billion years or so—the models disagree by a factor of two—the extra light will drive Earth's climate past a threshold called the moist greenhouse: the stratosphere fills with water vapour, ultraviolet splits it, the hydrogen escapes to space, and the oceans, molecule by molecule, leave the planet through the top of the sky. Long before the last of them go, the surface is uninhabitable for anything complex.
|
||||
|
||||
Some accounts give the biosphere less time still: the carbon dioxide that plants need is drawn down faster as the rocks weather in the heat, and in those models photosynthesis fails first, in six hundred million years or so. Either way, Earth has spent roughly four-fifths of its habitable lifetime.
|
||||
|
||||
It is a strange thing to know about one's own planet, and stranger that the number is not larger. The Earth will still exist. It will look, from a distance, like Venus, and for the same reasons.",
|
||||
},
|
||||
|
||||
EventId::AndromedaArrives => EventPassage {
|
||||
title: "Andromeda Arrives",
|
||||
passage: r"The Andromeda galaxy is 2.5 million light-years away and has been known for a century to be approaching, at about 110 kilometres per second. For decades the story was settled: the two largest galaxies of the Local Group would collide in about four and a half billion years, pass through each other, fall back, and merge into one elliptical galaxy that was named, in advance, Milkomeda. The Sun would most likely be flung into a wider orbit and survive; the night sky, for anyone left to see it, would be spectacular.
|
||||
|
||||
In 2025 a study that followed the whole Local Group—Andromeda, the Milky Way, the Triangulum galaxy and the Large Magellanic Cloud, with the uncertainties in all their motions and masses run through a hundred thousand simulations—found the merger to be a coin toss: about even odds that it does not happen within the next ten billion years. The Magellanic Cloud's orbit pulls the Milky Way sideways, and Andromeda's own sideways motion is still too poorly measured to settle it.
|
||||
|
||||
The marker is left at four and a half billion years, which is where the collision is if it comes. It is the only entry on this timeline with a fifty percent chance of not being an event.",
|
||||
},
|
||||
|
||||
EventId::SunRedGiant => EventPassage {
|
||||
title: "The Sun Becomes a Red Giant",
|
||||
passage: r"In about five billion years, the Sun will exhaust the hydrogen in its core and leave the main sequence. Fusion will migrate to a shell surrounding the now-helium core, and over the following two and a half billion years the outer layers will swell. At the tip of the red giant branch, 7.6 billion years from now, the Sun will be 256 times its present radius and some 2,700 times its present luminosity, having shed a third of its mass into space along the way.
|
||||
|
||||
Mercury will be engulfed. Venus will be engulfed. Earth's orbit will have widened as the Sun lightens, but not enough: in the models that follow the mass loss in detail, the photosphere reaches it just before the tip. Its surface will have been sterile for six billion years by then; the oceans go long before the Sun does.
|
||||
|
||||
This is not a disaster; it is simply stellar evolution. Stars live, stars die. The Sun will eventually shed its outer layers as a planetary nebula, leaving behind a white dwarf that will cool for trillions of years.",
|
||||
},
|
||||
|
||||
EventId::LocalGroupAlone => EventPassage {
|
||||
title: "The Local Group Alone",
|
||||
passage: r"Within about a hundred billion years the accelerating expansion carries every galaxy not already bound to ours beyond the horizon: not merely out of sight but out of causal reach, their light stretched and dimmed below anything that could be detected, and then not arriving at all. What remains within reach is the Local Group, by then merged into a single old galaxy, in a sky that is otherwise empty.
|
||||
|
||||
Astronomers of that time—should there be any—will be unable to discover that the universe is expanding. There will be no distant galaxies to measure the expansion with; the cosmic microwave background will have been redshifted to wavelengths longer than the galaxy is wide, and absorbed by its gas; the light elements, the last fingerprint of the first twenty minutes, will have been reprocessed by stars. Every pillar of the Big Bang will have quietly left the building. Their best theory, from their best evidence, would be a single island of stars in a static void—the universe as it was believed to be in 1900, arrived at correctly and wrongly at once.
|
||||
|
||||
The information is not destroyed. It is only that nobody can get to it. This calendar will still say what time it is.",
|
||||
},
|
||||
|
||||
EventId::LastStarsDie => EventPassage {
|
||||
title: "The Last Stars Die",
|
||||
passage: r"In approximately 100 trillion years, the last red dwarf will exhaust its hydrogen and fade to darkness. These smallest of stars, burning with miserly efficiency, are the longest-lived. When they are gone, no new stars will form; the universe will have consumed the gas required for stellar nucleosynthesis.
|
||||
|
||||
The Stelliferous Era—the age of stars, the age in which we live—will be over. It will have lasted approximately 10¹⁴ years, a mere flicker in the full span of cosmic time that the Loic Calendar attempts to encompass.
|
||||
|
||||
What follows is darkness: the slow decay of stellar remnants, the evaporation of black holes, the eventual heat death when thermodynamic equilibrium is achieved and change becomes impossible.",
|
||||
},
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// THE DEGENERATE ERA: white dwarfs cooling, planets stripped, galaxies unbound
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
EventId::DegenerateEra => EventPassage {
|
||||
title: "The Degenerate Era",
|
||||
passage: r"After the last stars die, the universe enters the Degenerate Era. White dwarfs cool toward absolute zero. Neutron stars spin down and fade. Brown dwarfs, never massive enough to sustain fusion, drift cold and dark through the void, and around 10¹⁵ years the last planets are stripped from their dead stars by the remnants that pass too close.
|
||||
|
||||
This era will last until around 10³⁷ to 10³⁹ years—the figure depends on how long protons last, which nobody knows—which is far longer than the Stelliferous Era, yet still a brief prelude to what follows. During this time, the dominant sources of energy will be rare collisions between stellar remnants and, if protons decay, the slow decay of nuclear matter itself.
|
||||
|
||||
The Degenerate Era is named for the quantum phenomenon of electron degeneracy pressure, which supports white dwarfs against gravitational collapse. It is an era of endings slowly arriving, of entropy patiently accumulating.",
|
||||
},
|
||||
|
||||
EventId::GalaxiesDisperse => EventPassage {
|
||||
title: "Galaxies Disperse",
|
||||
passage: r"Approximately 10¹⁹ to 10²⁰ years from now, gravitational dynamics will unbind galaxies. Stars, ejected by close encounters over quadrillions of years, will escape into intergalactic space. Those that remain will spiral inward, feeding the central black hole.
|
||||
|
||||
The familiar structures of the universe—spiral arms, galactic disks, star clusters—will dissolve. What remains will be lonely objects adrift in an expanding void: white dwarfs, neutron stars, brown dwarfs, black holes, and the occasional planet that somehow survived the death of its star.
|
||||
|
||||
Long before this, dark energy will have pushed every other galaxy group beyond the horizon, so each of these dissolutions happens alone, in an observable universe containing nothing else.",
|
||||
},
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// THE BLACK HOLE ERA: proton decay, if any; the holes, if so; the spheres, if not
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
EventId::ProtonDecayComplete => EventPassage {
|
||||
title: "Proton Decay, If Any, Completes",
|
||||
passage: r"Nobody has ever seen a proton decay. Super-Kamiokande—fifty thousand tonnes of ultrapure water under a mountain in Japan, watched by eleven thousand photomultipliers since 1996—has waited through 450 kilotonne-years of exposure for one to turn into a positron and a pion, and has seen none. The proton's lifetime in that mode, if it decays at all, exceeds 2.4 × 10³⁴ years: some 10²⁴ times the present age of the universe.
|
||||
|
||||
Grand Unified Theories say it must decay, eventually. The simplest of them predicted lifetimes that Super-Kamiokande has already ruled out; their successors predict 10³⁴ to 10³⁶ years, just past the edge of what has been searched. If they are right, then by 10⁴⁰ years essentially every proton and neutron in the universe has gone—the last atoms, the last white dwarfs, the last rocks, dissolved into positrons, pions and light—and what follows is the Black Hole Era. If they are wrong, matter persists, and the far future takes the shape of the entries marked as alternatives further along this timeline.
|
||||
|
||||
This calendar takes no position. It marks the decade in which the question is settled either way, and notes that the twenty orders of magnitude before it, from 10²⁰ to 10⁴⁰ years, are the longest stretch on this timeline in which nothing whatsoever is scheduled to happen.",
|
||||
},
|
||||
|
||||
EventId::BlackHoleEraBegins => EventPassage {
|
||||
title: "The Black Hole Era",
|
||||
passage: r"After approximately 10⁴⁰ years—if grand unified theories are correct and protons decay—matter as we know it will be gone. The only macroscopic objects remaining will be black holes, slowly evaporating via Hawking radiation.
|
||||
|
||||
The Black Hole Era will last until approximately 10¹⁰⁰ years—a duration so vast that all preceding eras combined are less than a rounding error by comparison. During this time, black holes will be the only significant concentrations of mass-energy in the universe.
|
||||
|
||||
Hawking radiation is thermal, random, structureless, or nearly so. Whether the glow secretly carries out what fell in—stars, planets, civilizations—in correlations that would take the age of the hole to read is the information paradox, argued for fifty years; the current answer, on paper, is that it does, and that nobody will ever be in a position to check. The Black Hole Era is an era of forgetting on a cosmic scale, whatever the paper says.",
|
||||
},
|
||||
|
||||
EventId::RocksBecomeSpheres => EventPassage {
|
||||
title: "Every Rock Becomes a Sphere",
|
||||
passage: r"On a timescale of 10⁶⁵ years, Freeman Dyson calculated in 1979, solid matter is not solid. Atoms tunnel. Any lump of rock, given that long, flows like a liquid—its atoms wandering through it the way molecules wander in a drop of water—and settles under its own gravity into the shape water takes: a sphere. Every asteroid, every dead planet, every remnant that has escaped the fate of falling into something is, by 10⁶⁵ years, round.
|
||||
|
||||
This assumes the protons last, which is the assumption the previous entries on this timeline do not make. If they decay, there are no rocks by then, and this entry is a footnote to an alternative. If they do not, it is the beginning of the strangest chapter of all: on longer timescales still, Dyson found, the same tunnelling lets nuclei fuse and fission toward iron, until after 10¹⁵⁰⁰ years every sphere is a sphere of iron—a figure this calendar cannot represent, its 512 bits running out a little past 10¹⁰³ years, and which is therefore left in the text where it belongs.
|
||||
|
||||
The rounding of the rocks is the first thing in this calendar that does not end anything. It only tidies.",
|
||||
},
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// THE DARK ERA: the holes evaporate, the dwarfs erode, the last coordinate arrives
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
EventId::StellarBlackHolesEvaporate => EventPassage {
|
||||
title: "Stellar Black Holes Evaporate",
|
||||
passage: r"A black hole with the mass of the Sun will evaporate via Hawking radiation in approximately 10⁶⁷ years. Its temperature is sixty billionths of a kelvin—far colder than today's cosmic microwave background, so that for now it absorbs more than it emits. Only once the expansion has chilled the background below that, early in its long life, does the hole become a net radiator, and begin, imperceptibly, to lose weight.
|
||||
|
||||
As the black hole shrinks, its temperature rises. In its final moments, it will glow with increasing intensity, finally releasing its last energy in a burst of gamma rays. Then nothing will remain—not even a remnant. The black hole will be gone entirely.
|
||||
|
||||
Stellar-mass black holes, formed from the deaths of massive stars, will be the first of the black holes we know exist to evaporate. Supermassive black holes, containing millions to billions of solar masses, will persist longer—but not forever.",
|
||||
},
|
||||
|
||||
EventId::WhiteDwarfsErode => EventPassage {
|
||||
title: "The White Dwarfs Erode Away",
|
||||
passage: r"White dwarfs were supposed to be the last things standing: the cooled cores of stars like the Sun, degenerate matter with nothing left to do, expected to persist until proton decay dissolved them or, if protons are stable, for some 10¹¹⁰⁰ years. In 2025 three physicists at Radboud University argued otherwise. The pair production that Hawking found at the horizon of a black hole, they showed, needs no horizon—only the curvature of spacetime—so any dense object slowly radiates its own mass away. For a white dwarf the timescale comes out at about 10⁷⁸ years; neutron stars go sooner.
|
||||
|
||||
The workspace README, section 4.7, takes the result seriously, and draws the conclusion: the rocks do not outlive the black holes. The black holes outlive the rocks, by twenty-two orders of magnitude.
|
||||
|
||||
The Sun will be one of them. Its white dwarf, cooled by then to a temperature the word does not cover, ends here—not with a bang, not with a whimper, but with a slow leak.",
|
||||
},
|
||||
|
||||
EventId::SagittariusAEvaporates => EventPassage {
|
||||
title: "Sagittarius A* Evaporates",
|
||||
passage: r"The black hole at the centre of the Milky Way has 4.3 million times the mass of the Sun, a horizon that would fit comfortably inside the orbit of Mercury, and a temperature, by Hawking's formula, of about fourteen quadrillionths of a kelvin. It radiates, in principle, and loses mass, in principle, at a rate that would take some 10⁸⁷ years to finish—if it stopped growing now, which it will not; every star and gas cloud it swallows, and the black hole of Andromeda if that merger comes, adds to the mass and lengthens the wait.
|
||||
|
||||
Evaporation is not steady. For nearly the whole of that span the hole shrinks imperceptibly, its temperature rising as its mass falls, until in the last second it is a subatomic fireball releasing millions of megatons of gamma rays, and then it is gone. What comes out over the whole 10⁸⁷ years is exactly the mass that went in, as thermal radiation—including, in some form, every star of the galaxy it anchored.
|
||||
|
||||
It is the point at which this timeline's own galaxy stops existing in any form. The calendar, which is measured from a moment in that galaxy, does not stop.",
|
||||
},
|
||||
|
||||
EventId::SupermassiveBlackHolesEvaporate => EventPassage {
|
||||
title: "The Last Black Holes Evaporate",
|
||||
passage: r"The largest supermassive black holes, containing 10¹¹ solar masses, will evaporate in approximately 10¹⁰⁰ years—a googol years, a number whose very name suggests whimsy but whose magnitude defies comprehension. When they are gone, the Black Hole Era ends.
|
||||
|
||||
The universe that remains will be a dilute sea of particles: photons, neutrinos, and perhaps exotic dark matter—all at temperatures indistinguishable from absolute zero. Nothing will remain to generate energy, to create structure, to enable change. This is the Dark Era, and by every account it has no end.
|
||||
|
||||
The Loic Calendar does not follow it there. Its 512 bits run out at Annihilation, a little past 10¹⁰³ years, and the workspace README, section 4.7, explains why the calendar treats the last observation, rather than the last process, as the end worth marking. Beyond that coordinate there is nothing to observe, and the calendar, which is an observer, declines to pretend otherwise.",
|
||||
},
|
||||
}
|
||||
}
|
||||
3194
crates/loic-gui/src/main.rs
Normal file
188
crates/loic-gui/src/overlay.rs
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
//! The veil: the overlay renderer of the Loic Observatory.
|
||||
//!
|
||||
//! This module furnishes the means by which a translucent veil may be
|
||||
//! interposed between the observer and the temporal display — a gossamer
|
||||
//! shroud of configurable opacity, behind which the digits, bars, wheels
|
||||
//! and coordinates of the moment may be put aside while a passage about
|
||||
//! some other moment is read. The passage is Channel 1, content, a story;
|
||||
//! the veil is what permits a story to be told over a display whose every
|
||||
//! element insists on the present.
|
||||
//!
|
||||
//! The implementation is minimal to the point of asceticism: one
|
||||
//! fullscreen quad, six vertices conjured from their indices by the
|
||||
//! shader, rendered with alpha blending, whose sole purpose is to darken
|
||||
//! whatever lies beneath. No textures, no vertex buffer, no instancing;
|
||||
//! merely the primitive and one uniform colour. In the Observatory's frame
|
||||
//! what lies beneath the veil is the cleared void alone, since the display
|
||||
//! is not drawn while a passage is open, so the veil is in practice a second
|
||||
//! coat of the same darkness and its translucency is a capacity held in
|
||||
//! reserve. It is drawn all the same, because the frame is composed in
|
||||
//! layers and the veil is one of them.
|
||||
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
/// The layout of the overlay's uniform buffer: a single four-component
|
||||
/// vector holding the veil's red, green, blue and alpha, each between zero
|
||||
/// and one, in the `f32` the GPU insists upon and the calendar tolerates
|
||||
/// for a colour, since a colour was never a Planck count. `Pod` and
|
||||
/// `Zeroable` so that the bytes may be handed to the GPU as they lie.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct OverlayUniforms {
|
||||
/// The veil's colour and opacity, in that order: red, green, blue,
|
||||
/// alpha.
|
||||
color: [f32; 4],
|
||||
}
|
||||
|
||||
/// The veil's renderer: one pipeline with alpha blending, one bind group,
|
||||
/// one uniform buffer holding one colour. It draws a fullscreen
|
||||
/// semi-transparent quad over whatever the render pass has already drawn,
|
||||
/// leaving that visible in proportion to one minus the alpha and replacing
|
||||
/// the rest with the veil's own colour — which is to say it dims, in the
|
||||
/// exact arithmetic of blending, everything beneath it by the same amount.
|
||||
pub struct OverlayRenderer {
|
||||
/// The pipeline: the overlay shader's two stages, alpha blending against
|
||||
/// the surface format, a triangle list, and nothing else.
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
/// The bind group presenting the uniform buffer to the fragment stage.
|
||||
bind_group: wgpu::BindGroup,
|
||||
/// The uniform buffer holding the veil's colour, written once at
|
||||
/// creation and again whenever the colour is changed.
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
}
|
||||
|
||||
impl OverlayRenderer {
|
||||
/// Conjures the veil's renderer into existence for a given device and
|
||||
/// surface format: the uniform buffer with its default colour, the bind
|
||||
/// group layout and bind group that present it to the fragment stage,
|
||||
/// the pipeline layout, the shader, and the pipeline.
|
||||
///
|
||||
/// The default colour is the void's own — red 0.02, green 0.02, blue
|
||||
/// 0.03, the same three components the surface is cleared to — at an
|
||||
/// opacity of 85 percent: enough to render whatever lay beneath it
|
||||
/// illegible without obliterating it, had anything been drawn beneath
|
||||
/// it, which in the Observatory nothing is. The colour may be changed
|
||||
/// afterwards through `set_color`, a capacity offered and, at present,
|
||||
/// declined.
|
||||
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
|
||||
// The default veil: the void's colour at 85 percent, so that the veil
|
||||
// and the ground it covers are one darkness.
|
||||
let uniforms = OverlayUniforms {
|
||||
color: [0.02, 0.02, 0.03, 0.85],
|
||||
};
|
||||
|
||||
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("overlay_uniforms"),
|
||||
contents: bytemuck::cast_slice(&[uniforms]),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
// One binding, a uniform buffer, visible to the fragment stage
|
||||
// alone: the vertex stage needs no colour to place a quad.
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("overlay_bind_group_layout"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("overlay_bind_group"),
|
||||
layout: &bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
}],
|
||||
});
|
||||
|
||||
// The pipeline layout: that one bind group, and no immediate data.
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("overlay_pipeline_layout"),
|
||||
bind_group_layouts: &[Some(&bind_group_layout)],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
// The shader, compiled into the binary as text and handed to the
|
||||
// device as WGSL: a vertex stage that conjures the quad from vertex
|
||||
// indices, and a fragment stage that returns the uniform colour.
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("overlay_shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("shaders/overlay.wgsl").into()),
|
||||
});
|
||||
|
||||
// The pipeline: no vertex buffers, since the shader needs none; a
|
||||
// triangle list; alpha blending against the surface format, which
|
||||
// is the whole of what makes a veil a veil rather than a wall.
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("overlay_pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
Self {
|
||||
pipeline,
|
||||
bind_group,
|
||||
uniform_buffer,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the veil: one draw call of six vertices, two triangles, one
|
||||
/// instance, forming a quad that covers the whole of clip space from
|
||||
/// corner to corner. The quad is blended over the pass in the current
|
||||
/// uniform colour, which by default is the void's own at 85 percent
|
||||
/// opacity.
|
||||
///
|
||||
/// The veil belongs between the layers it separates: after whatever is
|
||||
/// to be dimmed and before whatever is to be read through it, which in
|
||||
/// the Observatory is the passage's text. Drawn last, it would dim the
|
||||
/// passage; drawn first, it would dim nothing but the void, which is
|
||||
/// what it does regardless, but on principle.
|
||||
pub fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
render_pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
render_pass.draw(0..6, 0..1);
|
||||
}
|
||||
|
||||
/// Rewrites the veil's colour: four components between zero and one,
|
||||
/// written to the uniform buffer through the queue and in effect from
|
||||
/// the next draw. Offered so that the default darkness might be made
|
||||
/// deeper or shallower should an observer's eyes require it; no caller
|
||||
/// has yet required it, and the allowance beneath says so to the lint.
|
||||
#[allow(dead_code)]
|
||||
pub fn set_color(&self, queue: &wgpu::Queue, r: f32, g: f32, b: f32, a: f32) {
|
||||
let uniforms = OverlayUniforms {
|
||||
color: [r, g, b, a],
|
||||
};
|
||||
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
|
||||
}
|
||||
}
|
||||
620
crates/loic-gui/src/renderer.rs
Normal file
|
|
@ -0,0 +1,620 @@
|
|||
//! # The Digit Waterfall
|
||||
//!
|
||||
//! The renderer of the Observatory's principal exhibit: the cosmic time —
|
||||
//! Planck times since Creation, sixty-one digits of it today and a hundred
|
||||
//! and fifty-four at Annihilation — poured across the top of the window as
|
||||
//! rows of glyphs, one instanced quad per digit, each quad a lookup into a
|
||||
//! glyph atlas baked once at start-up from a five-by-seven bitmap font.
|
||||
//! The last thirty-four digits are cyan and change with every observation,
|
||||
//! since 10^34 Planck times fit in a nanosecond and the nanosecond's
|
||||
//! interior is entropy; the nine before them are gray and change faster
|
||||
//! than a person can read; the rest are white and hold still for a second
|
||||
//! or more. Which is which is the shader's business and is stated in
|
||||
//! `digit.wgsl`; this module's business is where the digits go and how big
|
||||
//! they are.
|
||||
//!
|
||||
//! The atlas is never scaled. The quads that sample it are, by integer
|
||||
//! factors and with nearest filtering, so that each texel is a crisp block
|
||||
//! of pixels at every step of the font; and the font steps down — once, at
|
||||
//! the hundred-and-twenty-first digit, around the year 1.7 × 10^69 — only
|
||||
//! when the digits outgrow the reserve of height they have been granted,
|
||||
//! so that nothing below the waterfall ever moves. The main thread makes
|
||||
//! one observation per frame and hands its digits here. The observer
|
||||
//! threads, which make orders of magnitude more, hand over nothing; theirs
|
||||
//! are made to be discarded, and discarded they are.
|
||||
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
/// The most digits the storage buffer holds, and therefore the most the
|
||||
/// waterfall will ever draw: 256. The cosmic time is 61 digits today and
|
||||
/// 154 at Annihilation, after which there is no more cosmic time to have
|
||||
/// digits; 256 is the next power of two above that, a size a GPU finds
|
||||
/// congenial and an allocation of one kilobyte, which even a calendar this
|
||||
/// extravagant declines to call waste. A slice longer than this is
|
||||
/// truncated to it by `update_digits`, a condition no valid instant can
|
||||
/// produce and no invalid one is offered.
|
||||
const MAX_DIGITS: usize = 256;
|
||||
|
||||
/// One cell of the atlas, at the font's true resolution: a 5×7 glyph with
|
||||
/// one texel of air to its right and below, six texels wide and eight
|
||||
/// tall. The atlas is never scaled; the quads that sample it are, by
|
||||
/// integer factors, with nearest filtering, so that every texel becomes a
|
||||
/// crisp block of pixels at any step. Six by eight, times the step, is also
|
||||
/// the pitch of the digits on screen: the air is part of the cell, and a
|
||||
/// row of digits is a row of cells with no further spacing added.
|
||||
const CELL_WIDTH: u32 = 6;
|
||||
const CELL_HEIGHT: u32 = 8;
|
||||
|
||||
/// The atlas: ten glyphs, 0 through 9, in one row of cells, sixty texels
|
||||
/// wide and eight tall, a single-channel texture of four hundred and eighty
|
||||
/// bytes that describes every digit the age of the universe will ever
|
||||
/// need. The shader finds glyph `d` at `d` tenths of the atlas width, which
|
||||
/// is why the order of the cells is the order of the digits and why there
|
||||
/// is no eleventh.
|
||||
const ATLAS_WIDTH: u32 = CELL_WIDTH * 10;
|
||||
const ATLAS_HEIGHT: u32 = CELL_HEIGHT;
|
||||
|
||||
/// The waterfall's horizontal margin, in pixels, on both sides: forty, the
|
||||
/// same forty the timeline bar keeps from the window's edges in `main.rs`,
|
||||
/// so that the first digit and the bar's opening bracket share a column and
|
||||
/// the eye, travelling down, finds nothing to correct. The margin is
|
||||
/// subtracted twice from the window's width to get the width the rows may
|
||||
/// fill, and the rows fill it with whole cells and leave the remainder.
|
||||
const X_MARGIN: u32 = 40;
|
||||
|
||||
/// The waterfall's top edge, in pixels from the top of the window: sixty,
|
||||
/// which places the first row just below the single-line header — the
|
||||
/// count of observers, their observations per second and the renders per
|
||||
/// second — that `main.rs` centres at thirty. The digits begin where the
|
||||
/// accounting ends.
|
||||
const WATERFALL_TOP: u32 = 60;
|
||||
|
||||
/// The waterfall's reserve: the height, in pixels, that every step of the
|
||||
/// font must fit its rows into, so that nothing below it — the wheels, the
|
||||
/// bar, the cards — ever moves, however many digits the cosmic time grows
|
||||
/// to between now and Annihilation. One hundred and sixty pixels is four
|
||||
/// rows of the largest step, whose cell is forty pixels tall; the layout
|
||||
/// beneath is placed on the assumption that the waterfall ends here, and
|
||||
/// the test `every_count_fits_the_reserve` sees that it does for every
|
||||
/// digit count the buffer can hold.
|
||||
const WATERFALL_HEIGHT: u32 = 160;
|
||||
|
||||
/// The steps of the font, largest first, as integer multiples of the cell's
|
||||
/// width and height. (4, 5) is the 24×40 look the Observatory opens with;
|
||||
/// the rest are what the digits shrink to when the universe outgrows the
|
||||
/// reserve, each a whole number of pixels per texel so that no glyph is
|
||||
/// ever resampled into a blur. At 800 pixels wide, thirty cells of the
|
||||
/// first step fit a row and four rows fit the reserve, so the first step
|
||||
/// holds 120 digits; forty of the second fit a row and five rows the
|
||||
/// reserve, so the second holds 200. The calendar's last digit is the
|
||||
/// 154th. The shrink therefore happens once, when the cosmic time reaches
|
||||
/// 10^120 Planck times — around the year 1.7 × 10^69 — and then never
|
||||
/// again; the third step, and the two below it, exist for widths narrower
|
||||
/// than the window allows and for counts larger than the universe supplies.
|
||||
const SCALE_STEPS: [(u32, u32); 5] = [(4, 5), (3, 4), (2, 3), (2, 2), (1, 1)];
|
||||
|
||||
/// Elect the step of the font: the largest whose rows of `digit_count`
|
||||
/// digits fit the reserve at this width. The width between the margins is
|
||||
/// divided by the step's cell width to get the digits per row, at least
|
||||
/// one; the count is divided by that, rounding up, to get the rows; and
|
||||
/// the rows times the step's cell height must not exceed the reserve. The
|
||||
/// first step that passes is the answer, and the smallest step is the
|
||||
/// answer if none passes — a fallback that no digit count the buffer can
|
||||
/// hold reaches at any width the window is allowed, and that exists so
|
||||
/// that the function has an answer for every question rather than a
|
||||
/// question of its own. Integer arithmetic throughout: a step of the font
|
||||
/// is a count of pixels per texel, and a count is not a thing to
|
||||
/// approximate.
|
||||
fn glyph_scale_for(digit_count: u32, screen_width: u32) -> (u32, u32) {
|
||||
let content_width = screen_width.saturating_sub(2 * X_MARGIN).max(CELL_WIDTH);
|
||||
SCALE_STEPS
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|&(kx, ky)| {
|
||||
let per_row = (content_width / (CELL_WIDTH * kx)).max(1);
|
||||
let rows = digit_count.div_ceil(per_row);
|
||||
rows * CELL_HEIGHT * ky <= WATERFALL_HEIGHT
|
||||
})
|
||||
.unwrap_or(SCALE_STEPS[SCALE_STEPS.len() - 1])
|
||||
}
|
||||
|
||||
/// The uniform block the digit shader reads, laid out in C order to match
|
||||
/// the `Uniforms` struct in `digit.wgsl` field for field: two `vec2<f32>`
|
||||
/// for the screen and the glyph, one for the atlas, then the digit count,
|
||||
/// the vertical offset, the horizontal margin and the time — forty bytes,
|
||||
/// rewritten whenever the digits, the window or the clock change. The
|
||||
/// floats are the point of GPU submission, where the heresy is tolerated
|
||||
/// because the hardware speaks nothing else; all but the last of them are
|
||||
/// integers below 2^24, cast at the last moment and carried exactly, and
|
||||
/// the last is a count of seconds that drives a shimmer and touches no
|
||||
/// instant.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Uniforms {
|
||||
/// The surface's width and height in pixels, by which the shader
|
||||
/// divides to reach clip space, where the GPU insists on living.
|
||||
screen_size: [f32; 2],
|
||||
/// The width and height of one digit's quad in pixels: the cell times
|
||||
/// the step of the font in force.
|
||||
glyph_size: [f32; 2],
|
||||
/// The atlas's width and height in texels, sixty by eight, so that the
|
||||
/// shader can find a glyph's tenth of it.
|
||||
atlas_size: [f32; 2],
|
||||
/// How many digits to draw, and therefore how many instances; the
|
||||
/// shader also counts back from this to colour the zones.
|
||||
digit_count: u32,
|
||||
/// The waterfall's top, in pixels from the top of the window:
|
||||
/// `WATERFALL_TOP`, sixty, placing the first row just below the
|
||||
/// single-line header of observers and rates that `main.rs` centres at
|
||||
/// y=30.
|
||||
y_offset: u32,
|
||||
x_margin: u32, // the horizontal margin, in pixels: X_MARGIN, the bar's own forty
|
||||
time: f32, // seconds since this renderer was born, for the cyan zone's shimmer
|
||||
}
|
||||
|
||||
/// The digit renderer: the GPU state for the waterfall — a pipeline, the
|
||||
/// bind group that ties atlas, sampler, uniforms and digit buffer together,
|
||||
/// the two buffers themselves — and the little it remembers between frames:
|
||||
/// the glyph size in force, the surface's size, how many digits it was last
|
||||
/// handed, and when it was born, from which the shimmer takes its phase.
|
||||
/// One of these exists, owned by the render state, and it outlives every
|
||||
/// instant it has ever drawn.
|
||||
pub struct DigitRenderer {
|
||||
/// The render pipeline: `digit.wgsl`'s vertex and fragment stages, alpha
|
||||
/// blending, a triangle list, no culling and no depth.
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
/// The four bindings the shader reads: uniforms, atlas view, sampler,
|
||||
/// digits, in that order and at those indices.
|
||||
bind_group: wgpu::BindGroup,
|
||||
/// The forty-byte uniform block, rewritten by `update_uniforms`.
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
/// The storage buffer of digits, `MAX_DIGITS` of them as `u32`, of which
|
||||
/// the first `digit_count` are drawn and the rest are ignored.
|
||||
digit_buffer: wgpu::Buffer,
|
||||
/// The width and height of one digit's quad in pixels, at the step of
|
||||
/// the font in force; recomputed by `fit_glyphs` on every resize and
|
||||
/// every new set of digits.
|
||||
glyph_size: (f32, f32),
|
||||
/// The surface's width and height in pixels, as last reported by
|
||||
/// `resize`; the width decides how many cells a row holds.
|
||||
screen_size: (u32, u32),
|
||||
/// How many digits are being drawn, which is how many the cosmic time
|
||||
/// has: sixty-one today, and never fewer again.
|
||||
digit_count: u32,
|
||||
/// The instant this renderer was constructed, from which the `time`
|
||||
/// uniform is measured for the shimmer; a monotonic clock, not the
|
||||
/// calendar's, because the shimmer is decoration and decoration may be
|
||||
/// measured in seconds without heresy.
|
||||
start_time: std::time::Instant,
|
||||
}
|
||||
|
||||
impl DigitRenderer {
|
||||
/// Assemble the renderer: bake the atlas and upload it as an `R8Unorm`
|
||||
/// texture, make the sampler that reads it without interpolating, lay
|
||||
/// out the uniform block and the digit buffer, bind the four of them,
|
||||
/// compile the shader, and build the pipeline that draws six vertices
|
||||
/// per instance with alpha blending onto a surface of the given
|
||||
/// `format`. Until `resize` and `update_digits` have been called the
|
||||
/// renderer believes the surface is 800 by 800 and the count is zero,
|
||||
/// draws nothing, and is right to.
|
||||
#[allow(
|
||||
clippy::too_many_lines,
|
||||
reason = "the GPU's initiation is one linear rite — atlas, texture, sampler, buffers, bind group, pipeline — each step needing the last; five functions would be five places to read one paragraph"
|
||||
)]
|
||||
pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, format: wgpu::TextureFormat) -> Self {
|
||||
// Bake the atlas on the CPU from the bitmap font below: sixty by eight texels, a byte each.
|
||||
let atlas_data = generate_glyph_atlas();
|
||||
|
||||
// The atlas as a texture: one channel, eight bits, unsigned and
|
||||
// normalised, so that a lit texel samples as 1.0 and an unlit one as
|
||||
// 0.0, and the fragment shader can use the sample as alpha directly.
|
||||
let atlas_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("glyph_atlas"),
|
||||
size: wgpu::Extent3d {
|
||||
width: ATLAS_WIDTH,
|
||||
height: ATLAS_HEIGHT,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::R8Unorm,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
queue.write_texture(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &atlas_texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
&atlas_data,
|
||||
wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(ATLAS_WIDTH),
|
||||
rows_per_image: Some(ATLAS_HEIGHT),
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: ATLAS_WIDTH,
|
||||
height: ATLAS_HEIGHT,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
|
||||
let atlas_view = atlas_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
// Nearest filtering, both ways: the quads are integer multiples of
|
||||
// the cell, so each texel lands on a whole block of pixels and
|
||||
// nothing is blurred; clamped at the edges, because a lookup outside
|
||||
// the atlas has no digit to find.
|
||||
let atlas_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("glyph_sampler"),
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// The uniform block at its initial values: the largest step of the
|
||||
// font, a surface of 800 by 800 that the first resize will correct,
|
||||
// and no digits, which the first observation will correct.
|
||||
let (kx, ky) = SCALE_STEPS[0];
|
||||
let uniforms = Uniforms {
|
||||
screen_size: [800.0, 800.0],
|
||||
glyph_size: [(CELL_WIDTH * kx) as f32, (CELL_HEIGHT * ky) as f32],
|
||||
atlas_size: [ATLAS_WIDTH as f32, ATLAS_HEIGHT as f32],
|
||||
digit_count: 0, // no digits until the first observation is handed over
|
||||
y_offset: WATERFALL_TOP,
|
||||
x_margin: X_MARGIN,
|
||||
time: 0.0,
|
||||
};
|
||||
|
||||
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("uniforms"),
|
||||
contents: bytemuck::cast_slice(&[uniforms]),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
// The digit buffer, allocated at its largest once and never grown:
|
||||
// MAX_DIGITS u32s, of which the first digit_count are read by the
|
||||
// shader and the remainder wait for the universe to age into them.
|
||||
let digits = vec![0u32; MAX_DIGITS];
|
||||
let digit_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("digits"),
|
||||
contents: bytemuck::cast_slice(&digits),
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
// The layout of the four bindings: the uniform block for both
|
||||
// stages, the atlas and its sampler for the fragment stage, and the
|
||||
// digits, read-only, for the vertex stage, which is where a digit's
|
||||
// value becomes a glyph's column in the atlas.
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("digit_bind_group_layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
multisampled: false,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("digit_bind_group"),
|
||||
layout: &bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::TextureView(&atlas_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::Sampler(&atlas_sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 3,
|
||||
resource: digit_buffer.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// One bind group and no immediates: the pipeline's layout is the
|
||||
// layout above and nothing more.
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("digit_pipeline_layout"),
|
||||
bind_group_layouts: &[Some(&bind_group_layout)],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
// The shader, compiled here from source that was embedded in the
|
||||
// binary at build time; the Observatory carries its own shaders and
|
||||
// reads none from disk.
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("digit_shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("shaders/digit.wgsl").into()),
|
||||
});
|
||||
|
||||
// The pipeline: six vertices per instance as a triangle list, no
|
||||
// culling, alpha blending so that the air around a glyph shows the
|
||||
// void through it, and no depth, the waterfall being flat.
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("digit_pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: None,
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
Self {
|
||||
pipeline,
|
||||
bind_group,
|
||||
uniform_buffer,
|
||||
digit_buffer,
|
||||
glyph_size: ((CELL_WIDTH * kx) as f32, (CELL_HEIGHT * ky) as f32),
|
||||
screen_size: (800, 800),
|
||||
digit_count: 0,
|
||||
start_time: std::time::Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Elect the step of the font for the current digit count and width,
|
||||
/// and record the quad it implies: the cell times the step, in pixels,
|
||||
/// as the two floats the shader wants. Called on every resize and every
|
||||
/// observation; it is cheap, and it changes its answer once at start-up
|
||||
/// and once around the year 1.7 × 10^69.
|
||||
fn fit_glyphs(&mut self) {
|
||||
let (kx, ky) = glyph_scale_for(self.digit_count, self.screen_size.0);
|
||||
self.glyph_size = ((CELL_WIDTH * kx) as f32, (CELL_HEIGHT * ky) as f32);
|
||||
}
|
||||
|
||||
/// Take note of the surface's new size, refit the font to the width,
|
||||
/// and push the uniforms. The height is recorded and handed to the
|
||||
/// shader, which needs it to reach clip space; the width is what decides
|
||||
/// how many cells a row holds and therefore which step of the font the
|
||||
/// reserve can afford. The Observatory's window is not resizable, so
|
||||
/// this is called once, after construction, and then never; the
|
||||
/// function does not know that and would not care.
|
||||
pub fn resize(&mut self, queue: &wgpu::Queue, width: u32, height: u32) {
|
||||
self.screen_size = (width, height);
|
||||
self.fit_glyphs();
|
||||
self.update_uniforms(queue);
|
||||
}
|
||||
|
||||
/// Take the digits of a fresh observation — most significant first, as
|
||||
/// many as the cosmic time has — write them to the storage buffer, and
|
||||
/// refit the font to the count: the digits shrink by a step when the
|
||||
/// count outgrows the reserve, and not before, and never grow back,
|
||||
/// since the cosmic time does not. A slice longer than `MAX_DIGITS` is
|
||||
/// cut to that length, a courtesy no valid instant will ever call on.
|
||||
/// Called once per frame by the main thread with its one observation.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the clamped count does not fit a `u32`, which it always
|
||||
/// does, 256 being somewhat less than four billion; the `expect` is the
|
||||
/// conversion's receipt, not a live possibility.
|
||||
pub fn update_digits(&mut self, queue: &wgpu::Queue, digits: &[u32]) {
|
||||
let count = digits.len().min(MAX_DIGITS);
|
||||
self.digit_count = u32::try_from(count).expect("a count clamped to MAX_DIGITS fits a u32");
|
||||
self.fit_glyphs();
|
||||
|
||||
// The digits go to the GPU as they came, one u32 each, and only the first count of them.
|
||||
queue.write_buffer(
|
||||
&self.digit_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&digits[..count]),
|
||||
);
|
||||
|
||||
// And the uniforms follow, carrying the new count and the glyph size it implies.
|
||||
self.update_uniforms(queue);
|
||||
}
|
||||
|
||||
/// Write the uniform block afresh: the surface, the glyph, the atlas,
|
||||
/// the count, the two layout constants, and the seconds elapsed since
|
||||
/// the renderer was born, which is the phase of the shimmer. Called
|
||||
/// after every resize and every observation, so the time is refreshed
|
||||
/// as often as the digits are, and the shimmer advances at the frame
|
||||
/// rate, whatever that happens to be.
|
||||
fn update_uniforms(&self, queue: &wgpu::Queue) {
|
||||
let elapsed = self.start_time.elapsed().as_secs_f32();
|
||||
let uniforms = Uniforms {
|
||||
screen_size: [self.screen_size.0 as f32, self.screen_size.1 as f32],
|
||||
glyph_size: [self.glyph_size.0, self.glyph_size.1],
|
||||
atlas_size: [ATLAS_WIDTH as f32, ATLAS_HEIGHT as f32],
|
||||
digit_count: self.digit_count,
|
||||
y_offset: WATERFALL_TOP,
|
||||
x_margin: X_MARGIN,
|
||||
time: elapsed,
|
||||
};
|
||||
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
|
||||
}
|
||||
|
||||
/// Draw the waterfall into the frame's render pass: one draw call, six
|
||||
/// vertices per instance, one instance per digit, the pipeline and the
|
||||
/// bind group set for the occasion. Draws nothing at all when the count
|
||||
/// is zero — before the first observation has been handed over — rather
|
||||
/// than asking the GPU to draw zero instances and finding out what it
|
||||
/// thinks of that.
|
||||
pub fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
if self.digit_count == 0 {
|
||||
return;
|
||||
}
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
render_pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
render_pass.draw(0..6, 0..self.digit_count);
|
||||
}
|
||||
}
|
||||
|
||||
/// Bake the glyph atlas for the digits 0 through 9 at the font's true
|
||||
/// resolution: one 6×8 cell per digit, the 5×7 glyph in its top-left
|
||||
/// corner, one texel of air to the right and below, lit texels at 255 and
|
||||
/// the rest at zero, in a single row sixty texels wide. The font is the
|
||||
/// bitmap below and nothing fancier: ten glyphs of seven rows of five bits,
|
||||
/// the most significant bit the leftmost column, legible at any integer
|
||||
/// scale and at no other, which is why no other is ever used.
|
||||
fn generate_glyph_atlas() -> Vec<u8> {
|
||||
// The font: ten glyphs of seven rows, each row five bits, the most
|
||||
// significant bit at the left. Read them by squinting.
|
||||
const PATTERNS: [[u8; 7]; 10] = [
|
||||
// 0
|
||||
[
|
||||
0b01110, 0b10001, 0b10011, 0b10101, 0b11001, 0b10001, 0b01110,
|
||||
],
|
||||
// 1
|
||||
[
|
||||
0b00100, 0b01100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110,
|
||||
],
|
||||
// 2
|
||||
[
|
||||
0b01110, 0b10001, 0b00001, 0b00110, 0b01000, 0b10000, 0b11111,
|
||||
],
|
||||
// 3
|
||||
[
|
||||
0b01110, 0b10001, 0b00001, 0b00110, 0b00001, 0b10001, 0b01110,
|
||||
],
|
||||
// 4
|
||||
[
|
||||
0b00010, 0b00110, 0b01010, 0b10010, 0b11111, 0b00010, 0b00010,
|
||||
],
|
||||
// 5
|
||||
[
|
||||
0b11111, 0b10000, 0b11110, 0b00001, 0b00001, 0b10001, 0b01110,
|
||||
],
|
||||
// 6
|
||||
[
|
||||
0b00110, 0b01000, 0b10000, 0b11110, 0b10001, 0b10001, 0b01110,
|
||||
],
|
||||
// 7
|
||||
[
|
||||
0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b01000, 0b01000,
|
||||
],
|
||||
// 8
|
||||
[
|
||||
0b01110, 0b10001, 0b10001, 0b01110, 0b10001, 0b10001, 0b01110,
|
||||
],
|
||||
// 9
|
||||
[
|
||||
0b01110, 0b10001, 0b10001, 0b01111, 0b00001, 0b00010, 0b01100,
|
||||
],
|
||||
];
|
||||
|
||||
let mut atlas = vec![0u8; (ATLAS_WIDTH * ATLAS_HEIGHT) as usize];
|
||||
for (digit, pattern) in (0u32..).zip(PATTERNS.iter()) {
|
||||
let cell_x = digit * CELL_WIDTH;
|
||||
for (y, &bits) in (0u32..).zip(pattern.iter()) {
|
||||
for col in 0..5u32 {
|
||||
if (bits >> (4 - col)) & 1 == 1 {
|
||||
let x = cell_x + col;
|
||||
atlas[(y * ATLAS_WIDTH + x) as usize] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
atlas
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The font keeps its largest step through 120 digits, steps down once
|
||||
/// at the 121st, and that step carries the waterfall to the calendar's
|
||||
/// last digit, the 154th, and on to 200; the third step takes the rest
|
||||
/// of what the buffer can hold, and nothing falls through to the
|
||||
/// smallest step at the window's width, which is the fallback nobody
|
||||
/// is meant to see.
|
||||
#[test]
|
||||
fn the_digits_shrink_once_and_only_when_they_must() {
|
||||
assert_eq!(glyph_scale_for(61, 800), (4, 5));
|
||||
assert_eq!(glyph_scale_for(120, 800), (4, 5));
|
||||
assert_eq!(glyph_scale_for(121, 800), (3, 4));
|
||||
assert_eq!(glyph_scale_for(154, 800), (3, 4));
|
||||
assert_eq!(glyph_scale_for(200, 800), (3, 4));
|
||||
assert_eq!(glyph_scale_for(201, 800), (2, 3));
|
||||
let max = u32::try_from(MAX_DIGITS).expect("two hundred and fifty-six fits a u32");
|
||||
assert_eq!(glyph_scale_for(max, 800), (2, 3));
|
||||
}
|
||||
|
||||
/// Every step's rows fit the reserve for every count the buffer can
|
||||
/// hold, at the window's width: the promise `WATERFALL_HEIGHT` makes to
|
||||
/// the layout beneath it, checked for all 256 counts and not merely the
|
||||
/// interesting ones.
|
||||
#[test]
|
||||
fn every_count_fits_the_reserve() {
|
||||
let max = u32::try_from(MAX_DIGITS).expect("two hundred and fifty-six fits a u32");
|
||||
for count in 1..=max {
|
||||
let (kx, ky) = glyph_scale_for(count, 800);
|
||||
let per_row = (800 - 2 * X_MARGIN) / (CELL_WIDTH * kx);
|
||||
assert!(
|
||||
count.div_ceil(per_row) * CELL_HEIGHT * ky <= WATERFALL_HEIGHT,
|
||||
"{count} digits overflow the reserve at the step elected for them"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
84
crates/loic-gui/src/shaders/artifact.wgsl
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
// The scar shader of the Loic Observatory.
|
||||
//
|
||||
// Renders the topological scars that resolution collapse leaves on the
|
||||
// surface: the workspace README, section 4.5, for the scars and section
|
||||
// 7.3 for the collapse. One time in four, the random observer draws a mask
|
||||
// the lattice cannot hold; the question fails to frame, and what the
|
||||
// failure leaves is one of three figures, placed at random and never
|
||||
// removed while the process lives. The GIN gear: a row of twenty-one
|
||||
// cells whose raised teeth are the set bits of the letters G, I and N in
|
||||
// seven-bit ASCII, from the seam of the project's logo, at a random
|
||||
// rotation. The failed mask: a ring with the seven cosmic primes around
|
||||
// it as dots, filled where the entropy set them, and the phantom eighth
|
||||
// bit — the one that overflowed the pool — dangling outside the boundary
|
||||
// in red, on a thin line from where it would have gone. The soliton:
|
||||
// concentric amber rings fading outward, the project's reading of what a
|
||||
// severed relation leaves in the weave. Each is a Channel 2 record: not
|
||||
// what was asked, but that a question was attempted here and could not be
|
||||
// framed coherently.
|
||||
//
|
||||
// The shader knows none of this. It receives vertices already in pixel
|
||||
// coordinates, each carrying its own RGBA, triangulated by the renderer
|
||||
// into filled circles, annuli and rotated rectangles; it moves them into
|
||||
// clip space, with the vertical axis turned over because pixel space
|
||||
// hangs from the top-left corner and clip space grows upward from the
|
||||
// centre, and passes each colour through untouched. A scar is drawn last,
|
||||
// on top of everything, and blocks input on the pixels it covers; that
|
||||
// policy lives in the renderer and the hit-test, and this file is merely
|
||||
// where the scar becomes visible.
|
||||
|
||||
// The one uniform: the surface's extent in pixels, by which pixel
|
||||
// coordinates become clip coordinates. The renderer pads its buffer to
|
||||
// sixteen bytes for the GPU's sake; the shader reads the first eight.
|
||||
struct Uniforms {
|
||||
screen_size: vec2<f32>,
|
||||
};
|
||||
|
||||
// Bound for the vertex stage alone; the fragment stage has no use for the
|
||||
// surface's size, or for anything but the colour it is handed.
|
||||
@group(0) @binding(0)
|
||||
var<uniform> uniforms: Uniforms;
|
||||
|
||||
// One vertex as the renderer built it: a position in pixels and a colour
|
||||
// with its own alpha, straight from a vertex buffer that is rebuilt every
|
||||
// frame from the list of scars, one draw for all of them.
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
};
|
||||
|
||||
// What the vertex stage hands the fragment stage: the position in clip
|
||||
// space, and the colour, interpolated across each triangle, which for a
|
||||
// triangle of one colour changes nothing.
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) color: vec4<f32>,
|
||||
};
|
||||
|
||||
// Vertex stage: pixels into clip space, colour passed through. The scar's
|
||||
// geometry was decided on the CPU, in pixels, where the hit-test can see
|
||||
// it; the shader adds nothing to it and takes nothing away.
|
||||
@vertex
|
||||
fn vs_main(in: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
// Horizontal: pixels over width, doubled, less one — minus one at the
|
||||
// left edge, one at the right.
|
||||
let clip_x = (in.position.x / uniforms.screen_size.x) * 2.0 - 1.0;
|
||||
// Vertical: the same, then taken from one, because pixel Y grows
|
||||
// downward and clip Y grows upward. Flip.
|
||||
let clip_y = 1.0 - (in.position.y / uniforms.screen_size.y) * 2.0;
|
||||
out.position = vec4<f32>(clip_x, clip_y, 0.0, 1.0);
|
||||
out.color = in.color;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Fragment stage: the interpolated colour, as given. Which colour — the
|
||||
// white of the teeth, the dim gray of the boundary and of the unset
|
||||
// primes, the red of the phantom, the amber of the rings, each with its
|
||||
// own alpha — is the renderer's decision, and the shader has no view on
|
||||
// it. Alpha blending outside this file lays the scar over whatever it
|
||||
// covers, which is everything, since it is drawn last.
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return in.color;
|
||||
}
|
||||
258
crates/loic-gui/src/shaders/digit.wgsl
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
//
|
||||
// DIGIT SHADER
|
||||
//
|
||||
// The waterfall: the cosmic time — Planck times elapsed since Creation,
|
||||
// sixty-one digits at present and one hundred and fifty-four at
|
||||
// Annihilation, where 512 bits run out — set as one textured quad per
|
||||
// digit, most significant first, wrapping within the margins. Six
|
||||
// vertices per digit are conjured from the vertex index; there is no
|
||||
// vertex buffer. The digit values arrive in a storage buffer and the ten
|
||||
// glyphs in an atlas of ten six-by-eight cells, sampled at whole-number
|
||||
// magnifications with nearest filtering, so that every texel is a crisp
|
||||
// block and nothing is ever interpolated into being.
|
||||
//
|
||||
// Zones are counted from the last digit, because that is where time
|
||||
// moves, and they stay where they are as the count grows:
|
||||
// Zone C, the last 34 digits: cyan — the sub-nanosecond entropy,
|
||||
// fresh with every observation
|
||||
// Zone B, the 9 before them: gray — nanosecond to second, faster
|
||||
// than perception
|
||||
// Zone A, everything above: white — changes slower than once a second
|
||||
//
|
||||
// The arithmetic: a nanosecond holds about 1.855 × 10^34 Planck times
|
||||
// and a second about 1.855 × 10^43, so 10^34 of them fit in a nanosecond
|
||||
// and 10^43 in a second. The last thirty-four digits therefore turn over
|
||||
// entirely between one nanosecond and the next, beneath anything the
|
||||
// kernel's clock can report; what they show is the entropy the library
|
||||
// draws for the sub-nanosecond remainder, a different answer at every
|
||||
// observation, and the shader colours them as what they are. The nine
|
||||
// above them change within a second and are shown as blur. Everything
|
||||
// above those can be read. The digit count grows with the age of the
|
||||
// universe; the zones do not. The present moment is a gradient, not a
|
||||
// point — the workspace README, section 1.3 — and this is the gradient,
|
||||
// rendered.
|
||||
//
|
||||
// Every number in this file is an f32, because the GPU speaks nothing
|
||||
// else and because nothing computed here is ever read back as time: it
|
||||
// is a pixel or it is nothing, which is one of the two exemptions the
|
||||
// calendar grants, the other being the hum.
|
||||
//
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// What the renderer tells both stages, once per frame: the surface, the
|
||||
// cell as magnified this frame, the atlas, how many digits there are and
|
||||
// where they go, and the clock for the shimmer. Bound at group zero,
|
||||
// binding zero, and visible to the fragment stage as well as the vertex
|
||||
// stage, because the shimmer needs the clock.
|
||||
struct Uniforms {
|
||||
screen_size: vec2<f32>,
|
||||
glyph_size: vec2<f32>,
|
||||
atlas_size: vec2<f32>,
|
||||
digit_count: u32,
|
||||
y_offset: u32, // Top of the first row, in pixels: sixty, just below the header
|
||||
x_margin: u32, // Air at both sides, in pixels: forty, as the timeline's bar keeps
|
||||
time: f32, // Seconds since the renderer was born, as an f32; for the shimmer only
|
||||
}
|
||||
|
||||
// What the vertex stage hands the fragment stage, interpolated across the
|
||||
// quad: the position, the texture coordinate, the zone's colour, and how
|
||||
// deep into the entropy this digit lies.
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
@location(2) glow_intensity: f32, // zero outside zone C, one at the last digit
|
||||
}
|
||||
|
||||
// The four bindings: the uniforms for both stages, the atlas and its
|
||||
// nearest-filtering sampler for the fragment stage, and the digits, most
|
||||
// significant first, for the vertex stage alone.
|
||||
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
||||
@group(0) @binding(1) var glyph_texture: texture_2d<f32>;
|
||||
@group(0) @binding(2) var glyph_sampler: sampler;
|
||||
@group(0) @binding(3) var<storage, read> digits: array<u32>;
|
||||
|
||||
// The three colours of the zones. The cyan is the Observatory's own, the
|
||||
// one the text renderer packs as #33E6CC, to within a rounding of the
|
||||
// green.
|
||||
const COLOR_A: vec3<f32> = vec3<f32>(1.0, 1.0, 1.0); // white: slower than a second
|
||||
const COLOR_B: vec3<f32> = vec3<f32>(0.5, 0.5, 0.5); // gray: within a second, blur
|
||||
const COLOR_C: vec3<f32> = vec3<f32>(0.2, 0.9, 0.8); // cyan: within a nanosecond, entropy
|
||||
|
||||
// Zone C: the last thirty-four digits. 10^34 Planck times fit in a
|
||||
// nanosecond, and a nanosecond is the finest thing the clock reports, so
|
||||
// every one of these digits is the library's entropy, drawn afresh for
|
||||
// each observation and owed to no clock at all.
|
||||
const QUANTUM_DIGITS: u32 = 34u;
|
||||
// Zone B: the nine digits before those, from a nanosecond up to a second,
|
||||
// 10^43 over 10^34. They change faster than an eye can follow and slower
|
||||
// than the entropy does, and are coloured accordingly.
|
||||
const BLUR_DIGITS: u32 = 9u;
|
||||
|
||||
// The distance of a digit from the last one: zero for the last digit
|
||||
// itself, the count less one for the first. The zones are measured in
|
||||
// this and never in the index from the front, which is why the count may
|
||||
// grow without the colours moving. Unsigned arithmetic, so it is only
|
||||
// ever asked about an index the draw call actually issued.
|
||||
fn digits_from_end(digit_index: u32) -> u32 {
|
||||
return uniforms.digit_count - 1u - digit_index;
|
||||
}
|
||||
|
||||
// The colour of a digit by its distance from the end. Inside zone C it
|
||||
// runs from the gray of zone B at the zone's first digit to full cyan at
|
||||
// the last; inside zone B, from the white of zone A at its first digit to
|
||||
// gray at its last; above both, white. Two ramps with no step between
|
||||
// them, so that the eye reads one gradient rather than three bands, which
|
||||
// is what the present moment is.
|
||||
fn get_zone_color(digit_index: u32) -> vec3<f32> {
|
||||
let from_end = digits_from_end(digit_index);
|
||||
if (from_end < QUANTUM_DIGITS) {
|
||||
// All but gray at the zone's first digit, cyan at the last.
|
||||
let t = 1.0 - f32(from_end) / f32(QUANTUM_DIGITS);
|
||||
return mix(COLOR_B, COLOR_C, t);
|
||||
} else if (from_end < QUANTUM_DIGITS + BLUR_DIGITS) {
|
||||
// All but white at the zone's first digit, gray at its last.
|
||||
let t = 1.0 - f32(from_end - QUANTUM_DIGITS) / f32(BLUR_DIGITS);
|
||||
return mix(COLOR_A, COLOR_B, t);
|
||||
}
|
||||
return COLOR_A;
|
||||
}
|
||||
|
||||
// Vertex stage: one call per corner per digit, six per instance. It
|
||||
// places the digit's quad on the surface, selects the digit's cell in the
|
||||
// atlas, and decides the digit's colour and glow by its distance from the
|
||||
// end, all of which the fragment stage receives interpolated, though only
|
||||
// the texture coordinate actually varies across a quad.
|
||||
@vertex
|
||||
fn vs_main(
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
@builtin(instance_index) instance_index: u32,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
// The quad: two triangles, six vertices, drawn from the vertex index
|
||||
// alone in the unit square, which the cell's size and position will
|
||||
// stretch into place. The corners, and the order the triangles take
|
||||
// them in:
|
||||
// 0--1 Triangles: 0,1,2 and 2,1,3
|
||||
// |\ |
|
||||
// | \|
|
||||
// 2--3
|
||||
let quad_uvs = array<vec2<f32>, 6>(
|
||||
vec2<f32>(0.0, 0.0), // 0, top-left
|
||||
vec2<f32>(1.0, 0.0), // 1, top-right
|
||||
vec2<f32>(0.0, 1.0), // 2, bottom-left
|
||||
vec2<f32>(0.0, 1.0), // 2, again
|
||||
vec2<f32>(1.0, 0.0), // 1, again
|
||||
vec2<f32>(1.0, 1.0), // 3, bottom-right
|
||||
);
|
||||
|
||||
let quad_uv = quad_uvs[vertex_index];
|
||||
|
||||
// This instance's digit, from the storage buffer, most significant
|
||||
// first; the vertex stage is the one stage permitted to read it.
|
||||
let digit_value = digits[instance_index];
|
||||
|
||||
// Where on the surface this digit lives: the rows wrap within the
|
||||
// margins, as many digits to a row as whole cells fit the content
|
||||
// width, and the arithmetic is integer until the pixel.
|
||||
let margin = f32(uniforms.x_margin);
|
||||
let content_width = uniforms.screen_size.x - 2.0 * margin;
|
||||
let chars_per_row = u32(content_width / uniforms.glyph_size.x);
|
||||
let row = instance_index / chars_per_row;
|
||||
let col = instance_index % chars_per_row;
|
||||
|
||||
let base_x = margin + f32(col) * uniforms.glyph_size.x;
|
||||
let base_y = f32(row) * uniforms.glyph_size.y + f32(uniforms.y_offset);
|
||||
|
||||
// The vertex, in pixels: the cell's corner plus the quad's unit
|
||||
// coordinate scaled to the cell's magnified size.
|
||||
let pixel_pos = vec2<f32>(
|
||||
base_x + quad_uv.x * uniforms.glyph_size.x,
|
||||
base_y + quad_uv.y * uniforms.glyph_size.y
|
||||
);
|
||||
|
||||
// Into clip space, minus one to one on both axes, the vertical one
|
||||
// turned over, because pixel space grows downward and clip space grows
|
||||
// upward, and neither will yield.
|
||||
let clip_pos = vec2<f32>(
|
||||
(pixel_pos.x / uniforms.screen_size.x) * 2.0 - 1.0,
|
||||
1.0 - (pixel_pos.y / uniforms.screen_size.y) * 2.0 // the turning over
|
||||
);
|
||||
|
||||
out.position = vec4<f32>(clip_pos, 0.0, 1.0);
|
||||
|
||||
// The texture coordinate. The atlas is ten cells in one row, so a
|
||||
// digit's cell is a tenth of the width, and the whole cell — five
|
||||
// columns of ink and one of air, eight rows with the last of them air —
|
||||
// is mapped onto the whole quad, air included, which is what keeps the
|
||||
// digits apart.
|
||||
let glyph_width = uniforms.atlas_size.x / 10.0;
|
||||
let u_start = f32(digit_value) * glyph_width / uniforms.atlas_size.x;
|
||||
let u_end = u_start + glyph_width / uniforms.atlas_size.x;
|
||||
|
||||
out.uv = vec2<f32>(
|
||||
mix(u_start, u_end, quad_uv.x),
|
||||
quad_uv.y
|
||||
);
|
||||
|
||||
// The zone's colour, by distance from the end, opaque.
|
||||
out.color = vec4<f32>(get_zone_color(instance_index), 1.0);
|
||||
|
||||
// The glow: zero outside zone C, rising through it to one at the last
|
||||
// digit, and carried to the fragment stage for the shimmer.
|
||||
let from_end = digits_from_end(instance_index);
|
||||
if (from_end < QUANTUM_DIGITS) {
|
||||
out.glow_intensity = 1.0 - f32(from_end) / f32(QUANTUM_DIGITS);
|
||||
} else {
|
||||
out.glow_intensity = 0.0;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// Fragment stage: the zone's colour at the atlas's coverage, and for zone
|
||||
// C a shimmer on top, since digits that are entropy should not sit still.
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// Coverage from the atlas's one channel, taken nearest, which is the
|
||||
// pixel's alpha: the air around a glyph is transparent, and the ink is
|
||||
// as opaque as ink.
|
||||
let alpha = textureSample(glyph_texture, glyph_sampler, in.uv).r;
|
||||
|
||||
// The shimmer, for zone C alone: the entropy digits glow, and the glow
|
||||
// breathes.
|
||||
if (in.glow_intensity > 0.0) {
|
||||
// Three sines at three angular frequencies — three, seven and
|
||||
// eleven radians per second — each digit shifted in phase by its
|
||||
// depth in the zone, so that the glow travels along the digits
|
||||
// rather than pulsing in unison. Three, seven and eleven are also
|
||||
// three of the five prime factors of the Unix offset, 5,419,722; no
|
||||
// mask is stated for that observation, so by the workspace README,
|
||||
// section 0, it is Channel 1, and it is recorded here only so that
|
||||
// nobody discovers it later.
|
||||
let phase_offset = in.glow_intensity * 6.28318; // one full turn, 2π, across the zone
|
||||
let wave1 = sin(uniforms.time * 3.0 + phase_offset) * 0.5 + 0.5;
|
||||
let wave2 = sin(uniforms.time * 7.0 + phase_offset * 1.7) * 0.5 + 0.5;
|
||||
let wave3 = sin(uniforms.time * 11.0 + phase_offset * 2.3) * 0.5 + 0.5;
|
||||
|
||||
// The three waves, weighted to sum to one, so that the shimmer lies
|
||||
// between zero and one and the weights say which wave leads.
|
||||
let shimmer = wave1 * 0.5 + wave2 * 0.3 + wave3 * 0.2;
|
||||
|
||||
// Deeper into the zone, stronger: the last digit may brighten by
|
||||
// forty percent, the zone's first by almost nothing.
|
||||
let glow_strength = in.glow_intensity * 0.4; // at most a forty percent boost
|
||||
let brightness_boost = 1.0 + shimmer * glow_strength;
|
||||
|
||||
// Brightened, and clamped at one per channel; the target would
|
||||
// clamp it anyway, but the shader prefers to say so itself.
|
||||
let glowing_color = min(in.color.rgb * brightness_boost, vec3<f32>(1.0, 1.0, 1.0));
|
||||
|
||||
return vec4<f32>(glowing_color, alpha);
|
||||
}
|
||||
|
||||
// Zones A and B: the colour, unshimmering, at the coverage.
|
||||
return vec4<f32>(in.color.rgb, alpha);
|
||||
}
|
||||
70
crates/loic-gui/src/shaders/overlay.wgsl
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// The veil of the Loic Observatory.
|
||||
//
|
||||
// This shader concerns itself with one task, monumental in the way that
|
||||
// only the simplest tasks are: drawing a rectangle. Not any rectangle — a
|
||||
// rectangle the size of the surface, in one colour at one alpha, blended
|
||||
// over whatever the pass has already drawn, so that a passage may be read
|
||||
// against a dimmed field rather than against the temporal cacophony. It
|
||||
// has no texture, no vertex buffer, no attributes and no opinions; it is
|
||||
// given a colour and it returns the colour, for every fragment, without
|
||||
// looking.
|
||||
//
|
||||
// The colour it is given, unless someone asks otherwise, is the surface's
|
||||
// own clear colour at eighty-five percent alpha: a shroud the exact shade
|
||||
// of the void behind it. And in the Observatory's composition, while a
|
||||
// passage is open the waterfall, the panels and the scars are not drawn at
|
||||
// all, so the veil is blended over the cleared void alone and dims a field
|
||||
// that was never lit. The dimming is real. Its subject is absent. The
|
||||
// shader is not told this, and would not change its behaviour if it were:
|
||||
// the rectangle is drawn, at the alpha requested, with perfect fidelity to
|
||||
// an instruction whose consequence is invisible, which is a fate the
|
||||
// calendar recognises.
|
||||
|
||||
// All the vertex stage has to say: a position. There is no colour to
|
||||
// carry and no coordinate to sample by; the fragment stage has the
|
||||
// uniform, and needs nothing from the vertex but where it is.
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
};
|
||||
|
||||
// The one uniform: the veil's colour — red, green, blue and alpha, each
|
||||
// from zero to one — bound for the fragment stage alone. A humble vec4
|
||||
// bearing the entire weight of the shader's output, since the output is
|
||||
// this and nothing else.
|
||||
@group(0) @binding(0)
|
||||
var<uniform> overlay_color: vec4<f32>;
|
||||
|
||||
// Vertex stage: six positions drawn from the vertex index alone, no
|
||||
// buffer consulted — the corners of clip space, minus one to one on both
|
||||
// axes, as two triangles: bottom-left, bottom-right, top-left; then
|
||||
// top-left, bottom-right, top-right. The pair covers the surface exactly,
|
||||
// whatever its size, because clip space is the same size on every
|
||||
// surface, which is the one convenience the GPU offers without being
|
||||
// asked.
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||
// The corners, in clip space, where up is up and no turning over is
|
||||
// owed.
|
||||
var positions = array<vec2<f32>, 6>(
|
||||
vec2<f32>(-1.0, -1.0), // bottom-left
|
||||
vec2<f32>( 1.0, -1.0), // bottom-right
|
||||
vec2<f32>(-1.0, 1.0), // top-left, closing the first triangle
|
||||
vec2<f32>(-1.0, 1.0), // top-left again, opening the second
|
||||
vec2<f32>( 1.0, -1.0), // bottom-right again
|
||||
vec2<f32>( 1.0, 1.0), // top-right
|
||||
);
|
||||
|
||||
var output: VertexOutput;
|
||||
output.position = vec4<f32>(positions[vertex_index], 0.0, 1.0);
|
||||
return output;
|
||||
}
|
||||
|
||||
// Fragment stage: the uniform colour, for every fragment, unconditionally.
|
||||
// No sampling, no arithmetic, no cleverness; the blend state outside this
|
||||
// file does the dimming, weighting by the alpha this colour carries, and
|
||||
// the shader's whole contribution is to hold one opinion about one colour
|
||||
// across the entire surface.
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return overlay_color;
|
||||
}
|
||||
140
crates/loic-gui/src/shaders/text.wgsl
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
//
|
||||
// TEXT SHADER
|
||||
//
|
||||
// The lettering of the Observatory: every run of characters, set as one
|
||||
// textured quad per character from a grayscale atlas of one hundred and
|
||||
// twelve cells, sixteen to a row, seven rows. Each instance is sixteen
|
||||
// bytes from a storage buffer — a position in pixels, a cell index and a
|
||||
// packed colour — and each becomes six vertices conjured from the vertex
|
||||
// index, since there is no vertex buffer and none is missed. The atlas
|
||||
// is sampled linearly at exactly one texel per pixel, which is a copy
|
||||
// when the position is whole and a half-pixel smear when it is not.
|
||||
//
|
||||
// The floating point is the GPU's, not the calendar's: nothing computed
|
||||
// here is a quantity of time, and nothing leaves but a pixel.
|
||||
//
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// What the renderer tells the vertex stage, once per resize: the surface's
|
||||
// extent, the cell's, and the atlas's grid. Twenty-four bytes, the mirror
|
||||
// of the renderer's own `Uniforms`, and the fragment stage never sees them.
|
||||
struct Uniforms {
|
||||
screen_size: vec2<f32>,
|
||||
glyph_size: vec2<f32>,
|
||||
atlas_cols: u32,
|
||||
atlas_rows: u32,
|
||||
}
|
||||
|
||||
// One character, sixteen bytes, exactly as the renderer packed it.
|
||||
struct CharInstance {
|
||||
pos: vec2<f32>, // The cell's top-left, in pixels from the surface's top-left
|
||||
glyph_index: u32, // The cell in the atlas, 0 through 111
|
||||
color: u32, // RGBA, one byte each, red in the high byte
|
||||
}
|
||||
|
||||
// What the vertex stage hands the fragment stage: the position, the
|
||||
// texture coordinate, and the colour, the last of which is the same at all
|
||||
// four corners and so survives interpolation unchanged.
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
}
|
||||
|
||||
// The four bindings: the uniforms for the vertex stage, the atlas and its
|
||||
// linear sampler for the fragment stage, and the instances for the vertex
|
||||
// stage.
|
||||
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
||||
@group(0) @binding(1) var glyph_texture: texture_2d<f32>;
|
||||
@group(0) @binding(2) var glyph_sampler: sampler;
|
||||
@group(0) @binding(3) var<storage, read> chars: array<CharInstance>;
|
||||
|
||||
// The packed colour undone: three shifts and four masks, each byte scaled
|
||||
// to the interval from zero to one. Red was packed highest and alpha
|
||||
// lowest, and the order here is the same order, or every colour on the
|
||||
// surface would be some other colour.
|
||||
fn unpack_color(packed: u32) -> vec4<f32> {
|
||||
let r = f32((packed >> 24u) & 0xFFu) / 255.0;
|
||||
let g = f32((packed >> 16u) & 0xFFu) / 255.0;
|
||||
let b = f32((packed >> 8u) & 0xFFu) / 255.0;
|
||||
let a = f32(packed & 0xFFu) / 255.0;
|
||||
return vec4<f32>(r, g, b, a);
|
||||
}
|
||||
|
||||
// Vertex stage: one call per corner per character, six per instance. It
|
||||
// places the character's quad on the surface, finds its cell in the atlas
|
||||
// by a modulus and a quotient, and unpacks its colour.
|
||||
@vertex
|
||||
fn vs_main(
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
@builtin(instance_index) instance_index: u32,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
// This instance: the character's position, cell and colour.
|
||||
let char_data = chars[instance_index];
|
||||
|
||||
// The quad: two triangles in the unit square, six vertices from the
|
||||
// vertex index, corners 0,1,2 and then 2,1,3.
|
||||
let quad_uvs = array<vec2<f32>, 6>(
|
||||
vec2<f32>(0.0, 0.0),
|
||||
vec2<f32>(1.0, 0.0),
|
||||
vec2<f32>(0.0, 1.0),
|
||||
vec2<f32>(0.0, 1.0),
|
||||
vec2<f32>(1.0, 0.0),
|
||||
vec2<f32>(1.0, 1.0),
|
||||
);
|
||||
|
||||
let quad_uv = quad_uvs[vertex_index];
|
||||
|
||||
// The vertex, in pixels: the cell's corner plus the unit coordinate
|
||||
// scaled to the cell, which is the atlas cell's own size, always.
|
||||
let pixel_pos = vec2<f32>(
|
||||
char_data.pos.x + quad_uv.x * uniforms.glyph_size.x,
|
||||
char_data.pos.y + quad_uv.y * uniforms.glyph_size.y
|
||||
);
|
||||
|
||||
// Into clip space, minus one to one, the vertical axis turned over,
|
||||
// because pixels grow downward and clip space grows upward.
|
||||
let clip_pos = vec2<f32>(
|
||||
(pixel_pos.x / uniforms.screen_size.x) * 2.0 - 1.0,
|
||||
1.0 - (pixel_pos.y / uniforms.screen_size.y) * 2.0
|
||||
);
|
||||
|
||||
out.position = vec4<f32>(clip_pos, 0.0, 1.0);
|
||||
|
||||
// The texture coordinate: the cell's column is the index modulo the
|
||||
// column count and its row the quotient, and each cell is one
|
||||
// sixteenth of the atlas across and one seventh of it down.
|
||||
let col = char_data.glyph_index % uniforms.atlas_cols;
|
||||
let row = char_data.glyph_index / uniforms.atlas_cols;
|
||||
let u_size = 1.0 / f32(uniforms.atlas_cols);
|
||||
let v_size = 1.0 / f32(uniforms.atlas_rows);
|
||||
|
||||
let u_start = f32(col) * u_size;
|
||||
let v_start = f32(row) * v_size;
|
||||
|
||||
out.uv = vec2<f32>(
|
||||
u_start + quad_uv.x * u_size,
|
||||
v_start + quad_uv.y * v_size
|
||||
);
|
||||
|
||||
// The colour, unpacked once per vertex; the four corners agree, so the
|
||||
// interpolation between them has nothing to do.
|
||||
out.color = unpack_color(char_data.color);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// Fragment stage: the atlas's coverage, sampled linearly from its single
|
||||
// channel, multiplied into the colour's own alpha, so that a glyph's ink
|
||||
// is as opaque as its colour says and its air is transparent whatever the
|
||||
// colour says. The colour is not sampled from anything; it arrives with
|
||||
// the instance and is applied to the coverage as ink to a stencil, which
|
||||
// is exactly what a bitmap font is.
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let alpha = textureSample(glyph_texture, glyph_sampler, in.uv).r;
|
||||
return vec4<f32>(in.color.rgb, in.color.a * alpha);
|
||||
}
|
||||
1176
crates/loic-gui/src/text.rs
Normal file
1348
crates/loic-gui/src/timeline.rs
Normal file
64
crates/loic/Cargo.toml
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# The core library: the lattice, the masks, the instants, the durations and
|
||||
# the cosmological constants, published to the registry under the
|
||||
# workspace's one version. Everything else in the workspace is a way of
|
||||
# looking at what this crate computes. The description, keywords and
|
||||
# categories are what the registry shows; the readme is what it renders
|
||||
# beneath them.
|
||||
[package]
|
||||
name = "loic"
|
||||
version.workspace = true
|
||||
edition = "2024"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
license-file.workspace = true
|
||||
description = "A temporal framework for the network age: 512-bit integer Planck times since the first ARPANET transmission, subdivided by the fifteen prime factors of two cosmological constants, observed through masks, and never once approximated"
|
||||
keywords = ["planck", "arpanet", "prime-lattice", "chronometric-heresy", "anti-ieee754"]
|
||||
categories = ["date-and-time", "mathematics", "science", "os::linux-apis"]
|
||||
readme = "README.md"
|
||||
|
||||
# What the library stands on. Nothing here touches a floating-point number
|
||||
# on the library's behalf, and each is present for one reason, stated.
|
||||
[dependencies]
|
||||
# 512-bit and 1024-bit integers — `I512`, `U512` and `U1024` — with `const`
|
||||
# arithmetic, the `n!` literal macro and the `As` cast trait: the entire
|
||||
# numeric substrate of the calendar. Everything the library adds, subtracts,
|
||||
# multiplies or divides at cosmological width, it does through these types,
|
||||
# and the release profile's optimisation level for dependencies is where
|
||||
# their speed is decided.
|
||||
bnum = "0.14"
|
||||
# The derive behind `LoicError`, so that each way the lattice can decline a
|
||||
# request carries its message in voice and implements the standard error
|
||||
# trait without a line of boilerplate to dilute it.
|
||||
thiserror = "2.0"
|
||||
# Serialization, optional: the `serde` feature below turns it on, together
|
||||
# with `bnum`'s own support, so that an instant can leave the process as
|
||||
# digits and return as the same digits, with `derive` within reach.
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
# The kernel's own interfaces: `clock_gettime` on `CLOCK_TAI`, which the
|
||||
# standard library does not expose, and `adjtimex(2)`, through which the TAI
|
||||
# lie detector reads the offset the kernel has or has not been told.
|
||||
libc = "0.2"
|
||||
# The seed of every thread's entropy stream, drawn once from the kernel's
|
||||
# pool; re-exported, so that the oracle can flood its terminal from the same
|
||||
# pool during resolution collapse.
|
||||
getrandom = "0.4"
|
||||
|
||||
# What the tests stand on and the library does not; nothing here reaches a
|
||||
# consumer of the crate.
|
||||
[dev-dependencies]
|
||||
# JSON, for the tests alone: a serialization a test can read back with its
|
||||
# own eyes when the `serde` feature is under examination.
|
||||
serde_json = "1.0"
|
||||
|
||||
# Nothing is on by default. `serde` is opt-in, and enabling it enables
|
||||
# `bnum`'s serde support alongside, since an instant's digits are `bnum`'s
|
||||
# digits and would otherwise have no way to leave the process.
|
||||
[features]
|
||||
default = []
|
||||
serde = ["dep:serde", "bnum/serde"]
|
||||
|
||||
# The library target, named and placed explicitly rather than inferred, so
|
||||
# that nothing about where the calendar lives is left to convention.
|
||||
[lib]
|
||||
name = "loic"
|
||||
path = "src/lib.rs"
|
||||
99
crates/loic/README.md
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
# loic
|
||||
|
||||
The core library for the Loic Calendar—a temporal framework measuring time from the first ARPANET transmission with Planck-scale precision.
|
||||
|
||||
## Overview
|
||||
|
||||
This library provides:
|
||||
|
||||
- **`LoicInstant`** — A moment in time, represented as a 512-bit signed integer counting Planck times (~5.39 x 10^-44 seconds) since the epoch
|
||||
- **`LoicDuration`** — A span of time in Planck units
|
||||
- **`Mask`** — An observer configuration selecting active primes from the 15-prime lattice
|
||||
- **`GridCoord`** — A position in the 128x256 asymmetric lattice (7 cosmic bits x 8 cycle bits)
|
||||
- **`SubdivisionResult`** — The result of observing an instant through a mask
|
||||
- **Cosmological coordinates** — Your position between Creation (Big Bang) and Annihilation (heat death)
|
||||
|
||||
## Usage
|
||||
|
||||
```rust
|
||||
use loic::{LoicInstant, Mask};
|
||||
|
||||
// Observe the current moment
|
||||
let now = LoicInstant::now();
|
||||
|
||||
// Raw Planck timestamp
|
||||
println!("{}", now.planck());
|
||||
|
||||
// Cosmological inquiry
|
||||
println!("Since Creation: {}", now.cosmic_time());
|
||||
println!("Until Annihilation: {}", now.terminal_time());
|
||||
|
||||
// Lattice position
|
||||
let coord = now.grid_coord();
|
||||
println!("Grid: ({}, {})", coord.cosmic(), coord.cycle());
|
||||
|
||||
// Observe through a mask (the mask is the observer)
|
||||
let mask = Mask::dyad(); // The binary split — cycle prime 2
|
||||
let observation = mask.observe(&now).unwrap();
|
||||
println!("Position: {}", observation.position());
|
||||
```
|
||||
|
||||
## The Lattice
|
||||
|
||||
15 prime factors (7 cosmic, 8 cycle) generate 32,768 temporal subdivisions. A mask selects which primes are active. The product of active primes determines the divisor. Any instant can be observed at any mask's resolution.
|
||||
|
||||
### Cosmic Primes (7 — named for logic gates)
|
||||
|
||||
| Name | Gate | Prime |
|
||||
|------|------|-------|
|
||||
| Andyte | AND | 16,631 |
|
||||
| Xoryte | XOR | 57,251 |
|
||||
| Oryte | OR | 21,325,891 |
|
||||
| Noryte | NOR | 3,014,283,645,143 |
|
||||
| Xnoryte | XNOR | 128,029,476,410,837,813,209,853,261 |
|
||||
| Notyte | NOT | 298,824,857,074,158,001,946,609,249 |
|
||||
| Nandyte | NAND | 2,862,914,325,674,107...999,805,373 |
|
||||
|
||||
### Cycle Primes (8 — axes of temporal subdivision)
|
||||
|
||||
| Name | Prime | Character |
|
||||
|------|-------|-----------|
|
||||
| Dyad | 2 | The binary split |
|
||||
| Pentad | 5 | The decimal bridge |
|
||||
| Heptad | 7 | The week |
|
||||
| Hendecad | 11 | The prime of primes |
|
||||
| Sigma | 683 | The first stranger |
|
||||
| Tau | 2,887 | The second stranger |
|
||||
| Phi | 4,279,037,621,376,791,777,197 | The cosmic grain |
|
||||
| Psi | 80,553,260,809,276,197,882,377 | The final factor |
|
||||
|
||||
## The Epoch
|
||||
|
||||
```
|
||||
1969-10-30 06:30:41 UTC
|
||||
```
|
||||
|
||||
The moment the first ARPANET message was transmitted. The offset 5,419,722 seconds from Unix epoch is squarefree (2 x 3 x 7 x 11 x 11,731), containing three cycle primes.
|
||||
|
||||
## Platform Requirements
|
||||
|
||||
This library requires Linux with `CLOCK_TAI` support (kernel 3.10+).
|
||||
|
||||
The Loic Calendar is built on TAI (International Atomic Time), which maintains the noble pretense that time flows uniformly. Operating systems that smear leap seconds, pretend they don't exist, or fail to provide atomic time have disqualified themselves from temporal inquiry.
|
||||
|
||||
Attempting to compile on unsupported platforms will fail. This is intentional.
|
||||
|
||||
It also requires the kernel to actually know what TAI is. `CLOCK_TAI` is `CLOCK_REALTIME` plus an offset that the kernel does not learn by itself: a time daemon has to read a leap-second table and set it through `adjtimex(2)`. chrony does this with `leapseclist /usr/share/zoneinfo/leap-seconds.list` or `leapsectz right/UTC`; ntpd with a `leapfile`; systemd-timesyncd does not do it at all. On a machine where the offset is zero, the first call to `LoicInstant::now()` panics and tells you which of those to configure. It will not observe a clock that is lying by a known amount.
|
||||
|
||||
## Precision and Storage
|
||||
|
||||
Time is stored as `I512` (512-bit signed integer) internally, providing:
|
||||
|
||||
- **Precision:** Individual Planck times (~5.39 x 10^-44 seconds)
|
||||
- **Range:** From Creation (~13.8 billion years before epoch) to Annihilation (2^511 - 1 Planck times, ~1.1 x 10^103 years from now — three orders of magnitude after the last black hole evaporates)
|
||||
|
||||
No floating-point arithmetic contaminates temporal calculations. The integers are exact. The timestamps are correct.
|
||||
|
||||
## License
|
||||
|
||||
Dual-licensed under AGPL-3.0 and the Loic Public License. See the workspace root for full terms, and the workspace README for the theoretical substrate the license keeps citing.
|
||||
5637
crates/loic/src/lib.rs
Normal file
26
crates/loid/Cargo.toml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# The LOID generator: a Planck-time timestamp with entropy in its
|
||||
# sub-nanosecond digits, offered as an identifier — two functions, an
|
||||
# integer, and a refusal to be a UUID. Published under the workspace's one
|
||||
# version; the registry metadata below is what it shows.
|
||||
[package]
|
||||
name = "loid"
|
||||
version.workspace = true
|
||||
edition = "2024"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
license-file.workspace = true
|
||||
description = "Loic Identifier. A Planck-time timestamp with hardware entropy in its sub-nanosecond digits. An integer. Your problem."
|
||||
readme = "README.md"
|
||||
keywords = ["identifier", "planck", "arpanet", "not-a-uuid", "your-problem"]
|
||||
categories = ["date-and-time"]
|
||||
|
||||
# The one dependency: the calendar itself, at the workspace's single stated
|
||||
# version. A LOID is an instant that has been asked to serve as a name, and
|
||||
# an instant is the library's to make.
|
||||
[dependencies]
|
||||
loic.workspace = true
|
||||
|
||||
# The library target, named and placed explicitly rather than inferred.
|
||||
[lib]
|
||||
name = "loid"
|
||||
path = "src/lib.rs"
|
||||
41
crates/loid/README.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# loid
|
||||
|
||||
Loic Identifier. A Planck-time timestamp. An integer. Your problem.
|
||||
|
||||
```rust
|
||||
let id = loid::loid(); // ~53 decimal digits, subject to the inevitable passing of time
|
||||
let short = loid::short(); // ~30 decimal digits, subject to the inevitable passing of time
|
||||
```
|
||||
|
||||
Two functions. One type (`I512`). One dependency (`loic`). No wrapper. No Display of
|
||||
its own. No serde. No monotonicity. No opinions. Universal and absolute.
|
||||
|
||||
## What it does
|
||||
|
||||
Each call samples the current moment via `CLOCK_TAI` (atomic time) and a generator
|
||||
seeded from the kernel's entropy pool and stirred with the nanosecond of the
|
||||
observation itself, producing a 512-bit signed integer counting Planck times since
|
||||
the Loic epoch (the first ARPANET transmission, 1969-10-30 06:30:41 UTC). Nineteen
|
||||
of its digits say which nanosecond you are in. The remaining thirty-four are the
|
||||
universe's answer to which Planck time within that nanosecond, and the universe does
|
||||
not repeat itself.
|
||||
|
||||
`short()` divides the full timestamp by 10²³, keeping ~11 of the original ~34
|
||||
entropy digits. Two `short()` values minted in the same nanosecond collide with
|
||||
probability better than one in a hundred billion, which is either reassuring or
|
||||
terrifying depending on what you were planning to do with them.
|
||||
|
||||
## Requirements
|
||||
|
||||
Linux, and a kernel that knows what TAI is. `CLOCK_TAI` is only TAI if a time daemon
|
||||
has read a leap-second table and told the kernel the offset — chrony with
|
||||
`leapseclist /usr/share/zoneinfo/leap-seconds.list` or `leapsectz right/UTC`, or ntpd with a `leapfile`. systemd-timesyncd never does. On a
|
||||
machine where the offset is zero, the first call panics and tells you which line to
|
||||
add. If you're on anything else, this crate is not for you, and you should reflect on
|
||||
why.
|
||||
|
||||
## License
|
||||
|
||||
Dual-licensed under AGPL-3.0 and the Loic Public License. See the workspace root for
|
||||
the terms, and the workspace README for the theoretical substrate the license keeps
|
||||
citing.
|
||||
149
crates/loid/src/lib.rs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
//! # LOID — Loic Identifier
|
||||
//!
|
||||
//! Two functions. One type. No opinions, beyond the several that follow.
|
||||
//!
|
||||
//! A LOID is a Planck-time timestamp drawn from the Loic Calendar's quantum
|
||||
//! observation pipeline—the mask-free half of it, the collapse without the
|
||||
//! question. Each invocation of `loid()` collapses the sub-nanosecond
|
||||
//! superposition of the present moment into a unique 512-bit signed
|
||||
//! integer—deterministic in its upper digits (the atomic clock, read through
|
||||
//! `CLOCK_TAI`), entropic in its lower digits (a per-thread generator seeded
|
||||
//! from the kernel's pool and stirred by the timing of the observation
|
||||
//! itself), and unreasonably large by any conventional metric, being a count
|
||||
//! of Planck times since the first transmission between networked machines
|
||||
//! and therefore some fifty-three digits long before it has finished
|
||||
//! introducing itself.
|
||||
//!
|
||||
//! The shortened variant, `short()`, amputates the lowest 23 decimal orders
|
||||
//! of magnitude via integer division, retaining ~11 digits of quantum
|
||||
//! entropy from the original ~34. The result is still an `I512`. It is still
|
||||
//! your problem. The workspace README lists this crate among the four as the
|
||||
//! one with no wrapper type and no opinions; the second claim was
|
||||
//! aspirational.
|
||||
//!
|
||||
//! ## What This Is Not
|
||||
//!
|
||||
//! - Not monotonic. Two calls within the same nanosecond may produce values
|
||||
//! in either order, because the entropy that fills the nanosecond makes no
|
||||
//! promises about sequence—it makes exactly one promise, uniformity, and
|
||||
//! uniformity is the opposite of order. Across nanoseconds the atomic
|
||||
//! clock is doing its best, and atomic clocks, when disciplined over a
|
||||
//! network, have been known to step. If you need monotonicity, enforce it
|
||||
//! yourself. The universe doesn't care, and per the workspace README,
|
||||
//! section 1.3, does not even agree with itself about simultaneity.
|
||||
//!
|
||||
//! - Not a UUID. Not a ULID. Not a KSUID. Not a Snowflake. Not any of the
|
||||
//! carefully-engineered identifier schemes designed by reasonable people
|
||||
//! for reasonable systems, with their version nibbles and their variant
|
||||
//! bits and their sixty-four-bit ambitions. This is a raw Planck timestamp
|
||||
//! with entropy spliced into its sub-nanosecond residue—the Loic Public
|
||||
//! License, Section 10, lists "generating unique identifiers from quantum
|
||||
//! observations of the sub-nanosecond void" among the permitted uses, and
|
||||
//! this crate is that permission exercised and nothing more. What you do
|
||||
//! with it is, as noted above, your problem.
|
||||
//!
|
||||
//! - Not wrapped. There is no newtype, no `Display` of its own, no `FromStr`
|
||||
//! of its own, no serde. The output is `I512`, which already has
|
||||
//! `to_string()` and `from_str()`. The rest is ceremony, and ceremony is
|
||||
//! not shipped here; a caller who wants an instant with methods on it
|
||||
//! wants `loic::LoicInstant`, which is where these two functions get
|
||||
//! theirs.
|
||||
//!
|
||||
//! - Not tested here. Every test would have to call `now()`, and `now()`
|
||||
//! refuses machines whose kernel does not know what TAI is (see below), so
|
||||
//! a test suite would pass or fail according to your time daemon rather
|
||||
//! than this crate. The arithmetic is `loic`'s and is tested there, where
|
||||
//! it is checked against a naive formula at twice the width; there is
|
||||
//! nothing in these two functions to test that is not either that
|
||||
//! arithmetic or the kernel.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! let id = loid::loid(); // full Planck timestamp (~53 digits)
|
||||
//! let short_id = loid::short(); // truncated by 10^23 (~30 digits)
|
||||
//! ```
|
||||
|
||||
#![warn(clippy::pedantic)]
|
||||
|
||||
/// The one type. Re-exported from `loic`, which re-exports it from `bnum`,
|
||||
/// so that a caller who names `loid::I512` and a caller who names
|
||||
/// `loic::I512` are naming the same integer and not two integers that
|
||||
/// merely agree—a distinction the type system enforces and a reader may
|
||||
/// safely forget, which is the best kind of distinction. Five hundred and
|
||||
/// twelve bits, signed, of which a fresh identifier uses about a hundred
|
||||
/// and seventy-five.
|
||||
pub use loic::I512;
|
||||
|
||||
/// Procures a fresh Loic Identifier: the full Planck timestamp of this
|
||||
/// instant, as an integer, with nothing wrapped around it.
|
||||
///
|
||||
/// Each call observes the current moment through the Loic Calendar's
|
||||
/// dual-source temporal pipeline—atomic time for the deterministic
|
||||
/// component, entropy seeded from the kernel's pool and stirred by the
|
||||
/// observation's own timing for the sub-nanosecond quantum residue—and
|
||||
/// returns the resulting `I512`, copied out of the instant it came in,
|
||||
/// since the instant has nothing else to offer an identifier.
|
||||
///
|
||||
/// The value contains approximately 19 deterministic digits (from
|
||||
/// `CLOCK_TAI`, encoding which nanosecond you are in) and 34 entropic digits
|
||||
/// (from the seeded generator, collapsing the sub-nanosecond superposition
|
||||
/// of roughly 10³⁴ Planck-scale coordinates into one), totaling roughly 53
|
||||
/// decimal digits. The two are not added but concatenated: the nanosecond
|
||||
/// occupies the high digits and the collapse the low ones, and the whole is
|
||||
/// one integer counting Planck times since the Whisper, not a pair of
|
||||
/// numbers wearing a single type. Whether this degree of precision is
|
||||
/// necessary, useful, or sane is a question this crate declines to
|
||||
/// entertain, and the Loic Public License, Section 2, declines on its
|
||||
/// behalf.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics under the same three conditions as `loic::LoicInstant::now()`, of
|
||||
/// which it is the thinnest possible wrapper: if `CLOCK_TAI` is unavailable,
|
||||
/// if the kernel's entropy pool cannot furnish the generator's seed, or—once
|
||||
/// per process, on the first call—if the kernel reports a TAI offset of
|
||||
/// zero, which means `CLOCK_TAI` has been `CLOCK_REALTIME` in a name tag all
|
||||
/// along. The first two do not occur on any Linux of the kernel generation
|
||||
/// the calendar requires. The third occurs on most of them, until chrony or
|
||||
/// ntpd is told where the leap-second table is; the panic message says which
|
||||
/// line to add, and the workspace README, section 1.2, says why it matters.
|
||||
/// On other operating systems, you should not be here, and the compiler will
|
||||
/// already have said so.
|
||||
#[inline]
|
||||
#[must_use = "A Loic Identifier minted and discarded — the universe collapsed thirty-four digits of superposition for an integer nobody kept, and it will not collapse them the same way twice."]
|
||||
pub fn loid() -> I512 {
|
||||
*loic::LoicInstant::now().planck()
|
||||
}
|
||||
|
||||
/// Procures a shortened Loic Identifier: the full timestamp divided by
|
||||
/// 10²³, by one 512-bit division that truncates toward zero.
|
||||
///
|
||||
/// This function observes the full instant exactly as `loid()` does—
|
||||
/// generating the full 34 digits of quantum entropy—and only then truncates
|
||||
/// 23 of them away, through `loic::LoicInstant::shortened()`. The ordering
|
||||
/// matters: one does not generate 11 digits of entropy directly. One
|
||||
/// generates 34 and discards 23, because the generator draws uniformly at a
|
||||
/// fixed granularity of one Planck time and truncating post-hoc preserves
|
||||
/// the statistical properties of the original quantum observation, whereas
|
||||
/// a generator asked for eleven digits would be answering a question about
|
||||
/// a coarser superposition that the calendar does not recognise.
|
||||
///
|
||||
/// The result retains approximately 11 digits of entropy, sufficient to
|
||||
/// distinguish simultaneous observations with high probability. "High
|
||||
/// probability" here means "better than one in a hundred billion," which is
|
||||
/// either reassuring or terrifying depending on your failure mode
|
||||
/// tolerance, and which the calendar notes is a great deal better than the
|
||||
/// odds most calendars offer two events in the same second.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Same conditions as `loid()`, for the same reason: the observation is the
|
||||
/// same. The truncation itself cannot fail; integer division by a power of
|
||||
/// ten is the one operation in this workspace that has never had an
|
||||
/// opinion.
|
||||
#[inline]
|
||||
#[must_use = "A shortened Loic Identifier minted and discarded — twenty-three digits were amputated to make it convenient, and it was still not kept, which suggests convenience was not the obstacle."]
|
||||
pub fn short() -> I512 {
|
||||
loic::LoicInstant::now().shortened()
|
||||
}
|
||||
690
derive_constants.sh
Executable file
|
|
@ -0,0 +1,690 @@
|
|||
#!/bin/sh
|
||||
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
||||
# ║ ║
|
||||
# ║ THE LOIC CYCLE DERIVATION ORACLE ║
|
||||
# ║ ║
|
||||
# ║ Computing the Fundamental Cycle of the Loic Calendar from ║
|
||||
# ║ the Hubble Constant, the Fine-Structure Constant, and Pi ║
|
||||
# ║ ║
|
||||
# ║ Cycle = H₀⁻¹ × α^π / π ║
|
||||
# ║ ║
|
||||
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# ON THE NECESSARY IMPURITY OF TRANSCENDENTAL COMPUTATION
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# The companion script, derive_constants.sh, uses only integer arithmetic.
|
||||
# This script cannot. The formula H₀⁻¹ × α^π / π requires computing
|
||||
# α raised to an irrational power, which demands the transcendental
|
||||
# functions e(x) and l(x) — exponential and natural logarithm.
|
||||
#
|
||||
# We use bc -l, which provides these functions via Taylor series expansion
|
||||
# at arbitrary decimal precision. This is NOT IEEE 754 floating-point.
|
||||
# bc does not use binary representation, does not silently truncate to
|
||||
# 64 bits, does not harbor negative zero, and does not pretend that
|
||||
# 0.1 + 0.2 equals 0.3 when it does not. bc computes in decimal, at
|
||||
# whatever precision we demand, truncating only where we instruct it.
|
||||
#
|
||||
# The scale is set to 200 decimal places — far beyond the 155-digit
|
||||
# limit of I512 and the ~46-digit physical significance of Planck time.
|
||||
# The excess precision ensures that accumulated rounding in intermediate
|
||||
# Taylor series terms does not contaminate the final result.
|
||||
#
|
||||
# When the computation is complete, the result is truncated to an integer.
|
||||
# The irrational numbers — π and the transcendental α^π — are consumed
|
||||
# during derivation and do not exist at runtime. Only the integer remains.
|
||||
# This is not a compromise. It is the same treatment given to every other
|
||||
# physical constant in the Loic Calendar: rational approximation with
|
||||
# error below physical significance.
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# USAGE
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# ./derive_cycle.sh # Full human-readable derivation
|
||||
# ./derive_cycle.sh --constant # Emit only PLANCK_PER_CYCLE
|
||||
# ./derive_cycle.sh --json # Machine-readable JSON output
|
||||
# ./derive_cycle.sh --help # Illuminate the path
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
set -e
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# WORKING PRECISION
|
||||
#
|
||||
# 200 decimal places. The universe requires ~46. We provide ~200.
|
||||
# The excess is not waste; it is insurance against the accumulation
|
||||
# of truncation error across dozens of Taylor series terms.
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
PRECISION=200
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# THE FUNDAMENTAL CONSTANTS
|
||||
#
|
||||
# Three axioms. Three constants. Three windows into the structure of
|
||||
# the universe, through which the Loic cycle is glimpsed.
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# THE HUBBLE CONSTANT
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
#
|
||||
# H₀ = 67.4 km/s/Mpc
|
||||
#
|
||||
# The rate of cosmic expansion as measured by the Planck satellite (2018)
|
||||
# through observation of the Cosmic Microwave Background — the oldest
|
||||
# light in the universe, emitted 380,000 years after Creation when the
|
||||
# cosmos first became transparent.
|
||||
#
|
||||
# A competing measurement exists: 73.0 ± 1.0 km/s/Mpc, derived from the
|
||||
# local distance ladder (Cepheid variables and Type Ia supernovae). The
|
||||
# discrepancy — known as the Hubble tension — is a 5σ disagreement that
|
||||
# represents arguably the greatest crisis in modern cosmology.
|
||||
#
|
||||
# We choose 67.4. The CMB measurement peers deeper into the past than any
|
||||
# other observation in the history of science. This is a calendar that
|
||||
# begins at the beginning. We use the measurement that looked closest
|
||||
# to Creation.
|
||||
#
|
||||
# The tension is documented. The choice is deliberate. If future physics
|
||||
# resolves the disagreement, the constant may be revised — but the formula
|
||||
# will endure.
|
||||
|
||||
H0_KM_S_MPC="67.4"
|
||||
|
||||
# 1 Megaparsec in meters (IAU 2012 exact definition)
|
||||
#
|
||||
# The parsec is defined as 648000/π AU, where 1 AU = 149,597,870,700 m
|
||||
# exactly (IAU 2012 Resolution B2). The megaparsec is 10⁶ parsecs.
|
||||
#
|
||||
# Since the parsec definition involves π, the megaparsec in meters is
|
||||
# itself irrational. We use a rational approximation to 17 significant
|
||||
# figures — vastly exceeding the 3 significant figures of H₀ itself.
|
||||
# The bottleneck of precision is the Hubble constant, not the unit
|
||||
# conversion.
|
||||
|
||||
MPC_METERS="30856775814913673000000"
|
||||
# 3.0856775814913673 × 10²² meters. The 'e' notation is eschewed;
|
||||
# bc does not speak exponential.
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# THE FINE-STRUCTURE CONSTANT
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
#
|
||||
# α = 7.2973525643 × 10⁻³
|
||||
#
|
||||
# The dimensionless coupling constant of quantum electrodynamics.
|
||||
# It governs the strength of the electromagnetic interaction — how
|
||||
# strongly charged particles talk to each other. Every photon emitted,
|
||||
# every electron captured, every signal transmitted through copper wire
|
||||
# is modulated by this number.
|
||||
#
|
||||
# The Whisper — "LO", transmitted from UCLA to Stanford over ARPANET —
|
||||
# was an electromagnetic event. The characters were encoded as electrical
|
||||
# signals, propagated through coaxial cable, decoded by transistors.
|
||||
# Every stage of that transmission was governed by α.
|
||||
#
|
||||
# A calendar born from the first network transmission, whose cycle is
|
||||
# derived from the constant that makes network transmissions physically
|
||||
# possible. This is not metaphor. This is electrodynamics.
|
||||
#
|
||||
# CODATA 2022 recommended value: 7.2973525643(11) × 10⁻³
|
||||
# Uncertainty: ±0.0000000011 × 10⁻³ (in the 11th significant figure)
|
||||
#
|
||||
# We express it without scientific notation, as bc demands.
|
||||
|
||||
ALPHA="0.0072973525643"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# THE PLANCK TIME RATIO (inherited from derive_constants.sh)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
PLANCK_TIME_NUMERATOR="5391247"
|
||||
PLANCK_TIME_DENOMINATOR="100000000000000000000000000000000000000000000000000"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# TIME UNIT DEFINITIONS (inherited)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
SECONDS_PER_DAY="86400"
|
||||
SECONDS_PER_JULIAN_YEAR="31557600"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# THE AGE OF THE UNIVERSE (inherited, Boltzmann encoding)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
AGE_UNIVERSE_JULIAN_YEARS="13806491023"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# COMPUTATION PHASE 1
|
||||
#
|
||||
# π TO 155 DECIMAL DIGITS
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# 155 decimal digits is the precision limit of I512 — the maximum number
|
||||
# of decimal digits representable in a 512-bit signed integer. We compute
|
||||
# π to the full capacity of our storage type, because to do less would be
|
||||
# to waste bits, and wasted bits are wasted precision.
|
||||
#
|
||||
# The computation uses bc's arctangent function: π = 4 × arctan(1).
|
||||
# The Taylor series for arctan converges slowly for x=1, but bc's
|
||||
# internal implementation handles this adequately at scale=200.
|
||||
#
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# A NOTE ON THE CANONICAL DERIVATION
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
#
|
||||
# The most elegant method for computing π is the Leibniz series:
|
||||
#
|
||||
# π/4 = 1 - 1/3 + 1/5 - 1/7 + 1/9 - ...
|
||||
#
|
||||
# To obtain 155 correct decimal digits via this series requires
|
||||
# approximately 10^155 terms. At one billion terms per second, this
|
||||
# computation would complete in approximately:
|
||||
#
|
||||
# 10^155 / 10^9 = 10^146 seconds
|
||||
# ≈ 3.17 × 10^138 years
|
||||
# ≈ 2.30 × 10^128 Hubble times
|
||||
#
|
||||
# The Leibniz series is the canonical derivation method. The hardcoded
|
||||
# value below was obtained by other means, for reasons of cosmic scheduling.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
compute_pi() {
|
||||
# bc -l provides a(x) = arctan(x)
|
||||
# π = 4 × arctan(1), computed at scale=$PRECISION
|
||||
PI=$(echo "scale=$PRECISION; 4 * a(1)" | bc -l)
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# COMPUTATION PHASE 2
|
||||
#
|
||||
# THE HUBBLE TIME IN SI SECONDS
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# H₀ is given in km/s/Mpc. To obtain the Hubble time (1/H₀) in seconds:
|
||||
#
|
||||
# H₀ [s⁻¹] = H₀ [km/s/Mpc] × 1000 [m/km] / Mpc [m]
|
||||
# t_H [s] = 1 / H₀ [s⁻¹]
|
||||
# = Mpc [m] / (H₀ [km/s/Mpc] × 1000)
|
||||
#
|
||||
# The result: approximately 4.578 × 10¹⁷ seconds, or ~14.5 billion years.
|
||||
# This is close to — but not equal to — the age of the universe (13.8 Gyr).
|
||||
# The near-coincidence is a deep fact about cosmological geometry.
|
||||
|
||||
compute_hubble_time() {
|
||||
HUBBLE_TIME_SECONDS=$(echo "scale=$PRECISION; $MPC_METERS / ($H0_KM_S_MPC * 1000)" | bc -l)
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# COMPUTATION PHASE 3
|
||||
#
|
||||
# THE HUBBLE TIME IN PLANCK UNITS
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# Convert from SI seconds to Planck times via rational arithmetic:
|
||||
#
|
||||
# planck_times = seconds × (1 / t_P)
|
||||
# = seconds × (10^50 / 5391247)
|
||||
# = seconds × PLANCK_TIME_DENOMINATOR / PLANCK_TIME_NUMERATOR
|
||||
#
|
||||
# We multiply first, then divide — preserving maximum precision.
|
||||
# The result is approximately 8.49 × 10⁶⁰ Planck times.
|
||||
|
||||
compute_hubble_planck() {
|
||||
HUBBLE_TIME_PLANCK=$(echo "scale=$PRECISION; ($HUBBLE_TIME_SECONDS * $PLANCK_TIME_DENOMINATOR) / $PLANCK_TIME_NUMERATOR" | bc -l)
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# COMPUTATION PHASE 4
|
||||
#
|
||||
# α^π — THE HEART
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# Here lies the transcendental core of the derivation.
|
||||
#
|
||||
# α^π cannot be computed by integer arithmetic. It requires:
|
||||
#
|
||||
# α^π = e^(π × ln(α))
|
||||
#
|
||||
# where e(x) is the exponential function and l(x) is the natural logarithm.
|
||||
# Both are provided by bc -l via Taylor series, evaluated at arbitrary
|
||||
# decimal precision.
|
||||
#
|
||||
# The result is approximately 1.936 × 10⁻⁷ — a small number, reflecting
|
||||
# the fact that α itself is small (~1/137) and raising it to the power
|
||||
# of π (~3.14159) attenuates it further.
|
||||
#
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# WHY α^π AND NOT α³
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
#
|
||||
# α³ produces a cycle of ~1,795 years. α⁴ produces ~13.1 years. Between
|
||||
# them, α^π produces ~894 years — roughly half the α³ cycle. The factor
|
||||
# of reduction is α^(π-3) = α^0.14159... ≈ 0.498.
|
||||
#
|
||||
# But the choice is not merely numerical. α³ is a rational exponent.
|
||||
# α^π is irrational — it encodes π into the very definition of the cycle,
|
||||
# alongside H₀ and α itself. The cycle becomes a function of three
|
||||
# fundamental quantities:
|
||||
#
|
||||
# • H₀ — the rate of cosmic expansion (cosmology)
|
||||
# • α — the strength of electromagnetism (quantum physics)
|
||||
# • π — the ratio of circumference to diameter (geometry)
|
||||
#
|
||||
# The universe speaks. The electron couples. The circle closes.
|
||||
# These three, combined, produce the rhythm of the Loic Calendar.
|
||||
|
||||
compute_alpha_to_pi() {
|
||||
# Step 1: Compute ln(α)
|
||||
LN_ALPHA=$(echo "scale=$PRECISION; l($ALPHA)" | bc -l)
|
||||
|
||||
# Step 2: Compute π × ln(α)
|
||||
PI_LN_ALPHA=$(echo "scale=$PRECISION; $PI * $LN_ALPHA" | bc -l)
|
||||
|
||||
# Step 3: Compute e^(π × ln(α)) = α^π
|
||||
ALPHA_TO_PI=$(echo "scale=$PRECISION; e($PI_LN_ALPHA)" | bc -l)
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# COMPUTATION PHASE 5
|
||||
#
|
||||
# THE CYCLE: H₀⁻¹ × α^π / π
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# The final assembly. The Hubble time (in Planck units), multiplied by
|
||||
# α^π, divided by π. The result is the number of Planck times in one
|
||||
# Loic cycle — approximately 5.23 × 10⁵³.
|
||||
#
|
||||
# The integer truncation occurs here. The decimal portion — approximately
|
||||
# 0.4 Planck times — is discarded. This is sub-Planck: below the threshold
|
||||
# at which physics itself can distinguish one moment from the next.
|
||||
#
|
||||
# The truncated integer is PLANCK_PER_CYCLE: the constant that will be
|
||||
# inscribed into the Loic Calendar implementation, replacing π, α, and H₀
|
||||
# with a single exact value from which no irrationality escapes.
|
||||
|
||||
compute_cycle() {
|
||||
# Compute the full-precision cycle in Planck times
|
||||
CYCLE_PLANCK_FULL=$(echo "scale=$PRECISION; ($HUBBLE_TIME_PLANCK * $ALPHA_TO_PI) / $PI" | bc -l)
|
||||
|
||||
# Truncate to integer (floor)
|
||||
# bc's scale=0 division by 1 achieves floor truncation for positive values
|
||||
PLANCK_PER_CYCLE=$(echo "scale=0; $CYCLE_PLANCK_FULL / 1" | bc)
|
||||
|
||||
# Compute the truncation error for documentation
|
||||
TRUNCATION_ERROR=$(echo "scale=$PRECISION; $CYCLE_PLANCK_FULL - $PLANCK_PER_CYCLE" | bc -l)
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# COMPUTATION PHASE 6
|
||||
#
|
||||
# HUMAN-SCALE CONVERSIONS
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
compute_human_units() {
|
||||
# Cycle in SI seconds
|
||||
CYCLE_SECONDS=$(echo "scale=$PRECISION; ($PLANCK_PER_CYCLE * $PLANCK_TIME_NUMERATOR) / $PLANCK_TIME_DENOMINATOR" | bc -l)
|
||||
|
||||
# Cycle in days
|
||||
CYCLE_DAYS=$(echo "scale=$PRECISION; $CYCLE_SECONDS / $SECONDS_PER_DAY" | bc -l)
|
||||
|
||||
# Cycle in Julian years
|
||||
CYCLE_YEARS=$(echo "scale=$PRECISION; $CYCLE_SECONDS / $SECONDS_PER_JULIAN_YEAR" | bc -l)
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# COMPUTATION PHASE 7
|
||||
#
|
||||
# COSMOLOGICAL CONTEXT
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
compute_cosmological_context() {
|
||||
# Age of the universe in seconds
|
||||
AGE_UNIVERSE_SECONDS=$(echo "$AGE_UNIVERSE_JULIAN_YEARS * $SECONDS_PER_JULIAN_YEAR" | bc)
|
||||
|
||||
# Age in Planck times
|
||||
AGE_UNIVERSE_PLANCK=$(echo "scale=0; ($AGE_UNIVERSE_SECONDS * $PLANCK_TIME_DENOMINATOR) / $PLANCK_TIME_NUMERATOR" | bc)
|
||||
|
||||
# Completed cycles since Creation
|
||||
CYCLES_SINCE_CREATION=$(echo "scale=0; $AGE_UNIVERSE_PLANCK / $PLANCK_PER_CYCLE" | bc)
|
||||
|
||||
# Precise ratio (for fractional position)
|
||||
CYCLES_SINCE_CREATION_PRECISE=$(echo "scale=20; $AGE_UNIVERSE_PLANCK / $PLANCK_PER_CYCLE" | bc -l)
|
||||
|
||||
# Position within current cycle (in Planck times)
|
||||
POSITION_IN_CYCLE=$(echo "$AGE_UNIVERSE_PLANCK % $PLANCK_PER_CYCLE" | bc)
|
||||
|
||||
# Fraction through current cycle
|
||||
CYCLE_FRACTION=$(echo "scale=20; $POSITION_IN_CYCLE / $PLANCK_PER_CYCLE" | bc -l)
|
||||
|
||||
# Position in years
|
||||
POSITION_YEARS=$(echo "scale=10; ($POSITION_IN_CYCLE * $PLANCK_TIME_NUMERATOR) / ($PLANCK_TIME_DENOMINATOR * $SECONDS_PER_JULIAN_YEAR)" | bc -l)
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# COMPUTATION PHASE 8
|
||||
#
|
||||
# ANNIHILATION AND CREATION
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
compute_boundaries() {
|
||||
ANNIHILATION_PLANCK=$(echo "2^511 - 1" | bc | tr -d '\\\n')
|
||||
CREATION_PLANCK_POS=$(echo "($AGE_UNIVERSE_SECONDS * $PLANCK_TIME_DENOMINATOR) / $PLANCK_TIME_NUMERATOR" | bc)
|
||||
CREATION_PLANCK="-$CREATION_PLANCK_POS"
|
||||
|
||||
# Total representable span
|
||||
TOTAL_SPAN=$(echo "$ANNIHILATION_PLANCK + $CREATION_PLANCK_POS" | bc)
|
||||
|
||||
# Cycles in total span
|
||||
TOTAL_CYCLES=$(echo "scale=0; $TOTAL_SPAN / $PLANCK_PER_CYCLE" | bc)
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# OUTPUT: HUMAN FORM
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
output_human() {
|
||||
cat << 'HEADER'
|
||||
╔═══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ LOIC CYCLE DERIVATION ║
|
||||
║ ║
|
||||
║ Cycle = H₀⁻¹ × α^π / π ║
|
||||
║ ║
|
||||
║ The Hubble time, attenuated through the electromagnetic coupling strength ║
|
||||
║ raised to the most fundamental irrational power, divided by that same ║
|
||||
║ irrational — the quantum of computation in Planck units. ║
|
||||
║ ║
|
||||
║ Computed using POSIX bc at 200 decimal places of working precision. ║
|
||||
║ No IEEE 754 floating-point arithmetic was used or contemplated. ║
|
||||
╚═══════════════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
HEADER
|
||||
|
||||
echo "═══════════════════════════════════════════════════════════════════════════════"
|
||||
echo " INPUT CONSTANTS"
|
||||
echo "═══════════════════════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo " H₀ (Hubble constant) = $H0_KM_S_MPC km/s/Mpc"
|
||||
echo " Source: Planck 2018 CMB (the measurement that looked closest to Creation)"
|
||||
echo " Tension: Local distance ladder gives 73.0 ± 1.0 km/s/Mpc"
|
||||
echo ""
|
||||
echo " α (fine-structure constant) = $ALPHA"
|
||||
echo " Source: CODATA 2022 recommended value"
|
||||
echo " Significance: governs electromagnetic coupling — why The Whisper was"
|
||||
echo " physically possible"
|
||||
echo ""
|
||||
echo " t_P (Planck time) = $PLANCK_TIME_NUMERATOR / 10^50 seconds"
|
||||
echo " The quantum of duration"
|
||||
echo ""
|
||||
|
||||
echo "═══════════════════════════════════════════════════════════════════════════════"
|
||||
echo " INTERMEDIATE VALUES"
|
||||
echo "═══════════════════════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo " π (to 155 decimal digits — the I512 precision limit):"
|
||||
# Format π with line wrapping for readability
|
||||
echo " $PI" | fold -w 74 | sed 's/^/ /'
|
||||
echo ""
|
||||
echo " Hubble time (1/H₀):"
|
||||
echo " In seconds: $(echo "$HUBBLE_TIME_SECONDS" | head -c 20)... × 10^17"
|
||||
echo " In Planck times: $(echo "$HUBBLE_TIME_PLANCK" | head -c 20)... × 10^60"
|
||||
echo ""
|
||||
echo " ln(α): $LN_ALPHA" | head -c 80
|
||||
echo "..."
|
||||
echo " π·ln(α): $PI_LN_ALPHA" | head -c 80
|
||||
echo "..."
|
||||
echo " α^π: $ALPHA_TO_PI" | head -c 80
|
||||
echo "..."
|
||||
echo ""
|
||||
|
||||
echo "═══════════════════════════════════════════════════════════════════════════════"
|
||||
echo " THE CYCLE"
|
||||
echo "═══════════════════════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo " Formula: H₀⁻¹ × α^π / π"
|
||||
echo ""
|
||||
echo " PLANCK_PER_CYCLE ="
|
||||
echo " $PLANCK_PER_CYCLE"
|
||||
echo ""
|
||||
echo " Digits: $(echo -n "$PLANCK_PER_CYCLE" | wc -c)"
|
||||
echo ""
|
||||
echo " Truncation error: $TRUNCATION_ERROR Planck times"
|
||||
echo " (< 1 Planck time — sub-Planck, below physical significance)"
|
||||
echo ""
|
||||
echo " In human units:"
|
||||
echo " Seconds: $(echo "$CYCLE_SECONDS" | head -c 20)..."
|
||||
echo " Days: $(echo "$CYCLE_DAYS" | head -c 20)..."
|
||||
echo " Julian years: $(echo "$CYCLE_YEARS" | head -c 20)..."
|
||||
echo ""
|
||||
|
||||
echo "═══════════════════════════════════════════════════════════════════════════════"
|
||||
echo " COSMOLOGICAL CONTEXT"
|
||||
echo "═══════════════════════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo " Age of universe: ~13.806 billion Julian years"
|
||||
echo ""
|
||||
echo " Completed cycles since Creation: $CYCLES_SINCE_CREATION"
|
||||
echo " (precisely: $CYCLES_SINCE_CREATION_PRECISE)"
|
||||
echo ""
|
||||
echo " Position within current cycle:"
|
||||
echo " Fraction: $CYCLE_FRACTION"
|
||||
echo " Years: $POSITION_YEARS Julian years into current cycle"
|
||||
echo ""
|
||||
echo " CREATION_PLANCK: $CREATION_PLANCK"
|
||||
echo " ($(echo -n "$CREATION_PLANCK" | wc -c | tr -d ' ') digits, ~8.08 × 10^60)"
|
||||
echo ""
|
||||
echo " ANNIHILATION_PLANCK: $ANNIHILATION_PLANCK"
|
||||
echo " ($(echo -n "$ANNIHILATION_PLANCK" | wc -c | tr -d ' ') digits, ~6.7 × 10^153)"
|
||||
echo ""
|
||||
echo " Total cycles in representable span: $TOTAL_CYCLES"
|
||||
echo " ($(echo -n "$TOTAL_CYCLES" | wc -c | tr -d ' ') digits)"
|
||||
echo ""
|
||||
|
||||
echo "═══════════════════════════════════════════════════════════════════════════════"
|
||||
echo " THE FORMULA, FOR THE RECORD"
|
||||
echo "═══════════════════════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo " PLANCK_PER_CYCLE = floor( (Mpc_m / (H₀ × 1000)) × (10^50 / t_P_num) × α^π / π )"
|
||||
echo ""
|
||||
echo " Where:"
|
||||
echo " Mpc_m = $MPC_METERS (1 Megaparsec in meters)"
|
||||
echo " H₀ = $H0_KM_S_MPC km/s/Mpc (Planck 2018 CMB)"
|
||||
echo " t_P_num = $PLANCK_TIME_NUMERATOR (Planck time numerator)"
|
||||
echo " 10^50 = Planck time denominator"
|
||||
echo " α = $ALPHA (CODATA 2022)"
|
||||
echo " π = 3.14159265358979... (computed to 155 decimal digits)"
|
||||
echo ""
|
||||
echo " The irrational numbers (π, α^π) were consumed during this derivation."
|
||||
echo " Only the integer PLANCK_PER_CYCLE survives into the implementation."
|
||||
echo " No irrationality escapes into runtime."
|
||||
echo ""
|
||||
|
||||
cat << 'FOOTER'
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
VERIFICATION NOTES
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
This constant should be independently verified using at least one other
|
||||
arbitrary-precision computation system (e.g., Mathematica, SageMath, or
|
||||
a sufficiently determined human with a 13-rod suanpan abacus).
|
||||
|
||||
The factorization of PLANCK_PER_CYCLE should also be computed and
|
||||
recorded, as the prime structure of this constant determines the
|
||||
subdivision lattice of the Loic Calendar.
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
FOOTER
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# OUTPUT: MACHINE FORM
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
output_json() {
|
||||
cat << EOF
|
||||
{
|
||||
"formula": "H0_inverse * alpha_to_pi / pi",
|
||||
"inputs": {
|
||||
"H0_km_s_Mpc": "$H0_KM_S_MPC",
|
||||
"H0_source": "Planck 2018 CMB",
|
||||
"alpha": "$ALPHA",
|
||||
"alpha_source": "CODATA 2022",
|
||||
"Mpc_meters": "$MPC_METERS",
|
||||
"PLANCK_TIME_NUMERATOR": "$PLANCK_TIME_NUMERATOR",
|
||||
"PLANCK_TIME_DENOMINATOR": "$PLANCK_TIME_DENOMINATOR",
|
||||
"working_precision_digits": $PRECISION
|
||||
},
|
||||
"result": {
|
||||
"PLANCK_PER_CYCLE": "$PLANCK_PER_CYCLE",
|
||||
"cycle_seconds": "$(echo "$CYCLE_SECONDS" | head -c 30)",
|
||||
"cycle_days": "$(echo "$CYCLE_DAYS" | head -c 20)",
|
||||
"cycle_julian_years": "$(echo "$CYCLE_YEARS" | head -c 20)",
|
||||
"truncation_error_planck": "$(echo "$TRUNCATION_ERROR" | head -c 20)",
|
||||
"digit_count": $(echo -n "$PLANCK_PER_CYCLE" | wc -c | tr -d ' ')
|
||||
},
|
||||
"cosmology": {
|
||||
"cycles_since_creation": "$CYCLES_SINCE_CREATION",
|
||||
"cycles_since_creation_precise": "$CYCLES_SINCE_CREATION_PRECISE",
|
||||
"current_cycle_fraction": "$CYCLE_FRACTION",
|
||||
"current_cycle_years": "$(echo "$POSITION_YEARS" | head -c 20)",
|
||||
"CREATION_PLANCK": "$CREATION_PLANCK",
|
||||
"ANNIHILATION_PLANCK": "$ANNIHILATION_PLANCK",
|
||||
"total_representable_cycles": "$TOTAL_CYCLES"
|
||||
},
|
||||
"derivation": {
|
||||
"method": "bc_arbitrary_precision_decimal",
|
||||
"ieee754_used": false,
|
||||
"pi_digits_computed": 155,
|
||||
"note": "Irrational numbers consumed during derivation. Only integer survives."
|
||||
}
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# OUTPUT: CONSTANT ONLY
|
||||
#
|
||||
# For piping into other tools. Just the number. Nothing else.
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
output_constant() {
|
||||
echo "$PLANCK_PER_CYCLE"
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
# THE MAIN RITUAL
|
||||
#
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
main() {
|
||||
mode="human"
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--json) mode="json" ;;
|
||||
--constant) mode="constant" ;;
|
||||
--help|-h)
|
||||
cat << 'HELP'
|
||||
╔═══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ LOIC CYCLE DERIVATION ORACLE ║
|
||||
╚═══════════════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
USAGE:
|
||||
./derive_cycle.sh [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
(none) Full human-readable derivation with cosmological context
|
||||
--constant Emit only the PLANCK_PER_CYCLE integer (for piping)
|
||||
--json Machine-readable JSON output
|
||||
--help, -h Illuminate the path
|
||||
|
||||
DESCRIPTION:
|
||||
Derives the Loic Calendar cycle constant from first principles:
|
||||
|
||||
Cycle = H₀⁻¹ × α^π / π
|
||||
|
||||
Where:
|
||||
H₀ = 67.4 km/s/Mpc (Planck 2018 CMB)
|
||||
α = 7.2973525643e-3 (CODATA 2022 fine-structure constant)
|
||||
π = 3.14159... (computed to 155 decimal digits)
|
||||
|
||||
Uses POSIX bc at 200-digit working precision. No IEEE 754 floating-point.
|
||||
The result is truncated to an integer with sub-Planck error.
|
||||
|
||||
PHILOSOPHY:
|
||||
"The universe speaks. The electron couples. The circle closes.
|
||||
These three — H₀, α, π — combined, produce the rhythm of the
|
||||
Loic Calendar. The irrational numbers are consumed during derivation.
|
||||
Only the integer survives."
|
||||
|
||||
HELP
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $arg" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ───────────────────────────────────────────────────────────────────────
|
||||
# THE COMPUTATION CASCADE
|
||||
# ───────────────────────────────────────────────────────────────────────
|
||||
# Each phase depends on the previous. The order is immutable.
|
||||
|
||||
compute_pi # Phase 1: π
|
||||
compute_hubble_time # Phase 2: H₀⁻¹ in seconds
|
||||
compute_hubble_planck # Phase 3: H₀⁻¹ in Planck times
|
||||
compute_alpha_to_pi # Phase 4: α^π
|
||||
compute_cycle # Phase 5: The cycle itself
|
||||
compute_human_units # Phase 6: Seconds, days, years
|
||||
compute_cosmological_context # Phase 7: Where are we?
|
||||
compute_boundaries # Phase 8: Creation and Annihilation
|
||||
|
||||
# ───────────────────────────────────────────────────────────────────────
|
||||
# THE OUTPUT
|
||||
# ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
case "$mode" in
|
||||
human) output_human ;;
|
||||
json) output_json ;;
|
||||
constant) output_constant ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# BEGIN EXECUTION
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
main "$@"
|
||||