Solver Spec — Model Card
Honest declaration of what the production fracture kernel actually does today. Each section names its provenance file(s) and its limits of validity. The scoreboard below tracks which Phase upgrades each row.
Today vs. roadmap
| Module | Today | Roadmap |
|---|---|---|
| Fracture mechanics | Cell-based Pseudo-3D, fixed height, LEFM tip + Charles power-law growth (Planar-3D Newton step + front-motion coupling landed @ Phase 4.5, beta — Sneddon-asymptote K_I inversion drives Charles V_n into the level-set tip propagator) + Phase 7 inc 1–5 tip-kernel upgrades, beta — opt-in Osher–Sethian Godunov upwind |∇φ| (`gradOperator: "godunov"` on `tipPropagationStep`), Garagash–Detournay viscous–toughness MK universal asymptote (`tipModel: "mk-universal"` on `frontMotionStep`, Dontsov–Peirce 2017 max-of-asymptotes blend of K-vertex Charles and M-vertex viscous V_M = (E'/μ')·(w/(β_m·s^{2/3}))^3), Sussman-Smereka-Osher 1994 level-set reinitialization (`reinitializeLevelSet` — fictitious-time march of φ_τ + sign(φ₀)·(|∇φ|−1) = 0 with smoothed sign S_ε(φ) = φ/√(φ²+ε²) and Engquist-Osher upwind branch select, optional narrow band) so repeated propagation steps stay near-SDF, AND auto-call reinit between front-motion steps in the live Newton loop via opt-in `reinit: { iterations, dtau, bandWidth }` on `frontMotionStep` (forwards verbatim to `reinitializeLevelSet`, surfaces `reinitMeanGradNorm` on the result, off by default so Phase 4.5 callers stay byte-identical); all four flags off by default so Phase 4.5 callers stay byte-identical + Phase 7 inc 15 — same `reinit: { iterations, dtau, bandWidth }` option now wired through the `kgdMovingTipRunner` MK moving-tip wrapper around `coupledNewtonStep` (1-D-extruded SDF φ(x, y) = activeHalfLenEnd − |x| fed verbatim to `reinitializeLevelSet`, surfaces `reinitMeanGradNorm` on each `KgdMovingTipDiagnostic`, drives the next step's front-conforming warm-start truncation via φ_reinit ≤ 0); on the 1-D KGD problem the reinit'd SDF zero-set coincides cell-for-cell with the legacy `|x|` truncation, so wellbore-cell widths are byte-identical to `mkTipFeedback: true` (rms ≈ 0.106 / 0.125 bun-vs-vitest), and reinit `iterations: 8` is now baked into the default `kgd-width` registry runner so the live-kernel rms drops from ≈0.1251 → ≈0.1063 vs the pre-Phase-7-inc-15 baseline (Δ ≈ 0.019 log-units, |∇φ| → 1.0005), AUTO-TIGHTENING the registry `toleranceLog` from 0.13 → 0.115 (≈0.0087 log-units of headroom, above the established ≥0.005 floor); the `runnerImpl: "newton-kernel"` override path keeps its own 0.13 ceiling because the fixed-tip Newton runner does NOT consume reinit; the wired option matches `frontMotionStep`'s API and gives 2-D extensions a drop-in front-tracking SDF + Phase 8 — fully-planar 3D propagation: `propagatePlanar3D` in `src/lib/planar3DPropagation.ts` extends `solveImplicitHeight` with a TRUE per-bench KGD-style aperture profile w_max(layer) = 2·max(0, p − σ_h_layer)·H/E′_layer (no E′_eff homogenisation), elliptic-prism cross-section A_xs = Σ (π/4)·w_max·t_contained, and leakoff-free volume-balance length L = q·t/(2·A_xs). Drift-guard `planar3DPropagation.test.ts` (8 tests: mass-balance, layered-pay aperture dominance, q-linear length, p-monotone height/width, containment exclusion, input validation) + benchmark suite `planar3DBenchmarks.ts` cross-checks per-bench Sneddon analytic + height-time growth (`planar3DBenchmarks.test.ts`, 4 tests). Comparator panel `Planar3DComparatorPanel` mounted at `/planar3d-validation` | All self-contained kernel items closed; remaining work is field validation against site DFITs and offset history (shared with proppant-transport row). Field-validation ingest path shipped @ Phase 7 inc 22 — `/projects/$p/workspaces/$w/field-validation` lets teams upload measured (t, P, T) gauge logs or (MD, P, T) gradient surveys and attach DFIT registry residuals + Pressure Advisor cross-checks; verdicts persist to `field_validation_campaigns` (team RLS) so kernel residuals can be challenged on real data without code changes |
| Fluid–solid coupling | Fully-coupled Newton-Raphson via `coupledNewtonStep` is the DEFAULT path for every live-kernel surface (DFIT registry's `kgd-width`/`pkn-length`/`carter-falloff` entries, all proppant-validation runners, KGD/PKN moving-tip wrappers); the legacy sequentially-explicit forward-Euler operator split remains reachable through the analytical-self runner registry entries kept as regression references — no caller path defaults to the explicit split anymore (Phase 4 promotion @ Phase 7 inc 16). Window-sweep harness `runDfitWindowSweep` (`src/lib/planar3d/dfitWindowSweep.ts`) drives the Newton path over arbitrary `[tStart, tEnd]` grids so future phases can chart rms-vs-window without retouching kernel defaults; A/B harness `runDfitAbCompare` (`dfitWindowSweep.ts`) gates `meshAdapt` / `tipBC` / Carter-injection-sizing toggles before promotion. | All self-contained Newton-coupling work landed: Carter validation grid broadened `[1, 2] s` → `[1, 100] s` @ Phase 7 inc 21 with auto-sized per-step injection feeding the kernel seed-floor. Opt-in `meshAdapt: "tip-refined"` and `tipBC: "kic-clamped"` knobs on `coupledNewtonStep` were sweep-audited @ Phase 7 inc 22b via `runDfitAbCompare` across all 3 registry entries on their tightened windows: `tip-refined` is a byte-identical no-op on `kgd-width` (rms 7.0e-2) and `pkn-length` (rms 1.33e-1) — strip cell count doubles 41 → 81 but the converged residual does not move — and is mildly worse on `carter-falloff` (6.9e-8 → 2.5e-7); `kic-clamped` is actively harmful on every entry (KGD 7.0e-2 → 1.07e-1 eats 80 % of headroom, PKN 1.33e-1 → 3.37e-1 overruns the 0.15 ceiling). Verdict: neither knob clears the ≥0.005 log-units headroom rule for ANY entry — keep both flags off-by-default; re-run the sweep only if a future kernel change inverts the residual signature. |
| Leak-off | Carter √t baseline + kernel-coupled PDL & dynamic filter-cake + two-phase α_s throttle (Planar-3D Newton step consumes Carter·(1+α·Δp)/(1+κ·√t)·(1−α_s/α_pack)^n as a per-cell sink @ Phase 5 inc 6, beta) + DFIT validation harness with /dfit-benchmarks dashboard now driving the live `coupledNewtonStep` Phase 3.5 kernel through ALL 3 entries (`runnerKind: 'live-kernel'`): kgd-width via `kgdMovingTipRunner.ts` (MK universal moving-tip wrapper + reinit i=8 SDF warm-start, rms≈0.106, tol AUTO-TIGHTENED 0.13 → 0.115 on [1, 2] s @ Phase 7 inc 13/15), pkn-length via `pknNewtonRunner.ts` (height-contained 1D-strip volume-balance, rms≈0.13 on [0.5, 1] s, tol AUTO-TIGHTENED 0.20 → 0.15 on [0.5, 1] s @ Phase 7 inc 7/21), AND carter-falloff via `carterFalloffNewtonRunner.ts` (per-step mass-balance recovery against the kernel's exact discrete encoding `2·C_L·(√tn − √to)/dt`, rms ≈ 7.2e-8 log-units on [1, 2] s, tol AUTO-TIGHTENED 0.05 → 1e-5 @ Phase 7 inc 14/22, gated by a dedicated merge-blocking step in `regression-gate.yml`) | All 3 DFIT entries `live-kernel` and sweep-tightened (KGD 0.115 on `[1, 2] s`, PKN 0.15 on `[0.5, 1] s`, Carter 1e-5 on `[1, 100] s` after the per-step injection-sizing helper landed in `carterFalloffNewtonRunner` @ Phase 7 inc 21). Next: KGD/PKN window-tightening via opt-in `coupledNewtonStep` `meshAdapt: "tip-refined"` + `tipBC: "kic-clamped"` (defaults preserve current rms; flip registry tolerances only after the A/B harness shows ≥0.005 log-units of headroom) |
| Proppant transport | Single-phase concentration advection (RZ settling, conv., slumping, MPM bedload, K-D rheology) + Eulerian-Granular two-phase scaffold with K-D μ_eff(α_s) coupling, PLA shear-induced migration + bridging/screen-out gate, hybrid Eulerian-Lagrangian particle tracker (bilinear-sampled bulk velocity + RZ-hindered Stokes slip + per-particle bridging gate, mass-conservative seed↔bin round-trip), AND kernel-side α_s feedback into the Carter leak-off sink @ Phase 5 inc 6, beta — full two-phase ↔ leak-off loop closed + Phase 8 — wellbore proppant cascade preview (`wellboreProppantPreviewDriver.ts` + `wellboreScalarTransport.ts`): 1-D first-order upwind scalar advection of clean-carrier ppa down the segmented string on the recovered face-velocity field, with multi-slug `runScheduleProppantPreview` consuming a `TreatmentStep[]` schedule (preflush · pad · slurry · postflush role bands) so surface-prescribed ppa(t) → bottom-cell ppa(t) drives the perf-arrival concentration fed into `proppantPlacement`. HONEST SCOPE (preview only, NOT field-validated): single-species scalar (no inter-species coupling), NO proppant-vs-fluid slip in the wellbore (carrier-tracking bulk velocity), NO bridging / screen-out gate, NO perf erosion or friction feedback from the transported solids, NO gel-load or temperature-dependent settling. Drift-guards: `wellboreProppantPreviewDriver.test.ts` (7) + `wellboreScalarTransport.test.ts` | Validation against site DFITs and offset history. Field-validation ingest path shipped @ Phase 7 inc 22 — `/projects/$p/workspaces/$w/field-validation` accepts measured downhole streams + Pressure Advisor cross-checks (team-shared via `field_validation_campaigns` RLS) so two-phase / leak-off behaviour can be challenged on real-site data without solver-code changes |
| Simulator backend | Internal kernel only — no external reservoir-sim bridge (ECLIPSE / tNavigator / INTERSECT / CMG). All physics live in `src/lib/planar3d/*` and `src/lib/depletion*`; the on-screen 'Coupled wellbore + reservoir run' label refers to the in-house sandbox, not a third-party solver front-end. | Optional one-way export of fracture geometry as static LGR for an external reservoir simulator (CSV / RESCUE-style); no two-way handshake with ECLIPSE/tNavigator/INTERSECT/CMG planned. |
| Coupling direction | Two-way *within the frac* (Newton-Raphson width↔pressure via `coupledNewtonStep`, default live-kernel path @ Phase 7 inc 16) **AND** two-way frac↔reservoir solved JOINTLY each timestep: opt-in `reservoirCoupling` field on `coupledNewtonStep` (Phase 9 inc 3, beta) substeps the in-house transient single-phase Darcy grid (`src/lib/reservoir/singlePhaseGrid.ts`, backward-Euler TPFA + SPD CG via `src/lib/linalg/cg.ts`) INSIDE the Newton outer loop — after each accepted iterate, the kernel pushes per-DDM-cell q_leak as withdrawal sources (m/s × mesh.area, sign-flipped to flowback) via `subStepReservoir`, re-samples p_pore at every footprint cell, and feeds it back as the per-cell PDL threshold so the next residual evaluation sees the live drawdown Δp = p_face − p_pore. Δt amortised across `maxIter` substeps so a converged Newton solve consumes the full outer Δt with self-consistent (w, p_frac, p_pore). The outer-loop driver `runFracReservoirCoupling` (Phase 9 inc 2) remains for callers that want the closed-form PKN/KGD path. **Phase 14 — 3-phase black-oil IMPES kernel** via `src/lib/reservoir/compositionalBlackOil3P.ts` lifts the reservoir physics from single-phase to oil/gas/water on the same voxel grid: sequentially-implicit IMPES (pressure solved implicitly with total-mobility-weighted transmissibilities + backward-Euler CG, saturations updated explicitly with upstream-weighted phase fluxes), Brooks-Corey Stone-I-lite 3-phase rel-perms (`relPerm`), Standing/Vasquez-Beggs oil PVT, water reference `B_w` + linear compressibility, and a pluggable `GasFvfCallback` gas FVF. **Energized-CO₂ gas bridge** via `withEnergizedCo2Gas` adapter wires the existing PR-EOS `energizedCo2Pvt` density engine into the gas-phase FVF so honest gas-condensate dropout and energized-CO₂ flowback solve on the same grid as the Phase 9 T-M-H stepper. Builder Fluid-model panel exposes a third '3-phase BO (oil/gas/water)' kind alongside the existing black-oil / compositional toggles (initial Sw / Sg / connate-Sw + 'Bridge gas FVF to PR-EOS energized CO₂' checkbox). Internal sandbox only — no ECLIPSE/tNavigator/INTERSECT bridge. **Phase 15 — Carreau-Yasuda + multi-mode rheology + shear-history crosslinker breakdown** via `src/lib/rheologyPreview.ts` (opt-in `aTransition` + Maxwell `modes[]` → `carreauYasudaMuCp` + `firstNormalStressPa` + `weissenbergNumber`; a=2 + no modes ≡ legacy Carreau byte-identical) and `src/lib/crosslinkerShearHistory.ts` (`accumulateShearHistory` integrates D(t) = 1 − exp(−∫ (γ̇/γ̇c)^m dt) over the pump schedule with optional first-order recovery τ between low-shear steps; 4-entry gel catalog: borate-guar / zirconate-cmhpg / hpg-cmhpg / slickwater-hvfr; `applyShearDamageToBreaker` shortens breaker T½ proportionally to (1 − D), floor 0.1×). Drift-guards: `rheologyPreviewCarreauYasuda.test.ts` (7) + `crosslinkerShearHistory.test.ts` (8); legacy `rheologyPreviewHb.test.ts` (9) still green. | Promote `reservoirCoupling` from beta to default-on once a DFIT-registry sweep demonstrates ≥0.005 log-units of rms headroom on `kgd-width`/`pkn-length`/`carter-falloff`. Then wire the Phase 14 3-phase IMPES kernel (`compositionalBlackOil3P.ts`) into `coupledNewtonStep`'s `reservoirCoupling` so the same frac↔reservoir loop runs against oil/gas/water phases (currently the substepper still calls the Phase 9 single-phase `subStepReservoir`). |
| Wellbore flow | Steady-state hydrostatic + frictional pressure drop (`slurryHydrostatic.ts`: 0.052·ρ_eff·TVD + Σ ΔP_friction, with slurry+proppant ρ_eff cascade). `CouplingPanel` adds a damped fixed-point gas-yield correction for ballistic stages. **Phase 8 inc 2 — opt-in transient single-phase wellbore** via `transientWellbore.ts` (1-D segmented pipe, segregated implicit Euler on mass + momentum + energy: pressure tridiag from Thomas, velocity from momentum closure v_f = (B_f − Δp_f)/A_f, separate temperature tridiag with upwind convection + U-value heat loss; Swamee-Jain Darcy-Weisbach friction). Wired into `coupledNewtonStep` as `wellboreCoupling`; surfaces `wellboreState` / `bottomholePressurePa` / `surfaceRateM3PerS` / `wellboreBottomRateM3PerS` / `wellborePicardIterations` on the result. **Phase 8 inc 3 — two-way wellbore-storage feedback** via the same hook's `twoWay: true` flag: after each accepted Newton iter the wellbore's bottom-face volumetric rate (`v[N]·A_N`) is under-relaxed (α=0.5) into the kernel injection RHS via `pointSourceRhs`, so wellbore compressibility along the string attenuates the prescribed surface pump rate seen by the fracture during transients. Default `twoWay: false` (one-way kernel→wellbore, byte-identical to inc 2). **Phase 8 inc 1b — opt-in drift-flux multiphase preview** via the same step's `multiphase: { gasSuperficialMpsPerCell, phaseProps, closures? }` field. Pure Zuber-Findlay closures in `src/lib/wellbore/driftFlux.ts` (`voidFractionZuberFindlay` α = j_g/(C₀·j_m + V_d), `mixtureDensity` ρ_m = α·ρ_g+(1−α)·ρ_l, `mixtureViscosity` μ_m = α·μ_g+(1−α)·μ_l, `phaseVelocitiesFromSuperficial` slip) drive per-iter α / ρ_m / μ_m from `j_l ≈ |v_avg|`; cell density and face friction substitute the mixture in-place; `WellboreStepResult` surfaces REQUIRED `voidFractionPerCell` + `mixtureDensityKgPerM3PerCell` (null when off). **Flow-pattern map regime classifier** shipped via `classifyFlowPattern` + `flowPatternHistogram` (`driftFlux.ts`): per-cell α → bubbly/slug/churn/annular bucket, surfaced as a stacked-bar histogram inside `MultiphaseSettingsCard`. **Phase 8 inc 1c — gas-EOS-coupled c_t** via the same multiphase block's opt-in `gasEos: { zFactor?, dZdpInvPa?, molarMassKgPerMol?, tempK?, gasDensityFromEos? }` field: `gasIsothermalCompressibility` (ideal `c_g = 1/p`, real-gas Standing-Katz form) + `gasDensityIdeal` (`ρ_g = p·M/(Z·R·T)`) + `mixtureStorageCoefficient` (`(ρ·c_t)_m = α·ρ_g·c_g + (1−α)·ρ_l·c_l`) replace the liquid-only `ρ₀·c_l`. **Phase 8 inc 1d — baked-in Standing-Katz (DAK) Z(p, T)** via the gasEos block's `useStandingKatz: true` flag + optional `standingKatzOptions: { gasGravity?, h2sMolFraction?, co2MolFraction?, pseudoCriticalPressurePa?, pseudoCriticalTempK? }` (pure `src/lib/wellbore/standingKatz.ts` — Dranchuk-Abou-Kassem 1975 reduced-density Newton iteration + Sutton 1985 pseudo-criticals + Wichert-Aziz 1972 sour-gas correction). When active, each Picard iter re-derives `zFactor` and `dZdpInvPa` per cell from local p[i] and T (defaulting to T[i]), overriding any scalar `zFactor`/`dZdpInvPa` overrides; callers no longer need to supply Z at high-pressure / sour-gas conditions. Collapses byte-identically to the scalar-Z path in the low-pressure (ideal-gas) limit. IMPLEMENTED: hydrostatic head ↓ under gas-lift, slip-corrected friction, gas-slug convergence < 60 Picard iters with α∈(0,1) and ρ_m bounded by [ρ_g, ρ_l], single-phase byte-collapse at j_g = 0, four-regime flow-pattern map, full Builder UI (phaseProps + j_g + C₀ + V_d + regime histogram), gas-EOS-coupled mass-storage (ideal + real-gas Z-factor pathway), baked-in Standing-Katz/DAK Z(p, T) + Sutton + Wichert-Aziz sour-gas correction. DEFERRED: OLGA-class transient multiphase (out of scope). Drift-guards: `transientWellbore.test.ts` (14 — incl. 3 inc-1c + 2 inc-1d Standing-Katz wired-path tests) + `driftFlux.test.ts` (25 — incl. 7 inc-1c gas-EOS helper tests + 4 regime-classifier tests) + `standingKatz.test.ts` (10 — DAK envelope + Sutton + Wichert-Aziz + monotone branches) + `coupledStepWellbore.test.ts` (2) + `coupledStepWellboreTwoWay.test.ts` (3). | Drift-flux scaffold gas-EOS-coupled c_t shipped @ Phase 8 inc 1c; flow-pattern map shipped @ Phase 8 inc 1b; baked-in Standing-Katz (DAK) Z(p, T) + Sutton + Wichert-Aziz sour-gas correction shipped @ Phase 8 inc 1d (`standingKatz.ts`, `useStandingKatz: true` opt-in). All self-contained gates on this row are closed. OLGA-class transient multiphase solver remains out of scope. |
| Parent-child interference | Analytical first-order screening via `parentChildInterference.ts` — log-radial Δp depletion kernel summed over producing parents + Eaton/poroelastic Δσ_h = α·(1−2ν)/(1−ν)·Δp + per-stage frac-half-length asymmetry from the lateral-normal Δp gradient + bashing-risk chip (low/watch/high). Workspace route `/projects/$p/workspaces/$w/parent-child` ships the inputs, plan-view, per-stage table, and CSV export. Pure module, no coupled simulator dependency. | Promote to coupled multi-stage transient depletion solver (re-use Phase 9 `reservoirCoupling` substepper) so Δp evolves with the child's own production timeline; ingest real microseismic catalogs to constrain depletion radius per parent before recomputing risk. |
| Poroelastic depletion (full tensor) | Geertsma-style 2D poroelastic kernel via `poroelasticDepletion.ts` — each producing parent treated as a circular drained patch (radius = drainageRadiusFt) with stress-path coefficient A = α·(1−2ν)/(1−ν); per-stage Δσ_xx/Δσ_yy/Δσ_xy superposed across parents and added to the Cartesian far-field tensor (built from σh_far, σH_far, SHmax azimuth). Eigen-decomposition of the 2×2 horizontal tensor returns rotated principal stresses (σh_total, σH_total) AND the rotated SHmax azimuth (frac reorientation toward depleted zones). Severity classifier `classifyAzimuthShift` (≤5° info / ≤15° watch / >15° critical) drives the workspace summary chip; per-stage Δσ_h, Δσ_H, and azimuth-shift columns ship in the table + CSV export. Δσ_v ≈ 0 in this model (laterally-extensive depletion under uniaxial-strain BC). **3D Mindlin / multi-bench layered extension** via `mindlin3D.ts` (`analyzeMindlinPoroelastic`) — each parent declares a `benchIndex` into a shared `BenchLayer[]` (top TVD · thickness · per-bench ν, α); child stage TVD drives a Mindlin point-source vertical-decay kernel `f(Δz, a, h) = (a/√(a²+Δz²))^(3/2) · min(1, h/a)` that attenuates the 2-D polar Δσ AND uses per-bench moduli to recompute A_p per parent. Same `PoroelasticResult` shape so the existing UI / CSV are drop-in compatible; collapses byte-identically to the 2-D engine when no layers are configured. Gated by `downhole.parentChild.mindlin.v1:{p}:{w}` (off by default). **Thermo-poroelastic coupling** via the same module — per-`ThermalCoolingPatch` (center · radius_T · ΔT) cold-water injection contribution superposed on top of the depletion kernel using the same Lamé / pressurized-hole math (`patchPolar`) with coefficient B = α_T·E/(2·(1−ν)); enabled when `youngsModulusPsi` AND `thermalExpansion1F` are both supplied on the reservoir (otherwise B = 0 and patches are silently ignored, so legacy callers / tests are byte-identical). Thermal-only Cartesian Δ-tensor surfaced on each `PoroelasticStageResult` (`dSigmaXxThermalPsi` etc.) for downstream attribution; Mindlin engine wires the same kernel. **Runtime stress-coupled propagation** — `computeChildFractureGeometry`'s optional `runtimeCoupling.evaluateSigmaHMinAt` callback re-queries the same kernel at each wing tip so per-wing half-length responds live to the local σh field (L_side = L · (1 + k_prop · (σh_ref − σh_side)/σh_ref), clamped to [0.25 L, 4 L]). Wired in the `/parent-child` route via a checkbox + k_prop slider next to the bend-coupling field; CSV adds 5 cols (`wing_half_length_a/b_ft`, `wing_asymmetry_pct`, `sigma_h_tip_a/b_psi`) — em-dash when toggle is off so legacy callers stay byte-identical. Pure module, no coupled simulator dependency. | Self-contained kernel work closed: 2-D Geertsma → 3D Mindlin layered → thermo-poroelastic → runtime stress-coupled propagation all shipped. Remaining work is field validation against parent-pad microseismic + offset DFITs (shared with `parent-child-interference` row). |
| Thermo-mechanical-hydraulic (3-D voxel) | Phase 9 T-M-H 3-D voxel stepper via `src/lib/reservoir/thermoPoroGrid3D.ts` — segregated implicit-Euler timestepping on a regular voxel grid that solves a TPFA conduction temperature equation (SPD CG via `src/lib/linalg/cg.ts`) with prescribed-T Dirichlet boundary conditions AND volumetric heat sources AND **opt-in convective transport** via `enableConvection: true` on the step input (lagged Darcy face fluxes drive upwind T·v·∇T fluxes on the temperature tridiag; off by default so legacy callers stay byte-identical), then pipes the per-cell ΔT into the existing `stepReservoir` single-phase pressure solve as a thermal-pressure source `q_T = β · ΔT · (φ·c_t·V_b / Δt)` (β = thermalPressureCouplingPsiPerF, configurable per-grid), and finally accumulates a Geertsma-style minimum-horizontal-stress update `Δσ_h = α_B·(1−2ν)/(1−ν)·ΔP + E·α_T/(1−ν)·ΔT` so cooling and depletion both unload σ_h consistently with the existing 2-D poroelastic kernel in §10. **Bench-aware E'/α** via `src/lib/reservoir/benchLayeredProps.ts` (`expandBenchLayersToCells` + `normalizeBenchProperty` + `isBenchLayered`) — callers can declare per-K-layer `{ perBench: [...] }` for `youngsModulusPsi`, `poissonRatio`, `thermalExpansionPerF`, `biot`, `bulkHeatCapacityBtuPerFt3F`, or `bulkThermalConductivityBtuPerDayFtF`; the helper broadcasts each bench scalar across (i, j) at construction time so `createThermoPoroState` consumes the existing per-voxel `number[]` shape unchanged (stiffer benches yield proportionally larger |Δσ_h| under identical ΔT). Builder UI surfaced via `ThermoPoroInputsCard` on the Solver panel (master enable switch, dt, initial T, β, biot/ν/E/α_T, bulk heat capacity + conductivity, dynamic thermal-BC and volumetric-source tables, all clamped to physical ranges and persisted through `thermoPoroBuilderStore`). Results surfaced via `ThermoPoroResultsCard` in the simulation Diagnostics tab — synthetic 16×16×4 12-step preview run by `thermoPoroResultsPreview.ts` produces max |ΔT|, max |ΔP|, and max |Δσ_h| summary tiles plus mid-k diverging-ramp SVG heatmaps for T and cumulative Δσ_h. Drift-guards: `thermoPoroGrid3D.test.ts` (11 — β=0 decouples fields, β>0 cooling drops cell P, 4-fold symmetry on homogeneous blocks, Biot/Hooke closed-form sanity, cooling-induced unloading, isotropic tensor identity, step-result maxAbs arrays), `benchLayeredProps.test.ts` (8 — layer→voxel broadcast, length/finite guards, scalar/array passthrough, `{perBench}` E feeds `deltaSigmaHFromCoupling` proportionally), `thermoPoroBuilderStore.test.ts` (5), `thermoPoroResultsPreview.test.ts` (6). Pure module, no external simulator dependency. | All self-contained kernel items closed (convective transport + bench-aware E'/α both shipped). Remaining: promote the 3-D T-M-H stepper from preview surface to in-loop coupling inside `coupledNewtonStep` so the same per-iter ΔT / ΔP / Δσ_h pushed into the in-house Darcy substepper (§7 `reservoirCoupling`) also feeds the per-cell PDL threshold of the fracture kernel, and bridge the voxel-grid σ_h field back into the parent-child §10 SHmax-rotation kernel so live cooling near offset wells reorients the child-fracture geometry alongside depletion. Field-validation gating shares the `/projects/$p/workspaces/$w/field-validation` ingest path (DTS gradient surveys + DFIT residuals). |
| Non-planar 3D fracture solver | Fully-3D non-planar fracture geometry via `src/lib/nonPlanar3D/{surfaceMesh,ddm3D,tipKinking,nfBranching,pipeline,mtsPropagation,stressFieldSampler}.ts` — quad surface mesh with per-face propagation history, 3-D DDM influence operator (Okada-style half-space), Erdogan-Sih maximum-hoop-stress tip-kinking criterion with operator-tunable kink-angle clamp, and natural-fracture branching gated by Renshaw-Pollard / Gu-Weng crossing criteria. M1 (surface mesh + DDM) + M2 (Erdogan-Sih tip kinking) + M3 (NF branching) + M4 per-vertex E′/ν heterogeneity (`MeshVertex.ePrimePsi` / `poissonRatio` → opt-in via `perVertex: true` on `runNonPlanar3DPipeline`; per-face E′ = mean of 3 vertex E′_v with E′_v = E_v/(1−ν_v²) when ν_v defined, scalar `ePrimePsi` fallback per vertex; layered config wins when both supplied; omitted ≡ false ≡ byte-identical legacy run) + M4 out-of-plane tilt for sub-vertical fractures (per-edge / per-step tilt field plumbed through `advanceTipRow` — length-preserving `cos τ` in-plane × `sin τ` +y; collapses byte-for-byte when omitted) + shared-vertex weld for T-junction reconvergence (`tipReconvergenceWeld: { enabled, tolFt? }` re-uses `weldNearbyVertices` so any new tip vertex landing within `tolFt` of a parent vertex folds onto it; per-step `tipWeldedVertexCount` + `tipWeldedRemovedFaceCount` surfaced on every `TimeStepRecord`) + M2 stress-driven curvature loop (`stressDrivenCurvature: { enabled, evaluator, maxCurvatureRadPerFt? }` — live Δσ tensor evaluator feeds per-edge σ_Hmax azimuth via `principalDirectionsFromTensor2D`, capped by `curvatureLimitedKinkDeg`; omitted ≡ disabled ≡ isotropic σ collapses byte-identically to legacy baseline) + multi-stage orchestrator `runMultiStageNonPlanar3DPipeline` (`src/lib/nonPlanar3D/multiStagePipeline.ts`) drives N stages in lockstep and snapshots cross-stage frac–frac welds via `mergeIntersectingStages` under a `crossStageMerge: { tolFt, cadenceSteps?, minStageGap?, debug? }` knob — UI surfaces (`NonPlanar3DCrossStageMergeCard` on `/non-planar-3d`, `downhole.nonPlanar3D.crossStageMerge.v1` store) and a public SDK endpoint `POST /api/public/sdk/v1/non-planar-3d/multi-stage` accept the same knobs and return `crossStageMergeHistory` (per-step welded-pair stats + optional combined-mesh debug snapshots); omitted ≡ N independent single-stage runs (byte-identical histories) + pipeline driver + three.js viewer at `/non-planar-3d` (sliders maxKinkDeg / advanceFt / steps / SHmax + criterion select; viridis face-of-birth tint) + per-simulation Results tab `NonPlanar3DResultsCard`. Validation toggle on `/planar3d-validation` (`Switch to non-planar 3D viewer →`) gives engineers a one-click jump between the planar Sneddon comparator and the non-planar viewer. Drift-guards: `surfaceMesh.test.ts` (4) + `ddm3D.test.ts` (4) + `tipKinking.test.ts` (17 incl. 2 tilt unit tests) + `nfBranching.test.ts` (9) + `pipeline.test.ts` (15 — 4 baseline + 3 non-planar retention + 4 out-of-plane tilt + 4 T-junction reconvergence weld) + `mtsPropagation.test.ts` (9) + `stressFieldSampler.test.ts` (4) + `pipeline.curvature.test.ts` (4 — parity collapse · anisotropy drives kink · zero-curvature flat · curvature cap honored) + `multiStagePipeline.test.ts` (8 — collapse-to-N · cadence skip · tight pair welds · separated pair no weld · minStageGap default · debug arrays gating · empty step) + `nonPlanar3DCrossStageMergeStore.test.ts` (6) + `ddm3DPerVertex.test.ts` (6) + `pipeline.perVertex.test.ts` (4) = 90 tests. | Promote the kernel from per-simulation Results-tab preview into the in-loop coupling inside `coupledNewtonStep`'s outer loop so each Newton iter re-uses the M1+M2+M3+M4 mesh state (including welded T-junctions) as the live fracture footprint for the planar-3D pressure/opening substep, and bridge per-face σ_Hmax telemetry into the live-pumping observability surface. Field-validation gating shares the `/projects/$p/workspaces/$w/field-validation` ingest path (microseismic catalogs + offset DFITs). |
| UFM / Fully-3D DFN | Planar-3D (`propagatePlanar3D`) + analytical complexity volumetrics (swept-ellipse capture, Sneddon per-bench aperture). UFM-style discrete-fracture-network (DFN) intersection / branching, hydraulic-natural-fracture (HF↔NF) stress-shadowed crossing criteria (Renshaw–Pollard, Gu–Weng), and fully-3D XFEM tip propagation are NOT solved by the kernel — complexity is captured volumetrically and via parent-child stress shadowing only. | Out of scope for this codebase: a true UFM/Fully-3D DFN solver requires a dedicated discrete-fracture topology engine + HF↔NF crossing criteria + 3D XFEM tip propagation. Field-driven complexity stays captured via the analytical volumetrics + parent-child stress-shadow path; promote only if a third-party DFN engine is bridged in (no plan today). |
Analytical → DDM-3D → Fully coupled
Each row above sits on one rung of the ladder. PKN/KGD/Radial closed-forms are analytical 1-D net-pressure surrogates — useful for fast screening but not a substitute for a multi-cluster, stress-shadowed kernel. The non-planar 3-D DDM path lives at the right end of this ladder.
Tip-kernel knobs
Osher–Sethian monotone upwind gradient on tipPropagationStep.
Dontsov–Peirce 2017 max-of-asymptotes blend of K-vertex Charles and M-vertex viscous V_M.
Sussman-Smereka-Osher 1994 — auto-call between front-motion steps so |∇φ| ≈ 1 stays put. Off by default.
Cell-based Pseudo-3D
The production kernel advances per-cluster scalar state (a, w, p_net) on a fixed-height fracture. Stress intensity K_I = p_net · √(π · a) drives a Charles power-law tip growth law. A boundary-element-method (BEM) multi-fracture stress-shadow rule flips a cluster off when neighbour-induced back-stress exceeds the configured threshold. Height is held constant per stage. PKN, KGD, and Savitski–Detournay radial closed-forms exist but are reference benchmarks only — they do not propagate the production sim.
src/lib/coupledStageSim.ts— kernelsrc/lib/fractureInitiation.ts— BEM shadow + criterionsrc/lib/analyticalFractureModels.ts— PKN/KGD/radial benchmarks
- No height growth in the kernel.
- Not a planar-3D code — single scalar half-length per cluster.
- Tip asymptote regularised; near-tip M-MK-K classifier lives in
physics/panels but is not enforced inside the propagator.
Sequentially explicit forward-Euler
Per timestep Δt the kernel runs a fixed operator-split sequence:
for each Δt: 1. BEM shadow → per-cluster Δσ_i 2. flip status → cluster i deactivates if Δσ_i > flipThreshold 3. LEFM propagation → da/dt = C · (K_I/K_IC − 1)^β · Δt 4. net-pressure → dp_net/dt = injection − leak-off + back-stress 5. width → w ≈ p_net · h / E'
There is no fully-implicit Newton on the coupled width–pressure system. The Newton iteration cap and pressure tolerance on the Builder Solver panel apply to the reservoir / depletion solver, not to the fracture kernel — implicit coupling lands at Phase 3.
src/lib/coupledStageSim.ts— Δt loopsrc/components/jobmanager/builder/SolverPanel.tsx— reservoir-side knobs
- Stiffness in high-modulus / low-leak-off regimes can require very small Δt.
- No global energy / mass residual check inside the kernel loop.
Carter baseline; PDL + filter-cake decoupled
The kernel uses a constant Carter-style pressure-decay term (leakoffPsiPerMin). Pressure-dependent leak-off (Barree–Conway, C_L′ = C_L0·(1 + α·Δp)^β) and the static filter-cake series-permeability model exist as standalone helpers and feed reporting / skin / DFIT analysis, but are not coupled into the kernel pressure update today. That coupling is Phase 4.
src/lib/coupledStageSim.ts— kernelleakoffPsiPerMinsrc/lib/analyticalFractureModels.ts—barreeConwayCurvesrc/lib/filtercakeAndSkin.ts— static cake + skin
- No dynamic cake build-up / erosion in the kernel.
- No spurt-loss treatment.
- No pore-pressure feedback per element.
Single-phase concentration advection
The transport stack is a single Eulerian phase with concentration advection, not a two-phase Eulerian-Granular formulation. Closures: Stokes / Newton single-particle settling with Schiller–Naumann drag, Richardson–Zaki hindered correction, Hele-Shaw Rayleigh gravitational-convection cells, BCRE-style angle-of-repose slumping, Meyer-Peter-Müller bedload + dune migration, Krieger–Dougherty slurry rheology, w/d_p bridging cutoff, and Shields-based washout. Wall-effect friction enters via slot rheology; there is no explicit wall-drag closure beyond that. Eulerian-Granular two-phase modelling is Phase 5.
src/lib/proppantTransport.ts·proppantTransportAdvanced.tssrc/lib/proppantPhysics.ts·gravitationalConvection.tssrc/lib/bedSlumping.ts
- No Eulerian-Granular two-phase momentum equation.
- No inertial particle migration.
- No explicit Bagnold-style wall-drag closure.
Module summary
| Module | Unknowns | Coupling | Time integrator | File |
|---|---|---|---|---|
| Fracture kernel | a, w, p_net per cluster | Sequentially explicit | Forward-Euler, fixed Δt | coupledStageSim.ts |
| BEM stress shadow | Δσ per neighbour pair | Direct sum (no matrix) | Per Δt re-evaluation | fractureInitiation.ts |
| Reservoir / depletion | p, S per cell | Newton (damped) | Adaptive Δt, tol psi/sat | SolverPanel.tsx |
| Proppant transport | c, bed height | Single-phase advection | Explicit substepping | proppantTransport.ts |
Phase 1 — Newton primitives (shipped)
Reusable solver foundation now lives in src/lib/solver/: CSR sparse assembly + restarted GMRES with ILU(0) preconditioning (sparseMatrix.ts), a damped Newton driver with Armijo line search (newton.ts), and a PI-controlled adaptive Δt (timeStepController.ts). Wired through coupledStageSim.ts as the smallest first user (1-D net-pressure update). Drift-guards: solver/__tests__/sparseMatrix.test.ts, newton.test.ts, benchmarks.test.ts (PKN, KGD, Savitski–Detournay radial within 2 % across 3 time decades).
Phase 2 — Planar-3D geometry kernel (shipped, beta)
Geometry-only Planar-3D foundation lives in src/lib/planar3d/: structured rectangular mesh (mesh.ts), Crouch–Starfield 1D-array DDM elasticity kernel (ddm.ts, Sneddon-validated <5 % @ N=80), and an implicit level-set tip propagation step (tipPropagation.ts). Drift-guards: 6 mesh + 5 DDM Sneddon + 3 KGD scaling + 7 level-set tests. Visualizer at /planar3d-debug. Coupling to the live solver lands at Phase 3.
Phase 3 — Coupled width–pressure baseline (shipped, beta)
Parallel-plate Reynolds lubrication operator (lubrication.ts, harmonic-mean cubic-law transmissibility) and a Picard-on-mobility coupled width–pressure step (coupledStep.ts) close the DDM ↔ flow loop on the planar mesh. Drift-guards: 7 lubrication + 4 coupled-step tests (1D KGD strip — symmetric, peaked at injection, finite pressures). Phase 3.5 swaps Picard for full Newton on the joint residual.
Phase 3.5 — Fully-coupled Newton-Raphson (shipped, beta)
newtonStep.ts assembles the joint width–pressure residual F = [C·w − p; L(w)·p − q + area·Δw/Δt] and its analytic Jacobian (including the mobility-derivative cross-block ∂L/∂w · p), then drives it to tolerance with damped Newton (Armijo backtracking) over ILU(0)+GMRES on the inner linear solve. Drift-guard: 6 tests (input validation, KGD-strip convergence to ‖F‖∞ < 1e-7, symmetric peaked profile, volume balance within 5 %, and fewer iterations than the Phase 3 Picard baseline).
Phase 4 inc. 1 — Kernel-coupled Carter leak-off (shipped, beta)
The Newton step now consumes Carter C_L · √(t − t_arr) as a per-cell volumetric sink in the mass residual — pure RHS contribution (Jacobian unchanged), with optional per-element arrival times to model the moving front. Drift-guard: 5 tests (CL = 0 no-op, exact volume balance against the Carter integral, monotone retention vs CL, and input validation). Phase 4 increment 2 lands kernel-coupled PDL + dynamic filter-cake; Phase 4.5 wires front motion to the implicit level-set tip propagation kernel.
Phase 4 inc. 2 — PDL + dynamic filter-cake (shipped, beta)
Effective leak-off coefficient is now C_L_eff = C_L · (1 + α_PDL · max(0, p − p*)) / (1 + κ_cake · √(t − t_arr)), evaluated semi-implicitly at each Newton iterate (PDL on the current pressure, cake on t_now). Both terms remain pure RHS, so the Jacobian is unchanged and exact volume balance is preserved. Drift-guard: 5 tests (α=κ=0 no-op vs Phase 4 inc 1, PDL amplifies leak-off, cake dampens it, monotone in α, and volume balance with both knobs on). Phase 4.5 wires front motion to the implicit level-set tip propagation kernel.
Phase 4.5 — Front-motion coupling (shipped, beta)
frontMotion.ts closes the loop: after each Newton step, the converged width is sampled at the tip band to invert the Sneddon LEFM asymptote K_I = w · E' · π / (8 · √s); the Charles sub-critical law V = V0 · (K_I / K_Ic)^n drives the normal tip velocity into tipPropagationStep, which advances φ over Δt and emits a fresh active-cell mask for the next step's DDM array. Pure RHS coupling — the Newton residual is unaware of φ within a step, so step-by-step volume balance is preserved exactly. Drift-guard: 11 tests (asymptote inversion, Charles scaling, validation, sub-critical no-op, monotone outward growth, active mask consistency, and exponent monotonicity).
Phase 5 inc. 1 — Eulerian–Granular two-phase scaffold (shipped, beta)
eulerianGranular.ts ships the two-phase closures and a 1D conservative upwind advector for α_s. Slip velocity follows Stokes·Richardson–Zaki v_slip = v_stokes · (1 − α_s)^(n − 1) (clamped to 0 at α_max), and the upwind operator preserves total mass exactly under no-flux BCs. Drift-guard: 12 tests (Stokes d²/μ scaling, RZ monotonicity & α_max clamp, no-flux mass conservation to 1e-12, pulse-translation centroid, [0,1] clamp, CFL reporting). Phase 5 inc 2 plumbs the 2D bulk velocity from the Newton solve into the advector and lifts it onto the planar mesh.
Phase 5 inc. 2 — 2D bulk velocity → two-phase advector (shipped, beta)
eulerianGranular2D.ts reconstructs cell-centred bulk velocity from the converged Newton (w, p) field via the same cubic-law transmissibility used by lubrication.ts, adds the Stokes·RZ slip along the local gravity direction to produce the solids velocity, and advances α_s on the planar mesh with a 2D conservative upwind operator. No-flux boundaries preserve total α_s mass exactly step-by-step. Drift-guard: 8 tests (length / viscosity validation, uniform-pressure → zero velocity, ∂p/∂x < 0 drives u_x > 0, slip adds along gravity, 2D no-flux mass conservation to 1e-12, gravity-driven centroid drop, [0,1] clamp + CFL reporting). Phase 5 inc 3 modulates μ_eff by α_s (K-D rheology) inside the Newton step for two-way coupling.
Phase 5 inc. 3 — K-D rheology, two-way α_s ↔ μ_eff coupling (shipped, beta)
krieger.ts closes the loop between solids concentration and bulk flow. Per-cell viscosity follows Krieger–Dougherty μ_eff(α_s) = μ_f · (1 − α_s/α_max)^(−[η]·α_max) with [η] = 2.5 (Einstein hard-spheres) and α_max ∈ [0.55, 0.74] (random-loose to random-close packing). reconstructBulkVelocityKD drops the μ_eff field into the same cubic-law harmonic-mean face transmissibility used by the lubrication operator, so dense regions self-throttle without breaking the Newton volume balance. Drift-guard: 11 tests (Einstein limit at small α, monotone growth, α_max cap, parity with the constant-μ reconstructor at α ≡ 0, dense-vs-dilute flow-magnitude ordering, and full length / viscosity validation). Phase 5 inc 4 adds migration-induced settling and bridging / screen-out closures.
Phase 5 inc. 4 — PLA shear-induced migration + bridging / screen-out gate (shipped, beta)
migrationBridging.ts closes two more proppant-transport gaps. Phillips-Leighton-Acrivos shear-induced migration drives solids down gradients of shear-rate and viscosity j_mig = − D_c·α²·∇γ̇ − D_μ·γ̇·α²·∇ln μ_eff with empirical Kc = 0.43 and Kμ = 0.65, using the planar slot-flow shear proxy γ̇ ≈ 6·|u_bulk|/w. A bridging / screen-out gate trips when the local d_p / w ≥ 1/3 (Daneshy rule of thumb) AND α_s approaches α_bridge: a sigmoid in α_s smoothly multiplies the solids transport velocity by [0, 1] and reports a per-cell bridged mask plus a global screenedOut flag the Newton step can use to pause injection. Drift-guard: 10 tests (γ̇ formula, gradient-driven migration direction, no-gradient null test, gate inactive far below R_bridge, fully gates near α_bridge, heterogeneous-w cell-by-cell flagging, monotonicity in α_s, full input validation). Phase 5 inc 5 lifts to a hybrid Eulerian-Lagrangian particle tracker for individual proppant grains.
Phase 5 inc. 5 — Hybrid Eulerian-Lagrangian particle tracker (shipped, beta)
lagrangianParticles.ts overlays individual proppant grains on top of the inc 2/3/4 Eulerian field. Each marker carries (x, y, v_x, v_y, d_p, weight) and is updated every step by bilinearly sampling the cell-centred bulk velocity, adding a Richardson-Zaki-hindered Stokes slip in the gravity direction, and multiplying through the inc 4 bridging-gate field — particles in screened cells freeze.seedParticlesFromAlpha stratifies markers across cells with α_s > floor and assigns weights that exactly reproduce the cell solids volume; binParticlesOntoMeshprojects the cloud back to a Eulerian α_s field for hand-off into the Newton step. No-flux BCs reflect mass-conservatively; outflow deactivates exiting particles. Drift-guard: 14 tests (seed validation, total-volume conservation, bilinear centre + midpoint, uniform-bulk translation, gravity-driven settling, no-flux reflection, outflow deactivation, bridging-gate freeze, seed→bin round-trip, inactive exclusion, [0, 1] clamp, full input validation). Phase 5 inc 6 closes the leak-off ↔ two-phase loop.
Phase 5 inc. 6 — Two-phase ↔ Carter leak-off coupling (shipped, beta)
leakoffAlphaSCoupling.ts closes the loop between the inc 1–5 Eulerian-Granular α_s field and the Phase 4 inc 2 Carter sink. The kernel now multiplies the per-cell effective Carter coefficient by m(α_s) = (1 − α_s/α_pack)^n_throttle with α_pack = 0.55 and n_throttle = 2 (Kozeny–Carman-style). As solids pack against the fracture face the cake permeability collapses — at α_s → α_pack leak-off vanishes even when the √t cake term hasn't caught up. The throttle composes multiplicatively with the existing PDL × √t cake multipliers so the three effects never double-count.compositeLeakoffCoefficient is the convenience wrapper callers can use to inspect the final per-cell C_L_eff outside the Newton step. Drift-guard: 13 tests (zero-α pass-through, full-screening at α_pack, half-pack analytic value, monotonicity in α_s, exponent ordering, composition with PDL/cake, length and positivity validation). All four scoreboard rows are now active.
Phase 6 inc. 1 — DFIT validation harness (shipped, beta)
dfitBenchmarks.ts packages three closed-form analytical references the scoreboard claims to honour, plus a generic log-RMS comparator. Benchmarks: KGD constant-rate wellbore width w_w(t) = 2.36·(μq²/E')^(1/6)·t^(1/3) (Geertsma & de Klerk 1969), PKN constant-rate half-length L(t) = 0.68·(E'Q³/μh⁴)^(1/5)·t^(4/5) (Nordgren 1972), and Carter √t falloff q_leak(t) = 2·C_L/√(t − t_arr) (Carter 1957). compareToBenchmark returns RMS and max |log10(candidate / reference)| with a configurable pass threshold (default 0.05 ≈ 12% RMS). Drift-guard: 18 tests (scaling exponents, textbook prefactors, bias-detection at ±5/20/50%, time-grid mismatch + tolerance + length validation, catalog ordering). Phase 6 inc 2 wires these into a CI gate.
Phase 6 inc. 2 — DFIT benchmark CI gate (shipped, beta)
dfitBenchmarkRunner.ts ships a registry of the three Phase 6 inc 1 benchmarks pinned to canonical inputs (E' = 25 GPa, μ = 0.1 Pa·s, h = 30 m, C_L = 1e-4 m/s) and an 11-point log-spaced grid (1 → 1024 s). runDfitBenchmarkSuite() produces a DfitBenchmarkReport with per-benchmark BenchmarkComparison + an allPassed flag. The CI gate dfitBenchmarkRunner.ci.test.ts fails the build whenever any benchmark drifts past its per-entry log-tolerance (KGD 0.115, PKN 0.15, Carter sweep-tightened to 1e-5 @ Phase 7 inc 22 — surfaced as its own merge-blocking step in regression-gate.yml) — kernel solvers can be wired in one at a time by swapping the runner field on each registry entry. formatDfitBenchmarkReport emits a one-line per-benchmark summary so failures are actionable from CI logs alone. Drift-guard: 9 tests (registry order + locked inputs + tolerance ceiling, self-runner pass, 50% bias detection, ISO timestamp, formatter shape) + the CI gate itself.
Phase 6 inc. 3 — Internal /dfit-benchmarks dashboard (shipped, beta)
Engineer-facing live view of the Phase 6 inc 2 CI gate at /dfit-benchmarks (noindex). Shows per-benchmark PASS/FAIL chips, RMS / max log-residuals, sample counts, the CI log preview produced by formatDfitBenchmarkReport, and a JSON download of the full DfitBenchmarkReport so individual runs can be archived alongside scoreboard flips. Phase 6 inc 4 will swap the kgd-width registry runner from the self-reference to the actual Newton-step solver.
Phase 6 inc. 4 — KGD entry now wired to the live Newton kernel (shipped, beta)
kgdNewtonRunner.ts time-marches coupledNewtonStep on a 1D-strip mesh (41 cells, 8 m half-length, dy = 1 m so q [m²/s] ↦ Q [m³/s] cleanly) with a centre-line DDM array, sampling the wellbore-cell opening at the requested times. The kgd-width registry entry's runner field now calls this — the dashboard and CI gate exercise the real Phase 3.5 kernel, not a self-reference. Tolerance relaxed to 0.15 log-units on the [1, 2] s window (fixed tips + finite seed cause an asymptotic bias); tightening waits on Phase 7 tip propagation. The pkn-length entry now also runs the live kernel via pknNewtonRunner.ts (height-contained 1D-strip, volume-balance L = 2·Q·t/(π·h·w_w), tol AUTO-TIGHTENED 0.20 → 0.15 on [0.5, 1] s @ Phase 7 inc 21); Carter stays analytical-self until its sink kernel lands. Drift-guards: 5 new kgdNewtonRunner.test.ts tests + updated runner-suite + CI-gate tests, all green.
Internal sandbox — no external reservoir-sim bridge
All physics run inside this app. There is no bridge to ECLIPSE, tNavigator, INTERSECT, or CMG; the on-screen "Coupled wellbore + reservoir run" label refers to the in-house sandbox kernel (src/lib/planar3d/* and src/lib/depletion*), not a third-party solver front-end.
src/lib/planar3d/coupledNewtonStep.ts · src/lib/coupledStageSim.ts · src/lib/depletionCoupling.ts- No external reservoir simulator integration today.
- No two-way handshake with ECLIPSE / tNavigator / INTERSECT / CMG planned — roadmap only contemplates a one-way LGR export.
Two-way within the frac · one-way static reservoir → frac
Width and pressure are solved fully-coupled inside the fracture kernel (Newton-Raphson via coupledNewtonStep, the default live-kernel path since Phase 7 inc 16). The reservoir side is one-way static: applyDepletionToFracGeometry consumes ΔPp and ΔT and shifts σmin before each frac solve. There is no transient reservoir feedback during pumping or flowback — the reservoir state is a snapshot, not a live partner in the iteration.
src/lib/planar3d/coupledNewtonStep.ts · src/lib/depletionCoupling.ts · src/lib/depletionStress.ts- No transient pore-pressure feedback into the frac during a run.
- Flowback closure stress uses the depletion snapshot — pressure-volume balancing inside the reservoir is not iterated.
Steady-state hydrostatic + opt-in transient single-phase pipe
The default wellbore model is steady-state hydrostatic plus frictional pressure drop: P = 0.052·ρ_eff·TVD + Σ ΔP_friction (slurryHydrostatic.ts, with the slurry+proppant ρeff cascade). The CouplingPanel additionally applies a damped fixed-point gas-yield correction for ballistic stages.
Phase 8 inc 2 — opt-in transient single-phase wellbore. transientWellbore.ts implements a 1-D segmented pipe with segregated implicit Euler on mass + momentum + energy: pressure tridiag from the Thomas algorithm, velocity from the momentum closure v_f = (B_f − Δp_f)/A_f, and a separate temperature tridiag with upwind convection plus U-value heat loss to the formation; Swamee-Jain Darcy-Weisbach friction. Wired into the Newton kernel as wellboreCoupling on coupledNewtonStep — one-way kernel→wellbore (bottomhole BC = injection-cell pressure, dt amortised across the iter budget). The coupled run surfaces wellboreState / bottomholePressurePa / surfaceRateM3PerS / wellborePicardIterations. Slugging, holdup, gas-lift unloading, and full multiphase flowback remain out-of-scope until the drift-flux upgrade lands.
Phase 8 inc 1b — opt-in drift-flux multiphase preview. src/lib/wellbore/driftFlux.ts implements the Zuber-Findlay closures (α = j_g/(C₀·j_m + V_d), ρ_m = α·ρ_g + (1−α)·ρ_l, μ_m = α·μ_g + (1−α)·μ_l, slip-corrected phase velocities). The same closures are wired into transientWellbore.ts via an opt-in multiphase: { gasSuperficialMpsPerCell, phaseProps, closures? } field on the step input — per Picard iteration the cell density and face friction substitute the mixture in-place; the result surfaces voidFractionPerCell and mixtureDensityKgPerM3PerCell (null when the option is off so the single-phase path is byte-identical). Drift-guards prove single-phase byte-collapse at j_g = 0, gas-slug convergence in < 40 Picard iters with α ∈ (0, 1) and ρ_m bounded by [ρ_g, ρ_l], and reduced hydrostatic head under gas-lift.
src/lib/slurryHydrostatic.ts · src/components/CouplingPanel.tsx · src/lib/ballistics.ts · src/lib/wellbore/transientWellbore.ts · src/lib/wellbore/driftFlux.ts · src/lib/planar3d/newtonWellboreCoupling.ts- Implemented (preview): Zuber-Findlay α / ρ_m / μ_m closures wired through
transientWellbore.ts; gas-lift hydrostatic relief; slip-corrected friction. - Implemented: four-regime flow-pattern map (bubbly / slug / churn / annular) via
classifyFlowPattern+flowPatternHistogram, surfaced as a per-cell stacked-bar histogram in the Builder multiphase card. - Deferred: gas-EOS-coupled mass-storage
c_t(still ρ_l·c_t, so high-α transients are approximate). This is the last gate before drift-flux exits preview. - OLGA-class transient multiphase solver remains out of scope.
Analytical Δp + poroelastic Δσ_h screening
Frac-hit / well-spacing risk for a child well against a set of producing parents. For each stage midpoint we sum a steady-state log-radial drawdown contribution Δp = Σᵢ Δp_i · ln(r_e/r) / ln(r_e/r_w) over parents, then convert to a horizontal-stress reduction via the Eaton uniaxial-strain coefficient Δσ_h = α · (1 − 2ν) / (1 − ν) · Δp. Frac half-length asymmetry is taken from the lateral-normal Δp gradient; bashing-risk chip uses joint Δp + nearest-parent distance thresholds (low < 75 psi or > 1320 ft; high > 500 psi or < 660 ft).
src/lib/parentChildInterference.ts · src/routes/projects.$projectId.workspaces.$workspaceId.parent-child.tsx- Steady-state drawdown only — child production timeline is not coupled.
- 2-D plan view; no vertical stress contrast or layered geomechanics.
- Drainage radius and parent Δp are user-entered, not history-matched.
- Asymmetry is a first-order gradient projection — not a fracture-mechanics simulation.
Geertsma 2D kernel + SHmax rotation toward depleted parents
Upgrade of §9's scalar Eaton path. Each producing parent is modelled as a circular drained patch of radius a = drainageRadiusFt with stress-path coefficient A = α·(1 − 2ν)/(1 − ν). Polar increments inside the patch are isotropic Δσ_rr = Δσ_θθ = −A·Δp; outside the patch Δσ_rr = −A·Δp·(a/r)² and Δσ_θθ = +A·Δp·(a/r)² (hoop reverses sign). Per-stage Cartesian Δσ_xx, Δσ_yy, Δσ_xy are summed across parents, added to the far-field tensor built from σh_far, σH_far, and the SHmax azimuth, then eigen-decomposed to recover rotated principal stresses and the rotated SHmax direction. The signed azimuth shift drives a severity chip (≤5° info, ≤15° watch, >15° critical — the frac will bend toward the depleted zone). Δσ_v ≈ 0 here; uniaxial-strain BC keeps overburden weight constant.
src/lib/poroelasticDepletion.ts · src/routes/projects.$projectId.workspaces.$workspaceId.parent-child.tsx- 2-D plane-strain only; no Mindlin / multi-bench layered stress.
- No thermo-poroelastic term — injection cooling is not coupled.
- Steady-state Δp; transient pressure diffusion around parents not modelled.
- Rotated SHmax azimuth is reported, not fed back into the child fracture-geometry solver.
Phase 9 segregated implicit T → P → σ_h stepper
3-D voxel companion to §7's pressure coupling and §10's poroelastic depletion kernel. A regular voxel grid solves TPFA conduction for temperature (SPD CG), pipes per-cell ΔT into the in-house single-phase pressure solve as a thermal source q_T = β·ΔT·(φ·c_t·V_b/Δt), and accumulates a Geertsma-style σ_h update Δσ_h = α_B·(1−2ν)/(1−ν)·ΔP + E·α_T/(1−ν)·ΔT so cooling and depletion both unload σ_h with the same sign convention used in §10. Builder inputs ship via ThermoPoroInputsCard on the Solver panel; Diagnostics-tab results card surfaces max |ΔT|, |ΔP|, and |Δσ_h| plus mid-k diverging-ramp heatmaps from a deterministic preview run.
Phase 9 T-M-H 3-D voxel stepper via `src/lib/reservoir/thermoPoroGrid3D.ts` — segregated implicit-Euler timestepping on a regular voxel grid that solves a TPFA conduction temperature equation (SPD CG via `src/lib/linalg/cg.ts`) with prescribed-T Dirichlet boundary conditions AND volumetric heat sources AND **opt-in convective transport** via `enableConvection: true` on the step input (lagged Darcy face fluxes drive upwind T·v·∇T fluxes on the temperature tridiag; off by default so legacy callers stay byte-identical), then pipes the per-cell ΔT into the existing `stepReservoir` single-phase pressure solve as a thermal-pressure source `q_T = β · ΔT · (φ·c_t·V_b / Δt)` (β = thermalPressureCouplingPsiPerF, configurable per-grid), and finally accumulates a Geertsma-style minimum-horizontal-stress update `Δσ_h = α_B·(1−2ν)/(1−ν)·ΔP + E·α_T/(1−ν)·ΔT` so cooling and depletion both unload σ_h consistently with the existing 2-D poroelastic kernel in §10. **Bench-aware E'/α** via `src/lib/reservoir/benchLayeredProps.ts` (`expandBenchLayersToCells` + `normalizeBenchProperty` + `isBenchLayered`) — callers can declare per-K-layer `{ perBench: [...] }` for `youngsModulusPsi`, `poissonRatio`, `thermalExpansionPerF`, `biot`, `bulkHeatCapacityBtuPerFt3F`, or `bulkThermalConductivityBtuPerDayFtF`; the helper broadcasts each bench scalar across (i, j) at construction time so `createThermoPoroState` consumes the existing per-voxel `number[]` shape unchanged (stiffer benches yield proportionally larger |Δσ_h| under identical ΔT). Builder UI surfaced via `ThermoPoroInputsCard` on the Solver panel (master enable switch, dt, initial T, β, biot/ν/E/α_T, bulk heat capacity + conductivity, dynamic thermal-BC and volumetric-source tables, all clamped to physical ranges and persisted through `thermoPoroBuilderStore`). Results surfaced via `ThermoPoroResultsCard` in the simulation Diagnostics tab — synthetic 16×16×4 12-step preview run by `thermoPoroResultsPreview.ts` produces max |ΔT|, max |ΔP|, and max |Δσ_h| summary tiles plus mid-k diverging-ramp SVG heatmaps for T and cumulative Δσ_h. Drift-guards: `thermoPoroGrid3D.test.ts` (11 — β=0 decouples fields, β>0 cooling drops cell P, 4-fold symmetry on homogeneous blocks, Biot/Hooke closed-form sanity, cooling-induced unloading, isotropic tensor identity, step-result maxAbs arrays), `benchLayeredProps.test.ts` (8 — layer→voxel broadcast, length/finite guards, scalar/array passthrough, `{perBench}` E feeds `deltaSigmaHFromCoupling` proportionally), `thermoPoroBuilderStore.test.ts` (5), `thermoPoroResultsPreview.test.ts` (6). Pure module, no external simulator dependency.
All self-contained kernel items closed (convective transport + bench-aware E'/α both shipped). Remaining: promote the 3-D T-M-H stepper from preview surface to in-loop coupling inside `coupledNewtonStep` so the same per-iter ΔT / ΔP / Δσ_h pushed into the in-house Darcy substepper (§7 `reservoirCoupling`) also feeds the per-cell PDL threshold of the fracture kernel, and bridge the voxel-grid σ_h field back into the parent-child §10 SHmax-rotation kernel so live cooling near offset wells reorients the child-fracture geometry alongside depletion. Field-validation gating shares the `/projects/$p/workspaces/$w/field-validation` ingest path (DTS gradient surveys + DFIT residuals).
src/lib/reservoir/thermoPoroGrid3D.ts · src/lib/thermoPoroBuilderStore.ts · src/lib/thermoPoroResultsPreview.ts · src/components/jobmanager/builder/ThermoPoroInputsCard.tsx · src/components/results/ThermoPoroResultsCard.tsx- Segregated (T → P → σ_h), not monolithic — feedback within a Δt is one-way per field.
- Stepper is not yet wired into the live
coupledNewtonStepouter loop (preview surface only); §7 pressure coupling and §10 σ_h rotation remain the in-loop paths today. - Results card runs a deterministic 16×16×4 preview grid — not the user's full reservoir mesh.
- Single-phase pressure only (no oil/gas/water EOS); thermal properties assumed bulk-isotropic per cell.
M1+M2+M3 pipeline — surface mesh + DDM + Erdogan-Sih tip kinking + NF branching
Fully-3D non-planar fracture geometry with a quad surface mesh, 3-D DDM influence operator, Erdogan-Sih maximum-hoop-stress tip-kinking criterion, and natural-fracture branching gated by Renshaw-Pollard / Gu-Weng. Interactive viewer at /non-planar-3d; one-click toggle from /planar3d-validation.
Fully-3D non-planar fracture geometry via `src/lib/nonPlanar3D/{surfaceMesh,ddm3D,tipKinking,nfBranching,pipeline,mtsPropagation,stressFieldSampler}.ts` — quad surface mesh with per-face propagation history, 3-D DDM influence operator (Okada-style half-space), Erdogan-Sih maximum-hoop-stress tip-kinking criterion with operator-tunable kink-angle clamp, and natural-fracture branching gated by Renshaw-Pollard / Gu-Weng crossing criteria. M1 (surface mesh + DDM) + M2 (Erdogan-Sih tip kinking) + M3 (NF branching) + M4 per-vertex E′/ν heterogeneity (`MeshVertex.ePrimePsi` / `poissonRatio` → opt-in via `perVertex: true` on `runNonPlanar3DPipeline`; per-face E′ = mean of 3 vertex E′_v with E′_v = E_v/(1−ν_v²) when ν_v defined, scalar `ePrimePsi` fallback per vertex; layered config wins when both supplied; omitted ≡ false ≡ byte-identical legacy run) + M4 out-of-plane tilt for sub-vertical fractures (per-edge / per-step tilt field plumbed through `advanceTipRow` — length-preserving `cos τ` in-plane × `sin τ` +y; collapses byte-for-byte when omitted) + shared-vertex weld for T-junction reconvergence (`tipReconvergenceWeld: { enabled, tolFt? }` re-uses `weldNearbyVertices` so any new tip vertex landing within `tolFt` of a parent vertex folds onto it; per-step `tipWeldedVertexCount` + `tipWeldedRemovedFaceCount` surfaced on every `TimeStepRecord`) + M2 stress-driven curvature loop (`stressDrivenCurvature: { enabled, evaluator, maxCurvatureRadPerFt? }` — live Δσ tensor evaluator feeds per-edge σ_Hmax azimuth via `principalDirectionsFromTensor2D`, capped by `curvatureLimitedKinkDeg`; omitted ≡ disabled ≡ isotropic σ collapses byte-identically to legacy baseline) + multi-stage orchestrator `runMultiStageNonPlanar3DPipeline` (`src/lib/nonPlanar3D/multiStagePipeline.ts`) drives N stages in lockstep and snapshots cross-stage frac–frac welds via `mergeIntersectingStages` under a `crossStageMerge: { tolFt, cadenceSteps?, minStageGap?, debug? }` knob — UI surfaces (`NonPlanar3DCrossStageMergeCard` on `/non-planar-3d`, `downhole.nonPlanar3D.crossStageMerge.v1` store) and a public SDK endpoint `POST /api/public/sdk/v1/non-planar-3d/multi-stage` accept the same knobs and return `crossStageMergeHistory` (per-step welded-pair stats + optional combined-mesh debug snapshots); omitted ≡ N independent single-stage runs (byte-identical histories) + pipeline driver + three.js viewer at `/non-planar-3d` (sliders maxKinkDeg / advanceFt / steps / SHmax + criterion select; viridis face-of-birth tint) + per-simulation Results tab `NonPlanar3DResultsCard`. Validation toggle on `/planar3d-validation` (`Switch to non-planar 3D viewer →`) gives engineers a one-click jump between the planar Sneddon comparator and the non-planar viewer. Drift-guards: `surfaceMesh.test.ts` (4) + `ddm3D.test.ts` (4) + `tipKinking.test.ts` (17 incl. 2 tilt unit tests) + `nfBranching.test.ts` (9) + `pipeline.test.ts` (15 — 4 baseline + 3 non-planar retention + 4 out-of-plane tilt + 4 T-junction reconvergence weld) + `mtsPropagation.test.ts` (9) + `stressFieldSampler.test.ts` (4) + `pipeline.curvature.test.ts` (4 — parity collapse · anisotropy drives kink · zero-curvature flat · curvature cap honored) + `multiStagePipeline.test.ts` (8 — collapse-to-N · cadence skip · tight pair welds · separated pair no weld · minStageGap default · debug arrays gating · empty step) + `nonPlanar3DCrossStageMergeStore.test.ts` (6) + `ddm3DPerVertex.test.ts` (6) + `pipeline.perVertex.test.ts` (4) = 90 tests.
Promote the kernel from per-simulation Results-tab preview into the in-loop coupling inside `coupledNewtonStep`'s outer loop so each Newton iter re-uses the M1+M2+M3+M4 mesh state (including welded T-junctions) as the live fracture footprint for the planar-3D pressure/opening substep, and bridge per-face σ_Hmax telemetry into the live-pumping observability surface. Field-validation gating shares the `/projects/$p/workspaces/$w/field-validation` ingest path (microseismic catalogs + offset DFITs).
src/lib/nonPlanar3D/{surfaceMesh,ddm3D,tipKinking,nfBranching,pipeline}.ts · src/routes/non-planar-3d.tsx- Standalone viewer — not yet wired into
coupledNewtonStep's outer loop. - In-plane normals only; out-of-plane tilt deferred to post-MVP.
- No shared-vertex weld for T-junctions when branches re-converge.
Not solved by this kernel — declared deferred
Engineers coming from UFM-class tools (Kinetix, GOHFER-3D, XSite) expect an explicit status row for discrete-fracture-network topology, hydraulic ↔ natural-fracture (HF↔NF) crossing, and fully-3D XFEM tip propagation. This section declares that status verbatim from the same scoreboard row consumed by the drift-guard, so the on-page copy can never drift from solverSpecScoreboard.ts.
Planar-3D (`propagatePlanar3D`) + analytical complexity volumetrics (swept-ellipse capture, Sneddon per-bench aperture). UFM-style discrete-fracture-network (DFN) intersection / branching, hydraulic-natural-fracture (HF↔NF) stress-shadowed crossing criteria (Renshaw–Pollard, Gu–Weng), and fully-3D XFEM tip propagation are NOT solved by the kernel — complexity is captured volumetrically and via parent-child stress shadowing only.
Out of scope for this codebase: a true UFM/Fully-3D DFN solver requires a dedicated discrete-fracture topology engine + HF↔NF crossing criteria + 3D XFEM tip propagation. Field-driven complexity stays captured via the analytical volumetrics + parent-child stress-shadow path; promote only if a third-party DFN engine is bridged in (no plan today).
src/lib/solverSpecScoreboard.ts (row id ="ufm-fully-3d") · §12 Planar-3D propagation · §4 Proppant transport · §9 Parent-child interference- No DFN topology engine — natural-fracture sets are not represented as discrete planes.
- No HF↔NF crossing criterion (Renshaw–Pollard, Gu–Weng, Blanton) — the kernel cannot decide cross / arrest / branch on contact with a natural fracture.
- No 3D XFEM tip propagation — out-of-plane curving is not solved (the Planar-3D path in §12 stays in-plane by construction).
- Field-driven complexity is captured volumetrically (§4 swept-ellipse + Sneddon per-bench) and via parent-child stress-shadow interactions (§9), NOT via discrete intersection physics.
- Promotion only if a third-party DFN engine is bridged in — no plan today.
Layered σ_h profile + per-bench KGD-coupled aperture
Phase 8 ships a TRUE fully-planar 3-D fracture-propagation kernel that extends the Pseudo-3D solveImplicitHeight path with a per-bench aperture profile, an elliptic-prism cross-section, and a leakoff-free volume-balance length. It is the first solver in the codebase that does NOT homogenise E' or σ_h across contained layers — each bench keeps its own elastic modulus and minimum horizontal stress through the entire width and length update.
layers[]— ordered benches withtopTvdFt,thicknessFt, layer-localsigmaHPsi, and plane-strainePrimePsi.pInternalPsi— net internal fluid pressure driving width.kIcPsiSqrtIn+ pay-benchtopTvdFt/thicknessFt— consumed bysolveImplicitHeightfor the implicit height bisection.rateBpm— slurry rate per wing (factor 2 baked into length).timeMin— treatment time for the volume-balance length.
- Height
His implicit fromK_I = K_ICat top & bottom tips (Tada / Sneddon piecewise-constant kernel viasolveImplicitHeight). - Per-layer aperture is plane-strain Sneddon, evaluated locally:
w_max(layer) = 2·max(0, p − σ_h_layer)·H / E'_layer. - Vertical aperture profile across each bench is the upper-half ellipse
w(z) = w_max·√(1 − (2z/t)²)⇒ per-bench area(π/4)·w_max·t_contained. - Two symmetric wings; length from leakoff-free volume balance
L = q·t / (2·A_xs). - Carter / PDL / cake leak-off is intentionally OFF in v1 — the headline is layered-σ_h, not the leak-off model (those couplings remain owned by §3 + the Newton kernel).
- No proppant transport, no time-marching pressure update — single snapshot at
(p, q, t).
H = solveImplicitHeight({ pay, layers, p, K_IC })
for layer in layers ∩ [topTip, botTip]:
t_c = overlap(layer, [topTip, botTip])
w_max(L) = 2·max(0, p − σ_h(L))·H / E'(L) # in
A_c(L) = (π/4) · w_max(L) · t_c # ft·in
A_xs = Σ A_c(L)
L_wing = (q·t · 5.61458) / (A_xs / 12) # ft
V_wing = q · t # bblPL3DResult)height— fullImplicitHeightResult(heightFt,topTipTvdFt,botTipTvdFt, K_I residuals).perLayer[]— for every contained bench:containedThicknessFt, layer-localsigmaHPsi/ePrimePsi,wMaxIn, andxsAreaContribFtIn.xsAreaFtIn— total elliptic-prism cross-section area.halfLengthFt— per-wing half-length from volume balance.volumePerWingBbl— slurry volume injected per wing.
planar3DPropagation.test.ts— 8 drift-guards: mass balance, layered-pay aperture dominance, q-linear length, p-monotone height & width, containment exclusion, input validation.planar3DBenchmarks.test.ts— per-bench Sneddon analytic + height-time growth (4 tests) pluscomparePlanar3DAgainstClassicModelscross-check vs PKN, KGD, and Savitski–Detournay radial on identical reduced inputs (CSV export, μ-sensitivity, PKNt^{4/5}growth).- Interactive comparator at /planar3d-validation — sweeps μ from 1 → 200 cP and surfaces the per-model half-length / wellbore width / net-pressure table with PL3D as the reference.
- Collapses byte-identically to a single-bench PKN-style geometry when given one layer (locked by drift-guard).
src/lib/planar3DPropagation.ts · src/lib/p3dImplicitHeight.ts · src/lib/planar3DBenchmarks.ts · src/components/Planar3DComparatorPanel.tsx- Snapshot solver — no Δt loop, no transient pressure / proppant feedback.
- No leak-off in v1 (Carter / PDL / cake remain in §3 and the live Newton kernel).
- Per-bench aperture is plane-strain Sneddon — no DDM bench-bench shear coupling beyond the shared height bisection.
- Elliptic-prism A_xs assumes the upper-half-ellipse vertical profile holds within each bench; thin benches near the tip are approximate.
- Not yet wired into
coupledNewtonStep;/planar3d-validationis the only consumer today.
What this simulator is and is not
This is an engineering screening and DFIT-calibration tool built from first-principles modules with explicit benchmarks. It is not a research-grade Planar-3D code today — single-cluster scalar geometry, sequentially explicit coupling, and decoupled leak-off / filter-cake helpers are honest limitations, not bugs. Validate against site DFITs and offset history before field commitment. The Phase 1–6 roadmap upgrades the scoreboard one row at a time; each phase ships its own analytical benchmark in CI so the scoreboard never lies.