Animation Module¶
The animation module provides efficient high-frequency frame delivery for LIFX devices, optimized for real-time effects and applications that need to push color data at 30+ FPS.
Overview¶
The animation system uses a streamlined architecture optimized for speed:
Application Frame -> FrameBuffer -> PacketGenerator -> Direct UDP
(canvas map) (prebaked packets) (fire-and-forget)
Key features:
- Direct UDP: Bypasses connection layer for maximum throughput
- Prebaked packets: Templates created once, only colors updated per frame
- Multi-tile canvas: Unified coordinate space for multi-tile devices
- Tile orientation: Automatic pixel remapping for rotated tiles
- Synchronous sending:
send_frame()is synchronous for minimum overhead
Quick Start¶
import asyncio
from lifx import Animator, MatrixLight
async def main():
async with await MatrixLight.from_ip("192.168.1.100") as device:
# Create animator for matrix device
animator = await Animator.for_matrix(device)
# Device connection closed - animator sends via direct UDP
try:
while True:
# Generate HSBK frame (protocol-ready uint16 values)
# H/S/B: 0-65535, K: 1500-9000
hsbk_frame = [(65535, 65535, 65535, 3500)] * animator.pixel_count
# send_frame() is synchronous for speed
stats = animator.send_frame(hsbk_frame)
print(f"Sent {stats.packets_sent} packets in {stats.total_time_ms:.2f}ms")
await asyncio.sleep(1 / 30) # 30 FPS
finally:
animator.close()
Multi-Tile Canvas¶
For devices with multiple tiles (like the original 5-tile LIFX Tile), the animator creates
a unified canvas based on tile positions (user_x, user_y). Animations span all tiles
as one continuous image.
async with await MatrixLight.from_ip("192.168.1.100") as device:
animator = await Animator.for_matrix(device)
# Check canvas dimensions
print(f"Canvas: {animator.canvas_width}x{animator.canvas_height}")
# For 5 horizontal tiles: "Canvas: 40x8"
# Generate frame for entire canvas (row-major order)
frame = []
for y in range(animator.canvas_height):
for x in range(animator.canvas_width):
hue = int(x / animator.canvas_width * 65535) # Gradient across all tiles
frame.append((hue, 65535, 65535, 3500))
animator.send_frame(frame)
HSBK Format¶
All color data uses protocol-ready uint16 values:
| Component | Range | Description |
|---|---|---|
| Hue | 0-65535 | Maps to 0-360 degrees |
| Saturation | 0-65535 | Maps to 0.0-1.0 |
| Brightness | 0-65535 | Maps to 0.0-1.0 |
| Kelvin | 1500-9000 | Color temperature |
This design pushes conversion work to the caller (e.g. using NumPy) for better performance.
The lifx-async library remains dependency-free.
# Red at full brightness
red = (0, 65535, 65535, 3500)
# 50% brightness warm white
warm_white = (0, 0, 32768, 2700)
# Convert from user-friendly values
def to_protocol_hsbk(
hue: float, sat: float, bright: float, kelvin: int
) -> tuple[int, int, int, int]:
"""Convert user-friendly values to protocol format."""
return (
int(hue / 360 * 65535),
int(sat * 65535),
int(bright * 65535),
kelvin,
)
Animator¶
High-level class integrating all animation components.
Animator
¶
Animator(
ip: str,
serial: Serial,
framebuffer: FrameBuffer,
packet_generator: PacketGenerator,
port: int = LIFX_UDP_PORT,
)
High-level animator for LIFX devices.
Sends animation frames directly via UDP for maximum throughput. No connection layer, no ACKs, no waiting - just fire packets as fast as possible.
All packets are prebaked at initialization time. Per-frame, only color data and sequence numbers are updated in place before sending.
| ATTRIBUTE | DESCRIPTION |
|---|---|
pixel_count |
Total number of pixels/zones
TYPE:
|
Example
Use the for_matrix() or for_multizone() class methods for
automatic configuration from a device.
| PARAMETER | DESCRIPTION |
|---|---|
ip
|
Device IP address
TYPE:
|
serial
|
Device serial number
TYPE:
|
framebuffer
|
Configured FrameBuffer for orientation mapping
TYPE:
|
packet_generator
|
Configured PacketGenerator for the device
TYPE:
|
port
|
UDP port (default: 56700)
TYPE:
|
| METHOD | DESCRIPTION |
|---|---|
for_matrix |
Create an Animator configured for a MatrixLight device. |
for_multizone |
Create an Animator configured for a MultiZoneLight device. |
for_light |
Create an Animator configured for a single Light device. |
send_frame |
Send a frame to the device via direct UDP. |
close |
Close the UDP socket. |
Source code in src/lifx/animation/animator.py
Attributes¶
pixel_count
property
¶
pixel_count: int
Get total number of input pixels (canvas size for multi-tile).
Functions¶
for_matrix
async
classmethod
¶
for_matrix(device: MatrixLight, duration_ms: int = 0) -> Animator
Create an Animator configured for a MatrixLight device.
Queries the device for tile information, then returns an animator that sends frames via direct UDP (no device connection needed after creation).
| PARAMETER | DESCRIPTION |
|---|---|
device
|
MatrixLight device (must be connected)
TYPE:
|
duration_ms
|
Transition duration in milliseconds (default 0 for instant). When non-zero, device smoothly interpolates between frames.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Animator
|
Configured Animator instance |
Example
Source code in src/lifx/animation/animator.py
for_multizone
async
classmethod
¶
for_multizone(device: MultiZoneLight, duration_ms: int = 0) -> Animator
Create an Animator configured for a MultiZoneLight device.
Only devices with extended multizone capability are supported. Queries the device for zone count, then returns an animator that sends frames via direct UDP.
| PARAMETER | DESCRIPTION |
|---|---|
device
|
MultiZoneLight device (must be connected and support extended multizone protocol)
TYPE:
|
duration_ms
|
Transition duration in milliseconds (default 0 for instant). When non-zero, device smoothly interpolates between frames.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Animator
|
Configured Animator instance |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If device doesn't support extended multizone |
Example
Source code in src/lifx/animation/animator.py
for_light
classmethod
¶
Create an Animator configured for a single Light device.
Unlike the matrix/multizone factories, this does not need to be async because single lights don't require any device queries for configuration.
| PARAMETER | DESCRIPTION |
|---|---|
device
|
Light device (must have ip and serial set)
TYPE:
|
duration_ms
|
Transition duration in milliseconds (default 0 for instant). When non-zero, device smoothly interpolates between frames.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Animator
|
Configured Animator instance |
Example
Source code in src/lifx/animation/animator.py
send_frame
¶
Send a frame to the device via direct UDP.
Applies orientation mapping (for matrix devices), updates colors in prebaked packets, and sends them directly via UDP. No ACKs, no waiting - maximum throughput.
This is a synchronous method for minimum overhead. UDP sendto() is non-blocking for datagrams.
| PARAMETER | DESCRIPTION |
|---|---|
hsbk
|
Protocol-ready HSBK data for all pixels. Each tuple is (hue, sat, brightness, kelvin) where H/S/B are 0-65535 and K is 1500-9000. |
| RETURNS | DESCRIPTION |
|---|---|
AnimatorStats
|
AnimatorStats with operation statistics |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If hsbk length doesn't match pixel_count |
Source code in src/lifx/animation/animator.py
close
¶
Close the UDP socket.
Call this when done with the animator to free resources.
AnimatorStats¶
Statistics returned by Animator.send_frame().
AnimatorStats
dataclass
¶
FrameBuffer¶
Canvas mapping and orientation handling for matrix devices.
FrameBuffer
¶
FrameBuffer(
pixel_count: int,
canvas_width: int = 0,
canvas_height: int = 0,
tile_regions: list[TileRegion] | None = None,
)
Orientation mapping for matrix device animations.
For matrix devices with tile orientation (like the original LIFX Tile), this class remaps pixel coordinates from user-space (logical layout) to device-space (physical tile order accounting for rotation).
For multi-tile devices, the FrameBuffer creates a unified canvas where each tile's position (user_x, user_y) determines which region of the canvas it displays. This allows animations to span across all tiles instead of being mirrored.
For multizone devices and matrix devices without orientation, this is essentially a passthrough.
| ATTRIBUTE | DESCRIPTION |
|---|---|
pixel_count |
Total number of device pixels
TYPE:
|
canvas_width |
Width of the logical canvas in pixels
TYPE:
|
canvas_height |
Height of the logical canvas in pixels
TYPE:
|
tile_regions |
List of tile regions with positions and orientations
TYPE:
|
Example
| PARAMETER | DESCRIPTION |
|---|---|
pixel_count
|
Total number of device pixels
TYPE:
|
canvas_width
|
Width of the logical canvas (0 = same as pixel_count)
TYPE:
|
canvas_height
|
Height of the logical canvas (0 = 1 for linear)
TYPE:
|
tile_regions
|
List of tile regions with positions and orientations. If provided, input is interpreted as a 2D canvas.
TYPE:
|
| METHOD | DESCRIPTION |
|---|---|
for_matrix |
Create a FrameBuffer configured for a MatrixLight device. |
for_multizone |
Create a FrameBuffer configured for a MultiZoneLight device. |
for_light |
Create a FrameBuffer configured for a single Light device. |
apply |
Apply orientation mapping to frame data. |
Source code in src/lifx/animation/framebuffer.py
Attributes¶
Functions¶
for_matrix
async
classmethod
¶
for_matrix(device: MatrixLight) -> FrameBuffer
Create a FrameBuffer configured for a MatrixLight device.
Automatically determines pixel count from device chain and creates appropriate mapping for tile orientations and positions.
For multi-tile devices (has_chain capability), creates a unified canvas based on tile positions (user_x, user_y). Each tile's position determines which region of the canvas it displays, allowing animations to span across all tiles.
| PARAMETER | DESCRIPTION |
|---|---|
device
|
MatrixLight device (must be connected)
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
FrameBuffer
|
Configured FrameBuffer instance |
Example
Source code in src/lifx/animation/framebuffer.py
for_multizone
async
classmethod
¶
for_multizone(device: MultiZoneLight) -> FrameBuffer
Create a FrameBuffer configured for a MultiZoneLight device.
Automatically determines pixel count from zone count. Multizone devices don't need permutation (zones are linear).
| PARAMETER | DESCRIPTION |
|---|---|
device
|
MultiZoneLight device (must be connected)
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
FrameBuffer
|
Configured FrameBuffer instance |
Example
Source code in src/lifx/animation/framebuffer.py
for_light
classmethod
¶
for_light(_device: Light) -> FrameBuffer
Create a FrameBuffer configured for a single Light device.
Single lights have exactly 1 pixel, so this is a trivial passthrough.
| PARAMETER | DESCRIPTION |
|---|---|
_device
|
Light device (unused — single lights always have 1 pixel)
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
FrameBuffer
|
Configured FrameBuffer instance |
Source code in src/lifx/animation/framebuffer.py
apply
¶
Apply orientation mapping to frame data.
For multi-tile devices, the input is interpreted as a row-major 2D canvas of size (canvas_width x canvas_height). Each tile extracts its region from the canvas based on its position.
For single-tile or multizone devices, this is a passthrough.
| PARAMETER | DESCRIPTION |
|---|---|
hsbk
|
List of protocol-ready HSBK tuples. - For multi-tile: length must match canvas_size - For single-tile/multizone: length must match pixel_count Each tuple is (hue, sat, brightness, kelvin) where H/S/B are 0-65535 and K is 1500-9000. |
| RETURNS | DESCRIPTION |
|---|---|
list[tuple[int, int, int, int]]
|
Remapped HSBK data in device order |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If hsbk length doesn't match expected size |
Source code in src/lifx/animation/framebuffer.py
TileRegion¶
Represents a tile's region within the canvas.
TileRegion
dataclass
¶
TileRegion(
x: int,
y: int,
width: int,
height: int,
orientation_lut: tuple[int, ...] | None = None,
)
Packet Generators¶
Device-specific packet generation with prebaked templates.
PacketGenerator (Base)¶
PacketGenerator
¶
Bases: ABC
Abstract base class for packet generators.
Packet generators prebake complete packets (header + payload) at initialization time. Per-frame, only color data and sequence numbers are updated in place.
| METHOD | DESCRIPTION |
|---|---|
create_templates |
Create prebaked packet templates. |
update_colors |
Update color data in prebaked templates. |
pixel_count |
Get the total pixel count this generator expects. |
Functions¶
create_templates
abstractmethod
¶
create_templates(source: int, target: bytes) -> list[PacketTemplate]
Create prebaked packet templates.
| PARAMETER | DESCRIPTION |
|---|---|
source
|
Client source ID for header
TYPE:
|
target
|
6-byte device serial for header
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
list[PacketTemplate]
|
List of PacketTemplate with prebaked packets |
Source code in src/lifx/animation/packets.py
update_colors
abstractmethod
¶
Update color data in prebaked templates.
| PARAMETER | DESCRIPTION |
|---|---|
templates
|
Prebaked packet templates
TYPE:
|
hsbk
|
Protocol-ready HSBK data for all pixels |
Source code in src/lifx/animation/packets.py
PacketTemplate¶
Prebaked packet template for zero-allocation frame updates.
PacketTemplate
dataclass
¶
Prebaked packet template for zero-allocation animation.
Contains a complete packet (header + payload) as a mutable bytearray. Only the sequence byte and color data need to be updated per frame.
| ATTRIBUTE | DESCRIPTION |
|---|---|
data |
Complete packet bytes (header + payload)
TYPE:
|
color_offset |
Byte offset where color data starts
TYPE:
|
color_count |
Number of HSBK colors in this packet
TYPE:
|
hsbk_start |
Starting index in the input HSBK array
TYPE:
|
fmt |
Pre-computed struct format string for bulk color packing
TYPE:
|
MatrixPacketGenerator¶
Generates Set64 packets for MatrixLight devices.
MatrixPacketGenerator
¶
Bases: PacketGenerator
Packet generator for MatrixLight devices.
Generates Set64 packets for all tiles. Uses prebaked packet templates with complete headers for maximum performance.
For standard tiles (≤64 pixels): - Single Set64 packet directly to display buffer (fb_index=0)
For large tiles (>64 pixels, e.g., Ceiling 16x8=128): - Multiple Set64 packets to temp buffer (fb_index=1) - CopyFrameBuffer packet to copy fb_index=1 → fb_index=0
Set64 Payload Layout (522 bytes): - Offset 0: tile_index (uint8) - Offset 1: length (uint8, always 1) - Offset 2-5: TileBufferRect (fb_index, x, y, width - 4 x uint8) - Offset 6-9: duration (uint32) - Offset 10-521: colors (64 x HSBK, each 8 bytes)
CopyFrameBuffer Payload Layout (15 bytes): - Offset 0: tile_index (uint8) - Offset 1: length (uint8, always 1) - Offset 2: src_fb_index (uint8, 1 = temp buffer) - Offset 3: dst_fb_index (uint8, 0 = display) - Offset 4-7: src_x, src_y, dst_x, dst_y (uint8 each) - Offset 8-9: width, height (uint8 each) - Offset 10-13: duration (uint32) - Offset 14: reserved (uint8)
| PARAMETER | DESCRIPTION |
|---|---|
tile_count
|
Number of tiles in the device chain
TYPE:
|
tile_width
|
Width of each tile in pixels
TYPE:
|
tile_height
|
Height of each tile in pixels
TYPE:
|
duration_ms
|
Transition duration in milliseconds (default 0 for instant)
TYPE:
|
| METHOD | DESCRIPTION |
|---|---|
pixel_count |
Get total pixel count. |
create_templates |
Create prebaked packet templates for all tiles. |
update_colors |
Update color data in prebaked templates. |
| ATTRIBUTE | DESCRIPTION |
|---|---|
is_large_tile |
Check if tiles have >64 pixels (requires multi-packet strategy).
TYPE:
|
packets_per_tile |
Get number of Set64 packets needed per tile.
TYPE:
|
Source code in src/lifx/animation/packets.py
Attributes¶
is_large_tile
property
¶
is_large_tile: bool
Check if tiles have >64 pixels (requires multi-packet strategy).
Functions¶
create_templates
¶
create_templates(source: int, target: bytes) -> list[PacketTemplate]
Create prebaked packet templates for all tiles.
| PARAMETER | DESCRIPTION |
|---|---|
source
|
Client source ID
TYPE:
|
target
|
6-byte device serial
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
list[PacketTemplate]
|
List of PacketTemplate with complete prebaked packets |
Source code in src/lifx/animation/packets.py
update_colors
¶
Update color data in prebaked templates.
| PARAMETER | DESCRIPTION |
|---|---|
templates
|
Prebaked packet templates
TYPE:
|
hsbk
|
Protocol-ready HSBK data for all pixels |
Source code in src/lifx/animation/packets.py
MultiZonePacketGenerator¶
Generates SetExtendedColorZones packets for MultiZoneLight devices.
MultiZonePacketGenerator
¶
Bases: PacketGenerator
Packet generator for MultiZoneLight devices with extended multizone.
Uses SetExtendedColorZones packets (up to 82 zones each). For devices with >82 zones, multiple packets are generated.
SetExtendedColorZones Payload Layout (664 bytes): - Offset 0-3: duration (uint32) - Offset 4: apply (uint8, 1 = APPLY) - Offset 5-6: zone_index (uint16) - Offset 7: colors_count (uint8) - Offset 8-663: colors (82 x HSBK, each 8 bytes)
| PARAMETER | DESCRIPTION |
|---|---|
zone_count
|
Total number of zones on the device
TYPE:
|
duration_ms
|
Transition duration in milliseconds (default 0 for instant)
TYPE:
|
| METHOD | DESCRIPTION |
|---|---|
pixel_count |
Get total zone count. |
create_templates |
Create prebaked packet templates for all zones. |
update_colors |
Update color data in prebaked templates. |
Source code in src/lifx/animation/packets.py
Functions¶
create_templates
¶
create_templates(source: int, target: bytes) -> list[PacketTemplate]
Create prebaked packet templates for all zones.
| PARAMETER | DESCRIPTION |
|---|---|
source
|
Client source ID
TYPE:
|
target
|
6-byte device serial
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
list[PacketTemplate]
|
List of PacketTemplate with complete prebaked packets |
Source code in src/lifx/animation/packets.py
update_colors
¶
Update color data in prebaked templates.
| PARAMETER | DESCRIPTION |
|---|---|
templates
|
Prebaked packet templates
TYPE:
|
hsbk
|
Protocol-ready HSBK data for all zones |
Source code in src/lifx/animation/packets.py
Tile Orientation¶
Pixel remapping for rotated tiles.
Orientation Enum¶
Orientation
¶
Bases: IntEnum
Tile orientation based on accelerometer data.
These values match the orientation detection in TileInfo.nearest_orientation but use integer enum for efficient comparison and caching.
Physical mounting positions
- RIGHT_SIDE_UP: Normal position, no rotation needed
- ROTATED_90: Rotated 90 degrees clockwise (RotatedRight)
- ROTATED_180: Upside down (UpsideDown)
- ROTATED_270: Rotated 90 degrees counter-clockwise (RotatedLeft)
- FACE_UP: Tile facing ceiling
- FACE_DOWN: Tile facing floor
| METHOD | DESCRIPTION |
|---|---|
from_string |
Convert TileInfo.nearest_orientation string to Orientation enum. |
Functions¶
from_string
classmethod
¶
from_string(orientation_str: str) -> Orientation
Convert TileInfo.nearest_orientation string to Orientation enum.
| PARAMETER | DESCRIPTION |
|---|---|
orientation_str
|
String from TileInfo.nearest_orientation
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Orientation
|
Corresponding Orientation enum value |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If orientation string is not recognized |
Source code in src/lifx/animation/orientation.py
build_orientation_lut¶
build_orientation_lut
cached
¶
build_orientation_lut(
width: int, height: int, orientation: Orientation
) -> tuple[int, ...]
Build a lookup table for remapping pixels based on tile orientation.
The LUT maps physical tile positions to row-major framebuffer indices. For a pixel at physical position i, lut[i] gives the framebuffer index.
This is LRU-cached because tiles typically have standard dimensions (8x8) and there are only 6 orientations, so the cache will be highly effective.
| PARAMETER | DESCRIPTION |
|---|---|
width
|
Tile width in pixels
TYPE:
|
height
|
Tile height in pixels
TYPE:
|
orientation
|
Tile orientation
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
int
|
Tuple of indices mapping physical position to framebuffer position. |
...
|
Tuple is used instead of list for hashability in caches. |
Example
lut = build_orientation_lut(8, 8, Orientation.RIGHT_SIDE_UP) len(lut) 64 lut[0] # First pixel maps to index 0 0 lut = build_orientation_lut(8, 8, Orientation.ROTATED_180) lut[0] # First physical position maps to last framebuffer index 63
Source code in src/lifx/animation/orientation.py
Examples¶
Matrix Animation (Single Tile)¶
import asyncio
from lifx import Animator, MatrixLight
async def rainbow_animation():
async with await MatrixLight.from_ip("192.168.1.100") as device:
animator = await Animator.for_matrix(device)
hue_offset = 0
try:
while True:
# Generate rainbow gradient
frame = []
for i in range(animator.pixel_count):
hue = (hue_offset + i * 1000) % 65536
frame.append((hue, 65535, 32768, 3500))
stats = animator.send_frame(frame)
print(f"Sent {stats.packets_sent} packets")
hue_offset = (hue_offset + 500) % 65536
await asyncio.sleep(1 / 30) # 30 FPS
finally:
animator.close()
Multi-Tile Animation (LIFX Tile with 5 tiles)¶
import asyncio
import math
from lifx import Animator, MatrixLight
async def multi_tile_wave():
async with await MatrixLight.from_ip("192.168.1.100") as device:
animator = await Animator.for_matrix(device)
# Canvas spans all tiles (e.g., 40x8 for 5 horizontal tiles)
width = animator.canvas_width
height = animator.canvas_height
print(f"Canvas: {width}x{height}")
hue_offset = 0
try:
while True:
frame = []
for y in range(height):
for x in range(width):
# Wave that flows across all tiles
pos = x + y * 0.5 # Diagonal wave
hue = int((pos / width) * 65535 + hue_offset) % 65536
frame.append((hue, 65535, 65535, 3500))
animator.send_frame(frame)
hue_offset = (hue_offset + 1000) % 65536
await asyncio.sleep(1 / 30)
finally:
animator.close()
MultiZone Animation¶
import asyncio
from lifx import Animator, MultiZoneLight
async def chase_animation():
async with await MultiZoneLight.from_ip("192.168.1.100") as device:
animator = await Animator.for_multizone(device)
position = 0
try:
while True:
# Generate chase pattern
frame = []
for i in range(animator.pixel_count):
if i == position:
frame.append((0, 65535, 65535, 3500)) # Red
else:
frame.append((0, 0, 0, 3500)) # Off
animator.send_frame(frame)
position = (position + 1) % animator.pixel_count
await asyncio.sleep(1 / 20) # 20 FPS
finally:
animator.close()
Performance Characteristics¶
Direct UDP Delivery¶
The animation module bypasses the connection layer entirely:
- No ACKs, no waiting, no retries
- Packets sent via raw UDP socket
- Maximum throughput for real-time effects
- Some packet loss is acceptable (visual artifacts are brief)
Prebaked Packet Templates¶
Packets are constructed once at initialization:
- Header and payload structure prebaked as
bytearray - Per-frame: only color data and sequence number updated
- Zero object allocation in the hot path
- Sequence number wraps at 256 (uint8)
Multi-Tile Canvas Mapping¶
For devices with multiple tiles:
- Tile positions read from device (
user_x,user_y) - Canvas bounds calculated from all tile positions
- Input frame interpreted as 2D row-major canvas
- Each tile extracts its region based on position
- Orientation correction applied per-tile
Typical Performance¶
| Device Type | Pixels | Packets/Frame | Send Time |
|---|---|---|---|
| Single tile (8x8) | 64 | 1 | <0.5ms |
| 5-tile chain | 320 | 5 | <1ms |
| Large Ceiling (16x8) | 128 | 3 | <1ms |
| MultiZone (82 zones) | 82 | 1 | <0.5ms |