MillOS v0.40: integrated refinement release - #1
Merged
Merged
Conversation
…tted) Captures the prior 12-lane audit's uncommitted fixes + coverage/ removal + _audit/ artifacts as a bisectable baseline before the launch-polish swarm. Baseline verified green: typecheck/lint/build pass, 1199 tests pass. Co-Authored-By: Claude <noreply@anthropic.com>
Parallel fix swarm (35 assignments, adversarial diff review) + 3 ripple fixes: - correctness: WebGL context-creation fallback (App), fire-drill workers stay mounted when camera outside (MillScene), PhysicsWorker registry cleanup, SCADAPanel quality/alarm type-safety + null guards, milestone div-by-zero guard - perf: module-level reusable temps (Machines/FarmArea/WireframeOverlay), addScaledVector vs clone().multiplyScalar (BreakdownEffects), geometry/texture dispose-on-unmount (Machines/FactoryFloor/AmbientDetails/ambient-safety) - a11y: role/aria-live/progressbar on breakdown fault cards - copy: README badges/version/clone-url, subject-verb template fix - dead code: UIOverlay.tsx.original (-2194), orphaned components/index.ts (-89), committed perf-test + playwright artifacts; unused union member - determinism: Date.now()-as-id -> ref counter (FarmArea) Gates green: typecheck + lint + build (17.1s) + 1199 tests all pass. Co-Authored-By: Claude <noreply@anthropic.com>
…anup Higher-stakes fixes (file-grouped swarm w/ adversarial review + careful inline): - correctness: Machines conditional-hook (Rules-of-Hooks) -> plain const; MobileFirstPersonController touch-look now runs every frame (was gated behind D-pad-held early-return); aiEngine tactical heuristic no longer pollutes the strategic API backoff state - graphics: generativeMaterials GLSL fix (vMapUv was undeclared -> vNormalMapUv / vRoughnessMapUv matching each material's actual map) — fixes shader link errors - multiplayer: join-timeout nulls the singleton (destroyMultiplayerManager) so a timed-out join no longer poisons all later attempts - perf/boot: lazy-load MultiplayerPanel (peerjs out of boot chunk) + AssetPrototypePage - resilience: REST adapter surfaces a distinct 'reconnect limit reached' error; useHistoricalMode selective store selectors (no whole-store re-subscribe); useSafetySimulation stable interval; mobile Dock overflow guard - privacy: in-app disclosure (plaintext key caution; WebRTC peer-IP note) - meta: og:image dims corrected to actual 3456x1993 - cleanup: removed 280MB unreferenced ship-cruft (3D source dumps .blend/.usdc/.zip, *-original.glb, 4x *.glb.backup.glb) + leftover perf-test stdout dumps Note: WebGL-context-fallback gating (App.tsx) was reverted — it gates 100% of users on an unverifiable-in-CI probe; flagged for owner with ready-to-apply patch instead. Gates green: typecheck + lint + build (8.8s) + 1199 tests; runtime smoke clean (full UI renders, zero CSP/shader/console errors). Co-Authored-By: Claude <noreply@anthropic.com>
The first fix swarm lost ~20 of 35 assignments to rate-limit/StructuredOutput failures in later waves (no retry pass). wf2 hardened (try/catch preserves assignment identity + retry pass); re-ran the dropped set, all recovered green: - correctness: PhysicsDebug Rules-of-Hooks violation (early-return before 3 useMemo -> quality-toggle crash); Cat petting-heart Date.now() id -> ref counter; audioManager stopAll() now stops in-flight TTS; aiEngine shift div-by-zero clamp - perf: GPU disposal (TerrainGround geometry+splatMap+heightmap, SafetyEquipment chain-link texture, worker materials, glass material); texturePreloader deferred to idle; dead nowStopped Set removed - a11y/copy: aria-expanded on collapsible sections; degree symbol on SCADA temps; mobile camera-preset icons (Truck/Warehouse) + removed misleading keyboard hints; stale terrain-depth comment - config: FactoryColliders WORLD_RADIUS dedup -> PhysicsConfig source of truth - dead code: deleted unused texture-worker subsystem (useTextureWorker + textureWorker, ~500 lines), empty utils/index.ts barrel, pruned unused sanitize.ts exports (kept all 3 used by multiplayer); deleted scratch truckbay/test-import.ts Agents correctly SKIPPED non-additive 3D refactors (StatusRing useMemo->useEffect timing, ForkliftModel per-frame traverse) per the additive-only rule. Gates green: typecheck + lint + build (8.4s) + 1199 tests; runtime smoke clean. Co-Authored-By: Claude <noreply@anthropic.com>
Applied 84 NEW findings from the explicit 12-lane parallel audit (96+23 agents,
non-overlapping per-lane slices; the structured pass the goal required). Focus on
the lanes the earlier file-grouped pass under-covered:
- L5 a11y: aria-pressed/expanded/region/progressbar, accessible names, roles across
~24 UI panels/widgets/overlays
- L7 copy: label/terminology/version-string corrections
- L4 error handling: multiplayer message default-case + payload validation (AI_VOTE,
PLAYER_JOIN color allowlist), unhandled-rejection guards
- L1 correctness: assorted scene/logic fixes
- L8 visual + L6 UX: safe, additive coherence/flow fixes
- VCLDebugPanel clipboard false-positive ('Copied!' when clipboard unavailable) fixed inline
Legal DRAFTS corrected for accuracy (DRACO + jsDelivr/troika fonts are every-visit,
not opt-in; SCADA external endpoints now disclosed) — still unpublished.
Gates green: typecheck + lint + build (11.6s) + 1199 tests.
NOTE: 30 of 54 assignments dropped to rate-limiting; re-running next (batch D pt2).
Co-Authored-By: Claude <noreply@anthropic.com>
…ents Re-ran (drop-proof, wave 4) the 30 of 54 12-lane fix assignments that dropped to rate-limiting in part 1. 102 fixes applied, 0 reverts: - L1 correctness: FarmAreaInstances + ExteriorVegetation Rules-of-Hooks fixes, SPCCharts cpk NaN guard, StrategicOverlay3D useFrame short-circuit - L9 perf: PhysicsWorker narrow drillMetrics.active selector (kills N-squared re-renders during drills), GPU disposal, hoisted random tilts/geometry, store selector narrowing across scada/vcp/stores - L5 a11y: aria across SCADA/ui/widgets/panels - L4: scada adapter + vcp generator error handling - copy/config across knowledge, game, ui-new A few single findings within assignments skipped as no-op/needs-care (CascadeVisualization substring, UtilityConduits inline-literal memo, PersonalityAnimationManager) — noted, no regressions. Gates green: typecheck + lint + build (8.6s) + 1199 tests. Co-Authored-By: Claude <noreply@anthropic.com>
…action, clear bugs) Focused pass on the highest-value DEFERRED 12-lane findings my conservative safe-only filter had skipped (46 findings, 24 file-groups): - L5 a11y interaction: VotingPanel vote-options/headers div->keyboard-operable button; PA announcement screen-reader live region + reduced-motion ticker + real dismiss button; modal dialog semantics - L7/L8 clear bugs: icon-slug rendered as literal text (KnowledgeTooltip, UnlockNotification) -> actual Lucide icon; FiveAxesPanel dynamic-Tailwind gradient never generated -> static literal classes; MachineInspector hardcoded 'Operating normally' contradicting warning/critical states - L6 UX: dead mobile EMERGENCY STOP (no onClick) wired; destructive Reset/E-Stop confirmations; Datalinks empty-state + initial-focus - L1: ExteriorVegetation Rules-of-Hooks fix Inline touch-ups after adversarial review caught two partial fixes: - Datalinks: split focus effect to [isOpen] only (onClose dep was stealing focus back on every parent re-render — a real regression) - PAAnnouncementSystem: Fragment root on every return path so the sr-only live region fiber stays stable (div<->Fragment flip was remounting it, defeating announcements) Legal DRAFTS further corrected (jsDelivr/troika every-visit). Gates green: typecheck + lint + build (9.8s) + 1199 tests; runtime smoke clean. Co-Authored-By: Claude <noreply@anthropic.com>
Pre-existing uncommitted feature found in the working tree at the start of
this launch-audit session; committed as one atomic unit after validating it
green (typecheck + lint + build + 1199 tests all pass). NOT authored in this
session — preserved intact and isolated from the audit commits so the audit
swarm builds on a clean tree.
Adds an on-device strategic AI backend as an alternative to the Gemini API:
- src/utils/webgpuClient.ts: drop-in for geminiClient (generateContent +
isConnected contract), Qwen3-4B q4f16 via @mlc-ai/web-llm, lazy dynamic
import, circuit breaker, adapter OOM advisory, cache management.
- aiConfigStore: llmBackend selector ('gemini'|'webgpu'), WebGPU lifecycle
state, idempotent load/cancel/unload/delete-cache, startup prewarm.
- aiEngine: backend-agnostic strategic layer via getActiveClient(); USD cost
tracked for Gemini only (local is free).
- GeminiSettingsModal -> AI Settings Modal: backend toggle + Local Neural Core
panel (progress, ready/error, cache delete).
- AICommandCenter: badges reflect the ACTIVE backend's readiness.
- useFocusTrap: live-query focusables (modal content changes on backend toggle).
- index.html CSP: +huggingface.co / hf.co / raw.githubusercontent.com (weights).
- vite.config: exclude @mlc-ai/web-llm from pre-bundling (dynamic import only).
OWNER SIGN-OFF NEEDED (product decision, flagged in FLAGS_FOR_NELL.md): adds a
dependency, ships GB of model weights to the browser on opt-in, widens CSP.
Branch commit != shipping. Does NOT resolve the Gemini model-ID launch-blocker
(default backend stays 'gemini' -> deprecated gemini-3-flash-preview).
Co-Authored-By: Claude <noreply@anthropic.com>
…oder Removes the only unconditional third-party connections on page load: the Google Fonts <link> (transmitted every visitor IP to Google US before any interaction) and the gstatic DRACO decoder (fired on plain page load via the default-scene worker/forklift GLBs). Fonts vendored to src/assets/fonts (vite-bundled, base-correct); DRACO decoder copied to public/draco and pathed via import.meta.env.BASE_URL. CSP tightened: drop fonts.googleapis.com, fonts.gstatic.com, www.gstatic.com. Resolves the L10/L3 GDPR headline finding. Co-Authored-By: Claude <noreply@anthropic.com>
CelebrationSystem: achievement-tracking setInterval was cleared+recreated by churning game-state effect deps before its 60s period elapsed, so it never fired (whole achievement path dead). Moved logic to a ref read by a stable empty-deps interval. MoodAura: registration effect keyed only on [workerId] ran once with a null materialRef on a no-worker-state first render, so auras never registered (frozen). Now gated on isActive so it re-runs when state arrives. InstancedPlansifters: useFrame bailed on null flywheelRef, but the flywheel mesh is not rendered on LOW quality — so sifter bodies + hanger rods never rendered on LOW. Guard now only requires flywheelRef on medium+. All three are behavior-unverified in this no-WebGL harness. Co-Authored-By: Claude <noreply@anthropic.com>
deploy.yml ran independently of ci.yml with no validation, so a broken push to main published straight to production. Add a gate step before build. Co-Authored-By: Claude <noreply@anthropic.com>
Repo-wide prettier --write. Clears pre-existing format drift across ~16 files plus the WebGPU-feature files (webgpuClient.ts, aiEngine.ts, GeminiSettingsModal.tsx) which were committed from WIP state without a format pass. format:check now clean tree-wide. Co-Authored-By: Claude <noreply@anthropic.com>
12-lane seeded sweep (32 agents); 17 verifiable-new fixes, each diff-reviewed by me and reconciled green (typecheck+lint+build+1199 tests). - MultiplayerManager: host-authoritative MACHINE_LOCK (host now DROPS inbound guest locks — verified only the host ever legitimately sends MACHINE_LOCK; guests use INTENT, so zero legit impact, blocks lock-spoofing) + validate peer-supplied selectedMachineId against the machine registry. - geminiClient: guard empty/null model output before caching; tighten token-budget estimate (4 -> 3.5 chars/token, 28k -> 24k cap). - StatusRing: useMemo-with-side-effects -> useEffect([color,opacity,pulseSpeed]) (off render path; <=1-frame status-color lag, behavior-unverified in no-WebGL harness). - MQTTAdapter: bounds-check topic length before slicing untrusted publish frames. - MobilePanel: correct multiplayer dock icon (Users). VotingPanel: visible focus ring (a11y). index.html: document CSP connect-src consumers. Full host-authority peerId->playerId redesign remains flagged (owner-scoped, FLAGS §3). Co-Authored-By: Claude <noreply@anthropic.com>
File-partitioned sweep over the 170-item deferred backlog (79 files, 158 agents); applied only the verifiable-safe subset, each adversarially diff-reviewed by me and reconciled green (typecheck+lint+format+build+1203 tests). a11y (L5): WCAG-AA contrast slate-500->400 across EnergyDashboard/CostEstimationOverlay/DecisionHistoryPanel/FederationPanel/FiveAxesPanel/OwnershipPanel/Datalinks; visible focus rings; region/list/heading semantics. UX (L6): destructive-action confirmations (ZoneCustomization, GraphicsSettings, IncidentHistory, SafetyAnalytics, MobilePanel, MultiplayerLobby); Escape/outside-tap dismiss (Datalinks, CameraPresetMenu); touch-visible delete; negative-radius clamp. copy (L7): terminology consistency within panels (BAMS), labels. perf (L9): aiConfigStore reference-check BAS subscription (drop per-mutation JSON.stringify); productionStore heat-map short-circuit when off; scenarioStore single-copy bounded append; positionRegistry allocation-free collision short-circuit. errors (L4): MQTTAdapter settle-once connect (reject on pre-CONNACK close); WebSocketAdapter connection-state listener; SignalingService explicit peer.reconnect(); MultiplayerManager intent-timeout sweep + resolve-when-no-host. correctness (L1): wildlife invalid scale prop moved to mesh; FocusIndicator always-mount+visible-toggle (line no longer orphaned, behavior-unverified); SCADAService alarm-archive dedup (+ new passing test). config (L12): deploy.yml rsync-excludes nested version dirs (mp3s kept — load-bearing for v0.30 audio); release.yml git-cliff keepachangelog (fixes missing cliff.toml); prototype-deck CSP parity. REVERTED (not shipped): src/stores/stabilityStore.ts require()->import — it revives the dead ownership->friction integration, which changes gameplay balance and broke 6 tests asserting current behavior. Flagged as an owner decision (activate+rebalance+update tests, or remove the dead code). Co-Authored-By: Claude <noreply@anthropic.com>
webgpuClient.ts (6454b86) imports ./webgpuWorker.ts via new Worker(new URL(...)) to run @mlc-ai/web-llm off the main thread. The worker was left untracked, so the committed feature would fail to build on a fresh checkout. Builds + typechecks + 1203 tests green with it present. Co-Authored-By: Claude <noreply@anthropic.com>
Curated launch-audit deliverables. _audit/.gitignore tracks only these hand-off docs; the machine scratch (raw findings JSON, per-lane agent output, fix manifests, workflow scripts) stays ignored. Co-Authored-By: Claude <noreply@anthropic.com>
Per the disconnected-features investigation (16-agent sweep): these are truly orphaned (zero importers) and superseded/abandoned/duplicate. Deleted (git-recoverable), typecheck + 1200 tests green: - ComplianceDashboard (superseded by live SCADAPanel), SPCCharts, ShiftHandover (note: live ShiftHandoverSummary is separate), ShiftBriefing, PreferenceRequestWidget, AssetShowcase, ManagementStylePanel (dup of MobilePanel ManagementContent), MillSceneMinimal (debug scene), VillageAreaOptimized (perf alt to live VillageArea), FarmAreaInstances (perf alt to live FarmArea), SmartForklift (vs live ProceduralForklift). - Cleaned the now-dead uiStore SPC-charts toggle (+ its tests). KEPT (live, false-positive in the L11 flag): WorkerMoodOverlay (rendered at WorkerSystem.tsx:1336), InstancedVillageComponents (VillageArea uses InstancedLamps). FLAGGED not deleted (owner call): CelebrationSystem (mixes R3F <points> + DOM — needs a 3D/DOM split before wiring, would crash if mounted as-is), CrisisEventSystem (+CrisisEffects, needs Canvas wiring), MissionControl (redundant with live OverviewPanel). Co-Authored-By: Claude <noreply@anthropic.com>
getOwnershipFrictionMultiplier used a CommonJS require() that silently failed in the ESM/Vite browser bundle, leaving the 'more worker ownership -> less friction' mechanic dead. Switched to a static import (no circular dep: ownershipStore doesn't import stabilityStore). Tests: reset ownership to 0 in beforeEach so the friction-math unit tests stay deterministic (neutral 1.0 multiplier); the integration is exercised via the default worker-owned state. 75/75 stabilityStore tests pass. Co-Authored-By: Claude <noreply@anthropic.com>
Per owner decision: align all og:/canonical/twitter URLs + ld+json + img-src CSP to www.millos.net (matches the CNAME file). Configure apex->www redirect + DNS A/AAAA or www CNAME at the registrar. Co-Authored-By: Claude <noreply@anthropic.com>
Code: - API_KEY now reads SCADA_API_KEY ?? API_KEY (deploy manifests inject API_KEY; the mismatch caused a prod crash-loop + silent auth-disable). - Constant-time API-key comparison (safeKeyEqual via crypto.timingSafeEqual) at both the REST middleware and the WebSocket upgrade — removes the timing side-channel of the prior ===. - (Production fail-fast on missing key was already present.) Dependencies (npm audit fix → 0 vulnerabilities, tsc still compiles): - jsrsasign (CRITICAL: DSA key extraction / signature-bypass / forgery, transitive via node-opcua-client), express-rate-limit (HIGH: IPv4-mapped-IPv6 rate-limit bypass), minimatch (HIGH: ReDoS), jsdiff (DoS) — all patched in package-lock. Still flagged: WS auth still accepts the key via query string (leaks into logs); moving it to a subprotocol/short-lived token needs a coordinated frontend (SCADAPanel) change. Co-Authored-By: Claude <noreply@anthropic.com>
…LOC) All verified zero live importers; build + 1200 tests green (git-recoverable): - utils/gpuManagement.ts (barrel) + its exclusive consumers geometryMerger.ts, lodIntegration.ts, objectPool.ts - utils/pathfinding.ts (~660 LOC A*, self-documented dead), utils/truckPath.ts - systems/bas/workerBehaviorEngine.ts - utils/apiSecurity.ts (648 LOC, fully unimported — never-wired CSRF/JWT/secureFetch helpers mixed with generic utils) FLAGGED not deleted (needs the prototype-deck decision first): the truckbay model cluster (TruckModel/MergedTruckParts/etc.) is still imported by src/prototypes/AssetPrototypePage.tsx, and the truckbay/ dir also holds LIVE files (DockBay, LoadingAnimation). Co-Authored-By: Claude <noreply@anthropic.com>
Per owner decision: governing law/jurisdiction = England and Wales (UK GDPR + Data Protection Act 2018); rights-contact = nell@ethicsnet.com. Self-hosting of fonts/DRACO noted as done. Still [CONFIRM] (not invented): controller legal identity (individual vs company), liability-cap, effective date, <Text> font self-host, min-age line. Drafts remain unpublished + carry an attorney-consultation disclaimer. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…MissionControl Per your 'proceed' on the recommendation brief (deep per-component investigation): - CelebrationSystem (+ConfettiBurst): unmountable hybrid (R3F <points> + DOM in one return) AND its 8 hardcoded achievement IDs don't exist in the 17-achievement store — a broken half-integration; milestones are already PA-announced. achievementsStore kept as a foundation. - CrisisEventSystem (+CrisisEffects): functionally overlaps the live fire-drill emergency system; never wired. Store crisis API kept (still exercised by storeIntegration.test). - MissionControl: redundant with the live OverviewPanel; zero importers. 1200 tests green. Co-Authored-By: Claude <noreply@anthropic.com>
… delete orphaned truckbay cluster
The Asset Prototype Deck ('Prototype Lab — Original vs Changed Models') is internal dev tooling, not user-facing (unreachable from the app, direct-URL only) yet shipped as a production rollup input with its own weaker CSP. Dropped the prototypes entry from vite.config + main.tsx route + prototypes/index.html. That orphaned the truckbay model cluster (~1500 LOC, only ever fed the deck) — deleted it. Kept the LIVE truckbay helpers useTruckPhysics + animationSystem (used by TruckBay/FactoryExterior).
Co-Authored-By: Claude <noreply@anthropic.com>
…nection) drei's <Text> (troika-three-text) fetches glyph data from cdn.jsdelivr.net (unicode-font-resolver) on first render whenever no font= is set — the last unconditional third-party connection on load. Added src/components/shared/SceneText: a drop-in <Text> defaulting font to a self-hosted Inter-Regular.otf (base-aware), and swapped the import in 19 of the 23 scene files (relative import; the @/ alias resolves in build+typecheck but NOT in vitest). Result: zero third-party IP transfer on load for Latin UI text. If the local font ever fails, troika falls back to the resolver (app keeps working) — so jsDelivr stays in the CSP for that safe fallback; remove it once 3D labels are confirmed rendering in a real browser. NOTE: behavior-unverified here (no WebGL); please eyeball the 3D labels. Co-Authored-By: Claude <noreply@anthropic.com>
…the concurrent-edit collision Co-Authored-By: Claude <noreply@anthropic.com>
…-6 audit fixes & dead-UI removal
Headline: the 3D scene rendered blank because SceneText defaulted <Text> to a CFF/PostScript
Inter-Regular.otf that troika-three-text cannot parse. troika swallows the parse error without
resolving drei's font suspend, so every <Text> suspended forever and the scene's
<Suspense fallback={null}> never resolved (DOM UI still showed). Converted Inter to TrueType (glyf)
via fontTools cu2qu, repointed SceneText, deleted the broken OTF, corrected the misleading comment.
Verified end-to-end under software WebGL: scene mounts, loading screen clears, zero font errors.
Parallel-audit defect fixes (find -> adversarial-verify -> fix; all gated green):
- Critical: MaintenanceSystem slip/cough reactions threw outside their provider, crashing the whole
scene via the app ErrorBoundary on ultra quality.
- Resource/render: village lamp glass remount, splash-buffer NaN on quality change, frozen
medium-quality rain, truck two-clock desync, physics-mode forklift stale state, invisible
grain-spill pile, DataFlowLine/StatusRing/HoloLabel/FarmArea GPU leaks, instanced silo/packer
cull-restore.
- Data/logic: CascadeVisualization ghost machine IDs + fuzzy load match, throughput KPI
triple-counting (added packer-only flow accumulator), flourishing per-worker reactivity, GPU
stats "geometrys" key typo, scenario completion/timing, truck-schedule unit mismatch, machine
temperature convergence, multiplayer vote application, markdown table flush, clipboard guard,
audio analyser disconnect.
- 3D placement: decision rings under elevated sifters, fireflies latch, city lights, worker
billboard LOD float, conveyor roller overhang, fire-drill exit sensors, AmbientDetails (geometry
leaks, JSX Math.random re-roll, geometry-scale ignored, moth right-wing).
Dead UI removal (~5,000 LOC): deleted the legacy UIOverlay shell + its test and 5 fully-orphaned
ui/ widgets (ui-new/GameInterface is the live shell); kept 9 stranded widgets for later re-home.
e2e: rewrote ai-command-center.spec.ts against the ui-new dock (added testids to the embedded
AICommandCenter); the old spec targeted deleted UIOverlay testids.
Also includes prior uncommitted launch-audit placement/layout work: machine roster 6->4 propagation
(materialFlowStore/tagDatabase/game-shared/CascadeVisualization), central spine conveyor, dock
opening dimensions, crane/pallet/shower/sign placement, suspension rods, and the self-hosted 3D
<Text> font swap (SceneText).
Known follow-up (not fixed): an intermittent, sequence-dependent "Maximum update depth" render loop
when opening the SCADA panel after rapidly cycling panels/camera; pre-existing useSyncExternalStore
race in src/scada/useSCADA.ts, flagged in _audit/FLAGS_FOR_NELL.md.
Gates: typecheck, eslint, prettier, 1183 tests, production build, and e2e boot smoke all green.
Co-Authored-By: Claude <noreply@anthropic.com>
GameInterface had two effects, each keyed on [<panel flag>, activeMode], that unconditionally forced activeMode to their own mode. The I and O keyboard shortcuts toggle showAIPanel and showSCADAPanel independently (useKeyboardShortcuts.ts:201/213), so pressing I then O makes BOTH true; effect A then drove activeMode -> 'ai' and effect B -> 'scada', each re-firing the other through the activeMode dependency -> an infinite ping-pong tripping React's "Maximum update depth exceeded". Each effect now depends only on its own flag and uses a functional setActiveMode, so it reacts to a flag change once with no feedback between the two effects (last-opened panel wins). Verified deterministically: open AI (I) then SCADA (O), both left open 18s -> 43 "Maximum update depth" bursts before, 0 after. Panels still open/switch correctly (AI via keyboard + dock; SCADA last-wins). Gate green: typecheck, eslint, prettier, 1183 tests, build. Co-Authored-By: Claude <noreply@anthropic.com>
The hardcoded 'gemini-3-flash-preview' model ID left the live AI layer silently dead once that preview model was retired. The client now walks a verified fallback chain (gemini-3.5-flash -> 3-flash-preview -> 2.5-flash) on model-not-found errors, surfaces the active model in the settings modal, and the cost tracker prices by the model actually serving requests (over-estimating for unknown IDs rather than under-reporting). Co-Authored-By: Claude <noreply@anthropic.com>
…ster split - Verified player identity assigned once at connect; every identity-bearing field (PLAYER_UPDATE.id, INTENT.playerId, CHAT.from, AI_VOTE.playerId) is stamped server-side so peers cannot impersonate. - handleMessage split into host/guest role-gated handlers; host-only message types from guests are dropped. - Host now relays PLAYER_UPDATE/CHAT/AI_VOTE to other guests (star topology) - previously 3+ player rooms had frozen avatars and host-only chat. - New _remoteRosterArray (id/name/color only, rebuilt on join/leave/ rename) for UI lists, so lobby/chat/voting panels stop re-rendering on every 20Hz movement packet; kinematic consumers keep the live array. Co-Authored-By: Claude <noreply@anthropic.com>
… completion - tickMaterialFlow was orphaned when ConveyorSystem was modularized (a5d0c21) - the grain->mill->sifter->packer network silently froze. Now ticked from UnifiedGameTick with machine-status coupling. - materialFlowStore ids aligned to the live scene roster (silo-0..4, rm-101..104, packer-0..2); legacy ids matched nothing. - Receiving-truck dockings refill the emptiest silo (15t) so silos no longer drain dry permanently. - Shift change now completes: leaving -> entering -> completeShiftHandover (supervisor handoff, shift-notes archive, production/incident reset). Previously shiftChangeActive stayed true forever. - resetGameState/clearPersistedState clear emergency/drill/shift flags. - WorkerDetailPanel task assignments use live machine ids. - VCP getAverageAccuracy reports neutral 0.5 (not perfect 1.0) with no track record. Co-Authored-By: Claude <noreply@anthropic.com>
…subscriptions - Safety analytics/config widgets re-homed into SafetyPanel tabs; PredictiveMaintenancePanel into OverviewPanel; KeyboardShortcutsModal mounted in GameInterface (? key finally opens it). - Redundant MillClockDisplay/TruckScheduleWidget deleted (OverviewPanel already covers both). - SettingsPanel/ScenarioPlayground/VotingPanel subscribe to used slices via useShallow instead of whole stores (re-render storms). - Shortcuts modal lists C (auto-rotate) and Ctrl+B (blueprint mode). Co-Authored-By: Claude <noreply@anthropic.com>
…ays, worker anim fixes - Ambient components (cobwebs, stains, bulletin board, photos, graffiti) dispose their manually-created CanvasTextures/geometries on unmount; stable random rotations (no per-render re-roll). - depthWrite=false on transparent overlay materials: village glass/ smoke, canal depth, boat cratch/shadow, daylight glow, splash particles, packer bag indicator (depth-sort flicker). Village cobble deliberately untouched (see Z-Fighting Audit Log). - Worker fatigue is accumulator-based (breaks visibly recover; no wall-clock clobber); dead idleDuration field removed and idle pause duration actually applied. - Shared belt normalScale / ceramic pot material instead of per-render or per-mount creation. Co-Authored-By: Claude <noreply@anthropic.com>
…solation, BAMS naming - @rollup/rollup-darwin-arm64 moved to optionalDependencies; lockfile regenerated so CI's npm ci no longer needs --force. - Root redirect page carries full SEO/OG/Twitter head (canonical URL is that page; crawlers don't follow the meta-refresh). - Playwright on dedicated port 5180 --strictPort (reuseExistingServer once silently tested a Grafana login page on :3000). - BAS -> BAMS naming swept in README/educational content. - CLAUDE.md: Interiora v5.2.6 sync. Co-Authored-By: Claude <noreply@anthropic.com>
public/vcp-logo.png + public/v0.20/vcp-logo.png -> transparent cropped VCP mark so the VCLDebugPanel gradient box shows through. Matches valuecontextprotocol.org canonical. Co-Authored-By: Claude <noreply@anthropic.com>
…raphics, tests Gameplay (previously built-but-dead systems now live): - Achievements: new useAchievementTracker hook drives all 17 achievements from real sim state; aiEngine phantom IDs remapped to real ones - Democratic voting: periodic vote generator + scenario vote_called events create real votes; VotingPanel stale-closure auto-close bug fixed - Scenario choice_points: interactive choice modal with effects, outcome text, and basMetrics recording (activates all four BAS scenarios) - Maintenance: Perform Maintenance button consumes spare parts and reduces wear; parts inventory finally reachable - Production loop: bag output now scales with material flow and machine health; Quality KPI tracks QC lab grades instead of freezing at 99.5; daily target resets at rollover with milestone celebrations; target widget shown by default; 3-step first-run onboarding - Breakdown/cooperation double-tick fixed (single authoritative driver) Accessibility: - Ctrl/Cmd/Alt no longer hijacked by single-letter shortcuts (WCAG 1.4.4) - Spacebar on focused buttons no longer triggers emergency stop (2.1.1) - Version select no longer navigates on change (3.2.2); focus/ARIA/contrast fixes across HUD, panels, modals; MotionConfig respects reduced motion - AboutModal wired into sidebar; all version strings from package.json Docs/education: - Five Axes entries now teach the shipped BAMS axes (knowledgeStore, README) - README/CLAUDE.md stats corrected (4 mills, 15 machines, 78 SCADA tags) - Mondragon timeline and Semler quote/revenue corrected; emoji policy violations in DOM UI replaced with Lucide icons Graphics: - Fireflies/HeartParticle throttle-compensated delta (full-speed animation) - Parking-lot depthWrite hazard fixed; better trees, office, fountain water, car shadows, LOW-quality machine silhouettes; dead TruckBay components and AtmosphericHaze toggle removed; NaN guards on degenerate geometry Tests: - Vacuous tick test replaced with exact assertions; fire-drill evacuation suite added; new coverage for materialFlow/breakdown/achievements/ engagement stores (66 new tests); flaky wall-clock perf assertions replaced with behavior assertions - e2e: serialized workers, headed locally (headless SwiftShader makes 3D mount take 150s+), onboarding-queue-aware dismissal Dead code removed: WorkerSystem.tsx (1375 lines, superseded), auditStore, GPUMemoryMonitor, ProductionTargetsWidget duplicate. Gates: typecheck, eslint, prettier, 1259 unit tests, 5 e2e tests, build all green. Verified live in Chrome (onboarding, target widget, zero console errors). Co-Authored-By: Claude <noreply@anthropic.com>
…y geometry - Receiving trucks now carry a spare-parts resupply alongside grain: new breakdownStore.restockDelivery() tops up each part type by 2 on the dock rising edge in UnifiedGameTick, capped at default stock. This closes the maintenance loop (repair consumes parts, deliveries replenish them) and makes the low-inventory warning reachable in both directions. Two tests. - ExteriorVegetation instanced parkland trees now use the icosahedron- cluster canopy (single source of truth exported from ExteriorVegetation; FactoryExterior's SimpleTree imports it instead of defining its own). Per-tree variant/rotation/scale jitter matches individually-placed trees; 4 draw calls total (trunk + 3 canopy variants). Dead SIMPLE_TREE_MATERIALS removed from sharedMaterials. Gates: typecheck, eslint, prettier, 1261 unit tests, build all green. Co-Authored-By: Claude <noreply@anthropic.com>
…ence, styled reset dialog - ProductionTargetWidget: rehome to bottom-left (clear of the settings sidebar), add close button + launcher pill; defaults closed now - gameSimulationStore/UnifiedGameTick: reconcile currentShift with the clock (fixes stale shift, e.g. "Afternoon" at 23:59); setGameTime syncs shift too - graphicsStore: default resolutionScale 50% -> 25% (+ persist v1 migration) - SkySystem: fix vertical cloud seam (atan branch cut -> seamless direction-vector projection) - game/shared: double PA announcement spacing (more occasional quips) - SettingsPanel + new ConfirmDialog: replace native window.confirm resets with a styled, portal-rendered dialog - aiConfigStore: production target defaults closed - tests: ProductionTargetWidget + UnifiedGameTick shift regression Co-Authored-By: Claude <noreply@anthropic.com>
…silo shell Checkpoint of the launch-audit-polish tree. Verified green at this state: typecheck, lint, build, and 91 test files / 1603 tests all pass. Blender and asset work in this checkpoint: - normalize-model-assets.mjs simplifies the forklift wheel bolt clusters and hubs via meshopt: 157,335 -> 124,188 render vertices, 482 KB -> 422 KB, with bounds and glTF file order unchanged. - Forklift animation clips are renamed by an explicit source-name map instead of by array index, and an unmapped clip now throws. A measured Blender round trip of the source permutes animation order; under the old index mapping that would have silently swapped fork raise/lower and reversed the wheel clips. The same round trip permutes mesh order, which is why the reduction is done in the pipeline rather than in a modeller. - asset-manifest.json pins the two hydraulic ram nodes that ForkliftModel resolves by generated index name, so mesh reordering fails validation instead of quietly disabling the telescoping ram. Budgets tightened to measured size. - CompactMachines silo drums use a corrugated lathe shell in place of a 16-sided cylinder, with the smooth envelope kept as a picking proxy: a raycast with the cursor over a silo stays at ~61 us rather than the 6.6 ms the dense shell would cost on every pointer move. Verified in the running app - silos render and selection still resolves. - scripts/blender/ adds the headless tooling used above, plus PROMPTS.md covering headless vs MCP routing. This also sweeps in the accumulated launch-audit-polish WIP already present in the tree. It is not separable: several files edited above were themselves untracked. Known: prettier still reports src/components/CameraController.tsx as unformatted. Pre-existing and untouched here. Co-Authored-By: Claude <noreply@anthropic.com>
NellInc
marked this pull request as ready for review
August 4, 2026 00:53
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
mainDeterministic validation
npm run typechecknpm run lintnpm run format:checknpm test: 99 files, 1,638 testsVERSION=v0.40 GITHUB_SHA=$(git rev-parse HEAD) npm run build: 3,721 modulesnpm run validate:assetsnpm run validate:depthnpm run validate:shadersnpm run validate:bundle: 0.59 MiB initial JS gzip, 100.62 MiB current buildnpm run build:archives: deterministic manifestnpm audit --audit-level=high: 0 vulnerabilitiesactionlint .github/workflows/*.ymlnpx impeccable detect src: no findingsnpm run test:e2e: 2 passed, including Axe WCAG A/AA checksRelease staging proof
v0.40-83a96caca77620bcbe044fc6d7ca7275999fec63ddce83fd49d0Runtime and visual proof
Publication
Ready for merge, final
v0.40.0tag, GitHub release, Pages deployment, and live verification.