AdrenalineNet

Replication that manages bandwidth by itself, and stops your vehicles, projectiles and physics props from teleporting. You do not change a line of gameplay code.

Unreal Engine 5.8 Win64 · Linux · Mac C++ source included Works with Steam Sockets & EOS Version 1.0.0
2.7×less bandwidth than Unreal, same scene
3.1 Bper position update
20 cmworst on-screen jump at 15 % packet loss
(160 cm without it)
0lines of gameplay code to change

Every number on this page is measured. How, and on what, is written next to each one in Measured results.

What AdrenalineNet does

In plain words, before any of the technical detail.

When your game replicates an actor, Unreal sends its position and properties to every client that can see it. Two things about how it does that cost you money and look bad:

AdrenalineNet replaces that layer. It sends the difference from the last state your client actually confirmed, packed to the precision you choose, and it spends your bandwidth on the actors the player is looking at. On the client it plays those updates back through a small buffer, so a lost packet becomes a brief smooth slowdown instead of a jump.

The important part: you change nothing It reads the UPROPERTY(Replicated) markup your project already has. No component to add, no property to re-declare, no macro to sprinkle. Actor spawning, ownership and the transport stay with Unreal, which is why it keeps working with Steam Sockets, EOS and any custom net driver.

Who this is for

Anyone shipping multiplayer with things that move and are not Characters: racing and driving games, shooters with projectiles, survival games with physics props, co-op with doors, platforms and vehicles, and anything where you have ever been told "it is laggy" and the ping was fine.

1Install

Two minutes. Works on a C++ project, and on a Blueprint project that is allowed to compile plugins.

Put the folder in your project

Unzip the download. Copy the AdrenalineNet folder into your project's Plugins folder, so you end up with:

MyGame/
  MyGame.uproject
  Plugins/
    AdrenalineNet/
      AdrenalineNet.uplugin
      Source/
      Config/

If your project has no Plugins folder, make one with exactly that name.

Open the project and let it build

Start the editor. It will say the plugin's modules are missing and offer to rebuild them. Click Yes and wait. This happens once.

If it says it cannot compile That is a Blueprint-only project without build tools. Install Visual Studio 2022 with the "Game development with C++" workload, then open the project again. This is Unreal's requirement for any C++ plugin, not ours.

Confirm it is enabled

Edit → Plugins, search for AdrenalineNet. The checkbox should already be ticked, because the .uplugin ships enabled. If it is not, tick it and restart.

Play

There is no step four. The plugin is on by default and starts working on its own. The next section is how to see that it did.

2Check that it works

Do this once, on your own project, before you trust any number on this page.

Turn the HUD on

Edit → Project Settings → Plugins → AdrenalineNet, tick Show HUD under Diagnostics.

Play with two windows

In the editor toolbar next to Play: set Number of Players to 2 and Net Mode to Play As Listen Server. Press Play.

Read four lines

On the server window you should see something like this, and the numbers should move:

AdrenalineNet  [active]
  upstream 12.4 kB/s   budget 1626 B/tick   actors 83 (sent 41)
  rtt 24 ms   jitter 4 ms   loss 0.3%   interp 96 ms
   41.2%  BP_Crate.[Location]   18240 B,  1821 sends, 0 dropped

If upstream is above zero and the bottom lines name your own actors, it is working.

Get the full picture

Open the console (~) and type ANet.Report. It writes an HTML file to Saved/AdrenalineNet/ with every property ranked by cost, plus exactly what the plugin did and did not take over in your project.

If the HUD says "link not measured yet" for more than a few seconds That is not cosmetic - it means acknowledgements are not coming back, and the plugin will be sending far more than it should. Go to Troubleshooting.

3The one thing worth knowing

Read this even if you read nothing else.

AdrenalineNet takes over

  • + Position, rotation and velocity of non-Character actors
  • + Your own replicated properties (numbers, booleans, strings, vectors, rotations)
  • + How much bandwidth each client gets, and which actors get it
  • + Smoothing on the client

Unreal keeps

  • = Character movement. Deliberately.
  • = Spawning, destroying, ownership, relevancy of actor channels
  • = The transport: your net driver, Steam Sockets, EOS
  • = Object references, arrays, maps, and structs we do not handle
Why Characters are excluded on purpose CharacterMovementComponent already predicts movement on the owning client and smooths it on everyone else, and that code is good. Competing with it would make your game worse, not better. The gap we fill is everything Unreal does not predict or smooth: vehicles, projectiles, physics props, doors, platforms, AI pawns without a movement component. That is also where most of the jerky motion in Unreal multiplayer comes from.

So: your characters behave exactly as before. Everything else gets smooth and cheaper.

4Reading the numbers

What each line of ANet.Stats and the HTML report actually means.

LineWhat it meansWhat is wrong if…
upstream Bytes per second this server is sending through the plugin, measured over a window. It is 0 while players are connected → nothing is being replicated by us. See Troubleshooting.
budget How many bytes the plugin allows itself this tick. It is derived from the link, not from a setting. It is stuck at 66 → the floor. The link looks 100 % lossy, which almost always means acks are not arriving.
rtt / jitter / loss Measured round trip, its variation, and packet loss over the recent window. It says "not measured yet" after ten seconds of play → same cause as above.
interp How far behind the client plays what it receives. Follows measured jitter and loss, so a clean LAN gets a short delay and a bad connection gets the buffer it needs. It sits at the 300 ms maximum → your connection has heavy jitter, or snapshot rate is set very low.
packets sent / acknowledged In the HTML report. These two should be nearly equal. Acknowledged is 0 while sent climbs → the report says so in orange. Go straight to Troubleshooting.
Received (client report) Snapshots, data, object updates applied, and updates discarded. Discarded is a large share of applied → the client's history is too short for this link. Raise snapshot rate or lower it; see FAQ.

Bandwidth by property

This is the part worth your attention. Every replicated property is listed with its share of the traffic, heaviest first:

   share  bytes      sends   dropped  property
   41.2%     18240    1821         0  BP_Crate.[Location]
   22.8%     10096    1821         0  BP_Crate.[Rotation]
    9.1%      4032     504         0  BP_Door.bIsOpen

Names in square brackets, like [Location], are the actor's transform. Everything else is your own property, named as you named it. If something is 40 % of your traffic and you did not expect it to be, that is your answer - and it is usually one line of code away from being cheaper.

dropped counts how often a field was skipped because the budget ran out that tick. A few is normal and healthy - that is the plugin spending on what matters. Thousands on a property you care about means your bandwidth ceiling is too low for your scene.

5Settings

Project Settings → Plugins → AdrenalineNet. Every default is chosen so that you never have to open this page.

SettingDefaultWhat it does
EnabledonOff = Unreal's replication, completely untouched. Useful for an A/B test of your own.
Measure Engine InsteadoffSends nothing, only measures what Unreal does. This is how you compare both on the same scene instead of taking our word for it.
Position Quantum1.0 cmPrecision of replicated positions. 1.0 means positions land on centimetres. Raise it to 5 for big outdoor worlds and save more; set 0 to disable quantization.
Rotation Bits12Bits per rotation component. 12 measures 0.07° of error. 9 is visibly steppy, 16 is wasteful.
Velocity Quantum1.0 cm/sSame idea for anything named like a velocity.
Max Upstream Per Client64 kB/sA ceiling, not a target. The real budget is measured from the link and is usually far lower.
Initial Upstream Per Client8 kB/sWhat it allows itself before the link has been measured. Deliberately quiet: better to start slow than to flood a weak connection in the first second.
Default Cull Distance15000 cmPast this, an actor is irrelevant - unless it just affected the player, which is never ignored.
Snapshot Rate30 HzHow often the server produces snapshots. Note Unreal's own NetServerMaxTickRate is 30 too; going above it buys nothing.
InterpolateonOff = actors snap, exactly like Unreal. Turn it off once to see the difference, then turn it back on.
Max Extrapolation250 msHow long the client may guess ahead before holding position. Holding reads as lag; overshooting and snapping back reads as a broken game.
Collect StatsonPer-property costs for the HUD and the report. Cheap; leave it on.
Show HUDoffThe live overlay.
Server and client must agree on precision Position Quantum, Velocity Quantum and Rotation Bits change the wire format. If a server and a client disagree on them, their packets decode into wrong positions. The plugin detects this and refuses the data with a clear error rather than letting it through - but it means you must ship the same settings to both. They live in DefaultEngine.ini, so normally this is automatic.

Console commands

CommandWhat it does
ANet.StatsLive bandwidth, link quality, and the per-property breakdown, into the log.
ANet.CoveragePer class: what the plugin took over, and what it left to the engine and why.
ANet.ReportWrites the full HTML report to Saved/AdrenalineNet/. Takes an optional path.

6Troubleshooting

These are real failures, with the symptom you will actually see first.

The plugin seems to do nothing. Upstream is 0.

Check the log for this line, which appears once at startup:

LogANetModule: AdrenalineNet: snapshot channel registered on 2 net driver class(es)

If it is missing, the module did not load - check that the plugin is enabled and compiled.

If it is there but upstream stays 0 while clients are connected, run ANet.Coverage. If it lists nothing, none of your actors qualified: they need bReplicates = true and at least one UPROPERTY(Replicated) or replicated movement.

The report says "nothing is being acknowledged".

This is the one failure that matters most, because the plugin still works - just far more expensively. Without acknowledgements it cannot confirm which state the client holds, so every update degenerates into a full state, and the bandwidth estimator reads the silence as 100 % packet loss and drops the budget to its floor.

Causes, in order of likelihood:

  • A custom net driver that does not call UChannel::ReceivedAck. Rare, but it happens with hand-rolled transports.
  • The snapshot channel was registered after the connection was established. The plugin registers it at engine init precisely to avoid this; if your project does something unusual with ChannelDefinitions at runtime, check the ordering.
  • Static channel index 3 taken by another plugin. See the next entry.
Another plugin also wants channel index 3.

AdrenalineNet uses static channel index 3. Unreal reserves 0 (Control), 1 (Voice) and 2 (DataStream). If another plugin claims 3, one of you loses.

Change ours in Plugins/AdrenalineNet/Config/DefaultEngine.ini - the StaticChannelIndex value in the ChannelDefinitions line - and set the same number on both server and client builds.

"Wire format mismatch" in the log, and nothing replicates.

The server and this client disagree on a setting that changes the wire format: Position Quantum, Velocity Quantum or Rotation Bits. The plugin refuses the data on purpose - decoding it would place actors at wrong positions with no error anywhere, which is far worse than a clear failure.

Make both builds ship the same values and the message goes away. Note the engine's own replication keeps working the whole time, so the game does not break.

My Character does not use the plugin.

Correct, and deliberate. See The one thing worth knowing. Characters keep CharacterMovementComponent's prediction and smoothing. Their other replicated properties are still handled by AdrenalineNet - only the movement is left alone.

An actor's position looks right but something else does not replicate.

Run ANet.Coverage. It prints, per class, every property the plugin declined to take over and why - object references, arrays, maps, and structs other than vectors and rotations stay with the engine. Those keep replicating exactly as before, so if one of them is broken, the cause is not here.

One real exception worth knowing: a RepNotify function that takes the previous value as a parameter is not called by AdrenalineNet. Plain OnRep_Something() is called normally, and only when the value really changed.

Motion is smooth but feels delayed.

That is the interpolation buffer, and it is the trade you are making: a small, constant delay instead of occasional jumps. The delay follows your measured jitter, so it is as short as your connection allows.

If you want it shorter, raise Snapshot Rate (more samples, shorter buffer) or accept more snapping by turning Interpolate off. For anything competitive and fast, raise the rate rather than disabling smoothing.

Lots of "discarded (no baseline)" on the client.

The client keeps the last 64 snapshots to decode deltas against. If updates arrive referring to something older than that, they are discarded rather than applied to the wrong state - which would decode cleanly and silently place the actor wrong.

In practice this needs a round trip longer than two seconds at 30 Hz. If you see it on a normal connection, something is stalling the client's game thread for seconds at a time, and that is worth finding for its own sake.

7Measured results

What was measured, how, and what it does not prove.

Against Unreal, same scene

A 34-second run, 60 moving replicated actors, identical script in every case, on loopback. The figure is the engine's own OutTotalBytes counter on the server, so it covers everything that left the machine - not just what the plugin measures about itself.

ConfigurationTotal sent
Unreal, default settings1 091 kB
Unreal, update rate matched to the plugin's 30 Hz1 073 kB
AdrenalineNet403 kB

Matching the update rate changed almost nothing, because Unreal is already limited by NetServerMaxTickRate (30 by default). The saving is in how the bytes are encoded and prioritised, not in sending less often.

From the same live session

ServerClient
Packets656 sent, 655 acknowledged, 0.00 % loss—
Link14.5 ms round trip, 3.2 ms jitter—
Bandwidth18.94 kB/s peak, budget self-raised to 2133 B/tick374.5 kB received
Updates32 424 position updates at 3.1 B each30 593 applied, 0 discarded
Interpolation—117 ms, 57 actors

From the core's own test suite

The replication core is plain C++ with no engine dependency and ships with 69 tests, every one of which plants a fault on purpose and fails if the core does not catch it.

Measure it on your own project These figures come from the scene described above, and your scene is not this scene. That is what Measure Engine Instead is for: it makes the plugin send nothing and only measure what Unreal does, so you can run the same comparison on your own game and read both numbers yourself.

Not compared against Iris

Iris is Epic's own new replication system. We have not benchmarked against it and make no claim about being faster. What is verifiable: in 5.8 it ships under Plugins/Experimental, and adopting it means moving your gameplay onto NetObject and ReplicationFragment. AdrenalineNet's advantage over it is cost of adoption, not bytes.

8Good to know

Where the edges of this version are, so nothing surprises you later.

It changes your actors' replication behaviour When AdrenalineNet takes over an actor's transform it switches that actor's bReplicateMovement off, because having both systems write the same actor every tick would defeat the point. If your game reads or toggles that flag at runtime, know that we touch it.

Questions

Do I need the C++ source, or does it work with Blueprint projects?

Source is included and the plugin compiles itself into your project. A Blueprint-only project works, as long as the machine has Visual Studio with the C++ game development workload so Unreal can build the plugin once. There is nothing to call from Blueprint - it works underneath your game.

Will it work with my dedicated server build?

Yes. The plugin is a runtime module and runs on dedicated servers, listen servers and clients. It is the dedicated server case where the bandwidth saving turns directly into money.

Does it break Steam Sockets, EOS, or my custom net driver?

No, and that is a deliberate design decision. We did not write our own UDP transport precisely so that your existing networking stack keeps working. The plugin registers one additional channel on the driver you already use.

Can I use it for some actors and not others?

Today it is per project, not per actor: any replicated actor with supported properties is taken over, except Characters. Per-actor opt-out is on the roadmap. If you need it now, the source is yours - the decision is made in one function in ANetSubsystem.cpp.

What happens if I remove the plugin later?

Your game goes back to Unreal's replication, unchanged. Nothing in your gameplay code depends on the plugin, because you never had to change it to adopt the plugin. That is the point of the design.

Is there a performance cost on the client?

The client keeps up to 64 small snapshots per replicated actor and interpolates between two of them per frame. That is cheap - interpolating a transform is arithmetic, and the memory is bounded. The cost that matters in this version is on the server, and it is relevance computation; see Limits.

Support

Something not working, or a number on this page that does not match what you see in your project? Tell us - a report that contradicts our measurements is the most useful mail we can get.

support@adrenalinegames.pl

Please include: your engine version, whether it is a listen or dedicated server, and the output of ANet.Report. That file usually contains the answer.