Back to Outbreak: Soycoon City
SOURCE / PINNED RELEASE

Made of little things.

Outbreak: Soycoon City

Release
db7750b1d009…
Author-recorded commit
3ee3f5fd27a6…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-b87c04571a9

Archive hash verified: dbebdc772b142a1c…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

index.html
<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"/><title>Outbreak: Soycoon City</title></head>
<body>
<main id="app">
  <header class="command-bar">
    <div class="precinct-mark"><span class="seal" aria-hidden="true">✦</span><span>S.C.P.D.<small>INCIDENT / 001</small></span></div>
    <nav class="mode-switch" aria-label="Workspace"><button id="playTab" class="active">Play</button><button id="editorTab">Editor</button><button id="levelsTab">Levels</button></nav>
    <button id="soundButton" class="sound-button" aria-label="Game sound" aria-pressed="false">Enable sound</button>
    <button id="helpButton" class="icon-button" aria-label="Help and controls">?</button>
  </header>
  <section id="playView" class="play-layout">
    <div class="play-main">
      <div class="location"><div><span class="overline">SOYCOON CITY POLICE DEPARTMENT</span><h1 id="roomTitle">Evidence Room</h1></div><span id="cameraLabel" class="camera-label">CAMERA 01</span></div>
      <div class="stage-surround"><div id="gameStage" class="game-stage"><div id="scene"></div><div class="scanlines" aria-hidden="true"></div><div id="hitFlash" aria-hidden="true"></div><div id="sceneState" class="scene-state"><span id="stateCaption">SEPTEMBER 29, 1998 · 02:14 AM</span><strong id="stateTitle">Something is still moving.</strong><button id="startButton" class="primary">Begin patrol <span>→</span></button></div><div id="doorHint" class="door-hint" hidden>Door nearby · E / Enter</div></div></div>
      <div class="game-hud"><div class="health"><span class="overline">CONDITION</span><div><svg viewBox="0 0 104 28" aria-hidden="true"><path d="M0 15H19L24 10L29 20L34 4L39 26L45 15H65L70 12L74 17L80 15H104"/></svg><strong id="healthLabel">Fine</strong></div></div><div class="ammo"><span class="overline">PUMP SHOTGUN</span><div><span id="shells">● ● ● ● ● ●</span><strong><span id="ammoCount">06</span><span class="ammo-divider"> / </span><span id="reserveCount">24</span></strong></div></div><button id="pauseButton" class="icon-button" aria-label="Pause game">Ⅱ</button></div>
      <div class="game-message"><span class="signal-dot"></span><output id="gameMessage">Clear the precinct. Keep your distance.</output><span class="ps1-tag">PS1 / 4:3</span></div>
      <div class="touch-controls" aria-label="Touch game controls"><div class="dpad"><button data-hold="forward" aria-label="Walk forward">▲</button><button data-hold="left" aria-label="Turn left">◀</button><button data-hold="backward" aria-label="Walk backward">▼</button><button data-hold="right" aria-label="Turn right">▶</button></div><div class="touch-actions"><button data-hold="aim">Aim</button><button data-hold="aimUp">Aim ↑</button><button data-action="shoot" class="fire-button">Fire</button><button data-action="reload">Reload</button><button data-action="interact">Interact</button></div></div>
    </div>
    <aside class="field-panel">
      <div class="case-heading"><span class="overline">FIELD REPORT</span><span class="case-number">01</span><h2>The night shift</h2><p>The precinct went quiet.<br>The evidence didn’t.</p></div>
      <div class="objective"><span class="overline">YOUR OBJECTIVE</span><p>Clear the precinct.</p><div class="objective-progress"><span id="killProgress">0 / 8 threats neutralized</span><div><i id="progressBar"></i></div></div></div>
      <div class="map-section"><div class="section-label"><span class="overline">FLOOR 01</span><span class="tiny">● YOU ARE HERE</span></div><canvas id="miniMap" aria-label="Map of connected precinct rooms"></canvas><p id="mapLocation" class="map-location">01 / Evidence Room</p></div>
      <div class="controls-reference"><span class="overline">SURVIVAL BASICS</span><div><kbd>W A S D</kbd><span>Move / turn</span></div><div><kbd>K + SPACE</kbd><span>Aim / fire</span></div><div><kbd>I</kbd><span>Aim up</span></div><div><kbd>SHIFT</kbd><span>Run</span></div><div><kbd>R</kbd><span>Reload</span></div><div><kbd>E</kbd><span>Door / herb</span></div><small>Tank controls. Facing stays the same when the camera changes.</small></div>
      <p id="controllerStatus" class="controller-state">Controller · press a button to connect</p><div class="agent"><img id="mascotPortrait" alt="Soybert holding his pump shotgun"/><div><span class="overline">ON DUTY</span><strong>Soybert</strong><small>Special tofu unit</small></div></div>
    </aside>
  </section>
  <section id="editorView" class="editor-layout" hidden>
    <aside class="room-sidebar"><div class="section-label"><span class="overline">ROOMS / FLOOR 01</span><button id="addRoom" aria-label="Add room">+</button></div><div id="roomList"></div><div class="editor-file-actions"><button id="saveMap">Save draft</button><button id="loadMap">Load draft</button><button id="exportMap">Export map</button><button id="importMap">Import map</button></div><details><summary>Map JSON</summary><textarea id="mapJson" aria-label="Map JSON" placeholder="Export or paste a map here"></textarea><button id="applyJson">Apply JSON</button><button id="showJson">Show current JSON</button></details><details class="level-settings"><summary>Level settings</summary><label>Level name<input id="stageName" maxlength="160"/></label><label>Starting health<input id="ruleHealth" type="number" min="25" max="100"/></label><label>Reserve shells<input id="ruleReserve" type="number" min="0" max="96"/></label><label>Zombie health<input id="ruleZombieHealth" type="number" min="1" max="8"/></label><label>Zombie speed (m/s)<input id="ruleZombieSpeed" type="number" min=".1" max="2" step=".05"/></label><label>Bite damage<input id="ruleBite" type="number" min="1" max="50"/></label></details><div id="hostDomains" class="host-domains" aria-label="Injected host domains"></div></aside>
    <div class="plan-column"><div class="plan-heading"><div><span class="overline">LAYOUT / TOP DOWN</span><h2 id="planTitle">Floor plan</h2></div><button id="testMap" class="primary">Test stage →</button></div><div class="editor-view-switch" aria-label="Editor views"><button id="roomPlanTab" class="active">Room plan</button><button id="precinctMapTab">Precinct map</button></div><section id="roomPlanPanel" class="room-plan-panel"><div class="plan-tools" role="toolbar" aria-label="Floor plan tools"><button data-tool="select" class="active" title="Select and move">↖ <span>Select</span></button><button data-tool="prop">▣ <span>Prop</span></button><button data-tool="camera">◎ <span>Camera</span></button><button data-tool="dress">▧ <span>Dress</span></button><button data-tool="zone">◩ <span>Zone</span></button><button data-tool="herb">✿ <span>Herb</span></button><button data-tool="zombie">✕ <span>Zombie</span></button><button data-tool="spawn">S <span>Spawn</span></button><select id="propKind" aria-label="Prop type"><option value="wall">Wall</option><option value="shelf">Shelf</option><option value="desk">Desk</option><option value="crate">Crate</option><option value="bench">Bench</option></select><select id="dressingKind" aria-label="Dressing type"><option value="pinboard">Pinboard</option><option value="photo">Framed photo</option><option value="poster">Poster</option><option value="papers">Floor papers</option><option value="cleaning">Brooms & bucket</option><option value="pipes">Service pipes</option></select></div><p class="editor-hint">Drag ◎ camera and + target. Polygon zones: tap vertices, then Finish. Select Zone to drag its handles.</p><p id="cameraCoverage" class="camera-coverage" role="status"></p><div class="plan-wrap"><canvas id="plan" aria-label="Editable room floor plan"></canvas></div><div class="plan-footer"><span id="selectedLabel">Nothing selected</span><button id="deleteSelection">Delete selected</button></div><div class="room-properties"><label>Room name<input id="roomName" maxlength="100"/></label><div class="field-row"><label>Width (m)<input id="roomWidth" type="number" min="3" max="30" step=".25"/></label><label>Depth (m)<input id="roomDepth" type="number" min="3" max="30" step=".25"/></label></div><details><summary>Room description & connections</summary><label>Art description<textarea id="roomDescription" rows="3"></textarea></label><div id="doorList"></div><label>Connect to<select id="connectTarget"></select></label><div class="field-row"><label>This wall<select id="connectSide"><option>east</option><option>west</option><option>north</option><option>south</option></select></label><label>Other wall<select id="targetSide"><option>west</option><option>east</option><option>north</option><option>south</option></select></label></div><div class="field-row"><label>This offset (m)<input id="connectOffset" type="number" step=".25" value="0"/></label><label>Other offset (m)<input id="targetOffset" type="number" step=".25" value="0"/></label></div><button id="connectRooms">Add paired doors</button><button id="deleteRoom" class="danger-button">Delete room</button></details></div></section>
      <section id="precinctMapPanel" class="precinct-map-panel" hidden>
        <div class="map-toolbar"><button id="mapMove" class="active">Move / select</button><button id="mapConnect">Connect doors</button><button id="mapFit">Fit</button><button id="mapZoomOut" aria-label="Zoom map out">−</button><button id="mapZoomIn" aria-label="Zoom map in">+</button></div>
        <p class="editor-hint">Move: drag room centers or door dots. Connect: tap two room edges, or drag between them. Drag empty space to pan.</p>
        <div class="map-wrap"><canvas id="mapCanvas" aria-label="Graphical precinct room positions and door connections"></canvas></div>
        <div class="map-footer"><output id="mapSelection">Choose a room or connection</output><button id="removeLink" class="danger-button" disabled>Remove connection</button></div>
        <div class="field-row map-coordinates"><label>Selected room · map X<input id="roomMapX" type="number" step=".5"/></label><label>Map Z<input id="roomMapZ" type="number" step=".5"/></label></div>
        <p class="editor-hint">Gold lines pair two doors. Each wall can hold several spaced openings. Move rooms to arrange the map; door dots set the playable entrances. Use Room plan for furniture and cameras.</p>
      </section>
    </div>
    <aside class="camera-panel"><div class="section-label"><span class="overline">CAMERA SETUP</span><button id="addCamera" aria-label="Add camera">+</button></div><div id="cameraList" class="camera-list"></div><label>Camera name<input id="cameraName"/></label><div id="cameraPreview" class="camera-preview"></div><div class="preview-caption"><button id="previewMode">Faces + edges</button><span>4:3 / FIXED</span></div><div class="camera-presets" aria-label="Camera angle presets"><button data-camera-preset="classic">Classic</button><button data-camera-preset="eye">Eye level</button><button data-camera-preset="frog">Frog view</button></div><label>Camera height <output id="heightLabel"></output><input id="cameraHeight" type="range" min=".25" max="40" step=".05"/></label><label>Lens <output id="lensLabel"></output><input id="cameraLens" type="range" min="25" max="90" step="1"/></label><p class="editor-hint">Drag the two handles in Room plan. Sliders and presets update the preview live.</p><details class="camera-numbers"><summary>Exact position & lens</summary><div class="field-row triple"><label>X<input id="camX" type="number" step=".1"/></label><label>Height<input id="camY" type="number" step=".1"/></label><label>Z<input id="camZ" type="number" step=".1"/></label></div><div class="overline form-section">LOOK TARGET</div><div class="field-row triple"><label>X<input id="targetX" type="number" step=".1"/></label><label>Y<input id="targetY" type="number" step=".1"/></label><label>Z<input id="targetZ" type="number" step=".1"/></label></div><label>Vertical field of view (°)<input id="camFov" type="number" min="25" max="90"/></label></details><details><summary>Trigger area</summary><label>Shape<select id="zoneShape"><option value="rectangle">Rectangle</option><option value="polygon">Polygon</option></select></label><div class="zone-actions"><button id="drawPolygon">Draw polygon</button><button id="finishPolygon">Finish</button><button id="undoPolygon">Undo point</button><button id="cancelPolygon">Cancel</button></div><p id="zoneHelp" class="editor-hint">Polygon vertices are draggable in the plan with Zone selected.</p><div class="field-row"><label>Priority<input id="cameraPriority" type="number" min="0" max="100" value="0"/></label><label class="checkbox-label"><input id="cameraFallback" type="checkbox"/>Frame recovery</label></div><label>Door approach<select id="cameraDoor"></select></label><button id="frameDoor">Frame selected door</button><div id="zoneRectFields"><div class="field-row"><label>Center X<input id="zoneX" type="number" step=".25"/></label><label>Center Z<input id="zoneZ" type="number" step=".25"/></label></div><div class="field-row"><label>Width<input id="zoneWidth" type="number" step=".25"/></label><label>Depth<input id="zoneDepth" type="number" step=".25"/></label></div></div></details><div class="art-workflow"><span class="overline">PRERENDER WORKFLOW</span><p>Schema → image AI → background</p><button id="exportSchema" class="primary">Export schema PNG ↗</button><div class="field-row"><button id="exportPrompt">Export prompt</button><button id="exportCamera">Camera JSON</button></div><button id="importBackdrop">Import background</button><small id="artStatus">Baked scene / bundled art</small><details><summary>Base style & shot prompt</summary><label>Shared art direction<textarea id="baseStyle" rows="6"></textarea></label><label>Derived shot prompt<textarea id="shotPrompt" rows="8" readonly></textarea></label><p>Keep the camera and geometry unchanged in image generation. Inspect alignment before using the image.</p></details><button id="removeBackdrop">Remove custom art</button><button id="deleteCamera" class="danger-button">Delete camera</button></div></aside>
  </section>
    <section id="levelsView" class="levels-layout" hidden aria-label="Player levels">
    <aside class="level-publish-panel">
      <span class="overline">PLAYER CREATIONS / PUBLIC</span><h2>Share a level</h2>
      <p class="muted">Publish your editor map and linked backgrounds so other players can load it.</p>
      <label>Title<input id="levelTitle" maxlength="160"/></label>
      <label>Description<textarea id="levelDescription" maxlength="2000" rows="3" placeholder="What awaits Soybert?"></textarea></label>
      <p id="levelIdentity" class="muted">Browse anonymously; connect your Nostr identity in the host to publish.</p>
      <p id="levelSelection" class="muted"></p>
      <button id="publishLevel" class="primary">Publish as new level</button>
      <div class="field-row"><button id="updateLevel" disabled>Update selected</button><button id="retryLevel" disabled>Retry publication</button></div>
      <button id="unpublishLevel" class="danger-button" disabled>Unpublish selected</button>
      <p class="editor-hint">Uploads and the Nostr record use host approval. Publication is public. Retry uses the prepared snapshot; new editor changes remain local.</p>
      <details><summary>Selected level address</summary><textarea id="levelAddress" readonly aria-label="Selected level address" rows="7"></textarea></details>
      <details><summary>Open a shared address</summary><label>Paste level address JSON<textarea id="sharedLevelAddress" rows="4"></textarea></label><button id="lookupLevel">Look up level</button></details>
    </aside>
    <div class="level-library-panel"><div class="repository-picker"><label>Checked-in levels<select id="repositoryLevel"></select></label><button id="openRepositoryLevel">Play selected</button></div><div class="plan-heading"><div><span class="overline">COMMUNITY / RECENT</span><h2>Player levels</h2></div><button id="refreshLevels">Refresh levels</button></div><p id="levelStatus" class="level-status" role="status">Refresh to browse recent levels from the host’s configured storage relay.</p><div id="levelList" class="level-list"></div></div>
  </section>
<output id="status" class="status" aria-live="polite" hidden></output>
  <div id="helpPanel" class="help-panel" hidden><button id="closeHelp" class="close-help" aria-label="Close help">×</button><span class="overline">SURVIVAL MANUAL</span><h2>Stay alive. Keep moving.</h2><p>W/S move forward and backward. A/D turn Soybert. Arrow keys also work. Hold Shift to run, hold K to turn carefully while aiming, and press Space or J to fire. Hold I (or K + Up) to aim upward: close shotgun blasts destroy heads. R reloads; E interacts with a door or uses a green herb. Escape pauses.</p><p>Clear every room to finish. Your shotgun is strongest at close range. Shelves and walls stop shots. Doorways connect the rooms; the demo skips the door animation.</p><h3>Sound</h3><p id="audioStatus">Click Begin patrol to enable sound.</p><p>Sound on/off is available in the top bar. Available recordings play during patrol; pausing, leaving gameplay or losing focus stops playback.</p><h3>Controller</h3><p id="controllerHelpStatus" class="muted">Press a button to connect.</p><p>Left stick or D-pad: tank movement. LT: aim. RT: fire. Hold LT and push the right stick up to aim high. X / □: reload. A / ×: door or herb. B / ○: run. Start: begin, resume or pause. Losing controller connection pauses the game.</p><h3>Build your own precinct</h3><p>In Stage editor, select a room or add one. Drag props and camera zones on the floor plan. Tap to place zombies or Soybert’s spawn. Drag the camera and look-target handles; use the height/lens sliders or Frog view preset for a low perspective. The exact fields remain available. In Precinct map, drag rooms and connect wall points; drag door dots to reposition endpoints, or select and remove a link. Multiple doors can share a wall when spaced apart. Test stage restarts the game with your current map.</p><p>Export the schema PNG and its prompt. Use them together in an image AI, then import a 4:3 PNG/JPEG/WebP (under 2 MB) as the camera background. Export map includes custom art; Save draft keeps geometry and prompts. Exported files appear in the host’s Files panel for download.</p><p class="muted">First demo · initial audio batch; more sound cues, stacked floors and inventory are still coming.</p></div>
  <div class="minimum-notice"><span class="seal">✦</span><strong>The precinct needs a little more room.</strong><p>Expand to 280 × 300 or open full screen to play and edit. Your session is preserved.</p></div>
</main><script type="module" src="/src/main.ts"></script>
</body></html>