Editor/Stair/ArchStairGuardPart.cs

A data model type for a single stair railing entry in the editor. It stores which edge (left/right/head), whether it is a balustrade or handrail, the fractional span on the step (From/To), height, and provides computed properties Start, End, Stands and a Copy method.

Reflection
namespace Sunless.Architecture;

// Which edge of a step a railing stands on, in the step's OWN frame - left and right are the walker's, so a
// railing keeps the side it was put on when the flight is turned or the whole stair is swung.
public enum StairEdge
{
	Left,
	Right,
	Head
}

// What stands on that edge. A balustrade is the free-standing run of newels and balusters; a handrail is what a
// side running against plaster gets instead, because balusters inside a wall are balusters nobody can see.
public enum StairRailing
{
	Balustrade,
	Handrail
}

// One railing, on one edge of one step. A row under the step in the stack, so a stair carries as many as its
// author wants and each is added, restyled and deleted on its own.
//
// A step with NO railing rows falls back to the stair's own Guard setting, which is what every stair authored
// before this had and what the walk still seeds. Putting one row on a step is what takes that step off the
// default - so adding a railing never has to mean re-stating the ones already there.
public sealed class ArchStairGuardPart : IArchNamed
{
	public int Id { get; set; }
	public string Name { get; set; } = "Railing";

	public StairEdge Edge { get; set; }
	public StairRailing Railing { get; set; }

	// The stretch of that edge it actually covers, as a fraction of the step's own run - so a railing keeps the
	// proportion it was dragged to when the step is lengthened, and a step resized under a railing never leaves it
	// hanging off the end. The whole edge is 0 to 1, which is what a fresh one takes.
	public float From { get; set; }
	public float To { get; set; } = 1f;

	// Zero takes the kit's handrail height.
	public float Height { get; set; }

	public float Start => Math.Clamp( MathF.Min( From, To ), 0f, 1f );

	public float End => Math.Clamp( MathF.Max( From, To ), 0f, 1f );

	public bool Stands => End - Start > 0.01f;

	public ArchStairGuardPart Copy() => new()
	{
		Id = Id,
		Name = Name,
		Edge = Edge,
		Railing = Railing,
		From = From,
		To = To,
		Height = Height
	};
}