GregOrigin - Sonant: Native Contextual Audio Middleware

Watch the promo video.

Read the GitBook docs.

STOP EDITING PROJECT SETTINGS.

Standard Unreal Engine audio implementation is a workflow bottleneck. To get basic footsteps, you have to define dozens of Physical Materials, assign them to every Material Instance, and build complex "Switch on Enum" graphs. To handle ambiance, you have to build elaborate Blueprint logic to detect when a player enters a Cave inside a Forest without breaking the mix.

MEET SONANT.

Sonant is a high-performance Gameplay Audio Manager that treats audio as Logic, not just Content. It runs entirely as a native Game Instance Subsystem.

CORE FEATURES:

1. INTELLIGENT SURFACE DETECTION (The "Zero-Config" Engine)
Sonant "reads" your specific context. Instead of relying solely on Physical Materials, Sonant scans the distinct name of the Material Interface (e.g., M_Temple_Stone_Wet) and matches it against a keyword registry you define.

  • The Workflow: Map the keyword "Stone" to your Stone Sound Pack once. Now, every material in your project with "Stone" in the name works instantly.

  • The Performance: Surface resolution is cached at runtime. The string search runs only once per material asset. Every subsequent footstep is a generic

    O(1) pointer lookup.

2. PRIORITY-BASED ATMOSPHERES (The "Wwise-Lite" Mixer)
Solved: The "Cave inside a Forest" problem.
Sonant replaces standard Sound Mix Modifiers with a Weighted Priority Stack.

  • Tag your volumes (e.g., Indoor, Underwater, Combat).

  • Assign a Priority Integer to each tag in the Config.

  • Sonant automatically calculates the highest-priority active tag and blends the correct Audio Modulation Control Bus instantly. No visual scripting required.

3. NATIVE C++ PERFORMANCE
Your game thread is precious. Sonant adds zero components to your Character. It contains no ticking actors. All logic is event-driven via the C++ Subsystem.

4. METASOUND READY
Fully compatible with UE 5.7+ MetaSounds. Drive procedural footsteps (walk vs. run) by plugging a MetaSoundSource directly into the Sonant registry.

Sonant Changelog

2026-03-11

🚀 Major Architectural Upgrades

  • Massive Performance Overhaul: Refactored the core USonantSubsystem to completely eliminate Game Thread synchronous loading hitches. The TSoftObjectPtr configuration is now loaded exactly once during Initialize() and cached as a hard TObjectPtr, dropping per-frame/per-hit lookup times down to pure O(1) memory access.
  • Amortized Raycasting (Auto-Reverb): Completely rewrote the UpdateReverb logic. Instead of firing 6 expensive, synchronous physical line traces in a single tick to calculate room volume, Sonant now amortizes the traces by performing exactly 1 trace per interval, drastically reducing CPU spikes while maintaining fluid, real-time spatial awareness.
  • Multiplayer & Networking Component: Introduced USonantNetworkComponent. Developers can now drop this replication helper onto their Pawns or Weapons to instantly gain standard Server and NetMulticast RPC wrappers for both Footsteps and Impacts, fully resolving the prior local-only subsystem limitation.
  • True BSP Volume Support: Introduced ASonantBSPVolume. Level Designers are no longer restricted to using primitive UBoxComponent shapes for ambient zones. They can now use standard Unreal Engine BSP brush tools to map complex, non-rectangular acoustic atmospheres (e.g., winding caves, angled corridors).

✨ New Features & API Enhancements

  • Generic Impact Intensity API: Added PlayImpactByIntensity(Location, Intensity). This allows developers who don't use standard Unreal Physics (e.g., custom kinematic controllers or line-trace weapons) to trigger dynamic impact audio simply by passing a normalized float (0.0 to 1.0), decoupling the audio engine from rigid physics mass requirements.
  • Configurable Gameplay Tags: Removed all hardcoded internal C++ strings. The base routing tags for events (e.g., Sonant.Event.Footstep, Sonant.Event.Impact.Heavy) are now fully exposed in the Project Settings via USonantSettings, allowing developers complete control over their tag hierarchies without touching source code.
  • Spectator / Free-Cam Reverb: Upgraded Auto-Reverb tracing to intelligently query GetViewTarget() before falling back to GetPawn(). Cinematic cameras, unpossessed vehicles, and free-cam spectators will now calculate correct spatial audio.

đź›  Demo & Onboarding Improvements

  • "Acoustics Laboratory" Showcase: Completely scrapped and rewrote SonantFeatureDemo. It is now an interactive 4-wing lab that perfectly spaces out and visually explains:
    • North Wing: Material Walkway (Footstep testing).
    • East Wing: Dynamic Reverb Chambers (Closet, Hall, Cavern volumes).
    • South Wing: Physics Impact Arena (Live simulation pit).
    • West Wing: Atmosphere Tag Volumes (Overlapping nested state visualization).

⚙️ Build & Compliance

  • Unreal Engine 5.6 Compatibility: Fixed a backwards-compatibility bug regarding EPhysicalSurface missing from ChaosEngineInterface.h in older engine builds.

:speaker_high_volume: Sonant Changelog

[0.2.0] - 2026-05-17

:sparkles: New Features

  • Setup Wizard (FTUE) :rocket:
    • Introduced a new Editor-only module (SonantEditor).
    • Added a native Slate window accessible via Window > Sonant > Sonant Setup & Scanner.
    • 1-Click Initialization: Automatically creates the default USonantConfig DataAsset, populates it with 8 industry-standard starter keywords (Wood, Metal, Stone, etc.), and assigns it directly to your Project Settings.
  • Smart Material Scanner :magnifying_glass_tilted_left:
    • Integrated into the Setup Wizard.
    • Automatically scans the /Game directory for UMaterialInterface assets.
    • Matches material naming conventions against common archetypes and auto-registers missing keywords into your active Sonant Config, eliminating manual data entry.
  • Visual Debugger :bug:
    • Added a new Cheat Console Variable: Sonant.Debug 1.
    • Reverb Trace Visualization: Draws real-time green debugging lines showing the 6-way amortized raycast used for Auto-Reverb estimation, alongside an on-screen readout of the calculated Room Radius and active Mix.
    • Acoustic Occlusion & Impact Data: Draws a sphere at sound impact sites (Cyan for clear, Red for occluded) and prints an on-screen log detailing the exact GameplayTag triggered, occlusion status, Volume, and Pitch.

:artist_palette: UI / UX Improvements

  • Stylized Editor Interface :milky_way:
    • Overhauled the Sonant Setup window with a premium “Audio Orchestrator” aesthetic.
    • Features a dark space-blue palette (#050D1E), glowing Cyan (#00CCFF) typography, and a custom drop-shadow.
    • Implemented structured, numbered blocks (“01”, “02”) for a clean, modern user onboarding flow.

:hammer_and_wrench: Architecture

  • Module Split: Separated Editor-only UI tools into the new SonantEditor module to ensure they are cleanly stripped from packaged runtime builds.

0.3.0 update, 2026-06-05 :headphone:

:wrench: Runtime Source Changes

  • :label: Added native built-in Sonant gameplay tags:
    • Plugins/Sonant/Source/Sonant/Public/SonantNativeTags.h
    • Plugins/Sonant/Source/Sonant/Private/SonantNativeTags.cpp
  • :puzzle_piece: Updated USonantSettings defaults to use native tag objects instead of FGameplayTag::RequestGameplayTag(...).
  • :hiking_boot: Updated footstep and impact fallback logic to use native tags when settings are missing or invalid.
  • :ocean: Updated demo atmosphere zones to use native atmosphere tags directly.
  • :brick: Removed hard-coded sample-project asset dependency from SonantDemoGameMode.
    • The plugin now defaults to ASonantDemoCharacter::StaticClass().
    • It no longer hard-loads /Game/ThirdPerson/... assets.
  • :hammer_and_wrench: Added missing Engine/Engine.h include in SonantSubsystem.cpp for clean plugin packaging.

:label: Gameplay Tag Config Changes

  • :white_check_mark: Added plugin-distributed tag config:
    • Plugins/Sonant/Config/Tags/SonantTags.ini
  • :white_check_mark: Added project gameplay tag entries:
    • Config/DefaultGameplayTags.ini
  • :compass: Tags covered:
    • Sonant
    • Sonant.Event.Footstep
    • Sonant.Event.Impact
    • Sonant.Event.Impact.Light
    • Sonant.Event.Impact.Heavy
    • Sonant.Atmosphere
    • Sonant.Atmosphere.Outdoor
    • Sonant.Atmosphere.Cave
    • Sonant.Atmosphere.Underwater

0.3.0a update, 2026-06-10

:building_construction: Architecture

  • Dedicated Demo Module :puzzle_piece:
    • Moved all demo/showcase classes (SonantDemoCharacter, SonantDemoGameMode, SonantDemoGenerator, SonantFeatureDemo, SonantAutoShowcase, SonantSpectacle, SonantGodRayShowcase) out of the core Sonant runtime module into a new dedicated SonantDemo runtime module.
    • The core middleware module is now lean (only the shippable runtime). The demo still ships and can be disabled per-project.
    • Added CoreRedirects in Config/DefaultEngine.ini so existing level/Blueprint references to the relocated classes resolve to /Script/SonantDemo.*.

:bug: Fixes & Hardening

  • Surface Cache :card_index_dividers:
    • Re-keyed the material → surface cache by FObjectKey to eliminate dangling/recycled material-pointer false hits, and it now invalidates when the active config changes.
  • Multiplayer :globe_with_meridians:
    • Fixed footsteps/impacts playing twice on the initiating client (USonantNetworkComponent). The client predicts locally once and the NetMulticast now skips the autonomous proxy.
  • GC Safety :recycling_symbol:
    • Converted stray raw pointers in the showcase actors to UPROPERTY/TObjectPtr (and FHitFlash/FPulsingWall to USTRUCT), preventing use-after-free when spawned actors/materials are recycled.
  • Subsystem :shield:
    • Null-guarded GetLocalPlayer() in UpdateReverb and PlaySoundAtLocation.

:video_game: Demo

  • Self-Contained Input :keyboard:
    • SonantDemoCharacter now binds WASD / mouse-look / action keys directly, so the demo runs with no project input mappings (no Enhanced Input or DefaultInput.ini setup required).
  • Cleanup :broom:
    • Removed a hard-coded /Game/M_Sonant_Demo material reference; the demo relies only on engine content.
    • Moved demo logging from LogTemp to a dedicated LogSonantDemo category.

0.3.1 update, 2026-06-11 :musical_score:

:hammer_and_wrench: Core Bug Fixes

  • Editor Crash Resolution: Fixed a critical Editor crash during garbage collection by refactoring the SmartCache in USonantSubsystem to use FObjectKey and FString values instead of raw pointers.
  • Atmosphere Stack Leak: Patched a stack leak in USonantVolume by validating that overlapping components match the Pawn’s Root Component, preventing duplicate atmosphere pushes from overlapping child colliders (like weapon meshes).
  • Material Lookup Bandwidth Bug: Decoupled RPCs in SonantNetworkComponent from sending the full FHitResult to the server. The client now passes lightweight data (FVector and Intensity) to PlayImpactByIntensity, drastically saving bandwidth and preventing mismatched server-side physical material lookups.
  • Multi-Listener Spline Audio Fix: Fixed a logic flaw in USonantSplineAudioComponent that clamped distances to the server’s local camera. It now accurately computes distance dynamically based on the local player’s view target, enabling multiplayer support.
  • Performance Optimization: Changed TSoftObjectPtr in the PhysicsMap back to TObjectPtr<UPhysicalMaterial> after identifying that async loading inside synchronous trace loops was causing micro-stutters.
  • Atmosphere State Corruption Fix: Prevented state corruption where leaving a volume sometimes failed to revert the audio mix. The system now accurately caches and restores the CurrentAppliedMixAsset.

:rocket: Setup Wizard & Workflow Enhancements

  • Memory-Safe Config Loading: The Setup Wizard now safely uses LoadObject instead of FindObject, ensuring that previously created Config assets on disk aren’t accidentally overwritten or broken if they aren’t resident in memory.
  • Automated Physics Mapping: The Smart Scanner now automatically searches the Asset Registry for UPhysicalMaterial assets across the /Game/ directory and pre-populates the PhysicsMap seamlessly.
  • Dynamic Keyword Discovery: Implemented a string-parsing fallback in the Smart Scanner. If a material doesn’t match a core keyword, it automatically extracts the suffix (e.g., “Mud” from “M_Ground_Mud”) and creates a custom KeywordMap entry.
  • Automated Saving: Setup Wizard actions now immediately serialize the generated Configuration Data Assets to disk using UEditorLoadingAndSavingUtils::SavePackages, protecting progress from Editor crashes.
  • Interactive UI Polish: Added dynamic Slate UI highlighting (GetDependentButtonColor) and an FScopedSlowTask progress bar dialogue during heavy Asset Registry scans to improve user experience.
  • Automated Collision Injection: Added a “Step 4: Inject AudioTrace Channel” button to the Setup Wizard. This safely parses DefaultEngine.ini and automatically claims an available ECC_GameTraceChannel for dedicated Sonant traces.

:gear: Performance & Architecture

  • Runtime Async Pre-Caching: Added PrecacheMaterials() to USonantSubsystem::Initialize. The subsystem now proactively sweeps all loaded UMaterialInterface assets and populates the SmartCache during the loading screen, eliminating trace stuttering on the first footstep.
  • Data Validation & Error Checking: USonantConfig now intercepts PostEditChangeProperty to automatically trim whitespace typos from KeywordMap string keys.
  • Blueprint Volume Validation: USonantVolume now utilizes Unreal’s CheckForErrors framework to throw clickable Map Check warnings and Property Editor alerts if a level designer forgets to assign a required AtmosphereTag.
  • :cool_button: Python Content Pipeline: Engineered a complete pipeline (GenerateWavFiles.py and GenerateSonantContent.py) to procedurally synthesize royalty-free demo content natively within the Unreal Engine environment.

Current Python demo content generators are also shared here:

GenerateSonantContent.py (4 KB)
GenerateWavFiles.py (2.4 KB)

0.4.0 update, 2026-07-18 :headphone:

Runtime

  • :world_map: Replaced synchronous config/event loading and global loaded-material scans with soft references, asynchronous loading, explicit readiness, scoped event preloading, and release APIs.
  • :scientist: Added canonical physical-surface routing, exact soft overrides, deterministic keyword priority/longest-match fallback, and automatic migration of legacy hard physical-material mappings.
  • :microphone: Rebuilt automatic reverb around the actual audio listener, enclosure confidence, opposing-wall and ceiling evidence, open-space-aware radius estimation, smoothing, hysteresis, dwell time, authored-state precedence, and opt-in defaults.
  • :ear: Unified atmosphere/reverb mix ownership and added complete world-travel/deinitialization cleanup.
  • Added configurable Medium impact routing and data-driven impact tiers.
  • Added event, VFX, and decal distance/scalability gates; made custom per-event occlusion opt-in and removed pitch shifting from occlusion.
  • Hardened the optional network component with server surface traces, proximity validation, rate limiting, quantized payloads, clamped intensity, and no replicated asset references. Client requests default to disabled.
  • Fixed spline source lifetime and true listener-to-spline selection, projectile swept closest approach, and foley lifecycle/external intensity control.

Editor, tests, and distribution

  • Replaced initial setup/scanning/collision injection with an explicit config creation confirmation and a
    read-only Asset Registry/config audit.
  • Added automation coverage for acoustic classification, keyword resolution, impact tier selection, network boundaries, and configured-asset migration.
  • Moved the demo module to UncookedOnly, removed runtime platform allowlists, aligned metadata with UE 5.7, and excluded raw development samples from production package filters.

I’ll create 1+ videos soon to actually explain what this plugin offers.

[0.4.5] update, 2026-08-29 :studio_microphone:

(A purely technical bugfix update, no new features at this time)

Streaming: transient loads were never retired

FStreamableHandle::IsActive() is !bCanceled && !bReleased. A handle that has
finished loading is still active, so the prune predicate never matched one.

   RequestAsyncLoad()
        |
        v
   +----------+  load finishes  +-----------+  last ref drops  +----------+
   | loading  | --------------> | completed | ---------------> | released |
   +----------+                 +-----------+                  +----------+

                           loading    completed    released
    IsLoadingInProgress()    true       false        false
    IsActive()               true       true (*)     false

    (*) still active once the load finishes - which is why a prune written
        as !IsActive() never retired a completed request
  • Fixed: the prune now tests IsLoadingInProgress(). Previously every sound,
    attenuation, concurrency, VFX and decal streamed for a transient event stayed
    resident for the life of the world, and IsTickable() never returned false again
    once any event had streamed - a permanent per-frame cost with automatic reverb off.
  before   handles: 1  2  3  4  5  6 ...   grows for the life of the world
  after    handles: 1  2  1  0  1  0 ...   settles back to zero
  • Warmed visual assets moved to a bounded 32-entry retention window. Pruning alone
    would have made the warm-up pointless (the assets would be collected before reuse),
    so a small recent window keeps repeated events fast without unbounded retention.
    PreloadEvent remains the guarantee for first-use VFX.

Networking: server-originated events fix

The autonomous-proxy early-out that stops a client double-hearing its own predicted
event was also swallowing events the server raised on that client’s pawn, because
both paths funnelled into the same multicast.

  Who hears a footstep raised on Player A pawn?

                                | Player A   | Player B    | listen
                                | (owner,    | (sees a     | server
                                |  autonom.) |  sim proxy) |
  ------------------------------+------------+-------------+--------
  A predicts locally, server    |  muted     |   hears     |  hears
  echoes it back                |  (correct) |             |
  ------------------------------+------------+-------------+--------
  Server raises it directly
      before  (0.3.0)           |  SILENT !  |   hears     |  hears
      after   (0.4.0)           |  hears     |   hears     |  hears
  • Fixed: the multicast RPCs carry bOwnerAlreadyPlayed. Only a client’s own echo
    is suppressed. Authority-originated audio now reaches the pawn’s owner.
  • The rule moved into the pure decision model as ShouldPlayReplicatedEvent, so it is
    covered by an automation test rather than only reachable through a live session.

Surfaces: exact overrides were unreachable

  before (0.3.0)                     after (0.4.0)
  --------------------------         ----------------------------
  1. SurfaceTypeMap        <-+       1. PhysicalMaterialOverrides
  2. PhysicalMaterialOver.   |       2. MaterialOverrides
  3. MaterialOverrides    ---+       3. SurfaceTypeMap
  4. keyword match             swap  4. keyword match
  5. DefaultSurface                  5. DefaultSurface
  • Fixed: exact soft overrides are consulted before the canonical map. The setup
    wizard seeds SurfaceTypeMap[SurfaceType_Default], so any physical material left on
    the default surface was captured at step 1 and its own override at step 2 could
    never run. Documented in README.md.
  • The material-decision cache (SmartCache) is now capped at 512 entries and drops
    entries whose FObjectKey no longer resolves. Content that mints materials at
    runtime - the plugin’s own endless-runner showcases do exactly this - grew it
    without limit.

Automatic reverb: clearing a requirement made it stricter

Confidence was scored against a fixed ceiling-inclusive maximum, so removing the
ceiling requirement lowered the achievable score without lowering the threshold.

  Sample: 3 walls including an opposing pair, no ceiling. Threshold 0.75.

  evidence   ceiling 0.25  |  horizontal 3 x 0.125  |  opposing 0.25
  scored     ---- 0.000 ---|-------- 0.375 ---------|---- 0.250 ----  = 0.625

  bRequireCeiling = true     0.625 / 1.00 = 0.62   fails (correct - no ceiling)

  bRequireCeiling = false
     before                  0.625 / 1.00 = 0.62   fails  <-- stricter!
     after                   0.625 / 0.75 = 0.83   passes <-- attainable evidence
  • Fixed: confidence is normalized against the evidence the current configuration
    can actually gather. Clearing bRequireCeiling now relaxes detection instead of
    silently demanding all four walls. A ceiling found while not required still counts
    and simply saturates the score.

Atmosphere volumes

  • USonantVolume binds its overlap delegates in BeginPlay, which runs after
    overlaps are generated at registration. A pawn standing inside a level-streamed
    volume - or spawned inside a placed one - was never seen. It now sweeps existing
    overlaps on begin play. (ASonantBSPVolume was unaffected: it overrides the
    always-live NotifyActorBeginOverlap virtual.)
  • The atmosphere refcount saturates instead of overflowing; Count + 1 was signed
    overflow at the limit, before FMath::Min could clamp it.

Spatial components

  • USonantFoleyComponent and USonantSplineAudioComponent name their audio component
    uniquely. A fixed NewObject name collided when one actor carried two of them.
  • Both honour UpdateInterval changes made after BeginPlay; the property is
    BlueprintReadWrite but was only read once.
  • USonantSplineAudioComponent warns when its owner has no spline, instead of
    silently doing nothing forever.
  • USonantProjectileTracker disables its tick once it has fired, until ResetWhizby.

Editor

  • The setup wizard writes Config/DefaultGame.ini via TryUpdateDefaultConfigFile().
    SaveConfig() targets the per-user Saved/Config/<Platform>/Game.ini, so the
    assignment the confirmation dialog promised was neither source-controlled nor
    carried into a packaged build.
  • No widget binds to the module instance any more - the status getters and button
    handlers are statics. A nomad tab outlives ShutdownModule (and a Live Coding
    reload), and the previous CreateRaw(this, ...) bindings dangled.
  • ExtendMenu result is null-checked.

Demo and showcases

  ASonantGodRayShowcase, per generation step:

    spawned   floor 1  +  pillars 2  +  meteor 0.25 (every 4th)   =  3.25
    retired   -------------- one per call ----------------------  =  1.00
                                                           net    = +2.25

    before   actors  _.-~=#@   climbs forever (physics bodies and
                              shadow-casting 50k-intensity lights)
    after    actors  ======    flat at the cap
  • Fixed unbounded actor growth in ASonantGodRayShowcase, ASonantAutoShowcase
    and ASonantDemoGenerator. Each retired exactly one actor per spawn call while
    adding more than one. Cleanup now drains to the cap.
  • ASonantSpectacle checked TileMesh after SpawnActor, leaving three untracked,
    never-destroyed actors behind on every row when it was unset. The check moved ahead
    of the spawn, and the actor now ships engine-content defaults like its siblings.
  • Fixed a null-material dereference in ASonantGodRayShowcase
    (UMaterialInstanceDynamic::Create returns null for a null parent).
  • The showcases re-acquire the player pawn instead of latching a single null in
    BeginPlay. Actor begin-play can precede the game mode spawning the pawn, which
    left them permanently inert.
  • ASonantGodRayShowcase no longer overrides world exposure, bloom and volumetric fog
    by default. It is opt-in via bOverrideWorldPostProcess, and auto-exposure is a
    range again rather than a locked min == max == 1.0 that blinded any host level.
  • The 1-5 keys now route by EPhysicalSurface through the previously unused
    TestSurfaces array. They built a FHitResult with no component and no physical
    material, so all five resolved to DefaultSurface and sounded identical.
  • ImpactTestForce drives the impact tests instead of being ignored.
  • The spline, whiz-by and occlusion exhibits take assignable sounds and say so on
    their signs when unset. They were silent by construction, and the occlusion wing had
    no audio source at all - its own source comment admitted it.
  • The demo projectile assigns its mesh before enabling physics (there was no body to
    simulate or receive the impulse), and its destroy timer holds a TWeakObjectPtr
    rather than a raw actor pointer.
  • PrintCurrentSurfaceInfo traces with bReturnPhysicalMaterial and reports it.

Build correctness

  • Added includes that only -StrictIncludes (non-unity, IWYU) exposes:
    Engine/World.h in SonantFeatureDemo.cpp, and Materials/MaterialInterface.h
    plus PhysicalMaterials/PhysicalMaterial.h in SonantDemoCharacter.cpp. These were
    pre-existing latent failures - an ordinary editor build does not catch them.

Tests

Now 11 automation tests, all passing.

  Sonant.Decision.Acoustics.OpenTerrain
  Sonant.Decision.Acoustics.EnclosedRoom
  Sonant.Decision.Acoustics.Overhang
  Sonant.Decision.Acoustics.CeilingNotRequired      (new)
  Sonant.Decision.Impacts.TierSelection
  Sonant.Decision.Impacts.TierEdgeCases             (new)
  Sonant.Decision.Surfaces.DeterministicKeyword
  Sonant.Decision.Surfaces.KeywordEdgeCases         (new)
  Sonant.Decision.Network.Boundaries
  Sonant.Decision.Network.EchoSuppression           (new)
  Sonant.Integration.ConfigAssetLoads

Run them with:

Automation RunTests Sonant
  • Extracted the replication echo rule into the pure decision model so the
    “server-originated events reach the owner” fix has a regression guard.
  • Added coverage for enclosure confidence when no ceiling is required, keyword edge
    cases and determinism, impact-tier boundaries and fallback, and rate-limit
    burst/idle behaviour.

I unified previous shipped “demo guides” into a single updated one. It also supersedes the ones currently bundled with the Fab edition:

DemoGuide.md (13.2 KB)