Compare commits

...
Sign in to create a new pull request.
Author SHA1 Message Date
Meta Vens
13e8c56f5f Plan geometry ownership migration 2026-07-22 02:26:02 +01:00
Meta Vens
9295278531 Return RUDP application receipts 2026-07-13 17:30:08 +01:00
3 changed files with 643 additions and 2 deletions

View file

@ -0,0 +1,552 @@
# CultMath and GameCult.Geometry Staged Migration Plan
Status: proposed for execution
Date: 2026-07-22
## Decision
`GameCult.Geometry` is the sole owner of geometric meaning and geometry
machinery. It builds on CultMath numeric primitives and participates in
CultCache, CultNet, and CultMesh through the normal CultLib integration paths.
CultMath owns the HLSL-shaped numeric substrate: scalar operations, vectors,
quaternions, and general numeric kernels that have no spatial domain authority.
It does not own regions, topology, projections, surfaces, planetary fields,
patches, geometry residency, meshes, or geometry workflow state.
VibeGeometry and `vg-csg` are sources to mine. They are not surviving owners or
dependencies.
Source transfer does not erase provenance. CultMath is MPL-2.0 and CultLib is
MIT-licensed; moved files require an explicit licensing decision and preserved
notices before they enter a differently licensed package.
## Objective
Produce one coherent geometry stack in which:
- consumers use `CultMath.float2`, `float3`, and `float4` as the canonical
floating-point vector primitives;
- `GameCult.Geometry` owns reusable geometry, planetary geometry, CSG, mesh
production, spatial queries, and the typed documents that coordinate those
systems;
- CultMath no longer publishes planetary or other geometry-owned APIs;
- `CultVec2` and `CultVec3` no longer exist;
- VibeGeometry can be archived after its useful implementation, evidence, and
doctrine have been absorbed;
- persisted geometry remains readable through an explicit migration path when
a live persisted corpus requires one.
## Authority Map
| Organ | Owns | Does not own |
| --- | --- | --- |
| CultMath | `float2`, `float3`, `float4`, integer/double/bool vector primitives, quaternion algebra, scalar and component-wise intrinsics, coordinate-agnostic numeric kernels, CPU/HLSL parity for those primitives | Rectangles, spheres, topology, projections, planetary fields, geometry pages, meshes, CSG, persistence schemas, worker commands |
| GameCult.Geometry | Spatial value types built from CultMath primitives, spatial queries, topology and projection, planetary field and page machinery, mesh and CSG implementation, geometry schema documents, stable geometry identities, geometry-specific HLSL and cross-runtime fixtures | Competing vector types, generic storage/network ownership, application gameplay consequences |
| CultCache/CultNet/CultMesh | Typed persistence, schema resolution, transport, discovery, publication, commands, and interface projection | Geometry semantics or geometry algorithm ownership |
| VibeGeometry / `vg-csg` | Nothing after migration | Any live runtime, schema, package, or compatibility authority |
Authority sentence:
> CultMath owns how numeric primitives behave; GameCult.Geometry owns what those
> primitives mean in space.
## Current Mechanism
The live body is split across three places:
1. CultMath publishes the canonical HLSL-shaped vector types, but also contains
`rect`, planetary topology/projection/field/page/residency systems, spherical
erosion, planetary HLSL, Unity adapters, web fixtures, and planetary tools.
2. `GameCult.Geometry` contains duplicate `CultVec2` and `CultVec3` values,
geometric primitives, and four CultCache document roots. Its document
members still encode many vectors, bounds, transforms, vertices, normals,
and UVs as anonymous `float[]` values.
3. VibeGeometry wraps the `vg-csg` Rust implementation for domain-tree intent,
LOD-aware selected cuts, CSG lowering, and triangle/collider output.
CultCache's current managed SoA projection only admits primitive scalar members.
It neither preserves arbitrary unmanaged structs as columns nor recursively
splits them. A `float2` or `float3` member is therefore omitted from the SoA
until that storage seam is repaired.
## Invariants
The migration must preserve these statements at every published boundary:
1. There is one canonical vector vocabulary: CultMath's HLSL-shaped primitive
types.
2. Geometry algorithms and geometry state have one owner: `GameCult.Geometry`.
3. CultMath never depends on CultLib, CultCache, MessagePack, or
`GameCult.Geometry`.
4. `GameCult.Geometry` may depend on CultMath and CultLib.
5. Persistence decoration and formatters do not contaminate CultMath primitive
definitions.
6. A geometry schema has one active writer. Legacy readers or offline
migrators may exist temporarily, but they cannot decide live behavior.
7. Record keys and stable fingerprints are identical across supported runtimes
for the same semantic geometry value.
8. SoA storage preserves each authored unmanaged member as one column of its
exact type. It never decomposes `float2`, `float3`, quaternion, or another
value type by reflection.
9. CPU, HLSL, Unity, web, and Rust fixtures compare the same claimed layer of
geometry behavior.
10. VibeGeometry is not retained as a wrapper, submodule, compatibility crate,
or second source of truth.
## Target Package Shape
The exact directory names may follow existing CultLib conventions, but the
ownership should lower into these runtime surfaces:
```text
CultMath
C# and Unity numeric primitives
CultMath.hlsl numeric mirror
coordinate-agnostic native numeric kernels
CultLib
src/GameCult.Geometry/
C# geometry API
planetary geometry
CultCache document contracts and formatters
geometry-specific HLSL packaged as content
packages/gamecult-geometry-rs/
Rust geometry, CSG, and cross-runtime fixtures
packages/gamecult-geometry-ts/ # only if the web runtime remains earned
web projection/tile helpers and parity fixtures
src/GameCult.Unity/Assets/Geometry/ # Unity-owned lowering/adapters
mesh/page upload and presentation adapters
Packages/org.gamecult.geometry/ # final location follows packaging audit
Unity package depending on CultMath and CultLib
```
The runtime directories are projections of one geometry authority. They are not
independent products allowed to invent their own vector, topology, schema, or
fingerprint semantics.
Each stage is a commit/review boundary. Do not begin the next stage until the
current gate passes, and never publish a state in which both repositories claim
the same geometry authority.
## Source Classification
### Keep in CultMath
- HLSL-shaped primitive types such as `float2`, `float3`, and `float4`.
- Integer, double, and boolean vector primitives.
- Quaternion algebra.
- `math` scalar/component-wise operations and the core `CultMath.hlsl` mirror.
- Numeric batch kernels whose contracts are independent of regions, topology,
surfaces, planets, meshes, CSG, rendering policy, and persistence.
### Move from CultMath to GameCult.Geometry
The initial move set is:
- `rect` and other spatial-region values;
- `PlanetaryTopology`;
- `PlanetaryProjection`;
- `PlanetaryQueries`;
- `PlanetaryPatch`;
- `PlanetaryPages`;
- `PlanetaryGpuPages`;
- `PlanetaryResidency`;
- `PlanetaryRadialRefinement`;
- `PlanetaryMapTileEncoding`;
- `PlanetaryField`;
- `AdvancedErosionFilter`;
- `ErosionFrequencyBands`;
- `SphericalErosion`;
- `AdvancedErosionFilter.hlsl`;
- `Planetary.hlsl`;
- spherical-erosion and planetary-radial-refinement shader bodies currently
embedded in `CultMath.hlsl`;
- the versioned `CMPT` planetary tile format and its TypeScript decoder;
- planetary tests, docs, tools, Unity adapters, web fixtures, and samples.
`AdvancedErosionFilter` and `ErosionFrequencyBands` move with planetary
geometry. Their public contracts encode terrain-field policy, they are direct
dependencies of the planetary field definition, and their HLSL is coupled to
that field. Voronoi tone rendering and `native/cultmath-core` are outside this
migration: the native crate currently owns only that tone kernel and has no
planetary implementation. Audit those separately rather than dragging them
across this boundary because their names look spatial.
### Rebuild inside GameCult.Geometry
- Make `CultRect`, `CultCircle`, and `CultSphere` the GameCult.Geometry-owned
spatial values, implemented over CultMath primitives, and delete
`CultMath.rect` at cutover.
- Replace `CultVec2` and `CultVec3` with `float2` and `float3` at every typed
boundary.
- Replace vector-shaped `float[]` document members with typed CultMath values or
intentional packed buffers.
- Keep packed mesh buffers packed when they are genuinely bulk artifact storage;
do not turn every vertex buffer into a forest of object wrappers.
- Put MessagePack/CultCache formatters for CultMath values in the integration
package or generated formatter output, not on the CultMath types.
### Mine from VibeGeometry and `vg-csg`
Evaluate and extract, in dependency order:
- convex plane splitting and brush operations;
- CSG tree and ordered operation semantics;
- domain-tree DSL and lowering;
- dirty-frontier and prefix-checkpoint work;
- selected-cut and LOD planning;
- triangle/collider assembly;
- performance fixtures and parity evidence;
- useful RealtimeCSG-derived behavioral fixtures;
- runtime doctrine that still describes the adopted machine.
Rewrite extracted code under GameCult.Geometry ownership. Preserve third-party
license notices and source provenance. Do not retain a `vg-csg` forwarding
crate or a VibeGeometry submodule after consumers have moved.
## Staged Migration
### Stage 0: Freeze and Baseline
Purpose: establish the evidence needed to move authority without guessing.
Work:
- Freeze new planetary and geometry API additions in CultMath, VibeGeometry,
and the current duplicate primitive surface.
- Do not publish an intermediate release containing duplicate live planetary
implementations. A coordinated working branch may hold source temporarily
for comparison, but only one owner may enter a package feed.
- Inventory public symbols, package versions, HLSL entrypoints, Unity samples,
web fixtures, native exports, persisted schema IDs, record-key algorithms,
and known consumers.
- Inventory exact contract constants: `FieldVersion`, enum ordinals, tile stable
keys, `CMPT` versions and bytes, MessagePack slots, GPU buffer layouts,
integer hashes, topology face/seam/corner rules, and shader include paths.
- Search deployed and checked-in `.cc` stores for the four
`gamecult.geometry.*.v1` schemas.
- Record whether any external consumer relies on CultMath planetary namespaces,
`CultVec*`, `CultMath.rect`, `vg-csg`, or the current HLSL function names.
- Capture passing baselines for CultMath tests, GameCult.Geometry tests,
`vg-csg` tests/clippy, cross-runtime planetary fixtures, and package builds.
- Add representative fixture hashes for projection, topology, field sampling,
patch selection, page encoding, CSG output, and geometry record keys.
- Resolve licensing and provenance for MPL-2.0 CultMath sources and any
third-party-derived `vg-csg` behavior before copying source into CultLib.
Gate:
- Every published surface has a named consumer or is marked for deletion.
- The persisted v1 corpus is known rather than assumed.
- Baseline fixtures can be run without booting an entire daemon.
### Stage 1: Establish the Destination and Dependency Direction
Purpose: make `GameCult.Geometry` capable of receiving geometry without growing
a second primitive vocabulary.
Work:
- Add the explicit `GameCult.Geometry -> CultMath` dependency.
- Define the target namespaces and package content layout for C#, Rust, HLSL,
Unity, and any retained web surface.
- Establish distribution before source transfer: an explicit NuGet dependency
and a dedicated `org.gamecult.geometry` Unity package depending on
`org.gamecult.cultmath` and `org.gamecult.cultlib`, unless package inspection
proves an existing surface already owns that role.
- Retain `net8.0` and `netstandard2.1` targets and Unity 2021.3 compatibility.
The geometry asmdef references CultMath; the Unity lowering assembly
references both. CultMath's asmdef remains reference-free with
`noEngineReferences: true`.
- Add GameCult.Geometry-owned MessagePack formatters for `float2`, `float3`,
`float4`, and quaternion with fixed positional layouts.
- Add cross-runtime byte fixtures for those value encodings.
- Repair CultCache's SoA member discovery so any direct unmanaged member is one
exact typed column. Add `Column<float2>` and `Column<float3>` tests and a
negative test proving no scalar decomposition occurs.
- Ensure generated schema metadata describes the authored vector member rather
than synthetic x/y/z members.
Gate:
- CultMath has no new dependency.
- `GameCult.Geometry` can serialize, deserialize, index, and expose intact
CultMath vectors.
- CultCache hot-path access returns intact vector spans.
- Built NuGet and UPM artifacts contain the expected dependency metadata,
formatters, shaders, and owned runtime assets.
### Stage 2: Replace Duplicate Primitives and Repair Geometry Schemas
Purpose: remove the duplicate vocabulary before moving larger geometry systems.
Work:
- Rewrite `CultRect`, `CultCircle`, and `CultSphere` with CultMath primitives.
- Delete `CultVec2` and `CultVec3`; do not add aliases or conversion wrappers.
- Replace typed references and opaque native-slice metadata using the obsolete
names.
- Classify every vector-shaped `float[]` in `CultGeometryDocuments.cs`:
- `Translation`, `SupportCenter`, `SupportSize`, `CameraPosition`,
`FrustumMin`, `FrustumMax`, `BoundsMin`, and `BoundsMax` become `float3`;
- `RotationXyzw` becomes quaternion;
- mesh `Positions` and `Normals` become `float3[]`;
- mesh `Uvs` becomes `float2[]`;
- scalar and index buffers remain packed arrays;
- use a named geometry value when bounds/topology carries an invariant.
- Define v2 schemas for every document whose field types change. CultCache
includes CLR type identity in the canonical schema and must reject an in-place
slot type mutation even when no durable v1 corpus is found.
- If live v1 documents exist, add bounded v1 reader DTOs and an offline
v1-to-v2 migrator. Stop all v1 writers at cutover. If no live persisted corpus
exists, omit the migrator and delete v1 runtime support, but still publish the
corrected contract under v2 identity.
- Reject malformed legacy vectors: fixed vectors must have the exact expected
component count, while packed mesh streams must be divisible by their element
width before conversion.
- Recompute and cross-check stable fingerprints and record keys against Rust
fixtures before accepting the new schemas. Preserve an old record key only
when the typed representation reproduces the prior IEEE-bit canonicalization
exactly; otherwise version the identity deliberately.
- Use CultMath's lowercase component names in v2 JSON inspection projections.
JSON is not the runtime transport, and uppercase `X/Y/Z` aliases must not keep
the deleted vector vocabulary alive. Any xenos-facing alternate casing is an
explicit boundary projection.
Gate:
- Repository-wide searches find no `CultVec2` or `CultVec3` outside historical
evidence.
- Repository and package searches find no `CultMath.rect`.
- No live writer emits vector-shaped anonymous arrays for semantic single
values.
- The old schema cannot overwrite, repair, or become a second owner of v2
state.
### Stage 3: Transfer Planetary Geometry out of CultMath
Purpose: move planetary authority as one coherent vertical slice.
Work:
- Move the classified planetary C# implementation and tests into
`GameCult.Geometry`.
- Move `Planetary.hlsl` and rename its include ownership and public entrypoints
to GameCult.Geometry. It may include CultMath numeric HLSL; CultMath must no
longer include planetary geometry. Remove the current implicit planetary
include from `CultMath.hlsl` only after consumers include the new owner.
- Move `AdvancedErosionFilter.hlsl` plus the spherical-erosion and
planetary-radial-refinement bodies embedded in `CultMath.hlsl`. Publish one
geometry-owned umbrella include that imports CultMath first and geometry
organs afterward.
- Move planetary documentation, fixture tools, web tile code, Unity page/mesh
adapters, and the planetary viewer sample to their consumer-owned runtime
locations.
- Move ownership of the `CMPT` little-endian tile format and TypeScript decoder.
Preserve v1 bytes exactly or introduce a new format version; never reuse a
wire version after changing its bytes.
- Rename public namespaces and symbols to their GameCult.Geometry authority.
Prefer a coordinated breaking release over permanent forwarding types.
- Move spherical/terrain erosion components classified as geometry. Keep only
genuinely coordinate-agnostic numeric kernels in CultMath.
- Run CPU/HLSL and cross-runtime fixtures from the new owner before deleting
the old files.
- Preserve exact fixtures for field versions, enum ordinals, integer hashes,
tile keys/bytes, GPU buffer packing, MessagePack slots, topology seams and
corners, and record hashes. Use explicit tolerances only for floating
CPU/HLSL comparisons.
- Delete the corresponding CultMath sources, package entries, and implicit
includes in the same authority-transfer stage after the destination passes.
Do not defer the actual ownership cut to consumer cleanup.
Gate:
- GameCult.Geometry passes the captured planetary baseline and owns the fixture
commands.
- CultMath packages no `Planetary.hlsl`, planetary sample, planetary tool, or
planetary public namespace.
- No source or shader include can select an old CultMath planetary path.
- Package inspection finds no planetary API, Unity adapter, sample, tile
decoder, documentation claim, or implicit shader include in CultMath.
### Stage 4: Mine and Absorb `vg-csg`
Purpose: preserve useful CSG machinery without preserving the defunct owner.
Work:
- Import the earned Rust implementation into the GameCult.Geometry runtime
surface with source provenance and applicable license notices.
- Replace `bevy_math`-shaped public contracts where they conflict with the
canonical cross-runtime primitive/layout contract. Internal use may remain
where it protects a measured invariant and does not leak a second public
vocabulary.
- Connect domain trees, selected cuts, build requests, and emitted artifacts to
the GameCult.Geometry schema definitions.
- Port useful fixtures before refactoring algorithms so observable behavior has
a witness.
- Preserve the existing Rust v1 tuple MessagePack and stable-hash fixtures as
migration witnesses, then make the GameCult.Geometry-owned Rust package emit
the v2 contract.
- Re-run performance fixtures and retain thresholds only where the fixture
represents a real workload.
- Move surviving doctrine into GameCult.Geometry docs and discard historical
instructions that would recreate VibeGeometry ownership.
Gate:
- No production build or test depends on the VibeGeometry repository or
`vg-csg` submodule.
- CSG output satisfies the adopted correctness and performance fixtures from
its new package.
- The Rust runtime speaks the same schema IDs, vector layouts, fingerprints,
and record keys as C#.
### Stage 5: Integrate Geometry Through CultCache, CultNet, and CultMesh
Purpose: prove the new owner through the real typed-state path.
Work:
- Register the adopted geometry schemas and formatters through the generated
registry path.
- Prove raw envelope interchange for geometry documents across C# and Rust,
adding TypeScript only if an actual web consumer remains.
- Publish geometry worker state and commands through the provider-owned
CultMesh surface.
- Ensure geometry build requests, selected cuts, chunk artifacts, and planetary
page state share one commit/derivation path for direct and programmatic use.
- Add a development probe exposing schema version, owner, source record key,
selected cut/page, content hash, and served package version.
Gate:
- A pipeline smoke proves typed handoff from domain input through selected cut
or planetary page selection to a persisted and republished artifact.
- The visible/debug projection and persisted state identify the same owner and
version.
- No ad hoc JSON or parallel status model becomes load-bearing geometry truth.
### Stage 6: Consumer Cutover and Coordinated Release
Purpose: switch consumers without maintaining two live authorities.
Release order:
1. Build an unpublished CultMath candidate that contains the stable primitive
contract and removes geometry-owned APIs.
2. Build an unpublished GameCult.Geometry candidate against that exact CultMath
version, containing adopted schemas, planetary geometry, and runtime packages.
3. Update and test GameCult services, Unity projects, web consumers, and tools
against a local candidate feed.
4. Publish the CultMath and GameCult.Geometry candidates in one coordinated
release window, with the primitives-only CultMath dependency entering the
feed first.
5. Publish consumer updates after both owning packages are available.
6. Convert persisted v1 geometry stores, if any, and remove migration tooling
after the retained-corpus policy is satisfied.
Work:
- Pin compatible package versions across the coordinated release.
- Update examples and developer-facing entrypoints to use
`GameCult.Geometry` plus CultMath primitives directly.
- Cut Fensalir's Engine/Contracts public planetary types and shader includes to
the new assembly before removing CultMath geometry. Its CPU/GPU parity tests
are release gates.
- Add the geometry package to Aetheria/Unity, move its directly compiled
planetary sources to the new assembly, preserve moved Unity `.meta` GUIDs,
and regenerate Unity project files.
- Verify served package, shader, schema, and migration versions in at least one
real consumer for each retained runtime.
- Run the preserved parity corpus through known consumers including Fensalir,
Zyphos, Aetheria/Unity, the web tile decoder when retained, C#, Rust, and HLSL.
- Refuse mixed old/new geometry writers rather than reconciling them later.
Gate:
- Every known consumer runs on the new owner.
- No published package graph has `CultMath -> CultLib` or
`CultMath -> GameCult.Geometry`.
- Old and new writers cannot target the same state surface.
### Stage 7: Delete Obsolete Authorities
Purpose: make the old ownership structurally unable to return.
Work:
- Confirm deletion of all CultMath planetary/geometry sources, tests, shaders,
samples, docs, tools, web surfaces, package content entries, and Unity
adapters; remove any residue found after consumer cutover.
- Delete obsolete GameCult.Geometry duplicate primitive code and stale schema
paths.
- Delete VibeGeometry workspace wiring and the `vg-csg` submodule dependency;
archive the repositories after provenance links and release tags are
recorded.
- Remove compatibility aliases, forwarding namespaces, dual registries, and
temporary migration flags.
- Update source indexes and repository memory so future agents retrieve the new
authority first.
Gate:
- Negative searches and package inspection prove the old paths are absent.
- Building CultMath alone cannot access planetary or geometry APIs.
- Building GameCult.Geometry cannot access `CultVec*` or VibeGeometry.
- The old persisted schema path cannot write or override current geometry.
## Schema Migration Policy
Do not create a permanent compatibility model pre-emptively.
- If no durable v1 corpus exists, change the schema once, update fixtures, and
publish the corrected contract as v2, then delete the old form without
building a migrator.
- If a durable v1 corpus exists, create an offline migrator with explicit input
and output schema IDs, content hashes, counts, and a dry-run report.
- A v1 reader may exist only inside migration/inspection tooling.
- The runtime writes only the current schema after cutover.
- Migration completes only when retained stores have receipts and no active
service advertises or writes v1.
## Verification Matrix
| Layer | Required evidence |
| --- | --- |
| CultMath primitives | C#/HLSL parity, unmanaged layout/size checks, package inspection |
| Geometry values | Exact `float2`/`float3` field ownership, intersection/query unit tests, MessagePack byte fixtures |
| CultCache SoA | Intact vector columns, no recursive flattening, add/update/remove/snapshot behavior |
| Geometry schemas | Catalog IDs and slots, v1 inventory or migration receipts, stable record-key parity |
| Planetary geometry | Projection round trips, topology adjacency, field/normal samples, radial refinement, page encoding/residency, CPU/HLSL parity |
| CSG | Boolean/convex fixtures, dirty rebuild behavior, selected-cut correctness, triangle/collider receipts, performance fixtures |
| Cross-runtime | C#/Rust payload and fingerprint parity; web parity only for retained web consumers |
| Packaging | CultMath contains no geometry assets; GameCult.Geometry includes its owned HLSL/docs/runtime assets |
| End to end | Domain or planetary input to persisted artifact through CultMesh with visible owner/version evidence |
## Negative Completion Checks
The migration is not complete until all are true:
- `CultVec2` and `CultVec3` cannot appear in newly generated schemas or native
slice metadata.
- CultMath cannot publish or execute planetary geometry through an old entrypoint.
- VibeGeometry and `vg-csg` cannot influence a build, runtime, fixture, or
generated artifact.
- VibeGeometry is absent from project references, package manifests,
submodules, CI, runtime identifiers, and authoritative documentation.
- Legacy v1 state cannot override or repair current geometry after the fact.
- CultCache cannot decompose a vector into independently owned scalar columns.
- Manual, programmatic, imported, persisted, and network-received geometry use
the same schema and commit paths.
- Passing unit tests are accompanied by package inspection and cross-runtime
evidence at the layer users actually consume.
## Definition of Done
The work is complete when `GameCult.Geometry` is the only geometry authority,
CultMath is a clean numeric substrate, all retained runtime projections agree on
typed layouts and stable identities, VibeGeometry has no live dependency path,
and the old owners are structurally unable to emit current geometry state.

View file

@ -4088,11 +4088,25 @@ export interface CultMeshRudpDocumentPut {
};
}
export interface CultMeshRudpDocumentReceipt<TDefinition extends AnyCultCacheDocumentDefinition = AnyCultCacheDocumentDefinition> {
binding: CultNetDocumentBinding<TDefinition>;
recordKey: string;
value: CultCacheDocumentValue<TDefinition>;
messageId?: string;
sourceRuntimeId?: string;
sourceAgentId?: string;
sourceRole?: string;
tags?: string[];
}
export interface CultMeshRudpDocumentServerOptions extends CultMeshRudpSocketOptions {
documents: CultNetDocumentRegistry;
getCache?: () => Promise<CultCache> | CultCache;
onError?: (error: Error) => void;
onDocumentPutRaw?: (document: CultMeshRudpDocumentPut) => void | Promise<void>;
onDocumentPutRaw?: (document: CultMeshRudpDocumentPut) =>
| CultMeshRudpDocumentReceipt
| void
| Promise<CultMeshRudpDocumentReceipt | void>;
wireContract?: CultNetWireContract;
sessionTimeoutMs?: number;
}
@ -4112,6 +4126,10 @@ export interface CultMeshRudpDocumentPublishOptions extends CultMeshRudpPeerOpti
flushTimeoutMs?: number;
}
export interface CultMeshRudpDocumentReceiptOptions extends CultMeshRudpDocumentPublishOptions {
receiptTimeoutMs?: number;
}
export interface CultMeshRudpEndpoint {
host: string;
port: number;
@ -5638,7 +5656,18 @@ export class CultMesh {
return;
case "cultnet.document_put_raw.v0":
if (options.onDocumentPutRaw) {
await options.onDocumentPutRaw(normalizeRudpDocumentPut(message, record.remote));
const receipt = await options.onDocumentPutRaw(normalizeRudpDocumentPut(message, record.remote));
if (receipt) {
sendSchemaMessage(record, options.documents.createRawDocumentPutMessage(
receipt.binding, receipt.messageId ?? message.messageId, receipt.recordKey, receipt.value,
{
sourceRuntimeId: receipt.sourceRuntimeId ?? runtimeId,
sourceAgentId: receipt.sourceAgentId,
sourceRole: receipt.sourceRole ?? "provider",
tags: receipt.tags ?? ["receipt"],
},
));
}
}
return;
default:
@ -5753,6 +5782,36 @@ export class CultMesh {
}
}
public static async publishRudpDocumentAndWaitForReceipt<TDefinition extends AnyCultCacheDocumentDefinition, TReceiptDefinition extends AnyCultCacheDocumentDefinition>(
runtimeId: string, connectionId: number, endpoint: string | CultMeshRudpEndpoint,
binding: CultNetDocumentBinding<TDefinition>, recordKey: string, value: CultCacheDocumentValue<TDefinition>,
receiptBinding: CultNetDocumentBinding<TReceiptDefinition>, options: CultMeshRudpDocumentReceiptOptions = {},
): Promise<CultCacheDocumentValue<TReceiptDefinition>> {
requireNonEmpty(runtimeId, "runtimeId");
requireNonEmpty(recordKey, "recordKey");
const messageId = options.messageId ?? `${runtimeId}:${binding.definition.type}:${recordKey}`;
const peer = await CultMesh.createRudpPeer(runtimeId, connectionId, endpoint, options);
try {
const receipt = new Promise<CultCacheDocumentValue<TReceiptDefinition>>((resolve, reject) => {
const timeout = setTimeout(() => { cleanup(); reject(new Error(`Timed out waiting for RUDP application receipt for ${recordKey}.`)); }, options.receiptTimeoutMs ?? 5_000);
const onMessage = (message: CultNetMessage): void => {
if (message.schemaVersion !== "cultnet.document_put_raw.v0" || message.messageId !== messageId || message.document.schemaId !== receiptBinding.definition.schemaId) return;
try {
const decoded = decode(new Uint8Array(message.document.payload));
cleanup();
resolve(receiptBinding.definition.schema.parse(decoded) as CultCacheDocumentValue<TReceiptDefinition>);
} catch (error) { cleanup(); reject(error); }
};
const cleanup = (): void => { clearTimeout(timeout); peer.off("message", onMessage); };
peer.on("message", onMessage);
});
peer.send(new CultNetDocumentRegistry([binding]).createRawDocumentPutMessage(binding, messageId, recordKey, value, {
sourceRuntimeId: options.sourceRuntimeId ?? runtimeId, sourceAgentId: options.sourceAgentId, sourceRole: options.sourceRole, tags: options.tags,
}));
return await receipt;
} finally { peer.close(); }
}
public static async createRudpServer(
runtimeId: string,
connectionId: number,

View file

@ -34,6 +34,13 @@ const noteAliasDocument = defineDocumentType({
name: "noteId",
});
const noteReceiptDocument = defineDocumentType({
type: "cultmesh.note_receipt",
schemaId: "cultmesh.note_receipt.v0",
schema: z.object({ commandId: z.string(), state: z.literal("applied"), body: z.string() }),
name: "commandId",
});
const foreignNoteDocument = defineDocumentType({
type: "runtime.generated.cultmesh.note",
schemaId: "runtime.generated.cultmesh.note.ui.42",
@ -2568,6 +2575,29 @@ test("CultMesh TS publishes one registered document to a RUDP catalog", async ()
}
});
test("CultMesh TS returns a correlated provider receipt after a RUDP document mutation", async () => {
const connectionId = 0x10203050;
const commandBinding = defineCultNetDocumentBinding({ definition: noteDocument });
const receiptBinding = defineCultNetDocumentBinding({ definition: noteReceiptDocument });
const server = CultMesh.createRudpDocumentServer("cultmesh-ts-receipt-server", connectionId, {
documents: new CultNetDocumentRegistry([commandBinding, receiptBinding]), bindHost: "127.0.0.1", bindPort: 0,
resendDelayMs: 25, resendPollMs: 5,
onDocumentPutRaw: async (document) => ({
binding: receiptBinding, recordKey: document.recordKey,
value: { commandId: document.recordKey, state: "applied", body: (document.payload as { body: string }).body },
}),
});
try {
await server.start();
const receipt = await CultMesh.publishRudpDocumentAndWaitForReceipt(
"cultmesh-ts-receipt-client", connectionId, `rudp://127.0.0.1:${server.bind.port}`,
commandBinding, "command:note", { noteId: "command:note", body: "provider applied this" }, receiptBinding,
{ messageId: "command:note", receiptTimeoutMs: 1_000, resendPollMs: 5 },
);
assert.deepEqual(receipt, { commandId: "command:note", state: "applied", body: "provider applied this" });
} finally { server.close(); }
});
test("CultMesh TS reads remote RUDP snapshots through document handles", async () => {
const connectionId = 0x10203048;
const node = await CultMesh.startNode(