⭐ If it saved you time, leave a star. That is how the next tester who needs it finds out it exists.

The short version. This release is about numbers you can trust and targeting that catches what
you aimed at. If you read nothing else:
- Aiming at a process now catches a connection from its first packet, TCP and UDP alike.
"Drop SYN" combined with a process target previously did nothing at all, and DNS and QUIC walked
past untouched. - "Effective loss" was wrong and is now right. It counts every impairment, over the traffic you
aimed at. A session losing 90% of its traffic to a speed limit used to report 0.0%. - Presets and the shipped scenarios were recalculated against published measurements. Latency
was applied twice, and three presets were eight times too fast. - A session moves about 1.3x more packets a second, and a delay you ask for arrives without the
old few-millisecond surcharge. - New: "Capture only the targeted traffic". On a real run without it, the driver threw away 43%
of the traffic you had aimed at before this tool ever saw it. - New: "Show only the targeted traffic", five new presets, a "Driver queue wait" reading, and a
warning when your target shares a port with another program. - Config and scenario files now reject a typo instead of ignoring it.
- Plus a long list of fixes to the counters, the Connections table and session start/stop.
If you script this tool, read the BREAKING section: presets, "Effective loss", profiles, the
statistics CSV, the connections CSV and the reproduction report all changed shape or meaning.
BREAKING
-
A misspelled setting in a config file is now an error.
"latancy": 300used to load in
silence, so--dry-runsaid "Configuration is valid" and the run went out with no latency at
all. Unknown settings now fail with code3, naming the key and suggesting the near miss. Files
this tool saved keep loading. A hand-written file with an unknown name will not. -
A mistake in a scenario file now says so instead of doing nothing.
"duraton"used to leave a
reset at its default and"lop"used to turn looping off, both silently, so the tool looked like
it was ignoring your file. Unknown keys, in a step or at the top level, are now an error naming
the key. A correct file keeps working. -
A scenario that ends now ends the run. A non-looping scenario with a timeline used to print
"Scenario finished." and then run forever, which in a pipeline is a job that hangs to its own
timeout and writes nosummary. Such a run now stops withstop_reason: "scenario_done"and says
so at the start.--durationstill wins wherever you pass it. -
The old
reset_nowscenario action is gone.reset_tcpdoes the same thing and is now the
only action. A file still usingreset_nowwill not load and says which step to fix. The
"Reset TCP now" button is unaffected. -
The presets were wrong and are now checked against published measurements. Latency was set as
if it were a ping, but it is added in both directions, so "Satellite link" at 600 ms delivered a
1200 ms ping. Three presets held kilobits where the field wants kilobytes, making them
eight times too fast. If you scripted--preset, your traffic changes. Ids are unchanged, but
--preset "Satellite link"is now "Satellite (geostationary)". -
The shipped scenarios in
scenarios/were recalculated the same way, so a scenario and a
preset describe the same network with the same numbers. The ping you get is now the number written
in the file. -
"Effective loss" now means what its name says, and your numbers will change. It used to be the
configured Loss percentage over every packet seen, ignoring speed limits, blocking, LAN cut, link
outages, resets, dropped SYNs and expired NAT. It now counts every impairment, over the traffic you
aimed at.effective_loss_pctandeffective_corruption_pctmoved with it, so reports from before
and after are not comparable. -
"Duplicated" counts packets actually sent twice, not packets the tool decided to duplicate.
Under load the copy was thrown away while the counter rose anyway: 40 packets in, "Duplicated"
read 40, nothing reached the wire. This matches "Corrupted", which always counted only real
changes. Reports from before and after are not comparable on that field. -
Profiles now remember more of the link: latency spikes, link outages (flapping) and the buffer,
on top of the original seven fields. Outages are why it was worth doing - a profile can finally
describe a link that cuts out. Old profiles load exactly as before. Picking a preset now clears
all of these, so "Perfect network" really does clear everything, and--presetfinally does what
the window does. -
The Connections table now shows what arrived AND what was offered. "down", "up" and "total"
held captured bytes under headings meaning delivered, so a row could read 5 MB while the
application got 0.4 MB. Those three are now delivered, and new "down seen" / "up seen"
hold what was captured. The connections CSV changed shape: the three old byte columns are
replaced by sixdelivered_*andcaptured_*ones, renamed rather than reused. -
The reproduction report's "connections reset" counted packets, not connections. One reset
connection could report itself as 50. The report now carries three keys that answer three
questions:connections_reset,rst_packets_droppedandrst_sent. The statistics CSV gained a
matchingconnections_resetcolumn. Nothing on screen changed. -
The statistics CSV gained
capture_narrowedandpackets_in_scopecolumns. The first records
whether "Capture only the targeted traffic" was in effect, without which two rows under one header
can count completely different traffic. Your existing file is moved aside with a timestamp and a
fresh one started, as it already is whenever columns change, so no row misaligns. A script reading
by column position needs the new offsets. -
--guino longer accepts any other option.--gui --loss 30 --duration 600used to open no
window and quietly impair in the background, with no STOP button anywhere. It now stops with a
usage error (code2) and says what to do instead. If a script relied on this, delete--gui
from it.
Added
-
Five new presets: Satellite (low orbit), Distant server (another continent), Congested home
link (bufferbloat), Train / metro (tunnels) and In-flight Wi-Fi. Two are worth a note: bufferbloat
only bites once you really saturate the link, and Train / metro is the only preset that takes the
connection fully down, so your application has to reconnect rather than just slow down. -
"Show only the targeted traffic" (Settings, off by default) points the counters, the chart, the
Connections table and the connections CSV at your target alone. It changes what you see, never
what is captured or impaired. Three things deliberately do not follow it: "Queue overflow",
"Dropped at stop" and "Send failed" always cover everything, because they count the tool's own
losses. The statistics CSV carries both totals instead. Reports and--format jsonare unchanged. -
"Capture only the targeted traffic" (
--narrow-filter) pushes your destination IP and port
into WinDivert, so traffic that could never be impaired is not handed over at all. It is a
correctness fix, not just a speed one: measured on a real run, without it the driver threw away
43% of the traffic you aimed at before the tool saw it. It applies at START, and does nothing
for a process target, a wildcard or anre:pattern - the run says which. -
"Driver queue wait (peak)" in the Session tab shows how long packets waited inside WinDivert
before the tool saw them. That is the one delay the tool adds and counted nowhere. It is measured,
not estimated. Expect a fraction of a millisecond when idle; above 50 ms the log says so. Blank
under--simulate. -
A session now records the WinDivert queue it ran behind (length, time and size) in the log and
in the reproduction report, so a report from a machine you do not have in front of you says which
queue produced its numbers. -
The tool now warns when your target shares a port with another program. Windows lets several
programs hold one local port - that is how mDNS, SSDP and DHCP work - and this tool decides what to
break from the port number. On this machine four ports out of 127 were shared, one of them by five
programs. Applying a target now names the port and who holds it, so you know that part of the
result is a coin toss. -
The command line says when your target stops matching, instead of finishing green in silence.
Measured: aiming by process id and restarting the program left five out of five new connections
untouched with nothing in the output. Aiming by name recovers on its own and costs only the
first connection. Aiming by process id never recovers. If the program under test restarts, aim
by name. -
Every run with a process target ends by saying how much of the captured traffic was yours -
"In scope: 40 of 500 captured packets" - and calls it out when that is zero. A run where your
target caught nothing looks exactly like a run where your application coped. It is a warning, not
a failure, because a quiet target is perfectly ordinary.
Changed
-
The checkbox is now called "Capture only the targeted traffic", not "Narrow the driver filter
to the target". The old name described the machinery. The--narrow-filterflag is unchanged, and
its tooltip was rewritten to say what it does and when it will not apply. -
A session moves about a third more packets a second. The tool handles packets on two threads,
and Python left one waiting up to 5 ms for its turn - that waiting, not the work, was the limit. A
session now asks for shorter turns and hands the setting back at STOP. Measured on loopback and a
real card: 1.33x to 1.36x, in 24 comparisons out of 24, at slightly less processor time per
packet. -
A packet you did not ask to change goes back on the wire exactly as it arrived, and the session
moves about 12% more traffic for it. Checksums used to be recomputed for every packet, including
untouched ones, which also meant a session with nothing configured did not quite pass traffic
through unchanged. Measured: 1.12x more packets a second, 8 comparisons out of 8, and 24 MiB of
TCP arrived byte for byte over a real card. -
Targeting a process no longer competes with the traffic it is measuring. Working out which
sockets belong to your target used to happen on the thread handling your packets, dozens of times a
second. It now runs on its own thread. A freshly opened connection is still impaired within tens of
milliseconds and STOP stays immediate. -
Semicolons are gone from the interface texts and both READMEs. Twenty-one tooltips and about
eighty lines of documentation used them to join sentences, which is not how people write. Code
samples keep theirs, where a semicolon is syntax. -
"Spike chance" and "Spike size" moved to "Latency (ping)" from "Advanced (NAT / connections)",
because a spike is latency. Nothing about how they work changed, nor their flags, nor their place
in a profile. -
"Blocking (firewall)" starts collapsed on a fresh install, like the other advanced panels. If
you have used the tool before, your own choices are remembered. -
The Latency and Jitter tooltips say the thing that was easy to get wrong: ping rises by about
twice the latency you set, because both the request and the reply are delayed, while jitter widens
the wobble by about 1.4x rather than doubling it. Both READMEs explain it too.
Fixed
-
Aiming at a process missed the first packet of every new connection. Measured: 20 fresh
connections with "Drop SYN" at 100% produced 20 successful connections and not one dropped SYN, so
"Drop SYN" plus a process target did nothing at all. The first packet is now checked against
the live socket map.
What changes for you: connections aimed at a process now take longer to open and a minority
fail outright, which is what a bad network does. If a test measured "time to first byte" under
impairment, expect it to move. -
Aiming at a process did not touch the first packet of a UDP exchange - and DNS and QUIC take a
fresh port every time, so it did not touch them at all. If you tested a game, a video call or a
browser over QUIC and the impairment seemed weaker than configured, this is why. Ordinary TCP data
is deliberately still not re-checked: measuring showed it would cost throughput for nothing. -
The live "which app owns this port" map could be dragged backwards by a stale reading, so a
connection was briefly credited to the wrong application. A periodic sweep of the socket table,
always a little behind, was applied on top of the live signal from the driver. Measured over 25
seconds of ordinary traffic: 919 times. Readings are now weighed by when they were taken. -
Targeting could follow a process id after the process was gone, and Windows hands those numbers
out again. A restarted target could come back under a remembered number and not be impaired,
while an unrelated program inheriting that number was. The tool now checks the process is still
the same one, and forgets it when it closes its last connection. -
Three fixes to short-lived and freshly opened connections. Targeting now follows the system's
socket events instead of scanning a few times a second, so a connection that opens and closes
between two scans is no longer missed. The Connections table fills in the owning program from the
first packet instead of leaving the row blank. And setting a process target no longer makes the
first START pause for a second or two. -
The "impaired?" column now reflects the whole session, not just this instant. It asked whether
the port was in the target right now, so a row flipped to "no" the moment its connection closed
and a run impairing all of Chrome looked like it caught almost nothing. The column, the row
highlight, the sort and the CSV now read one record, so they cannot disagree. -
The tool kept pausing itself and then blaming WinDivert. Every so often it spent up to half a
second working out which program owns which connection, stopped collecting packets while it did,
and then told you the driver's queue was backing up and to narrow your filter. The delay was its
own. Measured over 95 seconds with programs constantly starting: the worst pile-up dropped from
508 ms to 17 ms and the warning stopped appearing. -
The "driver held a packet" warning described lost accuracy when what you were losing was
traffic. Measured on a deliberately overloaded run: at 138,000 packets a second offered, the tool
moved about 14,000 and 91.75% was thrown away by the driver before the tool saw it, while every
drop counter on screen read zero. The warning now says a full driver queue means dropped packets,
not just late ones. -
With "Capture only the targeted traffic" on, the Statistics and Connections tabs said the exact
opposite of the truth, keeping their "counters cover ALL captured traffic" line while the driver
had been told to hand over nothing else. The checkbox tooltip promised the opposite and both
READMEs contradicted themselves. Both notes, the chart caption and the READMEs now describe what
the figures actually cover, including the case where a process target is set as well. -
Four fixes to the Scope settings. The two "only the targeted traffic" switches now sit in one
Scope card instead of separate panels a few rows apart, with a line that tells you before
START whether your destination can actually be narrowed. The Settings window scrolls, so no group
falls off the bottom. Starting a session logs which of the two outcomes you got. And the Session
panel gained a "Capture" row, so a saved run says which traffic it counted. -
Three counters were lying about how much was lost. "Buffer overflow" and "Dropped at stop"
charged for duplicate copies as well as packets, so a run duplicating everything reported nearly
twice as many dropped as captured. A connection's "dropped" count ignored packets its own queue
threw away. And packets the tool failed to re-inject left the arithmetic entirely - those now have
a "Send failed" counter, a banner and an event-log entry, throttled so they cannot flood the
log. -
Three tooltips were telling you things that were not true. "Dropped" claimed to count link
outages, which have had their own counter for a while. "Downloaded (MB)" promised a figure that
never appeared. And "Effective loss" read as "never reached the far end" when it measures damage
done on this machine - loss out in the network never arrives here, so nothing here can count it.
No numbers changed. -
The delay you set is the delay you get. Windows rounds up the wait used to hold a packet back,
and it was a fixed surcharge rather than a percentage, so it barely showed at 100 ms and swamped
small settings. Measured with a plainping: asking for 10 ms used to cost 12.6 ms extra and now
costs 1 ms, the same 1 ms as at 50 ms. Jitter below about 15 ms used to vanish into the noise. -
Long cut-offs and NAT blackouts now last as long as you set them. The tool forgets a connection
it has not seen for a while, and a forgotten connection looks brand new - so a reset cooldown above
about half a minute resumed after roughly 30 seconds however long you asked for, and "NAT mapping
expiry" at 30 seconds never blocked a single packet. -
"NAT mapping expiry" now really cuts the incoming direction. The packet rejected for "the
mapping has expired" was itself counted as activity, so the mapping reopened on the spot: an app
that never sent a keep-alive lost about one packet every five seconds and otherwise worked, passing
a test it should have failed. Incoming traffic now stays cut until the application sends something. -
"Reset connections" now works where it claimed to. It no longer fires on a connection that is
still opening, where the forged reset carries no acknowledgement number and Windows is entitled to
ignore it - measured, the connection hung until its own timeout. And it now really resets local
(loopback) connections, which previously just went quiet for the cooldown while the tool reported
an RST as sent. Connections to other machines were never affected. -
Ping traffic never appeared in the Connections tab, which claims to list all of them - anything
without ports was silently left out. Thirty seconds of pinging left the tab empty while the
counters ticked up beside it. Portless traffic is now listed as one row per address with the port
cells empty. -
The Connections tab could empty itself instead of dropping its oldest rows. When many rows
carried the same timestamp they all fell on the same side of the "old" estimate, so far more went
than intended - in the extreme, everything. It now refuses to drop below the level it is trimming
to. -
When the capture could not start, the tool told you the wrong reason. A handle that failed to
open producedWinDivert handle is not open- a symptom naming nothing - while the real cause went
to a diagnostic file nobody reads. The helpful messages both interfaces already had, including the
window's "run as Administrator" hint, could therefore never appear. Starting now fails immediately,
with the reason. -
Four ways a session mishandled its own start and stop. A failed start could leave the tool
holding your traffic without impairing it, refusing every later START. Ending a session kept hold
of your traffic for a moment while tidying up, so a connection could stall. STOP could take two
seconds in two different races. And the log could print a fault above its own "Start" line. -
An unforeseen error during a run is now an exit code, not a stack trace. A failure inside the
reporting loop escaped as a raw traceback with code1, which is also "the session could not
start", so a pipeline could not tell an internal bug from a driver that would not open. It is now
runtime(1) with a line saying what happened, and the run still writes its complete
summary- a--format jsonfile no longer ends mid-stream. -
Three fixes to
--dry-runand--doctorclaiming more than they checked.--dry-runsaid
"Configuration is valid" about a command that then exits7, so it now says which half it checked
and points at--doctor. It also reads your--scenariofile now, which it never opened.
--doctorno longer calls a driver "not loaded" when Windows simply refused to let it look. -
Three fixes to the shared-port warning. It listed your own target among the strangers, because
a program like Chrome runs several processes. It offered two possible outcomes when the socket
table already decides which one applies. And it printed bare numbers, so5353read like a fault
rather than mDNS. It now says5353 (mDNS), names the program, and states the one outcome that is
true. -
Three files could stop the program from starting, and no longer do. A translation file with a
malformed header, a window-layout file with an entry in the wrong shape, and a--configfile that
was valid JSON but not a set of settings - the last one reported the tool as having crashed. Each
is now skipped or reported clearly, and the rest of your setup is kept. -
Four smaller interface fixes. The throughput chart could be squeezed until it vanished on a
narrow window (the Live tab now scrolls). "Close" was cut in half at the bottom of the Settings
window. The profile picker cut long names off at 24 characters. And "Save profile..." opens with
the cursor already in the name box. -
Two things left litter behind. A failed "Export connections CSV" left a stray
.tmpfile next
to the real one, and stopping a session could file a crash report incrashes/for an ordinary
STOP. Neither happens now. -
Starting a second scenario without stopping the first is no longer possible. The engine
replaced the running one and left the old one going in the background, so two scenarios fought over
the same settings. Nothing in the program does this today, so it is a guard rather than a bug you
could have hit.
Docs
-
Both READMEs now document the scenario file format, the seven shipped scenarios, both CSV exports
and all 17 Connections columns - none of which were written down anywhere. The column meanings
existed only as tooltips, and the CSV headers only in the code. -
Three corrections to what the READMEs claimed. A connection is in scope "the moment it opens"
only for a program the tool already recognises, not for that program's first connection. An
exclusion like!chromealso covers every connection whose owner could not be identified, so do
not use one to protect an application - name the one you do want broken. And both files explained
greyed-out fields with an "Enable" checkbox that no longer exists. -
The Requirements and Tests sections now match reality.
psutilis not what makes process
targeting work on Windows - the socket table and process names come from the OS, and targeting
keeps working without it. Source installs work on Python 3.10 and newer while CI tests and builds
on 3.14 only, which is the version frozen into the released.exe. The Tests section also now
covers the GUI render check and how a release is produced.