Skip to content

MillOS v0.40: integrated refinement release - #1

Merged
NellInc merged 47 commits into
mainfrom
codex/v040-release
Aug 4, 2026
Merged

NellInc merged 47 commits into
mainfrom
codex/v040-release

Conversation

@NellInc

@NellInc NellInc commented Aug 4, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • integrates the complete v0.40 launch refinement with current main
  • restores a unified inside/outside mill world with walls and large windows, retained town, stream, garage, farm, logistics, personnel, celestial environment, SCADA, UI, and operational systems
  • preserves the deployed v0.30 application from its exact source commit while staging v0.10, v0.20, v0.30, and v0.40 without nested archives
  • isolates versioned builds so the current application stays within its bundle budget
  • updates release, Pages, SEO, dependency, asset, shader, depth, accessibility, and runtime evidence surfaces

Deterministic validation

  • npm run typecheck
  • npm run lint
  • npm run format:check
  • npm test: 99 files, 1,638 tests
  • VERSION=v0.40 GITHUB_SHA=$(git rev-parse HEAD) npm run build: 3,721 modules
  • npm run validate:assets
  • npm run validate:depth
  • npm run validate:shaders
  • npm run validate:bundle: 0.59 MiB initial JS gzip, 100.62 MiB current build
  • npm run build:archives: deterministic manifest
  • npm audit --audit-level=high: 0 vulnerabilities
  • actionlint .github/workflows/*.yml
  • npx impeccable detect src: no findings
  • npm run test:e2e: 2 passed, including Axe WCAG A/AA checks

Release staging proof

  • current manifest: v0.40-83a96caca776
  • archived releases: v0.10, v0.20, v0.30
  • v0.30 rebuilt from deployed source 20bcbe044fc6d7ca7275999fec63ddce83fd49d0
  • rebuilt v0.30 main bundle SHA-256 matches the live deployment byte-for-byte
  • no branch-introduced or tracked blobs exceed 50 MiB

Runtime and visual proof

  • uncontended native startup, five scenes: 262 to 420 ms, all pass
  • production-compressed Fast 3G staging, five scenes: 4.02 to 4.20 s, all pass against the 8 s gate
  • day art matrix: 10 scenes, continuous world, all pass, no console, page, or request failures
  • night art matrix: overview, water, village, and farm, all pass
  • dedicated sun and moon captures confirm both celestial bodies are present, animated, and camera-addressable
  • motion matrix confirms forklift loading/travel transitions, pallet state changes, truck steering/articulation, wheel rotation, doors, and landing gear
  • human acceptance was recorded on 2026-08-01

Publication

Ready for merge, final v0.40.0 tag, GitHub release, Pages deployment, and live verification.

NellInc and others added 30 commits June 4, 2026 01:23
…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>
…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>
NellInc and others added 17 commits June 12, 2026 01:15
…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
NellInc marked this pull request as ready for review August 4, 2026 00:53
@NellInc
NellInc merged commit 35d64cd into main Aug 4, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant