Resources Numerical accuracy Calculation policy

Flood depth and wind load playback

Works out how deep each room floods over time from a water-level record, the water's push on a wall, and the wind force, shear and overturning moment on each story.

How the precision is kept

Every value is a 64-bit number; the depth of each room at each sample is held in a Float64Array. The flood is sampled every dt seconds (default 60) from a hydrograph interpolated linearly between its points, so a peak between two samples is not seen. Each room's maximum depth is rounded to 0.001 m before it is returned. The wind speed follows a power law in height, with the height taken as at least 5 m.

Open item. Return the maximum depth unrounded and round it only where it is shown.

Calculations under this policy

4 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.

hazard.js BimCraft 0.8.1 64-bit 4 calculations Reviewed

Tests tools/bimcraft-test/hazard-test.mjs

Room depth at each time sample

64-bit

The water depth in every room at each time step, from the outside level and the openings the water has passed.

level(t) = linear between hydrograph points; depth = max(0, level − floor) for a room the water reaches

Sample times are i × dt, not a running sum, so they do not drift. The interpolation divides by max(1e-9, Δt), and an opening counts as passed only when the level is more than 1e-9 m above its sill; both are absolute values. Depths are stored in a Float64Array.

  • hazard.js
  • lines 34 to 62
  • BimCraft 0.8.1
export function floodRun({ rooms, openings = [], hydrograph, dt = 60 }) {
  const pts = (hydrograph || []).slice().sort((x, y) => x[0] - y[0]);
  if (pts.length < 2) return { ok: false, reason: 'nohydrograph' };
  const T = pts[pts.length - 1][0], n = Math.floor(T / dt) + 1, times = Array.from({ length: n }, (_, i) => i * dt);
  const at = (t) => { if (t <= pts[0][0]) return pts[0][1]; for (let i = 1; i < pts.length; i++) if (t <= pts[i][0]) { const f = (t - pts[i - 1][0]) / Math.max(1e-9, pts[i][0] - pts[i - 1][0]); return pts[i - 1][1] + f * (pts[i][1] - pts[i - 1][1]); } return pts[pts.length - 1][1]; };
  const level = times.map(at), byId = new Map(rooms.map((r) => [r.id, r]));
  const entry = (o) => { const base = o.b === null ? (byId.get(o.a)?.floor ?? 0) : Math.max(byId.get(o.a)?.floor ?? 0, byId.get(o.b)?.floor ?? 0); return base + (o.sill || 0); };
  const depth = rooms.map(() => new Float64Array(n));
  for (let i = 0; i < n; i++) {
    const w = level[i], wet = new Set(), queue = [];
    for (const o of openings) if (o.b === null && w > entry(o) + 1e-9 && byId.get(o.a) && w > byId.get(o.a).floor) { if (!wet.has(o.a)) { wet.add(o.a); queue.push(o.a); } }
    while (queue.length) {
      const cur = queue.shift();
      for (const o of openings) {
        if (o.b === null) continue;
        const other = o.a === cur ? o.b : o.b === cur ? o.a : null;
        if (other === null || wet.has(other)) continue;
        if (w > entry(o) + 1e-9 && w > byId.get(other).floor) { wet.add(other); queue.push(other); }
      }
    }
    rooms.forEach((r, k) => { if (wet.has(r.id)) depth[k][i] = Math.max(0, w - r.floor); });
  }
  const out = rooms.map((r, k) => {
    let first = null, max = 0, wetFor = 0;
    for (let i = 0; i < n; i++) { if (depth[k][i] > 0) { if (first === null) first = times[i]; wetFor += dt; } if (depth[k][i] > max) max = depth[k][i]; }
    return { id: r.id, name: r.name, firstWet: first, maxDepth: Math.round(max * 1000) / 1000, wetFor, depth: depth[k] };
  });
  return { ok: true, times, level, dt, rooms: out, wetCount: out.filter((x) => x.firstWet !== null).length, peak: Math.max(...level) };
}

Maximum depth, first wet time and wet duration

64-bit

For each room: the deepest water, the first sample time it was wet and how long it stayed wet.

maxDepth = round(max × 1000) ÷ 1000; wetFor = (number of wet samples) × dt

The maximum depth is rounded to 0.001 m with Math.round before it is returned, so the kept value has millimeter resolution. The wet duration is built by adding dt once per wet sample, so it is a multiple of dt.

  • hazard.js
  • lines 56 to 60
  • BimCraft 0.8.1
const out = rooms.map((r, k) => {
  let first = null, max = 0, wetFor = 0;
  for (let i = 0; i < n; i++) { if (depth[k][i] > 0) { if (first === null) first = times[i]; wetFor += dt; } if (depth[k][i] > max) max = depth[k][i]; }
  return { id: r.id, name: r.name, firstWet: first, maxDepth: Math.round(max * 1000) / 1000, wetFor, depth: depth[k] };
});

Hydrostatic push on a wall

64-bit

The force of standing water on one meter of wall, from the depth.

F = ½ × ρ × g × d² (ρ = 1000 kg/m³, g = 9.80665 m/s²)

One 64-bit expression of products. g = 9.80665 is the defined standard value; the water density is the round figure 1000 kg/m³. A negative depth is taken as zero.

  • hazard.js
  • line 63
  • BimCraft 0.8.1
export const wallPush = (d) => 0.5 * RHO_WATER * G * Math.max(0, d) ** 2;

Wind force, shear and moment by story

64-bit

The wind force on each story and the shear and overturning moment summed from the top story down.

V = V10 × (max(z, 5) ÷ 10)^α; q = ½ ρ V²; force = q × G × (Cp,w + Cp,l) × width × h; shear = Σ force above; moment = Σ force × z

64-bit products and one Math.pow. The speed uses the height clamped to at least 5 m, while the moment arm uses the story's unclamped height. The default air density 1.225 kg/m³ and exponent 0.143 are rounded figures; shear and moment are sums added in order from the top story.

  • hazard.js
  • lines 66 to 73
  • BimCraft 0.8.1
/** storeys: [{ z (m, the middle of the storey above the ground), h (m, its height), width (m, across the wind) }], bottom first. */
export function windRun({ V10, alpha = 0.143, G: gust = 1.2, cpWind = 0.8, cpLee = 0.5, storeys, rho = RHO_AIR }) {
  if (!(V10 > 0) || !storeys?.length) return { ok: false, reason: 'nowind' };
  const rows = storeys.map((s) => { const z = Math.max(s.z, 5), V = V10 * Math.pow(z / 10, alpha), q = 0.5 * rho * V * V; return { z: s.z, h: s.h, V, q, force: q * gust * (cpWind + cpLee) * s.width * s.h }; });
  let shear = 0, moment = 0;
  for (let i = rows.length - 1; i >= 0; i--) { shear += rows[i].force; rows[i].shear = shear; moment += rows[i].force * storeys[i].z; rows[i].moment = moment; }
  return { ok: true, storeys: rows, base: { shear, moment }, floors: rows.map((r) => r.force) };
}

In detail

Method
Flood: quasi-static, a room joined to wet rooms by an opening below the water stands at the outside level; depth = level − floor. Wall push ½ ρ g d². Wind: V(z) = V10 × (z ÷ 10)^α, q = ½ ρ V², force = q × G × (Cp windward + Cp leeward) × width × height.
Number types
64-bit numbers and Float64Array. Constants: water 1000 kg/m³, air 1.225 kg/m³, g = 9.80665 m/s² (the defined standard value).
Tolerances
1e-9 m, absolute: the water must be more than this above an opening's sill to enter. 1e-9 s, absolute: the smallest hydrograph interval used as a divisor.
Checked by
tools/bimcraft-test/hazard-test.mjs runs floodRun, wallPush and windRun in Node.
Limits
No flow rates: water is assumed to equalize within one time step. The wind model is a mean-speed power law with a single gust factor and two pressure coefficients typed by the person; it is not a design check against a code.

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.

Every policy that takes this approach