SOURCE / PINNED RELEASE
Made of little things.
Shiftfield
- Release
- 5a170c505f6b…
- Author-recorded commit
- a2e23e0171aa…
- License
- LICENSE
- Author’s source reference
- nostr://npub16nzc4ql748r2kv9202rgzrt0fmtk4n3tzsjl86yf6ty9agqe9u0qjcc7hc/wss%3A%2F%2Fgit.napplet.soy%2F/n-e4fb6341ccd
Archive hash verified: 66de7623efd91da1…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Shiftfield</title>
<meta name="description" content="— the Roguelike Minesweeper —
Clear sectors, bank scrap, die rich. Explore deeper depths for more riches, but watch out for hazards, and the ground that shifts beneath your feet.
Built 100% locally and offline with StrataLLM and Qwen3.8 Flash Next" />
</head>
<body>
<!--
Shiftfield — playable game shell (Phase 2.5 integration).
Boots the real GameEngine (chaos layer over the Phase 1 world).
Phase 3 replaces the cleared/dead overlays with the decision,
summary, and workshop screens.
-->
<main id="app" class="app">
<header class="hud" aria-label="Run status">
<span class="stat">D<span class="stat-key">epth</span> <b id="depth">0</b></span>
<span class="stat">T<span class="stat-key">urn</span> <b id="turn">0</b></span>
<span class="stat">F<span class="stat-key">low</span> <b id="flow">0</b></span>
<span class="stat stat-runscrap" id="runScrapStat" title="Scrap earned this run (lost on death unless banked)">◈<span class="stat-key"> Scrap</span> <b id="runScrap">0</b></span>
<span class="stat stat-scrap" title="Banked scrap (safe)">⛃<span class="stat-key"> Banked</span> <b id="scrap">0</b></span>
<span class="stat stat-shift" id="shiftStat" hidden>
⇄ <b id="shiftArrow">—</b> in <b id="shiftIn">—</b>
</span>
<span class="stat stat-shield" id="shieldStat" hidden>⛨ <b id="shieldCount">0</b></span>
<output id="status" class="status status-idle" aria-live="polite">Ready</output>
<button type="button" id="sfxToggle" class="icon-btn" aria-label="Toggle sound" aria-pressed="false" title="Sound on — click to mute">🔊</button>
<!-- Field Manual mid-run entry (ruling 2026-10-28, how_to_play_menu_ruling):
opens the manual landed on Tools & Workshop, without disturbing the run. -->
<button type="button" id="helpToggle" class="icon-btn" aria-label="Field Manual" title="Field Manual — tools & workshop reference">?</button>
</header>
<section id="boardHost" class="board-host" aria-label="Shiftfield grid"></section>
<!-- Depth-6 Shifter awakening (ruling 2026-10-28): one-shot per run.
BLOCKING modal — the run cannot continue until acknowledged (ruling
2026-10-28 follow-up). Full-viewport backdrop intercepts board
clicks; panel sits centered so the board and its wind stay visible
behind the dim. -->
<div id="shifterIntro" class="intro-modal" hidden>
<div class="intro-panel" role="dialog" aria-modal="true" aria-labelledby="shifterIntroTitle">
<b id="shifterIntroTitle">You sense a tremble beneath the earth</b>
<span>The mines are shifting. Watch the telegraphed movement —
and know that your actions aren't the only thing that wraps around
the board...</span>
<button type="button" id="shifterIntroOk">Understood</button>
</div>
</div>
<!-- ONE-TIME runner-name gate (first-run name gate ruling 2026-10-29):
interrupts the FIRST run only — while the board is available and the
player has never been asked (save.name_prompted). Blank + confirm is
a valid choice (run unnamed); the gate never interrupts again. The
summary field stays as the later rename path. -->
<div id="nameGate" class="intro-modal" hidden>
<div class="intro-panel" role="dialog" aria-modal="true" aria-labelledby="nameGateTitle">
<b id="nameGateTitle">Choose your runner name</b>
<span>The leaderboard remembers your deepest depth. Your name rides
every score you post — leave it blank to run unnamed.</span>
<label class="summary-name" for="nameGateInput">Runner name
<input id="nameGateInput" type="text" maxlength="24" placeholder="unnamed-runner" autocomplete="off" spellcheck="false">
</label>
<button type="button" id="nameGateOk">Enter The Depths</button>
</div>
</div>
<footer class="abilitybar" role="toolbar" aria-label="Abilities">
<button type="button" data-ability="radar" title="Radar Pulse — select, then click a tile: reveals safe tiles in a sweep around it">
<span class="label">Radar</span> <span class="cost">50f</span><span class="cd" data-cd="radar"></span>
</button>
<button type="button" data-ability="seeker" title="Seeker Drone — flags one mine per Workshop level">
<span class="label">Seeker</span> <span class="cost">50f</span><span class="cd" data-cd="seeker"></span>
</button>
<button type="button" data-ability="stabilizer" title="Stabilizer — freezes the Shifter countdown (buy levels in the Workshop)">
<span class="label">Stabilize</span> <span class="cost">30f</span><span class="cd" data-cd="stabilizer"></span>
</button>
</footer>
<!-- MENU — first screen of the §4.1 loop; Enter The Depths → RUNNING, Workshop → SHOP -->
<div id="menuScreen" class="overlay" hidden>
<div class="panel panel-anim" role="dialog" aria-modal="true" aria-labelledby="menuTitle">
<h2 id="menuTitle">Shiftfield</h2>
<p class="menu-tag">Roguelike minefield — clear sectors, bank scrap, die rich.</p>
<p class="menu-stats" id="menuStats"></p>
<div class="panel-actions">
<button id="startRunBtn" type="button">Enter The Depths</button>
<button id="menuWorkshopBtn" class="btn-secondary" type="button">Workshop</button>
<!-- Field Manual entry (ruling 2026-10-28, how_to_play_menu_ruling). -->
<button id="menuFieldManualBtn" class="btn-secondary" type="button">Field Manual</button>
<button id="menuLeaderboardBtn" class="btn-secondary" type="button" hidden>Leaderboard</button>
</div>
<p class="stub-note" id="saveNote" hidden></p>
</div>
</div>
<!-- DECISION — bank vs continue after a sector clear (§4.1 step 2) -->
<div id="decisionScreen" class="overlay" hidden>
<div class="panel panel-anim" role="dialog" aria-modal="true" aria-labelledby="decisionTitle">
<h2 id="decisionTitle">Sector cleared</h2>
<p id="decisionBody"></p>
<div class="panel-actions">
<button id="bankBtn" type="button">Bank & Exit</button>
<button id="continueBtn" class="btn-secondary" type="button">Go Deeper</button>
</div>
<p class="stub-note">Scrap in the vault is safe. An explosion destroys some of what you carry.</p>
</div>
</div>
<!-- SUMMARY — run over (§4.1 steps 3/5): banked exit or 50% death salvage -->
<div id="summaryScreen" class="overlay" hidden>
<div class="panel panel-anim" role="dialog" aria-modal="true" aria-labelledby="summaryTitle">
<h2 id="summaryTitle">Boom</h2>
<p id="summaryCause"></p>
<dl class="summary-ledger" id="summaryLedger"></dl>
<label class="summary-name" for="playerNameInput">Runner name
<input id="playerNameInput" type="text" maxlength="24" placeholder="unnamed-runner" autocomplete="off" spellcheck="false">
</label>
<p class="stub-note" id="summaryBoardNote" hidden></p>
<div class="panel-actions">
<button id="summaryWorkshopBtn" type="button">Workshop</button>
<button id="summaryBoardBtn" class="btn-secondary" type="button" hidden>Leaderboard</button>
<button id="summaryMenuBtn" class="btn-secondary" type="button">Menu</button>
</div>
</div>
</div>
<!-- LEADERBOARD — shared depth-v1 board (ruling 2026-10-28). Row names are
untrusted public data: rendered with textContent, never innerHTML. -->
<div id="boardScreen" class="overlay" hidden>
<div class="panel panel-anim" role="dialog" aria-modal="true" aria-labelledby="boardTitle">
<h2 id="boardTitle">Deepest Depth</h2>
<p id="boardNote"></p>
<dl class="summary-ledger board-rows" id="boardRows"></dl>
<p class="stub-note">Scores are player-reported; each runner's deepest board counts. Your identity lives in this browser — clearing site data starts a fresh runner.</p>
<div class="panel-actions">
<button id="boardRefreshBtn" type="button">Refresh</button>
<button id="boardCloseBtn" class="btn-secondary" type="button">Back</button>
</div>
</div>
</div>
<!-- FIELD MANUAL — how-to-play overlay (ruling 2026-10-28,
how_to_play_menu_ruling): 3 merged tabs, static approved copy only
(no engine reads). MENU opens Basics; the HUD `?` lands on Tools &
Workshop mid-run; Back hands the underlying screen straight back —
no §4.1 state change, the overlay never alters the phase.
The three explosion causes reuse the ratified player-word labels
(ui_status_copy_ruling); engine enums must never appear here —
pinned by the field-manual-tabs browser check. -->
<div id="helpScreen" class="overlay" hidden>
<div class="panel panel-help panel-anim" role="dialog" aria-modal="true" aria-labelledby="helpTitle">
<h2 id="helpTitle">Field Manual</h2>
<div class="help-tabs" role="tablist" aria-label="Manual sections">
<button type="button" class="help-tab active" role="tab" id="helpTabBasics" aria-selected="true">Basics</button>
<button type="button" class="help-tab" role="tab" id="helpTabHazards" aria-selected="false">Hazards & The Shift</button>
<button type="button" class="help-tab" role="tab" id="helpTabTools" aria-selected="false">Tools & Workshop</button>
</div>
<div class="help-body">
<section class="help-tab-panel" id="helpPanelBasics" role="tabpanel" aria-labelledby="helpTabBasics">
<h3>The Loop</h3>
<p>Your task is to clear a mine field one sector at a time, and maybe get some loot while you're at it.</p>
<ul>
<li><b>Left-click / tap</b> — Reveals a tile. A safe move rewards you with <b>Flow</b> and <b>Scrap</b>.</li>
<li><b>Right-click / long-press</b> — Places a flag which locks down the tile. A locked tile prevents anything from moving in, out, or through it.</li>
<li>Open every non-mine tile and the sector clears.</li>
<li>After each sector clear, you may choose to <b>Bank & Exit</b> to save your scrap, or <b>Go Deeper</b> to risk the depths for higher rewards.</li>
<li><b>Death destroys half of what you carry.</b> Bank your scrap to protect it.</li>
<li>Between runs, the <b>Workshop</b> turns banked scrap into upgrades and abilities.</li>
</ul>
<p>Controls: left-click reveal · right-click flag · tap reveal · long-press flag.</p>
<h3>Flow & Scrap</h3>
<p><b>Flow</b> powers abilities.</p>
<ul>
<li>Ceiling 100 by default. Raise the cap with upgrades in the Workshop.</li>
</ul>
<p><b>Scrap</b> is the run economy.</p>
<ul>
<li>Deeper sectors have more scrap, at the risk of more hazards.</li>
<li><b>Bank</b> at any decision screen to move your haul to the vault.</li>
<li>Scrap is spent in the Workshop on upgrades and abilities to aid later runs.</li>
</ul>
<h3>Death & Banking</h3>
<p>A run ends when something explodes. You, specifically. There are a few ways an explosion can occur:</p>
<ol>
<li><b>A mine detonated</b> — you opened a mine, or a shifted mine landed on ground you had already opened.</li>
<li><b>Mines collided</b> — a shift forced two mines into one tile.</li>
<li><b>A mine ignited</b> — fire spread to a mine, or a mine shifted beneath the flames.</li>
</ol>
<p>A shield charge absorbs any one of these (see Tools & Workshop). Without one — <b>Boom</b>: half of your unbanked scrap burns with you.</p>
<p><b>Turn law:</b> only <i>effective</i> actions advance the world. Blocked clicks, rejected flags and refused abilities cost nothing and tick nothing.</p>
</section>
<section class="help-tab-panel" id="helpPanelHazards" role="tabpanel" aria-labelledby="helpTabHazards" hidden>
<h3>The Shift</h3>
<p>From <b>depth 6</b> the mines themselves start moving.</p>
<ul>
<li>Every shift is <b>telegraphed</b>: the HUD chip shows <b>⇄ direction — in N turns</b>, and amber wind streaks blow across the board in that direction. The last three turns warn loudly.</li>
<li>The board <b>wraps</b>: mines leaving one edge re-enter at the opposite edge. The amber edge chevrons and corner brackets are permanent — <b>wrap is always on</b>, at every depth.</li>
<li>A <b>flag is an anchor</b>. Mines can never move into or out of a flagged tile — a mine whose path is blocked by your flag simply stays put.</li>
<li>Shifting can force two mines into the same tile if they are blocked by a flag. This will trigger an explosion unless a <b>shield charge</b> eats it and protects you.</li>
<li>Ice does <b>not</b> stop mines. Only flags do.</li>
<li>Shift cadence tightens with depth; the <b>Stabilizer</b> freezes the countdown.</li>
</ul>
<h3>Hazards</h3>
<p>Sectors past depth 10 come with additional hazards.</p>
<p><b>Ice</b> — frozen ground that is too hard to dig through for a time. It blocks <i>you</i>, not the mines.</p>
<p><b>Fire</b> — burns on hidden ground.</p>
<ul>
<li>On <b>odd turns</b>, each fire has a chance to ignite some neighboring tiles. Clear it before it spreads too far.</li>
<li>Fire is only deadly when it shares a tile <b>with a mine</b> — fire next to a mine does nothing, forever.</li>
<li>Revealing a fire tile puts it out. <b>Flagging</b> a fire tile puts it out as well (and anchors the ground).</li>
<li>Ice blocks fire spread.</li>
</ul>
</section>
<section class="help-tab-panel" id="helpPanelTools" role="tabpanel" aria-labelledby="helpTabTools" hidden>
<p>Abilities start <b>locked</b> — buy upgrades in the Workshop to use them.</p>
<ul>
<li><b>Radar Pulse</b> <i>(50f, 2T cooldown)</i> — select, then click: sweeps a patch of safe tiles open around the target. Grows with Radar Array levels: 3×3 → 21-tile circle → full 5×5. Radar pays <b>no Flow</b> and never floods its own sweep.</li>
<li><b>Seeker Drone</b> <i>(50f, 3T cooldown)</i> — auto-flags 1 mine (2, then 3 at higher levels).</li>
<li><b>Stabilizer</b> <i>(30f, 10T cooldown)</i> — freezes the shift countdown for 3 turns (5 / 7 / 10 at higher levels). At max, recast on cooldown and the Shifter never fires.</li>
<li><b>Shield Generator</b> — increases the number of <b>shield charges the run starts with</b>. A charge absorbs one mine hit, then it's gone until the next run. Charges carry across sectors.</li>
<li><b>Data Ledger</b> — +25% run scrap per level.</li>
<li><b>Flow Capacitor</b> — +100 Flow ceiling per level.</li>
</ul>
</section>
</div>
<div class="panel-actions">
<button id="helpBackBtn" type="button">Back</button>
</div>
</div>
</div>
<!-- SHOP / Workshop — tiered permanent upgrades (§4.1 step 6) -->
<div id="shopScreen" class="overlay" hidden>
<div class="panel panel-shop panel-anim" role="dialog" aria-modal="true" aria-labelledby="shopTitle">
<h2 id="shopTitle">Workshop</h2>
<p class="shop-scrap">⛃ <b id="shopScrap">0</b> scrap</p>
<div class="upgrade-list" id="upgradeList"></div>
<div class="panel-actions">
<button id="shopStartBtn" type="button">Enter The Depths</button>
<button id="shopBackBtn" class="btn-secondary" type="button">Back</button>
</div>
</div>
</div>
</main>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
