Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed public/portraits/aristotle.webp
Binary file not shown.
Binary file removed public/portraits/elinor-ostrom.webp
Binary file not shown.
Binary file removed public/portraits/jos-de-blok.webp
Binary file not shown.
Binary file removed public/portraits/jose-maria-arizmendiarrieta.webp
Binary file not shown.
Binary file removed public/portraits/ricardo-semler.webp
Binary file not shown.
Binary file removed public/portraits/robert-greenleaf.webp
Binary file not shown.
Binary file removed public/portraits/rodrick-wallace.webp
Binary file not shown.
Binary file removed public/portraits/w-edwards-deming.webp
Binary file not shown.
20 changes: 14 additions & 6 deletions scripts/validate-uncrewed-build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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,
Expand Down Expand Up @@ -87,20 +92,23 @@ 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}`);
}
}

const publicFiles = await collectFiles(CURRENT_PUBLIC);
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}`);
}
}

Expand All @@ -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.`
);
}

Expand Down
4 changes: 2 additions & 2 deletions src/components/FactoryExterior.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
11 changes: 9 additions & 2 deletions src/components/ForkliftSystem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -153,6 +156,8 @@ const WarningLight = React.memo<{
const lightRef = useRef<THREE.PointLight>(null);
const sweepRef = useRef<THREE.Group>(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
Expand Down Expand Up @@ -220,7 +225,9 @@ const WarningLight = React.memo<{
/>
</mesh>
</group>
<pointLight ref={lightRef} color={color} intensity={0} distance={7} decay={2} />
{realLightEnabled && (
<pointLight ref={lightRef} color={color} intensity={0} distance={7} decay={2} />
)}
</group>
);
});
Expand Down
144 changes: 81 additions & 63 deletions src/components/TruckBay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
import {
createTruckController,
getTruckControllerPose,
interpolateTruckControllerState,
stepTruckController,
type TruckControllerState,
} from './truckbay/truckController';
Expand Down Expand Up @@ -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 }) => (
<group name="fleet-telemetry-hub" position={position} rotation={[0, rotation, 0]}>
<mesh position={[0, 2, 0]} castShadow receiveShadow>
<boxGeometry args={[8, 4, 6]} />
<meshStandardMaterial color="#4b5563" roughness={0.78} metalness={0.12} />
</mesh>
<mesh position={[0, 4.15, 0]} castShadow>
<boxGeometry args={[8.6, 0.3, 6.6]} />
<meshStandardMaterial color="#1f2937" roughness={0.62} metalness={0.35} />
</mesh>
<mesh position={[0, 1.5, 3.04]}>
<boxGeometry args={[2.8, 2.7, 0.12]} />
<meshStandardMaterial color="#111827" roughness={0.5} metalness={0.65} />
</mesh>
{[-0.82, 0, 0.82].map((x, index) => (
<group key={x} position={[x, 1.62, 3.12]}>
<mesh>
<boxGeometry args={[0.58, 1.85, 0.08]} />
<meshStandardMaterial color="#0f172a" roughness={0.42} metalness={0.55} />
}> = ({ position, rotation = 0 }) => {
const yardLampsEnabled = useYardLampsEnabled();
return (
<group name="fleet-telemetry-hub" position={position} rotation={[0, rotation, 0]}>
<mesh position={[0, 2, 0]} castShadow receiveShadow>
<boxGeometry args={[8, 4, 6]} />
<meshStandardMaterial color="#4b5563" roughness={0.78} metalness={0.12} />
</mesh>
<mesh position={[0, 4.15, 0]} castShadow>
<boxGeometry args={[8.6, 0.3, 6.6]} />
<meshStandardMaterial color="#1f2937" roughness={0.62} metalness={0.35} />
</mesh>
<mesh position={[0, 1.5, 3.04]}>
<boxGeometry args={[2.8, 2.7, 0.12]} />
<meshStandardMaterial color="#111827" roughness={0.5} metalness={0.65} />
</mesh>
{[-0.82, 0, 0.82].map((x, index) => (
<group key={x} position={[x, 1.62, 3.12]}>
<mesh>
<boxGeometry args={[0.58, 1.85, 0.08]} />
<meshStandardMaterial color="#0f172a" roughness={0.42} metalness={0.55} />
</mesh>
{[0.48, 0.12, -0.24, -0.6].map((y, lightIndex) => (
<mesh key={y} position={[0, y, 0.05]}>
<boxGeometry args={[0.34, 0.08, 0.025]} />
<meshStandardMaterial
color={lightIndex === index ? '#67e8f9' : '#22c55e'}
emissive={lightIndex === index ? '#0891b2' : '#15803d'}
emissiveIntensity={1.2}
toneMapped={false}
/>
</mesh>
))}
</group>
))}
<group position={[0, 5.4, 0]}>
<mesh position={[0, 1.1, 0]}>
<cylinderGeometry args={[0.08, 0.12, 2.2, 12]} />
<meshStandardMaterial color="#94a3b8" roughness={0.28} metalness={0.82} />
</mesh>
{[0.48, 0.12, -0.24, -0.6].map((y, lightIndex) => (
<mesh key={y} position={[0, y, 0.05]}>
<boxGeometry args={[0.34, 0.08, 0.025]} />
<meshStandardMaterial
color={lightIndex === index ? '#67e8f9' : '#22c55e'}
emissive={lightIndex === index ? '#0891b2' : '#15803d'}
emissiveIntensity={1.2}
toneMapped={false}
/>
{[0, Math.PI / 2].map((rotationY) => (
<mesh key={rotationY} position={[0, 2.15, 0]} rotation={[0, rotationY, 0]}>
<boxGeometry args={[2.4, 0.08, 0.08]} />
<meshStandardMaterial color="#cbd5e1" roughness={0.3} metalness={0.8} />
</mesh>
))}
{yardLampsEnabled && (
<pointLight position={[0, 2.2, 0]} intensity={2} distance={8} color="#22d3ee" />
)}
</group>
))}
<group position={[0, 5.4, 0]}>
<mesh position={[0, 1.1, 0]}>
<cylinderGeometry args={[0.08, 0.12, 2.2, 12]} />
<meshStandardMaterial color="#94a3b8" roughness={0.28} metalness={0.82} />
</mesh>
{[0, Math.PI / 2].map((rotationY) => (
<mesh key={rotationY} position={[0, 2.15, 0]} rotation={[0, rotationY, 0]}>
<boxGeometry args={[2.4, 0.08, 0.08]} />
<meshStandardMaterial color="#cbd5e1" roughness={0.3} metalness={0.8} />
<group position={[0, 3.5, 3.2]}>
<mesh>
<boxGeometry args={[4.8, 0.65, 0.12]} />
<meshStandardMaterial color="#0c4a6e" roughness={0.45} metalness={0.3} />
</mesh>
))}
<pointLight position={[0, 2.2, 0]} intensity={2} distance={8} color="#22d3ee" />
</group>
<group position={[0, 3.5, 3.2]}>
<mesh>
<boxGeometry args={[4.8, 0.65, 0.12]} />
<meshStandardMaterial color="#0c4a6e" roughness={0.45} metalness={0.3} />
</mesh>
<Text position={[0, 0, 0.07]} fontSize={0.24} color="#ecfeff" anchorX="center">
FLEET TELEMETRY
</Text>
<Text position={[0, 0, 0.07]} fontSize={0.24} color="#ecfeff" anchorX="center">
FLEET TELEMETRY
</Text>
</group>
</group>
</group>
);
);
};

// Propane tank cage
export const PropaneTankCage: React.FC<{
Expand Down Expand Up @@ -2124,6 +2130,7 @@ const GuardShack: React.FC<{
}> = ({ position, rotation = 0, gateOpen }) => {
const gateRef = useRef<THREE.Mesh>(null);
const guardId = useId();
const yardLampsEnabled = useYardLampsEnabled();

useEffect(() => {
if (!gateRef.current) return;
Expand Down Expand Up @@ -2166,11 +2173,11 @@ const GuardShack: React.FC<{
<boxGeometry args={[0.9, 2, 0.05]} />
<meshStandardMaterial color="#374151" roughness={0.6} />
</mesh>
<mesh position={[0, 3.3, 1.5]}>
<mesh position={[0, 3.3, 1.5]} material={EXTERIOR_LAMP_LENS_MATERIAL}>
<boxGeometry args={[0.3, 0.2, 0.3]} />
<meshStandardMaterial color="#fef3c7" emissive="#fef3c7" emissiveIntensity={0.5} />
</mesh>
<pointLight position={[0, 3, 2]} intensity={15} distance={15} color="#fef3c7" />
<ExteriorLampPool radius={5.5} />
{yardLampsEnabled && <ExteriorPointLight position={[0, 3, 2]} intensity={15} distance={15} />}
<group position={[3, 0, 0]}>
<mesh position={[0, 1, 0]}>
<boxGeometry args={[0.4, 2, 0.4]} />
Expand Down Expand Up @@ -2475,12 +2482,10 @@ const HeadlightBeam: React.FC<{
rotation: [number, number, number];
isOn: boolean;
}> = ({ position, rotation, isOn }) => {
if (!isOn) return null;

return (
<group position={position} rotation={rotation}>
{/* Cone rotated to point forward (Z direction) - tip at light source, base spreading forward */}
<mesh position={[0, 0, 2]} rotation={[-Math.PI / 2, 0, 0]}>
<mesh visible={isOn} position={[0, 0, 2]} rotation={[-Math.PI / 2, 0, 0]}>
<coneGeometry args={[1.5, 4, 8, 1, true]} />
<meshBasicMaterial
color="#fef3c7"
Expand All @@ -2495,7 +2500,7 @@ const HeadlightBeam: React.FC<{
target-position={[0, 0, 5]}
angle={0.4}
penumbra={0.5}
intensity={5}
intensity={isOn ? 5 : 0}
distance={20}
color="#fef3c7"
/>
Expand Down Expand Up @@ -2743,6 +2748,8 @@ export const TruckBay: React.FC<TruckBayProps> = ({ productionSpeed }) => {
const receivingTruckStateRef = useRef<TruckAnimState>(initialReceivingState);
const shippingControllerRef = useRef<TruckControllerState>(initialShippingController);
const receivingControllerRef = useRef<TruckControllerState>(initialReceivingController);
const shippingPreviousControllerRef = useRef<TruckControllerState>(initialShippingController);
const receivingPreviousControllerRef = useRef<TruckControllerState>(initialReceivingController);
const shippingAccumulatorRef = useRef(0);
const receivingAccumulatorRef = useRef(0);

Expand Down Expand Up @@ -2841,6 +2848,7 @@ export const TruckBay: React.FC<TruckBayProps> = ({ productionSpeed }) => {
const advanceTruck = (
dock: 'shipping' | 'receiving',
controllerRef: React.MutableRefObject<TruckControllerState>,
previousControllerRef: React.MutableRefObject<TruckControllerState>,
accumulatorRef: React.MutableRefObject<number>,
truckStateRef: React.MutableRefObject<TruckAnimState>,
truckRef: React.MutableRefObject<THREE.Group | null>,
Expand All @@ -2852,10 +2860,12 @@ export const TruckBay: React.FC<TruckBayProps> = ({ 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,
Expand All @@ -2868,6 +2878,7 @@ export const TruckBay: React.FC<TruckBayProps> = ({ productionSpeed }) => {
accumulator -= TRUCK_CONTROLLER_STEP_SECONDS;
}
controllerRef.current = controller;
previousControllerRef.current = previousController;
accumulatorRef.current = accumulator;

if (scheduleBefore.arrivalReady && controller.active) {
Expand All @@ -2877,9 +2888,14 @@ export const TruckBay: React.FC<TruckBayProps> = ({ 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;
Expand All @@ -2893,11 +2909,11 @@ export const TruckBay: React.FC<TruckBayProps> = ({ 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,
Expand Down Expand Up @@ -3049,6 +3065,7 @@ export const TruckBay: React.FC<TruckBayProps> = ({ productionSpeed }) => {
advanceTruck(
'shipping',
shippingControllerRef,
shippingPreviousControllerRef,
shippingAccumulatorRef,
shippingTruckStateRef,
shippingTruckRef,
Expand All @@ -3061,6 +3078,7 @@ export const TruckBay: React.FC<TruckBayProps> = ({ productionSpeed }) => {
advanceTruck(
'receiving',
receivingControllerRef,
receivingPreviousControllerRef,
receivingAccumulatorRef,
receivingTruckStateRef,
receivingTruckRef,
Expand Down
14 changes: 11 additions & 3 deletions src/components/environment/NearHorizonCity.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand All @@ -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);
Expand All @@ -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);
});
});
});
Loading