matlab/matlab-simulate-radar-detections
> Configure, simulate, debug, and analyze radarDataGenerator within radarScenario. performance requirements; diagnosing missed detections and configuration errors; interpreting sensor spherical, body, and scenario-frame outputs; deriving ReferenceRange from hardware specs via link budget; scan mode configuration (mechanical, electronic/AESA, hybrid); and validating simulation results against analytical predictions.
npx skills add https://github.com/matlab/matlab-agentic-toolkit --skill matlab-simulate-radar-detections
Build detection-level radar simulations using radarDataGenerator within radarScenario. This skill bridges user hardware specs and performance requirements to the Radar Toolbox statistical simulation API.
radarDataGenerator code that isn't working — missed detections, configuration errorsradarTransceiver + pulse-Doppler chain)radarDataGenerator standalone (without radarScenario) in a custom simulation loopIf the user needs signal-level fidelity, explain the tradeoff and hand off.
radarDataGenerator supports two detection pathways. This skill uses the target-pose pathway exclusively:
| Pathway | Call Signature | Detection Governed By | When Used |
|---------|---------------|----------------------|-----------|
| Target-pose (this skill) | detect(scenario) | DetectionProbability, FalseAlarmRate, ReferenceRange, ReferenceRCS | Standard radar simulation — targets defined as platforms with trajectories |
| Emissions | detect(scenario, propagatedEmissions) | Sensitivity, DetectionThreshold | ESM receivers, bistatic with explicit emission propagation |
Properties from one pathway have zero effect on the other. Setting Sensitivity or DetectionThreshold in the target-pose pathway produces a "not relevant" warning.
Standalone mode: radarDataGenerator can also be called outside a scenario: dets, numDets, config] = rdg(targetPoses, simTime). Use for integration into custom loops (Simulink, event-driven). Loses advance(), trajectory automation, multi-sensor aggregation, and coverage visualization. See [references/detection-model.md for the full standalone API and pose struct requirements.
Follow these 9 steps interactively. Do NOT silently choose parameters — engage the user at each decision point.
Recommend statistical-level simulation using radarDataGenerator within radarScenario. Explain the tradeoff: fast iteration on scenario design vs less control over signal processing. If user needs I/Q-level fidelity, name the alternative path (radarTransceiver + pulse-Doppler + CFAR) and stop the structured 9-step flow.
When they confirm statistical-level, state the approach and name the APIs: radarScenario, radarDataGenerator, platform, waypointTrajectory/kinematicTrajectory/geoTrajectory.
Suggest a use case (e.g., ground-based surveillance scanning a sector). Confirm:
atmosphere(scenario, model) — 'EffectiveEarth' (4/3 radius), 'RefractivityGradient', or 'CRPL'. These add refraction bias to propagation paths (ray bending), affecting reported target positions — they do NOT add atmospheric attenuation to the link budget. Weather/precipitation is NOT modeled at statistical level.> "Which direction are you working? Top-down (specify requirements, derive hardware)? Bottom-up (specify hardware, derive performance)? Or a mix?"
If user provides a datasheet: follow the datasheet ingestion procedure in references/coupled-parameters.md — extract parameters, map to groups, identify gaps, close link budgets, flag conflicts.
The flow branches here:
Top-down path (Steps 4 → 5): User specifies performance requirements first, then derive hardware.
Bottom-up path (Steps 5 → 4): User specifies hardware first, then derive performance.
radareqrngMixed/Datasheet: Collect what they have, fill gaps from both directions, flag inconsistencies.
Both paths converge at Step 6 (Target Set Design).
Present as a reference target specification:
ReferenceRCS is in dBsm)Top-down: Present concrete defaults. Let user react/modify.
Bottom-up: Present values *derived from their hardware*. Show the derivation (which function, which inputs). For integration: assume coherent, ask number of pulses or CPI duration. Use detectability(Pd, Pfa, 1, 'SwerlingN') - 10*log10(N) for required SNR. The Swerling argument is a string: 'Swerling0', 'Swerling1', ..., 'Swerling4'. Never pass N to detectability for coherent systems — that applies non-coherent loss. See references/interaction-flow.md § Step 4 for the full decision table.
Show the parameter-relationship table from references/coupled-parameters.md. This builds confidence, shows traceability, invites correction.
Top-down: Use the table to derive what hardware is needed to meet the agreed reference performance.
Bottom-up: Use the table to collect the user's hardware specs and identify which groups are constrained.
Confirm geometry (radar placement, scan sector, airborne targets). Propose physically representative targets varying:
Offer Swerling models (I = slow-fluctuating, III = dominant scatterer). Configure per-target RCS via rcsSignature on each platform's Signatures property — see references/detection-model.md for patterns. Default platform RCS is 10 dBsm (Swerling0).
Sanity checks before proceeding:
horizonrange(antennaHeight). If any target is beyond LOS at its specified altitude, flag this to the user.| Target | RCS (dBsm) | Swerling | Range (km) | Expected Pd |
|--------|-----------|----------|-----------|-------------|
| UAV | -20 | 1 | 15 | 0.72 |
| Fighter | 0 | 1 | 40 | 0.95 |
Use: SNR_at_R = RadarLoopGain + RCS_dBsm - 40*log10(range), then map SNR to Pd with the correct Swerling formula (see references/radar-equation-tools.md). Flag any target where expected Pd < 0.5 — the user should know which targets will have unreliable detection before running the sim.
Ask: "Do you need terrain or ground returns, or is free-space sufficient?"
If applicable — see references/terrain-clutter-atmosphere.md for terrain options. Terrain and occlusion are additive after validating detections in free-space. landSurface for height maps, seaSurface for sea state, customSurface for user-defined. landSurface has occlusion() for LOS blocking. HasOcclusion on radarDataGenerator is target-to-target occlusion.
Ask in user's terms: seconds, number of scans, number of target illuminations, or event-based. Convert between these once scan parameters are locked.
Generate a standalone document with three sections — see references/requirements-sheet-template.md.
| Function | Purpose | Toolbox |
|----------|---------|---------|
| radarScenario | Scenario container (platforms, time, detect) | Radar |
| radarDataGenerator | Statistical detection sensor | Radar |
| platform | Add platform to scenario | Radar |
| waypointTrajectory | Waypoint-based motion in local coords (has ReferenceFrame) | Radar |
| kinematicTrajectory | State-based motion in local coords (NO ReferenceFrame) | Radar |
| geoTrajectory | Waypoint-based motion in geodetic coords (lat/lon/alt) — requires IsEarthCentered = true | Radar |
| radareqrng | Max detection range from radar equation | Radar |
| radareqpow | Required Tx power | Radar |
| radareqsnr | Received SNR at range | Radar |
| detectability | Required SNR (detectability factor) for Pd/Pfa/N/Swerling | Radar |
| albersheim | Required SNR for Pd/Pfa/N (Swerling 0 only) | Phased Array |
| shnidman | Required SNR for Pd/Pfa/N/Swerling 0–4 | Phased Array |
| horizonrange | Radar horizon from antenna height | Radar |
| height2el | Elevation angle from target height/range | Radar |
| freq2wavelen | Wavelength from frequency | Phased Array |
| rangeres2bw | Bandwidth from range resolution | Phased Array |
| bw2rangeres | Range resolution from bandwidth | Phased Array |
| speed2dop | Doppler shift from speed. One-way convention — for monostatic two-way: fd = 2*speed2dop(v, lambda) | Phased Array |
| dop2speed | Speed from Doppler shift. One-way convention — for monostatic two-way: v = dop2speed(fd, lambda)/2 or use lambda*fd/2 directly | Phased Array |
| beamwidth2gain | Antenna gain from half-power beamwidth. Must pass [azBW; elBW] column vector — scalar assumes symmetric beam. | Phased Array |
| aperture2gain | Antenna gain from effective aperture | Phased Array |
| gain2aperture | Effective aperture from antenna gain | Phased Array |
| ap2beamwidth | Beamwidth from aperture length and wavelength | Phased Array |
| beamwidth2ap | Aperture length from beamwidth and wavelength | Phased Array |
| effbeamwidth | Two-way effective beamwidth (Tx+Rx) → maps to AzimuthResolution/ElevationResolution | Phased Array |
| systemp | System noise temperature | Phased Array |
| noisepow | Noise power from temperature + bandwidth | Phased Array |
| theaterplot | Scenario visualization | Radar |
| coverageConfig | Coverage diagram | Radar |
| radarmetricplot | Plot metric vs range with objective/threshold lines and stoplight | Radar |
| orientationPlotter | Visualize beam pointing direction (theaterPlot plotter) | Radar |
| detectionPlotter | Visualize detections on theater plot | Radar |
| landSurface | Static terrain (height matrix or DTED) — occlusion only with IsEarthCentered=true | Radar |
| seaSurface | Dynamic ocean surface (spectral model, wind, fetch) | Radar |
| customSurface | Polarization scattering matrix surface for clutter | Radar |
| surfaceReflectivityLand | Land clutter reflectivity model (Barton, GIT, etc.) | Radar |
| surfaceReflectivitySea | Sea clutter reflectivity model | Radar |
| clutterGenerator | Add clutter to scenario (1:1 with radar sensor, requires a surface) | Radar |
| ringClutterRegion | Define explicit clutter region (required when UseBeam=false) | Radar |
| getClutterGenerator | Retrieve existing clutter generator for a radar | Radar |
radarDataGenerator provides built-in traceability — property names ARE the documentationwaypointTrajectory has 'ReferenceFrame' property: 'NED' or 'ENU' — use with IsEarthCentered = falsekinematicTrajectory has NO ReferenceFrame — inherits from scenario — use with IsEarthCentered = falsegeoTrajectory uses geodetic waypoints [lat, lon, alt] in [deg, deg, m] — requires IsEarthCentered = true'ReferenceFrame' ('NED'/'ENU') for velocity/orientation interpretationCourse, GroundSpeed, ClimbRate as alternatives to VelocitiesDetectionCoordinates = 'Scenario' reports in ECEF (meters), not lat/lonIsEarthCentered are strictly coupled — mixing produces an errorEvery parameter in the requirements sheet gets a tag:
Once the user's requirements and use case are confirmed (Steps 2–6), distinguish between:
If a simulation artifact occurs (e.g., missed detections due to beam stepping), fix it by adjusting simulation parameters. If the fix requires changing a system parameter, surface it to the user with physical intuition — it's a design insight, not a sim fix.
After deriving all parameters, validate the loop closes:
radareqrngReferenceRange = Rmax| Mistake | Why It's Wrong | Correct Approach |
|---------|---------------|-----------------|
| Changing non-tunable properties without release() | radarDataGenerator is a System object — non-tunable properties (FieldOfView, ScanMode, AzimuthResolution, scan limits) are locked after first call to detect() | Call release(radar) before modifying non-tunable properties, then restart(scenario) before re-running |
| ReferenceRCS in linear m² | Property expects dBsm | Convert: 10*log10(rcs_linear) |
| range2bw / bw2range | Deprecated | Use rangeres2bw / bw2rangeres |
| MechanicalScanLimits | Does not exist | Use MechanicalAzimuthLimits, MechanicalElevationLimits |
| Setting MaxAzimuthScanRate for electronic scan | Ignored (warning issued) | Beam steps by FieldOfView(1) per update in electronic mode |
| Confusing detect(scenario) with detect(platform, time) | Different signatures — scenario-level returns all sensors' detections combined | dets = detect(scenario) or [dets, configs] = detect(scenario) — both valid. Per-platform: [dets, ~, configs] = detect(platform, time) |
| Forgetting elevation sign in NED | Targets above horizon have negative elevation | Check sign: el = -asind(alt/range) in NED |
| HasScanLoss with non-Custom scan | Only applies when ScanMode='Custom' | Omit for Mechanical/Electronic modes |
| Mixing trajectory reference frames | Causes silent position errors | Set all waypointTrajectory objects to same 'ReferenceFrame' |
| waypointTrajectory shorter than StopTime | Scenario stops at max trajectory endpoint, NOT at StopTime. Platform goes to NaN position after trajectory ends — silently stops being detected. | Extend all waypointTrajectory endpoints to >= StopTime (add hold waypoint). Or use kinematicTrajectory for constant-velocity platforms. |
| Confusing radar/waypointTrajectory with drivingScenario trajectories | Different API — drivingScenario uses actor waypoints, not this class | In radarScenario, always use waypointTrajectory, kinematicTrajectory, or geoTrajectory |
| Using kinematicTrajectory for a moving platform with a forward-looking sensor | Body frame orientation is FIXED (default: identity = scenario axes). Does NOT rotate with velocity — a forward-looking sensor will not track the flight path. | Use waypointTrajectory or geoTrajectory for moving platforms with sensors. Their body frame rotates with heading. |
| Hardcoded noise (e.g., 0.2*randn) | Not traceable, breaks self-consistency | Use radarDataGenerator built-in noise model or comment the source |
| Not setting sensor UpdateRate | Default is 1 Hz — scan rate = FoV(1)*1 = crawl | Always set 'UpdateRate' explicitly on radarDataGenerator |
| Scenario UpdateRate < sensor UpdateRate | Aliasing: only a fraction of FoV dwell positions are sampled. Targets between sampled positions get zero detections silently. | Set scenario UpdateRate = sensor UpdateRate for detection-complete sims. |
| Comparing sensor rectangular measurements directly to scenario-frame truth | Sensor rectangular is a beam-rotating frame — each detection's coordinate system depends on where the beam was pointing at detection time. Raw measurements will differ from truth by tens of km. | Use per-detection MeasurementParameters(1): scenarioPos = Orientation * Measurement + OriginPosition. The Orientation matrix encodes mounting + current scan angle. |
| Assuming spherical measurement order is [range, az, el] | Actual order is [az, el, range] for 'Sensor spherical' | Check DetectionCoordinates setting; default is 'Body' (Cartesian) |
| Not setting RangeLimits | Default is [0, 100 km] — targets beyond 100 km silently produce no detections | Set 'RangeLimits', [0, R] where R = max(maxTargetRange, ReferenceRange * 1.2) |
| Using 'Sector' with MechanicalElevationLimits | 'Sector' sets HasElevation=false — elevation limits are ignored (warning issued) | Use explicit 'ScanMode','Mechanical' with HasElevation=true if elevation scanning is needed |
| Setting or investigating HasElevation for detection issues | HasElevation does NOT affect whether targets are detected. It only controls whether elevation angle is measured and whether the beam scans in elevation. Default is false (azimuth-only). | Never set HasElevation=false (it's already the default). Never investigate HasElevation when debugging missing detections — it cannot cause them. Missing detections are caused by: RangeLimits, target outside scan sector, insufficient ReferenceRange/ReferenceRCS, or UpdateRate too low. |
| Calling atmosphere(scene, model) with IsEarthCentered=false | Errors: "The IsEarthCentered property must be true to modify the atmosphere model" | All atmosphere/refraction models require IsEarthCentered=true. Flat-earth scenarios are always free-space. |
| Expecting terrain to block targets with IsEarthCentered=false | Terrain occlusion in detect() only works with IsEarthCentered=true | If terrain masking matters, use earth-centered scenario with geoTrajectory |
| Confusing HasOcclusion with terrain occlusion | HasOcclusion is target-to-target (extended objects); terrain LOS is via SurfaceManager | For terrain masking: IsEarthCentered=true + landSurface. For target-to-target: HasOcclusion=true |
| clutterGenerator without any surface | Produces 0 clutter detections silently | Add landSurface(scene) or seaSurface(scene) before creating clutter generator |
| ScattererDistribution='RangeDopplerCells' with radarDataGenerator | Error at detect() | Only works with radarTransceiver; use 'Uniform' (default) for radarDataGenerator |
| UseBeam=false without ringClutterRegion | 0 clutter detections | Add explicit regions: ringClutterRegion(cg, minR, maxR, azSpan, azCenter) |
| Setting Sensitivity or DetectionThreshold in target-pose mode | "Not relevant" — these are emissions-pathway only | In the target-pose pathway (detect(scenario)), detection is governed solely by Pd/Pfa/ReferenceRange/ReferenceRCS |
| Increasing FalseAlarmRate to simulate surface clutter | FalseAlarmRate produces uniformly distributed false detections (white noise); it does not model spatially correlated surface clutter with realistic sigma-zero, geometry, or Doppler | Use clutterGenerator with a surface (landSurface/seaSurface) and reflectivity model. See terrain-clutter-atmosphere.md. |
| Expecting radarDataGenerator to model signal processing losses (MTI, STAP, CFAR) | The statistical model does not simulate clutter-rejection filter losses, STAP adaptive weight losses, or detectability degradation near the clutter ridge in range-Doppler space | These effects require I/Q-level simulation (Phased Array System Toolbox waveform + receiver chain). For statistical-level approximation, add expected processing losses via the Loss parameter in the link budget (radareqsnr), or reduce DetectionProbability in clutter-affected regions. |
| dop2speed(1/CPI, lambda) for monostatic velocity resolution | Gives 2× correct value (3.0 m/s instead of 1.5 m/s) | dop2speed uses one-way convention. Monostatic velocity resolution = lambda/(2*CPI). Either use manual formula or dop2speed(fd,lambda)/2. |
| radareqrng(SNR, lambda, ...) — wrong arg order | Silent wrong answer (no error) | Correct: radareqrng(lambda, SNR, Pt, tau, ...) — lambda first |
| Passing Gain in linear to radareqrng | Absurd range (10^49 km) | Gain is dBi, not linear. Pass 30, not 1000. |
| Passing RCS in dBsm to radareqrng | Wrong by 3× or errors on negative | RCS is linear m². Convert: db2pow(rcs_dBsm) |
| Passing average power to radareqrng as Pt | Underestimates range | Pt is peak power (Watts). Derive: Ppeak = Pavg / (tau * PRF) |
| Using FieldOfView for antenna gain derivation | Wrong gain when FoV ≠ beamwidth | Gain comes from beamwidth2gain([AzimuthResolution; ElevationResolution]). Both beamwidths required — scalar input assumes symmetric beam (up to 10 dB error for fan beams). FoV is the angular coverage per scan position; it is not necessarily equal to the receive beamwidth. |
| Assuming FieldOfView must equal AzimuthResolution | Incorrect scan step for AESA or wide-Tx configurations | FoV and resolution are independent. FoV = Tx beamwidth (typical). For shared aperture, FoV = AzRes. For wide Tx + narrow Rx, FoV > AzRes. |
| Applying Swerling I Pd formula to a default (Swerling0) target | Underpredicts Pd by ~40% at reference range (0.52 vs 0.89) | Check target.Signatures{1}.FluctuationModel. Default is Swerling0 — use marcumq. For Swerling1 targets: Pfa^(1/(1+SNR_lin)). |
| Mode | Key Behavior |
|------|-------------|
| 'Mechanical' | actualRate = min(FieldOfView(1) * sensorUpdateRate, MaxAzimuthScanRate). See scan rate model below. |
| 'Electronic' | Beam steps by FieldOfView(1) per update. MaxAzimuthScanRate irrelevant. |
| 'Mechanical and electronic' | Mechanical provides coarse pointing, electronic refines. |
| 'No scanning' | Fixed staring beam. |
Convenience constructors: radarDataGenerator(id, 'Raster'), 'Rotator', 'Sector', 'Custom'.
The actual scan rate for mechanical mode is:
actualScanRate = min(FieldOfView(1) * sensorUpdateRate, MaxAzimuthScanRate) [deg/s]
stepPerScenarioUpdate = actualScanRate / scenarioUpdateRate [deg]
Default sensor UpdateRate is 1 Hz. If not set explicitly, a 2-deg beam scans at only 2 deg/s regardless of MaxAzimuthScanRate. Always set sensor UpdateRate explicitly:
radar = radarDataGenerator(1, 'Sector', ...
'UpdateRate', 20, ... % MUST set — default 1 Hz causes very slow scan
'FieldOfView', [2; 20], ...
'MaxAzimuthScanRate', 36, ...
...);
% actualRate = min(2*20, 36) = 36 deg/s ✓
To achieve a desired scan rate with a narrow beam:
sensorUpdateRate >= desiredRate / FieldOfView(1)UpdateRate >= 18 HzThe sensor fires at its own UpdateRate; the scenario UpdateRate controls how often advance()/detect() execute. If scenario rate < sensor rate, only a fraction of dwell positions are sampled — targets between sampled positions produce zero detections silently.
Rule: Always set scenario UpdateRate = sensor UpdateRate unless you specifically want sparse detection cadence.
SNR = RadarLoopGain + RCS_dBsm - 40*log10(Range)
RadarLoopGain: read-only, derived from ReferenceRange + ReferenceRCS + DetectionProbability + FalseAlarmRate. The reference SNR uses Swerling0 (non-fluctuating) internally — target fluctuation models are applied separately at detection time.CenterFrequency/Bandwidth affect resolution only, not detection rangeσ² = (Δ/√(2·SNR))² + (BiasFraction·Δ)² — Cramér-Rao plus bias floor. Defaults: AzimuthBiasFraction=0.1, RangeBiasFraction=0.05, ElevationBiasFraction=0.1. At high SNR the bias floor dominates (0.3° azimuth, 3.75m range).marcumq for Swerling0, Pfa^(1/(1+SNR)) for Swerling1. Using the wrong formula gives ~40% Pd error. See references/radar-equation-tools.md for the full recipe.The agent's job is to bridge between user hardware specs and this statistical interface using radar equation tools as the translation layer.
| Symptom | Likely Cause | Diagnostic / Fix |
|---------|-------------|-----------------|
| No detections at all | RangeLimits too short | Check radar.RangeLimits(2) >= max target range |
| No detections at all | Sensor UpdateRate = 1 Hz (default) | Beam scanning too slowly — set UpdateRate explicitly |
| No detections at all (UpdateRate is set) | Scenario UpdateRate < sensor UpdateRate | FoV dwell aliasing — only sampled positions produce detections. Set scenario UpdateRate = sensor UpdateRate. |
| No detections at all | Target outside beam FoV | Use coverageConfig(radar) to get current beam direction; compare to target bearing |
| Detections only on some scans | Target near beam step boundary | Normal for mechanical scan — fast targets at bearings aligned with beam step positions may be missed on some sweeps. Increase sensor/scenario UpdateRate to reduce step size (never change system parameters like FieldOfView to fix a simulation artifact). |
| Too many detections | Self-detection (radar platform) | Filter: ignore detections where TargetIndex == radar platform ID (usually 1) |
| Pd much lower than configured | Target beyond ReferenceRange | SNR drops as 40·log10(R) — expected; Pd is specified AT reference range only |
| Pd higher than expected | Target RCS > ReferenceRCS | Higher RCS increases SNR — check rcsSignature on target platform |
| Empirical Pd > 1.0 | Dividing detections by scans, not illuminations | Mechanical scan is bidirectional: illuminations = 2 × scans for sector scan. Count beam passes from IsScanDone or compute from scan timing: nIlluminations = StopTime / (sector / scanRate). |
| Measured position far from truth | Plotting raw .Measurement against scenario-frame truth | Must convert to scenario frame first. Use MeasurementParameters.Orientation and .OriginPosition to transform. "0 azimuth" = sensor boresight, not north. See API Answer Key for conversion recipes. |
| Range-limited vs LOS-limited | Targets beyond radar horizon | Use horizonrange(antennaHeight) to check. Comment in code where missed detections are expected due to LOS. This should also be caught as a sanity check before writing code (Step 6 — verify target geometry is within radar horizon). |
| Self-consistency check fails | Hardware-derived Rmax ≠ configured ReferenceRange | Recheck radareqrng inputs match the hardware specs fed to the derivation |
Detection accuracy check — verify measurement errors are within expected bounds. This must account for DetectionCoordinates mode — raw .Measurement values cannot be compared to scenario-frame truth unless using 'Scenario' mode:
% Convert detection to scenario frame FIRST (required for all non-Scenario modes)
mp = dets{k}.MeasurementParameters(1); % first element = position transform
meas = dets{k}.Measurement;
switch mp.Frame
case 'Spherical'
az = meas(1); el = meas(2); R = meas(3);
posSensor = [R*cosd(el)*cosd(az); R*cosd(el)*sind(az); R*sind(el)];
measScenario = mp.Orientation * posSensor + mp.OriginPosition(:);
case 'Rectangular'
measScenario = mp.Orientation * meas(1:3)' + mp.OriginPosition(:);
end
posError = norm(measScenario - truthPos);
expectedSigma = sqrt(trace(dets{k}.MeasurementNoise(1:3,1:3)));
assert(posError < 5*expectedSigma, 'Position error exceeds 5-sigma');
SNR check — verify reported SNR matches link budget prediction (within ~1 dB):
expectedSNR = radar.RadarLoopGain + rcs_dBsm - 40*log10(slantRange);
reportedSNR = dets{k}.ObjectAttributes{1}.SNR;
assert(abs(reportedSNR - expectedSNR) < 1.0, 'SNR mismatch > 1 dB');
See Coordinates & Transforms — Verification for full mode-aware verification patterns.
When diagnosing "no detections" or unexpected detection gaps, isolate whether the problem is beam pointing vs. something else:
origFoV = radar.FieldOfView;
origScan = radar.ScanMode;
release(radar);
radar.FieldOfView = [360; 180];
radar.ScanMode = 'No scanning';
restart(scenario) + advance loop)release(radar);
radar.FieldOfView = origFoV;
radar.ScanMode = origScan;
Note: radarDataGenerator is a System object. Non-tunable properties (FieldOfView, ScanMode, AzimuthResolution, scan limits, etc.) cannot be changed after detect() has been called without first calling release(radar). Always release() before modifying non-tunable properties.
Do this BEFORE drilling into individual parameters — it splits the problem space in half with one test.
When validation fails, fix the latest step that could be wrong. Never change a system parameter (Steps 1–5) to accommodate a scenario choice (Steps 6–7). If the radar's FoV doesn't cover a target, the target is out of coverage — don't widen FoV to "fix" it. Only revisit earlier steps when there is a genuine design error.
Critical NED traps that cause silent errors:
atand(-dx(3) ./ horizRange) — negate D-axis so airborne targets get positive angle. Sensor spherical elevation has opposite sign for NED (sensor +z = Down): airborne targets get negative el.atan2d(East, North) — swapping arguments gives azimuth from East (90° error)platformID = targetIndex + 1 (radar is platform 1) — off-by-one is commonmarcumq), not Swerling I — using wrong formula gives 40% errorRadarLoopGain + RCS_dBsm - 40*log10(R) — do NOT add integration gain (already in RLG)See Coordinates & Transforms for full conversion recipes and verification patterns.
radarDataGenerator. Set every relevant property explicitly in the constructor, even when using the default value. This makes the code self-documenting — a reader should see every parameter choice without consulting documentation. Hidden defaults are hidden assumptions. Exception — property gating: Do NOT set properties that are irrelevant to the current configuration. Setting gated properties produces warnings ("not relevant in this configuration"). See the property-gating table below.Properties gated by Has* flags — do NOT set these when the flag is false:
| Gate (when false) | Do NOT set |
|---------------------|-----------|
| HasElevation | ElevationResolution, ElevationBiasFraction, MaxElevationScanRate, MechanicalElevationLimits, ElectronicElevationLimits |
| HasRangeRate | RangeRateResolution, RangeRateBiasFraction, RangeRateLimits, HasRangeRateAmbiguities |
| HasRangeAmbiguities | MaxUnambiguousRange |
| HasRangeRateAmbiguities | MaxUnambiguousRadialSpeed |
Properties gated by ScanMode:
| ScanMode | Do NOT set |
|----------|-----------|
| 'No scanning' | MaxAzimuthScanRate, MaxElevationScanRate, MechanicalAzimuthLimits, MechanicalElevationLimits, ElectronicAzimuthLimits, ElectronicElevationLimits |
| 'Mechanical' | ElectronicAzimuthLimits, ElectronicElevationLimits |
| 'Electronic' | MechanicalAzimuthLimits, MechanicalElevationLimits, MaxAzimuthScanRate, MaxElevationScanRate |
| 'Mechanical and electronic' | (all scan properties valid) |
Never set HasScanLoss — it warns in all scan modes (not valid in any current configuration).
When EmissionsInputPort=true: do NOT set ScanMode, FieldOfView, or any scan-limit/rate properties (scan is driven by emissions).
Properties gated by TargetReportFormat:
| Format | Do NOT set |
|--------|-----------|
| 'Clustered detections' or 'Detections' | FilterInitializationFcn, ConfirmationThreshold, DeletionThreshold, TrackCoordinates |
| 'Tracks' | DetectionCoordinates |
Properties gated by DetectionMode:
| Mode | Do NOT set |
|------|-----------|
| 'ESM' | ReferenceRange, ReferenceRCS, DetectionProbability |
Load only what the task requires — do not read all references for every prompt.
| Reference | When to Read |
|-----------|-------------|
| Detection Model | Configuring radar properties, debugging property names, understanding detection pathway |
| FoV & Scan | Setting FieldOfView, scan mode config, UpdateRate/scan rate derivation |
| Coordinates & Transforms | Interpreting detections, converting frames, NED elevation/azimuth, plotting |
| Trajectories | Target motion, stationary platforms, scenario duration, body frame orientation |
| Terrain, Clutter & Atmosphere | Adding surfaces, clutter generator, atmosphere models, occlusion |
| Radar Equation Tools | Bottom-up derivation, hardware-to-ReferenceRange bridge, Pd vs range |
| Visualization | theaterPlot, coverage plots, radarmetricplot |
| Coupled Parameters | Step 5: parameter-relationship table, datasheet ingestion |
| Interaction Flow | Full 9-step workflow with suggested phrasings |
| Requirements Sheet Template | Step 9: three-section output artifact structure |
----
Copyright 2026 The MathWorks, Inc.
Take matlab/matlab-simulate-radar-detections from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.