Skip to content

Releases: TimoP80/Scene_Simulator

v0.7.4

Choose a tag to compare

@TimoP80 TimoP80 released this 03 Aug 02:24

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 of CHANGELOG.md. See CHANGELOG.md for 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 in sim/data/partyAttendance.ts, driven by the usePartyAttendanceMode hook + PartyAttendancePanel overlay. 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.rivalries with 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.
  • useKeyboardShortcuts hook — single-listener global shortcut system with spec → handler map, ignoreWhenTyping guard, and pure tested parsers. New M (Music) and S (Settings) shortcuts join L (logo generator) and Ctrl/Cmd+Shift+D (dev mode). 30-check smoke suite.
  • ScenarioPreset type + shared constants + ScenarioEditor devtools tab — typed presets for 1985 / 1991 / 1998, applyScenarioPreset() helper, shared seed article constants, NewsLogReset event, 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 — bumped 0.7.30.7.4; five new smoke scripts (test:keyboard-shortcuts, test:save-load, test:rivalry-heatmap, test:party-attendance, test:partygoers) wired into test:all (32 smoke suites now).
  • src/App.tsx — keyboard handling migrated to useKeyboardShortcuts; scenario loading refactored onto `applyScenarioPre...
Read more

v0.5.0 — Competition & Party Simulation Expansion

Choose a tag to compare

@TimoP80 TimoP80 released this 19 Jul 08:52

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

Choose a tag to compare

@TimoP80 TimoP80 released this 19 Jul 07:19

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

Choose a tag to compare

@TimoP80 TimoP80 released this 09 Jul 11:45

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 of CHANGELOG.md. See CHANGELOG.md for 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 served node_modules/chiptune3/chiptune3.worklet.js + libopenmpt.worklet.js through protocol.handle. Electron 42's Chromium rejects the static import './libopenmpt.worklet.js' chain with "Unable to load a worklets module" even after the v0.3.1 Content-Type patch. v0.3.2 abandons that plumbing entirely; scripts/bundle-worklet.mjs concatenates both files into a single public/worklets/openmpt.bundled.worklet.js asset shipped through Vite's regular asset pipeline, served at the document's same-origin URL.
  • Single canonical production-build pipelinescripts/dist.mjs orchestrates npm run build:all followed by electron-builder --win --x64 (or --dir) and copies the resulting NSIS + portable artifacts plus latest.yml / win-unpacked/ blockmap outputs into the project's local release/ folder. npm run dist:win and npm run dist:dir route through this script instead of invoking electron-builder directly, so contributor builds have one canonical log path and a single place to thread future revisions (codesigning, S3 upload).
  • npm run test:all is 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 clean rewritten for Windows-safe recursive delete — the old rm -rf dist server.js dist-electron failed on Windows; v0.3.2 swaps to node -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 up public/worklets/ and release/ artefacts without POSIX rm -rf assumptions.
  • PartyEvent.year typed anchorpackages/types/src/party.ts::PartyEvent gained a year: number field alongside the existing month; sim/data/partyCalendar.ts writes 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 branchelectron/settings.ts::readSettings now contains a dedicated parsed.schemaVersion === 1 branch that lifts the legacy geminiApiKey and seeds an empty music.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. Companion normaliseMusic(raw) helper now sanitises every playlist entry field-by-field (storedName / displayName / format enum / size number).
  • Extended DEMO_EFFECTS catalogue to JSON-paritysim/data/demoEffects.ts gained 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 in data/effects.json but missing from the original hand-maintained TypeScript catalogue. Each carries the v0.3.0 metadata (complexity / visualImpact / compatiblePlatforms / synergyTags / researchRequired).
  • JOB_TEMPLATES explicit requiresCrewSkill tags — every template in sim/data/jobTemplates.ts now declares requiresCrewSkill: "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 — note job_modern_synthsize is type: "tool_contract" not music_commission, so correctly tagging it coding matches its "build a byte-sized synth library" description).
  • sim/data/index.ts barrel gains BBS + effect-unlocks exports — the existing catalogue barrel now exports BBS_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 / colorForHandle from bbsMessages, the BBSBoard / Era / SpylineTemplate / BBSRandomEvent / BBSCategory / BBSPersonality types, and the effectSynergies / artisticDirections / effectUnlocks / judgingProfiles exports that the data-migration smoke test relies on. Importers can now read any catalog through @sim/data without reaching into a subpath.
  • sim/domain/index.ts scoring-barrel export — added export * from "./scoring" so sim/domain/scoring.ts's scoreProduction / scoreBreakdownFor / competeAgainstJudgingProfile / ScoreBreakdown helpers surface through the documented sim/domain barrel that other layers import.
  • sim/data/softwareCatalog.ts Photoshop 5 LE effectUnlocks corrected — the legacy procedural_textures id has been renamed to domain_warp_field so the purchased-tool unlock matches the matching entry in the new DEMO_EFFECTS JSON-only catalogue. The previous id referenced a procedural_textures effect that was never added, so a player who bought Photoshop 5 LE on PC_PENTIUM_II in 1998 would have surfaced zero unlocks in the studio.
  • Docs / repo metadata de-provenancedREADME.md removed the Initial 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.md apps/server/ row wording now reads *(reserved — Express server for hosted/headless deployment parity)* (the legacy AI Studio parity phrasing is gone — the project does not run on Cloud Run); .env.example documents both halves of the Electron+Vite split (GEMINI_API_KEY explains the first-run Electron prompt vs. the Vite dev export; APP_URL is now described as an optional self-referential / OAuth / API endpoint override rather than an AI-Studio-injected Cloud Run URL); index.html <title> is now Demoscene Simulator (was the AI-Studio default "My Google AI Studio App"); .gitignore adds an inline # electron-builder output (NSIS installers, portable EXEs, blockmaps, latest.yml, win-unpacked/) comment above the release/ ignore line.

What Was Removed

  • worklet:// custom-privileged scheme plumbing in electron/main.ts — the protocol.registerSchemesAsPrivileged([{ scheme: 'worklet', privileges: {...} }]) call that ran before app.whenReady(), the protocol.handle('worklet', …) handler that mapped worklet://userData/worklets/, and the per-launch music:init-worklet IPC handler are all gone.
  • getWorkletUrl preload surfaceelectron/preload.ts's electronAPI no longer exposes a getWorkletUrl() method. The renderer's src/audio/trackerPlayer.ts::init() now references the statically-known /worklets/openmpt.bundled.worklet.js URL against the document origin; any stale caller would trip TypeError: ... is not a function.
  • build.extraResources chiptune3 entries in package.json — the two array entries that copied node_modules/chiptune3/chiptune3.worklet.js and libopenmpt.worklet.js into process.resourcesPath/worklets/ for the packaged build are removed. The renderer no longer reads from resourcesPath/worklets/; it reads dist/worklets/openmpt.bundled.worklet.js as 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 ...
Read more

v0.3.1 - interval clear on load + audio-toggle hoist + electron dev script fix

Choose a tag to compare

@TimoP80 TimoP80 released this 08 Jul 01:53

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 of CHANGELOG.md. See CHANGELOG.md for the canonical source.


Highlights

  • Stable dev-environment bootstrappingnpm run dev:electron no longer fails on a fresh checkout looking for window.electronAPI; a concurrent wait-on watcher holds the Electron launch until the dist-electron/preload.cjs build is ready.
  • Mute/play state persists across fullscreen transitions — hoisting audioEnabled / isPlaying into App.tsx means 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 importcompileIntervalRef + partyVoteIntervalRef route the compile and party-vote setInterval ids through useRef, are guarded with clearInterval at the top of loadSavedGame'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 past clearInterval.

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:electron launch failure on fresh checkout — the script was just concurrently vite + electron ., so electron . loaded package.json's main field (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, leaving window.electronAPI undefined (src/audio/trackerPlayer.ts::init() would then trip the "requires Electron host" error and disable the music player). Now the script also runs vite build -c electron.vite.config.ts --watch in a third concurrent process, and wait-on blocks the Electron launch until both the Vite dev server and the freshly-built dist-electron/preload.cjs are ready. The host-watcher keeps main.cjs / preload.cjs up to date on subsequent electron/* edits; reload the window (Ctrl+R / Cmd+R) to pick up host changes.

  • Inline MUTE resetting on fullscreen toggleaudioEnabled and isPlaying were trapped in two useState declarations inside DemoScreen (one for the inline CRT card, one for the portal-rendered <FullscreenDemoView/>). Pressing F to 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 into App.tsx as crtAudioEnabled + crtIsPlaying (with useCallback togglers toggleCrtAudio + toggleCrtPlay) sitting next to the existing crtMusicTrack state, and five props (musicTrackStoredName, audioEnabled, isPlaying, onToggleAudio, onTogglePlay) are forwarded through both surfaces. The hero-capture preview mount in src/preview/CapturePreview.tsx receives a NOOP callback 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 1 useRef<> declaration of compileIntervalRef and partyVoteIntervalRef).

  • Ghost release / prize drops on saved game loadtriggerAssembleCompiler and startPartyVotingProcess capture partial state in their setInterval closure 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 + partyVoteIntervalRef useRef<ReturnType<typeof setInterval> | null>(null) declarations route both setInterval ids through the refs; the inner-callback clearInterval(interval) calls were rewritten to clear+null the ref; a guarded clearInterval pair sits at the top of loadSavedGame'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 through clearInterval. sim/__tests__/loadDuringImport.smoke.ts was rewritten for determinism (a makeInterval() stub + manual tick()/clear() cycle instead of real setInterval(..., 50) which was flaking under sequential npm run test:all event-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 0
  • npm run test:all — 7/7 smokes green, including the rewritten deterministic loadDuringImport smoke (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 green
  • npm run build — clean renderer bundle
  • Electron worklet:// + npm run dev:electron rounds-trip — window.electronAPI populated on first launch; reload window picks up subsequent electron/* 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

Choose a tag to compare

@TimoP80 TimoP80 released this 07 Jul 12:12

Added:

  • Module player
  • Expanded demo production
  • Editor for data (json)

v0.2.0 — Seed-in-state inversion + dynamic player identity + headless capture pipeline

Choose a tag to compare

@TimoP80 TimoP80 released this 07 Jul 08:12

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 of CHANGELOG.md and add the post-merge polish + fix scope. See CHANGELOG.md for the canonical source.


Highlights

  • 🧾 Seed-in-state inversion — bootstrap invariant money === Σ(income) − Σ(expense) now holds by construction across every consumer (production App.tsx, smoke tests, replay runs, projections).
  • 🪪 Event-sourced player identityPlayerIdentitySet event hydrates handle + groupName; closes the v0.1.0 TODO(dynamic-name).
  • 📸 Headless capture pipelinenpm run capture:preview writes build/preview.{png,webm,gif} from a self-contained vite-dev subprocess; the capture runs against a ?capture=1 short-circuited entry that bypasses the API-key gate.
  • 🤖 GitHub Actions CItsc --noEmit + every smoke + audit:docs + vite build on every PR + push to main; manual capture-preview job (requires system Chrome) on push to main or via workflow_dispatch.
  • 🧪 Economy + replay smokeseconomicsView + appendOnlyReplayDeterminism lock the ledger model + replay determinism; both files include catalog sanity gates that fail fast if a future ship strips a seed catalog.
  • 📚 Contributor guideCONTRIBUTING.md documents 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 inside emptyWorldState() itself as a leading IncomeLedgerEntry row in ledger.income (id "seed", year 1985, month 1, source IncomeSource.Other, sourceRefId "starting_allowance"). The LITERAL invariant state.player.money === sum(ledger.income) − sum(ledger.expense) holds by construction across every consumer. Stays correct under replay because MoneyEarned's reducer case already dedups by event.id (so an accidental duplicate MoneyEarned{id: "seed", ...} would short-circuit against the baked-in row), AND live production callers route exclusively through M1 ledger-aware reducers MoneyEarned/MoneySpent. The diagnostic MoneyChanged reducer bypasses the ledger by design and is reserved for the dispatchStampedEvent.smoke.ts M1-bug regression pin (no production dispatcher fires it).
  • Event-sourced player identity (PlayerIdentitySet) — new SimEvent variant carries handle + groupName. App.tsx::handleNewGame dispatches the event so state.player.groupName flows from the event log instead of being baked into emptyWorldState(). Reducer case is idempotent on (handle, groupName) so a stale-snapshot re-dispatch is a no-op. Closes the v0.1.0 TODO(dynamic-name) hardcode planning item.
  • emit.playerIdentitySet buildersim/events/appendEvent.ts exposes a convenience helper alongside the other emit.* builders. App.tsx uses simulationLoopRef.current?.dispatch(draft) so the dispatch survives an in-flight StrictMode null ref.

Tests

  • sim/__tests__/economicsView.smoke.ts — end-to-end exercise of the EconomyView projection: 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 the docs/event-sourcing.md "If all events are replayed in order, the world state must be identical" invariant. Replays a fixed EventDraft sequence three times through reduceAll and asserts structural equality; secondary scenarios cover SimulationLoop-path idempotency, ts → (year, month) decoding, and the MoneySpent balance 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 via puppeteer-core, records a 6-second 30fps WebM via MediaRecorder and a single-frame PNG via canvas.toDataURL; two-pass ffmpeg-static palette quantisation produces a deterministic GIF for markdown previews. Spawns vite dev on :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.tsx branched entry tree/ mounts <App> wrapped in <ApiKeyBootstrap>; /?capture=1 short-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.tsx window.__CAPTURE__ hook + <canvas id="capture-target-canvas"> — exposes { canvas, isPlaying, resize(w, h) } to the page window (gated on dev mode via import.meta.env.PROD) so the StrictMode-safe capture script can waitForFunction-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 to main: npm citsc --noEmitaudit:docstest:allvite build. Concurrency group cancels in-flight runs on new commits. Default Ubuntu runner + Node 20. Manual capture-preview job (requires system Chrome, opt-in via workflow_dispatch or push to main) runs the headless capture and uploads build/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 from docs/architecture.md for new contributors.
  • README.md "Screenshots & Captures" section — documents the capture:preview scripts + the system-Chrome requirement + the ?capture=1 query short-circuit.

What Changed

  • sim/engine/reducer.tsemptyWorldState().player.groupName retains the "Tricycle Crews" seed default only for the brief pre-MainMenu bootstrap window. The comment documents the contract: the value is overwritten by the first PlayerIdentitySet event dispatched at NEW GAME, so projection readers should treat state.player.groupName as derived from the event log (the same way they treat money from MoneyEarned).
  • package.json — bumped 0.1.00.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 MoneyEarned dispatch in src/App.tsx — the SIM_LOOP_BOOTSTRAP useEffect no longer credits the starting allowance; the seed row in emptyWorldState() is now the single source of truth for player.money = 250 (no IncomeSource import needed in App.tsx anymore).
  • Local dispatchSeed(loop) helpers in sim/__tests__/economicsView.smoke.ts and sim/__tests__/dispatchStampedEvent.smoke.ts — the helpers existed only to dispatch the canonical seed event before each scenario; with the seed baked into emptyWorldState(), fresh loops already start with the canonical state.
  • GEED_SEED alias + leading seed-allowance MoneyEarned event in sim/__tests__/appendOnlyReplayDeterminism.smoke.ts::deterministicEventSequence — the seed lives in emptyWorldState() now, so the scene's stamped sequence starts at the first user-action event (PlayerIdentitySet).

Bug Fixes

  • src/App.tsx any-cast tightencompetitors: any[] replaced with a local RivalEntry interface matching the startPartyVotingProcess rivalsList shape; m: any cast in the BBS message map typed as BBSMessage; choice: any cast in the BBS choice map typed via BBSThread["choices"][number]; type: "collaboration" as any replaced with as SocialEdgeType (already imported). Zero new any types introduced into src/.

Internal Hardening (pre-tag review nits)

  • CI: chromium package name for ubuntu-24.04.github/workflows/ci.yml installs chromium (not chromium-browser) on the current ubuntu-latest. The capture script's findSystemChrome() already checks /usr/bin/chromium.
  • CI: workflow_dispatch trigger — added a manual trigger so release engineers can refresh `build/prev...
Read more

Demoscene Simulator v0.1.0 — initial release

Choose a tag to compare

@TimoP80 TimoP80 released this 29 Jun 17:34

Demoscene Simulator v0.1.0 — initial release

Demoscene Simulator v0.1.0 in-game CRT demo screen preview

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/setState inside /sim and forbid re-declared sim-facing types outside @packages/types.
  • Event-sourced kernel — discriminated SimEvent union (now 30 variants), pure reduce(state, event) → state, deterministic SimulationLoop with a StrictMode-safe reset pattern, append-only eventStore with on(type | "*") subscribe.
  • Deterministic event-sourced economy system — money / hardware / software / freelance jobs / sponsors, with trust-weighted NPC payouts (an NPC cognitive.trustGraph["__player__"] modulates template.basePayment between 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 — paired MoneySpent + HardwarePurchased dispatches 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/utils shared packages — single source of truth for Character, 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.json and vite.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 testsdispatchStampedEvent (locks the "never dispatch(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-time useEffect rewrites 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. New paintDemoFrame module-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 to eventStore.on("*") and re-derived each tick purely through the economicsView projection (no WorldState mutation, no dispatch from 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 — typed RIVAL_RELEASES filter (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 shared paintDemoFrame painter 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.groupName hardcode flagged with a TODO(dynamic-name) for the eventual event-sourced hydrate path (current UI bypasses this through MainMenu's setPlayerGroupName).
  • 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-level ledgerMonthLabel(ts) helper. Helper math uses ((ts - 1) % 12) + 1 so the December → January boundary 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.

Removed

  • Stale "tricycle" sentinel keyword from src/App.tsx::handlePostCustomBbsMessage recruitKeywords — recruit detection is now purely intent-based.
  • src/data.ts and src/types.ts — merged into @sim/data and @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