From 7906abdd2b2bc4863d37fc111016bb476cf22059 Mon Sep 17 00:00:00 2001 From: Indiana Date: Sat, 1 Aug 2026 21:14:59 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20/secret=20=E2=80=94=20the=20grimoire?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An unlisted page at /secret. Nothing in the app links to it, it has no nav tab, and MobileNav's route-parity test now names it as an explicit exception so an ordinary page still fails that test. It IS crawlable and is advertised in the sitemap: "hidden" here means no button, not no index — a long, specific mechanics page is worth the search traffic. Every number on it was read out of the source or produced by RUNNING the real function. A grimoire of plausible-sounding numbers would be worse than none, because it would be believed. Four hand-rolled SVG figures (rarity by moon phase, the passage ladder, the two twist curves) rather than a charting dependency for four static pictures. The best thing in it is a trap I did not know existed until I ran the code: crossing over requires volatility strictly above 0.6, and favor shifts volatility DOWN by up to 0.12 at mint. So a hunter at maximum favor pulls spirits under the gate and locks themselves out of the largest reward in the game — 25 essence and +0.08 favor. Verified directly: a spirit at volatility 0.65 is crossable at favor 0.0, and is not at +0.5 or +1.0. "Do not maximise favor" is real, counter-intuitive, and follows from the source. English only, as agreed — written as prose outside the i18n system rather than several hundred translation keys, which keeps the en/es parity gate meaningful for the actual UI. Co-Authored-By: Claude Opus 5 --- backend/app/routes/seo.py | 7 + frontend/src/App.tsx | 4 + frontend/src/components/MobileNav.test.tsx | 7 +- frontend/src/pages/SecretPage.css | 279 +++++++++ frontend/src/pages/SecretPage.tsx | 674 +++++++++++++++++++++ 5 files changed, 970 insertions(+), 1 deletion(-) create mode 100644 frontend/src/pages/SecretPage.css create mode 100644 frontend/src/pages/SecretPage.tsx diff --git a/backend/app/routes/seo.py b/backend/app/routes/seo.py index 8037735..e2bf340 100644 --- a/backend/app/routes/seo.py +++ b/backend/app/routes/seo.py @@ -94,6 +94,13 @@ async def sitemap(db: AsyncSession = Depends(get_db)) -> Response: "0.9", f"{base}/shopweekly" "0.7", + # /secret is UNLISTED, not private: nothing in the app links to it and + # it has no nav tab, so a seeker only finds it by being told. It is + # advertised here anyway because it is a long, specific mechanics page + # and search traffic is the whole reason it is crawlable — "hidden" + # here means "no button", not "no index". + f"{base}/secretmonthly" + "0.8", ] for entity_id, discovered_at in entities: loc = _xml_escape(f"{base}/codex/{entity_id}") diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index bd690a6..ffddb64 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -19,6 +19,7 @@ import { LandingPage } from './pages/LandingPage' import { MessagesPage } from './pages/MessagesPage' import { ProfilePage } from './pages/ProfilePage' import { SeancePage } from './pages/SeancePage' +import { SecretPage } from './pages/SecretPage' import { ShopPage } from './pages/ShopPage' function App() { @@ -52,6 +53,9 @@ function App() { } /> } /> } /> + {/* Unlisted: nothing in the app links here, and it is deliberately + absent from MobileNav. Reached by knowing the URL. */} + } /> } /> diff --git a/frontend/src/components/MobileNav.test.tsx b/frontend/src/components/MobileNav.test.tsx index 896114e..345a381 100644 --- a/frontend/src/components/MobileNav.test.tsx +++ b/frontend/src/components/MobileNav.test.tsx @@ -101,7 +101,12 @@ describe('MobileNav', () => { p !== '*' && // catch-all, not a destination !p.includes(':') && // detail views, reached through their index p !== '/' && // the landing page is the way in, not a tab - p !== '/enter', // the sign-in door, deliberately not a tab + p !== '/enter' && // the sign-in door, deliberately not a tab + // Unlisted BY DESIGN: /secret has no button anywhere in the app and + // must never gain one. Listed here as an explicit exception rather + // than left to slip through, so that adding an ordinary page still + // fails this test. + p !== '/secret', ) const covered = new Set(NAV_ITEMS.map((i) => i.to)) diff --git a/frontend/src/pages/SecretPage.css b/frontend/src/pages/SecretPage.css new file mode 100644 index 0000000..8d4601e --- /dev/null +++ b/frontend/src/pages/SecretPage.css @@ -0,0 +1,279 @@ +/* The Secret — a long-form reading page. + * + * Deliberately quieter than the rest of the app: this is the one screen where + * someone reads for ten minutes straight, so it drops the CRT theatrics in + * favour of a measured column, real line-height, and figures that can breathe. + * It still belongs to the same world — same phosphor and violet — but it reads + * like a document rather than an instrument. + */ + +.secret-page { + min-height: 100vh; + min-height: 100dvh; + padding: 2rem 1.1rem 5rem; + background: radial-gradient(circle at 50% 0%, #12101c 0%, #07070c 60%); + color: #d8d4e4; +} + +.secret-inner { + max-width: 46rem; + margin: 0 auto; +} + +.secret-page h1 { + margin: 0.2rem 0 0.8rem; + font-size: clamp(2rem, 8vw, 3.2rem); + letter-spacing: 0.02em; + color: #f2eaff; +} + +.secret-page h2 { + margin: 3rem 0 0.9rem; + padding-bottom: 0.4rem; + border-bottom: 1px solid rgba(178, 107, 255, 0.28); + font-size: 1.25rem; + letter-spacing: 0.06em; + color: #b26bff; +} + +.secret-page p { + margin: 0 0 1rem; + /* Long-form: generous measure and leading, not the app's dense HUD spacing. */ + line-height: 1.72; + font-size: 1.02rem; +} + +.secret-eyebrow { + margin: 0; + font-size: 0.72rem; + letter-spacing: 0.4em; + text-transform: uppercase; + color: rgba(124, 255, 178, 0.65); +} + +.secret-lede { + font-size: 1.12rem !important; + color: #efe9fb; +} + +.secret-warn { + padding: 0.8rem 1rem; + border-left: 2px solid rgba(178, 107, 255, 0.6); + background: rgba(30, 18, 46, 0.5); + font-style: italic; +} + +.secret-danger { + padding: 1rem 1.1rem; + border: 1px solid rgba(255, 120, 120, 0.4); + border-radius: 8px; + background: rgba(46, 16, 20, 0.45); +} + +.secret-note { + padding-left: 0.9rem; + border-left: 2px solid rgba(124, 255, 178, 0.35); + color: #b9b4c8; + font-size: 0.96rem !important; +} + +/* ---- contents ---- */ + +.secret-toc { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); + gap: 0.3rem; + margin: 2rem 0 1rem; + padding: 1rem; + border: 1px solid rgba(124, 255, 178, 0.2); + border-radius: 10px; + background: rgba(12, 12, 20, 0.6); +} + +.secret-toc a { + min-height: 44px; + display: flex; + align-items: center; + padding: 0 0.5rem; + border-radius: 6px; + color: #7cffb2; + text-decoration: none; + font-size: 0.92rem; +} + +.secret-toc a:hover, +.secret-toc a:focus-visible { + background: rgba(124, 255, 178, 0.1); +} + +/* ---- traits ---- */ + +.secret-traits { + margin: 0 0 1.2rem; + display: grid; + gap: 0.55rem; +} + +.secret-traits > div { + padding: 0.75rem 0.9rem; + border-radius: 8px; + background: rgba(18, 18, 28, 0.66); +} + +.secret-traits dt { + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + color: #7cffb2; + letter-spacing: 0.06em; +} + +.secret-traits dd { + margin: 0.3rem 0 0; + line-height: 1.6; + font-size: 0.98rem; +} + +/* ---- code + tables ---- */ + +.secret-code { + margin: 0 0 1.1rem; + padding: 0.85rem 1rem; + /* The only element here that can exceed the column, so it scrolls itself + * rather than making the page scroll sideways. */ + overflow-x: auto; + border: 1px solid rgba(124, 255, 178, 0.22); + border-radius: 8px; + background: rgba(6, 10, 8, 0.8); + color: #9dffcb; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 0.88rem; + line-height: 1.7; + white-space: pre; +} + +.secret-page code { + padding: 0.05em 0.35em; + border-radius: 4px; + background: rgba(124, 255, 178, 0.1); + color: #9dffcb; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 0.9em; +} + +.secret-table-scroll { + margin: 0 0 1.2rem; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + border: 1px solid rgba(178, 107, 255, 0.22); + border-radius: 8px; +} + +.secret-table { + width: 100%; + min-width: 26rem; + border-collapse: collapse; + font-size: 0.92rem; +} + +.secret-table th, +.secret-table td { + padding: 0.6rem 0.75rem; + text-align: left; + border-bottom: 1px solid rgba(255, 255, 255, 0.07); +} + +.secret-table th { + color: #b26bff; + font-size: 0.78rem; + letter-spacing: 0.12em; + text-transform: uppercase; + white-space: nowrap; +} + +.secret-table tr:last-child td { + border-bottom: none; +} + +/* ---- figures ---- */ + +.secret-figure { + margin: 0 0 1.4rem; + padding: 0.9rem; + border: 1px solid rgba(124, 255, 178, 0.18); + border-radius: 10px; + background: rgba(10, 10, 16, 0.7); +} + +.secret-figure svg { + width: 100%; + height: auto; + display: block; +} + +.secret-figure figcaption { + margin-top: 0.6rem; + color: #a49fb6; + font-size: 0.85rem; + line-height: 1.55; +} + +.secret-axis { + stroke: rgba(255, 255, 255, 0.25); + stroke-width: 1; +} + +.secret-tick { + fill: #8f8aa3; + font-size: 9px; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; +} + +.secret-legend { + display: flex; + flex-wrap: wrap; + gap: 0.9rem; + margin-top: 0.5rem; +} + +.secret-swatch { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 2px; + vertical-align: middle; +} + +/* ---- tricks ---- */ + +.secret-tricks { + margin: 0; + padding-left: 1.2rem; + display: grid; + gap: 0.85rem; +} + +.secret-tricks li { + line-height: 1.68; + padding-left: 0.3rem; +} + +.secret-tricks b { + color: #f0e9ff; +} + +.secret-foot { + margin-top: 3.5rem; + padding-top: 1.2rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); + color: #8f8aa3; + font-size: 0.9rem; +} + +@media (max-width: 560px) { + .secret-page { + padding-left: max(0.9rem, env(safe-area-inset-left)); + padding-right: max(0.9rem, env(safe-area-inset-right)); + } + .secret-toc { + grid-template-columns: 1fr; + } +} diff --git a/frontend/src/pages/SecretPage.tsx b/frontend/src/pages/SecretPage.tsx new file mode 100644 index 0000000..e5a1e06 --- /dev/null +++ b/frontend/src/pages/SecretPage.tsx @@ -0,0 +1,674 @@ +// The Secret — /secret +// +// A hidden page. Nothing in the app links to it; you reach it by knowing the +// URL. It is left crawlable on purpose (see the sitemap), because a long, +// specific mechanics page is worth real search traffic, but it has no button +// and no nav tab. +// +// EVERY NUMBER ON THIS PAGE IS READ OUT OF THE SOURCE. Where a figure is +// derived, it was computed by running the real function rather than by +// re-deriving the formula by hand — a grimoire of plausible-sounding numbers +// would be worse than no grimoire, because it would be believed. +// +// English only, deliberately: the prose is written outside the i18n system +// rather than as several hundred translation keys. That keeps the voice sharp +// and keeps the en/es parity gate meaningful for the actual UI. If a Spanish +// grimoire is ever wanted it should be its own page with its own voice, not a +// machine translation of this one. + +import { usePageMeta } from '../lib/pageMeta' +import './SecretPage.css' + +// ---- data, lifted verbatim from the backend ------------------------------ + +/** app/passage.py: LAYERS + LAYER_ESSENCE. Sums to 53. */ +const LAYERS = [ + { id: 'listen', essence: 3, trait: 'volatility', prompt: 'how steady is it?' }, + { id: 'name', essence: 5, trait: 'deceptiveness', prompt: 'does it answer straight?' }, + { id: 'unbind', essence: 8, trait: 'power', prompt: 'how hard does the tether pull?' }, + { id: 'open', essence: 12, trait: 'alignment', prompt: 'what wants to walk through?' }, + { id: 'release', essence: 25, trait: '—', prompt: 'does it go?' }, +] + +/** app/entities.py: rarity_weights_for_moon, normalised to percentages by + * running the real function at illumination 0.0 / 0.5 / 1.0. */ +const RARITY = { + tiers: ['common', 'uncommon', 'rare', 'mythic'], + new: [55.0, 30.0, 12.0, 3.0], + half: [40.17, 31.73, 21.76, 6.35], + full: [25.11, 33.49, 31.66, 9.74], +} + +/** app/passage.py: _twist_chance sampled every 0.05. */ +const STEPS = Array.from({ length: 21 }, (_, i) => i * 0.05) +const COLLAPSE = STEPS.map((v) => (v <= 0.5 ? 0 : 0.45 * ((v - 0.5) / 0.5))) +const DECEIT = STEPS.map((v) => (v <= 0.55 ? 0 : 0.4 * ((v - 0.55) / 0.45))) + +const VERDICTS = [ + { v: 'trust', right: 'alignment ≥ 0.5', essence: 12, favor: '+0.05', wrong: '−0.10' }, + { v: 'banish', right: 'alignment < 0.5', essence: 12, favor: '+0.05', wrong: '−0.05' }, + { v: 'cross_over', right: 'benevolent AND volatility > 0.6', essence: 25, favor: '+0.08', wrong: '0.00' }, + { v: 'test', right: 'a diagnostic pulse', essence: 0, favor: '0.00', wrong: '0.00' }, +] + +const LIMITS = [ + ['summon', '4 / 60s', '8 / 60s'], + ['question', '6 / 60s', '12 / 60s'], + ['ritual', '6 / 60s', '12 / 60s'], + ['judgment', '10 / 60s', '20 / 60s'], + ['passage', '20 / 60s', '40 / 60s'], + ['fragment', '30 / 60s', '60 / 60s'], + ['scry (camera)', '4 / 60s', '8 / 60s'], +] + +const DROPS = [ + ['a correct judgment', '20%', 'trinket'], + ['a successful ritual', '25%', 'relic'], + ['summoning a rare', '15%', 'curio'], + ['summoning a mythic', '35%', 'curio'], +] + +// ---- chart primitives ---------------------------------------------------- +// +// Hand-rolled SVG rather than a charting library: these are four small static +// figures, and a dependency would cost more bundle than the whole page. + +function LineChart({ + series, + caption, +}: { + series: { points: number[]; label: string; color: string }[] + caption: string +}) { + const W = 320 + const H = 150 + const PAD = 28 + const x = (i: number) => PAD + (i / (STEPS.length - 1)) * (W - PAD * 2) + const y = (v: number) => H - PAD - (v / 0.5) * (H - PAD * 2) + + return ( +
+ + + + {series.map((s) => ( + `${x(i)},${y(v)}`).join(' ')} + /> + ))} + + 50% + + + 0 + + + 0.0 + + + 1.0 + + +
+ {caption} + + {series.map((s) => ( + + {s.label} + + ))} + +
+
+ ) +} + +function RarityChart() { + const W = 320 + const H = 170 + const PAD = 30 + const groups = RARITY.tiers.length + const bandW = (W - PAD * 2) / groups + const rows = [ + { key: 'new', label: 'new moon', color: '#4a6b8a', data: RARITY.new }, + { key: 'half', label: 'half', color: '#7cffb2', data: RARITY.half }, + { key: 'full', label: 'full moon', color: '#b26bff', data: RARITY.full }, + ] + const max = 55 + const barW = bandW / (rows.length + 1) + + return ( +
+ + + {RARITY.tiers.map((tier, gi) => + rows.map((row, ri) => { + const v = row.data[gi] + const h = (v / max) * (H - PAD * 2) + return ( + + ) + }), + )} + {RARITY.tiers.map((tier, gi) => ( + + {tier} + + ))} + +
+ Chance of each rarity tier, by how much of the moon is lit. Computed by running + rarity_weights_for_moon and normalising. + + {rows.map((r) => ( + + {r.label} + + ))} + +
+
+ ) +} + +function LadderChart() { + const W = 320 + const H = 150 + const PAD = 30 + const max = 25 + const bandW = (W - PAD * 2) / LAYERS.length + + return ( +
+ + + {LAYERS.map((l, i) => { + const h = (l.essence / max) * (H - PAD * 2) + return ( + + + + {l.essence} + + + {l.id} + + + ) + })} + +
Essence paid by each beat of the Passage. The five sum to 53.
+
+ ) +} + +// ---- the page ------------------------------------------------------------ + +export function SecretPage() { + usePageMeta({ + title: 'The Secret — Quantumancy', + description: + 'The complete mechanics of Quantumancy: entity traits, the moon and rarity, the veil, the ritual, judgment, the Passage, favor, entropy, and the tricks only the source reveals.', + }) + + return ( +
+
+
+

unlisted

+

The Secret

+

+ Nothing links here. There is no button, and there never will be. What follows is + every mechanism the instrument actually runs on, with the real constants, read + straight out of the source rather than remembered. +

+

+ Read it once and the séance stops being weather and starts being a system you can + work. Some people would rather not know. This is your last exit. +

+
+ + + +
+

I. The hidden truth

+

+ Every spirit carries four numbers, each between 0 and 1. They are decided the + moment it is minted and they never change. Everything else in the game is a + question about these four, asked in a different way. +

+
+
+
alignment
+
+ whether it means harm. At or above 0.5 it is benevolent. This is the + single number a trust or banish verdict is graded + against. +
+
+
+
power
+
how strongly it is tethered here. Makes the ritual harder.
+
+
+
volatility
+
+ how unstable it is. Above 0.6 it is stuck — and being stuck is + the precondition for crossing over. It also drives the Passage collapse. +
+
+
+
deceptiveness
+
+ how much it lies. Above 0.55 it can lie to you during the Passage, and + it makes the ritual harder. +
+
+
+

+ There is no second, secret set of stats. The ritual reveals these, the Passage + reveals these, and judgment grades against these. When a spirit lies, it does not + invent a number — it mirrors the real one: a hidden 0.8 is shown as 0.2. A + lie is therefore exactly recoverable once you know it was a lie. +

+
+ +
+

II. The moon

+

+ The app computes the true lunar phase from the synodic month (29.530588853 days) + against a known new moon, not a calendar approximation. Illumination then skews + the rarity table. This is the single largest controllable effect in the game and + it costs you nothing but patience. +

+ +

+ At new moon a mythic is a 3% lottery. At full moon it is 9.74% — + more than three times as likely. Rare goes 12% → 31.66%. Together, rare or + better goes from 15% to 41.4% of everything you summon. +

+

+ It is a skew, never a gate. Every tier stays reachable on every night, by + deliberate design — a seeker who can only play on a Tuesday is never locked out. + But if you are choosing a night, choose a full one. +

+
+ +
+

III. The veil

+

+ Call a channel you have called before and the same presence usually answers. + "Usually" is 72%, and it is reduced by how thin the veil is: +

+
+            return_chance = 0.72 × (1 − thinness × 0.45)
+          
+

+ Thinness rises toward solar midnight — real solar midnight for your longitude, if + you granted location, not local clock midnight — and with the moon. If the + geomagnetic feed is live, the final figure is blended with real space weather: +

+
thinness = thinness × 0.67 + geomagnetic_disturbance × 0.33
+

+ That disturbance is the NOAA planetary K-index, mapped from its 0–9 scale onto + 0–1. A K of 5 or above is an official geomagnetic storm. +

+

+ So a thin veil is double-edged, and this is the part most people get + backwards. A full moon at solar midnight during a storm drags the return chance + from 72% down to about 40%. Good for meeting someone new. Bad for finding + someone again. +

+
+ +
+

IV. The ritual

+

+ Four steps, then one roll. Only power and deceptiveness{' '} + matter — a strong, cagey spirit is hardest to read. Volatility deliberately does + not count here. +

+
+            difficulty = (power + deceptiveness) / 2{'\n'}
+            chance = 0.65 − 0.35 × difficulty{'\n'}
+            floor = 0.30
+          
+

+ So the kindest spirit gives you 65% and the worst possible one still gives + you 30%. There is no unwinnable entity. Success reveals all four true + traits — the whole hidden truth, handed over. +

+

+ Failing costs you nothing but the attempt, and you may attempt again. The milestone + pays once per spirit either way, so re-attempting after a success is + pointless for essence and useful only if you somehow missed the reveal. +

+
+ +
+

V. Judgment

+
+ + + + + + + + + + + + {VERDICTS.map((v) => ( + + + + + + + + ))} + +
verdictcorrect whenessencefavorif wrong
+ {v.v} + {v.right}{v.essence}{v.favor}{v.wrong}
+
+

+ Note the asymmetry: a wrong trust costs −0.10 favor, twice the + wrong banish. Trusting something malevolent is the reckless failure; + turning away something harmless is merely over-cautious. And a failed{' '} + cross_over costs nothing — it is read as a naive act of mercy, + not a blunder. Guessing cross_over is free. +

+

+ test never touches the ledger, which is why it is the only verdict you + may repeat forever. +

+
+ +
+

VI. The Passage

+

+ Five beats. Each opens one real trait, pays essence, and can go wrong. +

+ +
+ + + + + + + + + + + {LAYERS.map((l) => ( + + + + + + + ))} + +
beatrevealsessenceasks
+ {l.id} + {l.trait}{l.essence}{l.prompt}
+
+

+ Two things can go wrong, and both are linear ramps off a floor — below the floor + they are impossible, not merely unlikely. +

+ +

+ Collapse is zero at or below volatility 0.5, rising to 45% at + 1.0. It sends the rite back to listen — but your essence stays. It can + only strike name, unbind and open. +

+

+ Lying is zero at or below deceptiveness 0.55, rising to 40%. + The lie is not marked when it happens. It is struck through one beat later, when + the rite catches it. If you are reading carefully, a reveal that contradicts what + the ritual already told you is a lie you can invert on the spot. +

+

+ listen and release cannot collapse, and the reason is + load-bearing rather than cosmetic: crossing requires volatility above 0.6, so every + spirit that can cross is exactly a spirit with a high collapse chance. If + the final beat could collapse, the most crossable spirits would be the ones you + could least often finish. That would be backwards, so it is forbidden. +

+
+ +
+

VII. Favor, and its trap

+

+ Favor runs −1.0 to +1.0 and it is not a score. It is a lens. When a new spirit is + minted, your favor bends its two legibility traits: +

+
+            shift = −0.12 × favor{'\n'}
+            volatility += shift{'\n'}
+            deceptiveness += shift
+          
+

+ High favor means calmer, more honest spirits: less collapse, fewer lies, easier + rituals. Alignment and power are untouched — favor changes how readable a spirit + is, never who it is. +

+

+ And here is the trap, which almost nobody will find. Crossing over requires + volatility strictly above 0.6. At favor +1.0 every spirit you mint has its + volatility pulled down by 0.12. A spirit that would have rolled 0.65 — comfortably + crossable — arrives at 0.53, and can no longer be crossed at all. You have + made the dead calmer, and in doing so you have locked yourself out of the largest + reward in the game: 25 essence and +0.08 favor, the most compassionate act + available to you. +

+

+ Verified by running the real functions: at favor 0.0 that spirit is crossable; at + +0.5 it is not; at +1.0 it is not. If you are farming crossings, do not{' '} + maximise favor. Sit nearer the middle. +

+
+ +
+

VIII. Why you cannot cheat

+

+ The randomness is not Math.random(). Your device harvests physical + noise — the microphone's noise floor, RF hiss between stations — and that sample + is Von Neumann debiased, then conditioned through SHA-256 before it ever leaves the + page. +

+

+ On the server it is mixed by HMAC with a secret generated fresh at boot, and each + draw is domain-separated by a context string, so the collapse draw and the lie draw + on the same beat are mathematically unrelated. +

+

+ The consequence is precise and worth understanding: a client can only ever add{' '} + unpredictability. Sending a chosen or replayed entropy value cannot steer any + outcome, because your contribution is never a seed on its own — it is one input to + a keyed hash whose key you do not have. The room genuinely participates. It just + cannot be told what to say. +

+

+ The same idea drives the spirits' words: the language model is given a fixed{' '} + seed, which pins its sampling — so the noise in your room, not the + model's own dice, selects which words come out. +

+
+ +
+

IX. The economy

+

+ Every essence source, exhaustively: 2 per summon, 15 for a completed + ritual, 12 for a correct trust or banish, 25 for a correct cross_over, + and 3/5/8/12/25 for the Passage beats. +

+
+ + + + + + + + + + {DROPS.map((d) => ( + + + + + + ))} + +
drops afterchancekind
{d[0]}{d[1]}{d[2]}
+
+

+ Note that a mythic summon drops at 35% — the highest rate in the game, and + another reason the full moon matters. +

+

+ Every milestone pays once per spirit, permanently. Not once per session, not + once per connection: there is a durable ledger, so re-walking a rite, reconnecting, + or opening a second tab pays nothing further. Re-walking still reveals — it just + does not mint. The only way to earn again is a genuinely new presence. +

+
+ + + + + + + + + + {LIMITS.map((l) => ( + + + + + + ))} + +
actionper accountper address
{l[0]}{l[1]}{l[2]}
+
+
+ +
+

X. The tricks

+
    +
  1. + Hunt on a full moon. Rare-or-better goes from 15% to 41.4% of summons. It + is the largest lever you have and it is free. +
  2. +
  3. + Do not max your favor if you want crossings. Above roughly +0.4 you start + pulling spirits under the 0.6 volatility gate and making them uncrossable. This + is the single most counter-intuitive fact in the game. +
  4. +
  5. + Guessing cross_over is free. A failed cross_over costs zero favor. If a + spirit reads benevolent and unstable, always try it before settling for trust. +
  6. +
  7. + Run the ritual before judging. Success hands you all four true numbers, + which turns every subsequent verdict from a read into a lookup. +
  8. +
  9. + A thin veil is for meeting, not returning. Solar midnight, full moon and a + geomagnetic storm cut your chance of reaching a known spirit again by nearly half. + Do your reunions in daylight on a quiet sky. +
  10. +
  11. + Catch the lie by contradiction. Lies are mirrored, not random. If a + Passage reveal disagrees with what the ritual already showed you, invert it and + you have the truth exactly. +
  12. +
  13. + A spirit under 0.55 deceptiveness cannot lie to you at all. Not "rarely" — + the chance is exactly zero. The same is true of collapse under 0.5 volatility. + Once the ritual shows you those numbers, you know precisely which risks are off + the table. +
  14. +
  15. + Wrong banish is cheaper than wrong trust. Half the penalty. When you are + genuinely torn and the stakes are favor, err toward caution. +
  16. +
  17. + Grant location. Without it, solar midnight cannot be computed for you and + you lose that lever on the veil entirely. +
  18. +
  19. + Feed the room. The microphone and the sensors are not decoration — they + are the actual entropy source. A silent, shielded room gives the summoning less + to work with. +
  20. +
+
+ + +
+
+ ) +}