BeamNG.Earth
A 1:1 scale streamed Earth for BeamNG.drive: real elevation and satellite imagery, floating origin, LOD streaming from a companion server
翻译准备中
正在加载完整 README。 以下为已保存的摘录。
Player- and Handler-facing tools for a Delta Green campaign, published as a static site via GitHub Pages, installable as an offline-capable PWA.
Live site: enable GitHub Pages on this repo (Settings → Pages → Deploy from branch → main → / (root)) and it'll be served at https:// .github.io/ / — this repo is dg, so https://turulsen.github.io/dg/.查看完整 README ↗Player- and Handler-facing tools for a Delta Green campaign, published as a static site via GitHub Pages, installable as an offline-capable PWA.
Live site: enable GitHub Pages on this repo (Settings → Pages → Deploy from branch → main → / (root)) and it'll be served at https://<your-username>.github.io/<repo-name>/ — this repo is dg, so https://turulsen.github.io/dg/.
index.html is a three-clearance landing page (after a one-time, session-gated boot-splash animation): Agent, for players, Friendly, for one-shot players with a ready-made pregen, and A-Cell, for the Handler, password-gated. The three are aged manila folders lying fanned on the black page, labelled by hand (since v3.4.0). Both lead into a shared "manila folder on a desk" visual system (assets/theme-folder.css, with the patina as CSS tokens) used across every hub page; the Field Notes notebook shares that same paper/ink/typewriter system directly rather than having a separate look.
| Page | Purpose |
|---|---|
index.html | Clearance chooser (boot splash → Agent / Friendly / A-Cell cards). Entry point for everyone. |
friendly.html | Friendly clearance: a one-shot player picks one of the Handler's pregenerated Agents and plays — two taps from Clearance to rolling, no sign-up, no character creation. Since v3.5.0 the picker is a desk of open dossiers on the black page: each folder's inside flap carries the MORS ET SCIENTIA seal stamped in its group's ink, a pencilled case number, birth and nationality (and on about one file in three a pencilled remark drawn from the pregen); the typed sheet leads with the profession, then the name, HP/WP/SAN/BP and the three best skills; the group is a coloured tab on the folder's edge, and the filter uses short group names. The dossier is A-Cell Play's one-Agent view, read-only: tap a stat (×5), skill, weapon skill or weapon damage to roll; HP/WP/SAN are starting values the player tracks on paper. If the Handler puts pregens in a Cell (A-Cell → Cells, "Friendlies" in the add list), a Cell filter appears and the Cell's own link (friendly.html?cell=ID) preselects it — rolls then land in that Cell's Live Rolls feed; otherwise they stay on the device. ?agent=ID opens one pregen directly. See Friendly pregens below. |
agent-hub.html | The player's own hub, and each Agent's whole file on one page. Reads this browser's local Agent roster (localStorage's dg_agent_roster) as a stack of manila folders, one per Agent, plus "+ New Recruit": each folder's tab stands a third of the way along from the one before it (half on a phone), the name handwritten in blue, black or red ink, and the open Agent's folder stands at the front (since v3.4.0). Past six Agents the stack is just "+ New Recruit", an Agents (N) ▾ folder that opens a list of every Agent, and the open Agent's folder, at any width. An Agent's tab: photo, name, and three drop-downs, Appearance ▾ (the physical description and the full Profiling form: open with what's still missing until it's complete), Era photos ▾ (Face and Outfit Plates per era, made from Appearance; assets/agent-file.js, one instance moved into whichever tab is open) and Cell ▾ (the initiative list) — in a row beside the name on a desktop, under the photo on a phone; then line and stamps, then the Agent File, which since v2.7.0 is the character sheet the Agent plays from (see Agent File below; Notes, Field ID, Requisition, Rules and the rest live in the Field Notes notebook; Recruit shows only for an Agent with no sheet yet), then Evidence with private per-Agent notes. + New Recruit (since v3.0.0) is the creation wizard: twelve steps from the dice to the Contract — statistics with what each means, the Agent's Handbook's and The Complex's professions and agency postings, bonus skills, Damaged Veteran, personal data (the Agent Code is made here), Bonds with the generator and an AI-drafted description, Motivations and disorders, the Incursion, the profession's kit, Profiling (play era, the Appearance brief and Plates), then the review and the Contract — or one drop zone to bring an Agent in from any format, a Friendly, or an Agent Code (assets/recruit-wizard.js, assets/recruit-data.js, assets/agent-import.js; see FEATURES.md §5). ?recruit=CODE builds a sheet for an Agent who has none. ?code=CODE opens one Agent (looked up and added if they aren't on this device yet); #appearance / #photos go to that part. The Agent's main photo everywhere is the Active Era's Face Plate (the first era's with no Active Era chosen), worked out by dgStore.mainPhoto(). Backed by Firestore (briefs/{code}) and Firebase Storage. Since v3.8.0 every player page has short tours of the new features (assets/tour.js): each starts once by itself, can be left any time (✕ Exit tour, Esc, a tap outside), and a "New · N tours" stamp by the notebook or Settings → Tours replays them. |
dg-agent-portal.html | Retired: the old three-tab Agent Portal (Profiling, Agent File, Field IDs). Now only forwards old links and bookmarks: ?code= to that Agent's tab on Agent Hub, #cover (Take Photo) to their era photos, #ids to the Field ID Fabricator in the notebook. |
404.html | The not-found page (GitHub Pages and Firebase Hosting serve it for any missing address). Since v3.3.0 it also forwards old links to the removed character sheet: stats/index.html?load=CODE opens that Agent on Agent Hub, ?new=1 opens New Recruit. |
a-cell.html | The Handler's dashboard, password-gated. Since v3.11.0 a Desk tab comes first: any of the tabs below as panels side by side (a session note docked beside Play and Music during a game; drag by the grip, resize by the dividers, save layouts on the device; stacked on a phone). Seven tabs (the seventh, ID Creator since v3.9.0, makes an NPC's ID from the evidence-pack stock, an agency template or an invented company and files it to a Cell as unreleased Evidence; it also uploads the stock): Play (every Agent on file for running the table, plus a Cell Dashboard with a DEX-based Initiative Tracker — live off Firestore, no Handler session needed to load), Cells (group Agents into named Cells with their own Handler — live Firestore listeners, written straight to Firestore), Evidence (an evidence locker — file items into per-Operation folders within a Cell, stage them with a Released toggle, optionally restrict to specific Agents; since v3.6.0 one item can go to several Cells and Operations as linked copies, each in its Cell's language — English or Magyar, with a Translate button — plus Duplicate and simple formatting; since v3.7.0 a List |
notes/index.html | Shared/private session notes, scoped to a Cell. Anything marked Shared is visible to every other member of that Cell (in a combined, date-groupable Timeline view); everything else stays private to its author. Live editing surface is Editor.js (real Header/List/Delimiter blocks, Bold/Italic/Highlight, and paste-splitting from Google Docs/Notion). Also has Pin (surfaces a block in its own sidebar section), Tags (since v3.2.0: highlight words and tag them NPC / Location / Clue / Operation, marked in colour in the note; the sidebar's Tags list counts them and filters the notes; search finds tags too), Quick notes from the notebook waiting in the sidebar until Add to page, ✎ Arrange for the sidebar's lists, cross-tab Search everywhere, and a Plain Fonts toggle reverting everyone's own chosen cursive display font back to the site's Special Elite/Courier Prime look. Reached from the Field Notes notebook's Notes tab (on a desktop it spans both pages: index and Evidence on the left, the editor on the right; tapped again it folds to quick notes), or from the sheet. |
field-id.html | The Field ID Fabricator (since v3.9.0 a forger's terminal, inside the notebook): [1] Mirror credential (the cover employer's agency card), [2] Spoof a local office (a sheriff, a city, a fire company, a lab badge… on the Handler's evidence-pack stock, uploaded once in A-Cell and never in this repo), [3] Fabricate an entity (name a company, pick a drawn logo and one of six layouts). Every card carries the Agent's Face Plate; Print to wallet files it to the Agent's private wallet (wallets/{code}), which the notebook's Field ID pocket and page flip through ‹ › — the card on top is the one carried. Print opens over the page, PROP line on paper only. Cards: assets/field-id-cards.js (agencies), assets/forge-cards.js (the rest); Employer drop-down: assets/agencies.js. |
the-incursion.html | The Incursion tables with the same roll/pick/freehand picker the sheet uses (assets/incursion.js). Linked from Agent Hub. |
dg-id-creator.html | A simpler, standalone paper/dossier-styled field-ID card generator, superseded by the Field ID Fabricator (field-id.html, in the notebook) and no longer linked from anywhere — its last link, the notebook's Blank ID Creator button, went in v2.1.0. Its old DG- base64 save-code loader was removed in an earlier cleanup — the page has no code system of its own left, just cosmetic leftovers (e.g. the printed barcode graphic). Kept in the repo rather than deleted. |
All pages are static HTML/CSS/JS with no build step. Shared code lives in assets/; the data tables the Agent File and New Recruit roll on (professions, Bonds, bio and Motivation tables, the equipment catalog) are in assets/data/, and the DD Form 315 / spreadsheet readers in assets/sheet-io/.
Live since v2.1.0. A leather notebook (assets/field-notes.js/.css) carried on every player page, and inside the Hub shell (hub.html) one notebook for the whole tab. Closed, it's a small notebook in the corner of the screen (on a phone, a notebook button and a dice button). Open, it's a two-page book with real page turns:
It replaces the floating Table Radio pill, the Dice Roller panel, the sheet's settings cog and its phone Notes button, and drives their existing engines rather than duplicating them. Split View (sheet and Notes side by side) is retired: its buttons are hidden wherever the notebook runs. A-Cell has no notebook; the Handler keeps the radio pill and dice panel there. The Handler's dice panel shows Live Rolls from every Cell, with a filter at the top for one Handler's Cells (each Cell's Handler field) or a single Cell, remembered on each device. Players only ever see their own Cell's rolls: the Handler's feed exists on A-Cell alone, so a shared tablet logged into A-Cell still gives players their own Agent's dice and Cell on every other page.
New-Agent onboarding. Finishing the character wizard, or importing an Agent onto the sheet (including Friendly's "Make this my Agent"), arms a clearance briefing: the first time the player then leaves the sheet, a green terminal types out the five tenets (our own wording, not the rulebook's) and asks CAN WE CALL ON YOU? [Y/N], answered by typing or tapping the letter. A plain line on top says the Agent is saved and the briefing happens once. Y records it on the Agent File and opens the Agent's era photos to take the Face Plate; once a photo exists the next step offered is the Field ID. N/Escape puts it off until they next leave the sheet.
Since v2.7.0 the Agent File paper (assets/agent-paper.js/.css, rules in assets/agent-rules.js, one live session per Agent in assets/agent-live.js) is a sheet to play from, on the notebook's Agent File page and on Agent Hub alike. Every change saves straight to the Agent's characters/{code} document, in the same format the old sheet uses, so A-Cell, Friendly and the old sheet still read it.
Since v2.8.0 it reads in five numbered parts — 1 Statistics, 2 Skills (with Find a skill…), 3 Psyche (Bonds, Motivations, Mental Disorders, Sanity adaptation), 4 Combat & gear, 5 Record (Incursion, Operations) — with jump buttons under the vitals. Their headings take one of three looks, picked per device in the notebook's Settings → Agent File look: Typed form (default), Folder tabs or Rubber stamps. Appearance (with the physical description), Era photos and the Cell are drop-downs beside the photo rather than parts of the sheet; in the notebook they're folds under the photo, Era photos showing the plates with a link to make them on Agent Hub.
assets/agent-wear.css, built by scripts/agent-wear/build.py). Clean at 50+, then a stage per ten points: grain and faint yellowing (49–40), a fold and foxing (39–30), a coffee ring (29–20), a water stain (19–10), the first blood and the page's own edge torn (9–1), blood spatter (0). It covers the whole page the file sits on, kept light enough to play from even when INSANE; since v3.2.0 its strength rises point by point with SAN (49–40 1–5%, 39–30 6–10%, 29–20 11–25%, 19–10 26–50%, 9–0 51–100%). From SAN 9 an INSANE stamp sits on the photo and name, worn and patchy, filling in from SAN 5 to solid at 0. Each Mental Disorder gets a small rubber stamp (red, violet or blue-black ink) scattered over the header, never on a button, the name or the vitals, always in the same spot. A new stage fades in, and a new disorder's stamp comes down with a thump (no motion with reduced motion). Agent Hub and the notebook only; A-Cell stays clean. Settings → "Paper wears as SAN falls" turns it all off on that device.Design and every feedback round: docs/field-notes-widget/SPEC.md.
assets/table-radio.js is the engine included on every player-reachable hub page that keeps a player "tuned in" to whatever the Handler is broadcasting from A-Cell's Music tab, staying loosely in sync as players move between pages via a server-stamped started_at timestamp every device reads. Backed by a live Firestore listener on radio/{channel} (it began as an Apps Script addition before the Sheet was retired). Players tune it from the Field Notes notebook's radio; its own floating pill now shows only on A-Cell.
iPhone/iPad: Safari plays nothing until the player has tapped the page once. After that first tap, new tracks and sound effects start by themselves, because the widget reuses <audio> elements a tap has unlocked. Any later tap anywhere, including inside the hub shell's content frame, also starts anything that was refused and routes the sound through Web Audio, the only volume control iOS honours. (See BUGFIXES.md, "Table Radio silent after the Storage CORS config".)
A soundboard on A-Cell's Music tab layers 7 ambient loops (toggled
on/off per channel) and 24 one-shot stingers (grouped Screams &
Laughter / Impacts & Weather / Bells, Rhythm & Texture / Creatures &
Voices) under the main
track — real recorded SFX (assets/ambient/, assets/stingers/), both
mirrored onto the same radio/{channel} Firestore doc Now Playing
lives on so every tuned-in player's widget picks them up instantly. A
Handler-draggable seek slider on the same tab lets the Handler jump the
current track to a new position; players only ever see a read-only
progress bar, so nobody can scrub their own copy out of sync.
An earlier attempt at ambient/stingers was built, tested, and then
deliberately abandoned before ever merging to main — the
procedurally-synthesized audio didn't sound convincing (only gunshot
variants were usable). See
design-graveyard/table-radio-audio-soundscape's own RETROSPECTIVE.md
for the full account; its own recommendation to use real recordings
instead of synthesis is what the current, shipped version above is
built on.
friendly/pregens.json is built from the Handler's fillable DD Form 315 pregens (the Arc Dream Agent Dossiers PDFs) in two steps, both in scripts/pregens/ (see its README): extract.py reads each PDF's form fields and reports what the sheet leaves blank; build.js fills those blanks — name, sex, age/DOB, nationality, employer, education, physical description, distinguishing features, Bond names, Motivations — with the same tables the Creator's Random Bio / Random Motivation use, seeded per sheet, and finishes each sheet the way its own notes say ("Choose two from the following skills…", "Bonus skill points: Add +20% each to any six skills"). All 62 Dossiers ship; the picker groups them into six categories and has a one-tap "Hand me a dossier". The PDFs and the raw extract stay out of the repo (the site is public); only game numbers plus generated flavour are published.
manifest.json + sw.js make this installable to a phone's home screen and usable at the table with no signal. sw.js runs a stale-while-revalidate strategy over the static app shell (every page, script, stylesheet, and icon listed in SHELL_FILES) — instant load from cache, refreshed in the background for next time — while deliberately leaving every Apps Script/backend call alone, so it never serves stale campaign data offline and calls it a feature. CACHE_NAME gets bumped on every shell file change so returning visitors aren't stuck on old JS. One real constraint worth knowing: iOS disables window.alert()/confirm()/prompt() entirely inside a home-screen-installed (standalone) PWA, so this app avoids native dialogs in favor of real in-page UI (inline inputs, window.dgConfirm() in assets/field-notes.js) wherever a flow needs to work standalone, not just in a normal browser tab.
Since v3.2.0 the Agent's Appearance brief fills its empty fields from their saved character sheet when the Agent File opens (afCarryFromCharacter_ in assets/agent-file.js): sex, age range and nationality, a build from STR+CON, and clothes that fit the profession (from the Appearance generator, assets/appearance-gen.js). Carried-over fields are locked against the Random Agent Generator, so a reroll can't overwrite what the sheet already says. An Agent with a saved sheet but no brief at all gets one made from it the first time any page looks for it. The rest of how the Agent looks comes from New Recruit's Profiling step or the Appearance brief on Agent Hub.
Appearance counts as "done" only once every field the brief marks required has a real value; until then the Agent's tab shows it open, with how many details are left (and a callout under Play), whatever's already known filled in and locked, and the era photos wait -- they're made from it.
New Recruit's start screen (Agent Hub, + New Recruit) has one drop zone that takes any of these and works out the format itself (assets/agent-import.js), then walks through the wizard's review steps with everything the file carried filled in:
.toml (a hand-rolled parser for exactly the flat subset Kappa Black exports; no Sex field, so that's entered by hand) and its .json, which is a Foundry VTT actor..json, and this site's own saved-character .json..pdf (assets/sheet-io/pdf-export.js): this site's own exported sheets and official pregenerated Agent PDFs, whose form fields are the same..xlsx (assets/sheet-io/sheets-import.js): the template the old sheet exported, including a copy edited in Google Sheets and downloaded again..html, which carries its Agent inside.None of them recovers everything (specialty instances, weapon rows and full Bond detail may need entering again).
The Agent File saves the character to Firestore (characters/{code}.character_json) as it's played, a moment after each change (assets/agent-live.js), and Edit mode saves on the Oath's Y. Any device opens the same Agent by its code (agent-hub.html?code=CODE, or New Recruit's Load by Agent Code…) or by Cover Identity below. The notebook's Settings → Export builds a DD Form 315 PDF from the saved character.
A persistent, multi-agent roster in this browser (localStorage's dg_agent_roster) — every Agent made, saved, opened by code or pulled in by Cover Identity on this device, with photo/name/code/codename/demographics/era. Agent Hub renders it as its folder tabs. (The old Agent Portal's slide-up roster drawer went with the portal.)
The roster above is purely local to one browser — until now, a fresh device or a cleared browser had no way to find a player's existing Agents at all beyond knowing their exact Agent Code by heart. Cover Identity (your real name, not the Agent's — reuses the player_name field already on the character sheet's Biography, now doubling as a lookup key) fixes that: enter it once in the field at the top of agent-hub.html and every Agent tied to that name — character sheet, Agent File, or both — replaces this browser's claimed roster entries with exactly that set. Remembered locally afterward, so a returning visit refreshes automatically with no retyping.
An Agent tied to a different player_name won't show up here even if this browser touched it before — that's the point, so one device can be handed between players without their rosters bleeding into each other. Since v3.2.0 the lookup keeps only that name's Agents: an Agent with no player_name yet comes back by its code (?code=CODE), or once the player fills in their Player Name or a Handler backfills it from A-Cell's Sheet tab.
A bare name match, deliberately — no PIN, no auth. Real access control (so players can't pull down agents that aren't theirs just by guessing a name) is tracked as its own separate, later roadmap item below, not bundled into this.
(History — now a Firestore query on player_name_lc.) Needed a small, additive Apps Script change at the time: a player_name column on both the Characters and Delta Green Briefs sheets, and a new find_by_player_name lookup action — see backend/Code.gs. Degrades gracefully (shows "No Agents found") until that's pasted in and redeployed, same as every other backend addition here.
Also: the Agent's own printable fake-ID-card feature used to be called "Cover ID" — renamed to Field ID throughout (buttons, tabs, dg-id-creator.html) to free up "Cover Identity" for this, since the two are unrelated concepts (one's the Agent's in-fiction alias, the other is the real player behind the keyboard).
Retired as the data store (v97, see Firebase / Firestore below) — after the one-time migration it only serves the Drive image proxy for old gdrive: links. The Google Apps Script + Sheets project lives outside this repo (Apps Script has no git integration of its own) — backend/Code.gs is a checked-in mirror of its full source, kept in sync by hand: edit it here, then paste the whole file over the live project's Code.gs in the Apps Script web editor and redeploy. Every page degrades gracefully when a given action isn't live yet (JSONP calls just fail silently), so this repo's frontend can stay ahead of the deployed backend without breaking anything.
The app's backend. It was layered on top of Sheets over several migration passes and replaced them: a live Firestore read is both faster and immune to the JSONP-timeout class of "could not load X" failures a single-shot Apps Script round trip was prone to under a weak connection.
assets/dg-store.js; A-Cell's Handler actions are its own window.dgAcellApi. AI prompt/image generation runs as Cloud Functions (functions/index.js); generatePrompt, generatePlateImage and dailyBackup were only actually deployed on 2026-10-01 — until then Generate failed with "internal" (see BUGFIXES.md). Since v2.2.0 a failure shows its real message under the prompt and lands in functions:log; the functions' service account needs roles/datastore.user and roles/storage.objectAdmin (FEATURES.md §12). Like the rules below, functions deploy by hand: firebase deploy --only functions:NAME. The only Apps Script call left is the Drive image proxy (imgdata) for old gdrive: photo links. Going live takes the one-time steps in docs/firebase-migration/SHEET-RETIREMENT.md.Storage CORS (storage.cors.json). The Storage bucket needs a CORS config allowing this site's origin before Table Radio can control the main track's volume on iOS (issue #39): the widget only routes a track through Web Audio (the one volume control iOS honours) after the track's host has answered a CORS check, and plays it the plain way otherwise. It is set once, by hand, on the bucket -- not by firebase deploy: in Google Cloud Shell, gcloud storage buckets update gs://dg-app-b3447.firebasestorage.app --cors-file=storage.cors.json. No code change is needed afterwards; listeners pick it up on their next page load. The Table Radio debug line shows route=webaudio once it's working (route=element before).
firestore.rules/firestore.indexes.json/storage.rules in this repo are, like backend/Code.gs, mirrors only — a git push never deploys them; that needs firebase deploy --only firestore:rules (or the matching target) run by hand from a checkout that actually has the change. A stale live deploy of an otherwise-correct-looking rules file is a real, previously-hit failure mode (see BUGFIXES.md's Live Rolls entry) — when a Firestore permission error doesn't match what's in this repo's firestore.rules, suspect a missed deploy before suspecting the rule itself.
Handler authentication is now a single prompt, full stop. Typing the real Handler password into A-Cell's own black-terminal Clearance gate both clears that gate AND signs into Firebase in the same action, calling the handlerLogin Cloud Function directly; that ID token (carrying a handler:true custom claim) is the only Handler credential anywhere — Firestore's rules check it via isHandler(), and every remaining Handler-gated Apps Script action verifies the same token server-side via verifyHandlerIdToken_() (Identity Toolkit's accounts:lookup REST endpoint, since Apps Script can't run the Admin SDK). There is no second password box anywhere in A-Cell — that used to be a separate, real-credential prompt right below the header, folded into the gate itself on request. The Clearance gate's own OLD public flavor password (MASTICATE, purely client-side, no real security) is gone from A-Cell entirely; the old separate Apps Script session (handler_login POST action, opaque dg_acell_session token, requireHandlerSession_()/HANDLER_SESSION_TTL_SECONDS) is also gone — Code.gs never sees the raw password at all now. See BUGFIXES.md's "Handler auth unification" and "Clearance gate IS the Handler login" entries for the full before/after.
The character sheet this app started from was a copy of pigeon-labs-stack/DELTA-GREEN-STATS (stats/, removed in v3.3.0 once the Agent File and New Recruit had replaced it). What remains of it is its data tables (assets/data/professions.js, bonds.js, bio.js, equipment-data.js) and its DD Form 315 field map (assets/sheet-io/pdf-export.js), licensed under the PolyForm Noncommercial License 1.0.0 (© 2024 Brook Morton) — not MIT. Full text in assets/data/LICENSE-UPSTREAM.md; each of those files carries the notice at the top. This use (a free, unofficial, noncommercial fan hub) is squarely inside what that license permits. The upstream README is kept for reference in docs/upstream-originals/.
Everything else in this repo — the Agent Hub and Agent File, A-Cell, the Field ID Fabricator, and Table Radio — is built fresh; the Delta Green stat/skill rules referenced there are generic game mechanics (not copyrightable), not lifted from anyone's code.
Two suites. test/e2e/ (since v3.3.0) runs the real pages against local Firebase emulators with the real rules and Cloud Functions, several browsers at once as Handler and players: New Recruit, play, two devices on one Agent, a dropped connection, restricted Evidence, a Handler deleting an open Agent, every import, old links, private mode, an iPhone home-screen app, a returning device with the last release cached, Live Rolls, and a replay of every live Agent from a read-only snapshot (test/e2e/README.md). It never touches the live campaign.
There's also an automated smoke-test suite in test/ (139 test functions, over 1,200 individual checks) that exercises every page — index.html, agent-hub.html (each Agent's whole file), the Field Notes notebook and its clearance briefing, field-id.html, a-cell.html, dg-id-creator.html, notes/ — including the Field IDs Fabricator, New Recruit and every import format (real Kappa Black, xlsx and DD Form 315 files, the PDF built and read back), the Agent Roster, the Evidence Locker (folders, Released toggle, per-Agent restriction), Notes (Timeline, Pin, Tags, cross-tab search), Table Radio (main track, ambient layers, stingers), saving the character, and full offline/PWA behavior, with Firestore faked out in-page for every test page and every production Google/Firebase endpoint blocked, so it never touches real data (see install_notes_firestore_stub/push_firestore_snapshot in test/run_tests.py for the Firestore side). See test/README.md.
Releases are semver-tagged on main (vMAJOR.MINOR.PATCH); the current one is v3.11.0 (PR #92: A-Cell's Desk — a session note docked beside Play and Music, panels dragged, resized and saved as layouts); v3.10.4 (PR #91: profession · employer under the name in Agent Hub's folder header; Mental Disorder stamps kept off the buttons); v3.10.3 (PR #90, v3.10.2–v3.10.3: Personal data starts from the profession and employer shown under the name; Army, Navy, Air Force, Marine Corps and AFOSI cards in a new Military group); v3.10.1 (PR #89: profession · employer · Cover under the Agent's name on the Agent File); v3.10.0 (PR #88: universities, libraries, city and county government, law offices and counseling in Fabricate an entity, each with its own insignia and an e-mail on .edu, .gov or .org); v3.9.11 (PR #87, v3.9.3–v3.9.11: a free title field next to the preset titles in the Fabricator; 34 agencies with proper logos in the Employer drop-down and the Fabricator, grouped and searchable); v3.9.2 (PR #86: the notebook follows the Agent opened on Agent Hub); v3.9.1 (PR #85: Print to wallet saves on the live site); v3.9.0 (PR #84: the Field ID Fabricator as a forger's terminal — mirror your employer's credential, spoof a local office on the Handler's evidence-pack stock, or fabricate a company with a drawn logo — a private wallet flipped ‹ › in the notebook, print in place, the Employer drop-down, and A-Cell's ID Creator); v3.8.2 (PR #84: the photo tour's wording); v3.8.1 (PR #83: the tours fixed — they work in the Hub shell and from Settings → Tours on a phone, Sanity's Adaptation post-it frames its boxes, clearer wording); v3.8.0 (PR #82: short player tours of the new features, which can be left any time); v3.7.0 (PR #82: Evidence as a Gallery, and tags: the Handler's labels and each Agent's own private tags, with a Tags filter); v3.6.1 (PR #82: a numbered step in Evidence keeps its own number); v3.6.0 (PR #81: Evidence shared with several Cells and Operations as linked copies, each in its Cell's language with a Translate button; Duplicate; formatting; a growing text box and Edit in place); v3.5.3 (PR #80: on a phone, a scroll that starts on the Dice Roller bar no longer drags it mid-screen); v3.5.2 (PR #79: the Friendly dossier in Agent Hub's manila folders; Notes tagging finished — headings keep their tags, the Tag button is gone, Quick notes from the notebook show at once and are tagged like words on the page); v3.5.1 (PR #78: Agent Hub's folder stack stays together with + New Recruit open); v3.5.0 (PR #77: the Friendly picker as open dossiers with the MORS ET SCIENTIA seal and pencilled remarks); v3.4.0 (PR #76: Clearance folders fanned on the black page, Agent Hub's tabs a stack of manila folders); v3.3.0 (PR #75: the old character sheet removed, its data tables and PDF/xlsx readers kept; the end-to-end suite on Firebase emulators and the eight bugs it found); v3.2.1 (the Cell list strikes KIA / INSANE names in red, and the phone's Agents menu stamps them); v3.2.0 (PR #73: Appearance takes sex, age, a build and clothes from the character sheet; SAN 0 and Bonds at 0 struck through like KIA; Face and Outfit Plates retry a dropped save and never look saved when they aren't; the notebook's Agent card flips through your Agents; Evidence sorts by date or title; Notes tagging by highlighting, a Tags filter, Quick notes waiting for Add to page and ✎ Arrange; the paper wear follows SAN point by point; a Cover Identity lookup keeps only that name's Agents); v3.1.0 (PR #72: the Agent File's paper wears as SAN falls — stains, blood and a torn edge over the whole page, the INSANE stamp on the photo and name, and a rubber stamp per Mental Disorder on the header); v3.0.1 (Cloud Functions on Node.js 22, ahead of Node 20's retirement on 2026-10-30); v3.0.0 (PR #70: the old character sheet retired — Agent Hub's New Recruit wizard creates and imports Agents, the notebook's Settings export a DD Form 315, and every link moved off the old sheet); v2.9.0 (PR #69: Field Notes' Split mode, the Agent File on the left page and the brown tabs on the right; and the Agent File no longer stuck on "Could not load" after one failed read); v2.8.0 (PR #68) put the Agent File in five numbered parts with three looks to pick from, Find a skill, and Appearance / Era photos / Cell as drop-downs beside the photo; v2.7.0 (PR #67) made the Agent File the character sheet: play from it, Edit with the Oath on save, the SAN loss flow with its post-its, the Cell as initiative; v2.6.0 (PR #66) brought Roll SAN and Cell member cards on the Agent File, full-screen zoomable Evidence attachments on Safari, and Mental Disorders apart from Motivations; v2.5.0 (PR #65: the Handler's Live Rolls filter by Handler or Cell, and the Handler's roll feed on A-Cell only, so players never see another Cell's rolls); v2.4.1 (PR #64) added six soundboard stingers, Creatures & Voices; v2.4.0 (PR #63) brought Session Notes pages uploaded and opened inside A-Cell, ticks remembered; v2.3.0 (PR #62) added A-Cell Session Notes and full-body Outfit Plates; v2.2.0 (PR #61) was iOS radio autoplay and volume, in play, A-Cell Sheet columns and one Agent per name; v2.1.0 was the Field Notes notebook (PRs #56–#58). A GitHub
Actions workflow runs the QA suite above plus a check that 's
gets bumped alongside any shell-file change, on every push
to / (not on pull requests or session
branches; it can be started by hand on any branch). Full policy, including how frontend and
backend rollback differ (they're deployed completely separately), is in
.
Rough priority order, cheapest/highest-value first:
PREFIX-XXXX code (assets/agent-code.js), saved in Firestore.assets/dice-roller.js), on the notebook's Dice page.isHandler() custom claim (see Firebase/Firestore above) for Handler-gated collections, and per-Agent Firestore reads (Evidence, etc.) go through a real per-Agent Firebase Auth token (exchangeAgentToken) rather than a bare code — but this only covers the Firestore-backed surfaces, and the Apps Script side (still most of the write path) has no equivalent.notes/, one shared+private notebook per Cell (see the table above): Timeline, Pin, Tags, cross-tab search, and a per-viewer Plain Fonts toggle.visible_to from the Firestore Cell's members itself, so the Sheet-scan problem that blocked it no longer exists. Every other "clearest remaining partial migration" surface has moved to direct Firestore writes (see Firebase/Firestore above); Evidence content (//) was next in line and was actually written and working in isolation, but shelved before shipping: (called whenever a Handler adds/removes a Cell member) recomputes every Evidence item's by scanning the , not Firestore — if Evidence writes stopped hitting Sheets, any item created afterward would be invisible to it forever, silently going stale the next time that Cell's membership changed. Needs a real Firestore capability in first (a REST call, converting its typed-value response back to plain JS — the existing // helpers only ever address one document at a time by id), untestable end-to-end through this repo's own Playwright suite (it mocks Firestore, not a real GCP project), so worth building and testing as its own piece of work rather than bundling it into the next batch. See 's "Phase 2 (Sheets removal), investigated but deliberately NOT migrated: Evidence Locker content CRUD" for the full account.Delta Green is a trademark of Arc Dream Publishing. This is an unofficial, fan-made set of campaign tools.
+ Add Bondsw.jsCACHE_NAMEmainfirebase-migrationVERSIONING.mdcreate_evidenceupdate_evidencedelete_evidencerecomputeEvidenceVisibleToForCell_()visible_tobackend/Code.gs:runQueryfirestoreDualWrite_firestoreDualPatch_firestoreDualDelete_BUGFIXES.md