Joins lines, cable routes and equipment into a network and works out the current in every span and cable and the voltage drop at every node from each source.
How the precision is kept
64-bit numbers. The drop in each edge is the usual approximate formula √3·I·L·(R·cos φ + X·sin φ), with resistance at 20 °C and a typical reactance when the library item gives none, so the formula and its data set the accuracy. Currents use the nominal voltage, not the dropped voltage. The file header calls it a design aid with lumped loads on radial circuits.
Calculations under this policy
5 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.
Finds the support or equipment a route end or a piece of equipment connects to.
nearest node with distance × m per unit ≤ snap + 1e−9
The snap distance is absolute, 1 m by default. A node wins when it is nearer by more than 1e-12; at an equal distance within 1e-12 equipment is preferred over a support.
powerline/network.js
lines 82 to 90
CivilCraft 0.8.1
const nearest = (q, kinds) => {
let best = null, bd = Infinity;
for (const n of net.nodes) {
if (!kinds.includes(n.kind)) continue;
const d = Math.hypot(n.x - q.x, n.y - q.y) * um;
if (d <= snap + 1e-9 && (d < bd - 1e-12 || (Math.abs(d - bd) <= 1e-12 && n.kind === 'equipment'))) { bd = d; best = n; }
}
return best;
};
Resistance, reactance and ampacity of an edge
64-bit
The electrical data of a span or cable route, with parallel cables counted.
R = R₂₀ ÷ n; X = X ÷ n (0.08 or 0.35 Ω/km by default); ampacity × n
When the item has no reactance a typical value is used and a note is added. The resistance is the library's value at 20 °C, without a temperature correction.
The active and reactive power each edge carries, and the current each load draws.
Q = P·tan(acos pf); I = √(P² + Q²) ÷ (√3·U)
The power factor is limited to (0, 1], so the acos argument stays in range. P and Q are added from the far ends of the tree back to the source in reverse breadth-first order.
powerline/network.js
lines 173 to 186
CivilCraft 0.8.1
// loads, summed upstream (reverse BFS order)
const P = new Map(), Q = new Map();
for (const v of order) {
const eq = equip.get(v);
const p = eq && eq.source !== true && fin(eq.load_kW) && eq.load_kW > 0 ? eq.load_kW : 0;
const pf = fin(eq?.pf) && eq.pf > 0 && eq.pf <= 1 ? eq.pf : 0.95;
P.set(v, p); Q.set(v, p * Math.tan(Math.acos(pf)));
const U = Unom.get(v);
out.nodes[v].I_A = fin(U) && U > 0 ? Math.hypot(p, p * Math.tan(Math.acos(pf))) / (SQ3 * U) : null;
}
for (let i = order.length - 1; i > 0; i--) {
const v = order[i], e = parent.get(v), u = e.from === v ? e.to : e.from;
P.set(u, P.get(u) + P.get(v)); Q.set(u, Q.get(u) + Q.get(v));
}
Current, voltage drop and node voltage
64-bit
The current and voltage drop in each edge and the voltage and cumulative drop at each node.
ΔU = √3·I·(L ÷ 1000)·(R·cos φ + X·sin φ); drop % += ΔU ÷ (U × 1000) × 100; U node = U·(1 − drop ÷ 100)
The approximate drop formula, as the file header says. The current is computed from the nominal voltage of the edge, not from the voltage after the drop upstream, so the result is a first pass and not an iterated load flow. Percent drops are added along the path.
powerline/network.js
lines 187 to 206
CivilCraft 0.8.1
// edges and voltages (BFS order: the parent first)
out.nodes[src].U_kV = U0; out.nodes[src].drop_pct = fin(U0) ? 0 : null;
for (const v of order.slice(1)) {
const e = parent.get(v), u = e.from === v ? e.to : e.from, k = E.get(e.id);
const U = Unom.get(v); // the edge's nominal voltage
const p = P.get(v), q = Q.get(v), S = Math.hypot(p, q);
const r = out.edges[e.id];
const I = fin(U) && U > 0 ? S / (SQ3 * U) : null; // kVA / (√3 kV) = A
r.I_A = I;
r.ampacity_A = k.amp;
r.load_pct = I != null && k.amp > 0 ? (I / k.amp) * 100 : null;
if (I != null && fin(k.R) && fin(k.X)) {
const cos = S > 0 ? p / S : 1, sin = S > 0 ? q / S : 0;
r.dU_V = SQ3 * I * (e.length_m / 1000) * (k.R * cos + k.X * sin);
} else if (I != null && I > 0) out.notes.push({ code: 'nor', edge: e.id, key: e.item });
const d0 = out.nodes[u].drop_pct;
const d = d0 != null && fin(U) && U > 0 ? d0 + (r.dU_V ?? 0) / (U * 1000) * 100 : null;
out.nodes[v].drop_pct = d;
out.nodes[v].U_kV = d != null ? U * (1 - d / 100) : null;
}
In detail
Method
Breadth-first tree from each source; loads summed upstream; I = S ÷ (√3·U); ΔU = √3·I·L·(R cos φ + X sin φ); drop percent summed along the path, as the file header describes.
Number types
64-bit numbers. √3 from Math.sqrt(3).
Tolerances
Route ends and equipment snap to a node within 1 m by default, with a slack of 1e-9; ties at equal distance are decided within 1e-12. A drop is flagged above the limit plus 1e-9; an overload above 100 percent with no slack.
Limits
Reactance defaults to 0.08 Ω/km for a cable and 0.35 Ω/km for an overhead conductor. Resistance is the 20 °C value. Span lengths are plan lengths. No transformation inside a tree and no loops.
Approach: 64-bit, stated limit
The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.