The problem I needed to solve
Events cannot be compared reliably if numbers and field names change with device language. I separated field construction from sending and JSON encoding.
Implementation in this snapshot
AnalyticsFieldBuilder.Add trims and lowercases keys and caps long values. Numeric overloads use InvariantCulture, while booleans become true or false.
The design decision and its cost
AddList limits item count and skips empty strings. ToDictionary returns a copy, so consumers need not mutate the builder’s internal collection to build a payload.
Current boundaries
This builder stringifies values and is not a complete schema validator. Final transport types and the overall event size limit also matter; individual field limits are insufficient.
Review scenarios and contract meaning
A type distinction is hidden in this small builder: AnalyticsFieldBuilder’s numeric overload converts a value to text using invariant culture. That stabilizes textual formatting but does not produce a JSON number. PHP’s Number schema requires an actual int or float and rejects a numeric-looking string. Reporting likewise includes only actual numbers in sums and averages. A field intended as a numeric metric must therefore use a correctly typed Dictionary and SDK path. A duration_seconds label or correctly formatted text does not establish the final type.
Server validation adds other boundaries. data must be an Object, timestamps must include a timezone and received_at is reserved for the server. Duplicate JSON keys are rejected, and integers outside the supported exact range need strings to avoid silent rounding. Such strings suit identifiers rather than replacing report metrics. Required checks absence or null; domain meaning still needs contract design. When extra fields are prohibited, validation checks data roots rather than claiming strict validation of every Object subkey. Disciplined construction starts the work; agreement on types, size and meaning between Unity and PHP is the necessary next stage.
A report layout is portable, but importing it into another game does not create the required data. Its data paths must match actual payloads. An empty chart may indicate a wrong field rather than no players. For general Bunny Circus Analytics use, a template begins a contract that must match sender code without substituting sample events for real behavior.
What this stage taught me
For me, event discipline starts before a dashboard. Stable names and numbers let interface language change without changing data meaning, while avoiding unbounded information in a single event.




