Releases: TimoP80/Scene_Simulator
Release list
v0.7.4
Scene Simulator v0.7.4 — Party Attendance Mode, Partygoer Dialogue System, Living Rivalry Heatmap, BBS Modern Era
Release date: 2026-08-02
Compare: v0.7.3...v0.7.4
Commits on this tag: the v0.7.4 feature work (party systems, WorldState migration, BBS era split) plus the new Windows + macOS desktop packaging pipeline — CI now builds and publishes installers for both platforms on release tags.
v0.7.4 is the biggest player-experience drop since the competition system landed. For the first time you can live inside a demoparty: walk the floor of a venue packed with hundreds of procedurally generated sceners who remember you, then spend a full Friday→Sunday weekend balancing demo deadlines, compos, sleep, and coffee against seven survival needs. Under the hood, the WorldState migration finishes making the event log the single source of truth, the rival sim finally writes real rivalry-heatmap relationships that feed the scene press, and the BBS rumor pool becomes era- and relationship-gated — including a brand-new modern era for the 2006–2026 world. Plus a save/load regression that silently reset progress on Continue is fixed.
Release notes mirror the
[0.7.4]section ofCHANGELOG.md. SeeCHANGELOG.mdfor the canonical source.
Highlights
🏟️ Party Attendance Mode — spend the weekend, don't just watch it
A brand-new game mode that simulates an entire demoparty weekend (Friday 16:00 → Sunday 21:00). Explore 14 venues — entrance, main hall, seating, compo hall, stage, cafeteria, sleeping hall, showers, retro exhibition, merch shop, outdoor area, quiet workspace, organizer desk, info point — each with its own ambiance and ambient chatter. The weekend runs on a 16-block schedule (registration, opening ceremony, concerts, seminars, workshops, compos, prize ceremony, farewell) that fires live as time advances.
- Manage 7 player needs (sleep, hunger, thirst, hygiene, energy, motivation, stress) that decay hourly and throttle your productivity.
- Build a production in a workspace (code → optimize → test → package) and enter any of 10 competition categories (PC Demo, 64k, 4k, Shader Showdown, Graphics, Music, Executable Music, Fast Compo, Game Dev, Wild) before hard deadlines — late entries are rejected.
- Survive 13 deterministic random events: coffee spills, computer crashes, network outages, famous sceners, fire alarms, pizza deliveries.
- When the party ends, read a personalized trip report: productions completed, competitions entered, awards won, friendships, reputation gained, hours coded and slept, coffees consumed.
💬 Partygoer Dialogue System — "Walk the Floor"
The party floor is now alive. Hundreds of procedurally generated sceners populate the active party, each with a personality, role (coder / musician / graphician / organizer / visitor / newcomer), country, age, group affiliation, and a current project. A pure DialogueEngine turns context (role × personality × party phase × player reputation × friendship) into context-aware lines: reputation-based greetings, event reactions, location ambient, scene knowledge, and pre/post-compo chatter. A RelationshipManager tracks friendship / respect / rivalry with the player across repeated conversations — the same scener remembers you.
🔥 The rival sim finally writes the rivalry heatmap
simulateRivalGroups now populates RivalGroupState.rivalries instead of leaving it {} forever. Five writers drive real relationships over 40 years of simulated history: bootstrap historical seeds (Future Crew ↔ Razor 1911, Fairlight ↔ Razor 1911, …), same-month release races (winner gains disdain, loser resentment), member-poaching shocks, splits (parent resents the breakaway), and rare per-pair collaborations that build friendly ties. The GroupDossierPanel rivalry heatmap and the spyline scandal gate now light up from real simulated relationships instead of dead code.
🗺️ WorldState migration completion
12+ redundant useState mirrors removed from src/App.tsx. Game state now reads exclusively from the event-sourced WorldState via useSimulationSelector — the append-only event log is the single source of truth for money, reputation, rigs, techs, crew, releases, news, and the calendar. New Game and scenario loads reset through chained dispatch() calls per item.
📡 BBS era model split: 3 → 4 buckets, with a new modern era
The late bucket (which had folded the whole 1996–2026 span) is split: early (<1990), mid (1990–1995), late (1996–2005), and the new modern (2006–2026). All four era-indexed pools gained hand-written modern content — HD/streaming/AI-era topics for categories, boards, voice profiles, and personalities — so a 2020 thread gets GPU/raymarching/AI talk instead of Voodoo 3 nostalgia. The rumor-spyline pool is now era-gated AND relationship-gated: a scandal headline only fires when the rumor's endpoints have a real hostile relationship in WorldState, and a 1985-era dialup leak headline is never served alongside a 2023-era thread.
🖥️ Windows + macOS desktop builds — installers ship with this release
npm run dist:win (NSIS installer + portable EXE, x64) and npm run dist:mac (DMG + zip for x64 and arm64) now flow through one shared orchestrator, and the CI (build-macos on macos-latest, build-windows on windows-latest) packages both platforms on release tags and uploads the artifacts straight to the GitHub Release. The icon generator also emits a proper macOS icon.icns. macOS DMGs are built unsigned/ad-hoc in CI (no Apple signing cert in this repo yet).
💾 Save/load key-mismatch regression fixed (Continue reset progress)
A v0.7.4-era regression where saving and reloading could silently reset money / reputation / rigs / techs / crew / releases to defaults is fixed with a canonical AutosaveData contract (SAVE_VERSION = 2), a migrateSave() normalizer that handles both legacy and buggy-window keys, and both readers routed through it. Your saved active platform is restored too.
Screenshots-Worthy Callouts
- The Party tab → "ATTEND A WEEKEND" — the full-screen weekend overlay: a venue map of 14 explorable halls, a live needs grid that decays every simulated hour, the compo board with real deadlines counting down, and the rolling weekend log.
- "Walk the Floor" — the partygoer overlay: hundreds of unique sceners with role badges, country flags, and contextual dialogue that changes with the party phase and your reputation.
- GroupDossierPanel rivalry heatmap — the 40-year rival relationship web now populated by the sim itself: hostile ties in rose, friendly collaborations in cyan, built up from release races, poaching, and splits.
- The weekend trip report end-screen — a personalized retrospective: hours coded vs. slept, coffees consumed, friendships made, awards won, reputation gained.
- ScenarioEditor devtools tab — jump to 1985 / 1991 / 1998 presets and live-edit money, rigs, techs, crew, and seed releases mid-game.
What's New
- Party Attendance Mode — full weekend life-sim at a living demoparty (14 venues, 16-block schedule, 7 needs, 10 compo categories, 13 random events, personalized trip report). Pure deterministic engine (
sim/domain/partyAttendance.ts), content insim/data/partyAttendance.ts, driven by theusePartyAttendanceModehook +PartyAttendancePaneloverlay. 18-check smoke suite. - Partygoer Dialogue System — hundreds of generated sceners with personalities, roles, and a relationship manager that remembers you across conversations. Pure DialogueEngine (
sim/domain/partygoers.ts) over curated pools (sim/data/partygoers.ts), surfaced as the "Walk the Floor" overlay. 25-check smoke suite. - Rival sim writes the rivalry heatmap — five deterministic writers (bootstrap seeds, release races, poaching, splits, collaborations) populate
RivalGroupState.rivalrieswith the GroupDossierPanel sign convention (intensity > 0 = hostile, < 0 = friendly, −100…+100). 16-check smoke suite with PREDICTED rolls — no assertion waits on chance. - WorldState migration completion — 12+ redundant state mirrors removed; all game state reads from the event-sourced WorldState.
useKeyboardShortcutshook — single-listener global shortcut system with spec → handler map,ignoreWhenTypingguard, and pure tested parsers. NewM(Music) andS(Settings) shortcuts joinL(logo generator) and Ctrl/Cmd+Shift+D (dev mode). 30-check smoke suite.ScenarioPresettype + shared constants + ScenarioEditor devtools tab — typed presets for 1985 / 1991 / 1998,applyScenarioPreset()helper, shared seed article constants,NewsLogResetevent, and a runtime playtest editor wired into DevMenu.- BBS thread library expansion — seed library grows from 48 to 383 threads: music gear debates, demo style analysis, scene economy, pixel-art tutorials, tracker flame wars, cracktro ethics, sizecoding challenges, real-hardware compatibility reports, and more.
What Changed
- SPYLINE_TEMPLATES era-gated + relationship-gated — the rumor-spyline pool is now indexed by era (
early/mid/late/modern, 11 templates total) and scandal articles only publish when the rumor's endpoints have a real hostile relationship in WorldState (heatmap entry, social-graph rivalry edge, or fresh split/disband drama within 6 months). A positive "glorifying" rumor between rivals is praise, never a leak story. package.json— bumped0.7.3→0.7.4; five new smoke scripts (test:keyboard-shortcuts,test:save-load,test:rivalry-heatmap,test:party-attendance,test:partygoers) wired intotest:all(32 smoke suites now).src/App.tsx— keyboard handling migrated touseKeyboardShortcuts; scenario loading refactored onto `applyScenarioPre...
v0.5.0 — Competition & Party Simulation Expansion
v0.5.0 — Competition & Party Simulation Expansion
Competition Engine
- Multi-judge scoring system — 6 judge personalities (Oldschool, Technical, Artistic, Experimental, Music-focused, Graphics-focused) with weighted scores across 7 categories
- Audience reaction system — 9 tiers from Legendary Moment to Booing, influencing reputation and bonuses
- Dynamic party events — 14 random events (projector failure, live coding, audience hype, etc.) that modify scoring mid-competition
New UI
- Party ranking ceremony — animated overlay with phased ranking reveals, judge panels, audience reactions, confetti for wins
- Hall of Fame — permanent database with search, filters, sorting, and award badges
- Stats dashboard — 12 tracked stats with reputation tiers (Nobody → Myth)
- Production timeline — animated lifecycle visualization for every release
Scene Awards
8 award categories (Best Graphics, Best Music, Best Code, Best Design, Best Intro, Audience Favorite, Technical Excellence, Best Demo) with configurable thresholds.
BBS Variety
Expanded voice profiles, 2 new seed threads, 3 new random events.
Full Release Notes
https://github.com/TimoP80/Scene_Simulator/blob/main/CHANGELOG.md
v0.4.0 — Production type configs, multi-scene sequencing, scene variety scoring
v0.4.0 — Production type configs, multi-scene sequencing, scene variety scoring
Added
- ProductionTypeConfig system — every production type (Mega-Demo, 64KB/4KB Intro, MusicDisk, Cracktro, Slide Show) now has typed config defining max effects, size budgets, per-category score bonuses, and suggested effort splits
- Multi-scene sequencing — scene count selector (1–6) with per-scene effect assignment, 7 transition types (cut, fade, crossfade, slide, zoom, dissolve), and a SceneTimeline in the summary modal
- Scene variety scoring — bonuses for scene count, unique transitions, and effect distribution across scenes
- Production type scoring modifiers — each type biases scoring categories (4KB boosts originality; MusicDisk boosts music; Cracktro boosts audience appeal; etc.)
- 4 new rival groups — Crest (France), Spaceballs (Sweden), Triton (Germany), Complex (Finland) — plus 7 new sceners (Gibson, Zardax, Prowizard, Crypton, Membrane, Sync, Pixie)
- Enhanced DemoStudio — type info banner with size budget tracking (color-coded at 50%/80%), multi-scene scene editor cards with transition dropdowns
- Enhanced DemoSummary — SceneTimeline, production type config section, two new factor tiles (Scene Variety, Type Modifier)
Changed
- Version bumped 0.3.2 → 0.4.0
- DemoStudio expanded controls grid; App.tsx scene state wiring; scoring test expectations updated
Also in this release (carried forward from earlier commits)
- 6 new dev-tools editor tabs completing the 8-tab DevMenu
- 2 new content types (SceneEvent, MusicTrackMetadata) + curated JSON packs
- Social graph projections bridge — 9 pure projection functions wiring editor data into the social graph
- Content store reactivity fix — immutable upsert pattern so editors re-render after saves
Scene Simulator v0.3.2
Scene Simulator v0.3.2 — Worklet Re-bundle for Electron 42 + Dist Orchestrator + Per-pack Smoke Matrix
Release date: 2026-07-09
Compare: v0.3.1...v0.3.2
Commits on this tag: 1 (the chore(release): cut v0.3.2 version-bump commit containing 14 grouped infra + catalog changes against the v0.3.1 baseline, ~+24k/-1k lines across 98 files).
This is a maintenance release resolving the v0.3.0 → v0.3.1 AudioWorklet chain breakage under Electron 42, replacing the v0.3.0 custom-privileged worklet:// scheme with a Vite-served bundled asset, introducing a single canonical scripts/dist.mjs production-build orchestrator, expanding npm run test:all from a 7-step chain to a 24-step chain (17 new per-pack smoke runners), and tightening the type / catalog / settings-schema plumbing.
Release notes mirror the
[0.3.2]section ofCHANGELOG.md. SeeCHANGELOG.mdfor the canonical source.
Highlights
- Tracker-music worklet loads under Electron 42 again — the v0.3.0 shipping path registered a custom privileged
worklet://scheme and servednode_modules/chiptune3/chiptune3.worklet.js+libopenmpt.worklet.jsthroughprotocol.handle. Electron 42's Chromium rejects the staticimport './libopenmpt.worklet.js'chain with "Unable to load a worklets module" even after the v0.3.1Content-Typepatch. v0.3.2 abandons that plumbing entirely;scripts/bundle-worklet.mjsconcatenates both files into a singlepublic/worklets/openmpt.bundled.worklet.jsasset shipped through Vite's regular asset pipeline, served at the document's same-origin URL. - Single canonical production-build pipeline —
scripts/dist.mjsorchestratesnpm run build:allfollowed byelectron-builder --win --x64(or--dir) and copies the resulting NSIS + portable artifacts pluslatest.yml/win-unpacked/blockmap outputs into the project's localrelease/folder.npm run dist:winandnpm run dist:dirroute through this script instead of invokingelectron-builderdirectly, so contributor builds have one canonical log path and a single place to thread future revisions (codesigning, S3 upload). npm run test:allis now a 24-step chain — 17 granular per-pack smoke runners (test:effect-unlocks,test:scoring,test:judging-profiles,test:artistic-directions,test:effect-synergies,test:hardware-catalog,test:sponsorship-catalog,test:technology-tree,test:software-catalog,test:demo-effects,test:initial-npcs,test:initial-groups,test:party-calendar,test:job-templates,test:bbs-messages,test:rival-releases,test:platforms) join the existing 7 (smoke+audit-docs+load-during-import+economics+replay+migration+music), each pinning an invariant specific to its catalogue and independently runnable.
What's New
No new user-visible features. The v0.3.1 → v0.3.2 transition is a patch release rolled up from a long stretch of infra / type / catalogue work that shipped without a dedicated tag.
What Changed
npm run cleanrewritten for Windows-safe recursive delete — the oldrm -rf dist server.js dist-electronfailed on Windows; v0.3.2 swaps tonode -e "['dist', 'server.js', 'dist-electron', 'public/worklets', 'release'].forEach(p => require('fs').rmSync(p, {recursive:true,force:true}))", so the warm-rebuild path now also picks uppublic/worklets/andrelease/artefacts without POSIXrm -rfassumptions.PartyEvent.yeartyped anchor —packages/types/src/party.ts::PartyEventgained ayear: numberfield alongside the existingmonth;sim/data/partyCalendar.tswrites the actual year every party ran (Copy Party 1989,Venlo Meeting 1987,The Gathering 1992,Twilight Zone 1992,Assembly Summer 1992,Sun Demoparty 1992,Mekka & Symposium 1992, …) on every entry. Projections can filter "upcoming parties in year Y" without a fragile month-only heuristic.- Explicit settings-schema v1→v2 migration branch —
electron/settings.ts::readSettingsnow contains a dedicatedparsed.schemaVersion === 1branch that lifts the legacygeminiApiKeyand seeds an emptymusic.playlist. Pre-v0.3.2 settings on first launch fell through to{ ...DEFAULTS }and would have silently dropped a user's persisted Gemini key on every subsequent read until the user re-entered it. CompanionnormaliseMusic(raw)helper now sanitises every playlist entry field-by-field (storedName/displayName/formatenum /sizenumber). - Extended DEMO_EFFECTS catalogue to JSON-parity —
sim/data/demoEffects.tsgained a "JSON-only catalogue" section with 23 additional entries (color_cycling,interlace_flicker,dither_gradient_sky,roto_zoomer,copper_sprite_multiplex,dual_playfield_parallax,blitter_zoomsprite,wireframe_flythrough,rotating_logo,particle_system,l_system_plants,perlin_noise_clouds,chromatic_aberration,bump_mapped_torus,environment_mapping,boids_flocking,morphing_mesh,domain_warp_field,reaction_diffusion,raytraced_spheres,volumetric_fog,voxel_city,realtime_vocoder) that were always present indata/effects.jsonbut missing from the original hand-maintained TypeScript catalogue. Each carries the v0.3.0 metadata (complexity/visualImpact/compatiblePlatforms/synergyTags/researchRequired). JOB_TEMPLATESexplicitrequiresCrewSkilltags — every template insim/data/jobTemplates.tsnow declaresrequiresCrewSkill: "coding" | "graphics" | "music"so the projection's job-board filter is skill-based instead of description-string matching. Split across the 12 entries:2× music(job_8bit_chiptuneset,job_16bit_trackerpack),4× graphics(job_8bit_loadscreen,job_16bit_introart,job_pc_shareware_pack,job_modern_introfilm),6× coding(the rest — notejob_modern_synthsizeistype: "tool_contract"notmusic_commission, so correctly tagging itcodingmatches its "build a byte-sized synth library" description).sim/data/index.tsbarrel gains BBS + effect-unlocks exports — the existing catalogue barrel now exportsBBS_BOARDS/BBS_SCRIBES/SYSOP_REPLIES/SYSOP_MODERATION_MESSAGES/ERA_TOPICS/SPYLINE_TEMPLATES/BBS_RANDOM_EVENTS/BBS_MUTATIONS/VOICE_PROFILES/CATEGORY_MESSAGES/BBS_PERSONALITIES/getSeedThreads/getEra/generateFollowedReply/generatePersonalityMessage/colorForHandlefrombbsMessages, theBBSBoard/Era/SpylineTemplate/BBSRandomEvent/BBSCategory/BBSPersonalitytypes, and theeffectSynergies/artisticDirections/effectUnlocks/judgingProfilesexports that the data-migration smoke test relies on. Importers can now read any catalog through@sim/datawithout reaching into a subpath.sim/domain/index.tsscoring-barrel export — addedexport * from "./scoring"sosim/domain/scoring.ts'sscoreProduction/scoreBreakdownFor/competeAgainstJudgingProfile/ScoreBreakdownhelpers surface through the documentedsim/domainbarrel that other layers import.sim/data/softwareCatalog.tsPhotoshop 5 LEeffectUnlockscorrected — the legacyprocedural_texturesid has been renamed todomain_warp_fieldso the purchased-tool unlock matches the matching entry in the new DEMO_EFFECTS JSON-only catalogue. The previous id referenced aprocedural_textureseffect that was never added, so a player who bought Photoshop 5 LE onPC_PENTIUM_IIin 1998 would have surfaced zero unlocks in the studio.- Docs / repo metadata de-provenanced —
README.mdremoved theInitial project started with Google AI studio, further development made locally with other AI tools.attribution line (the project now self-describes its hybrid simulation + narrative stack without referencing the bootstrap environment);docs/architecture.mdapps/server/row wording now reads*(reserved — Express server for hosted/headless deployment parity)*(the legacyAI Studio parityphrasing is gone — the project does not run on Cloud Run);.env.exampledocuments both halves of the Electron+Vite split (GEMINI_API_KEYexplains the first-run Electron prompt vs. the Vite dev export;APP_URLis now described as an optional self-referential / OAuth / API endpoint override rather than an AI-Studio-injected Cloud Run URL);index.html<title>is nowDemoscene Simulator(was the AI-Studio default"My Google AI Studio App");.gitignoreadds an inline# electron-builder output (NSIS installers, portable EXEs, blockmaps, latest.yml, win-unpacked/)comment above therelease/ignore line.
What Was Removed
worklet://custom-privileged scheme plumbing inelectron/main.ts— theprotocol.registerSchemesAsPrivileged([{ scheme: 'worklet', privileges: {...} }])call that ran beforeapp.whenReady(), theprotocol.handle('worklet', …)handler that mappedworklet://→userData/worklets/, and the per-launchmusic:init-workletIPC handler are all gone.getWorkletUrlpreload surface —electron/preload.ts'selectronAPIno longer exposes agetWorkletUrl()method. The renderer'ssrc/audio/trackerPlayer.ts::init()now references the statically-known/worklets/openmpt.bundled.worklet.jsURL against the document origin; any stale caller would tripTypeError: ... is not a function.build.extraResourceschiptune3 entries inpackage.json— the two array entries that copiednode_modules/chiptune3/chiptune3.worklet.jsandlibopenmpt.worklet.jsintoprocess.resourcesPath/worklets/for the packaged build are removed. The renderer no longer reads fromresourcesPath/worklets/; it readsdist/worklets/openmpt.bundled.worklet.jsas a normal Vite-served asset. Reduces the installer footprint by ~50KB and removes the devDep-on-prod-runtime coupling.
Bug Fixes
- **Tracker-music worklet fails ...
v0.3.1 - interval clear on load + audio-toggle hoist + electron dev script fix
Scene Simulator v0.3.1 — Interval Clear on Load + Audio-Toggle Hoist + Electron Dev Script Fix
Release date: 2026-07-08
Compare: v0.3.0...v0.3.1
Commits on this tag: 1 (the chore(release): cut v0.3.1 version-bump commit containing 3 grouped bug fixes).
This is a maintenance branch resolving three edge-case bugs spanning the Electron development environment, the React component-tree boundary, and the event-sourcing loadSavedGame snapshot restorer.
Release notes mirror the
[0.3.1]section ofCHANGELOG.md. SeeCHANGELOG.mdfor the canonical source.
Highlights
- Stable dev-environment bootstrapping —
npm run dev:electronno longer fails on a fresh checkout looking forwindow.electronAPI; a concurrentwait-onwatcher holds the Electron launch until thedist-electron/preload.cjsbuild is ready. - Mute/play state persists across fullscreen transitions — hoisting
audioEnabled/isPlayingintoApp.tsxmeans the inline MUTE preference no longer snaps back to "TAP TO PLAY" when the CRT monitor enters or exits fullscreen view. - Watertight interval clearing on snapshot import —
compileIntervalRef+partyVoteIntervalRefroute the compile and party-vote setInterval ids throughuseRef, are guarded withclearIntervalat the top ofloadSavedGame's snapshot-apply path, and the ephemeral compile/party state is explicitly reset to defeat the React-18 auto-batching race where queued setStates from a dying interval's terminal tick could otherwise leak pastclearInterval.
What's New
No new features. The v0.3.0 → v0.3.1 transition is a patch release.
What Changed
No architectural changes or dependency updates.
Bug Fixes
-
npm run dev:electronlaunch failure on fresh checkout — the script was justconcurrently vite + electron ., soelectron .loadedpackage.json'smainfield (dist-electron/main.cjs) which never existed on a fresh checkout. The renderer would load from Vite, but the Electron main process would either fail to start or launch with a missing preload, leavingwindow.electronAPIundefined (src/audio/trackerPlayer.ts::init()would then trip the "requires Electron host" error and disable the music player). Now the script also runsvite build -c electron.vite.config.ts --watchin a third concurrent process, andwait-onblocks the Electron launch until both the Vite dev server and the freshly-builtdist-electron/preload.cjsare ready. The host-watcher keepsmain.cjs/preload.cjsup to date on subsequentelectron/*edits; reload the window (Ctrl+R / Cmd+R) to pick up host changes. -
Inline MUTE resetting on fullscreen toggle —
audioEnabledandisPlayingwere trapped in twouseStatedeclarations insideDemoScreen(one for the inline CRT card, one for the portal-rendered<FullscreenDemoView/>). PressingFto enter fullscreen unmounted+remounted the surface with fresh state, snapping the MusicBadge back to the "TAP TO PLAY" armed state. The two state hooks were hoisted intoApp.tsxascrtAudioEnabled+crtIsPlaying(withuseCallbacktogglerstoggleCrtAudio+toggleCrtPlay) sitting next to the existingcrtMusicTrackstate, and five props (musicTrackStoredName,audioEnabled,isPlaying,onToggleAudio,onTogglePlay) are forwarded through both surfaces. The hero-capture preview mount insrc/preview/CapturePreview.tsxreceives aNOOPcallback stub since the capture pipeline is a stateless preview. As a bonus, this round deduped a stale duplicate of the "Interval ids owned by…" comment block from earlier patch work (the leading copy remains; the orphan was collapsed, leaving exactly 1useRef<>declaration ofcompileIntervalRefandpartyVoteIntervalRef). -
Ghost release / prize drops on saved game load —
triggerAssembleCompilerandstartPartyVotingProcesscapture partial state in theirsetIntervalclosure callbacks; opening LOAD while one of these was in flight left the interval running. The terminal-tick callbacks (finishCompilation(),awardPartyContestPoints()) could fire AFTER the import had reset state, dropping a leftover release or prize credit into the freshly loaded save. Fix:compileIntervalRef+partyVoteIntervalRefuseRef<ReturnType<typeof setInterval> | null>(null)declarations route bothsetIntervalids through the refs; the inner-callbackclearInterval(interval)calls were rewritten to clear+null the ref; a guardedclearIntervalpair sits at the top ofloadSavedGame's try-block to cancel in-flight compile/vote intervals before the snapshot applies; and an explicit ephemeral-state reset block (setIsCompiling(false),setCompilerProgress(0),setCompilerLogs([]),setShowCompilingOverlay(false),setActiveParty(null),setIsPartyRunning(false),setPartyStep(0),setPartyRivals([]),setPartyVoteTally({}),setPartySelectedProdId(''),setPartyContestLogger([])) defeats the React-18 auto-batching race where queued setStates from a dying interval's terminal tick would otherwise leak throughclearInterval.sim/__tests__/loadDuringImport.smoke.tswas rewritten for determinism (amakeInterval()stub + manualtick()/clear()cycle instead of realsetInterval(..., 50)which was flaking under sequentialnpm run test:allevent-loop pressure); all three scenarios (positive-control bug repro, compile-interval cleared, vote-interval cleared) are green.
Upgrade Notes
There is no installer / binary distribution for v0.3.1 — this is a code-only patch.
For end-users: if you are currently running the v0.3.0 installed binary (NSIS installer or portable exe), you are unaffected by the npm run dev:electron build-script fix and unaffected by the music-player state quirk (which only manifests on the inline CRT monitor when toggling between inline and fullscreen modes in vite dev). The loadSavedGame interval-clear fix would be observable if you ever clicked LOAD while a demo was mid-compile or while a party vote was mid-flight in v0.3.0, but v0.3.1 ships this fix and the bug never reached production binaries. Wait for the next feature-drop release (v0.4.0) for a packaged installer with all three fixes bundled.
For contributors: simply git pull / git checkout v0.3.1 / npm install. npm run dev:electron now reliably rebuilds the Main-process preload rather than crashing on a missing window.electronAPI. The new mid-flight interval clear pattern (compileIntervalRef + partyVoteIntervalRef) is the precedent for any future interval-typed setState chain.
Verification
npm run lint(tsc --noEmit) — exit 0npm run test:all— 7/7 smokes green, including the rewritten deterministicloadDuringImportsmoke (Scenarios A/B/C: positive-control bug repro WITHOUT clearInterval, compile interval IS cleared on import, vote interval IS cleared on import)npm run audit:docs— doc/sim parity gate greennpm run build— clean renderer bundle- Electron
worklet://+npm run dev:electronrounds-trip —window.electronAPIpopulated on first launch;reload windowpicks up subsequentelectron/*edits
License: Apache-2.0 (per the SPDX headers and package.json). By submitting a PR, you agree to license your contribution accordingly.
v0.3.0
v0.2.0 — Seed-in-state inversion + dynamic player identity + headless capture pipeline
Scene Simulator v0.2.0 — Seed-in-state inversion + dynamic player identity + headless capture pipeline
Release date: 2026-07-07
Compare: v0.1.0...v0.2.0
Commits on this tag: 3 (polish + review-nits + vite-env types, on top of the squash-merge that brought the v0.2.0 core from feature/v0.2.0-seed-inversion).
This release inverts the economy bootstrap (the $250 starting allowance now lives inside sim/engine/reducer.ts::emptyWorldState() as a leading IncomeLedgerEntry row, so state.player.money === sum(ledger.income) − sum(ledger.expense) holds by construction), event-sources the player identity (PlayerIdentitySet event), ships a self-contained headless capture pipeline (puppeteer-core + ffmpeg-static), and lands GitHub Actions CI plus a first-cut contributor guide.
Release notes mirror the
[0.2.0]section ofCHANGELOG.mdand add the post-merge polish + fix scope. SeeCHANGELOG.mdfor the canonical source.
Highlights
- 🧾 Seed-in-state inversion — bootstrap invariant
money === Σ(income) − Σ(expense)now holds by construction across every consumer (productionApp.tsx, smoke tests, replay runs, projections). - 🪪 Event-sourced player identity —
PlayerIdentitySetevent hydrateshandle+groupName; closes the v0.1.0TODO(dynamic-name). - 📸 Headless capture pipeline —
npm run capture:previewwritesbuild/preview.{png,webm,gif}from a self-contained vite-dev subprocess; the capture runs against a?capture=1short-circuited entry that bypasses the API-key gate. - 🤖 GitHub Actions CI —
tsc --noEmit+ every smoke +audit:docs+vite buildon every PR + push tomain; manualcapture-previewjob (requires system Chrome) on push to main or viaworkflow_dispatch. - 🧪 Economy + replay smokes —
economicsView+appendOnlyReplayDeterminismlock the ledger model + replay determinism; both files include catalog sanity gates that fail fast if a future ship strips a seed catalog. - 📚 Contributor guide —
CONTRIBUTING.mddocuments the first-run dev loop, three-layer rules, path-alias cheatsheet, test layout, coding style, build pipeline, and PR template checklist.
What's New
Economy (event-sourced player bootstrap)
- Seed-in-state bootstrap (
sim/engine/reducer.ts) — the $250 starting allowance now lives insideemptyWorldState()itself as a leadingIncomeLedgerEntryrow inledger.income(id"seed", year 1985, month 1, sourceIncomeSource.Other, sourceRefId"starting_allowance"). The LITERAL invariantstate.player.money === sum(ledger.income) − sum(ledger.expense)holds by construction across every consumer. Stays correct under replay becauseMoneyEarned's reducer case already dedups byevent.id(so an accidental duplicateMoneyEarned{id: "seed", ...}would short-circuit against the baked-in row), AND live production callers route exclusively through M1 ledger-aware reducersMoneyEarned/MoneySpent. The diagnosticMoneyChangedreducer bypasses the ledger by design and is reserved for thedispatchStampedEvent.smoke.tsM1-bug regression pin (no production dispatcher fires it). - Event-sourced player identity (
PlayerIdentitySet) — newSimEventvariant carrieshandle+groupName.App.tsx::handleNewGamedispatches the event sostate.player.groupNameflows from the event log instead of being baked intoemptyWorldState(). Reducer case is idempotent on(handle, groupName)so a stale-snapshot re-dispatch is a no-op. Closes the v0.1.0TODO(dynamic-name)hardcode planning item. emit.playerIdentitySetbuilder —sim/events/appendEvent.tsexposes a convenience helper alongside the otheremit.*builders.App.tsxusessimulationLoopRef.current?.dispatch(draft)so the dispatch survives an in-flight StrictModenullref.
Tests
sim/__tests__/economicsView.smoke.ts— end-to-end exercise of theEconomyViewprojection: M1 double-store deposit→buy-hardware pattern, ledger invariant, hardware/software purchases, travel subscription round-trip, and the trust-weighted job payout band[0.7× .. 1.5×]. Six scenarios + a catalog sanity gate.sim/__tests__/appendOnlyReplayDeterminism.smoke.ts— pins thedocs/event-sourcing.md"If all events are replayed in order, the world state must be identical" invariant. Replays a fixedEventDraftsequence three times throughreduceAlland asserts structural equality; secondary scenarios coverSimulationLoop-path idempotency,ts → (year, month)decoding, and theMoneySpentbalance floor (Math.max(0, …)) under repeated insufficient-budget spends.
Headless capture pipeline
scripts/capture-preview.mjs— self-contained headless capture script (340 lines). Drives system Chrome viapuppeteer-core, records a 6-second 30fps WebM viaMediaRecorderand a single-frame PNG viacanvas.toDataURL; two-passffmpeg-staticpalette quantisation produces a deterministic GIF for markdown previews. Spawnsvite devon:3000, polls for the port, tears down on exit. Flags:--no-gif(CI determinism),--width/--height(resolution override),--chrome-path(override auto-detect),--keep-server(debug),--no-headless(debug). Hard wall-clock deadline prevents hung runs.src/preview/CapturePreview.tsx— bare<DemoScreen/>mount with a deterministic hero effect preset (raster_bars + starfield_2d + animated_plasma + pixel_fire + vector_cube + tunnel_effect + sine_scroller). The full WORKSPACE capture is a v0.2.x follow-up.src/main.tsxbranched entry tree —/mounts<App>wrapped in<ApiKeyBootstrap>;/?capture=1short-circuits to<CapturePreview>directly so the capture script never has to navigate MainMenu or pass the API-key gate. The same React tree runs in both modes.src/components/DemoScreen.tsxwindow.__CAPTURE__hook +<canvas id="capture-target-canvas">— exposes{ canvas, isPlaying, resize(w, h) }to the page window (gated on dev mode viaimport.meta.env.PROD) so the StrictMode-safe capture script canwaitForFunction-poll until the canvas is reachable. The DOM id is the primary lookup because it always lands on the currently-mounted element after StrictMode settles; the ref is the fallback.
CI
.github/workflows/ci.yml— GitHub Actions workflow runs the full gate on every PR + push tomain:npm ci→tsc --noEmit→audit:docs→test:all→vite build. Concurrency group cancels in-flight runs on new commits. Default Ubuntu runner + Node 20. Manualcapture-previewjob (requires system Chrome, opt-in viaworkflow_dispatchor push to main) runs the headless capture and uploadsbuild/preview.{png,webm}as a 14-day retention artifact.
Docs
CONTRIBUTING.md— first-run dev loop, three-layer rules, path-alias cheatsheet, test layout with one-liner summaries per smoke, coding-style rules, build/ship pipeline table, PR template checklist. Anchors the merge-blockers fromdocs/architecture.mdfor new contributors.README.md"Screenshots & Captures" section — documents thecapture:previewscripts + the system-Chrome requirement + the?capture=1query short-circuit.
What Changed
sim/engine/reducer.ts—emptyWorldState().player.groupNameretains the"Tricycle Crews"seed default only for the brief pre-MainMenu bootstrap window. The comment documents the contract: the value is overwritten by the firstPlayerIdentitySetevent dispatched at NEW GAME, so projection readers should treatstate.player.groupNameas derived from the event log (the same way they treatmoneyfromMoneyEarned).package.json— bumped0.1.0→0.2.0. New scripts:test:all(run every smoke test sequentially, fail fast),test:economics(just the EconomyView smoke),test:replay(just the determinism smoke),capture:preview(PNG + WebM + GIF),capture:preview:no-gif(CI determinism),capture:preview:hi-res(1920×1080 override). New devDeps:puppeteer-core ^25.2.1,ffmpeg-static ^5.3.0.
What Was Removed
- Bootstrap
MoneyEarneddispatch insrc/App.tsx— theSIM_LOOP_BOOTSTRAPuseEffect no longer credits the starting allowance; the seed row inemptyWorldState()is now the single source of truth forplayer.money = 250(noIncomeSourceimport needed inApp.tsxanymore). - Local
dispatchSeed(loop)helpers insim/__tests__/economicsView.smoke.tsandsim/__tests__/dispatchStampedEvent.smoke.ts— the helpers existed only to dispatch the canonical seed event before each scenario; with the seed baked intoemptyWorldState(), fresh loops already start with the canonical state. GEED_SEEDalias + leading seed-allowanceMoneyEarnedevent insim/__tests__/appendOnlyReplayDeterminism.smoke.ts::deterministicEventSequence— the seed lives inemptyWorldState()now, so the scene's stamped sequence starts at the first user-action event (PlayerIdentitySet).
Bug Fixes
src/App.tsxany-cast tighten —competitors: any[]replaced with a localRivalEntryinterface matching thestartPartyVotingProcessrivalsList shape;m: anycast in the BBS message map typed asBBSMessage;choice: anycast in the BBS choice map typed viaBBSThread["choices"][number];type: "collaboration" as anyreplaced withas SocialEdgeType(already imported). Zero newanytypes introduced intosrc/.
Internal Hardening (pre-tag review nits)
- CI: chromium package name for
ubuntu-24.04—.github/workflows/ci.ymlinstallschromium(notchromium-browser) on the currentubuntu-latest. The capture script'sfindSystemChrome()already checks/usr/bin/chromium. - CI:
workflow_dispatchtrigger — added a manual trigger so release engineers can refresh `build/prev...
Demoscene Simulator v0.1.0 — initial release
Demoscene Simulator v0.1.0 — initial release
Above: SVG mockup of the in-game CRT demo screen (raster bars, vector cube, sine-scroller, HUD) — a stand-in for v0.1.0. v0.2.0 will replace this with a real PNG screenshot + a 30-second animated GIF of the demo screen playing.
What's shipped in v0.1.0
The initial public release of the Demoscene Simulator — a 1985–2005 life-sim of the demoscene built on a deterministic event-sourced kernel. You pick a crew name, hustle hardware and freelance gigs, ship demos at era-appropriate parties, and watch the scene evolve from C64 cracktros in disk-swapping gymnasiums all the way through the early-2000s 3D-shader revolution.
Highlights
- Three-layer architecture (
/sim,/apps,/packages) — strict rules forbid React/fetch/setStateinside/simand forbid re-declared sim-facing types outside@packages/types. - Event-sourced kernel — discriminated
SimEventunion (now 30 variants), purereduce(state, event) → state, deterministicSimulationLoopwith a StrictMode-safe reset pattern, append-onlyeventStorewithon(type | "*")subscribe. - Deterministic event-sourced economy system — money / hardware / software / freelance jobs / sponsors, with trust-weighted NPC payouts (an NPC
cognitive.trustGraph["__player__"]modulatestemplate.basePaymentbetween 0.85× and 1.35×) and wear-level decay recomputed deterministically in the projection (1 quarter-year per reliability point). Driven by the M1 double-store money-flow pattern — pairedMoneySpent+HardwarePurchaseddispatches instead of a single composed event. MainMenu+ApiKeyBootstrap— splash overlay (New Game / Continue / Load-from-file) plus first-run Gemini API key entry.- Electron + electron-builder — packaged as NSIS installer + portable Windows x64 with auto-update
latest.yml.
New
@packages/types+@packages/utilsshared packages — single source of truth forCharacter,Production,PlatformId,BBSThread, etc.; pure helpers (generateId,simTimestamp,advanceMonths,MONTH_NAMES).- Typed seed data under
sim/data/—HISTORICAL_PLATFORMS,DEMO_EFFECTS,TECHNOLOGY_TREE,INITIAL_NPCS,INITIAL_GROUPS,PARTY_CALENDAR,RIVAL_RELEASES. - Path aliases in lock-step between
tsconfig.jsonandvite.config.ts—@packages/types,@packages/utils,@sim,@sim/data,@sim/events,@sim/engine,@tools,@apps/*. - Economy catalogs: 23 hardware SKUs (C64-era 8-bit through 2005 GeForce 256 / Radeon DDR shader parts), 12 freelance job templates (1985–2005), 12 software offerings (assemblers, trackers, image editors, compressors), 5 late-game sponsor deals keyed off reputation milestones.
- Smoke tests —
dispatchStampedEvent(locks the "neverdispatch(emit.*)/dispatch(appendEvent)" rule),audit-docs(doc-vs-exports parity),loadDuringImport(autosave re-hydration under reload). - Dynamic crew-name propagation in
src/App.tsx— mount-timeuseEffectrewrites BBS reply / recruit / release-flow / party-memory / alliance-message / cognitive-stats UI to use the player's typed crew name. No more"Tricycle Crews"bake when you pick a custom name. - Expanded 1985–2005 demoparty calendar — 13 era-appropriate venues across four eras (crack/C64, Amiga-dominant, 16-bit-peak + early-3D-card, 3D-shader revolution).
<FullscreenDemoView/>(src/components/DemoScreen.tsx) — portal-rendered native-res effects playback. ESC /F/ Exit-button close. NewpaintDemoFramemodule-level helper keeps the inline card and the fullscreen view visually identical without sharing animation state across portals.<EconomyPanel/>(src/components/EconomyPanel.tsx, 705 lines) — live economy UI subscribed toeventStore.on("*")and re-derived each tick purely through theeconomicsViewprojection (noWorldStatemutation, nodispatchfrom inside projections). Renders balance / net-worth / hardware inventory with wear-level decay / freelance job board filtered by year + reputation / available sponsorships / recent ledger entries, and exposes player actions: buy hardware, buy software, take job, sign sponsorship.
Changed
- Party-rivals logic in
App.tsx— typedRIVAL_RELEASESfilter (year / disbanded / focus-band predicates) replaces the inline 4-rival hardcoded array. <DemoScreen/>— inline CRT now delegates every effect (raster bars, starfield, plasma, pixel fire, vector cube / pyramid, tunnel, sine scroller, HUD) to the sharedpaintDemoFramepainter so the inline + fullscreen surfaces stay visually identical without sharing animation state.
Fixed
- BBS reply / recruit, release flow, party memories, alliance messages, cognitive stats UI no longer hardcode
"Tricycle Crews"when a custom crew name is picked. sim/engine/reducer.ts::emptyWorldState.groupNamehardcode flagged with aTODO(dynamic-name)for the eventual event-sourced hydrate path (current UI bypasses this through MainMenu'ssetPlayerGroupName).- EconomyPanel post-launch nit cleanup:
- Force-render setter renamed
setTick→_tick(underscore-prefixed, comment block explains the non-standard role). - Recent-ledger rows now render sim-month labels (
Y1985 M11-style) via a module-levelledgerMonthLabel(ts)helper. Helper math uses((ts - 1) % 12) + 1so theDecember → Januaryboundary does not inflate the displayed year. - SOFTWARE_CATALOG empty-state now distinguishes "catalog itself empty (data missing)" from "filtered to empty for the current year" — the player sees an actionable CTA.
- Force-render setter renamed
Removed
- Stale
"tricycle"sentinel keyword fromsrc/App.tsx::handlePostCustomBbsMessagerecruitKeywords— recruit detection is now purely intent-based. src/data.tsandsrc/types.ts— merged into@sim/dataand@packages/types.
Download options
| File | What it is |
|---|---|
Demoscene Simulator Setup 0.1.0.exe |
NSIS installer (recommended — writes Start Menu + Programs & Features entry). |
Demoscene Simulator-Portable-0.1.0.exe |
Portable — no install, run from any folder. |
Demoscene Simulator Setup 0.1.0.exe.blockmap |
Auto-update blockmap (delta-update metadata for electron-updater). |
latest.yml |
Auto-update channel manifest (consumed by electron-updater). |
preview.svg |
This SVG mockup of the in-game CRT demo screen. Removed in v0.2.0 in favor of a real PNG + GIF. |
Both Windows binaries are signed by electron-builder's default signing identity (no code-signing cert configured). Auto-update will work for users running from the NSIS install path.
*README / GDD / triage: https://github.com/TimoP80/Scene_SImulator · Issues / v0.2.0 planning: https://github.com/TimoP80/Scene_SImulator/issues