<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>DomeCommand Blog</title>
        <link>https://domecommand.ai/blog</link>
        <description>Building DomeCommand in the open.</description>
        <lastBuildDate>Mon, 13 Jul 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>© 2026 DomeCommand</copyright>
        <item>
            <title><![CDATA[Hello, world: how DomeCommand started]]></title>
            <link>https://domecommand.ai/blog/hello-world-drone-command</link>
            <guid>https://domecommand.ai/blog/hello-world-drone-command</guid>
            <pubDate>Mon, 13 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A cheap drone forces a response that costs a thousand times more, the operator is drowning in sensor feeds with too few good data points, and today's decision support is often thousands of hardcoded rules. DomeCommand is being built to change that, with a solver and an agentic harness instead.]]></description>
            <content:encoded><![CDATA[<figure style="margin:0 0 1.75rem;border:1px solid var(--dc-hairline);border-radius:10px;overflow:hidden"><img src="https://domecommand.ai/img/blog/hello/console.jpg" alt="The DomeCommand console: a live map with two suspect drones inbound on an airport, three friendly assets tasked to follow and patrol." style="width:100%;display:block"></figure>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="problem">Problem<a href="https://domecommand.ai/blog/hello-world-drone-command#problem" class="hash-link" aria-label="Direct link to Problem" title="Direct link to Problem" translate="no">​</a></h2>
<p>A cheap drone now forces a response that can cost a thousand times more. Send a hundred at once and the hard part is no longer the hardware. It is the decision: what to send, at what, and when, faster than a person can make it.</p>
<!-- -->
<p>And the person making that call is already overloaded. Sensors pour feeds at the operator, but the data points that actually decide the outcome, a clean track, a confident classification, stay scarce. Too much signal, not enough of it good.</p>
<p>What holds the decision together in production today is, honestly, thousands of hardcoded rules. That is what a lot of "decision support" really amounts to: hand-written logic, brittle at the edges, and almost impossible to keep current with how fast the threat is changing.</p>
<p>So the problem is three things at once. A decision that has to be made faster than a human can make it. An operator drowning in feeds but starved of good data. And a status quo held up by rules nobody can maintain. DomeCommand is being built to take all three on. This blog is the log, in the open, from the start.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="how-it-started">How it started<a href="https://domecommand.ai/blog/hello-world-drone-command#how-it-started" class="hash-link" aria-label="Direct link to How it started" title="Direct link to How it started" translate="no">​</a></h2>
<p>Years ago at <a href="https://yojee.com/" target="_blank" rel="noopener noreferrer">Yojee</a> I worked on route optimization, assigning vehicles to jobs under messy real-world constraints. Later I dabbled a little with programming my own flight controller using a Crazyflie. And for a while now I have been building <a href="https://distri.dev/?ref=domecommand" target="_blank" rel="noopener noreferrer">Distri</a>, an open-source agentic harness.</p>
<p>Applying a solver brain on top of an agentic brain sounded like a great fit for a counter-UAS proposition. So I built a demo.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="the-first-demo">The first demo<a href="https://domecommand.ai/blog/hello-world-drone-command#the-first-demo" class="hash-link" aria-label="Direct link to The first demo" title="Direct link to The first demo" translate="no">​</a></h2>
<p>The first demo was a Space Invaders style planning screen, built on <a href="https://distri.dev/?ref=domecommand" target="_blank" rel="noopener noreferrer">Distri</a> and focused on C-UAS: waves of drones come down the screen at Singapore, and the screen models the cost of defending against them.</p>
<p>It runs two things at once. A solver continuously re-allocates the effectors you have against the threats that are inbound, under the constraints and the cost of each intercept. An agent reasons around the solver as the picture changes, deciding what to ask it next and what to recommend. Traditional planning algorithms and an agentic brain might be a good mix. This was the cheapest way to find out.</p>
<a href="https://www.youtube.com/watch?v=vfC3sAdcLoU" target="_blank" rel="noreferrer" style="display:block;position:relative;margin:2rem 0;border-radius:10px;overflow:hidden;border:1px solid var(--dc-hairline)"><img src="https://domecommand.ai/img/blog/hello/demo.gif" alt="The cost-based planning demo, re-solving the allocation continuously as the threat picture changes." style="width:100%;display:block"><span style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none"><span style="display:inline-flex;align-items:center;gap:0.6rem;padding:0.6rem 1.1rem;border-radius:999px;background:rgba(7,9,10,0.72);border:1px solid rgba(124,192,76,0.5);color:#F2EFE9;font-family:var(--ifm-font-family-monospace);font-size:0.85rem;letter-spacing:0.04em">▶ Watch the demo</span></span></a>
<p>The solver does the maths. The agent does the small amount of reasoning around it, and that small amount is what changes the answer.</p>
<p>DomeCommand has since been invited to submit a proposal for a three-month project at the <a href="https://enterprise.nus.edu.sg/event/sdth2026/" target="_blank" rel="noopener noreferrer">Singapore Defence Tech Hackathon</a>.</p>
<p>After a number of conversations with defence consultants, military experts, and people who have spent years close to this, the idea has evolved and deepened a lot. The backdrop is hard to miss too: drones changed the fight in Ukraine, they are cheap and numerous and hard to stop, and the hardware is years ahead of the software that has to command a response to it.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="autonomous-continuous-planning-and-decision-support">Autonomous continuous planning, and decision support<a href="https://domecommand.ai/blog/hello-world-drone-command#autonomous-continuous-planning-and-decision-support" class="hash-link" aria-label="Direct link to Autonomous continuous planning, and decision support" title="Direct link to Autonomous continuous planning, and decision support" translate="no">​</a></h2>
<p>What we aim to build is an autonomous decision support system that really aids an operator: one that can move the sensors to where they are needed, a fleet that auto adjusts, and an agentic brain aiding the traditional AI and ML approaches.</p>
<p>Rules are still in there. They are necessary. They just cannot all be hardcoded, because the threat moves faster than anyone can maintain them. We wrote our own DSL for how policy and instructions are expressed, and I will cover it in a later post.</p>
<p>Three bets follow from this.</p>
<h3 class="anchor anchorWithStickyNavbar_vmXh" id="01--dynamic-aerial-sensor-fusion">01 · Dynamic aerial sensor fusion<a href="https://domecommand.ai/blog/hello-world-drone-command#01--dynamic-aerial-sensor-fusion" class="hash-link" aria-label="Direct link to 01 · Dynamic aerial sensor fusion" title="Direct link to 01 · Dynamic aerial sensor fusion" translate="no">​</a></h3>
<p>Radar, RF, EO/IR and acoustic each see a different slice of the world, and they have to agree on one track. Most systems treat the sensors as fixed points on a map. Ours does not. When identity is uncertain, the system sends a flying sensor to go earn a better look. Fusion stops being passive.</p>
<figure class="dc-diagram"><div class="dc-diagram__frame" style="height:320px"><svg viewBox="0 0 880 320" aria-hidden="true" style="width:100%;height:100%;display:block"><defs><radialGradient id="fuseGlow" cx="50%" cy="50%" r="50%"><stop offset="0%" stop-color="#7CC04C" stop-opacity="0.30"></stop><stop offset="100%" stop-color="#07090A" stop-opacity="0"></stop></radialGradient></defs><g font-family="JetBrains Mono, monospace" font-size="13" letter-spacing="0.12em"><g><rect x="20" y="28" width="180" height="44" fill="rgba(14,19,22,0.85)" stroke="#2A343C"></rect><circle cx="46" cy="50" r="5" fill="#7CC04C"></circle><text x="68" y="54" fill="#C9D2D8">RADAR</text><path d="M 200 50 C 300 50, 320 160, 410 160" fill="none" stroke="#7CC04C" stroke-width="1.4" opacity="0.5" stroke-dasharray="4 5"></path></g><g><rect x="20" y="98" width="180" height="44" fill="rgba(14,19,22,0.85)" stroke="#2A343C"></rect><circle cx="46" cy="120" r="5" fill="#7CC04C"></circle><text x="68" y="124" fill="#C9D2D8">RF-DETECT</text><path d="M 200 120 C 300 120, 320 160, 410 160" fill="none" stroke="#7CC04C" stroke-width="1.4" opacity="0.5" stroke-dasharray="4 5"></path></g><g><rect x="20" y="168" width="180" height="44" fill="rgba(14,19,22,0.85)" stroke="#2A343C"></rect><circle cx="46" cy="190" r="5" fill="#FFB020"></circle><text x="68" y="194" fill="#C9D2D8">EO/IR</text><path d="M 200 190 C 300 190, 320 160, 410 160" fill="none" stroke="#FFB020" stroke-width="1.4" opacity="0.5" stroke-dasharray="4 5"></path></g><g><rect x="20" y="238" width="180" height="44" fill="rgba(14,19,22,0.85)" stroke="#2A343C"></rect><circle cx="46" cy="260" r="5" fill="#5C6B75"></circle><text x="68" y="264" fill="#C9D2D8">ACOUSTIC</text><path d="M 200 260 C 300 260, 320 160, 410 160" fill="none" stroke="#5C6B75" stroke-width="1.4" opacity="0.5" stroke-dasharray="4 5"></path></g></g><circle cx="470" cy="160" r="78" fill="url(#fuseGlow)"></circle><circle cx="470" cy="160" r="52" fill="rgba(7,9,10,0.9)" stroke="#7CC04C" stroke-width="1.5"></circle><text x="470" y="156" text-anchor="middle" fill="#7CC04C" font-family="JetBrains Mono, monospace" font-size="13" letter-spacing="0.1em">FUSION</text><text x="470" y="174" text-anchor="middle" fill="#5C6B75" font-family="JetBrains Mono, monospace" font-size="11" letter-spacing="0.1em">ENGINE</text><path d="M 522 160 L 640 160" fill="none" stroke="#7CC04C" stroke-width="1.6"></path><g transform="translate(660,160)"><rect x="0" y="-46" width="200" height="92" fill="rgba(124,192,76,0.06)" stroke="#7CC04C" stroke-width="1.4"></rect><text x="16" y="-22" fill="#7CC04C" font-family="JetBrains Mono, monospace" font-size="12" letter-spacing="0.1em">TRACK 0148</text><text x="16" y="-2" fill="#C9D2D8" font-family="JetBrains Mono, monospace" font-size="11">class · FPV</text><text x="16" y="18" fill="#C9D2D8" font-family="JetBrains Mono, monospace" font-size="11">conf · 0.92</text><text x="16" y="36" fill="#5C6B75" font-family="JetBrains Mono, monospace" font-size="11">hostile</text></g></svg></div><figcaption class="dc-diagram__cap dc-mono">Many sensors, one track. The harder version: some of those sensors are flying.</figcaption></figure>
<a class="dc-readmore" href="https://domecommand.ai/blog/dynamic-sensor-fusion"><span><span class="dc-readmore__eyebrow dc-mono">The deep dive</span><span class="dc-readmore__title">Dynamic sensor fusion: what if your sensors could move?</span><span class="dc-readmore__blurb">Cross-cueing, slew to cue, and a picture that is honest about what it does not know.</span></span><span class="dc-readmore__arrow" aria-hidden="true">→</span></a>
<h3 class="anchor anchorWithStickyNavbar_vmXh" id="02--dynamic-multi-purpose-drone-swarm">02 · Dynamic, multi-purpose drone swarm<a href="https://domecommand.ai/blog/hello-world-drone-command#02--dynamic-multi-purpose-drone-swarm" class="hash-link" aria-label="Direct link to 02 · Dynamic, multi-purpose drone swarm" title="Direct link to 02 · Dynamic, multi-purpose drone swarm" translate="no">​</a></h3>
<p>One fleet you can task on demand. It defends, and it also watches, patrols, and scouts. Drones self-organize, reassign themselves mid-flight, and keep flying as links drop. Defence is the hard edge that forces the system to be fast and trustworthy. The everyday jobs are what make a fleet worth owning.</p>
<figure class="dc-diagram"><div class="dc-diagram__frame" style="height:320px"><svg viewBox="0 0 600 360" aria-hidden="true" style="width:100%;height:100%;pointer-events:none;opacity:0.85"><defs><g id="dcDrone"><circle cx="0" cy="0" r="6" fill="none" stroke="currentColor" stroke-width="1.2"></circle><line x1="-22" y1="-22" x2="-12" y2="-12" stroke="currentColor" stroke-width="1.2"></line><line x1="22" y1="-22" x2="12" y2="-12" stroke="currentColor" stroke-width="1.2"></line><line x1="-22" y1="22" x2="-12" y2="12" stroke="currentColor" stroke-width="1.2"></line><line x1="22" y1="22" x2="12" y2="12" stroke="currentColor" stroke-width="1.2"></line><ellipse cx="-22" cy="-22" rx="9" ry="3" fill="none" stroke="currentColor" stroke-width="0.9" opacity="0.7"></ellipse><ellipse cx="22" cy="-22" rx="9" ry="3" fill="none" stroke="currentColor" stroke-width="0.9" opacity="0.7"></ellipse><ellipse cx="-22" cy="22" rx="9" ry="3" fill="none" stroke="currentColor" stroke-width="0.9" opacity="0.7"></ellipse><ellipse cx="22" cy="22" rx="9" ry="3" fill="none" stroke="currentColor" stroke-width="0.9" opacity="0.7"></ellipse></g></defs><g stroke="#7CC04C" stroke-width="0.6" opacity="0.18" fill="none" stroke-dasharray="3 5"><path d="M 360 180 L 180 90"></path><path d="M 360 180 L 490 70"></path><path d="M 360 180 L 140 270"></path><path d="M 360 180 L 520 290"></path><path d="M 360 180 L 70 180"></path></g><g transform="translate(360,180) scale(1.6)" color="#7CC04C" opacity="0.9"><use href="#dcDrone"></use></g><g transform="translate(180,90) scale(1.05)" color="#7CC04C" opacity="0.55"><use href="#dcDrone"></use></g><g transform="translate(490,70) scale(1)" color="#7CC04C" opacity="0.45"><use href="#dcDrone"></use></g><g transform="translate(140,270) scale(0.95)" color="#7CC04C" opacity="0.4"><use href="#dcDrone"></use></g><g transform="translate(520,290) scale(1.1)" color="#7CC04C" opacity="0.55"><use href="#dcDrone"></use></g><g transform="translate(70,180) scale(0.7)" color="#7CC04C" opacity="0.3"><use href="#dcDrone"></use></g></svg></div><figcaption class="dc-diagram__cap dc-mono">One fleet, many jobs: defend, watch, patrol.</figcaption></figure>
<a class="dc-readmore" href="https://domecommand.ai/blog/built-to-survive"><span><span class="dc-readmore__eyebrow dc-mono">The deep dive</span><span class="dc-readmore__title">Four loops, two links: the architecture that keeps flying</span><span class="dc-readmore__blurb">How the swarm holds together when GPS, the uplink, and the mesh each get taken away.</span></span><span class="dc-readmore__arrow" aria-hidden="true">→</span></a>
<h3 class="anchor anchorWithStickyNavbar_vmXh" id="03--agentic-and-solver-decision-brains">03 · Agentic and solver decision brains<a href="https://domecommand.ai/blog/hello-world-drone-command#03--agentic-and-solver-decision-brains" class="hash-link" aria-label="Direct link to 03 · Agentic and solver decision brains" title="Direct link to 03 · Agentic and solver decision brains" translate="no">​</a></h3>
<p>The solver carries the math, allocating assets under hard constraints of cost, coverage, and reach. The agent reasons around it: what changed, what matters now, what to re-solve, and briefs the trade-offs in plain language. Two kinds of intelligence in one loop, and a human approves the call.</p>
<figure class="dc-diagram"><div class="dc-diagram__frame" style="height:320px"><svg viewBox="0 0 600 460" aria-hidden="true" style="width:100%;height:100%;display:block"><circle cx="300" cy="230" r="175" fill="none" stroke="#2A343C" stroke-width="1.4" stroke-dasharray="3 6"></circle><circle cx="300" cy="230" r="58" fill="rgba(7,9,10,0.9)" stroke="#2A343C" stroke-width="1"></circle><text x="300" y="226" text-anchor="middle" fill="#5C6B75" font-family="JetBrains Mono, monospace" font-size="11" letter-spacing="0.14em">OPERATOR</text><text x="300" y="244" text-anchor="middle" fill="#7CC04C" font-family="JetBrains Mono, monospace" font-size="11" letter-spacing="0.14em">LED</text><g transform="translate(300,55)"><rect x="-62" y="-17" width="124" height="34" fill="rgba(14,19,22,0.92)" stroke="#7CC04C" stroke-width="1.4"></rect><text x="0" y="5" text-anchor="middle" fill="#C9D2D8" font-family="JetBrains Mono, monospace" font-size="12" letter-spacing="0.08em">01 · SENSE</text></g><g transform="translate(466.43489035165186,175.92202598438422)"><rect x="-62" y="-17" width="124" height="34" fill="rgba(14,19,22,0.92)" stroke="#7CC04C" stroke-width="1.4"></rect><text x="0" y="5" text-anchor="middle" fill="#C9D2D8" font-family="JetBrains Mono, monospace" font-size="12" letter-spacing="0.08em">02 · PLAN</text></g><g transform="translate(402.8624191511828,371.57797401561584)"><rect x="-62" y="-17" width="124" height="34" fill="rgba(255,176,32,0.10)" stroke="#FFB020" stroke-width="1.4"></rect><text x="0" y="5" text-anchor="middle" fill="#FFB020" font-family="JetBrains Mono, monospace" font-size="12" letter-spacing="0.08em">03 · DECIDE</text></g><g transform="translate(197.1375808488172,371.57797401561584)"><rect x="-62" y="-17" width="124" height="34" fill="rgba(14,19,22,0.92)" stroke="#7CC04C" stroke-width="1.4"></rect><text x="0" y="5" text-anchor="middle" fill="#C9D2D8" font-family="JetBrains Mono, monospace" font-size="12" letter-spacing="0.08em">04 · ACT</text></g><g transform="translate(133.5651096483481,175.92202598438422)"><rect x="-62" y="-17" width="124" height="34" fill="rgba(14,19,22,0.92)" stroke="#7CC04C" stroke-width="1.4"></rect><text x="0" y="5" text-anchor="middle" fill="#C9D2D8" font-family="JetBrains Mono, monospace" font-size="12" letter-spacing="0.08em">05 · ASSESS</text></g></svg></div><figcaption class="dc-diagram__cap dc-mono">Sense, plan, decide, act, assess. The operator sits in the middle of it.</figcaption></figure>
<a class="dc-readmore" href="https://domecommand.ai/blog/building-in-simulation"><span><span class="dc-readmore__eyebrow dc-mono">The deep dive</span><span class="dc-readmore__title">The simulation engine</span><span class="dc-readmore__blurb">Where the two brains are exercised: scenarios generated across fleet and threat catalogs, and the history used to train them.</span></span><span class="dc-readmore__arrow" aria-hidden="true">→</span></a>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="where-this-goes">Where this goes<a href="https://domecommand.ai/blog/hello-world-drone-command#where-this-goes" class="hash-link" aria-label="Direct link to Where this goes" title="Direct link to Where this goes" translate="no">​</a></h2>
<p>That is where DomeCommand starts. A solver, an agentic harness, a hackathon proposal, and a problem that turned out to be very real.</p>
<a class="dc-readmore" href="https://domecommand.ai/blog/inturai-loi"><span><span class="dc-readmore__eyebrow dc-mono">Read more</span><span class="dc-readmore__title">Inturai has signed an LOI to acquire DomeCommand</span><span class="dc-readmore__blurb">And where all of it landed: sensing through walls, paired with a fleet that can act on it.</span></span><span class="dc-readmore__arrow" aria-hidden="true">→</span></a>
<p>I am building it in the open, and writing up each piece as it comes together. If this is your kind of problem, follow along. Hello, world.</p>]]></content:encoded>
            <category>Story</category>
        </item>
        <item>
            <title><![CDATA[Inturai has signed an LOI to acquire DomeCommand]]></title>
            <link>https://domecommand.ai/blog/inturai-loi</link>
            <guid>https://domecommand.ai/blog/inturai-loi</guid>
            <pubDate>Wed, 08 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Inturai Ventures has signed an exclusive letter of intent to acquire the technology behind DomeCommand. A short note on what Inturai's sensors do, and why sensors that can be carried are the part worth paying attention to.]]></description>
            <content:encoded><![CDATA[<p>Good news to share. <a href="https://inturai.com/" target="_blank" rel="noopener noreferrer">Inturai Ventures</a> (CSE: URAI) has signed an exclusive letter of intent to acquire the technology behind DomeCommand, together with a perpetual, worldwide licence to <a href="https://distri.dev/?ref=domecommand" target="_blank" rel="noopener noreferrer">Distri</a>, the open-source agentic harness it runs on. It was <a href="https://irw-press.com/en/inturai-ventures-signs-exclusive-loi-to-acquire-ai-driven-drone-swarm-c2-command-platform/" target="_blank" rel="noopener noreferrer">announced on July 6</a>, and it is a good home for this work.</p>
<!-- -->
<p>Inturai builds <a href="https://www.newswire.ca/news-releases/inturai-releases-proprietary-duo-1-quantum-secure-devices-to-see-through-walls-and-track-human-activity-and-heartbeats-860836699.html" target="_blank" rel="noopener noreferrer">sensors</a> that read the radio environment instead of looking at it, so they can pick up people and movement through a wall with no camera and no line of sight. That is a genuinely different way to sense a space, and it is useful well beyond defence: search and rescue, site monitoring, aged care. We have been building the other half of that loop, which is what to do once you know something is there.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="extending-dynamic-sensor-fusion">Extending dynamic sensor fusion<a href="https://domecommand.ai/blog/inturai-loi#extending-dynamic-sensor-fusion" class="hash-link" aria-label="Direct link to Extending dynamic sensor fusion" title="Direct link to Extending dynamic sensor fusion" translate="no">​</a></h2>
<p>The part worth paying attention to is that these sensors can be carried. In <a href="https://www.tradingview.com/news/prnewswire:f24127f42df0f:0-inturai-advances-drone-defence-and-in-home-intelligence-with-new-technology/" target="_blank" rel="noopener noreferrer">late June</a> Inturai said their sensors now talk directly to each other with no internet, and that they are testing one mounted on a drone. A sensor that needs no fixed install, no network, and no line of sight is a sensor you can fly. Coverage stops being a property of the building and becomes something you task: instead of wiring a village to see inside it, you fly a few drones over it and stitch what they heard into one picture. That is the bet DomeCommand was built on, from the <a href="https://domecommand.ai/blog/hello-world-drone-command">first post here</a>. Fusing sensors that hold still is largely solved. Fusing sensors that move is not, because each reading arrives with the platform's own motion in it, and two drones passing the same wall from different angles have to be resolved into one contact rather than two. That is the <a href="https://domecommand.ai/blog/dynamic-sensor-fusion">dynamic sensor fusion</a> problem, now with a sensor that sees through the wall instead of down the street. Inturai senses what you cannot see, DomeCommand acts on it beyond line of sight, and the pilot the two teams are running closes that loop with a person approving each step. It is a letter of intent, a step and not a finish line, and I will share more as it firms up.</p>
<figure class="dc-diagram"><div class="dc-diagram__frame" style="height:340px"><svg viewBox="0 0 880 340" aria-hidden="true" style="width:100%;height:100%;display:block"><defs><linearGradient id="coneFade" x1="0%" y1="0%" x2="0%" y2="100%"><stop offset="0%" stop-color="#C900FE" stop-opacity="0.28"></stop><stop offset="100%" stop-color="#C900FE" stop-opacity="0.02"></stop></linearGradient></defs><line x1="20" y1="292" x2="600" y2="292" stroke="#2A343C" stroke-width="1.2"></line><path d="M 150 84 L 54 292 L 246 292 Z" fill="url(#coneFade)"></path><path d="M 330 62 L 234 292 L 426 292 Z" fill="url(#coneFade)"></path><path d="M 510 90 L 414 292 L 606 292 Z" fill="url(#coneFade)"></path><g fill="rgba(14,19,22,0.9)" stroke="rgba(124,192,76,0.5)" stroke-width="1.4"><rect x="96" y="232" width="108" height="60"></rect><rect x="232" y="244" width="82" height="48"></rect><rect x="344" y="226" width="116" height="66"></rect><rect x="488" y="240" width="96" height="52"></rect></g><g><circle cx="130" cy="262" r="4.5" fill="#C9D2D8"></circle><circle cx="130" cy="262" r="10" fill="none" stroke="#E76BFF" stroke-width="1.2" stroke-dasharray="2 3"></circle></g><g><circle cx="166" cy="274" r="4.5" fill="#C9D2D8"></circle><circle cx="166" cy="274" r="10" fill="none" stroke="#E76BFF" stroke-width="1.2" stroke-dasharray="2 3"></circle></g><g><circle cx="268" cy="268" r="4.5" fill="#C9D2D8"></circle><circle cx="268" cy="268" r="10" fill="none" stroke="#E76BFF" stroke-width="1.2" stroke-dasharray="2 3"></circle></g><g><circle cx="382" cy="254" r="4.5" fill="#FF3C5C"></circle><circle cx="382" cy="254" r="10" fill="none" stroke="#FF3C5C" stroke-width="1.2" stroke-dasharray="2 3"></circle></g><g><circle cx="420" cy="266" r="4.5" fill="#C9D2D8"></circle><circle cx="420" cy="266" r="10" fill="none" stroke="#E76BFF" stroke-width="1.2" stroke-dasharray="2 3"></circle></g><g><circle cx="528" cy="264" r="4.5" fill="#FF3C5C"></circle><circle cx="528" cy="264" r="10" fill="none" stroke="#FF3C5C" stroke-width="1.2" stroke-dasharray="2 3"></circle></g><g stroke="#E76BFF" stroke-width="1.2" stroke-dasharray="4 5" fill="none" opacity="0.75"><path d="M 150 74 L 330 52"></path><path d="M 330 52 L 510 80"></path><path d="M 150 74 C 330 150, 330 150, 510 80" opacity="0.35"></path></g><text x="330" y="26" text-anchor="middle" fill="#E76BFF" font-family="JetBrains Mono, monospace" font-size="10" letter-spacing="0.16em">MESH · NO INTERNET · NO FIXED SITE</text><g transform="translate(150,74)"><g stroke="#7CC04C" stroke-width="1.3" fill="none"><circle r="7"></circle><line x1="-22" y1="-14" x2="-11" y2="-6"></line><line x1="22" y1="-14" x2="11" y2="-6"></line><ellipse cx="-24" cy="-15" rx="9" ry="3" opacity="0.75"></ellipse><ellipse cx="24" cy="-15" rx="9" ry="3" opacity="0.75"></ellipse></g><rect x="-9" y="8" width="18" height="10" fill="rgba(201,0,254,0.16)" stroke="#C900FE" stroke-width="1.2"></rect><circle cx="0" cy="13" r="1.8" fill="#E76BFF"></circle></g><g transform="translate(330,52)"><g stroke="#7CC04C" stroke-width="1.3" fill="none"><circle r="7"></circle><line x1="-22" y1="-14" x2="-11" y2="-6"></line><line x1="22" y1="-14" x2="11" y2="-6"></line><ellipse cx="-24" cy="-15" rx="9" ry="3" opacity="0.75"></ellipse><ellipse cx="24" cy="-15" rx="9" ry="3" opacity="0.75"></ellipse></g><rect x="-9" y="8" width="18" height="10" fill="rgba(201,0,254,0.16)" stroke="#C900FE" stroke-width="1.2"></rect><circle cx="0" cy="13" r="1.8" fill="#E76BFF"></circle></g><g transform="translate(510,80)"><g stroke="#7CC04C" stroke-width="1.3" fill="none"><circle r="7"></circle><line x1="-22" y1="-14" x2="-11" y2="-6"></line><line x1="22" y1="-14" x2="11" y2="-6"></line><ellipse cx="-24" cy="-15" rx="9" ry="3" opacity="0.75"></ellipse><ellipse cx="24" cy="-15" rx="9" ry="3" opacity="0.75"></ellipse></g><rect x="-9" y="8" width="18" height="10" fill="rgba(201,0,254,0.16)" stroke="#C900FE" stroke-width="1.2"></rect><circle cx="0" cy="13" r="1.8" fill="#E76BFF"></circle></g><text x="150" y="118" text-anchor="middle" fill="#5C6B75" font-family="JetBrains Mono, monospace" font-size="10" letter-spacing="0.1em">DUO · AIRBORNE</text><path d="M 592 292 C 622 292, 622 200, 646 200" fill="none" stroke="#7CC04C" stroke-width="1.4" opacity="0.7"></path><g transform="translate(650,116)" font-family="JetBrains Mono, monospace" letter-spacing="0.08em"><rect width="216" height="168" fill="rgba(124,192,76,0.05)" stroke="#7CC04C" stroke-width="1.4"></rect><text x="16" y="28" fill="#7CC04C" font-size="12">ONE PICTURE</text><line x1="16" y1="40" x2="200" y2="40" stroke="#2A343C"></line><g transform="translate(16,66)"><text fill="#5C6B75" font-size="11">inside walls</text><text x="184" text-anchor="end" fill="#C9D2D8" font-size="13">11</text></g><g transform="translate(16,92)"><text fill="#5C6B75" font-size="11">outside walls</text><text x="184" text-anchor="end" fill="#C9D2D8" font-size="13">18</text></g><g transform="translate(16,118)"><text fill="#5C6B75" font-size="11">armed</text><text x="184" text-anchor="end" fill="#FF3C5C" font-size="13">02</text></g><g transform="translate(16,144)"><text fill="#5C6B75" font-size="11">vitals held</text><text x="184" text-anchor="end" fill="#E76BFF" font-size="13">29</text></g></g><text x="758" y="306" text-anchor="middle" fill="#5C6B75" font-family="JetBrains Mono, monospace" font-size="10" letter-spacing="0.12em">FUSED · RE-STITCHED AS THEY MOVE</text></svg></div><figcaption class="dc-diagram__cap dc-mono">Sensors you can fly: they mesh to each other with no fixed site, and the picture is re-stitched as they move.</figcaption></figure>
<a class="dc-readmore" href="https://domecommand.ai/blog/dynamic-sensor-fusion"><span><span class="dc-readmore__eyebrow dc-mono">Read more</span><span class="dc-readmore__title">Dynamic sensor fusion: what if your sensors could move?</span><span class="dc-readmore__blurb">The long version of the problem above: fusing a picture from sensors that will not hold still.</span></span><span class="dc-readmore__arrow" aria-hidden="true">→</span></a>]]></content:encoded>
            <category>News</category>
            <category>Sensor Fusion</category>
        </item>
        <item>
            <title><![CDATA[Dynamic sensor fusion: what if your sensors could move?]]></title>
            <link>https://domecommand.ai/blog/dynamic-sensor-fusion</link>
            <guid>https://domecommand.ai/blog/dynamic-sensor-fusion</guid>
            <pubDate>Sun, 28 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Most sensor fusion quietly assumes the inputs are clean and the sensors sit still. Neither is true. Here is how DomeCommand models a picture that is honest about what it does not know, and then sends a drone to go find out.]]></description>
            <content:encoded><![CDATA[<p>In the <a href="https://domecommand.ai/blog/hello-world-drone-command">first post</a> I said DomeCommand is a bet on two things. This one is about the first: sensor fusion, and why I think the interesting version of it involves sensors that move.</p>
<p>Let me start with the honest version of the problem, because most descriptions skip it.</p>
<!-- -->
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="the-picture-is-usually-bad">The picture is usually bad<a href="https://domecommand.ai/blog/dynamic-sensor-fusion#the-picture-is-usually-bad" class="hash-link" aria-label="Direct link to The picture is usually bad" title="Direct link to The picture is usually bad" translate="no">​</a></h2>
<p>"Sensor fusion" sounds like addition. You have radar, RF, electro-optical, infrared, acoustic. Add them up, get one clean track. If only.</p>
<p>In reality each sensor lies in its own way. Radar gives you a position but not a name. RF tells you something is transmitting but not exactly where. A camera confirms what a thing is, but only once it is pointed at it and close enough to see. Coverage has holes. The contacts you care about are often the ones sitting in a hole, or the ones two sensors disagree about.</p>
<p>So the failure mode of a naive system is not that it misses things. It is that it is <em>confidently wrong</em>. Every blip becomes a track with a threat level attached the instant it appears. The operator ends up staring at a screen full of certainty that the system has not earned. Fuse bad inputs faster and all you have done is reach a wrong answer sooner.</p>
<figure class="dc-diagram"><div class="dc-diagram__frame" style="height:320px"><svg viewBox="0 0 880 320" aria-hidden="true" style="width:100%;height:100%;display:block"><defs><radialGradient id="fuseGlow" cx="50%" cy="50%" r="50%"><stop offset="0%" stop-color="#7CC04C" stop-opacity="0.30"></stop><stop offset="100%" stop-color="#07090A" stop-opacity="0"></stop></radialGradient></defs><g font-family="JetBrains Mono, monospace" font-size="13" letter-spacing="0.12em"><g><rect x="20" y="28" width="180" height="44" fill="rgba(14,19,22,0.85)" stroke="#2A343C"></rect><circle cx="46" cy="50" r="5" fill="#7CC04C"></circle><text x="68" y="54" fill="#C9D2D8">RADAR</text><path d="M 200 50 C 300 50, 320 160, 410 160" fill="none" stroke="#7CC04C" stroke-width="1.4" opacity="0.5" stroke-dasharray="4 5"></path></g><g><rect x="20" y="98" width="180" height="44" fill="rgba(14,19,22,0.85)" stroke="#2A343C"></rect><circle cx="46" cy="120" r="5" fill="#7CC04C"></circle><text x="68" y="124" fill="#C9D2D8">RF-DETECT</text><path d="M 200 120 C 300 120, 320 160, 410 160" fill="none" stroke="#7CC04C" stroke-width="1.4" opacity="0.5" stroke-dasharray="4 5"></path></g><g><rect x="20" y="168" width="180" height="44" fill="rgba(14,19,22,0.85)" stroke="#2A343C"></rect><circle cx="46" cy="190" r="5" fill="#FFB020"></circle><text x="68" y="194" fill="#C9D2D8">EO/IR</text><path d="M 200 190 C 300 190, 320 160, 410 160" fill="none" stroke="#FFB020" stroke-width="1.4" opacity="0.5" stroke-dasharray="4 5"></path></g><g><rect x="20" y="238" width="180" height="44" fill="rgba(14,19,22,0.85)" stroke="#2A343C"></rect><circle cx="46" cy="260" r="5" fill="#5C6B75"></circle><text x="68" y="264" fill="#C9D2D8">ACOUSTIC</text><path d="M 200 260 C 300 260, 320 160, 410 160" fill="none" stroke="#5C6B75" stroke-width="1.4" opacity="0.5" stroke-dasharray="4 5"></path></g></g><circle cx="470" cy="160" r="78" fill="url(#fuseGlow)"></circle><circle cx="470" cy="160" r="52" fill="rgba(7,9,10,0.9)" stroke="#7CC04C" stroke-width="1.5"></circle><text x="470" y="156" text-anchor="middle" fill="#7CC04C" font-family="JetBrains Mono, monospace" font-size="13" letter-spacing="0.1em">FUSION</text><text x="470" y="174" text-anchor="middle" fill="#5C6B75" font-family="JetBrains Mono, monospace" font-size="11" letter-spacing="0.1em">ENGINE</text><path d="M 522 160 L 640 160" fill="none" stroke="#7CC04C" stroke-width="1.6"></path><g transform="translate(660,160)"><rect x="0" y="-46" width="200" height="92" fill="rgba(124,192,76,0.06)" stroke="#7CC04C" stroke-width="1.4"></rect><text x="16" y="-22" fill="#7CC04C" font-family="JetBrains Mono, monospace" font-size="12" letter-spacing="0.1em">TRACK 0148</text><text x="16" y="-2" fill="#C9D2D8" font-family="JetBrains Mono, monospace" font-size="11">class · FPV</text><text x="16" y="18" fill="#C9D2D8" font-family="JetBrains Mono, monospace" font-size="11">conf · 0.92</text><text x="16" y="36" fill="#5C6B75" font-family="JetBrains Mono, monospace" font-size="11">hostile</text></g></svg></div><figcaption class="dc-diagram__cap dc-mono">The comfortable story: many sensors collapse into one clean track. The uncomfortable truth: each source sees a different, partial, sometimes contradictory slice.</figcaption></figure>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="three-questions-not-one">Three questions, not one<a href="https://domecommand.ai/blog/dynamic-sensor-fusion#three-questions-not-one" class="hash-link" aria-label="Direct link to Three questions, not one" title="Direct link to Three questions, not one" translate="no">​</a></h2>
<p>The mistake underneath the bad picture is that it collapses three different questions into a single answer. When we started modelling this properly, the first move was to pull them apart. A fused entity is not one number. It is three separate channels, each answering its own question:</p>
<ul>
<li><strong>Existence, "is it real?"</strong> A running score that goes up when a sensor corroborates the contact and down when it does not. It has a status you can read: tentative, confirmed, coasting, deleted. A single radar return is not a track. It is a suspicion that has to earn its way up.</li>
<li><strong>Identity, "what is it?"</strong> A belief spread across the possible classes rather than a hard label, with a measure of how uncertain that belief is. Radar alone leaves it near "unknown." An RF match narrows it toward a commercial quad. A close visual look collapses it to a specific model.</li>
<li><strong>Affiliation, "whose is it?"</strong> Friend, neutral, suspect, hostile, on a proper ladder, tagged with whether a <em>machine</em> inferred it or a <em>human</em> declared it.</li>
</ul>
<p>Keeping these apart lets the console show something it never could before: the honest state of "we know something is there, but not what it is or whose it is." That is a real, common situation in this domain, and pretending otherwise is how you get a fabricated picture.</p>
<p>It also lets us hold one rule that matters more than any other:</p>
<blockquote>
<p>Kinematics alone can never make something hostile.</p>
</blockquote>
<p>A fast contact heading for a protected asset can reach <em>suspect</em> on its own. It cannot reach <em>hostile</em> without positive identification, a hostile RF signature, or a human making the call. Speed is not intent. Baking that into the type system, not the training material, is the point.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="the-part-i-actually-care-about-making-it-move">The part I actually care about: making it move<a href="https://domecommand.ai/blog/dynamic-sensor-fusion#the-part-i-actually-care-about-making-it-move" class="hash-link" aria-label="Direct link to The part I actually care about: making it move" title="Direct link to The part I actually care about: making it move" translate="no">​</a></h2>
<p>Here is where the "dynamic" in the title comes in.</p>
<p>There is a well-worn idea in this field called cross-cueing, or slew-to-cue: one sensor's output tasks another. A radar hit slews an electro-optical camera to go look. It is production-proven and it works. But notice the quiet assumption. The camera is bolted to a mast. The cue can only ever point the sensors you already happen to have, wherever they already happen to be.</p>
<p>So the question I keep coming back to: what if the cue could task a sensor that <em>flies</em>?</p>
<p>When identity is uncertain and the contact is near something you care about, the system does not just sit there and average its confusion. It raises a request: this contact is ambiguous, the thing that would resolve it is a closer visual look, and here is the drone that can get there, with an estimated time to do it. The operator sees a plain-English recommendation. <em>Unidentified contact 1.2 km from the site. Send a drone to identify? ETA 40 seconds.</em> They approve. The drone repositions, gets eyes on, and a picture comes back that firms the identity from "unknown" to a named model, and the affiliation off "unknown" to a real call.</p>
<figure class="dc-diagram"><div class="dc-diagram__frame" style="height:420px"><svg viewBox="0 0 600 460" aria-hidden="true" style="width:100%;height:100%;display:block"><circle cx="300" cy="230" r="175" fill="none" stroke="#2A343C" stroke-width="1.4" stroke-dasharray="3 6"></circle><circle cx="300" cy="230" r="58" fill="rgba(7,9,10,0.9)" stroke="#2A343C" stroke-width="1"></circle><text x="300" y="226" text-anchor="middle" fill="#5C6B75" font-family="JetBrains Mono, monospace" font-size="11" letter-spacing="0.14em">OPERATOR</text><text x="300" y="244" text-anchor="middle" fill="#7CC04C" font-family="JetBrains Mono, monospace" font-size="11" letter-spacing="0.14em">LED</text><g transform="translate(300,55)"><rect x="-62" y="-17" width="124" height="34" fill="rgba(14,19,22,0.92)" stroke="#7CC04C" stroke-width="1.4"></rect><text x="0" y="5" text-anchor="middle" fill="#C9D2D8" font-family="JetBrains Mono, monospace" font-size="12" letter-spacing="0.08em">01 · SENSE</text></g><g transform="translate(466.43489035165186,175.92202598438422)"><rect x="-62" y="-17" width="124" height="34" fill="rgba(14,19,22,0.92)" stroke="#7CC04C" stroke-width="1.4"></rect><text x="0" y="5" text-anchor="middle" fill="#C9D2D8" font-family="JetBrains Mono, monospace" font-size="12" letter-spacing="0.08em">02 · PLAN</text></g><g transform="translate(402.8624191511828,371.57797401561584)"><rect x="-62" y="-17" width="124" height="34" fill="rgba(255,176,32,0.10)" stroke="#FFB020" stroke-width="1.4"></rect><text x="0" y="5" text-anchor="middle" fill="#FFB020" font-family="JetBrains Mono, monospace" font-size="12" letter-spacing="0.08em">03 · DECIDE</text></g><g transform="translate(197.1375808488172,371.57797401561584)"><rect x="-62" y="-17" width="124" height="34" fill="rgba(14,19,22,0.92)" stroke="#7CC04C" stroke-width="1.4"></rect><text x="0" y="5" text-anchor="middle" fill="#C9D2D8" font-family="JetBrains Mono, monospace" font-size="12" letter-spacing="0.08em">04 · ACT</text></g><g transform="translate(133.5651096483481,175.92202598438422)"><rect x="-62" y="-17" width="124" height="34" fill="rgba(14,19,22,0.92)" stroke="#7CC04C" stroke-width="1.4"></rect><text x="0" y="5" text-anchor="middle" fill="#C9D2D8" font-family="JetBrains Mono, monospace" font-size="12" letter-spacing="0.08em">05 · ASSESS</text></g></svg></div><figcaption class="dc-diagram__cap dc-mono">The loop that makes fusion active: sense, weigh what is unknown, decide to collect, act by moving a sensor, then reassess with what it learned.</figcaption></figure>
<p>That is the whole thesis in one motion. A fixed sensor sees what it sees. A moving one can be sent to the gap in coverage, to the ambiguous contact, to the one angle you do not have. It turns fusion from "combine what you were given" into "go get a better look, then combine." Harder to build. Also, I think, where the real advantage is.</p>
<p>And it changes what certainty even means on the screen. Instead of a made-up "87% confident," a contact reads as its evidence: <em>radar plus RF, two sensors, seen three seconds ago, identity still open.</em> The operator instantly sees which modality is missing, and therefore what would fix it. The system is not hiding its ignorance. It is showing you the shortest path out of it.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="what-we-are-building">What we are building<a href="https://domecommand.ai/blog/dynamic-sensor-fusion#what-we-are-building" class="hash-link" aria-label="Direct link to What we are building" title="Direct link to What we are building" translate="no">​</a></h2>
<p>We are building this in layers, and the foundation is in place: the three-channel model, the existence scoring, the identity belief and the affiliation ladder with the kinematics-never-hostile rule enforced. The association math that keeps two crossing targets from swapping identities. Sensor models honest enough that RF and acoustic come in as bearings without a magic position attached, so fusion has to triangulate the way it would anywhere else.</p>
<p>On top of that we are building the collection loop end to end: the recommendation, the human approval gate, the drone repositioning, and the look coming back to firm the picture. In the simulator the closer look is a range-degraded capture, blurry far out and sharpening as the drone nears, so we can build and exercise the whole loop deterministically. The architecture is shaped so that as we bring the loop onto real optics and real airframes, the machinery is already there to receive it.</p>
<p>That is the through-line of everything here: build the honest picture first, then build the loop that goes and improves it.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="why-this-is-the-first-bet">Why this is the first bet<a href="https://domecommand.ai/blog/dynamic-sensor-fusion#why-this-is-the-first-bet" class="hash-link" aria-label="Direct link to Why this is the first bet" title="Direct link to Why this is the first bet" translate="no">​</a></h2>
<p>Fusion is where most of C2 quietly gives up and hardcodes its way to a confident guess. Making it honest about uncertainty is worth something on its own. Making it <em>active</em>, letting the picture task the fleet to go improve itself, is the part I think is genuinely new, and it only works because the same drones that defend can also go collect. Which is the second bet, and the subject of another post.</p>
<p>If a picture that argues with itself and then goes and settles the argument sounds like your kind of problem, follow along.</p>]]></content:encoded>
            <category>Sensor Fusion</category>
        </item>
        <item>
            <title><![CDATA[What is sensor fusion? One drone, four sensors, one picture]]></title>
            <link>https://domecommand.ai/blog/what-is-sensor-fusion</link>
            <guid>https://domecommand.ai/blog/what-is-sensor-fusion</guid>
            <pubDate>Thu, 18 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Sensor fusion sounds like addition, add up your sensors and get one clean answer. It isn't. Here is a concrete, tick-by-tick walkthrough of one drone seen by four sensors, and the one distinction that makes the whole thing work.]]></description>
            <content:encoded><![CDATA[<p>If you spend any time around counter-drone systems you hear the phrase "sensor fusion" constantly, usually as if it settles the matter. You have radar, RF, cameras, acoustics. Fuse them. Done.</p>
<p>It is worth slowing down on what that word actually means, because the naive version of it is the source of most bad pictures. So here is sensor fusion explained the way I think about it, with a concrete example you can follow tick by tick.</p>
<!-- -->
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="the-naive-idea-and-why-it-breaks">The naive idea, and why it breaks<a href="https://domecommand.ai/blog/what-is-sensor-fusion#the-naive-idea-and-why-it-breaks" class="hash-link" aria-label="Direct link to The naive idea, and why it breaks" title="Direct link to The naive idea, and why it breaks" translate="no">​</a></h2>
<p>The comfortable mental model is addition. Each sensor gives you a piece, you sum the pieces, and the total is the truth.</p>
<p>The problem is that the pieces are not the same kind of thing. Radar gives you a position and a velocity but has no idea what the object <em>is</em>. A camera can tell you it is a DJI quadrotor, but only once it is pointed at it and close enough to resolve it. RF tells you something is transmitting. Acoustic hears rotors. Each one answers a different question, at a different range, with a different error.</p>
<p>Sum them naively and you get the classic failure: a screen full of confident tracks the instant anything appears, each with a threat label it has not earned. The system looks decisive and is quietly guessing.</p>
<figure class="dc-diagram"><div class="dc-diagram__frame" style="height:320px"><svg viewBox="0 0 880 320" aria-hidden="true" style="width:100%;height:100%;display:block"><defs><radialGradient id="fuseGlow" cx="50%" cy="50%" r="50%"><stop offset="0%" stop-color="#7CC04C" stop-opacity="0.30"></stop><stop offset="100%" stop-color="#07090A" stop-opacity="0"></stop></radialGradient></defs><g font-family="JetBrains Mono, monospace" font-size="13" letter-spacing="0.12em"><g><rect x="20" y="28" width="180" height="44" fill="rgba(14,19,22,0.85)" stroke="#2A343C"></rect><circle cx="46" cy="50" r="5" fill="#7CC04C"></circle><text x="68" y="54" fill="#C9D2D8">RADAR</text><path d="M 200 50 C 300 50, 320 160, 410 160" fill="none" stroke="#7CC04C" stroke-width="1.4" opacity="0.5" stroke-dasharray="4 5"></path></g><g><rect x="20" y="98" width="180" height="44" fill="rgba(14,19,22,0.85)" stroke="#2A343C"></rect><circle cx="46" cy="120" r="5" fill="#7CC04C"></circle><text x="68" y="124" fill="#C9D2D8">RF-DETECT</text><path d="M 200 120 C 300 120, 320 160, 410 160" fill="none" stroke="#7CC04C" stroke-width="1.4" opacity="0.5" stroke-dasharray="4 5"></path></g><g><rect x="20" y="168" width="180" height="44" fill="rgba(14,19,22,0.85)" stroke="#2A343C"></rect><circle cx="46" cy="190" r="5" fill="#FFB020"></circle><text x="68" y="194" fill="#C9D2D8">EO/IR</text><path d="M 200 190 C 300 190, 320 160, 410 160" fill="none" stroke="#FFB020" stroke-width="1.4" opacity="0.5" stroke-dasharray="4 5"></path></g><g><rect x="20" y="238" width="180" height="44" fill="rgba(14,19,22,0.85)" stroke="#2A343C"></rect><circle cx="46" cy="260" r="5" fill="#5C6B75"></circle><text x="68" y="264" fill="#C9D2D8">ACOUSTIC</text><path d="M 200 260 C 300 260, 320 160, 410 160" fill="none" stroke="#5C6B75" stroke-width="1.4" opacity="0.5" stroke-dasharray="4 5"></path></g></g><circle cx="470" cy="160" r="78" fill="url(#fuseGlow)"></circle><circle cx="470" cy="160" r="52" fill="rgba(7,9,10,0.9)" stroke="#7CC04C" stroke-width="1.5"></circle><text x="470" y="156" text-anchor="middle" fill="#7CC04C" font-family="JetBrains Mono, monospace" font-size="13" letter-spacing="0.1em">FUSION</text><text x="470" y="174" text-anchor="middle" fill="#5C6B75" font-family="JetBrains Mono, monospace" font-size="11" letter-spacing="0.1em">ENGINE</text><path d="M 522 160 L 640 160" fill="none" stroke="#7CC04C" stroke-width="1.6"></path><g transform="translate(660,160)"><rect x="0" y="-46" width="200" height="92" fill="rgba(124,192,76,0.06)" stroke="#7CC04C" stroke-width="1.4"></rect><text x="16" y="-22" fill="#7CC04C" font-family="JetBrains Mono, monospace" font-size="12" letter-spacing="0.1em">TRACK 0148</text><text x="16" y="-2" fill="#C9D2D8" font-family="JetBrains Mono, monospace" font-size="11">class · FPV</text><text x="16" y="18" fill="#C9D2D8" font-family="JetBrains Mono, monospace" font-size="11">conf · 0.92</text><text x="16" y="36" fill="#5C6B75" font-family="JetBrains Mono, monospace" font-size="11">hostile</text></g></svg></div><figcaption class="dc-diagram__cap dc-mono">Four modalities feeding one track. The trap is treating them as interchangeable inputs to a sum. They are not.</figcaption></figure>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="a-concrete-walkthrough">A concrete walkthrough<a href="https://domecommand.ai/blog/what-is-sensor-fusion#a-concrete-walkthrough" class="hash-link" aria-label="Direct link to A concrete walkthrough" title="Direct link to A concrete walkthrough" translate="no">​</a></h2>
<p>Let me make it real. This is a scenario straight out of our simulator: one drone closing on a protected asset at 25 m/s from 400 m out, seen by four sensors that come online in the order they realistically would.</p>
<table><thead><tr><th>Sensor</th><th>What it reports</th><th>Can it identify?</th></tr></thead><tbody><tr><td>Radar</td><td>position + velocity</td><td>No, it abstains</td></tr><tr><td>Acoustic</td><td>position + velocity</td><td>No, it abstains</td></tr><tr><td>RF/DF</td><td>position + velocity</td><td>No, it abstains</td></tr><tr><td>EO camera</td><td>position + velocity</td><td>Yes, "uav_multirotor"</td></tr></tbody></table>
<p>Radar picks it up first. Acoustic corroborates. Then RF. And only at the end, once it is close, does the camera get a good enough look to say what it is. Here is what the fused picture does, tick by tick:</p>
<table><thead><tr><th style="text-align:right">tick</th><th>sensors reporting</th><th style="text-align:right">existence</th><th>class</th><th>what the operator sees</th></tr></thead><tbody><tr><td style="text-align:right">t2</td><td>radar</td><td style="text-align:right">20</td><td>unknown</td><td>🟡 Contact, position only, unidentified</td></tr><tr><td style="text-align:right">t3</td><td>radar, acoustic</td><td style="text-align:right">30</td><td>unknown</td><td>🟡 Contact, position only, unidentified</td></tr><tr><td style="text-align:right">t4</td><td>radar, acoustic, rf</td><td style="text-align:right">40</td><td>unknown</td><td>🟡 Contact, position only, unidentified</td></tr><tr><td style="text-align:right">t5</td><td>+ <strong>EO camera</strong></td><td style="text-align:right">50</td><td><strong>uav_multirotor</strong></td><td>🔴 Threat, identified</td></tr><tr><td style="text-align:right">t6</td><td>all four</td><td style="text-align:right">61</td><td>uav_multirotor</td><td>🔴 Threat, identified</td></tr></tbody></table>
<p>One object stays one track the whole way through, no matter how many sensors see it. The identity resolves <em>on that same track</em>. It does not spawn a second one.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="the-one-distinction-that-matters">The one distinction that matters<a href="https://domecommand.ai/blog/what-is-sensor-fusion#the-one-distinction-that-matters" class="hash-link" aria-label="Direct link to The one distinction that matters" title="Direct link to The one distinction that matters" translate="no">​</a></h2>
<p>Read those five rows again and the whole idea falls out of them.</p>
<p><strong>Existence and identity are two different questions, answered by two different kinds of evidence.</strong></p>
<p>By t4 the system is very confident <em>something is really there</em>. Three independent sensors, three different physics, all agree on a real, closing object 350 m from the asset. The existence score has climbed past the confirmation threshold. And yet the class is still "unknown," because not one of those three sensors can say <em>what it is</em>. Radar, acoustic, and RF all abstain on identity. They are honest about it.</p>
<p>This is the part the naive "just add them up" model gets wrong. More corroboration buys you more <em>existence</em>. It does not buy you <em>identity</em>. You can stack ten radars and still not know whether you are looking at a quadrotor or a large bird. Identity only arrives at t5, when a sensor that can actually <em>see</em> gets close enough to resolve it.</p>
<p>So a good fusion engine tracks these as separate channels:</p>
<ul>
<li><strong>Existence</strong>, is it real? Earned by corroboration, scored, and allowed to say "not sure yet."</li>
<li><strong>Identity</strong>, what is it? Earned only by a sensor that carries identity, and left honestly "unknown" until then.</li>
</ul>
<p>Keeping them apart is what lets the console show the true state at t4: <em>confirmed, corroborated, closing, and we still do not know what it is.</em> That is a real situation, it happens constantly, and pretending otherwise is exactly how you end up with a fabricated picture.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="where-this-points-next">Where this points next<a href="https://domecommand.ai/blog/what-is-sensor-fusion#where-this-points-next" class="hash-link" aria-label="Direct link to Where this points next" title="Direct link to Where this points next" translate="no">​</a></h2>
<p>Sit with the t4 state for a second, because it is the interesting one. You have a confirmed object bearing down on something you care about, and no amount of <em>more of the same</em> sensing will identify it. Every fixed sensor that can see it already is.</p>
<p>That is precisely the moment the system should say: <em>go look at it.</em> Send something with a camera to close the range and earn the identity. Which is only possible if your sensors can move, and that is the idea I dig into in <a href="https://domecommand.ai/blog/dynamic-sensor-fusion">dynamic sensor fusion</a>.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="what-we-are-building">What we are building<a href="https://domecommand.ai/blog/what-is-sensor-fusion#what-we-are-building" class="hash-link" aria-label="Direct link to What we are building" title="Direct link to What we are building" translate="no">​</a></h2>
<p>The picture above is not a mockup. It is generated from a test in our fusion engine that regenerates the numbers from the running code and fails if they drift, so the walkthrough cannot quietly disagree with what the system actually does. The existence channel and the cross-modality association that keeps one drone as one track are what we are building on right now.</p>
<p>From there we are building the identity belief that measures how unsure we are, the affiliation logic that decides friend from foe under strict rules, and the collection loop that turns a high-uncertainty contact into a tasked "go identify it" mission. Each piece is a post of its own as it comes together.</p>
<p>If a picture that is honest about what it does not yet know sounds more useful than one that is confidently wrong, that is the whole bet. Follow along.</p>]]></content:encoded>
            <category>Sensor Fusion</category>
            <category>Fundamentals</category>
        </item>
        <item>
            <title><![CDATA[Four loops, two links: the architecture that keeps flying]]></title>
            <link>https://domecommand.ai/blog/built-to-survive</link>
            <guid>https://domecommand.ai/blog/built-to-survive</guid>
            <pubDate>Fri, 05 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A command system for a real fight can't assume GPS, can't assume a radio link, and can't assume the drone is even smart. Here's how DomeCommand is architected to degrade gracefully: four loops, two links, and one policy that survives all the way down to a single drone flying solo.]]></description>
            <content:encoded><![CDATA[<p>The demo version of any drone system quietly assumes three things: that GPS works, that the radio link holds, and that the drone is smart enough to fly itself. A real fight grants you none of those. GPS gets jammed. The link drops. Half the airframes are cheap things with no brain on board.</p>
<p>So the interesting engineering question for DomeCommand isn't "how well does it work on a good day." It's "what happens on the bad day, and the worse one after that." This post is about how the system is built to degrade: to keep doing something useful as each assumption gets taken away.</p>
<!-- -->
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="one-loop-living-in-different-places">One loop, living in different places<a href="https://domecommand.ai/blog/built-to-survive#one-loop-living-in-different-places" class="hash-link" aria-label="Direct link to One loop, living in different places" title="Direct link to One loop, living in different places" translate="no">​</a></h2>
<p>Underneath everything, DomeCommand runs the same shape: <strong>fuse</strong> the sensor picture, <strong>plan</strong> against it, <strong>execute</strong> the plan, <strong>coordinate</strong> so nobody collides or double-targets. What changes under pressure is <em>where</em> each part of that loop runs.</p>
<p>There are two radio links that can independently fail:</p>
<ul>
<li>the <strong>uplink</strong>: from the edge command node up to the drones (low rate, tolerant of dropouts),</li>
<li>the <strong>peer mesh</strong>: drone-to-drone (a self-forming, self-healing MANET where every node is also a relay).</li>
</ul>
<p>Cross those two links, each either up or down, and you get four operating modes. The system doesn't switch between them with a "degraded mode" toggle. It's a set-membership test run every tick: <em>given what's actually connected right now, what can I still do?</em></p>
<p><img decoding="async" loading="lazy" alt="The four modes: Hybrid, Central, Autonomous, Solo. How an instruction survives as each link drops." src="https://domecommand.ai/assets/images/four-modes-a914eccaa61d662993524fcc556f4f61.png" width="2080" height="2610" class="img_vSzN"></p>
<p>Read that picture left-to-right, best case to worst:</p>
<ul>
<li><strong>Hybrid</strong>: both links up. This is the design point. The edge pushes <em>one mission per group</em> (intent, not micro-control); the drones self-organize over the mesh; telemetry flows back. Every maneuver is available.</li>
<li><strong>Central</strong>: the mesh is down but the uplink holds. The edge substitutes for the mesh: it computes lanes centrally and pushes per-drone waypoints at a higher rate. Works, but only for small groups, since you're paying comms for what the swarm used to do itself.</li>
<li><strong>Autonomous</strong>: the uplink is gone and the edge is blind. An elected <strong>leader</strong> holds the last mission and re-tasks the group over the mesh. A degraded planning loop, now running <em>on the swarm itself</em>.</li>
<li><strong>Solo</strong>: both links dead. Each drone flies its last mission alone and vision-flocks just to avoid hitting its neighbors. No new orders, no target-sharing, but still flying, still on task.</li>
</ul>
<p>The point of drawing it this way is that there's no cliff. Capability falls off in steps, and at every step the system is honest about what it can and can't still do.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="gps-denied-is-a-signal-not-a-mode">GPS-denied is a signal, not a mode<a href="https://domecommand.ai/blog/built-to-survive#gps-denied-is-a-signal-not-a-mode" class="hash-link" aria-label="Direct link to GPS-denied is a signal, not a mode" title="Direct link to GPS-denied is a signal, not a mode" translate="no">​</a></h2>
<p>The instinct is to build a "GPS-denied mode." We deliberately didn't. GPS health is just another live input the loop reads every tick, sitting alongside the two link states. When it's good, the drone uses it. When it's jammed, self-positioning <strong>falls back</strong>: to visual-inertial odometry, to ultra-wideband ranging between drones, to plain vision of its neighbors. The swarm holds formation on <em>relative</em> position instead of absolute.</p>
<p>Because it's a signal and not a special case, degradation gets authored the same way everything else does, as an ordinary guard in the policy:</p>
<div class="language-text codeBlockContainer_xRo2 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_xgBx"><pre tabindex="0" class="prism-code language-text codeBlock_EVLM thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_tAUu"><span class="token-line" style="color:#F8F8F2"><span class="token plain">saturation_strike:                        # one policy, deployed once</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  when neighbors_alive == 0:      pursue(asset)       # alone → press the attack</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  when contacts_within(250m):     evade(nearest)      # threat close → break</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  otherwise:                      pursue(asset)       # ingress on the mesh lane</span><br></span></code></pre></div></div>
<p>The drone doesn't pick a "rung." Its behavior tree branches on the live signals, and a collision-avoidance floor sits underneath any policy regardless. That's the whole trick: the hard cases aren't bolted on, they're the same machinery reading worse inputs.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="flying-solo-is-the-actual-autonomy">"Flying solo" is the actual autonomy<a href="https://domecommand.ai/blog/built-to-survive#flying-solo-is-the-actual-autonomy" class="hash-link" aria-label="Direct link to &quot;Flying solo&quot; is the actual autonomy" title="Direct link to &quot;Flying solo&quot; is the actual autonomy" translate="no">​</a></h2>
<p>Here's the part I care most about. The <strong>execution loop runs on the drone, at about 20 Hz, link or no link.</strong> That's not a fallback. That <em>is</em> the autonomy. The mission coming down the uplink only selects <em>which</em> pre-deployed policy to run, against which target, with which rules of engagement. The decision logic already lives on the aircraft before the fight starts.</p>
<p>Which means when the uplink dies, nothing about the drone's inner loop stops. It keeps sensing, keeps deciding, keeps flying. Two safety floors are wired in beneath every policy and can't be authored away: an always-on collision-avoid, and a solo vision-flock fallback for when even the mesh is gone. The worst case isn't a crash. It's a drone quietly finishing its last assignment.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="smart-drones-and-dumb-drones-same-loop">Smart drones and dumb drones, same loop<a href="https://domecommand.ai/blog/built-to-survive#smart-drones-and-dumb-drones-same-loop" class="hash-link" aria-label="Direct link to Smart drones and dumb drones, same loop" title="Direct link to Smart drones and dumb drones, same loop" translate="no">​</a></h2>
<p>The last assumption to kill is that the drone has a brain at all. Most don't. So a drone's <em>control profile</em> carries two facts:</p>
<ul>
<li><strong>the link</strong> it speaks: MAVLink, DJI's PSDK, Crazyflie's CRTP, or none at all, and</li>
<li><strong>where its execution loop runs</strong>: <code>OnDevice</code> or <code>Offboard</code>.</li>
</ul>
<p>A <strong>capable</strong> drone flashed with our firmware is <code>OnDevice</code>: it runs the loop itself and flies solo when cut off. A <strong>primitive</strong> drone, no compute, just a flight controller, is <code>Offboard</code>: the runtime hosts <em>the exact same loop</em> on a companion computer (think a Raspberry Pi zip-tied to the airframe) and streams setpoints down over MAVLink. Same policy, same decisions; only the transport differs. A stock Crazyflie is <code>Offboard</code>; the same Crazyflie flashed with the firmware becomes <code>OnDevice</code>. (Genuinely hostile platforms resolve to <em>link: none</em>, observed, never tasked.)</p>
<p>That symmetry matters because it means the fleet doesn't have to be homogeneous or expensive. You can put a smart loop behind a dumb airframe and it behaves like the smart one, right up until the companion computer loses its own link, at which point a dumb airframe is exactly as blind as it should be.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="what-were-building">What we're building<a href="https://domecommand.ai/blog/built-to-survive#what-were-building" class="hash-link" aria-label="Direct link to What we're building" title="Direct link to What we're building" translate="no">​</a></h2>
<p>Here's where the work sits right now.</p>
<p><strong>In place:</strong> the loop architecture, the policy DSL with its comms-degradation predicates (<code>mesh_down</code>, <code>link_lost</code>, <code>neighbors_alive</code>), the vehicle-adapter layer that makes MAVLink/PSDK/CRTP interchangeable, and, importantly, a fully <strong>deterministic</strong> simulator where a whole engagement replays byte-for-byte from a seed. The plan → policy → execute path is closed end-to-end, under a human approve/reject gate.</p>
<p><strong>Building next:</strong> contested-RF mesh networking on real radios, GPS-denied VIO, monocular vision-flocking, on-device target recognition. The near-term proof we're driving toward is a companion computer plus a PX4 drone plus one mesh radio, with the harder swarm-survivability behaviours maturing in simulation first, then moving onto hardware.</p>
<p>So "built to survive" is first a statement about <em>architecture</em>. The design assumes the bad day from the start: one loop that keeps running as GPS, then the uplink, then the mesh, then everything gets taken away. Bringing that all the way onto hardware is the work ahead, and I'll write it up as it happens.</p>]]></content:encoded>
            <category>Drone Swarm</category>
        </item>
        <item>
            <title><![CDATA[The simulation engine]]></title>
            <link>https://domecommand.ai/blog/building-in-simulation</link>
            <guid>https://domecommand.ai/blog/building-in-simulation</guid>
            <pubDate>Sun, 24 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The data you most need to build a counter-swarm system is the data you can never collect, so we generate it. A look at the simulation engine: scenario generation across fleet and threat catalogs, why determinism matters, and how we are using engagement history to train the planner.]]></description>
            <content:encoded><![CDATA[<p>Suppose you want to test how a defense holds up against a hundred drones arriving at once, from three directions, with a radar blackout halfway through.</p>
<p>You cannot go and film that. Real saturation attacks are rare, expensive to stage, and impossible to repeat on demand, and the whole point of testing is repetition. This is the quiet problem at the center of building anything like DomeCommand: <strong>the data you most need is the data you can never collect.</strong> So you generate it instead.</p>
<p>We are putting a lot of time into the simulation engine for exactly this reason. It is where the synthetic data gets exercised in full, across combinations we could never stage: fleet laydowns against threat patterns, run and re-run until the failure modes show themselves.</p>
<!-- -->
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="scenarios-you-can-generate-not-just-author">Scenarios you can generate, not just author<a href="https://domecommand.ai/blog/building-in-simulation#scenarios-you-can-generate-not-just-author" class="hash-link" aria-label="Direct link to Scenarios you can generate, not just author" title="Direct link to Scenarios you can generate, not just author" translate="no">​</a></h2>
<p>If you cannot get real fights, you have to be able to <em>generate</em> them, precisely and repeatably. A <strong>scenario</strong> in DomeCommand is three things bundled together:</p>
<ul>
<li>a <strong>theater</strong>: where it happens, over real satellite imagery,</li>
<li>a <strong>laydown</strong>: the fleet, what you are defending with, and where your sensors and effectors sit,</li>
<li>a <strong>threat package</strong>: what is coming, how many, of what type, from where, in what waves.</li>
</ul>
<p>Both halves come out of catalogs, so scenarios combine rather than get hand-built one at a time. On the fleet side: sensor types and their coverage, effector tiers (long-range area defence, short-range gun and missile, point defence at the high-value assets), the number of defender posts and where they sit. On the threat side: airframe types, wave sizes, attack vectors, timing, and faults injected mid-fight. Pick from each and you have a scenario, which means the interesting space is a product of the catalogs and not a list somebody wrote down.</p>
<p>A few from the current library, to make it concrete:</p>
<table><thead><tr><th>Scenario</th><th>What it tests</th></tr></thead><tbody><tr><td><code>Baseline Saturation</code></td><td>100-drone wave from three axes. The default benchmark.</td></tr><tr><td><code>NE Surge</code></td><td>A 50-drone north-east wave stacked on top of the baseline.</td></tr><tr><td><code>Changi Blackout</code></td><td>A sensor goes dark mid-engagement; the inner layer has to compensate.</td></tr><tr><td><code>West Radar Down</code></td><td>Radar out for two minutes, right as an approach develops.</td></tr><tr><td><code>Comms Loss</code></td><td>The mission-command channel drops mid-fight; effectors hold posture.</td></tr><tr><td><code>SAM-C Down</code></td><td>The central long-range effector is unavailable for the whole engagement.</td></tr><tr><td><code>EW Saturation</code></td><td>Compound electronic warfare: multiple sensor faults plus a surge.</td></tr></tbody></table>
<p>And you can author a new one in plain language. You describe the fight the way you would say it out loud, <em>"a saturation swarm of 60 FPV drones from the north-west, with a two-minute radar blackout halfway through"</em>, and the engine parses it into the same structured parameters the catalog scenarios use: threat type, count, attack vectors, defender posts, difficulty, duration, faults. You get to review and correct the parameters before it runs.</p>
<p><img decoding="async" loading="lazy" alt="Authoring a scenario in plain English. The parsed parameters are shown for review before the scenario is created." src="https://domecommand.ai/assets/images/nl-scenario-creation-63fca7b6b4538a8c6532e7f7bb146341.png" width="1500" height="920" class="img_vSzN"></p>
<p>That plain-language front door matters more than it looks. It is the difference between a simulator only its authors can drive and one an operator can use to ask <em>"what if it came from the other direction?"</em> and get an answer in seconds.</p>
<p>Then you run it, and watch the whole engagement play out: the threat tally, the rules of engagement in force, the priority queue, the plans the system is proposing, and what each one is predicted to cost you.</p>
<p><img decoding="async" loading="lazy" alt="The simulator running a saturation scenario: threat tally, rules of engagement, priority threat queue, and the plans proposed for approval." src="https://domecommand.ai/assets/images/simulator-03c6577c5982347dbcfbcf4baa184731.png" width="1028" height="542" class="img_vSzN"></p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="determinism-is-the-whole-game">Determinism is the whole game<a href="https://domecommand.ai/blog/building-in-simulation#determinism-is-the-whole-game" class="hash-link" aria-label="Direct link to Determinism is the whole game" title="Direct link to Determinism is the whole game" translate="no">​</a></h2>
<p>Here's the part I'd defend hardest. A simulator is only useful for <em>learning from</em> if it's <strong>deterministic</strong>: if the same setup plays out identically every single time.</p>
<p>We put real engineering into this. Every source of randomness in the sim funnels through one seam: a virtual clock, a seeded random-number generator per agent, and an event-logged command stream. The consequence is that a whole engagement becomes a <strong>pure function of the seed plus the ordered list of commands.</strong> Replay it and you get a byte-for-byte identical fight, even with reactive drones and even with an operator making live decisions in the middle, because those decisions get recorded as events too.</p>
<p>(If that sounds familiar, we borrowed the idea openly from deterministic-simulation testing in databases like TigerBeetle and FoundationDB. Funnel all nondeterminism through one place, and your entire system becomes reproducible.)</p>
<p>Why care so much? Because it turns the simulator into an instrument. Run a scenario, make one different decision partway through, run it again, and because <em>nothing else</em> could have changed, the difference in outcome is attributable to your decision and not to luck. That's the precondition for learning anything real from a simulation. Without it you're just watching fireworks.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="threats-that-behave-not-dots-that-fly-straight">Threats that <em>behave</em>, not dots that fly straight<a href="https://domecommand.ai/blog/building-in-simulation#threats-that-behave-not-dots-that-fly-straight" class="hash-link" aria-label="Direct link to threats-that-behave-not-dots-that-fly-straight" title="Direct link to threats-that-behave-not-dots-that-fly-straight" translate="no">​</a></h2>
<p>The first version of any sim has threats that follow scripted paths: fly from A to B, ignore everything. That's useless the moment your defense does something, because a real adversary reacts.</p>
<p>So the threats are little <strong>agents</strong> with intent. A kamikaze drone terminates on its target; a payload-drop runs in, drops, and egresses; a recon drone loiters and watches. Each one senses only what it could actually see: an attacker notices an interceptor when it comes into camera range, and can <em>feel</em> a GPS jammer by the way its satellite fix diverges from its inertial one. Then it reacts: press the attack, break evasively, dead-reckon through the jam zone.</p>
<p>None of this is hand-waved. The maneuvers are grounded in the real guidance literature: proportional navigation (how missiles have pursued targets for decades), Dubins paths (the tightest turn a fixed-wing can make), vector fields for orbiting. The goal is an adversary that's hard for honest reasons.</p>
<p>One detail I find clarifying: the simulator holds <strong>two</strong> versions of the world. There's ground truth, exactly where everything is and what it intends, which only the sim knows. And there's the <strong>deduced</strong> picture, noisy observations run through sensor fusion into tracks, which is all the operator is allowed to see. Same data structure, opposite provenance. The gap between them <em>is</em> the fog of war, and being able to measure that gap is a large part of why you simulate at all.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="learning-from-the-history">Learning from the history<a href="https://domecommand.ai/blog/building-in-simulation#learning-from-the-history" class="hash-link" aria-label="Direct link to Learning from the history" title="Direct link to Learning from the history" translate="no">​</a></h2>
<p>Every run leaves a record: the scenario, the picture the operator saw, the plans on offer, which one was approved, and what it cost. That history is a training set, and we are experimenting with two ways of using it.</p>
<p>The first is <strong>fine-tuning</strong>. Take the engagements that went well, and the human approvals and rejections attached to them, and tune a model on that trace so its next plan looks more like the ones that worked and less like the ones a person threw out. This is the cheaper, more direct path, and it learns the taste of an operator as much as the physics of the fight.</p>
<p>The second is <strong>reinforcement learning</strong> against the simulator itself. That is the more ambitious one, and it only works because of the determinism above: an episode you cannot reproduce is an episode you cannot learn from.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="the-alphazero-shaped-bet">The AlphaZero-shaped bet<a href="https://domecommand.ai/blog/building-in-simulation#the-alphazero-shaped-bet" class="hash-link" aria-label="Direct link to The AlphaZero-shaped bet" title="Direct link to The AlphaZero-shaped bet" translate="no">​</a></h2>
<p>Now the part I'm most excited about building toward: this is a <strong>direction we're heading</strong>, and the groundwork for it is what we've been putting in place.</p>
<p>Think about how AlphaZero learned Go. It wasn't fed human games. It was given the rules (a closed, deterministic world where every move has a defined outcome) and it played itself, millions of times, getting better by grinding against its own improving opponent. The rules never changed, so every game was a clean experiment, and self-play could squeeze signal out of it indefinitely.</p>
<p>Look back at what we just built. A <strong>deterministic</strong> environment with fixed rules. Threats and defenders that are <strong>agents</strong> taking actions and getting outcomes. A <strong>reproducible</strong> result for any given setup. That is <em>exactly</em> the shape a self-play system needs. The simulator isn't just a test harness. It's a candidate training environment.</p>
<p>The experiment we're circling: wrap the deterministic sim as a reinforcement-learning environment. A scenario becomes an episode. The action space is a <strong>plan</strong>, the same structured mission format the system already emits. The reward is the outcome the sim already scores: leakers stopped, cost spent, coordination quality, rules-of-engagement compliance, robustness to comms loss. Then let a planner learn against that reward the way AlphaZero learned against a win. The adversary gets harder over iterations in ways grounded in real simulated outcomes, not hand-tuned difficulty knobs, and a human stays in the loop on what gets promoted.</p>
<p>Here's how the work stacks: the deterministic engine, the behavior model, the plain-English scenarios, and the replay-and-compare loop are the foundation we've built. The self-play / RL layer is what we're building toward on top of it. I'm writing it down now because the interesting thing about the architecture is that it was <em>designed</em> to make that possible. You build the fixed-rule world first. What you do with it is the next chapter.</p>
<hr>
<p><em>The scenario-authoring screenshots are from the DomeCommand console. The determinism approach draws on publicly documented deterministic-simulation testing (TigerBeetle's VOPR, FoundationDB); the threat maneuvers on standard guidance literature. Nothing here reflects a specific site or operator.</em></p>]]></content:encoded>
            <category>Simulation</category>
        </item>
        <item>
            <title><![CDATA[How an autonomous flying machine actually works]]></title>
            <link>https://domecommand.ai/blog/how-a-flying-machine-works</link>
            <guid>https://domecommand.ai/blog/how-a-flying-machine-works</guid>
            <pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Two jobs, and everything else is detail: work out where you are, then work out what the motors should do about it. A ground-up explainer built on the cascaded controller I wrote for Udacity's Flying Car Nanodegree.]]></description>
            <content:encoded><![CDATA[<p>Long before any of the swarm work, there was a tiny quadcopter hovering over my desk, and me not quite believing it could hold itself in the air.</p>
<p>My first real flight-controller project was Udacity's <a href="https://www.udacity.com/course/flying-car-nanodegree--nd787" target="_blank" rel="noopener noreferrer">Flying Car and Autonomous Flight Engineer Nanodegree</a>. The course is four projects, and they are more or less the four things an autonomous aircraft has to do: fly a shape (backyard flyer), plan a route through a city (3D motion planning), control the vehicle along that route (build a controller), and work out where it is in the first place (estimation and sensor fusion). I wrote mine in Python and the code is <a href="https://github.com/v3g42/FCND-Controls" target="_blank" rel="noopener noreferrer">still on GitHub</a>. It is a basic implementation, which is exactly what makes it a good way to explain this.</p>
<p>Underneath, it comes down to two jobs. <strong>Know where you are. Decide what to do about it.</strong> Everything else is detail.</p>
<!-- -->
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="state-estimation-where-am-i">State estimation: where am I?<a href="https://domecommand.ai/blog/how-a-flying-machine-works#state-estimation-where-am-i" class="hash-link" aria-label="Direct link to State estimation: where am I?" title="Direct link to State estimation: where am I?" translate="no">​</a></h2>
<p>You would think you could just read the sensors. You cannot, and the reason is worth sitting with.</p>
<p>A <strong>gyroscope</strong> measures how fast the drone is rotating. To get an actual angle you integrate that rate over time, and any small constant error in the measurement integrates too, piling up forever. Integrate the gyro alone and your sense of which way is up slowly rots, even sitting still on a desk. That is drift, and it is fatal.</p>
<p>An <strong>accelerometer</strong> feels gravity, which is an absolute, drift-free reference for which way is down. It also feels vibration and every movement the drone makes, so in the short term it is noisy and untrustworthy.</p>
<p>They fail in opposite directions: the gyro is smooth but drifts, the accelerometer is stable but noisy. So you fuse them, trusting the gyro moment to moment and letting the accelerometer pull the long-run drift back towards level. Add GPS and a magnetometer and you can do the same for position and heading, which is what the course's estimation project builds: an <strong>Extended Kalman Filter</strong> that holds a belief about the full state along with how uncertain it is, predicts that state forward with the physics on every tick, then corrects it against whatever measurements arrived, weighting each by how much it deserves to be trusted.</p>
<p>The cheap version is worth knowing too. A <strong>complementary filter</strong> does a smaller version of the job with almost no machinery: trust the gyro at high frequency, the accelerometer at low frequency, blend. It gives you attitude and not much more, and for "just stay level" that is plenty. The <a href="https://www.bitcraze.io/" target="_blank" rel="noopener noreferrer">Crazyflie</a> I later kept on my desk ships with both, and choosing between them is a neat summary of the trade: complementary filter to hold it level, EKF the moment you want it to know where it is in a room.</p>
<figure style="background:#f7f7f4;padding:1.25rem 1.25rem 0.5rem;border-radius:10px;margin:2rem 0"><img src="https://domecommand.ai/img/blog/crazyflie/extended_kalman_filter.png" alt="Extended Kalman Filter block diagram: a predict and update cycle fusing several sensors into a full pose estimate." style="width:100%;display:block"><figcaption style="font-size:0.8rem;color:#555;padding:0.75rem 0;font-family:monospace">Predict, then correct. Diagram: <a href="https://www.bitcraze.io/documentation/repository/crazyflie-firmware/master/functional-areas/sensor-to-control/state_estimators/" target="_blank" rel="noreferrer">Bitcraze crazyflie-firmware docs</a>.</figcaption></figure>
<p>Whichever filter you run, the output is the same: one clean belief about position, velocity and attitude. That belief is the only thing the next job gets to see.</p>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="the-control-loop-what-do-i-do-about-it">The control loop: what do I do about it?<a href="https://domecommand.ai/blog/how-a-flying-machine-works#the-control-loop-what-do-i-do-about-it" class="hash-link" aria-label="Direct link to The control loop: what do I do about it?" title="Direct link to The control loop: what do I do about it?" translate="no">​</a></h2>
<p>The controller's question is narrower. Given where I am, and where I am supposed to be, what should the motors do?</p>
<p>The trick is that you never answer "be at that point in space" in one shot. You build a <strong>cascade</strong>: a stack of small controllers, each turning a goal into a goal for the next one down. My implementation is five functions, and reading them in order is the whole idea.</p>
<ul>
<li><code>lateral_position_control</code> turns the horizontal position error into a desired <strong>acceleration</strong>.</li>
<li><code>altitude_control</code> does the vertical half, turning altitude error into a <strong>thrust</strong>, accounting for the tilt of the craft (a tilted drone spends part of its thrust sideways).</li>
<li><code>roll_pitch_controller</code> turns that desired acceleration into a desired <strong>attitude</strong>. To accelerate forward, tilt forward. A quadcopter has no other way to move.</li>
<li><code>body_rate_control</code> turns the desired attitude into the <strong>moments</strong> the motors have to produce. This is the innermost loop, fed straight from the gyro.</li>
<li><code>yaw_control</code> handles heading on its own, since where the craft points is decoupled from where it is going.</li>
</ul>
<p>Two things make the cascade work. The first is that each loop is little more than a gain on an error, which keeps tuning tractable: in my controller the inner rate loops are stiff (<code>Kp_p = Kp_q = 20</code>) and the outer position loop is soft (<code>Kp_pos = 6</code>). The inner loop has to be fast, because rotational dynamics are twitchy and a quadcopter that loses its attitude flips. The outer loop can afford to be slow, because moving through space is comparatively sluggish. <strong>Fast and reflexive on the inside, slow and strategic on the outside.</strong></p>
<p>The second is saturation. Every command is clamped before it leaves: a maximum tilt, a maximum ascent and descent rate, a maximum speed. Without those, the position loop cheerfully asks for a ninety-degree bank and the drone falls out of the sky doing exactly what it was told.</p>
<p>Production firmware is this, tuned harder. The Crazyflie runs the same cascade with a PID at each stage, the inner rate loop at 500 Hz and the outer position loop at 100 Hz, and there are fancier controllers again (Mellinger's geometric controller, INDI) once you want aggressive flight. The shape does not change.</p>
<figure style="background:#f7f7f4;padding:1.25rem 1.25rem 0.5rem;border-radius:10px;margin:2rem 0"><img src="https://domecommand.ai/img/blog/crazyflie/cascaded_pid_controller.png" alt="Cascaded PID controller: position to velocity to attitude to attitude-rate to motor commands." style="width:100%;display:block"><figcaption style="font-size:0.8rem;color:#555;padding:0.75rem 0;font-family:monospace">The same cascade in the Crazyflie firmware. Diagram: <a href="https://www.bitcraze.io/documentation/repository/crazyflie-firmware/master/functional-areas/sensor-to-control/controllers/" target="_blank" rel="noreferrer">Bitcraze docs</a>.</figcaption></figure>
<h2 class="anchor anchorWithStickyNavbar_vmXh" id="why-i-am-telling-you-this">Why I am telling you this<a href="https://domecommand.ai/blog/how-a-flying-machine-works#why-i-am-telling-you-this" class="hash-link" aria-label="Direct link to Why I am telling you this" title="Direct link to Why I am telling you this" translate="no">​</a></h2>
<p>Squint at the two jobs and you get: sense, estimate, decide, act, and then do it again, forever. That is the shape DomeCommand runs, one level up. Where the quadcopter fuses a gyro and an accelerometer into which way is up, DomeCommand fuses radar and RF and cameras into what is out there. Where the cascade turns a setpoint into motor commands, the planner turns a commander's intent into tasks for a fleet.</p>
<p>The instinct that a hard autonomy problem is really a clean estimation problem stacked on a clean control problem came from a course assignment and a small quadcopter I could not stop tinkering with. It scales further than I expected.</p>
<hr>
<p><em>The two diagrams above are from <a href="https://www.bitcraze.io/documentation/repository/crazyflie-firmware/master/functional-areas/sensor-to-control/" target="_blank" rel="noopener noreferrer">Bitcraze's open crazyflie-firmware documentation</a>, a genuinely good place to learn this hands-on.</em></p>]]></content:encoded>
            <category>Fundamentals</category>
        </item>
    </channel>
</rss>