# Packet Capture
Recap intercepts outbound packets matching a plugin-registered allowlist, stores the raw bytes in the active recording, and re-sends them during playback at the same tick. This is how lightning, sounds, and client-mod plugin messages (e.g. Lectern effects) replay correctly without per-plugin glue.
---
## Allowlist API
```java
IRecap recap = RecapAPI.get();
// By packet type name (PacketEvents PacketType constants)
recap.capturePacketType("SOUND_EFFECT");
recap.capturePacketType("ENTITY_SOUND_EFFECT");
recap.capturePacketType("ENTITY_ANIMATION");
// By plugin-message channel
recap.capturePluginMessageChannel("yourplugin:effect_channel");
// Inspect / remove
recap.getCapturedPacketTypes();
recap.getCapturedPluginMessageChannels();
recap.uncapturePacketType("SOUND_EFFECT");
recap.uncapturePluginMessageChannel("yourplugin:effect_channel");
```
Defaults active at plugin enable:
- `SOUND_EFFECT`
- `ENTITY_SOUND_EFFECT`
- `lectern:receive_event` (Lectern Paper plugin integration)
---
## How it works on the wire
1. **Recording side** — a high-priority PacketEvents listener inspects every outbound packet. If the packet type or plugin-message channel is in the allowlist AND the recipient has an active recorder, the raw bytes are queued on the recorder's pending-packet list.
2. **Tick boundary** — the recorder drains the queue into the current frame's `capturedPackets` list and serializes it to the `.recap` stream as an `ACTION_CAPTURED_PACKETS` action.
3. **Playback side** — every actor's `applyFrame` reads `frame.capturedPackets` and re-sends each payload to every viewer via `User.sendPacket(ByteBuf)`.
---
## Rate limiting
Replay is capped at **32 packets per actor per tick**, overflow queued to a deferred list and bled out across subsequent ticks. Without this guard a single frame with many captures (e.g. an explosion's burst of sound packets) replayed all at once and pushed the per-tick outbound count past the client's 4096-packet bundle limit.
---
## Playback-only entries skip replay
Scene entries flagged `playbackOnly` (e.g. hijacker paths) skip captured-packet replay so per-lap recordings don't stack their sound / particle bytes over the NPC reactive's identical events. See [[Recap/Server Owners/Features/Hijack]].
---
## What NOT to allowlist
- **Bundle delimiters** — would nest delimiters in the replay stream and crash clients.
- **Entity-data / spawn / despawn packets** — those refer to entity IDs that don't exist at playback time.
- **Position / movement packets** — duplicates Recap's own movement broadcasts.
- **Anything entity-ID-bound** — replayed bytes have the original recording's entity ID, not the playback NPC's.
Best fits: per-location effects (sounds, lightning), plugin-message channels carrying opaque payloads to a client mod, particle bursts that are world-space not entity-space.
---
## Integration example: Lectern
Lectern (a Lodestone client mod) dispatches camera, cutscene, screen-shake, and audio effects to clients via a plugin-message channel `lectern:receive_event`. Recap allowlists this channel by default — any effect Lectern fires during an active recording is captured and replays automatically when the recording plays back.
If you're building a similar client-mod, allowlist your channel at plugin enable:
```java
@Override
public void onEnable() {
var recap = RecapAPI.get();
if (recap != null) {
recap.capturePluginMessageChannel("yourmod:effects");
}
}
```
---
## See Also
- [[Recap/Developers/Recording Format]] — ACTION_CAPTURED_PACKETS layout
- [[Recap/API/IRecap]] — full method list