Editor/Data/ArchKit.cs

An editor-side data container for architectural kit parameters and presets. It stores many float/int sizing defaults (walls, roofs, stairs, fittings), lists of preset types, a palette, and loads catalogs in the constructor; provides lookup helpers for openings, road lines and profiles.

File AccessNetworking
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

public sealed class ArchKit
{
	public string Name { get; set; } = "default";

	// What everything in the plan collides as unless a part says otherwise. Solids by default: the generators know
	// their own convex pieces, and a colonnade of them costs the solver a fraction of one concave mesh per part.
	public ArchCollisionMode Physics { get; set; } = ArchCollisionMode.Solids;

	public float GridSize { get; set; } = 32f;
	// Read through ArchContact, never here.
	public float ContactBite { get; set; } = 0.4f;
	public float GroundClearance { get; set; } = 2f;
	public float TerrainPadding { get; set; } = 48f;
	public float WallHeight { get; set; } = 128f;
	public float WallThickness { get; set; } = 8f;
	public float FloorThickness { get; set; } = 8f;
	// Deeper than a floor slab on purpose: a ceiling is the body a coffer or a beam zone is taken out of.
	public float CeilingThickness { get; set; } = 12f;
	public float FoundationDepth { get; set; } = 24f;
	public float FoundationOversize { get; set; } = 2f;
	public float FoundationRise { get; set; } = 18f;

	// How far a break edge wanders off the clean cut, and how deep a sticker window's box reads.
	public float BreakJitter { get; set; } = 6f;
	public float FalseInteriorDepth { get; set; } = 24f;

	// Driveway apron, not a wheelchair ramp - steeper than code to fit a front garden.
	public float RampSlope { get; set; } = 0.15f;
	public float RampThickness { get; set; } = 5f;
	public float RampKerbHeight { get; set; } = 5f;
	public float RampKerbWidth { get; set; } = 4f;

	public float CasingWidth { get; set; } = 4f;
	public float CasingDepth { get; set; } = 1.5f;
	public float BaseboardHeight { get; set; } = 6f;
	public float BaseboardDepth { get; set; } = 1.5f;
	// Height is per wall; only the section is a kit dimension.
	public float WallBandHeight { get; set; } = 4f;
	public float WallBandDepth { get; set; } = 1.5f;
	public float WallCapHeight { get; set; } = 6f;
	public float SillDepth { get; set; } = 3f;
	public float SillThickness { get; set; } = 2f;

	// A burglar bar is on every window in these suburbs, so its pitch is a kit dimension and never a
	// per-opening number.
	public float GrilleBar { get; set; } = 0.9f;
	public float GrilleGap { get; set; } = 5f;
	public float GrilleProud { get; set; } = 1.5f;
	public float GrilleFrame { get; set; } = 1.6f;

	// The lap is deliberately not the casing's depth, or the saddle z-fights it down both jambs.
	public float ThresholdProud { get; set; } = 0.6f;
	public float ThresholdLap { get; set; } = 2f;

	// The shadow under every course comes from geometry - a flat texture can't do this at grazing sun.
	public float SidingCourse { get; set; } = 7f;
	public float SidingProud { get; set; } = 1.4f;

	public float SheetPitch { get; set; } = 9f;
	public float SheetCrest { get; set; } = 3.5f;
	public float SheetProud { get; set; } = 1.2f;

	public float FloorBoardWidth { get; set; } = 6f;
	public float FloorBoardGap { get; set; } = 0.25f;
	public float FloorBoardThickness { get; set; } = 1.2f;
	public float FloorBoardLength { get; set; } = 96f;

	public float WainscotHeight { get; set; } = 38f;
	public float WainscotProud { get; set; } = 1.1f;
	public float WainscotStile { get; set; } = 3.5f;
	public float WainscotBay { get; set; } = 26f;
	public float ChairRailHeight { get; set; } = 2.6f;
	public float ChairRailDepth { get; set; } = 2.4f;

	public float FrameFace { get; set; } = 2.6f;
	public float FrameDepth { get; set; } = 3.4f;
	public float FrameSetback { get; set; } = 1.6f;
	public float SashFace { get; set; } = 2.2f;
	public float SashDepth { get; set; } = 1.6f;
	public float GlazingBar { get; set; } = 0.8f;

	public float PorchDeckThickness { get; set; } = 2.5f;
	public float PorchPostSize { get; set; } = 7f;
	public float PorchBeamDepth { get; set; } = 9f;
	public float PorchRailHeight { get; set; } = 34f;
	public float PorchCeilingThickness { get; set; } = 2f;
	public float PorchFriezeHeight { get; set; } = 7f;
	// Frieze and flashing both sit on the house wall, so they share one proud.
	public float PorchFriezeDepth { get; set; } = 2f;
	public float PorchFlashingHeight { get; set; } = 6f;
	public float RoofRafterDepth { get; set; } = 9f;
	public float RoofRafterWidth { get; set; } = 3.4f;
	public float RoofRafterSpacing { get; set; } = 60f;
	public float RoofPurlinDepth { get; set; } = 4f;
	public float RoofPurlinWidth { get; set; } = 2.6f;
	public float RoofPurlinSpacing { get; set; } = 44f;

	public float PorchRafterDepth { get; set; } = 5f;
	public float PorchRafterWidth { get; set; } = 2.4f;
	public float PorchRafterSpacing { get; set; } = 36f;
	public float PorchBraceSize { get; set; } = 3.2f;
	// A knee brace is cut square, so far off 1 it reads as a prop, not a brace.
	public float PorchBraceRake { get; set; } = 1f;
	public float BalusterSpacing { get; set; } = 5.5f;
	public float BalusterGap { get; set; } = 2.2f;
	public float BalusterScale { get; set; } = 1f;
	// Five sides and five rings is the whole silhouette at this fidelity.
	public int BalusterSides { get; set; } = 5;

	public float PicketWidth { get; set; } = 3.5f;
	public float PicketGap { get; set; } = 2.4f;
	public float PicketThickness { get; set; } = 1.2f;
	public float FencePostSize { get; set; } = 5f;
	public float FenceRailDepth { get; set; } = 2.4f;
	public float FenceRailHeight { get; set; } = 3.2f;

	public float PalisadeWidth { get; set; } = 1.6f;
	public float PalisadeGap { get; set; } = 4.5f;
	public float PalisadeThickness { get; set; } = 1.2f;

	public float PrecastPostDepth { get; set; } = 7f;
	public float PrecastPostWidth { get; set; } = 6f;
	public float PrecastSlotDepth { get; set; } = 1.8f;
	public float PrecastJoint { get; set; } = 0.3f;
	public float PrecastPlinthProud { get; set; } = 0.9f;
	public float DropperThickness { get; set; } = 0.8f;
	public float WireThickness { get; set; } = 0.25f;
	// A fraction of the span, so one number sags a short spur and a long crossing alike.
	public float CatenarySag { get; set; } = 0.06f;
	// Run per rise: a retaining wall leans back into the fill it holds.
	public float RetainingBatter { get; set; } = 0.08f;

	public float PavementFall { get; set; } = 1.2f;
	public float DroppedKerb { get; set; } = 0.8f;
	public float KerbTransition { get; set; } = 24f;
	// Sampled across the pavement; along the road, knots crowd straights and thin at the turns.
	public int CrossoverSegments { get; set; } = 8;
	public float EdgeLineInset { get; set; } = 8f;
	public float ZebraBar { get; set; } = 18f;
	public float ZebraGap { get; set; } = 18f;
	public float RoadPadding { get; set; } = 96f;
	public float RoadInset { get; set; } = 4f;
	// 15 ft is the shallow end of a suburban corner; crossings this close merge into one node.
	public float JunctionRadius { get; set; } = 180f;
	public float JunctionSpread { get; set; } = 240f;
	// Generous on purpose - real minimum clearances are measured in feet.
	public float JunctionLevel { get; set; } = 30f;
	// A pier ending exactly on grade floats the moment the terrain moves.
	public float PierEmbedment { get; set; } = 12f;
	// A pier standing on the kerb line is as wrong as one in the lane.
	public float PierClearance { get; set; } = 24f;

	public float RoofThickness { get; set; } = 6f;
	public float RoofPitch { get; set; } = 30f;
	public float RoofOverhang { get; set; } = 16f;
	public float FasciaHeight { get; set; } = 8f;

	// Roof edge protection comes off the same stock everywhere, so only its height is per-roof.
	public float RoofGuardBay { get; set; } = 96f;
	public float RoofGuardPost { get; set; } = 3f;

	// What stands ON a deck: a flue, a vent cowl, a tank. Its base comes off the deck plane, never an
	// authored z, so only its own size lives here.
	public float PlantHeight { get; set; } = 40f;
	public float PlantWidth { get; set; } = 16f;

	public float GutterBracketSpacing { get; set; } = 96f;
	public float DownpipeBracketSpacing { get; set; } = 48f;
	public float DownpipeWallGap { get; set; } = 1f;

	// All ladders come off the same stock, so the sections are kit-wide.
	public float LadderBar { get; set; } = 2.5f;
	public float LadderBracketSpacing { get; set; } = 60f;
	public float LadderCageSpacing { get; set; } = 36f;
	// Clears the climber, not the ladder.
	public float LadderCageReach { get; set; } = 30f;

	// Every fixture hung on an elevation comes off the same stock, so its sections are kit-wide and only
	// its extent is dragged.
	public float BalconyReach { get; set; } = 36f;
	public float BalconySlab { get; set; } = 5f;
	public float BalconyRail { get; set; } = 40f;
	public float EscapeLanding { get; set; } = 48f;
	public float EscapeWidth { get; set; } = 36f;
	public float ConduitBracketSpacing { get; set; } = 60f;

	public float StepRise { get; set; } = 8f;
	public float StepGoing { get; set; } = 12f;
	public float StairWidth { get; set; } = 48f;
	public float StringerThickness { get; set; } = 3f;
	public float StringerDepth { get; set; } = 14f;
	public float HandrailHeight { get; set; } = 36f;
	// Clears the climber's head over the nosing: how close the flight has to come to a slab to open it.
	public float StairHeadroom { get; set; } = 80f;
	public float NewelSize { get; set; } = 4.5f;
	public float NewelRise { get; set; } = 6f;
	public float StairBalusterSpacing { get; set; } = 5f;
	// This close, the flight runs against the wall: suppresses that side's balustrade, picks the socket face.
	public float StairWallGap { get; set; } = 10f;
	// Well past the stair's width on purpose - a short landing leaves floor nobody can reach.
	public float StairCornerReach { get; set; } = 96f;
	// How far ONTO the floor the walk off a stair carries before a guard may stand in it. A mouth judged as the
	// one edge it leaves over lets the ring close a step past the head, which blocks the stair just as surely.
	public float StairWalkClearance { get; set; } = 60f;

	// Proud is read through ArchContact so the stair capping and floor nosing agree.
	public float WellTrimProud { get; set; } = 0.75f;
	public float WellTrimDepth { get; set; } = 3.5f;

	public float DoorLeafThickness { get; set; } = 3f;
	// A leaf hangs clear of what it closes onto.
	public float DoorUndercut { get; set; } = 0.5f;

	public List<ArchOpeningPreset> Openings { get; set; }
	public List<ArchWallPreset> Walls { get; set; }
	public List<ArchProfile> Profiles { get; set; }
	public List<ArchRoadLine> RoadLines { get; set; }
	public ArchPalette Palette { get; set; } = new();

	// A kit is born holding whatever the catalogs ship, so one built in memory offers the same presets as one read
	// off disk - and a shelf no catalog claims simply stands empty instead of stocking a list the kit would have to
	// know the contents of.
	public ArchKit()
	{
		var shelves = ArchCatalogs.Load();

		Openings = shelves.Ships<ArchOpeningPreset>( ArchShelf.Openings );
		Walls = shelves.Ships<ArchWallPreset>( ArchShelf.Walls );
		Profiles = shelves.Ships<ArchProfile>( ArchShelf.Profiles );
		RoadLines = shelves.Ships<ArchRoadLine>( ArchShelf.RoadLines );
	}

	public ArchOpeningPreset FindOpening( string name )
	{
		return Openings.FirstOrDefault( preset => string.Equals( preset.Name, name, StringComparison.OrdinalIgnoreCase ) )
			?? Openings.FirstOrDefault();
	}

	// An unnamed line is no line - null, not the first preset.
	public ArchRoadLine FindRoadLine( string name )
	{
		return string.IsNullOrWhiteSpace( name )
			? null
			: RoadLines.FirstOrDefault( line => string.Equals( line.Name, name, StringComparison.OrdinalIgnoreCase ) );
	}

	public ArchProfile FindProfile( string name )
	{
		return Profiles.FirstOrDefault( profile => string.Equals( profile.Name, name, StringComparison.OrdinalIgnoreCase ) )
			?? Profiles.FirstOrDefault();
	}
}