Arc-Spline Transport
Every right-of-way is an arc spline; roads and rail are bundles of one or more of them.
Core Model
One right-of-way = one arc spline — the CAGD term (Bolton 1975; Meek & Walton) for a tangent-continuous (G1) piecewise curve of circular arcs and straight line segments, a line segment simply being the infinite-radius special case of an arc. Straight segments cover the common case (a plain block-length street); finite-radius arcs are inserted only where the player actually curves the road. Curvature is well-defined everywhere, constant-width offsetting is trivial on both segment types, and the whole curve is cheap to sample by arc length.
Bundles
- Two-way road = a bundle of 2 lane-splines, offset by lane width, facing opposite directions.
- N-lane road = a bundle of N lane-splines, offsets stacked from a shared bundle centerline.
- Rail = a single bidirectional arc spline (no bundling — trains share the one right-of-way).
Editing Model
- Player sketches a centerline as a rough polyline.
- Auto-fit to a piecewise arc spline: near-straight runs stay line segments, curved runs get tangent-continuous arcs inserted (curvature-bounded per road class).
- Lane offsets are derived from the fitted centerline + bundle’s lane count/width.
Curvature Bound
Minimum radius per road class is the AASHTO formula R_min = V² / (15 × (e_max + f_s)), re-tightened live as the player adjusts a segment's speed limit. Exact e_max/f_s assumptions per class are being tuned.
Intersections
Bundle endpoints resolve into a junction node; turn lanes are generated as short connector arc-splines between incoming/outgoing lane endpoints — no separate hand-authored intersection meshes.
Data Model
| Entity | Fields |
|---|---|
| LaneSpline | segments[] (each a Line or Arc), width, direction, speedLimit |
| Bundle | lanes[], classification (street/avenue/highway/rail) |
| Junction | incoming/outgoing lane refs, generated connectors |
| RouteGraph | nodes: Map<string, RouteNode>, adjacency: Map<string, RouteEdge[]> |
| RouteEdge | fromNodeId, toNodeId, spline, length, speedLimit, modes: ("walk" | "drive")[] |
| Route | legs: RouteLeg[], totalLength, estimatedDuration, mode |
Pathfinding & Route Graph Architecture
The transport network provides the topological routing substrate for all simulated city mobility. Pathfinding operates directly on a directed RouteGraph extracted from lane bundles and junction turn connectors.
Graph Construction & Mode Allocation
- Directed Lane Edges: Each lane in a
Bundleproduces a directedRouteEdgereferencing its underlying G1 arc spline with start/endRouteNodes. - Junction Connectors: Every internal turn movement in
Junction.connectorsgenerates an edge connecting the incoming approach lane's end node to the outgoing departure lane's start node. - Multimodal Allocation: Vehicular traffic (
"drive") traverses all paved vehicular lanes. Pedestrian traffic ("walk") is allocated to the outermost lanes of road bundles (representing roadside sidewalks and shoulders). Speeds are assigned from road class limits (converted to cm/s) or standard walking speed (~140 cm/s / ~5 km/h).
Origin/Destination Snapping & Parcel Access
Arbitrary 2D world points (building entrances, parcel centroids) connect to the network via geometric snapping (Spline.prototype.closestPoint):
- Parcel Access Points: Origins and destinations resolve parcel frontage and driveway access lines connecting the parcel centroid to the road curb.
- Driveway & Parking Setbacks: If a parcel's front setback depth is ≥ 5 meters (500 cm), vehicles park within the driveway/front setback; if < 5 meters, vehicles park at the curb frontage directly in front of the building.
A* Shortest-Travel-Time Search
Pathfinding evaluates the minimum-travel-time path using travel time ($t = \text{length} / \text{speed}$) as edge cost, guided by an admissible Euclidean heuristic ($h = \text{distance} / v_{\max}$). The search stitches together an origin driveway access leg, start lane remainder, intermediate lane and connector edges, end lane prefix, and destination driveway access leg into a continuous, composite Route.
Off-Thread Web Worker Solver
Route graph construction and A* queries run asynchronously on a dedicated Web Worker (transport.worker.ts) via a typed IPC bridge (TransportService / WorkerClient). To guard against prototype stripping across postMessage structured cloning, bundles and junctions are transmitted as plain serialized descriptors (SerializedBundle[], SerializedJunction[]) and rehydrated into graph topology worker-side.
Deterministic Traversal & In-View Frustum Embodiment
Background agent commuting progresses deterministically along route splines via a numerical step function (advanceAlongRoute). To eliminate 3D scene-graph overhead for off-screen trips:
- Frustum Culling: Physical 3D visual entities (vehicle/pedestrian prisms) are spawned and animated only when their sampled world position is within the player's camera frustum (
isPositionInFrustum). - Pedestrian Flow: On-screen pedestrian meshes are offset laterally to the outer sidewalk edge and apply a lightweight 2D social-force flocking rule (
applyPedestrianFlow) so opposing walkers steer around each other.
Downstream Ties
Lane-spline geometry drives procedural road materials/markings (see Production & Materials docs) and gives traffic agents a cheap arc-length parameterization to move along (see Citizen Simulation doc).
Economy & Zoning
Because every right-of-way in the game is an arc spline (or a bundle of them), any reachable relationship between parcels in the city is mediated by the transport graph — there is no off-road movement. Roads, rail, bike paths, sidewalks, and future transit modes are all built from these arc splines, and an agent's path between two parcels is a route through whatever modes of that graph are available to them.
This means the economy & zoning sim does not have a spatial notion of "nearby." Its data store models the city as a graph of parcels keyed by ID, and the only meaningful proximity is network-reachability within a travel-time budget. Once the citizen simulation exists, that will look like:
- Mode choice is gated by household wealth. Wealthy citizens can afford a car or other private transport and reach destinations faster; poor citizens are limited to walking and public transit, with a smaller reachable set for the same travel-time budget.
- Effective travel speed is mode-dependent. A given parcel pair is reachable for a rich household in five minutes (by car) but unreachable for a poor household in the same five minutes (by walking/transit), so a fixed travel-time budget yields a different reachable set per wealth profile.
- Reachable sets are what the macro sim aggregates from. The R/C/I demand signals and per-parcel land-value bonuses that the economy rolls up are ultimately functions of which parcels can reach which other parcels through the player's multimodal transit network — and through which modes a given household can afford to use.
In short: the transport network is the city's connective tissue for both traffic and the macroeconomy. Equity in transit coverage is an economic lever, not a flavor mechanic — a commercial zone that is only reachable by car is invisible to the poor households on the other side of town.
Future Work
- Multimodal Mode-Switching: Transfers between disparate travel modes within a single journey (e.g. driving to a commuter park-and-ride lot, walking to a transit stop, rail transit traversal, and walking to final destination).
- Traffic Signal Phasing & Congestion: Dynamic intersection delay calculations from junction control priorities (stop sign vs traffic signal cycles) and mesoscopic link density speed-decay curves.
- Dynamic Re-routing: Real-time path re-evaluation in response to temporary road closures, construction zones, or incident lane blockages.
- Elevation Integration: Grade separation, bridges, and ramps attaching 3D vertical profiles to planar arc splines.
Open Questions
- What
e_max/f_svalues should each road class use in the AASHTO minimum-radius formula? - How do elevation changes (bridges, tunnels, grade) attach to an otherwise-planar arc spline?
- What fit tolerance should apply between the sketch polyline and the resulting spline?
- What's the threshold for when a near-straight run stays a line segment vs. gets an arc?