From 29d05bbfc8aba9683c13d859023487f7b5f1e7d2 Mon Sep 17 00:00:00 2001 From: NellInc Date: Tue, 11 Aug 2026 10:12:17 +0100 Subject: [PATCH] Polish v0.40 industrial coherence --- public/portraits/aristotle.webp | Bin 11348 -> 0 bytes public/portraits/elinor-ostrom.webp | Bin 14028 -> 0 bytes public/portraits/jos-de-blok.webp | Bin 6776 -> 0 bytes .../jose-maria-arizmendiarrieta.webp | Bin 6396 -> 0 bytes public/portraits/ricardo-semler.webp | Bin 6896 -> 0 bytes public/portraits/robert-greenleaf.webp | Bin 11904 -> 0 bytes public/portraits/rodrick-wallace.webp | Bin 6292 -> 0 bytes public/portraits/w-edwards-deming.webp | Bin 16204 -> 0 bytes scripts/validate-uncrewed-build.mjs | 20 ++- src/components/FactoryExterior.tsx | 4 +- src/components/ForkliftSystem.tsx | 11 +- src/components/TruckBay.tsx | 144 ++++++++++-------- .../environment/NearHorizonCity.test.ts | 14 +- .../environment/NearHorizonCity.tsx | 33 ++-- .../exterior/organicLakeGeometry.test.ts | 25 +-- .../exterior/organicLakeGeometry.ts | 62 ++++++-- src/components/knowledge/Datalinks.tsx | 16 +- .../knowledge/KnowledgeEntryCard.tsx | 13 +- src/components/scenery/FairytaleCastle.tsx | 16 +- .../truckbay/truckController.test.ts | 41 +++++ src/components/truckbay/truckController.ts | 47 ++++++ src/components/ui-new/GameInterface.tsx | 2 +- src/components/ui-new/hud/StatusHUD.tsx | 3 +- src/config/humanPresencePolicy.test.ts | 8 + src/constants/__tests__/siteLayout.test.ts | 30 +++- src/constants/siteLayout.ts | 16 +- .../vehicles/forkliftController.test.ts | 30 +++- src/simulation/vehicles/forkliftController.ts | 49 +++++- src/stores/knowledgeStore.ts | 1 - src/systems/UnifiedGameTick.ts | 82 ++++------ .../UnifiedGameTick.production.test.ts | 24 +-- 31 files changed, 457 insertions(+), 234 deletions(-) delete mode 100644 public/portraits/aristotle.webp delete mode 100644 public/portraits/elinor-ostrom.webp delete mode 100644 public/portraits/jos-de-blok.webp delete mode 100644 public/portraits/jose-maria-arizmendiarrieta.webp delete mode 100644 public/portraits/ricardo-semler.webp delete mode 100644 public/portraits/robert-greenleaf.webp delete mode 100644 public/portraits/rodrick-wallace.webp delete mode 100644 public/portraits/w-edwards-deming.webp diff --git a/public/portraits/aristotle.webp b/public/portraits/aristotle.webp deleted file mode 100644 index ed05a87f8d943283d70ea239b6757e42c26f1639..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11348 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 diff --git a/public/portraits/jos-de-blok.webp b/public/portraits/jos-de-blok.webp deleted file mode 100644 index 9533057bc20fd9d6f43ecc2d0b66bd658d1af17d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6776 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 a)bau1xu=zXpXiMLcCLhk{vM6c0001fWDr>Z diff --git a/public/portraits/jose-maria-arizmendiarrieta.webp b/public/portraits/jose-maria-arizmendiarrieta.webp deleted file mode 100644 index 5afc5bff18cd05ce679e327616ee56397dbd9102..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6396 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

czS~bf4;d)3^4%pQy>P^+3tp zBukz%ytO~Q)v1EMsjQ^iL$1RVx{o;kg;KPuY48jS_an-U#l180Gy?t_3K@tUai33^ z0GX+91HQJZVA-S~SHVV{s*B=0|gMVoSSw*ru2roeUs zN^#{Oh*Ub=5QdIktw)WHf3>~uc26>Hbz$n~2{vfdQZ7f_p-q!X7>HUxKMdzsNyb&THj9!PHGN_$ z@mCmBQxwt|!EZrqw}$H+>I9B8;`HS5hLApgW}r0txTaRQ-`VH|{8u{RHQXA2YfPF> zt#&@JXfSI-{7cW^_f!o4Rnu-7$b^3g*N|`V1a6@-0>I%e91Ni+9z$eIE{4W~*o7gm z;C%wjvkQ`{dkYloRPbCXLRNN0OcTaxKL1+!#7{te(wJ$7BePVgvE64uya3add#0`Q zd0L)5wb5-!!RG~Qx59~+E0)CSaqUcBA~0T&HU=#8VYf&62n9kp-h|lyKw+ zd_)1aVuL`+rHzuuu|WRg!Tun4E)$gqR35oV=A6@X^}`Z{fccIFwT~x>hywO^*{ECF z{Pmo7EN9z(_tv%lVAGZ$gx86`SJEOt);}Zdr;0RU{c0=SDxT%%r_5<=qI<20x6K;m z1OB5y=nh#)2C>t}$Ga1H^d@NTsd@0ZcI zW<@|Z93-N|TICIY&tB7rZ1{7(JXGI~L_U(o6S}aI66p?fEp7wJ)cWYivjp%KFzH8j ze#>c;F=|z@Q6@^UkNqWGtVUN7J&=62Jv7!hR0vDY1WEgai|RRq1GbL0%9*V(f)`A+ z5n5@M+~7#{U>wQVxR4?8)%%K~+rUmu{U_&D*I+m|?K%GSgJ`{_Dj5BY-QtnB1q5Bg zd4kh-+J9|BaxIft10T&OQ*Peo(7d3dxX~%5hB=v&m0*9jlWH-EzJjxs~i@Wp9OM0%dfMm z*`L7iEQbFxIcG^cRD--hU8nJ=Ev02$B~ZCni;~^M2D*N}Z1d80`s2)Rn+aiF=qFIp q8R3CLv10OotNhFI9HU(N44LeB)26t`7Ma3iD+c?v-9(Bw0001Ho`+Zf diff --git a/public/portraits/robert-greenleaf.webp b/public/portraits/robert-greenleaf.webp deleted file mode 100644 index 1314007ae6e4dd8d40396e6680556eb118407a91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11904 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 _5 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 GNdN$SQcP+9 diff --git a/public/portraits/rodrick-wallace.webp b/public/portraits/rodrick-wallace.webp deleted file mode 100644 index e166f6a41fbcf9e3d8e8f4ed4f8fb7d5d5c63dbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6292 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 diff --git a/scripts/validate-uncrewed-build.mjs b/scripts/validate-uncrewed-build.mjs index f4319c3..8e06572 100644 --- a/scripts/validate-uncrewed-build.mjs +++ b/scripts/validate-uncrewed-build.mjs @@ -12,7 +12,8 @@ const DESIGN_FILES = [ ]; const forbiddenPathPatterns = [ - { label: 'portrait directory', pattern: /(^|\/)assets\/workers(\/|$)/i }, + { label: 'worker asset directory', pattern: /(^|\/)assets\/workers(\/|$)/i }, + { label: 'portrait directory', pattern: /(^|\/)portraits(\/|$)/i }, { label: 'character model directory', pattern: /(^|\/)models\/worker(\/|$)/i }, { label: 'human-specific asset filename', @@ -27,7 +28,11 @@ const forbiddenRuntimePatterns = [ pattern: /WorkerSystemNew|WorkerDetailPanel|WorkforcePanel|WorkerModel|RemotePlayerAvatar|SeatedVehicleOperator|DockSpotter|WarehouseWorkerWithPalletJack/, }, - { label: 'human asset URL', pattern: /assets\/workers|models\/worker|worker-(masculine|feminine)/i }, + { + label: 'human asset URL', + pattern: /assets\/workers|models\/worker|worker-(masculine|feminine)/i, + }, + { label: 'portrait asset URL', pattern: /portraitPath|[/\\]portraits[/\\]/i }, { label: 'portrait roster', pattern: /marcus_chen|sarah_mitchell|james_rodriguez|emily_ronson|jennifer_lee/i, @@ -87,12 +92,14 @@ async function main() { for (const file of sourceFiles) { if (isArchivedPath(file.relative) || isTestSource(file.relative)) continue; for (const rule of forbiddenPathPatterns) { - if (rule.pattern.test(file.relative)) failures.push(`active source ${rule.label}: ${file.relative}`); + if (rule.pattern.test(file.relative)) + failures.push(`active source ${rule.label}: ${file.relative}`); } if (!/\.[cm]?[jt]sx?$/i.test(file.relative)) continue; const content = await readFile(file.absolute, 'utf8'); for (const rule of forbiddenRuntimePatterns) { - if (rule.pattern.test(content)) failures.push(`active source ${rule.label}: ${file.relative}`); + if (rule.pattern.test(content)) + failures.push(`active source ${rule.label}: ${file.relative}`); } } @@ -100,7 +107,8 @@ async function main() { for (const file of publicFiles) { if (isArchivedPath(file.relative)) continue; for (const rule of forbiddenPathPatterns) { - if (rule.pattern.test(file.relative)) failures.push(`current public ${rule.label}: ${file.relative}`); + if (rule.pattern.test(file.relative)) + failures.push(`current public ${rule.label}: ${file.relative}`); } } @@ -118,7 +126,7 @@ async function main() { } console.log( - `Uncrewed delivery contract passed: ${files.length} delivery files and ${sourceFiles.length} source files; no human assets, host voices, personnel modules, or personnel design studies.` + `Uncrewed v0.40 contract passed: ${files.length} current delivery files and ${sourceFiles.length} active source files contain no human assets, portraits, host voices, personnel modules, or personnel design studies. Archived releases and source provenance remain outside this current-release gate.` ); } diff --git a/src/components/FactoryExterior.tsx b/src/components/FactoryExterior.tsx index 653b2ec..9b2ae8e 100644 --- a/src/components/FactoryExterior.tsx +++ b/src/components/FactoryExterior.tsx @@ -1503,8 +1503,8 @@ const Lake: React.FC<{ const safeH = Number.isFinite(size?.[1]) && size[1] > 0 ? size[1] : 20; const mainRadiusX = Math.max(0.1, safeW / 2 - 1); const mainRadiusZ = Math.max(0.1, safeH / 2 - 1); - const shoreRadiusX = Math.max(0.1, safeW / 2 + 2); - const shoreRadiusZ = Math.max(0.1, safeH / 2 + 2); + const shoreRadiusX = Math.max(0.1, safeW / 2 + 1.2); + const shoreRadiusZ = Math.max(0.1, safeH / 2 + 1.2); const waterGeometry = useMemo( () => createOrganicLakeSurfaceGeometry(mainRadiusX, mainRadiusZ), [mainRadiusX, mainRadiusZ] diff --git a/src/components/ForkliftSystem.tsx b/src/components/ForkliftSystem.tsx index 53a6aa7..99cee1d 100644 --- a/src/components/ForkliftSystem.tsx +++ b/src/components/ForkliftSystem.tsx @@ -139,7 +139,10 @@ const BEACON_SWEEP_RATE = 4; * illumination while it is moving rather than only when it stops. * * The wedge is additive geometry rather than a spotlight for the same reason: - * a mesh's `visible` flag costs nothing, a light's changes every shader. + * a mesh's `visible` flag costs nothing, a light's changes every shader. The + * real point light is reserved for high and ultra because each point light + * adds a loop to every lit material in the complete scene. Medium retains the + * emissive beacon and sweeping pool without that scene-wide shader cost. */ const WarningLight = React.memo<{ isStopped: boolean; @@ -153,6 +156,8 @@ const WarningLight = React.memo<{ const lightRef = useRef(null); const sweepRef = useRef(null); const isTabVisible = useGameSimulationStore((state) => state.isTabVisible); + const graphicsQuality = useGraphicsStore((state) => state.graphics.quality); + const realLightEnabled = graphicsQuality === 'high' || graphicsQuality === 'ultra'; useFrame((state, delta) => { // PERFORMANCE: Skip animations when tab hidden @@ -220,7 +225,9 @@ const WarningLight = React.memo<{ /> - + {realLightEnabled && ( + + )} ); }); diff --git a/src/components/TruckBay.tsx b/src/components/TruckBay.tsx index 9a6988b..333a631 100644 --- a/src/components/TruckBay.tsx +++ b/src/components/TruckBay.tsx @@ -27,6 +27,7 @@ import { import { createTruckController, getTruckControllerPose, + interpolateTruckControllerState, stepTruckController, type TruckControllerState, } from './truckbay/truckController'; @@ -1157,63 +1158,68 @@ const TruckWashStation: React.FC<{ position: [number, number, number]; rotation? export const FleetTelemetryHub: React.FC<{ position: [number, number, number]; rotation?: number; -}> = ({ position, rotation = 0 }) => ( - - - - - - - - - - - - - - {[-0.82, 0, 0.82].map((x, index) => ( - - - - +}> = ({ position, rotation = 0 }) => { + const yardLampsEnabled = useYardLampsEnabled(); + return ( + + + + + + + + + + + + + + {[-0.82, 0, 0.82].map((x, index) => ( + + + + + + {[0.48, 0.12, -0.24, -0.6].map((y, lightIndex) => ( + + + + + ))} + + ))} + + + + - {[0.48, 0.12, -0.24, -0.6].map((y, lightIndex) => ( - - - + {[0, Math.PI / 2].map((rotationY) => ( + + + ))} + {yardLampsEnabled && ( + + )} - ))} - - - - - - {[0, Math.PI / 2].map((rotationY) => ( - - - + + + + - ))} - - - - - - - - - FLEET TELEMETRY - + + FLEET TELEMETRY + + - -); + ); +}; // Propane tank cage export const PropaneTankCage: React.FC<{ @@ -2124,6 +2130,7 @@ const GuardShack: React.FC<{ }> = ({ position, rotation = 0, gateOpen }) => { const gateRef = useRef(null); const guardId = useId(); + const yardLampsEnabled = useYardLampsEnabled(); useEffect(() => { if (!gateRef.current) return; @@ -2166,11 +2173,11 @@ const GuardShack: React.FC<{ - + - - + + {yardLampsEnabled && } @@ -2475,12 +2482,10 @@ const HeadlightBeam: React.FC<{ rotation: [number, number, number]; isOn: boolean; }> = ({ position, rotation, isOn }) => { - if (!isOn) return null; - return ( {/* Cone rotated to point forward (Z direction) - tip at light source, base spreading forward */} - + @@ -2743,6 +2748,8 @@ export const TruckBay: React.FC = ({ productionSpeed }) => { const receivingTruckStateRef = useRef(initialReceivingState); const shippingControllerRef = useRef(initialShippingController); const receivingControllerRef = useRef(initialReceivingController); + const shippingPreviousControllerRef = useRef(initialShippingController); + const receivingPreviousControllerRef = useRef(initialReceivingController); const shippingAccumulatorRef = useRef(0); const receivingAccumulatorRef = useRef(0); @@ -2841,6 +2848,7 @@ export const TruckBay: React.FC = ({ productionSpeed }) => { const advanceTruck = ( dock: 'shipping' | 'receiving', controllerRef: React.MutableRefObject, + previousControllerRef: React.MutableRefObject, accumulatorRef: React.MutableRefObject, truckStateRef: React.MutableRefObject, truckRef: React.MutableRefObject, @@ -2852,10 +2860,12 @@ export const TruckBay: React.FC = ({ productionSpeed }) => { ): void => { const scheduleBefore = useTruckScheduleStore.getState().truckSchedule[dock]; let controller = controllerRef.current; + let previousController = previousControllerRef.current; let accumulator = accumulatorRef.current + controllerDelta; let departedThisFrame = false; while (accumulator >= TRUCK_CONTROLLER_STEP_SECONDS) { + previousController = controller; const result = stepTruckController(controller, { deltaSeconds: TRUCK_CONTROLLER_STEP_SECONDS, arrivalReady: scheduleBefore.arrivalReady, @@ -2868,6 +2878,7 @@ export const TruckBay: React.FC = ({ productionSpeed }) => { accumulator -= TRUCK_CONTROLLER_STEP_SECONDS; } controllerRef.current = controller; + previousControllerRef.current = previousController; accumulatorRef.current = accumulator; if (scheduleBefore.arrivalReady && controller.active) { @@ -2877,9 +2888,14 @@ export const TruckBay: React.FC = ({ productionSpeed }) => { useTruckScheduleStore.getState().recordTruckDeparture(dock, simulationMinutes); } - const truckState = getTruckControllerPose(controller, safetyHoldActive); + const displayController = interpolateTruckControllerState( + previousController, + controller, + accumulator / TRUCK_CONTROLLER_STEP_SECONDS + ); + const truckState = getTruckControllerPose(displayController, safetyHoldActive); truckStateRef.current = truckState; - wheelRotationRef.current = controller.wheelTravel / TRUCK_WHEEL_RADIUS; + wheelRotationRef.current = displayController.wheelTravel / TRUCK_WHEEL_RADIUS; if (truckRef.current) { truckRef.current.visible = truckState.active; @@ -2893,11 +2909,11 @@ export const TruckBay: React.FC = ({ productionSpeed }) => { phase: truckState.phase, servicePhase: truckState.servicePhase, speed: truckState.speed, - acceleration: controller.motion.acceleration, + acceleration: displayController.motion.acceleration, steeringAngle: truckState.steeringAngle, wheelRotation: wheelRotationRef.current, - wheelTravel: controller.wheelTravel, - routeDistance: controller.phaseDistance, + wheelTravel: displayController.wheelTravel, + routeDistance: displayController.phaseDistance, trailerAngle: truckState.trailerAngle, articulation: truckState.articulation, parkingBrake: truckState.parkingBrake, @@ -3049,6 +3065,7 @@ export const TruckBay: React.FC = ({ productionSpeed }) => { advanceTruck( 'shipping', shippingControllerRef, + shippingPreviousControllerRef, shippingAccumulatorRef, shippingTruckStateRef, shippingTruckRef, @@ -3061,6 +3078,7 @@ export const TruckBay: React.FC = ({ productionSpeed }) => { advanceTruck( 'receiving', receivingControllerRef, + receivingPreviousControllerRef, receivingAccumulatorRef, receivingTruckStateRef, receivingTruckRef, diff --git a/src/components/environment/NearHorizonCity.test.ts b/src/components/environment/NearHorizonCity.test.ts index 49b5f72..599d6cf 100644 --- a/src/components/environment/NearHorizonCity.test.ts +++ b/src/components/environment/NearHorizonCity.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; +import { SITE_LAYOUT } from '../../constants/siteLayout'; import { buildNearCitySpecs } from './NearHorizonCity'; describe('near horizon city layout', () => { @@ -8,8 +9,11 @@ describe('near horizon city layout', () => { expect(first).toHaveLength(42); first.forEach((building) => { expect(Object.values(building).every(Number.isFinite)).toBe(true); - expect(Math.hypot(building.x, building.z)).toBeGreaterThanOrEqual(210); - expect(Math.hypot(building.x, building.z)).toBeLessThanOrEqual(245); + expect(Math.hypot(building.x, building.z)).toBeGreaterThanOrEqual(184); + expect(Math.hypot(building.x, building.z)).toBeLessThanOrEqual(224); + expect(building.height).toBeGreaterThanOrEqual(6.5); + expect(building.height).toBeLessThan(30); + expect(building.width).toBeGreaterThanOrEqual(5.5); expect(building.districtBand).toBeGreaterThanOrEqual(0); expect(building.districtBand).toBeLessThanOrEqual(2); expect(building.roofStyle).toBeGreaterThanOrEqual(0); @@ -31,8 +35,12 @@ describe('near horizon city layout', () => { }); it('stays clear of the authored castle footprint', () => { + const castle = SITE_LAYOUT.landmarks.castle; + const castleClearance = Math.max(...castle.footprint) * castle.scale * 0.5; buildNearCitySpecs().forEach((building) => { - expect(Math.hypot(building.x - 45, building.z + 200)).toBeGreaterThan(45); + expect( + Math.hypot(building.x - castle.position[0], building.z - castle.position[2]) + ).toBeGreaterThan(castleClearance); }); }); }); diff --git a/src/components/environment/NearHorizonCity.tsx b/src/components/environment/NearHorizonCity.tsx index a37a345..eb327d1 100644 --- a/src/components/environment/NearHorizonCity.tsx +++ b/src/components/environment/NearHorizonCity.tsx @@ -30,19 +30,20 @@ export const buildNearCitySpecs = (count = 42): NearCityBuildingSpec[] => const positionInBand = Math.floor(index / 3); const progress = buildingsInBand <= 1 ? 0.5 : positionInBand / (buildingsInBand - 1); const angle = - THREE.MathUtils.lerp(-1.06, -0.2, progress) + (deterministicNoise(index, 6) - 0.5) * 0.035; + THREE.MathUtils.lerp(-1, -0.12, progress) + (deterministicNoise(index, 6) - 0.5) * 0.045; // Three shallow depth bands create real parallax and an enclosing district // silhouette without bringing city geometry into the operational yard. - const radius = 210 + districtBand * 14 + deterministicNoise(index, 0) * 7; - const landmark = deterministicNoise(index, 1) > 0.9; - const height = 9 + deterministicNoise(index, 2) * 19 + districtBand * 1.5 + (landmark ? 8 : 0); + const radius = 184 + districtBand * 16 + deterministicNoise(index, 0) * 8; + const landmark = deterministicNoise(index, 1) > 0.93; + const height = + 6.5 + deterministicNoise(index, 2) * 13 + districtBand * 1.25 + (landmark ? 6 : 0); const x = Math.cos(angle) * radius; const z = Math.sin(angle) * radius; return { x, z, - width: 4 + deterministicNoise(index, 3) * 3.2, - depth: 4 + deterministicNoise(index, 4) * 4.5, + width: 5.5 + deterministicNoise(index, 3) * 5.5, + depth: 5 + deterministicNoise(index, 4) * 5.5, height, yaw: Math.atan2(-x, -z), tone: deterministicNoise(index, 5), @@ -54,38 +55,40 @@ export const buildNearCitySpecs = (count = 42): NearCityBuildingSpec[] => const UNIT_BOX = new THREE.BoxGeometry(1, 1, 1); const CITY_BODY_MATERIAL = new THREE.MeshStandardMaterial({ color: '#ffffff', + emissive: '#222927', + emissiveIntensity: 0.22, roughness: 0.88, metalness: 0.04, vertexColors: true, }); const CITY_ROOF_MATERIAL = new THREE.MeshStandardMaterial({ - color: '#4b5052', + color: '#666d6c', roughness: 0.82, metalness: 0.08, }); const CITY_WINDOW_MATERIAL = new THREE.MeshStandardMaterial({ - color: '#263d48', + color: '#647d88', emissive: '#ffca72', emissiveIntensity: 0, roughness: 0.36, metalness: 0.06, }); const CITY_MECHANICAL_MATERIAL = new THREE.MeshStandardMaterial({ - color: '#626b6c', + color: '#7a8483', roughness: 0.7, metalness: 0.18, }); const CITY_STACK_MATERIAL = new THREE.MeshStandardMaterial({ - color: '#3f4547', + color: '#58605f', roughness: 0.58, metalness: 0.28, }); const CITY_BODY_COLOURS = [ - new THREE.Color('#858986'), - new THREE.Color('#91877c'), - new THREE.Color('#78858a'), - new THREE.Color('#827d78'), - new THREE.Color('#8c908a'), + new THREE.Color('#9ba39e'), + new THREE.Color('#aa9f91'), + new THREE.Color('#8fa1a6'), + new THREE.Color('#9e9690'), + new THREE.Color('#a5aaa2'), ] as const; interface WindowSpec { diff --git a/src/components/exterior/organicLakeGeometry.test.ts b/src/components/exterior/organicLakeGeometry.test.ts index 589032e..2450ed9 100644 --- a/src/components/exterior/organicLakeGeometry.test.ts +++ b/src/components/exterior/organicLakeGeometry.test.ts @@ -23,6 +23,13 @@ describe('organic lake geometry', () => { expectFiniteGeometry(bank); expect(water.getAttribute('uv').count).toBe(water.getAttribute('position').count); expect(bank.getAttribute('color').count).toBe(bank.getAttribute('position').count); + expect(water.getAttribute('position').count).toBeGreaterThan(72 * 4); + + const bankPositions = bank.getAttribute('position'); + const bankHeights = Array.from({ length: bankPositions.count }, (_, index) => + bankPositions.getZ(index) + ); + expect(Math.max(...bankHeights) - Math.min(...bankHeights)).toBeGreaterThan(0.1); water.dispose(); bank.dispose(); @@ -32,16 +39,14 @@ describe('organic lake geometry', () => { const segments = 24; const water = createOrganicLakeSurfaceGeometry(12, 8, segments); const positions = water.getAttribute('position'); - const boundaryOffset = 1; - - expect(positions.getX(boundaryOffset)).toBeCloseTo( - positions.getX(boundaryOffset + segments), - 6 - ); - expect(positions.getY(boundaryOffset)).toBeCloseTo( - positions.getY(boundaryOffset + segments), - 6 - ); + const radialSegments = (positions.count - 1) / (segments + 1); + expect(Number.isInteger(radialSegments)).toBe(true); + for (let ring = 0; ring < radialSegments; ring += 1) { + const first = 1 + ring * (segments + 1); + const last = first + segments; + expect(positions.getX(first)).toBeCloseTo(positions.getX(last), 6); + expect(positions.getY(first)).toBeCloseTo(positions.getY(last), 6); + } water.dispose(); }); diff --git a/src/components/exterior/organicLakeGeometry.ts b/src/components/exterior/organicLakeGeometry.ts index 8789a38..21cae87 100644 --- a/src/components/exterior/organicLakeGeometry.ts +++ b/src/components/exterior/organicLakeGeometry.ts @@ -11,33 +11,58 @@ function shorelineFactor(angle: number, seed: number): number { ); } -/** Build a softly irregular lake using the original proven fan topology. */ +/** + * Build a softly irregular lake with concentric radial subdivisions. The extra + * rings let vertex waves bend continuously across the surface instead of + * stretching one centre-to-shore triangle over the full lake radius. + */ export function createOrganicLakeSurfaceGeometry( radiusX: number, radiusZ: number, segments = DEFAULT_SEGMENTS, - seed = 0.74 + seed = 0.74, + radialSegments = 6 ): THREE.BufferGeometry { const safeRadiusX = Math.max(0.1, radiusX); const safeRadiusZ = Math.max(0.1, radiusZ); const safeSegments = Math.max(12, Math.floor(segments)); + const safeRadialSegments = Math.max(2, Math.floor(radialSegments)); const positions: number[] = [0, 0, 0]; const uvs: number[] = [0.5, 0.5]; const indices: number[] = []; - for (let segment = 0; segment <= safeSegments; segment += 1) { - const progress = segment === safeSegments ? 0 : segment / safeSegments; - const angle = progress * Math.PI * 2; - const edgeFactor = shorelineFactor(angle, seed); - const x = Math.cos(angle) * safeRadiusX * edgeFactor; - const z = Math.sin(angle) * safeRadiusZ * edgeFactor; - positions.push(x, z, 0); - uvs.push(0.5 + Math.cos(angle) * 0.5, 0.5 + Math.sin(angle) * 0.5); + for (let ring = 1; ring <= safeRadialSegments; ring += 1) { + const radialRatio = ring / safeRadialSegments; + for (let segment = 0; segment <= safeSegments; segment += 1) { + const progress = segment === safeSegments ? 0 : segment / safeSegments; + const angle = progress * Math.PI * 2; + const edgeFactor = shorelineFactor(angle, seed); + const factor = THREE.MathUtils.lerp(1, edgeFactor, radialRatio * radialRatio); + const x = Math.cos(angle) * safeRadiusX * radialRatio * factor; + const z = Math.sin(angle) * safeRadiusZ * radialRatio * factor; + positions.push(x, z, 0); + uvs.push( + 0.5 + Math.cos(angle) * radialRatio * 0.5, + 0.5 + Math.sin(angle) * radialRatio * 0.5 + ); + } } for (let segment = 0; segment < safeSegments; segment += 1) { indices.push(0, 1 + segment, 2 + segment); } + const ringStride = safeSegments + 1; + for (let ring = 1; ring < safeRadialSegments; ring += 1) { + const innerStart = 1 + (ring - 1) * ringStride; + const outerStart = innerStart + ringStride; + for (let segment = 0; segment < safeSegments; segment += 1) { + const innerLeft = innerStart + segment; + const innerRight = innerLeft + 1; + const outerLeft = outerStart + segment; + const outerRight = outerLeft + 1; + indices.push(innerLeft, outerLeft, innerRight, innerRight, outerLeft, outerRight); + } + } const geometry = new THREE.BufferGeometry(); geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); @@ -61,15 +86,18 @@ export function createOrganicLakeBankGeometry( seed = 0.74 ): THREE.BufferGeometry { const safeSegments = Math.max(12, Math.floor(segments)); - const rows = 3; + const rows = 5; const positions: number[] = []; const colors: number[] = []; const indices: number[] = []; const rowColors = [ - new THREE.Color('#4f655d'), - new THREE.Color('#9e9375'), - new THREE.Color('#5d744f'), + new THREE.Color('#425750'), + new THREE.Color('#6e7366'), + new THREE.Color('#928a70'), + new THREE.Color('#6f7759'), + new THREE.Color('#526c49'), ]; + const rowHeights = [-0.05, 0.015, 0.105, 0.055, 0] as const; for (let segment = 0; segment <= safeSegments; segment += 1) { const progress = segment === safeSegments ? 0 : segment / safeSegments; @@ -83,7 +111,11 @@ export function createOrganicLakeBankGeometry( const radiusX = THREE.MathUtils.lerp(waterRadiusX * 0.985, shoreRadiusX, ratio); const radiusZ = THREE.MathUtils.lerp(waterRadiusZ * 0.985, shoreRadiusZ, ratio); const factor = THREE.MathUtils.lerp(innerFactor, outerFactor, ratio); - positions.push(Math.cos(angle) * radiusX * factor, Math.sin(angle) * radiusZ * factor, 0); + positions.push( + Math.cos(angle) * radiusX * factor, + Math.sin(angle) * radiusZ * factor, + rowHeights[row] + ); const color = rowColors[row]; colors.push(color.r, color.g, color.b); } diff --git a/src/components/knowledge/Datalinks.tsx b/src/components/knowledge/Datalinks.tsx index 5bb0247..2a45181 100644 --- a/src/components/knowledge/Datalinks.tsx +++ b/src/components/knowledge/Datalinks.tsx @@ -326,18 +326,10 @@ export function Datalinks({ isOpen, onClose }: DatalinksProps) { : 'hover:bg-slate-800/50 border-l-2 border-transparent' }`} > - {entry.portraitPath ? ( - {entry.title} - ) : ( - - )} +

diff --git a/src/components/knowledge/KnowledgeEntryCard.tsx b/src/components/knowledge/KnowledgeEntryCard.tsx index b19cb85..eb7e63f 100644 --- a/src/components/knowledge/KnowledgeEntryCard.tsx +++ b/src/components/knowledge/KnowledgeEntryCard.tsx @@ -265,18 +265,7 @@ export function KnowledgeEntryCard({ entry, onClose, onNavigate }: KnowledgeEntr Back to list
- {entry.portraitPath ? ( - {entry.title} { - e.currentTarget.style.display = 'none'; - }} - /> - ) : ( - - )} +
{getCategoryLabel(entry.category)} diff --git a/src/components/scenery/FairytaleCastle.tsx b/src/components/scenery/FairytaleCastle.tsx index 6f33b15..a3651db 100644 --- a/src/components/scenery/FairytaleCastle.tsx +++ b/src/components/scenery/FairytaleCastle.tsx @@ -307,17 +307,19 @@ export const FairytaleCastle: React.FC = React.memo( scale = 1, rotation = [0, 0, 0] as [number, number, number], }) => { - // Neuschwanstein Palette + // Weathered heritage palette keeps the landmark grounded in the same + // overcast industrial valley as the mill instead of reading as white set + // dressing pasted over the mountain range. const colors = { - walls: '#e8e6e1', // Off-white limestone - roofs: '#2b5a75', // Deep blue slate - trim: '#c5a582', // Decorative sandstone trim - rock: '#5d5d5d', // Grey mountain rock - gold: '#ffd700', // Spires + walls: '#c6c3ba', + roofs: '#314c5a', + trim: '#9f8e76', + rock: '#505957', + gold: '#b89743', }; return ( - + {/* ==================== BASE MOUNTAIN ==================== */} {/* Stays at 7 sides. It is the widest part here (75 m across) but 7 is deliberate: with `flatShading` it reads as a crag rather than a cone, diff --git a/src/components/truckbay/truckController.test.ts b/src/components/truckbay/truckController.test.ts index 9f09576..b1f68e4 100644 --- a/src/components/truckbay/truckController.test.ts +++ b/src/components/truckbay/truckController.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'; import { createTruckController, getTruckControllerPose, + interpolateTruckControllerState, stepTruckController, } from './truckController'; @@ -207,4 +208,44 @@ describe('schedule-owned articulated truck controller', () => { expect(restricted.maximumArticulation).toBeLessThanOrEqual(0.7 + 1e-8); expect(restricted.maximumSteering).toBeLessThanOrEqual(0.55 + 1e-8); }); + + it('interpolates render poses smoothly across the angle wrap', () => { + const previous = { + ...createTruckController('shipping'), + x: 10, + z: 20, + tractorYaw: Math.PI - 0.1, + trailerYaw: Math.PI - 0.2, + steeringAngle: -0.2, + wheelTravel: 40, + phaseElapsed: 2, + motion: { speed: 4, acceleration: 0.5 }, + }; + const current = { + ...previous, + x: 14, + z: 24, + tractorYaw: -Math.PI + 0.1, + trailerYaw: -Math.PI + 0.2, + steeringAngle: 0.2, + wheelTravel: 44, + phaseElapsed: 3, + motion: { speed: 6, acceleration: 1.5 }, + }; + + const display = interpolateTruckControllerState(previous, current, 0.5); + expect(display.x).toBe(12); + expect(display.z).toBe(22); + expect(display.wheelTravel).toBe(42); + expect(display.steeringAngle).toBeCloseTo(0); + expect(display.motion).toEqual({ speed: 5, acceleration: 1 }); + expect(Math.abs(Math.abs(display.tractorYaw) - Math.PI)).toBeLessThan(1e-8); + expect(Math.abs(Math.abs(display.trailerYaw) - Math.PI)).toBeLessThan(1e-8); + }); + + it('keeps active transitions discrete so trucks never ghost into or out of the yard', () => { + const previous = createTruckController('shipping', false); + const current = createTruckController('shipping', true); + expect(interpolateTruckControllerState(previous, current, 0.25)).toBe(current); + }); }); diff --git a/src/components/truckbay/truckController.ts b/src/components/truckbay/truckController.ts index c1163ad..f5293c9 100644 --- a/src/components/truckbay/truckController.ts +++ b/src/components/truckbay/truckController.ts @@ -364,6 +364,53 @@ export function getTruckControllerPose( return poseFromState(state, safetyHold); } +const interpolateScalar = (from: number, to: number, alpha: number): number => + from + (to - from) * alpha; + +const interpolateAngle = (from: number, to: number, alpha: number): number => + normalizeVehicleAngle(from + shortestVehicleAngle(from, to) * alpha); + +/** + * Produces a render-only pose between deterministic 60 Hz controller ticks. + * Discrete phase and interlock authority comes from the current state while + * position, articulation, wheel travel and service mechanisms move at the + * display refresh rate. This removes repeated poses on 90/120/144 Hz displays + * without making simulation outcomes depend on render cadence. + */ +export function interpolateTruckControllerState( + previous: TruckControllerState, + current: TruckControllerState, + alpha: number +): TruckControllerState { + const t = Math.max(0, Math.min(1, Number.isFinite(alpha) ? alpha : 0)); + if (previous.active !== current.active || previous.dock !== current.dock) return current; + + const samePhase = previous.phase === current.phase; + const sameServicePhase = previous.servicePhase === current.servicePhase; + return { + ...current, + phaseDistance: samePhase + ? interpolateScalar(previous.phaseDistance, current.phaseDistance, t) + : current.phaseDistance, + phaseElapsed: samePhase + ? interpolateScalar(previous.phaseElapsed, current.phaseElapsed, t) + : current.phaseElapsed, + serviceElapsed: sameServicePhase + ? interpolateScalar(previous.serviceElapsed, current.serviceElapsed, t) + : current.serviceElapsed, + motion: { + speed: interpolateScalar(previous.motion.speed, current.motion.speed, t), + acceleration: interpolateScalar(previous.motion.acceleration, current.motion.acceleration, t), + }, + x: interpolateScalar(previous.x, current.x, t), + z: interpolateScalar(previous.z, current.z, t), + tractorYaw: interpolateAngle(previous.tractorYaw, current.tractorYaw, t), + trailerYaw: interpolateAngle(previous.trailerYaw, current.trailerYaw, t), + steeringAngle: interpolateScalar(previous.steeringAngle, current.steeringAngle, t), + wheelTravel: interpolateScalar(previous.wheelTravel, current.wheelTravel, t), + }; +} + export function stepTruckController( prior: TruckControllerState, input: TruckControllerInput diff --git a/src/components/ui-new/GameInterface.tsx b/src/components/ui-new/GameInterface.tsx index 1298673..5003d46 100644 --- a/src/components/ui-new/GameInterface.tsx +++ b/src/components/ui-new/GameInterface.tsx @@ -32,7 +32,7 @@ const INTRO_STEPS: OnboardingStep[] = [ title: 'Follow the process', icon: 'factory', content: - 'The camera is flying to the full site. Grain moves from the rear silos through milling and sifting, then reaches packing and shipping. Drag to orbit. Scroll or pinch to zoom.', + 'The camera is flying to the full site. Grain moves from the rear silos through milling and sifting, then reaches packing and shipping. Use W A S D to move, Q and E to descend or climb, drag to orbit, and scroll or pinch to zoom.', }, { title: 'Protect today’s target', diff --git a/src/components/ui-new/hud/StatusHUD.tsx b/src/components/ui-new/hud/StatusHUD.tsx index 58dc33b..4855688 100644 --- a/src/components/ui-new/hud/StatusHUD.tsx +++ b/src/components/ui-new/hud/StatusHUD.tsx @@ -209,7 +209,8 @@ export const StatusHUD: React.FC = () => {
{throughput.toLocaleString()} BAGS/H
diff --git a/src/config/humanPresencePolicy.test.ts b/src/config/humanPresencePolicy.test.ts index 519a12e..29b3e76 100644 --- a/src/config/humanPresencePolicy.test.ts +++ b/src/config/humanPresencePolicy.test.ts @@ -60,6 +60,7 @@ describe('v0.40 uncrewed-site contract', () => { it('does not expose character models, portraits, or host voices to production', () => { const prohibitedPublicPaths = [ 'public/assets/workers', + 'public/portraits', 'public/models/worker', 'public/textures/compressed/worker_color.ktx2', 'public/textures/compressed/worker_normal.ktx2', @@ -88,6 +89,13 @@ describe('v0.40 uncrewed-site contract', () => { expect(productionAudioSources).not.toMatch( /speechSynthesis|SpeechSynthesis|ttsEnabled|speakAnnouncement|startWorkerVoices|startRadioChatter|playRadioDispatch|WorkerModel|WORKER_ASSET_PATHS|WORKER_VARIANTS/ ); + + const knowledgeSources = [ + source('src/stores/knowledgeStore.ts'), + source('src/components/knowledge/Datalinks.tsx'), + source('src/components/knowledge/KnowledgeEntryCard.tsx'), + ].join('\n'); + expect(knowledgeSources).not.toMatch(/portraitPath|[/\\]portraits[/\\]| { diff --git a/src/constants/__tests__/siteLayout.test.ts b/src/constants/__tests__/siteLayout.test.ts index a5908fd..bc10a17 100644 --- a/src/constants/__tests__/siteLayout.test.ts +++ b/src/constants/__tests__/siteLayout.test.ts @@ -74,12 +74,32 @@ describe('canonical site layout', () => { const tankFarm = getServiceAssetBounds(SITE_LAYOUT.serviceYard.utilityTankFarm); expect(boundsOverlapXZ(tankFarm, propaneClearance)).toBe(false); - expect(propaneClearance.maxX).toBeLessThanOrEqual(SITE_LAYOUT.perimeter.maxX); - expect(tankFarm.maxX).toBeLessThan(SITE_LAYOUT.perimeter.maxX); - expect(tankFarm.minZ).toBeGreaterThan(SITE_LAYOUT.perimeter.minZ); - const telemetryHub = getServiceAssetBounds(SITE_LAYOUT.serviceYard.fleetTelemetryHub); - expect(telemetryHub.minX).toBeGreaterThan(SITE_LAYOUT.docks.shipping.apron.maxX); + for (const asset of bounds) { + expect(boundsOverlapXZ(asset.bounds, FACTORY_BOUNDS), `${asset.id} crosses factory`).toBe( + false + ); + expect(asset.bounds.minX, `${asset.id} west perimeter`).toBeGreaterThanOrEqual( + SITE_LAYOUT.perimeter.minX + ); + expect(asset.bounds.maxX, `${asset.id} east perimeter`).toBeLessThanOrEqual( + SITE_LAYOUT.perimeter.maxX + ); + expect(asset.bounds.minZ, `${asset.id} north perimeter`).toBeGreaterThanOrEqual( + SITE_LAYOUT.perimeter.minZ + ); + expect(asset.bounds.maxZ, `${asset.id} south perimeter`).toBeLessThanOrEqual( + SITE_LAYOUT.perimeter.maxZ + ); + expect( + boundsOverlapXZ(asset.bounds, SITE_LAYOUT.docks.shipping.apron), + `${asset.id} crosses shipping apron` + ).toBe(false); + expect( + boundsOverlapXZ(asset.bounds, SITE_LAYOUT.docks.receiving.apron), + `${asset.id} crosses receiving apron` + ).toBe(false); + } }); it('keeps canonical forklift swept corridors clear of machines and service assets', () => { diff --git a/src/constants/siteLayout.ts b/src/constants/siteLayout.ts index c734a7f..0435e9a 100644 --- a/src/constants/siteLayout.ts +++ b/src/constants/siteLayout.ts @@ -172,31 +172,31 @@ export const SITE_LAYOUT = { serviceYard: { maintenanceGarage: { id: 'maintenance-garage', - position: [85, 0, 30], + position: [83.5, 0, 36], rotation: -Math.PI / 2, - footprint: [12, 10], + footprint: [12.3, 10.8], height: 8, clearance: 6, }, propaneCompound: { id: 'propane-compound', - position: [85.5, 0, 10.5], + position: [83.5, 0, 14.5], rotation: 0, - footprint: [9, 5], + footprint: [12, 9], height: 5, - clearance: 5, + clearance: 4, }, utilityTankFarm: { id: 'utility-tank-farm', position: [75, 0, -15], rotation: 0, - footprint: [16, 36], + footprint: [22, 42], height: 12, clearance: 2, }, trailerDropYard: { id: 'trailer-drop-yard', - position: [-60, 0, 35], + position: [-78, 0, 35], rotation: 0, footprint: [20, 30], height: 5, @@ -341,7 +341,7 @@ export const SITE_LAYOUT = { water: { position: [158, 23, 154], target: [118, 0.08, 116] }, village: { position: [-142, 28, 64], target: [-190, 5, 0] }, farm: { position: [128, 26, 174], target: [75, 4, 120] }, - garage: { position: [69, 6.5, 30], target: [85, 3, 30] }, + garage: { position: [66, 7, 36], target: [83.5, 3, 36] }, celestial: { position: [90, 12, 72], target: [0, 12, 0] }, }, renderCells: { diff --git a/src/simulation/vehicles/forkliftController.test.ts b/src/simulation/vehicles/forkliftController.test.ts index d61802d..0def563 100644 --- a/src/simulation/vehicles/forkliftController.test.ts +++ b/src/simulation/vehicles/forkliftController.test.ts @@ -103,7 +103,7 @@ describe('forklift controller', () => { expect(disengaged.cargoEngaged).toBe(false); }); - it('preserves route progress across common rendering frequencies', () => { + it('preserves route progress across common and degraded rendering frequencies', () => { const plan = createForkliftRoutePlan(points, actions); const simulate = (framesPerSecond: number) => { let state = createInitialForkliftMotion(plan, [0, 0, 0]); @@ -118,9 +118,9 @@ describe('forklift controller', () => { return state; }; - const results = [30, 60, 120].map(simulate); + const results = [15, 30, 60, 120].map(simulate); const wheelTravel = results.map((state) => state.wheelTravel); - expect(Math.max(...wheelTravel) - Math.min(...wheelTravel)).toBeLessThan(0.15); + expect(Math.max(...wheelTravel) - Math.min(...wheelTravel)).toBeLessThan(0.05); for (const state of results) { expect(Number.isFinite(state.x)).toBe(true); expect(Number.isFinite(state.z)).toBe(true); @@ -189,4 +189,28 @@ describe('forklift controller', () => { expect(state.wheelTravel - wheelTravel).toBeCloseTo(0.01); expect(state.speed).toBe(0); }); + + it('substeps a dropped frame without violating a nearby movement authority', () => { + const plan = createForkliftRoutePlan(points, actions); + let state = createInitialForkliftMotion(plan, [0, 0, 0]); + for (let frame = 0; frame < 180; frame += 1) { + state = stepForkliftMotion(state, plan, { + targetSpeed: 2, + stopReason: 'none', + loaded: false, + deltaSeconds: 1 / 60, + }); + } + + const wheelTravel = state.wheelTravel; + state = stepForkliftMotion(state, plan, { + targetSpeed: 2, + stopReason: 'none', + loaded: false, + deltaSeconds: 0.2, + maximumTravelDistance: 0.08, + }); + expect(state.wheelTravel - wheelTravel).toBeCloseTo(0.08); + expect(state.speed).toBe(0); + }); }); diff --git a/src/simulation/vehicles/forkliftController.ts b/src/simulation/vehicles/forkliftController.ts index 3edff20..c32543a 100644 --- a/src/simulation/vehicles/forkliftController.ts +++ b/src/simulation/vehicles/forkliftController.ts @@ -79,6 +79,8 @@ export const FORKLIFT_WHEELBASE_METRES = 1.55; export const FORKLIFT_REAR_TRACK_METRES = 1.05; export const FORKLIFT_MAXIMUM_STEERING_RADIANS = 0.56; export const FORKLIFT_MAXIMUM_STEERING_RATE = 1.4; +const FORKLIFT_MAXIMUM_SUBSTEP_SECONDS = 1 / 60; +const FORKLIFT_MAXIMUM_REQUEST_SECONDS = 0.25; const UNLOADED_LIMITS: LongitudinalMotionLimits = { maximumForwardSpeed: 3.5, @@ -153,11 +155,11 @@ export function distanceAheadOnClosedPath( return direct >= 0 ? direct : path.totalLength + direct; } -export function stepForkliftMotion( +const stepForkliftMotionOnce = ( state: ForkliftMotionState, plan: ForkliftRoutePlan, request: ForkliftMotionRequest -): ForkliftMotionState { +): ForkliftMotionState => { const limits = request.loaded ? LOADED_LIMITS : UNLOADED_LIMITS; const lookAheadBeforeStep = sampleArcLengthPath( plan.path, @@ -214,6 +216,49 @@ export function stepForkliftMotion( z: sample.z, stopReason: request.stopReason, }; +}; + +/** + * Integrate at no more than 60 Hz per kinematic step. A dropped render frame + * can otherwise feed a 100 to 200 ms impulse into acceleration and steering, + * producing the visible lurch that makes an autonomous forklift look keyed by + * hand. Substeps retain render-rate motion while keeping jerk, braking and + * movement-authority results bounded across common display cadences. + */ +export function stepForkliftMotion( + state: ForkliftMotionState, + plan: ForkliftRoutePlan, + request: ForkliftMotionRequest +): ForkliftMotionState { + let remainingTime = Math.min( + FORKLIFT_MAXIMUM_REQUEST_SECONDS, + Math.max(0, Number.isFinite(request.deltaSeconds) ? request.deltaSeconds : 0) + ); + if (remainingTime <= 0) return state; + + let current = state; + let remainingAuthority = Number.isFinite(request.maximumTravelDistance) + ? Math.max(0, request.maximumTravelDistance ?? 0) + : undefined; + + while (remainingTime > 1e-9) { + const deltaSeconds = Math.min(FORKLIFT_MAXIMUM_SUBSTEP_SECONDS, remainingTime); + const wheelTravelBefore = current.wheelTravel; + current = stepForkliftMotionOnce(current, plan, { + ...request, + deltaSeconds, + maximumTravelDistance: remainingAuthority, + }); + if (remainingAuthority !== undefined) { + remainingAuthority = Math.max( + 0, + remainingAuthority - Math.max(0, current.wheelTravel - wheelTravelBefore) + ); + } + remainingTime -= deltaSeconds; + } + + return current; } /** diff --git a/src/stores/knowledgeStore.ts b/src/stores/knowledgeStore.ts index ab22d56..9f1e5b5 100644 --- a/src/stores/knowledgeStore.ts +++ b/src/stores/knowledgeStore.ts @@ -52,7 +52,6 @@ export interface KnowledgeEntry { seeInAction: string[]; unlockCondition: UnlockCondition; quote?: KnowledgeQuote; - portraitPath?: string; } export interface UnlockContext { diff --git a/src/systems/UnifiedGameTick.ts b/src/systems/UnifiedGameTick.ts index fbb0a91..ad6ebf7 100644 --- a/src/systems/UnifiedGameTick.ts +++ b/src/systems/UnifiedGameTick.ts @@ -24,7 +24,7 @@ import { import { useTruckScheduleStore } from '../stores/truckScheduleStore'; import { useBreakdownStore, type BreakdownType } from '../stores/breakdownStore'; import { useUIStore } from '../stores/uiStore'; -import type { MachineData } from '../types'; +import { BAG_WEIGHT_KG, type MachineData } from '../types'; import { useOperationsCampaignStore, type DispatchLoadSnapshot, @@ -51,37 +51,32 @@ const GRAIN_DELIVERY_KG = 15000; // A shipping truck can load up to 5 t of finished flour or semolina. const FINISHED_GOODS_SHIPMENT_KG = 5000; const SHIPPING_LOAD_RATE_KG_PER_SECOND = 400; -const BAGS_PER_SECOND_BASE = 12; -const NOMINAL_PACKER_KG_PER_SECOND = 25; -const NOMINAL_PACKER_COUNT = 3; let _bagProductionCarry = 0; export const calculateBagsProducedForTick = ( deltaSeconds: number, - productionSpeed: number, - gameSpeed: number, - runningPackerCount: number, - healthFactor: number + packerFlowKgPerSecond: number, + bagWeightKg: number = BAG_WEIGHT_KG ): number => { if ( !Number.isFinite(deltaSeconds) || - !Number.isFinite(productionSpeed) || - !Number.isFinite(gameSpeed) || - !Number.isFinite(runningPackerCount) || - !Number.isFinite(healthFactor) + !Number.isFinite(packerFlowKgPerSecond) || + !Number.isFinite(bagWeightKg) || + bagWeightKg <= 0 ) { return 0; } - return ( - BAGS_PER_SECOND_BASE * - Math.max(0, deltaSeconds) * - Math.max(0, productionSpeed) * - (Math.max(0, gameSpeed) / 60) * - (Math.max(0, runningPackerCount) / NOMINAL_PACKER_COUNT) * - Math.max(0, Math.min(1, healthFactor)) - ); + return (Math.max(0, deltaSeconds) * Math.max(0, packerFlowKgPerSecond)) / bagWeightKg; }; +export const calculatePackerThroughputBagsPerHour = ( + packerFlowKgPerSecond: number, + bagWeightKg: number = BAG_WEIGHT_KG +): number => + Number.isFinite(packerFlowKgPerSecond) && Number.isFinite(bagWeightKg) && bagWeightKg > 0 + ? Math.round((Math.max(0, packerFlowKgPerSecond) / bagWeightKg) * 3600) + : 0; + function sumMaterialInventory(flow: MaterialFlowState, materialType: MaterialType): number { let total = 0; flow.machineBuffers.forEach((buffer) => { @@ -490,12 +485,10 @@ function unifiedGameTick(ctx: TickContext): void { // 3. Count running packers for throughput calculation let runningPackerCount = 0; - let runningPackerEfficiencySum = 0; for (let i = 0; i < machines.length; i++) { const m = machines[i]; if (m.type === 'PACKER' && (m.status === 'running' || m.status === 'warning')) { runningPackerCount++; - runningPackerEfficiencySum += m.metrics.efficiency ?? 100; } } @@ -546,22 +539,12 @@ function unifiedGameTick(ctx: TickContext): void { ) / 10 : 100; - // Throughput: actual production rate in bags per game-hour - // Based on App.tsx production formula: 12 bags/sec base × productionSpeed × gameSpeedFactor × packerScale - // Converted to bags per game-hour for display - const gameSpeedFactor = safeGameSpeed / 60; - const packerScale = runningPackerCount / 3; // 3 packers at full capacity - - // Production per real second - const bagsPerRealSecond = - BAGS_PER_SECOND_BASE * effectiveProductionSpeed * gameSpeedFactor * packerScale; - - // Convert to bags per game-hour: realSeconds per gameHour = 3600 / safeGameSpeed - // Guard against division by zero (paused state handled above, but be safe) - const realSecondsPerGameHour = safeGameSpeed > 0 ? 3600 / safeGameSpeed : 0; - const bagsPerGameHour = bagsPerRealSecond * realSecondsPerGameHour; - - _metricsUpdate.throughput = Math.round(bagsPerGameHour); + // The operator KPI follows the same final-stage mass flow used by SCADA. + // This value is from the preceding material tick, so it is one 500 ms sample + // behind the scene while remaining stable and free of an extra store write. + _metricsUpdate.throughput = calculatePackerThroughputBagsPerHour( + useMaterialFlowStore.getState().currentPackerFlowRate + ); // 5. Update store - machines only if changed, metrics always if (anyMachineChanged) { @@ -659,27 +642,14 @@ function unifiedGameTick(ctx: TickContext): void { : undefined ); - // 4c. Count completed bags on the same central 500 ms cadence as the - // material network. The former five-second interval produced visually large - // jumps and created a second simulation clock. Fractional carry keeps this - // smooth without losing production to integer rounding. + // 4c. Convert the exact final-stage mass flow into completed 25 kg bags on + // the same central cadence. Fractional carry preserves mass across ticks. + // The HUD, SCADA packer-flow tag, finished inventory and bag counter now all + // describe the same packer output rather than parallel rate estimates. if (runningPackerCount > 0) { const liveFlow = useMaterialFlowStore.getState(); const flowRate = liveFlow.currentPackerFlowRate; - const flowSimulationLive = - Number.isFinite(flowRate) && (flowRate > 0 || liveFlow.totalMaterialProcessed > 0); - const fallbackEfficiency = runningPackerEfficiencySum / Math.max(1, runningPackerCount * 100); - const healthFactor = flowSimulationLive - ? Math.max(0, Math.min(1, flowRate / (NOMINAL_PACKER_KG_PER_SECOND * runningPackerCount))) - : Math.max(0, Math.min(1, fallbackEfficiency)); - - _bagProductionCarry += calculateBagsProducedForTick( - deltaSeconds, - effectiveProductionSpeed, - safeGameSpeed, - runningPackerCount, - healthFactor - ); + _bagProductionCarry += calculateBagsProducedForTick(deltaSeconds, flowRate); const completedBags = Math.floor(_bagProductionCarry); if (completedBags > 0) { _bagProductionCarry -= completedBags; diff --git a/src/systems/__tests__/UnifiedGameTick.production.test.ts b/src/systems/__tests__/UnifiedGameTick.production.test.ts index 7a6fbd2..1c85398 100644 --- a/src/systems/__tests__/UnifiedGameTick.production.test.ts +++ b/src/systems/__tests__/UnifiedGameTick.production.test.ts @@ -1,20 +1,24 @@ import { describe, expect, it } from 'vitest'; -import { calculateBagsProducedForTick } from '../UnifiedGameTick'; +import { + calculateBagsProducedForTick, + calculatePackerThroughputBagsPerHour, +} from '../UnifiedGameTick'; describe('UnifiedGameTick bag cadence', () => { - it('emits small half-second increments at the default simulation speed', () => { - const bags = calculateBagsProducedForTick(0.5, 0.8, 180, 3, 1); - expect(bags).toBeCloseTo(14.4); - expect(bags).toBeLessThan(20); + it('converts the exact packer mass flow into small half-second increments', () => { + const bags = calculateBagsProducedForTick(0.5, 60); + expect(bags).toBeCloseTo(1.2); + expect(bags).toBeLessThan(2); }); - it('tracks packer availability and flow health', () => { - expect(calculateBagsProducedForTick(0.5, 0.8, 180, 1, 0.5)).toBeCloseTo(2.4); - expect(calculateBagsProducedForTick(0.5, 0.8, 180, 0, 1)).toBe(0); + it('keeps the headline throughput on the same mass-flow authority', () => { + expect(calculatePackerThroughputBagsPerHour(60)).toBe(8640); + expect(calculateBagsProducedForTick(0.5, 0)).toBe(0); }); it('rejects invalid values instead of contaminating the counter', () => { - expect(calculateBagsProducedForTick(Number.NaN, 1, 180, 3, 1)).toBe(0); - expect(calculateBagsProducedForTick(0.5, 1, 180, 3, Number.POSITIVE_INFINITY)).toBe(0); + expect(calculateBagsProducedForTick(Number.NaN, 60)).toBe(0); + expect(calculateBagsProducedForTick(0.5, 60, Number.POSITIVE_INFINITY)).toBe(0); + expect(calculatePackerThroughputBagsPerHour(60, 0)).toBe(0); }); });