Modern web-based replacement for the legacy Delphi MIXED directional drilling application. A complete TypeScript port of ~19,000 lines of Pascal trajectory math + UI, rebuilt as a multi-package monorepo with React + Fastify + Prisma.
The original Pascal sources live read-only under
old_delphi_code/; every TypeScript file that ports Pascal
code cites the source unit and line number in a comment so the lineage stays
auditable. The complete port-status audit is in
PORT_AUDIT.md.
Plan and verify directional wells:
- Design the trajectory — pick from 30+ profile types (HC3D, CH3D, HCH, CH, D3DS, D3DS-HOLD, CC3D, curve-EOC variants, fly-to-target, multi-curve combos) and let the dispatcher solve the geometry. Auto-handles drop curves (negative DLS), quadratic-branch selection, and reports the minimum DLS needed when targets are unreachable.
- See the well in 3D — orbit-camera wellbore viewer, vertical-section chart, plan-view scatter, cross-section chart, contoured field map with depth ramp.
- Manage fields — upload Petrel
.grdgrids, browse contoured maps, place wells by clicking on the map, lasso a polygon to clip the grid, compute reservoir volumes between two horizons. - Export — landscape A4 PDF reports (pdfmake) and
.xlsxworkbooks (SheetJS) matching the original DelphiUnit10.RvSystem1Printcolumn layout.
Audited 27 Pascal units (MIXED.dpr direct dependencies). Per-procedure
results in PORT_AUDIT.md.
| Category | Status | Notes |
|---|---|---|
| Trajectory builders (Unit02.pas) | ✅ 100% | All 30+ profile codes: hold, c3, sursta, hoctt, hc3dtft, ch3dffk, ch, hch, ch2dc1, ch2dc2, cc2d, curveEoc (E1–E5), flyto (1–5), mcombo (61–103) |
| VSEC / TF / BR / TR | ✅ Computed | Post-passes in the dispatcher ported from Unit02.pas:2578–2624 |
| DLS sign handling | ✅ Auto | Dispatcher tries both signs (and both quadratic branches for CH2DC1/2) so drop curves "just work" |
| Field map (Unit21.pas) | ✅ + extras | 2D raster + contours + colour ramp + click-to-place-wells + polygon clip + cross-section line picker |
| 3D viewer (Unit03, Unit25, Unit35) | ✅ Mesh + wells | Three.js / R3F. Stereo anaglyph and voxel-cube modes deferred (cosmetic) |
| Reporting (Unit10.pas) | ✅ PDF + XLSX | Column layout matches Pascal RvSystem1Print |
| Casing / BHA / Mud / Hydraulics (Unit41) | ❌ Empty in original Pascal too | All CREATE TABLEs for these are commented out in Unit01.pas:940-1210 |
.mdb import (Unit01.Load1Click) |
❌ Replaced with CSV importer | ODBC isn't feasible server-side; bulk CSV import covers the data flow |
| Per-well snapshot list (Unit02.SURVEYCOPY) | ❌ Architectural | TS app uses one canonical Calculation per well — would need a Versions model |
| Unit-preset modal (Unit42) | ❌ Conversion math present, no UI | @dd/shared/units has the conversions; just no preset picker |
Detailed list of every remaining file/line with reasons is in
PORT_AUDIT.md.
| Layer | Choice | Reason |
|---|---|---|
| Monorepo | npm workspaces | Zero extra tooling, full TS path-mapping |
| Frontend | React 18 + Vite + Tailwind + TanStack Query + React Router | Fast HMR, code-splitting, typed cache |
| 3D | Three.js + @react-three/fiber + drei | Orbit controls, instanced meshes, lazy-loaded (1.1 MB chunk) |
| Charts | Recharts | Lazy-loaded (395 kB chunk) |
| Backend | Fastify + Prisma + SQLite (Postgres-ready) | Fast schema migrations; type-safe queries |
| Validation | Zod | Schemas shared between web ↔ api ↔ tests |
| Tests | Vitest (unit) + Playwright (e2e) | Fast, parallel, native-TS |
| Math | Pure functions in packages/shared |
Same code runs locally in the browser preview AND server-side on save |
Initial JS bundle: 153 kB gzip (Three.js + pdfmake + xlsx + recharts all lazy-loaded). Dev startup: ~2s.
┌─────────────────────────────────────────────────────────────────┐
│ apps/web │
│ React 18 + Vite. Lazy-loads heavy deps (Three, pdfmake, xlsx) │
│ - pages/CalculationPage — editable grid + 3D + charts + export │
│ - pages/FieldMapPage — .grd upload + contour map + wells │
│ - components/MapViewer2D — canvas raster + click tools │
│ - components/WellViewer3D / FieldScene3D — Three.js viewers │
└─────────────────────────────┬───────────────────────────────────┘
│ /api proxy (Vite dev)
▼
┌─────────────────────────────────────────────────────────────────┐
│ apps/api │
│ Fastify + Prisma. Routes: │
│ /projects /countries /fields /wells /calculations │
│ /grids /grids/:id/volume /grids/upload │
│ │
│ POST /calculations/:id/calculate │
│ 1. Load segments from Prisma │
│ 2. Run @dd/shared dispatch() — same code the browser previews │
│ 3. Persist stations + keypoints in one transaction │
└──────┬──────────────────────────────────┬───────────────────────┘
│ │
▼ ▼
┌───────────────────┐ ┌──────────────────────────────────┐
│ packages/shared │ │ packages/grd │
│ Trajectory math: │ │ .grd ASCII parser + contours + │
│ - 14 builders │ │ volume + colour ramp + line │
│ - dispatcher │ │ sampler + polygon clip │
│ - VSEC/TF/BR/TR │ │ 13 unit tests against a real │
│ post-passes │ │ Petrel grid │
│ - 57 unit tests │ └──────────────────────────────────┘
│ Zod schemas │
│ Unit conv. │
└───────────────────┘
- Math is shared, not duplicated. The browser previews calculations
client-side; the API recomputes server-side on save. Both import from the
same
@dd/shared/math/dispatcher. There is no possibility of drift. - Angles always in radians; distances always in the project's unit. Conversion happens only at the UI / CSV import boundaries.
- Pascal traceability. Every ported file references the Pascal unit and
line range it was ported from. Grep the codebase for
Unit02.pasto find the audit trail.
- Profile picker modal ports
Form04/Form05/Form06. 30+ profile types organized by family (standard / hold / single-curve / multi-curve). - Per-profile editable-cell mask ports
Unit02.pas:rowcolor. Yellow cells are user inputs; gray cells are computed. Each profile spawns the exact set of milestone rows (KOP / EOC / Target / EOC #1 / KOP #2 / ...) Pascal would. - Exact algebraic keypoints — every milestone (KOP, EOC, Target) lands at
its exact analytic MD instead of being snapped to the nearest 100-ft densified
station. Pascal does the same in
Form02.cellfill. - Auto DLS sign flip — enter
+5for a drop curve and the dispatcher tries the negative sign internally. Output displays magnitude only (per Pascal'swlpt2[1].dls := abs(...)convention). - Quadratic branch selection — CH2DC1 / CH2DC2 try both quadratic roots
and pick the feasible one. Pascal's fixed
|tgtx| > |r1|heuristic was a bug for chained CH→D3DS profiles. - Min-DLS hints on failure — when CH or HCH can't reach the target, the
error message includes the minimum DLS that would solve the geometry.
Ported from
Unit02.pas:3134-3138. - Smart-diff segment saves — adding a row to the grid no longer wipes previously-calculated stations. Only changed/added/removed orders and rows after them are invalidated.
- Undo / redo — 50-deep history with Ctrl+Z / Ctrl+Shift+Z; never traps the browser's own undo when typing in a cell.
- Debounced autosave — 1.5 s after the last edit; status pill shows "Unsaved / Saving… / Saved 12s ago / Save failed".
.grdparser — ASCII grid format (FSASCI header +!metadata + column-major floats). Verified against a 1.15 MBTOP_HITH_DEPTH.grdPetrel export.- Coloured raster — spectrum / warm / grayscale ramps via the Pascal
Form23.Degradealgorithm. Hover tooltip shows cell value. - Marching-squares contours — extract iso-lines at user-suggested levels; draw on overlay canvas.
- Cross-section line picker — click point A, click point B, see the elevation profile chart along the line (Recharts).
- Click-to-place wells — Pascal
Form21.Image2MouseDownported with a modal asking for the well name; POST/wellsand the new pin shows up. - Polygon clip — lasso a region, double-click to finish, cells outside
become null. Pascal
Form21.Button3Click. - Volume calculator — sum-method volume between two horizons. Verified to give 0 when comparing a grid to itself.
- Wellbore viewer —
WellViewer3Duses R3F with a tubular mesh along the densified stations, ground grid, compass markers, wellhead and target spheres. - Field scene —
FieldScene3Drenders the grid as a triangle mesh coloured by depth, with all wells in the field overlaid as tubes. - Orbit controls, lazy-loaded (Three.js is a 1.1 MB chunk).
- PDF — landscape A4 multi-page table via
pdfmake. Column layout matchesUnit10.RvSystem1Print: Comment, MD, Incl, Azm, TVD, VSEC, NS, EW, DLS, TF, BR, TR, DMD. - XLSX —
.xlsxworkbook via SheetJS. Same column order. - CSV import — bulk-load
countries.csv+fields.csv+wells.csv+calculations.csv+segments.csvin one transaction. Column names match PascalCREATE TABLEstatements.
- Node 20+
- npm 10+ (workspaces support)
# 1. Install all workspace dependencies
npm install
# 2. Set up the API database (SQLite by default)
cd apps/api
cp .env.example .env
npx prisma generate
npx prisma migrate dev --name init
cd ../..
# 3. Build the shared package (web and api import from its dist/)
npm run build:sharedIn one terminal:
npm run dev:api # → http://localhost:4000 (health check: /health)In another:
npm run dev:web # → http://localhost:5173
# Vite proxies /api → http://localhost:4000Or both at once:
npm run devnpm test # 57 @dd/shared + 13 @dd/grd unit tests
npm --workspace e2e test # Playwright happy-path (needs servers running)The unit suite enforces numerical fidelity for the trajectory math primitives and the .grd parser/contour/volume code.
/apps
/web React + Vite frontend (port 5173)
/src
/api Typed fetch client
/components 3D viewers, charts, field map, profile picker,
editable cell, profile roles
/export PDF + XLSX generators (lazy-loaded)
/hooks useHistoryState (undo/redo)
/import CSV → import-payload converter
/pages CalculationPage, FieldMapPage, ProjectsPage, etc.
/shell App shell / nav
/api Fastify backend (port 4000)
/prisma schema.prisma + migrations
/src
/routes REST endpoints
/packages
/shared Types, zod schemas, units, trajectory math
/src
/math
/builders hold, c3, sursta, hoctt, hc3dtft, ch3dffk, ch,
hch, cc2d, ch2dc1, ch2dc2, curveEoc, flyto, mcombo
dispatcher.ts Orchestrator + VSEC/TF/BR/TR post-passes
plane.ts, vector.ts, rotation.ts, solve.ts
profile-types.ts
/schemas Zod
/units cm/m/km/ft/yd/mi/nmi + deg/rad + DLS conversion
/grd .grd parser + volume + contours + colour ramps +
line sampler + polygon clip
/e2e Playwright happy-path tests
/old_delphi_code Reference (read-only)
PORT_AUDIT.md Full audit of every Pascal unit + procedure
PHASE5_NOTES.md Why .mdb importer + casing/BHA/mud are deferred
REACT_CONVERSION_PROMPT.md Original specification
- All trajectory math lives in
packages/sharedso the UI can preview locally and the API can recompute on save. No duplication. - All angles stored in radians; all distances in the project's storage length unit. Convert only at the UI / import boundaries.
- TypeScript strict mode throughout; no
anyoutside narrow interop spots. - The Delphi sources are the source of truth for algorithm behaviour. When
porting, cite
Unit##.pas:###in a code comment so the lineage stays auditable. - DLS is stored signed internally (the dispatcher uses sign to encode build vs. drop direction); always displayed as a magnitude per Pascal convention.
Phase 6 polish is complete. Items deferred for later, ordered by user impact:
- Form07 azimuth-disambiguation modal — proper popup with both candidates shown. Current behaviour: global Branch 1 / Branch 2 selector persisted in localStorage.
- Unit-preset modal (Imperial / Metric / API / SI) —
@dd/shared/unitshas the conversion math; just no UI to bulk-pick a preset. - Form24 raw .grd header viewer — needs
Grid.metadataPrisma field to persist the raw!-prefixed lines. - Volume methods 1–4 (Simpson + biquadratic surface fit) from
Unit30.pas. Currently only the sum method is in@dd/grd/volume. - 3D viewer extras — voxel-cube render mode and red/green anaglyph
stereo from
Unit35.pas. Both are cosmetic alternatives to the smooth-mesh path. - Captured fixtures from MIXED.exe to pin numerical fidelity at ±1e-6
(the test harness in
packages/shared/test/fixtures/is ready to receive them). - CI wiring for Vitest + Playwright + the fixture suite.
- PostgreSQL deployment target (Prisma makes this a one-line swap).
Documented as not-feasible-here:
- Casing / BHA / Mud / Hydraulics designers — never finished in the original
Pascal either (all
CREATE TABLEstatements commented out in Unit01.pas); these would be net-new features rather than a port. - Legacy
.mdbimporter — ODBC isn't practical server-side; CSV importer is the substitute. ProEffectImage.pas(1,238 lines of bitmap effects) — modern browsers do this via CSSfilter:.
Internal project. The reference Pascal sources under old_delphi_code/ retain
their original author's copyright.