Xmldef Descriptor Spec
Status: draft / deferred tooling spec.
This document converges the Xmldef Toolchain Design further into an implementable specification, defining the inputs, validation, IR, outputs, and stability rules of
xmldef/xml -> canonical IR -> descriptor package. The goal is not to explain "why a descriptor is needed" but to give subsequent compiler/runtime/generator implementations a unified contract.
Scope
This document only defines:
- the
xmldef/xmlinput model - semantic validation
- the canonical IR
- the descriptor package layout
- ID / hash / stability rules
- the minimal fields runtime consumption needs
- the minimal fields generator consumption needs
Not defined:
- the
shield_protocCLI UX - concrete Unity/U3D generated-code style
- binary encoding details of the runtime
XmldefBodyCodec - multi-version patch / Merkle incremental protocols
If your goal is "what does this version do first, and what counts as done", continue with the Xmldef Phase 1 Implementation Plan. If your goal is "where does code live and how are module responsibilities split", continue with the Xmldef Compiler / Runtime MVP.
Source Model
Layout
Suggested xmldef source file organization:
protocol/
manifest.xml
common/
types.xml
login/
messages.xml
avatar/
messages.xmlmanifest.xml is the only entry point.
Manifest
Minimal manifest semantics:
<protocol-manifest name="game"
version="1.0.0"
packageId="game"
codeNamespace="Game.Protocol">
<module name="common" path="common"/>
<module name="login" path="login"/>
<module name="avatar" path="avatar"/>
</protocol-manifest>Minimal fields:
nameversionpackageIdcodeNamespacemodule[]
Constraints:
packageIdstays stable long-term in the descriptorcodeNamespaceis not auto-derived fromname; it must be declared explicitlymodule.nameis unique within the package
Message Definition
xmldef focuses on message / method / field definitions. Sketch of the minimal message model:
<protocol name="avatar">
<message id="0x1001"
name="Avatar.move"
direction="client_to_server"
request="avatar.MoveRequest"
response=""/>
</protocol>This document does not freeze XML tag name details; it freezes the compiled semantic fields.
Minimal semantic fields:
route_idfull_namedirectionrequest_typeresponse_type?target_hint?binding_hint?
Schema addressing no longer gets its own field: the schema's location is the type-graph node that request_type points to; when exporting to the host route table, the converged codec ABI produces only schema_name (see the Route section below and the "Schema addressing convergence" of protocol-codec-plugins.md).
Type Definition
Reusing the type system conventions of schema-design.md, xmldef minimally must support:
- scalar
- enum
- struct
- alias
- list
- map
where map keys are limited in the first version to:
string- integer scalar
- enum
Semantic Validation
The compiler must complete strong validation at the XML stage; errors must not be pushed to the runtime.
Required Checks
Must validate:
route_iduniquenessmethod full_nameuniquenessrequest_type/response_typereferences exist- field IDs unique within a struct
- field names unique within a struct
- enum value uniqueness
- enum item name uniqueness
- client-visible types must not reference server-only types
directionvalues legal- reserved IDs declared in XML must not be reused
Direction Rules
Minimal supported directions:
client_to_serverserver_to_clientbidirectionalserver_to_server
The compiler normalizes them into a stable enum and keeps no raw strings.
Method Kind
xmldef must make method kind explicit at the descriptor layer:
sendcallstream
Rules:
sendmust have a request type and must not have a response/item typecallmust have request + response typesstreammust have request + item types
Canonical IR
Top-level Shape
Minimal canonical IR structure:
package {
package_id
package_name
package_version
code_namespace
modules[]
types[]
methods[]
routes[]
}Package
Fields:
package_id: stringpackage_name: stringpackage_version: stringcode_namespace: stringcompiler_version: stringschema_root_hash: bytes[32]
Module
Fields:
module_id: uint16name: stringpath: stringhash: bytes[32]dependencies[]: module_id
Stability rules:
module_idmust come from a lock file or an explicit declaration- must not be reassigned after release
Type
Fields:
type_id: uint32module_id: uint16full_name: stringkind: enumclient_visibility: enumhash: bytes[32]
Supported kind:
scalarenumstructalias
Struct
Fields:
type_idfields[]reserved_field_ids[]
Minimal field fields:
field_id: uint32name: stringtype_refcardinalitydefault_value?deprecated: boolvalidation_rules[]
TypeRef
Minimal model:
type_ref {
kind // scalar | named | list | map
scalar_kind?
named_type_id?
list_item?
map_key?
map_value?
}Method
Minimal fields:
method_id: uint16route_id: uint32module_id: uint16full_name: stringshort_name: stringkind: enum(send/call/stream)direction: enumrequest_type_id: uint32response_type_id?: uint32item_type_id?: uint32target_hint?: stringbinding_hint?: stringhash: bytes[32]
Route
The route table is kept explicit, so generators or the runtime don't have to derive it from methods again:
route_id: uint32method_id: uint16full_name: stringdirectionschema_name: string
schema_name is the codec pipeline's only addressing key, resolved at compile time: an explicit request_schema override wins, otherwise full_name (the same-name convention). This matches the convergence rule of the host-side RouteEntry.schema_name; routes exported by the catalog produce only this name and no longer expose numeric addressing such as schema_id — such ids, even if they exist inside a concrete schema system (like protobuf file numbers), are that system's private concept and enter neither the descriptor's general contract nor the codec ABI.
Canonical Ordering
To guarantee stable hashes, the IR must be canonically sorted before being written to the descriptor:
- modules by
module_id - named types by
type_id - methods by
route_id - route table by
route_id - struct fields by
field_id - enum values by
value
Not allowed:
- outputting in XML file order
- outputting in include order
- sorting by string locale
Descriptor Outputs
Required Files
Minimal output:
descriptor.bindescriptor.debug.jsonroute_constants.jsonmanifest.json
descriptor.bin
For the runtime; requirements:
- fast loading
- little-endian
- stable offsets
- no reliance on target-language reflection
Contains at least:
- package header
- module table
- type table
- method table
- route table
- string table
descriptor.debug.json
For generators / debug / QA tools.
Minimal requirements:
- able to fully express the binary descriptor's semantic content
- explicit field names
- no loss of
route_id/schema_name/direction - losslessly readable by generators
route_constants.json
A lightweight artifact for simple tools, scripts, and tests.
Sketch:
{
"package": "game",
"version": "1.0.0",
"routes": [
{
"route_id": 4097,
"name": "Avatar.move",
"schema_name": "Avatar.move",
"direction": "client_to_server"
}
]
}Hashing Rules
schema_root_hash
schema_root_hash must depend only on semantic content, not on:
- compile time
- compiler version
- source dirtiness
- XML formatting
- comments
meta vs schema
Must distinguish:
MetaBlock: build info; excluded from the semantic hashSchemaBlock: semantic content; included in the semantic hash
Runtime Consumption Contract
After loading a descriptor, the runtime can at minimum perform these lookups:
find_method_by_route(route_id)find_method_by_name(full_name)find_type(type_id)find_request_type(route_id)find_response_type(route_id)
Subsequent XmldefBodyCodec implementations must not depend on the XML source files again.
Generator Consumption Contract
The generator side must at least be able to obtain:
- package id / version / namespace
- route constants
- method full name
- request/response/item type
- direction
- field layout
- optional binding hint
If the descriptor lacks these fields, the compiler contract is deemed incomplete.
Stability Rules
After release, the following IDs must not be reused:
module_idtype_idmethod_idroute_id- struct
field_id
Deletion can only:
- mark deprecated
- or move into reserved
They must not be deleted and then reassigned to new semantics.
Non-goals
This document does not:
- define Unity generated file name details
- define the runtime socket frame format
- define the final API names of the Lua binding table
- define whether the XML tags must be named
<message>/<method>
These belong to upper-layer generator/runtime contracts, not the descriptor compiler's core semantics.