Goldray

A metal detector turns each instant of coil response into a tone and forgets it. Add the position of the coil, kept by a phone on the shaft, and the swing itself becomes an imaging array.

Can a phone, a Raspberry Pi and an audio chip turn a metal detector into an imaging instrument?
Simultaneous tones
7
1.59 to 72.09 kHz, chosen by the planner
Crest factor
2.06
Composite drive current, peak over rms
Exposure cap
0.1 A
About half the ICNIRP reference field at the coil centre
Units built
0
Milestones M0 to M2 of twelve complete

An ordinary metal detector reports one number at a time and keeps none of them. Goldray keeps all of them. A module on the shaft, the Ray Core, transmits seven tones at once through a double-D search coil and records the coil’s complex response at each tone about 94 times a second. The Goldfall app on a phone clipped to the same shaft already tracks where the coil is to about a centimetre, so every reading can be stamped with the coil’s position and orientation.

A cloud of readings taken from many positions is the raw material of synthetic-aperture imaging. Back-projection turns it into a three-dimensional map with a depth axis, and a fit at each bright spot recovers the object’s magnetic polarizability tensor across frequency, the quantity that separates a gold nugget from a nail from a magnetic stone. Towed arrays that classify unexploded ordnance already work this way. Goldray proposes doing it with one coil, a phone, a Pi Zero and a stereo audio codec.

Three boxes and a pose

goldray docs/01 · architecture as specified, not built
Phone · Goldfall appARCore visual-inertial pose6-DoF, ~1 cm, 30 Hz uplinkhex map, depth prisms, audioRay Core · on the shaftRaspberry Pi Zero 2 W192 kHz stereo codec HATtones → lock-in → ground fit→ back-projection → inversionpose ↑ ~1.2 kB/s↓ results, BLEHead · DD coil280 mm double-DTX: 7 tones, 1.6–72 kHzRX: complex responsedrive ↓↑ receive01 Rayone record: seven complex responses, their noise estimates, the Core sample clock and the coil pose at that instant.About 94 a second.02 Where the work runspose is the smaller stream, so it travels up; the physics runs on the Core, in the same Python package as thesimulator. The phone only draws.
The Core and the coil do not exist yet. The parts are chosen and checked against datasheets (goldray hw/bom/verified.md), and the Goldfall app already supplies the pose.

Everything here is design and simulation. The parts have been checked against datasheets and the transmissions against the Australian class licence, the field, target, ground and tone-planning code passes tests against closed-form physics, and the full chain from raw frame to classified target runs on simulated scenes. Nothing has been soldered, wound or switched on.

Ironstone country in Western Australia’s goldfields is hard ground for a detector. The soil carries fine magnetic iron oxides, the surface is strewn with dark lateritic pebbles that ring like metal, and the gold worth looking for is often small. A detectorist sweeping that ground hears a continuous wash of response from the soil, a rise and fall as the coil’s height changes, and every few metres a signal that might be a nugget, a boot tack or a hot rock. Deciding which comes down to listening and digging.

Inside the detector, a transmit winding drives an alternating magnetic field into the ground and a receive winding picks up whatever disturbs it. The measurement is a single complex number: the change in mutual coupling between the two windings, with a part in phase with the drive and a part a quarter-cycle behind it. Goldray’s concept document lists three lossy things a conventional detector then does with that number. It collapses it to a tone, it discards it after a few tens of milliseconds, and it never knows where the coil was when it took it. Depth, size and shape are reconstructed, if at all, in the operator’s head from how the tone changes as the coil is wiggled.

A detector is a one-pixel camera that forgets each frame as soon as it has shown it.

Goldfall, the survey app Goldray is built to serve, already removes the third limitation. It puts a phone on the shaft, uses ARCore’s visual-inertial tracking to follow the coil, and paints a hex grid of where the ground has been swept. What it plots today is crude: the app derives its signal level from the sound level at the phone’s microphone. Goldray is the hardware and signal chain meant to give that map something better to draw.

Radar engineers learned long ago that a small antenna moved along a known path can stand in for a large one. Record the echoes with their positions, and the processor can ask, for every point in the scene, how well a reflector there would explain all of them at once. Goldray applies that idea to magnetic induction. Each record, which the project calls a ray, holds the complex responses at seven frequencies, their noise estimates, a timestamp from the Core’s audio clock and the coil’s six-degree-of-freedom pose. A survey is a cloud of rays, and nothing in it is thrown away.

For a compact buried object, the response at one coil position is set by three things: how strongly the transmit winding’s field reaches the object, how strongly the object’s induced field reaches the receive winding, and how the object itself responds.

Y(ω,p)=hR(r)T M(ω) hT(r)Y(\omega, p) = h_R(r)^{\mathsf T}\, M(\omega)\, h_T(r)

Here r is the object’s position relative to the coil, h_T and h_R are the field patterns of the two windings per amp of current, known from the coil’s geometry, and M(ω) is the object’s 3 × 3 magnetic polarizability tensor. Y is measured in metres, which is the reason the project uses it: field patterns in inverse metres and a tensor in cubic metres multiply out with no stray frequency factor. M belongs to the object alone. Its eigenvalues encode size and aspect ratio, and the way they change with frequency encodes conductivity and permeability.

A single reading cannot tell a small shallow object from a large deep one, because both can produce the same number. A cloud of readings can, because the pattern a buried object traces as the coil passes over it broadens with depth in a way the geometry predicts exactly. The kernel κ = h_R · h_T, the coupling a small isotropic object would produce at each point under the coil, shows how fast that pattern changes.

Where the coil is listening

goldray.sim.biot_savart · idealised DD, two 140 mm semicircles, at the null
windings, top viewTXRX5 cm below · peak 2.8410 cm below · peak 0.6820 cm below · peak 0.1101 Kernelκ = h_R · h_T, how strongly a small isotropic object at each point couples the two windings (one turn each, per amp,m⁻²). Each slice spans 60 × 60 cm; coarse dither is positive, fine dither negative.02 Depthfrom 5 to 20 cm the peak falls 26-fold and the pattern spreads. That broadening is what lets a cloud of readingsseparate a small near object from a large deep one.
The two D windings as modelled, then the kernel on horizontal slices 5, 10 and 20 cm below the winding plane. Each slice is scaled to its own peak, which is printed beside it. Values smaller than 4 % of the slice’s peak are left as paper.

Goldray’s map tier, Tier 1, is a matched filter over space. Under the swept footprint it lays tiles of voxels 5 cm across with eight depth layers spaced logarithmically from 2 to 40 cm below the ground. Each incoming ray adds to two running sums per voxel and tone: the kernel times the measured residual, and the kernel squared, each weighted by the ray’s pose confidence and inverse noise variance. Their ratio is the polarizability a target at that voxel would need; the detection statistic is the summed squared signal-to-noise ratio across tones. Because only two accumulators per voxel are kept, the image updates as the operator swings, and repeated passes add coherently, so the statistic grows as the square root of the number of passes.

Tier 1 accumulators, per voxel v and tone k
Avk+=qi κiv eik/σik2,Bvk+=qi κiv2/σik2A_{vk} \mathrel{+}= q_i\, \kappa_{iv}\, e_{ik} / \sigma^2_{ik}, \qquad B_{vk} \mathrel{+}= q_i\, \kappa_{iv}^2 / \sigma^2_{ik}SNRv2=∑k∣Avk∣2/Bvk\mathrm{SNR}_v^2 = \textstyle\sum_k |A_{vk}|^2 / B_{vk}

Milestone M2 implemented that back-projector and checked it against a golden scene: a 2 cm sphere of gold-like conductivity 10 cm down, three raster passes at 0°, 90° and 45°, and 1 cm of correlated error in the pose the processor is given. The specification’s acceptance test asks for the peak within 2.5 cm laterally and within one depth layer. The figure below runs goldray’s own scene generator and back-projector on that scene. Along any one line of the swing the signal is a smooth bump with nothing in it to say how deep the sphere is. In the image the peak lands 1.1 cm from the sphere in plan, in the 11 cm layer.

A swing becomes an image

goldray M2 · sim.scene and dsp.backproject on validation/scenes/sphere-10cm.yaml
-40-2002040-40-2002040coil track, plan view, cm-40-2002040back-projected SNR at 11.1 cm depthpeak 1.1 cm from truth-40-2002040one line (dashed) · |Y| at 10.22 kHz00.51235711172640depth layer, cmSNR down the peak column, of max01 Scenegoldray’s golden scene: a 2 cm sphere of gold-like conductivity, 10 cm down, swept in three raster passes at 0°, 90°and 45° (3264 rays, every third drawn), with 1 cm of correlated pose error. Circle: the sphere.02 One pixelone line of the swing, as a single coil reading at a time offers it: a bump, with nothing in it to say how deep orhow big.03 Imagethe peak voxel, refined between voxels, lands 1.1 cm from the sphere in plan and in the right depth layer; thespecification allows 2.5 cm and one layer.04 Depthdown the column the statistic is flat: the 40 cm layer still scores 88 % of the peak. Tier 1 finds the object; depthis pinned down by the Tier 2 fit.
The scene generator and back-projector are goldray’s own (milestone M2), run here on the project’s golden scene with the 1 cm production kernel grid; the scene has no soil and white measurement noise. Voxels are 5 cm across, in eight depth layers spaced logarithmically from 2 to 40 cm. Script: scripts/goldray/export_figures.py.

The image is less decisive about depth than that sounds. Down the column under the peak the statistic barely falls: the 40 cm layer still scores 88 % of the peak, because a larger, deeper object explains the same bumps almost as well as the right one. Tier 1 finds the object and a best depth. Pinning the depth down, and saying what the object is, is the job of the next stage.

Tier 2, also built in M2, takes the strongest candidates and solves for them properly. For a trial position, each ray contributes one row of six real coefficients to a design matrix that is the same at every tone, so one QR factorisation gives the six unique elements of M at all seven frequencies. A Levenberg–Marquardt search moves the trial position to minimise the residual, a scheme known as variable projection. A joint diagonalisation of the real and imaginary parts at each tone yields the three principal polarizabilities as functions of frequency, the features a classifier reads.

Survey teams hunting unexploded ordnance have done this for years, with multi-coil arrays on carts and RTK satellite positioning. What Goldray changes is the scanner: one coil, a hand, and a phone for positioning, updating live.

Goldray needs all its tones present at once and phase-locked to a clock it controls, because the rays are meant to be combined coherently across seconds and passes. A 24-bit audio codec running at 192 kHz, with its digital-to-analogue and analogue-to-digital converters on one shared clock, does that by construction. One output channel drives the transmit amplifier; one input measures the transmit current across a shunt and the other the receive voltage, sampled together, so the measurement is a ratio and the codec’s own drift cancels.

The tones sit on exact bins of a 2048-sample frame, 93.75 Hz apart, so each frame of 10.67 ms holds a whole number of cycles of every tone and one FFT reads all seven with no window and no leakage between them. Choosing which bins is a small combinatorial problem with seven rules. The bins must be odd and avoid multiples of 8 and 16, which is where harmonics of 50 Hz and 60 Hz mains land on this grid. They should be spread roughly evenly on a logarithmic axis. No tone may sit on a second- or third-order intermodulation product of the others, since any nonlinearity in the amplifier would put energy exactly there. And no tone may fall where the Australian class licence, or a European cross-check of it, says not to transmit.

The specification handed the planner a test vector to match or beat: bins 17, 37, 79, 163, 331, 673 and 811, reaching 76.03 kHz. The planner in goldray.dsp.tones, a branch-and-bound search re-verified by a separate brute-force enumerator that shares no code with it, returns 17, 31, 59, 109, 211, 397 and 769. Every one is prime. The nearest of the 182 second- and third-order products falls three bins from any tone, the same clearance as the test vector, and the worst ratio between neighbouring tones is 1.94 against the vector’s 2.18. Its top tone is 72.09 kHz rather than 76.03.

Seven tones, five gaps

goldray.dsp.tones · planner output for 1.5–76 kHz · AU LIPD 2025 keep-outs
1101001k2k5k10k20k50k100kfrequency, Hz (log)TX current per tone, mA peak (log)19.95–20.05 kHz gaptime-signal spotscodec −3 dB?173159109211397769spec test vector (17 … 811)01 Tonesbins 17, 31, 59, 109, 211, 397, 769 of a 2048-point frame at 192 kHz, each 93.75 Hz apart. All prime, so none is amultiple of 8 or 16, which is where 50 Hz and 60 Hz mains harmonics land.02 Productsall 182 second- and third-order products checked by enumeration; the nearest falls 3 bins from any tone.03 Drivecurrent falls as 1/f so each tone induces the same voltage, summing to 100 mA rms, the exposure cap. Peak drive 5.16V into the modelled coil.
Pink stems: the tones the planner chose, labelled by bin, with height the drive current each gets. Teal ticks: the hand-made test vector in the specification, which the planner had to match or beat. Blue hatching: frequencies the Australian class licence rules out (the 20 kHz gap) or where a European cross-check leaves only 2.5 dB of margin (time-signal stations at 60, 66.6, 75 and 77.5 kHz), drawn at least 4 px wide so they can be seen. The dashed line is the codec maker’s quoted −3 dB point, which does not name the path; it has not been measured.

The drive current per tone falls as 1/f. Into an inductive coil that gives every tone the same induced voltage, which is what plain voltage drive into an inductance would do anyway, and it puts most of the current at the bottom of the band, where large and deeply buried objects answer. The total is set by the head’s exposure cap of 0.1 A rms. With the reference coil modelled as 0.72 mH and 2.1 Ω, the composite peaks at 5.16 V, well inside the amplifier’s 9 V limit, so the cap and not the amplifier sets the drive.

Seven sine waves added together have a peak that depends on their relative phases. The amplifier has to swing the peak, while the field and the exposure limit follow the rms, so a low ratio between the two, the crest factor, buys field strength for free. Starting from Schroeder’s phase formula and polishing with seeded Nelder–Mead restarts, the planner reaches 2.059. The specification originally asked for 2 or less; the M1 work found that unreachable with seven tones on a 1/f spectrum, logged the change in the decisions file and set the bound at 2.1.

One frame of drive current

goldray.dsp.tones.plan_amplitudes · 0.1 A rms cap · modelled coil 0.72 mH, 2.1 Ω
-200-10001002000246810time within one frame, mscoil current, mApeak 205 mArms 100 mA01 Crest factorpeak over rms is 2.059. The amplifier must swing the peak, while the field, and the exposure limit, follow the rms.Phases are searched (Schroeder start, Nelder–Mead polish) to keep the ratio low; with seven tones and a 1/f spectrumabout 2.05 is the floor.02 Frame2048 samples, 10.67 ms. Every tone completes a whole number of cycles in it, so one FFT per frame reads all sevenwith no window and no leakage between them.
The seven tones summed with the phases the optimiser found, sampled every fourth point. Dotted: the composite peak. Dashed teal: the rms current, the quantity the exposure cap limits.

For a sphere the tensor collapses to one number, and that number has an exact closed form, which is why ball bearings are Goldray’s calibration kit. The project’s M1 code implements it in a form stable at every frequency and tests it against both limits and an independent pole series.

Conducting, permeable sphere, radius a
β(ω)=2πa3 (2μr+1)(kacoth⁡ka−1)−(ka)2(μr−1)(kacoth⁡ka−1)+(ka)2,k2=iωσμ0μr\beta(\omega) = 2\pi a^3\, \frac{(2\mu_r + 1)(ka\coth ka - 1) - (ka)^2}{(\mu_r - 1)(ka\coth ka - 1) + (ka)^2}, \qquad k^2 = i\omega\sigma\mu_0\mu_r

Signatures in frequency

goldray.sim.targets.sphere_beta · normalised by 2πa³
-10121001k10k100k1MHzin-phase, β′ / 2πa³7-tone band00.20.40.61001k10k100k1MHzquadrature, −β″ / 2πa³gold 0.1 ggold 1 gcopper ball 10 mmsteel ball 10 mm01 Peaka non-magnetic sphere’s quadrature response peaks near the frequency set by its eddy-current time constant,σμ₀a²/π². A 1 g gold sphere (2.3 mm radius) peaks near 6.8 kHz; 0.1 g (1.1 mm) near 32 kHz.02 Signthe steel ball starts positive in-phase, because it is permeable, and only turns over at high frequency. A ferritewould stay positive and flat; hot rocks and magnetic soil sit between the two.
In-phase and quadrature parts of the polarizability of four spheres, from the closed-form solution goldray tests against its limits and a pole series. Conductivities are textbook values chosen for this figure: gold 4.1e7 S/m, copper 5.8e7 S/m, steel 5e6 S/m with relative permeability 100. Real nuggets are alloys and not spheres, so their curves shift; the shapes are the point.

The figure carries the central constraint of hunting small gold. A non-magnetic sphere’s quadrature response peaks near the frequency set by its first eddy-current time constant, σμ₀a²/π², which grows with the square of the radius. A 1 g sphere of gold-like conductivity, 2.3 mm in radius, peaks near 6.8 kHz. A 0.1 g sphere, 1.1 mm in radius, peaks near 32 kHz, and at 72 kHz its in-phase response has reached only about 60 % of its high-frequency limit. The top of the band is where the smallest pieces of gold say anything at all, which is why the plan pushes towards 76 kHz and why losing that tone, discussed below, costs more than one seventh of the data.

Discrimination then works on shape as well as spectrum. A compact nugget has three similar eigenvalues; a nail has one dominant, permeable axis, positive in-phase at low frequency; a ring-pull or a piece of foil behaves like a shorted loop, one dominant eigenvalue along its normal with a single-pole spectrum; a magnetic stone behaves like ferrite, real, positive and nearly flat. Real nuggets are irregular alloys and will not trace a sphere’s curve, which is why the specification plans a library of computed tensors for nugget-like blobs, nails, foil, coins and bottle caps, using the open-source MPT-Calculator finite-element code, and why the classifier is to be trained on dig outcomes logged in Goldfall rather than on the closed forms alone.

The transmit field is many orders of magnitude stronger than anything a small object sends back, and the receive winding sits right next to it. A double-D coil handles that geometrically. Two D-shaped windings lie side by side with their flat edges overlapping, and at one particular overlap the transmit flux passing through the receive D sums to zero. For the idealised reference DD, two semicircles of 140 mm radius, Biot–Savart integration puts that null at 19.17 mm of overlap.

Finding the null

goldray.sim.biot_savart · idealised DD · sim/reports/S1-kernels.md
-0.2-0.100.1010203040overlap of the two D windings, mmTX–RX coupling coefficient knull at 19.17 mm01 Nullslide the receive D over the transmit D and the flux it collects changes sign. At the null the coil hears almostnothing of its own transmitter, only what a target or the ground adds.02 Tolerancenear the null, k moves by 6.01e-4 for every 0.1 mm of overlap: −64 dB of the full coupling. The former leaves ±3 mmto trim before potting; the digital null takes over from there.
Coupling coefficient between the two D windings as their flat sides overlap, from mutual and self inductance computed by Biot–Savart. Squares are the computed points.

The null is sharp. Near it the coupling coefficient changes by 6.0 × 10⁻⁴ for every 0.1 mm of overlap, −64 dB of full coupling, so the printed former leaves ±3 mm of adjustment and the coil is trimmed by hand before potting, with a target residual of −40 dB. The Core then takes the leftover leakage out digitally. Its second output channel emits the same seven tones with adjustable complex weights, summed into the receive preamplifier, and during a short air null at the start of a session, with the coil held at least a metre from ground and metal, the weights are iterated until the residual is 40 dB below its un-nulled level.

Then the weights are frozen. The specification is blunt on this point: an adaptive canceller left running during a survey removes targets as readily as leakage. Slow drift after freezing is absorbed by the ground tracker’s offset term and reported, and the app prompts a re-null if it grows too large. The same machinery is meant to make a badly balanced or home-wound coil usable, which is as close as the project comes to digital tuning.

Mineralised soil is the main reason small gold is hard to find. Detectors deal with it through a ground balance that tracks one or two numbers and subtracts them. Goldray models the soil instead. With seven frequencies and the coil’s height known from the pose, the ground response splits into two physical terms. Viscous magnetic susceptibility, from the fine magnetic grains, gives an in-phase part that falls slowly and logarithmically with frequency and a constant quadrature part. Conductivity gives a quadrature part that rises in proportion to frequency. Each is a coil-geometry factor times a soil property, and the soil properties change slowly across the hex grid.

Ground response at tone k, in the Born approximation
Yg,k=χ(ωk) Sχ(h)+(−iωkμ0σ) Sσ(h)+bkY_{g,k} = \chi(\omega_k)\, S_\chi(h) + (-i\omega_k\mu_0\sigma)\, S_\sigma(h) + b_kχ(ω)≈χa−χb(ln⁡(ω/ωref)+iπ/2)\chi(\omega) \approx \chi_a - \chi_b\left(\ln(\omega/\omega_{\mathrm{ref}}) + i\pi/2\right)

The specification refused to assume the sign and form of the conductive term and required it to be derived from a full layered-earth solver. M1 did that with empymod, the open-source electromagnetic modelling code, cross-checked against a magnetostatic image method and two closed-form integrals to a few parts in 10⁵. The result: at low induction number the conductive term is purely imaginary, with a positive coupling factor, the same sign as a conductive target. For coincident small loops the magnetic term falls as the inverse cube of height and the conductive term only as the inverse of height.

How loudly the ground answers

goldray sim/reports/S3-ground.md · one turn each · image method and empymod
1e-41e-31e-21e-125102040coil height above ground, cmmagnetic soil, S_χ (m)1e-51e-41e-325102040coil height above ground, cmconductive soil, S_σ (m³)DD, at its nullsingle loop01 Humpthe DD’s magnetic ground coupling peaks near 5 cm and falls on both sides; a single loop’s keeps rising as it nearsthe ground. Bobbing the coil therefore changes the DD’s ground signal less near the surface.02 Two lawsfor small coils the magnetic term falls as 1/h³ and the conductive term only as 1/h. Height from the pose lets theprocessor scale both, rather than balance them away with one knob.
Ground coupling factors for an idealised DD at its null (pink) and a single 140 mm loop (blue), against height. Multiply by the turns in each winding for a real coil. The tilted and two-layer cases are not computed yet.

The DD produced one surprise. A single loop’s magnetic ground coupling keeps rising as the coil approaches the soil; the DD’s peaks near 5 cm and falls again below that. Coil bob near the surface therefore moves the DD’s ground signal less than a loop’s.

The ground state, two susceptibility parameters, a conductivity and a slow per-tone offset, is to be tracked by a Kalman filter whose process noise grows with distance moved over the ground rather than with time, since soil changes with place. Updates down-weight large innovations so a target cannot drag the ground estimate with it, and are suspended while the instant detection statistic is high. The posterior goes into the hex cell under the coil and seeds the next visit. As a by-product the app gets a mineralisation map, which a prospector may find as useful as the targets.

The linear model has a limit. In very conductive ground the induced currents interact with each other and the response stops scaling simply with conductivity. M1 measured where that happens, as the conductivity at which empymod’s exact half-space response departs from the linear term by 5 %.

Where the simple ground model fails

goldray sim/reports/S3-ground.md · empymod against the Born form
0.0010.010.1110251020coil height above ground, cmground conductivity where the linear model is 5 % off, S/mtypical soils, 0.001–0.1 S/msaline soil, salt lakes, 1–10 S/mat 1.5 kHzat 76 kHz01 Below the lineground less conductive than the curve is described by the simple linear (Born) model to 5 %. Above it, the modelneeds a correction. The open square at 2 cm means more than 10 S/m.02 Top toneat 76 kHz and 10 cm the limit is 0.064 S/m, inside the range of ordinary soil. Saline ground is beyond it at everyheight tested.
For a conductive half-space, the conductivity at which empymod’s full layered-earth response differs from the linear ground term by 5 %, at the lowest and highest tones. The hatched bands are the ranges quoted in the same report.

At the bottom tone the linear model holds to above 1 S/m at every height tested. At 76 kHz it holds to 0.14 S/m with the coil 5 cm up and to 0.064 S/m at 10 cm. Ordinary soils run from about 0.001 to 0.1 S/m, so the top tones already sit near the edge, and saline ground and salt-lake margins at 1 to 10 S/m are well past it at every height. There the specification calls for a non-Born correction, fitted to the solver directly, which has not been written.

Pose arrives over Bluetooth and may lag by 100 ms, and not every operator will want to wait for a map. Goldray therefore splits its output into tiers by latency. Tier 0 needs no pose at all: subtract the tracked ground, sum the residual’s power across tones against its noise, fit one time constant and a ferrous indicator, and send loudness and pitch to the phone within an 80 ms budget. It behaves like an ordinary detector and is meant to be useful from the first hour and never worse than one when tracking drops out.

Four speeds of answer

goldray docs/01 §3 · latency budgets, not measurements
10 ms100 ms1 s10 s1 min10 minlatency (log)Tier 0 · instantTier 1 · mapTier 2 · targetTier 3 · offline01 instanta ground-tracked tone, like any detector; works with no pose at all02 mapback-projection volume and a ground parameter per hex cell03 targetposition, depth, tensor spectra, class, confidence04 offlinewhole-session re-inversion on a laptop, training data05 RayNeta learned model for the residual the physics misses, held to about 40 ms a window on the Pi. It may label a targetonly when the physics fit agrees; it may not invent one.
Each bar spans its specified latency, from the 10.7 ms frame to the upper budget. Nothing has been timed on a Raspberry Pi yet; docs/04 §13 sets the compute budgets that M8 must meet.

All of it runs on the Core rather than the phone. Pose is the smaller stream, about 1.2 kB/s, and tolerates delay; rays are larger and are recorded where they are made. Keeping the physics on the Pi means the simulator, the offline tools and the live daemon are one Python package, recordings are self-contained on the Core’s SD card, and the change to Goldfall stays a thin Bluetooth client. An emulator mode replaces the audio hardware with the simulator, consuming a real or scripted pose track and producing the rays a described scene would, so the app integration can be built and tested before any hardware exists.

A neural model, RayNet, sits on top for three jobs the physics does poorly: suppressing structured interference and ground residual the parametric model misses, classifying from the tensor spectra with raw ray context, and proposing quick starting points for the exact solver. Its rule is restrictive. A detection is shown with a class only when the physics residual agrees with the network, and the network may never report a target the forward model cannot explain. The classical pipeline is the baseline every model has to beat.

The project’s documents list their own risks plainly, and several are already priced.

Codec roll-off at the top tone

The chosen codec board is a HiFiBerry DAC2 ADC Pro. HiFiBerry quotes 10 Hz to 70 kHz at −3 dB without saying which path that applies to, while the converter chip’s own filter passes about 87 kHz. The decision recorded in M0 is to keep the board, measure the input response at 76 kHz on the bench, and if it rolls off, cap the top tone near 63 kHz, bin 673. Given the polarizability curves, that is the tone the smallest gold needs most. For the bench stage an RME Babyface Pro FS, the one interface found with a published flat response to about 90 kHz, is the fallback.

Exposure cap

Halving the drive current for exposure costs about 6 dB of signal-to-noise ratio before the first measurement. The coil-design simulation, S2, is to recompute the cap from the real winding, and the link-budget simulation, S5, is to state the depth it costs. The concept document already concedes that single-pass depth will not beat a purpose-built detector at first; the claim is more information per detection and coherent gain from re-sweeping.

Pose quality

This is the largest risk the project has found so far, and M2 found it. Mapping tolerates centimetre pose error: at 1 cm the golden-scene sphere is still located to 1.1 cm. Classification does not. Running goldray’s Tier 2 test with the pose noise turned up, the sphere’s recovered eigenvalues are 18 % off at 1 cm, roughly what phone visual-inertial tracking gives, against 8 % at 2 mm and 2 % with perfect pose; a tilted shorted loop’s axis comes back 10.6° off at 1 cm and 2.3° at 2 mm. The specification’s own test allows 10 % and 10°.

Enough pose to map, not to classify

goldray M2 · tests/test_inversion.py run() swept over pose noise · seed 1
051015200246810pose noise, mmsphere eigenvalue error, %10 % (spec)phone VIO ≈ 1 cm048120246810pose noise, mmloop axis error, degrees10° (spec)phone VIO ≈ 1 cm01 Map vs classat 1 cm, about what the phone’s tracking gives, the back-projector still finds the sphere to 1.1 cm, but itsrecovered eigenvalues are 18.4 % off and the loop’s axis 10.6°. At 2 mm: 8.0 % and 2.3°.02 Teal bandup to about 3 mm of relative pose error across the rays a fit uses, the sphere test passes. That is a fewmillimetres held over seconds of swinging, not a centimetre.
For each pose-noise level, goldray’s Tier 2 test run is repeated: simulate three passes over the target, back-project, take the rays near the peak and invert for the tensor. Left: the largest error in the sphere’s recovered eigenvalues across the seven tones. Right: the angle between the recovered and true axis of a tilted shorted loop. One noise realisation per point, so the curves are not smooth; goldray’s decisions log, from its own runs, gives 20 % and 9.5° at 1 cm.

The project’s decisions log draws the consequence: the phone is enough to map, not to classify, and Tier 2 needs relative pose good to a few millimetres across the rays it uses. Fast swings blur the camera, a ground-facing view gives visual-inertial odometry little to track, and the coil sits about a metre from the phone at the end of a shaft that flexes. The proposed remedies are short windows of rays, an IMU on the Core to bridge between camera poses, and autofocus: sweep back and forth over a coin from two directions and tune the six phone-to-coil extrinsic parameters and a timing offset until the back-projected peak is sharpest. In simulation the autofocus recovers a 3.6 cm lever-arm error and a 30 ms timing offset to 1 mm and 0.3 ms, once its objective uses a flat-disc kernel; with the default isotropic kernel, a wrong lever arm scored 10 % better than the true one, because a coin lying flat responds only through its vertical axis. None of this has met a real phone.

Saline ground

As the Born figure shows, salty ground breaks the linear soil model at the top tones at every height. Without the correction, the ground fit would leave structured residual exactly where small gold appears.

One axis of illumination

A single coil swept in one direction excites a buried object mostly along one axis, so the tensor comes back well for that axis and poorly for the others. Tier 2 reports an excitation-diversity score, and when it is low the app is to ask for a second pass at 90°, flagging weak eigenvalues rather than reporting them as fact.

Commercial coils

Commercial search coils are wound for one frequency. Forum teardowns of one popular coil family report a transmit winding of about 2.4 mH with a tuning capacitor of about 12 nF built in, which resonates near 30 kHz. Such coils can run a reduced tone set or have the capacitor removed. The wideband reference coil, designed for bandwidth with a receive self-resonance of at least 150 kHz, is the primary head, and it has not been wound.

Nothing built

Every number in this article comes from a specification, a datasheet, a regulatory text or a simulation. The Pi has not been benchmarked against the compute budgets, the codec’s simultaneous 192 kHz duplex behaviour is unconfirmed, the amplifier is a hand-built OPA548T stage that SPICE has yet to show stable into the coil, and RayNet’s 40 ms inference target on the Pi is still marked for verification in the project’s own files.

Goldray is written to be built by a coding agent with a person doing the purchasing, soldering, printing and field work, and its plan orders the milestones so software never waits on hardware. Three of twelve are done, all dated 11 November 2026.

Twelve milestones, three done

goldray docs/12 and docs/PROGRESS.md · as of 11 November 2026
M0 · doneScaffold, verificationM1 · donePhysics coreM2 · doneDSP and inversionM3Daemon and emulatorM4Design simulationsM5Goldfall integrationM6Bench rigM7MechanicalM8Core buildM9Garden and groundM10RayNetM11More heads01 DoneM0 checked every part, library and regulatory claim against a primary source; M1 built the field, target, ground andtone code, each tested against a closed form; M2 the lock-in, ground tracker, back-projection and inversion, testedon synthetic scenes. All on 11 November 2026.02 Teal dota person is needed: buying, soldering, printing, transmitting, digging. Software reaches a working emulator (M3)before any hardware is bought; the first field result sits at M9.
The twelve milestones of the implementation plan. Solid: done. Dashed: not started. The critical path to a first field result runs M0, M1, M2, M3, M4, M6, M8, M9.

M0 checked every claim the specifications made from memory, each tagged for verification, against a datasheet, a library’s documentation or the legislation itself, and recorded the source and date. It corrected the regulatory basis, replaced a 12 V analogue supply that could swing only about ±4 V with a ±15 V split rail, swapped an amplifier module for a hand-built OPA548T stage, since no reputable module could be found, demoted commercial coils, and pinned the Python toolchain to 3.12, the one version that resolves the whole stack on both the development machine and the Pi. The recommended bill of materials starts at a USD 15 Raspberry Pi Zero 2 W and an AUD 119.90 codec board.

M1 built the physics core, and each module ships with a test against a closed form. Exact straight-segment Biot–Savart matches a circular loop’s on-axis field to 1.3 × 10⁻⁶ and Maxwell’s coaxial mutual inductance to 5.7 × 10⁻⁶. The sphere polarizability reproduces both of its limits and agrees with an independent pole series to 10⁻⁶. empymod’s ground kernel matches the image dipole to 2 × 10⁻⁵, and the viscous soil model, implemented through empymod’s permeability hook, agrees with the Born form to 3.5 × 10⁻⁵. The tone planner proves its products and keep-outs by enumeration.

One check failed for want of a source. The specification asked for the sphere formula to be cross-checked against a published table, from Wait in 1951 or Ward and Hohmann; both were paywalled, and the check uses the published pole series instead.

M2 added everything between a raw frame and a classified target, tested on synthetic data: lock-in recovering each tone to within 10⁻⁶ with less than 10⁻¹² leakage between tones, a digital null of at least 40 dB under 10 % gain and 5° phase error, the ground tracker holding its residual at the noise floor with 30 % soil variation and ±3 cm of coil bob while a planted sphere keeps 95 % of its signal, back-projection, inversion and autofocus. On an x86 container Tier 1 costs 0.7 ms a ray and Tier 2 1.8 s a candidate, inside budget; the ground tracker and Tier 0 together take 2.4 ms a frame on that machine, already over the 2 ms they are allowed on the much slower Pi. Optimising them is left for M8.

Next is M3, the daemon and its emulator, which lets the Goldfall integration be built against simulated scenes. After that come the first purchases for a bench rig, and only at M9, after a core build and a garden of buried test targets, a field result.

Method and sources

Source: drksci/goldray at commit 64a1a5e, read on 11 November 2026. Figure data were produced by running goldray’s own modules (goldray.dsp.tones, goldray.sim.biot_savart, goldray.sim.targets, goldray.sim.scene, goldray.dsp.backproject, goldray.dsp.inversion) under Python 3.12 through scripts/goldray/export_figures.py in this site’s repository, which writes components/labs/goldrayData.ts; the ground and Born tables are copied from goldray’s generated S3 report. The back-projection figure runs goldray’s golden scene (validation/scenes/sphere-10cm.yaml) with the 1 cm kernel grid. The pose-noise figure repeats goldray’s Tier 2 test (tests/test_inversion.py) at seven noise levels, one realisation each, so its values differ slightly from those in goldray’s decisions log (20 % and 9.5° at 1 cm there, 18.4 % and 10.6° here). Sphere conductivities in the polarizability figure (gold 4.1 × 10⁷ S/m, copper 5.8 × 10⁷ S/m, steel 5 × 10⁶ S/m with μr 100) are textbook values chosen for the figure, and nugget masses were converted to radii at 19.3 g/cm³. The drive waveform uses the reference-head descriptor’s example impedance, 0.72 mH and 2.1 Ω. The goldray specifications, verification files and M0–M2 code were produced by Claude Code sessions under the author’s direction. No hardware was built, no field was transmitted and nothing was measured.

  1. Concept, architecture and risks
    context · internal · goldray README.md, docs/00-concept.md, docs/01-architecture.md · as of 2026-11-11
  2. Signal processing: tone rules, lock-in, null, ground model, forward model, Tier 0–2
    context · internal · goldray docs/04-signal-processing.md · as of 2026-11-11
  3. Tone planner output, product clearance, crest factor and amplitudes
    derived · internal · goldray src/goldray/dsp/tones.py; tests/test_tones.py · as of 2026-11-11
  4. S1 field and kernel library: validation and idealised DD null
    derived · internal · goldray sim/reports/S1-kernels.md; src/goldray/sim/biot_savart.py · as of 2026-11-11
  5. S3 ground models: Born coupling tables, conductive term, validity limits
    derived · internal · goldray sim/reports/S3-ground.md; src/goldray/sim/ground.py · as of 2026-11-11
  6. Analytic target polarizabilities: sphere, shorted loop, ferrite
    derived · internal · goldray src/goldray/sim/targets.py; tests/test_targets.py · as of 2026-11-11
  7. Golden-scene back-projection and Tier 2 pose-noise sweep, run for this piece with goldray M2 code
    derived · internal · scripts/goldray/export_figures.py (APERTURE, POSE); goldray tests/test_backproject.py, tests/test_inversion.py, DECISIONS.md · as of 2026-11-11
  8. derived · public · goldray docs/regulatory-check.md, docs/11-safety-regulatory.md · as of 2026-11-11
  9. Verified hardware and the recommended v1 bill of materials
    context · internal · goldray hw/bom/verified.md · as of 2026-11-11
  10. Decisions, implementation plan and progress log
    context · internal · goldray docs/DECISIONS.md, docs/12-implementation-plan.md, docs/PROGRESS.md · as of 2026-11-11