diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Contracts/AbCipDriverOptions.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Contracts/AbCipDriverOptions.cs
index 9fc101c3..9656a70e 100644
--- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Contracts/AbCipDriverOptions.cs
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Contracts/AbCipDriverOptions.cs
@@ -117,6 +117,12 @@ public sealed class AbCipDriverOptions
/// device round trips (the whole point: a strictly sequential batch pays one full round
/// trip per tag); lowering it to 1 restores strictly sequential reads. Values below
/// 1 are clamped to 1 at the use site.
+ ///
+ /// On the transport the effective window is at least 64: there the
+ /// window caps how many services one Multiple Service Packet coalesces (the device's shared scheduler packs
+ /// what is queued, bounded by the connection's request/reply byte budget), so a small value only multiplies
+ /// round trips. A value above 64 is honoured; a value below it is raised.
+ ///
///
public int MaxConcurrentReads { get; init; } = 8;
diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipArrayCodec.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipArrayCodec.cs
new file mode 100644
index 00000000..bb6455ab
--- /dev/null
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipArrayCodec.cs
@@ -0,0 +1,80 @@
+using System.Buffers.Binary;
+using System.Collections;
+
+namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
+
+///
+/// Encodes a whole-array write (review 07 U10): count elements at the element type's wire width,
+/// little-endian, exactly the layout a Logix atomic array holds and the read path decodes. Shared by both
+/// runtimes so the bytes on the wire are transport-independent.
+///
+///
+/// Element values CONVERT, they are not exact-matched. ScadaBridge (and any OPC UA client that
+/// round-trips what it read) writes CLR lists such as int[] to an INT array — the read path itself
+/// publishes INT/SINT elements as int — so each element goes through the same the
+/// scalar path uses. A value that does not fit is an (BadOutOfRange) and one that
+/// cannot convert a / (BadTypeMismatch), all
+/// thrown before any byte is sent.
+/// BOOL arrays (bit-packed DWORDs — a byte-per-element encode would clobber neighbour bits), STRING arrays
+/// (structures) and structure arrays are not encodable here; the driver refuses them BadNotSupported
+/// before a runtime exists.
+///
+internal static class AbCipArrayCodec
+{
+ /// Wire width of one element of an encodable array type, or null when the type is not encodable.
+ /// The element type.
+ /// The element width in bytes, or null.
+ public static int? ElementWidth(AbCipDataType type) => type switch
+ {
+ AbCipDataType.SInt or AbCipDataType.USInt => 1,
+ AbCipDataType.Int or AbCipDataType.UInt => 2,
+ AbCipDataType.DInt or AbCipDataType.UDInt or AbCipDataType.Real or AbCipDataType.Dt => 4,
+ AbCipDataType.LInt or AbCipDataType.ULInt or AbCipDataType.LReal => 8,
+ _ => null,
+ };
+
+ ///
+ /// The elements of an array write value, or null when the value is not a list (a scalar, a string, null).
+ /// Any non-string is accepted — int[], short[], List<object> …
+ ///
+ /// The client's write value.
+ /// The elements, or null.
+ public static object?[]? Elements(object? value) => value switch
+ {
+ null or string => null,
+ IEnumerable list => [.. list.Cast