Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
d6a8bf9
refactor(libfers): prepare codebase for implementing additional propa…
SFBdragon Sep 5, 2026
d9e64c2
fix(libfers): fix libfers tests and bugs
SFBdragon Sep 7, 2026
809b940
fix(verification/oversampling): fix to match libfers refactor
SFBdragon Sep 7, 2026
a47ee28
fix(simulation): reinstate FMCW trackers
SFBdragon Sep 8, 2026
5f61376
Merge stpaine/FERS master: port file-backed CW/FMCW waveforms onto th…
SFBdragon Sep 8, 2026
b68e3d7
tweaks(libfers): remove unused field and import, remove stale comments
SFBdragon Sep 8, 2026
7e49d6a
fix(libfers): remove usage of std::generator to preserve GCC 13 compa…
SFBdragon Sep 8, 2026
32f887a
Merge pull request #2 from SFBdragon/merge-david-waveforms
SFBdragon Sep 8, 2026
82d60a2
fix(propagation): use modulated carrier frequency instead of nominal,…
SFBdragon Sep 15, 2026
25e0df0
fix(libfers): fix pointscatter carrier modulation check, add SPDX hea…
SFBdragon Sep 15, 2026
7881163
feat(libfers): raytracing-based propagation model with CUDA/OptiX bac…
SFBdragon Sep 22, 2026
c0903d3
Merge branch 'master' into raytracing
SFBdragon Sep 22, 2026
42a8b9e
fix(libfers): PointScatterModel fixes, polarisation wiring and test f…
SFBdragon Sep 23, 2026
9ac3ed8
fix(libfers): Rewrites streaming source remove to avoid assuming Poin…
SFBdragon Sep 30, 2026
b9941a6
feat(libfers): implement full-path radiation calculations, remove act…
SFBdragon Sep 30, 2026
2ca5c55
Merge branch 'streaming_cleanup' of https://github.com/SFBdragon/FERS…
SFBdragon Sep 30, 2026
6aff9ee
fix(libfers): address change in GO step limit behaviour - merge fix
SFBdragon Sep 30, 2026
e1e8169
fix(libfers): fix fresnel equation, taper appropriately based on non-…
SFBdragon Oct 1, 2026
265f1a6
fix(raytracing): fix PO implementation, improve naming
SFBdragon Oct 2, 2026
3f4f3c1
refactor(raytracing): remove unused default Response interval
SFBdragon Oct 2, 2026
0321c93
feat(raytracing): implement dual-weighted sampling around the boresight
SFBdragon Oct 2, 2026
b843f81
refactor(propagation): remove vendored linalg.h dependency
SFBdragon Oct 2, 2026
5748f8b
fix(libfers): fix XML schemas with new asset types and improve RT par…
SFBdragon Oct 3, 2026
a8949c7
fix(libfers): fix shadow ray minimum distance check, small improvements
SFBdragon Oct 3, 2026
9c670f6
fix(libfers): restore removed constructor definition
SFBdragon Oct 3, 2026
c00bc0a
refactor(libfers): make rcs optional and refactor libfers accordingly…
SFBdragon Oct 5, 2026
25c018d
fix(libfers): fix and improve ray direction sampling tests
SFBdragon Oct 5, 2026
52fca8f
fix(libfers): optix engine fixes, SBR physics fixes, fersxml specific…
SFBdragon Oct 8, 2026
ecac154
fix(libfers): make polarimetric computation optional for the point sc…
SFBdragon Oct 8, 2026
4c20634
feat(raytracing): added Embree back-end to the ray-tracing propagatio…
SFBdragon Oct 9, 2026
1068e17
feat(raytracing): chunk optix launches to avoid exhausting GPU memory…
SFBdragon Oct 9, 2026
432f698
fix(raytracing): fix direct path direction, fix patch computation, ea…
SFBdragon Oct 13, 2026
f86008d
refactor(libfers): refactor Response class using a builder pattern to…
SFBdragon Oct 13, 2026
450af05
fix(pointscatter): fix pulsed carrier frequency
SFBdragon Oct 13, 2026
7d31593
fix(raytracer): address nan-generating sqrt radicand, and rename Prop…
SFBdragon Oct 15, 2026
9603bb4
feat(raytracer): batch by receiver for CW sampling, amortizing scene …
SFBdragon Oct 15, 2026
aab46c5
feat(libfers): add optional coarse-rate CW sampling, revert instantan…
SFBdragon Oct 16, 2026
5ff1329
fix(raytracing): fixes dielectric Snell's and Fresnel calculations
SFBdragon Oct 17, 2026
8278cf2
fix(libfers): rectify vw coarse sampling from starting 50% through a …
SFBdragon Oct 18, 2026
8cd5af9
fix(raytracing): bound tmin for near-origin launches
SFBdragon Oct 20, 2026
0a770ae
fix(libfers): uncommited part of the CW coarse sampling feature
SFBdragon Oct 21, 2026
43fad16
feat(libfers): add weak path pruning, small fixes
SFBdragon Oct 22, 2026
6ab46c1
chore(libfers): address all warnings and some lints
SFBdragon Oct 26, 2026
5b6dc58
chore(libfers): address unused parameter warning when Embree is disabled
SFBdragon Oct 26, 2026
144eb14
fix(ui): cargo link broke due to unresolved assimp genex dependency d…
SFBdragon Oct 26, 2026
74c71fa
fix(libfers): temporary return value in for loop is C++23 only, not s…
SFBdragon Oct 26, 2026
086d9b6
fix(libfers): include algorithm for std::clamp
SFBdragon Oct 26, 2026
4ca85f5
fix(raytracing): improve self-intersection fix
SFBdragon Oct 27, 2026
198fa45
Merge branch 'raytracing' of https://github.com/SFBdragon/FERS into r…
SFBdragon Oct 27, 2026
03b51c5
Merge pull request #3 from SFBdragon/raytracing
SFBdragon Oct 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@
include(FersOptions)

if (NOT FERS_DOCS_ONLY)
# Embree is an optional vcpkg manifest feature (see vcpkg.json) so it is only
# fetched/built when actually requested. This must be set before the vcpkg
# toolchain file runs, i.e. before project() below.
if (FERS_ENABLE_EMBREE)
list(APPEND VCPKG_MANIFEST_FEATURES "embree")
endif ()

# Auto-detect vcpkg toolchain before project() so dependency resolution works in CLion and CLI.
# Note: when building via `cargo build` (the Tauri path), CMAKE_TOOLCHAIN_FILE is always passed
# explicitly by build.rs, so this block is effectively dead in that path. It is retained here
Expand Down Expand Up @@ -135,7 +142,7 @@
add_compile_options(-fprofile-arcs -ftest-coverage -fprofile-update=atomic)
add_link_options(--coverage)
else ()
message(WARNING "Code coverage is only supported for GCC and Clang")

Check warning on line 145 in CMakeLists.txt

View workflow job for this annotation

GitHub Actions / Build & Test (windows-2025)

Code coverage is only supported for GCC and Clang

Check warning on line 145 in CMakeLists.txt

View workflow job for this annotation

GitHub Actions / Build & Test (windows-11-arm)

Code coverage is only supported for GCC and Clang
endif ()
endif ()

Expand Down
1 change: 1 addition & 0 deletions Testing/Temporary/CTestCostData.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
---
3 changes: 3 additions & 0 deletions Testing/Temporary/LastTest.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Start testing: Sep 30 21:27 SAST
----------------------------------------------------------
End testing: Sep 30 21:27 SAST
23 changes: 3 additions & 20 deletions cmake/FersEmbedFile.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,10 @@ file(READ "${INPUT}" _fers_embed_hex HEX)
string(LENGTH "${_fers_embed_hex}" _fers_embed_hex_length)
math(EXPR _fers_embed_byte_count "${_fers_embed_hex_length} / 2")

set(_fers_embed_body)
# Convert the string to a C-style array of byte values.
set(_fers_embed_body "${_fers_embed_hex}")
if (_fers_embed_byte_count GREATER 0)
math(EXPR _fers_embed_last_index "${_fers_embed_byte_count} - 1")
foreach (_fers_embed_index RANGE 0 ${_fers_embed_last_index})
math(EXPR _fers_embed_pos "${_fers_embed_index} * 2")
string(SUBSTRING "${_fers_embed_hex}" ${_fers_embed_pos} 2 _fers_embed_byte)

if (_fers_embed_index GREATER 0)
string(APPEND _fers_embed_body ",")
endif ()

math(EXPR _fers_embed_col "${_fers_embed_index} % 12")
if (_fers_embed_col EQUAL 0)
string(APPEND _fers_embed_body "\n\t")
else ()
string(APPEND _fers_embed_body " ")
endif ()

string(APPEND _fers_embed_body "0x${_fers_embed_byte}")
endforeach ()
string(APPEND _fers_embed_body "\n")
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " _fers_embed_body "${_fers_embed_body}")
endif ()

file(WRITE "${OUTPUT}"
Expand Down
25 changes: 25 additions & 0 deletions cmake/FersEmbree.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# FERS Embree CPU ray-tracing backend.
#
# Included (once) from packages/libfers/src/CMakeLists.txt when
# FERS_ENABLE_EMBREE is ON. Embree is sourced via vcpkg's "embree" manifest
# feature (see vcpkg.json) and exports its own CMake config package, so no
# custom Find module is needed (unlike OptiX).
#
# Sets, in the caller's scope:
# FERS_EMBREE_LINK_LIBRARIES - libraries to add to FERS_NATIVE_LINK_DEPENDENCIES

find_package(embree CONFIG REQUIRED)

set(_fers_embree_dir ${CMAKE_CURRENT_SOURCE_DIR}/src/propagation/raytracing/embree)

set(FERS_EMBREE_PRIVATE_HEADERS
${_fers_embree_dir}/kernel.h
${_fers_embree_dir}/embree.h
${_fers_embree_dir}/utils.h
)
set(FERS_EMBREE_SOURCES
${_fers_embree_dir}/kernel.cpp
${_fers_embree_dir}/embree.cpp
)

set(FERS_EMBREE_LINK_LIBRARIES unofficial::embree::embree)
88 changes: 88 additions & 0 deletions cmake/FersOptiX.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# FERS OptiX GPU ray-tracing backend.
#
# Included (once) from packages/libfers/src/CMakeLists.txt when
# FERS_ENABLE_OPTIX is ON. Locates OptiX + CUDA, compiles kernel.cu to PTX with
# nvcc, and embeds the PTX into a generated header (via FersEmbedFile.cmake,
# the same tool used for the XML schema headers) so optix.cpp can hand the
# module bytes straight to optixModuleCreate() without FERS needing to locate
# a loose .ptx file next to the binary at runtime.
#
# Expects GENERATED_HEADERS_DIR to already be set by the including
# CMakeLists.txt. Sets, in the caller's scope:
# FERS_OPTIX_SOURCES - .cpp files to add to LIBFERS_SOURCES
# FERS_OPTIX_PRIVATE_HEADERS - headers to add to LIBFERS_PRIVATE_HEADERS
# FERS_OPTIX_LINK_LIBRARIES - libraries to add to FERS_NATIVE_LINK_DEPENDENCIES
# FERS_OPTIX_GENERATE_TARGET - target the library target must add_dependencies() on

# Find OptiX with cmake/FindOptiX.cmake
find_package(OptiX REQUIRED)
# Find the CUDA toolkit so we can compile device programs to PTX.
find_package(CUDAToolkit REQUIRED)

set(_fers_optix_dir ${CMAKE_CURRENT_SOURCE_DIR}/src/propagation/raytracing/optix)

set(FERS_OPTIX_PRIVATE_HEADERS
${_fers_optix_dir}/optix.h
)
set(FERS_OPTIX_SOURCES
${_fers_optix_dir}/optix.cpp
)
set(FERS_OPTIX_LINK_LIBRARIES
OptiX::OptiX
CUDA::cuda_driver
CUDA::cudart
)

# -------------------- PTX Generation -------------------- #

set(_fers_optix_kernel_cu ${_fers_optix_dir}/kernel.cu)
set(_fers_optix_kernel_ptx ${CMAKE_CURRENT_BINARY_DIR}/optix_ptx/kernel.ptx)
set(_fers_optix_kernel_ptx_header ${GENERATED_HEADERS_DIR}/kernel_ptx.h)

add_custom_command(
OUTPUT ${_fers_optix_kernel_ptx}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/optix_ptx
COMMAND "${CUDAToolkit_NVCC_EXECUTABLE}"
--ptx
--expt-relaxed-constexpr
-lineinfo
-std=c++${CMAKE_CXX_STANDARD}
-Xcompiler -Wall,-Wextra,-Wshadow,-Wconversion,-Wsign-conversion,-Wold-style-cast
--Wreorder --Wext-lambda-captures-this --Wdefault-stream-launch
-I "${OptiX_INCLUDE_DIR}"
-I "${_fers_optix_dir}"
-I "${CMAKE_CURRENT_SOURCE_DIR}/src"
-o "${_fers_optix_kernel_ptx}"
"${_fers_optix_kernel_cu}"
DEPENDS
${_fers_optix_kernel_cu}
${_fers_optix_dir}/kernel_defs.h
${_fers_optix_dir}/utils.h
${CMAKE_CURRENT_SOURCE_DIR}/src/propagation/raytracing/sbr_shared.h
${CMAKE_CURRENT_SOURCE_DIR}/src/propagation/raytracing/sbr_impl.h
${CMAKE_CURRENT_SOURCE_DIR}/src/propagation/math.h
COMMENT "Compiling kernel.cu to PTX with nvcc"
VERBATIM
)

add_custom_command(
OUTPUT ${_fers_optix_kernel_ptx_header}
COMMAND ${CMAKE_COMMAND}
"-DINPUT=${_fers_optix_kernel_ptx}"
"-DOUTPUT=${_fers_optix_kernel_ptx_header}"
-DVAR=devicePrograms_ptx
-P "${CMAKE_SOURCE_DIR}/cmake/FersEmbedFile.cmake"
DEPENDS ${_fers_optix_kernel_ptx} "${CMAKE_SOURCE_DIR}/cmake/FersEmbedFile.cmake"
COMMENT "Embedding kernel.ptx into kernel_ptx.h"
)

# ALL so that a plain `cmake --build` regenerates it even before anything
# depends on it yet (matches generate_schemas' behaviour below in the parent
# CMakeLists.txt) - useful for e.g. warming up an IDE's build directory.
add_custom_target(generate_optix_ptx ALL DEPENDS ${_fers_optix_kernel_ptx_header})
set(FERS_OPTIX_GENERATE_TARGET generate_optix_ptx)

unset(_fers_optix_dir)
unset(_fers_optix_kernel_cu)
unset(_fers_optix_kernel_ptx)
unset(_fers_optix_kernel_ptx_header)
2 changes: 2 additions & 0 deletions cmake/FersOptions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ option(FERS_BUILD_DOCS "Enable building Doxygen documentation" OFF)
option(FERS_DOCS_ONLY "Configure only for documentation generation" OFF)
option(FERS_BUILD_TESTS "Build unit tests" OFF)
option(FERS_ENABLE_COVERAGE "Enable code coverage generation" OFF)
option(FERS_ENABLE_OPTIX "Enable the OptiX GPU ray tracing backend (requires the OptiX SDK and CUDA)" OFF)
option(FERS_ENABLE_EMBREE "Enable the Embree CPU ray tracing backend (sourced via vcpkg)" OFF)

# Ensure at least one library type is selected.
if (NOT FERS_BUILD_SHARED_LIBS AND NOT FERS_BUILD_STATIC_LIBS)
Expand Down
24 changes: 24 additions & 0 deletions cmake/FindOptiX.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Searches OPTIX_ROOT, environment variable, and default install locations
find_path(OptiX_INCLUDE_DIR
NAMES optix.h
PATHS
${OPTIX_ROOT}
$ENV{OPTIX_ROOT}
/opt/optix
/opt/nvidia/optix
"C:/ProgramData/NVIDIA Corporation/OptiX SDK 9.1.0"
"C:/ProgramData/NVIDIA Corporation/OptiX SDK 8.0.0"
"C:/ProgramData/NVIDIA Corporation/OptiX SDK 7.7.0"
PATH_SUFFIXES include
)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(OptiX
REQUIRED_VARS OptiX_INCLUDE_DIR
VERSION_VAR OptiX_VERSION
)

if(OptiX_FOUND AND NOT TARGET OptiX::OptiX)
add_library(OptiX::OptiX INTERFACE IMPORTED)
target_include_directories(OptiX::OptiX INTERFACE ${OptiX_INCLUDE_DIR})
endif()
11 changes: 11 additions & 0 deletions docs/wiki/FERS-CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Windows:
| `--vita49-queue-depth <packets>` | Optional VITA sender queue depth. Must be greater than zero. Default is `1024`. |
| `--log-level=<level>` | Set logging detail. Use `TRACE`, `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `FATAL`. |
| `--log-file=<file>` | Write logs to a `.log` or `.txt` file as well as the terminal. |
| `--rt-engine=<engine>` | Force the ray-tracing engine for the SBR propagation model: `auto`, `optix`, or `embree` (case-insensitive). Defaults to `auto`. |
| `-n=<threads>` | Choose how many worker threads to use. |

If `--out-dir` is not supplied, results are written beside the scenario file.
Expand Down Expand Up @@ -84,6 +85,16 @@ Tune VITA UDP packet sizing and queue depth:

For the packet format, metadata fields, counters, and source-backed implementation contract, see [[VITA49 Streaming Implementation]].

## Choosing a Ray-Tracing Engine

SBR scenarios (`<propagation><raytracing .../></propagation>` in the `.fersxml`) auto-detect between OptiX and Embree by default, preferring OptiX. Force a specific engine with `--rt-engine=`:

```bash
./build/release/packages/fers-cli/fers-cli scenario.fersxml --rt-engine=embree
```

Accepted values are `auto`, `optix`, and `embree`, matched case-insensitively. Requesting an engine that wasn't compiled into this build fails with an error when the simulation starts.

## Choosing Thread Count

Use `-n=<threads>` to control the worker pool:
Expand Down
1 change: 1 addition & 0 deletions docs/wiki/Simulation-Pipelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ Important CW settings:
| Antenna patterns and platform rotation | Directional gain over time. |
| `nodirect` | Removes direct transmitter-to-receiver path. |
| `nopropagationloss` | Removes path-loss scaling for debugging. |
| `<cwSimSamplingRate>` | The CW analogue of `<simSamplingRate>`. Sets the propagation/geometry sampling rate. |

## FMCW Pipeline Without Built-In Dechirp

Expand Down
1 change: 1 addition & 0 deletions docs/wiki/Using-FERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ The most important rates are:
| --- | --- |
| `<rate>` | Base output sample rate in Hz. Raw CW/FMCW/SFCW streaming output is written at this rate. |
| `<simSamplingRate>` | Rate used for geometry interpolation in pulsed response generation. |
| `<cwSimSamplingRate>` | Rate used for geometry interpolation in CW-family sampling. Disabled by default; propagation modelled at `<rate> * <oversample>`. |
| `<oversample>` | Internal oversampling multiplier. FMCW aliasing checks and legacy full-rate dechirped IF output use `<rate> * <oversample>`. |
| `<if_sample_rate>` | Optional receiver-local FMCW IF output rate after dechirping and resampling. |

Expand Down
66 changes: 65 additions & 1 deletion docs/wiki/XML-Schema-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,14 @@ Optional children, in order:
| --- | --- | --- | --- |
| `<c>` | m/s | `299792458` | Propagation speed. Use the default for free-space radio propagation. |
| `<simSamplingRate>` | Hz | `1000` | Geometry interpolation rate used while building pulsed responses. Increase it when fast platform motion needs finer geometry sampling. |
| `<cwSimSamplingRate>` | Hz | disabled | Geometry interpolation rate used while simulating CW/streaming propagation, instead of every output sample (default). Increase for past motion, decrease for expensive propagation modelling, e.g. high density ray tracing. |
| `<randomseed>` | integer | random | Seed for repeatable noise and random models. Set this when you want repeatable runs. |
| `<adc_bits>` | bits | `0` | ADC quantization depth. `0` disables quantization and stores normalized floating-point samples. |
| `<oversample>` | multiplier | `1` | Internal oversampling factor. Valid range is `1` to `8`. FMCW waveform aliasing checks use `<rate> * <oversample>`. Most final output is still written at `<rate>`, but legacy dechirped FMCW output without `<if_sample_rate>` is written at `<rate> * <oversample>`. |
| `<rotationangleunit>` | `deg` or `rad` | `deg` | Unit used by platform rotation values. |
| `<origin>` | attributes | built-in KML default | Geodetic reference used only by ENU KML/geospatial export. It does not affect signal simulation geometry. |
| `<coordinatesystem>` | attributes | `ENU` | Coordinate frame used when converting platform coordinates to geodetic coordinates for KML/geospatial export. It does not transform positions during simulation. |
| `<propagation>` | `<pointscatter>` or `<raytracing>` sub-element | `pointscatter` model | Selects the propagation model and its model-specific parameters. |

### `<origin>`

Expand Down Expand Up @@ -119,6 +121,46 @@ Frames:

FERS does not accept platform latitude/longitude waypoints. Latitude and longitude appear only in `<origin>`, where they define the geodetic reference used by ENU KML export.

### `<propagation>`

`<propagation>` selects the propagation model as a choice of exactly one sub-element: `<pointscatter>`, carrying its polarimetric-transport flag, or `<raytracing>`, carrying its ray-sampling parameters. Omitting `<propagation>` entirely defaults to `pointscatter` with polarimetric transport disabled.

```xml
<propagation>
<pointscatter polarimetric="true"/>
</propagation>
```

```xml
<propagation>
<raytracing scatter_limit="4" boresight_width="10"
boresight_tube_solid_angle="2e-8" off_boresight_tube_solid_angle="1e-5"
prune_below_max_db="60"/>
</propagation>
```

`<pointscatter>` attributes:

| Attribute | Required | Unit | Default | Meaning |
| --- | --- | --- | --- | --- |
| `polarimetric` | No | boolean | `false` | Enables full polarimetric transport: antenna Jones-vector polarisation coupling and target scattering matrices on reflected paths. When `false`, direct and reflected paths compute scalar amplitudes. |

`<raytracing>` attributes:

| Attribute | Required | Unit | Default | Meaning |
| --- | --- | --- | --- | --- |
| `scatter_limit` | No | integer bounces | `4` | Maximum number of GO bounces per ray path (max ray depth is `scatter_limit + 1`). |
| `boresight_width` | No | `<rotationangleunit>` | `0.3` rad (~17.2°) | Full angular width of the ray-sampling cone centered on each source antenna's boresight. The cone's half-angle is `boresight_width / 2`. Must be less than a full turn (360°/2π rad). |
| `boresight_tube_solid_angle` | No | steradians | `2e-8` | Target ray-tube solid angle for rays sampled within the boresight cone. |
| `off_boresight_tube_solid_angle` | No | steradians | `1e-5` | Target ray-tube solid angle for rays sampled outside the boresight cone. |
| `prune_below_max_db` | No | dB (power, positive magnitude) | unset (no pruning) | For each receiver at a given time, drops paths more than this many dB weaker than that group's strongest path, for performance. No pruning if disabled. |

`rays_per_source` and the cone's ray count are not set directly - they're derived from `boresight_width` and the two tube-solid-angle attributes and logged at the start of the simulation.

`prune_below_max_db` culls paths to a receiver below the maximum dB path. It's generally safe to set this to something like "60" depending on your SNR. Dropped paths are individually below the kept power by at least that many dB, but enough of them summing in phase could in principle raise the dropped total above the per-path floor. Pick a threshold with enough margin over your expected noise floor/dynamic range that this isn't a concern for your scenario.

Each ray tube's footprint on a hit facet is approximately `R^2 * S / cos(theta)`, where `R` is the range to the facet, `S` is the tube's solid angle, and `theta` is the incidence angle off the facet normal. A tube whose footprint exceeds a facet's area does not fully resolve that facet. To choose `boresight_tube_solid_angle` or `off_boresight_tube_solid_angle`, pick the smallest facet area `A_facet` that regime needs to resolve, and the shortest range and most oblique incidence angle targets in that regime will be seen at, then solve for the tube solid angle: `S <= A_facet * cos(theta) / R^2`. A smaller `S` resolves finer detail at the cost of more rays (and compute).

## `<waveform>`

A waveform defines what a transmitter emits.
Expand Down Expand Up @@ -369,6 +411,27 @@ Optional child parameters:
| `<azscale>` | `gaussian` | Azimuth spread for Gaussian pattern. |
| `<elscale>` | `gaussian` | Elevation spread for Gaussian pattern. |
| `<efficiency>` | `isotropic`, `sinc`, `squarehorn`, `parabolic`, `xml`, `file` | Linear gain multiplier applied to the computed antenna gain. Defaults to `1`. |
| `<boresight_polarisation>` | all | Boresight polarisation Jones vector (Ludwig-3). See below. Defaults to purely horizontal if omitted. |

### `<boresight_polarisation>`

```xml
<antenna name="Iso" pattern="isotropic">
<boresight_polarisation h="1.0+0.0i" v="0.0+0.0i"/>
</antenna>
```

| Attribute | Required | Meaning |
| --- | --- | --- |
| `h` | No | Horizontal component of the Jones vector, as a complex number literal. Defaults to `0+0i`. |
| `v` | No | Vertical component of the Jones vector, as a complex number literal. Defaults to `0+0i`. |

The vector is normalised on load, so only the ratio between `h` and `v` matters.

A complex number literal is a real term, an imaginary term, or both (real first). An
imaginary term's unit marker (`i` or `j`, case-insensitive) may come before or after its
magnitude, and whitespace around signs and units is ignored. For example, all of
`1.0+0.0i`, `1.0 + j0.5`, `-0.5 - i2`, `0.5j`, and `1e-3+2e-4i` are valid.

Pattern notes:

Expand Down Expand Up @@ -484,7 +547,7 @@ Contains one or more `<rotationwaypoint>` elements.

| Element | Unit | Meaning |
| --- | --- | --- |
| `<azimuth>` | `<rotationangleunit>` | Platform azimuth. |
| `<azimuth>` | `<rotationangleunit>` | Platform compass azimuth (left-handed angle from north). |
| `<elevation>` | `<rotationangleunit>` | Platform elevation. |
| `<time>` | seconds | Time for this pointing waypoint. |

Expand Down Expand Up @@ -860,6 +923,7 @@ The XML schema is intentionally simple, so some important checks happen when FER
- FMCW count fields must be positive integers when present.
- FMCW sweep edges, including `start_frequency_offset`, must fit the effective sample rate `<rate> * <oversample>`.
- FMCW receiver IF-chain fields are allowed only when dechirping is enabled.
- `<raytracing>`'s `prune_below_max_db`, when present, must be a finite positive number.
- SFCW `step_size` must be nonzero, count fields must be positive integers, `dwell_time` must be no greater than `step_period`, and every emitted RF step must be positive.
- Receiver noise temperature must not be negative.
- UTM KML/geospatial export needs a valid zone and hemisphere.
Expand Down
10 changes: 9 additions & 1 deletion packages/fers-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Run the simulator from the command line, providing the path to a scenario XML fi
| `--vita49-queue-depth <packets>` | Set the paced sender's steady-state backpressure watermark. Must be greater than zero; default: `1024`. |
| `--log-level=<level>` | Set the logging level (`TRACE`, `DEBUG`, `INFO`, `WARNING`, `ERROR`, `FATAL`). |
| `--log-file=<file>` | Log output to the specified `.log` or `.txt` file in addition to the console. |
| `--rt-engine=<engine>` | Force the ray-tracing engine for the SBR propagation model (`auto`, `optix`, `embree`; case-insensitive). Defaults to `auto`. |
| `-n=<threads>` | Set the number of threads to use for the simulation. |

### Example
Expand All @@ -74,6 +75,13 @@ Stream receiver output as VITA 49.2 UDP:
--vita49 127.0.0.1:4991 --vita49-fullscale 1.0
```

VITA transport selection is a runtime option and is not stored in `.fersxml`. See the
Force the Embree ray-tracing engine for an SBR scenario:

```bash
./build/release/packages/fers-cli/fers-cli scenario.fersxml --rt-engine=embree
```

VITA transport selection and ray-tracing engine selection are runtime options and are not stored in
`.fersxml`. See the
[VITA 49.2 streaming implementation guide](../../docs/wiki/VITA49-Streaming-Implementation.md) for packet layout,
pacing, metadata, and counter semantics.
Loading
Loading