You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* docs: describe protoc-gen-bitwise as it actually works; drop dead BitReader/BitWriter
CLAUDE.md still documented the original bit-stream design (BitWriter/
BitReader classes, float fields, "68 bits = 9 bytes on the wire"). The
plugin actually emits quantized-accessor partials (*.Bitwise.cs) over
plain uint32 fields sent as standard protobuf varints, backed only by
Quantize.cs; runtime BitReader.cs/BitWriter.cs were referenced by
nothing and are removed. Also sync README with current generator output
(QuantizedStep consts, AreQuantizedFieldsInRange, EncodePower/
DecodePower, per-proto-file output naming) and drop the incorrect
"cached" accessor wording.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: normalize CRLF when comparing gen:test golden fixtures
With core.autocrlf=true (and no .gitattributes) git materializes the
golden .cs fixtures with CRLF on Windows while the generator always
emits LF, so the strict byte comparison failed on any fresh Windows
checkout. Normalize line endings when reading the goldens.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: add realm in pulse PlayerJoined message
* added realm to teleport performed message
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Lorenzo Ranciaffi <lorenzo.ranciaffi@decentraland.org>
Reads `.proto` files with custom field options and generates **bitwise encode/decode code** in C#, keeping all client implementations bit-for-bit identical.
21
+
Reads `.proto` files with custom field options and generates C# **partial classes** (`*.Bitwise.cs`) that add typed float accessors on top of quantized `uint32` fields, keeping the quantization math bit-for-bit identical across all client implementations.
22
+
23
+
The wire format is **standard protobuf** — a quantized value lives in a plain `uint32` field and travels as an ordinary varint. There is no custom bit stream; any protobuf-capable client can parse the messages without this plugin. Per annotated field the plugin emits:
24
+
25
+
-`float {Field}Quantized` — computed accessor (no backing cache): the getter decodes the stored `uint32`, the setter encodes a float back into it, via the static `Quantize` helpers
26
+
-`const float {Field}QuantizedStep` — the coarsest quantization step of the field, safe as an equality tolerance
27
+
- per message: `bool AreQuantizedFieldsInRange()` — pure-integer check that every stored code fits its declared bit width (`0 .. 2^bits-1`); reject malformed/hostile messages before storing or relaying
28
+
29
+
Only non-repeated `uint32` fields get accessors; `bit_packed` and unannotated fields pass through with no generated code.
// Varint wire cost: dx/dy/dz/entity_id ≤ 4 B each (1 B tag + ≤ 3 B varint),
76
+
// sequence ≤ 3 B — worst-case 19 B, less when proto3 omits zero-valued fields.
65
77
```
66
78
79
+
`proto/decentraland/common/quantization_example.proto` is the fully worked reference: per-field wire costs for the linear, power-law, and bit-packed annotations.
└── runtime/cs/ # C# runtime; Quantize.cs is copied into the generated output
89
+
└── runtime/cs/ # C# runtime: Quantize.cs — consumers copy it next to the generated files
76
90
```
77
91
78
92
Plugin contract: a protoc plugin that reads a serialized `CodeGeneratorRequest` from stdin and writes a serialized `CodeGeneratorResponse` to stdout. It is a plain Node script — **no `npm install` required, only `node` on PATH**. protoc invokes it through a tiny wrapper that runs `node plugin.js` (`.cmd` on Windows, a shell script elsewhere, since protoc cannot exec a `.js` directly).
@@ -89,85 +103,30 @@ Parity is locked down by `npm run gen:test` (compares generator output against g
89
103
90
104
---
91
105
92
-
## BitWriter / BitReader
106
+
## Quantize Runtime
93
107
94
-
The C# implementation uses the following bit layout: **big-endian within each byte**, MSB written first. Use **`Round`** (not truncate) when quantizing to minimize error.
108
+
Generated accessors call the static `Quantize` class (`protoc-gen-bitwise/runtime/cs/Quantize.cs`, namespace `Decentraland.Networking.Bitwise`) — the only C# runtime file; consumers copy it next to the generated `*.Bitwise.cs` partials. Quantization uses **`Round`** (not truncate) to minimize error; identical rounding on both sides makes encode -> decode a round-trip no-op.
95
109
96
-
### Core math — WriteQuantizedFloat
110
+
### Core math — linear (`Quantize.Encode` / `Quantize.Decode`)
Proto3 omits fields equal to 0, so typical cost is lower than worst-case.
152
+
184
153
---
185
154
186
155
## Key Design Principles
187
156
188
157
-`.proto` files are the **single source of truth** for all message schemas
189
-
- The protoc plugin generates **C#** from the schema — never hand-write serialization
158
+
- The protoc plugin generates the **C# quantized accessors** from the schema — never hand-write quantization math; standard protobuf handles the wire encoding
190
159
- Encode -> decode is a **no-op** (round-trip safe) due to consistent use of `Round`
160
+
- Validate inbound quantized messages with `AreQuantizedFieldsInRange()` before storing or relaying — the server relays raw codes verbatim
191
161
- Prefer **client-driven resync** over proactive server corrections
192
162
- Push complexity to clients where appropriate; server maintains authority
|`[(decentraland.common.quantized_power)]`|`uint32`|`max`, `pow`, `bits`| Power-law quantizer over `[-max, max]`: `(bits-1)`-bit magnitude (high bits) + sign (LSB), decoded as `sign·max·u^pow`. Exact zero; `pow>1` gives fine resolution near zero, coarse near `±max`; sign in the LSB keeps small magnitudes one varint byte. Cached `float {Name}Quantized` accessor (`Quantize.EncodePower`/`DecodePower`) |
138
+
|`[(decentraland.common.quantized)]`|`uint32`|`min`, `max`, `bits`| Plugin emits a `float {Name}Quantized` accessor and a `{Name}QuantizedStep` const|
139
+
|`[(decentraland.common.quantized_power)]`|`uint32`|`max`, `pow`, `bits`| Power-law quantizer over `[-max, max]`: `(bits-1)`-bit magnitude (high bits) + sign (LSB), decoded as `sign·max·u^pow`. Exact zero; `pow>1` gives fine resolution near zero, coarse near `±max`; sign in the LSB keeps small magnitudes one varint byte. `float {Name}Quantized` accessor (`Quantize.EncodePower`/`DecodePower`) |
138
140
|`[(decentraland.common.bit_packed)]`|`uint32`|`bits`| Documents the value range; protobuf handles varint compaction automatically |
139
141
140
142
### Wire cost at worst-case (all bits set)
@@ -183,13 +185,15 @@ Assets/
183
185
```
184
186
185
187
`Quantize.cs` lives in the `Decentraland.Networking.Bitwise` namespace and
186
-
provides two static methods used by the generated accessors:
188
+
provides the static encode/decode methods used by the generated accessors:
0 commit comments