Inventari del Joc: Equipament Determinista, Comerç, Magatzem i Botí
Els jugadors tenen inventaris deterministes, espais d’equipament, reixetes d’emmagatzematge i moneders. Equipar, comerciar, obtenir botí, recollides i consolidació de piles es resolen a través d’un petit conjunt de primitives repetibles.
Si estàs llegint això sense context, aquest és un recorregut per l’inventari de producció: equipament determinista, comerç, caigudes de botí i operacions de magatzem sense seguiment del moviment dels jugadors. Cada canvi d’estat és un compromís auditable amb claus d’idempotència estables, així que els fluxos de treball de servei en viu són reproduïbles i anti-duplicats per defecte.
Per què això és important
Els sistemes d’inventari de servei en viu viuen o moren en funció de l’anti-duplicació, les transaccions atòmiques i els fluxos de treball de suport reutilitzables. Asset Core fa que l’inventari sigui autoritari tractant cada equip, intercanvi i caiguda com una transacció determinista.
Model de sistema
- Jugadors: modelats com a propietaris i identificadors d’actors (sense seguiment de moviment dels jugadors).
- Inventaris: contenidors de graella per a la col·locació determinista + regles de col·lisió.
- Equipament: contenidors de ranura per a semàntica d’equip explícita.
- Stash: contenidor de graella per mostrar mecàniques d’emmagatzematge a llarg termini.
- Moneders: contenidors de saldo per a moneda i comerços atòmics.
- Reixeta de botí de zona: ancoratges de botí deterministes per a trobades, cofres o caigudes.
- Els ancoratges de botí representen espais autoritzats, no física ni moviment.
- Classes i formes: registrades perquè les regles de col·locació es mantinguin coherents.
- Unitats: cel (fixed_point_scale=1).
Configuració universal
Dos commits de base estableixen el domini d’inventari una vegada: la configuració crea contenidors i registra classes; la llavor crea els articles inicials, els saldos i un bloquejador d’emmagatzematge. Cada cas que segueix assumeix aquesta base per poder mantenir-se enfocat i autònom.
Setup commit: crear contenidors i registrar classes d’articles utilitzades per cada cas. Seed commit: crear articles inicials, balances de cartera, piles de pocions i el bloquejador d’emmagatzematge utilitzat en el cas de col·lisió.
Contenidors creats
- zone_loot (id container-32001): Reixeta de botí de zona per a caigudes d’enfrontaments i recompenses de cofres.
- p1_inventory (id container-32002): Reixeta d’inventari del jugador 1 (bossa).
- p1_equipment (id container-32004): Espais d’equipament del jugador 1 (arma, mà secundària, armadura).
- p1_stash (id container-32008): Reixeta d’emmagatzematge a llarg termini del jugador 1.
- p1_wallet (id container-32006): Contenidor de saldo de la cartera del jugador 1 (moneda).
- p2_inventory (id container-32003): Reixeta d’inventari del jugador 2.
- p2_equipment (id container-32005): Espais d’equipament del jugador 2.
- p2_stash (id container-32009): Reixeta d’emmagatzematge del jugador 2.
- p2_wallet (id container-32007): Contenidor de saldo de la cartera del jugador 2.
Classes registrades
- espasa (id class-42001): Instància d’element d’espasa.
- escut (id class-42002): Instància d’objecte d’escut.
- arc (id class-42003): Instància d’objecte arc.
- armadura (id class-42004): Instància d’objecte d’armadura.
- poció (id class-42005): Article de poció apilable.
- or (id class-42006): Or moneda fungible.
Configuració del compromís
Registra classes/formes i crea contenidors.
Per què és important aquest pas: Aquesta és l’única vegada que creem contenidors i registrem classes d’articles. Tot el que ve després és un moviment, fusió o canvi d’equilibri en relació amb aquestes definicions.
Condicions prèvies:
- Cap. Aquest és el commit de bootstrap.
El que crea aquest commit:
- zone_loot (id container-32001): Reixeta de botí de zona per a caigudes d’enfrontaments i recompenses de cofres.
- p1_inventory (id container-32002): Reixeta d’inventari del jugador 1 (bossa).
- p1_equipment (id container-32004): Espais d’equipament del jugador 1 (arma, mà secundària, armadura).
- p1_stash (id container-32008): Reixeta d’emmagatzematge a llarg termini del jugador 1.
- p1_wallet (id container-32006): Contenidor de saldo de la cartera del jugador 1 (moneda).
- p2_inventory (id container-32003): Reixeta d’inventari del jugador 2.
- p2_equipment (id container-32005): Espais d’equipament del jugador 2.
- p2_stash (id container-32009): Reixeta d’emmagatzematge del jugador 2.
- p2_wallet (id container-32007): Contenidor de saldo de la cartera del jugador 2.
Què registra aquest commit:
- espasa (id class-42001): Instància d’element d’espasa.
- escut (id class-42002): Instància d’objecte d’escut.
- arc (id class-42003): Instància d’objecte arc.
- armadura (id class-42004): Instància d’objecte d’armadura.
- poció (id class-42005): Article de poció apilable.
- or (id class-42006): Or moneda fungible.
Què cal notar en el codi:
- Cada trucada a CreateContainer es correspon a 1:1 amb un contenidor enumerat anteriorment.
- Cada RegisterClass defineix les regles de col·locació i apilament que s’utilitzaran més tard.
Canvi d’estat esperat:
- Els contenidors i les classes estan registrats i llestos per a les interaccions de joc.
Operacions
CreateContainer(x9) - Crea un contenidor (regió de memòria estructurada) amb el tipus sol·licitat.RegisterClass(x6) - Registra una definició de classe perquè les operacions futures la puguin referenciar.RegisterClassShape(x5) - Registra una empremta de forma de graella per a una classe o variant de classe.
await write_client.commit_operations(
[
CreateContainer(
kind={"capacity": 25, "grid_width": 5, "type": "grid"},
policies=None,
external_id="container-32001",
owner_external_id="owner-73",
),
CreateContainer(
kind={"capacity": 25, "grid_width": 5, "type": "grid"},
policies=None,
external_id="container-32002",
owner_external_id="owner-71",
),
CreateContainer(
kind={"capacity": 25, "grid_width": 5, "type": "grid"},
policies=None,
external_id="container-32003",
owner_external_id="owner-72",
),
CreateContainer(
kind={"count": 4, "type": "slots"},
policies=None,
external_id="container-32004",
owner_external_id="owner-71",
),
CreateContainer(
kind={"count": 4, "type": "slots"},
policies=None,
external_id="container-32005",
owner_external_id="owner-72",
),
CreateContainer(
kind={"quantization_inv": 1, "type": "balance"},
policies=None,
external_id="container-32006",
owner_external_id="owner-71",
),
CreateContainer(
kind={"quantization_inv": 1, "type": "balance"},
policies=None,
external_id="container-32007",
owner_external_id="owner-72",
),
CreateContainer(
kind={"capacity": 25, "grid_width": 5, "type": "grid"},
policies=None,
external_id="container-32008",
owner_external_id="owner-71",
),
CreateContainer(
kind={"capacity": 25, "grid_width": 5, "type": "grid"},
policies=None,
external_id="container-32009",
owner_external_id="owner-72",
),
RegisterClass(
request={
"behavior": {"balance_scale": 1},
"class_id": "class-42001",
"flags": 0,
"name": "item-sword",
},
),
RegisterClass(
request={
"behavior": {"balance_scale": 1},
"class_id": "class-42002",
"flags": 0,
"name": "item-shield",
},
),
RegisterClass(
request={
"behavior": {"balance_scale": 1},
"class_id": "class-42003",
"flags": 0,
"name": "item-bow",
},
),
RegisterClass(
request={
"behavior": {"balance_scale": 1},
"class_id": "class-42004",
"flags": 0,
"name": "item-armor",
},
),
RegisterClass(
request={
"behavior": {"balance_scale": 1},
"class_id": "class-42005",
"flags": 0,
"name": "item-potion",
},
),
RegisterClass(
request={
"behavior": {"balance_scale": 1},
"class_id": "class-42006",
"flags": 0,
"name": "currency-gold",
},
),
RegisterClassShape(
request={"class_id": "class-42001", "shape": {"height": 1, "width": 1}},
),
RegisterClassShape(
request={"class_id": "class-42002", "shape": {"height": 2, "width": 2}},
),
RegisterClassShape(
request={"class_id": "class-42003", "shape": {"height": 1, "width": 1}},
),
RegisterClassShape(
request={"class_id": "class-42004", "shape": {"height": 2, "width": 2}},
),
RegisterClassShape(
request={"class_id": "class-42005", "shape": {"height": 1, "width": 1}},
),
],
namespace_id=1,
idempotency_key="game-inventory-setup",
actor_id="game-setup",
metadata={
"job_id": "job-inventory-001",
"step": "setup",
"trace_id": "trace-inventory-001",
},
)OpaqueTransaction* tx = NULL;
check_ok(asset_tx_begin(runtime, 1, &tx), "asset_tx_begin");
{
AssetContainerKind kind = {0};
kind.kind = AssetContainerKindTag_Grid;
kind.capacity = 25;
kind.grid_width = 5;
uint32_t out_container_id = 0;
check_ok(
asset_tx_create_container(runtime, tx, 32001, kind, 73, NULL, &out_container_id),
"asset_tx_create_container[0]");
}
{
AssetContainerKind kind = {0};
kind.kind = AssetContainerKindTag_Grid;
kind.capacity = 25;
kind.grid_width = 5;
uint32_t out_container_id = 0;
check_ok(
asset_tx_create_container(runtime, tx, 32002, kind, 71, NULL, &out_container_id),
"asset_tx_create_container[1]");
}
{
AssetContainerKind kind = {0};
kind.kind = AssetContainerKindTag_Grid;
kind.capacity = 25;
kind.grid_width = 5;
uint32_t out_container_id = 0;
check_ok(
asset_tx_create_container(runtime, tx, 32003, kind, 72, NULL, &out_container_id),
"asset_tx_create_container[2]");
}
{
AssetContainerKind kind = {0};
kind.kind = AssetContainerKindTag_Slots;
kind.slots = 4;
uint32_t out_container_id = 0;
check_ok(
asset_tx_create_container(runtime, tx, 32004, kind, 71, NULL, &out_container_id),
"asset_tx_create_container[3]");
}
{
AssetContainerKind kind = {0};
kind.kind = AssetContainerKindTag_Slots;
kind.slots = 4;
uint32_t out_container_id = 0;
check_ok(
asset_tx_create_container(runtime, tx, 32005, kind, 72, NULL, &out_container_id),
"asset_tx_create_container[4]");
}
{
AssetContainerKind kind = {0};
kind.kind = AssetContainerKindTag_Balance;
kind.quantization_inv = 1;
uint32_t out_container_id = 0;
check_ok(
asset_tx_create_container(runtime, tx, 32006, kind, 71, NULL, &out_container_id),
"asset_tx_create_container[5]");
}
{
AssetContainerKind kind = {0};
kind.kind = AssetContainerKindTag_Balance;
kind.quantization_inv = 1;
uint32_t out_container_id = 0;
check_ok(
asset_tx_create_container(runtime, tx, 32007, kind, 72, NULL, &out_container_id),
"asset_tx_create_container[6]");
}
{
AssetContainerKind kind = {0};
kind.kind = AssetContainerKindTag_Grid;
kind.capacity = 25;
kind.grid_width = 5;
uint32_t out_container_id = 0;
check_ok(
asset_tx_create_container(runtime, tx, 32008, kind, 71, NULL, &out_container_id),
"asset_tx_create_container[7]");
}
{
AssetContainerKind kind = {0};
kind.kind = AssetContainerKindTag_Grid;
kind.capacity = 25;
kind.grid_width = 5;
uint32_t out_container_id = 0;
check_ok(
asset_tx_create_container(runtime, tx, 32009, kind, 72, NULL, &out_container_id),
"asset_tx_create_container[8]");
}
{
AssetClassBehavior behavior = {0};
behavior.has_balance_scale = 1;
behavior.balance_scale = 1;
uint32_t out_class_id = 0;
check_ok(asset_tx_register_class(runtime,
tx,
42001,
0,
&behavior,
(const uint8_t*)"item-sword",
10,
&out_class_id),
"asset_tx_register_class[9]");
}
{
AssetClassBehavior behavior = {0};
behavior.has_balance_scale = 1;
behavior.balance_scale = 1;
uint32_t out_class_id = 0;
check_ok(asset_tx_register_class(runtime,
tx,
42002,
0,
&behavior,
(const uint8_t*)"item-shield",
11,
&out_class_id),
"asset_tx_register_class[10]");
}
{
AssetClassBehavior behavior = {0};
behavior.has_balance_scale = 1;
behavior.balance_scale = 1;
uint32_t out_class_id = 0;
check_ok(asset_tx_register_class(runtime,
tx,
42003,
0,
&behavior,
(const uint8_t*)"item-bow",
8,
&out_class_id),
"asset_tx_register_class[11]");
}
{
AssetClassBehavior behavior = {0};
behavior.has_balance_scale = 1;
behavior.balance_scale = 1;
uint32_t out_class_id = 0;
check_ok(asset_tx_register_class(runtime,
tx,
42004,
0,
&behavior,
(const uint8_t*)"item-armor",
10,
&out_class_id),
"asset_tx_register_class[12]");
}
{
AssetClassBehavior behavior = {0};
behavior.has_balance_scale = 1;
behavior.balance_scale = 1;
uint32_t out_class_id = 0;
check_ok(asset_tx_register_class(runtime,
tx,
42005,
0,
&behavior,
(const uint8_t*)"item-potion",
11,
&out_class_id),
"asset_tx_register_class[13]");
}
{
AssetClassBehavior behavior = {0};
behavior.has_balance_scale = 1;
behavior.balance_scale = 1;
uint32_t out_class_id = 0;
check_ok(asset_tx_register_class(runtime,
tx,
42006,
0,
&behavior,
(const uint8_t*)"currency-gold",
13,
&out_class_id),
"asset_tx_register_class[14]");
}
{
AssetItemShape shape = {.width = 1, .height = 1};
uint32_t inserted = 0;
check_ok(asset_tx_register_class_shape(runtime, tx, 42001, 0, shape, &inserted),
"asset_tx_register_class_shape[15]");
}
{
AssetItemShape shape = {.width = 2, .height = 2};
uint32_t inserted = 0;
check_ok(asset_tx_register_class_shape(runtime, tx, 42002, 0, shape, &inserted),
"asset_tx_register_class_shape[16]");
}
{
AssetItemShape shape = {.width = 1, .height = 1};
uint32_t inserted = 0;
check_ok(asset_tx_register_class_shape(runtime, tx, 42003, 0, shape, &inserted),
"asset_tx_register_class_shape[17]");
}
{
AssetItemShape shape = {.width = 2, .height = 2};
uint32_t inserted = 0;
check_ok(asset_tx_register_class_shape(runtime, tx, 42004, 0, shape, &inserted),
"asset_tx_register_class_shape[18]");
}
{
AssetItemShape shape = {.width = 1, .height = 1};
uint32_t inserted = 0;
check_ok(asset_tx_register_class_shape(runtime, tx, 42005, 0, shape, &inserted),
"asset_tx_register_class_shape[19]");
}
AssetTxMeta meta = {0};
OpaqueEventBatch* batch = NULL;
check_ok(asset_tx_commit(runtime, tx, &meta, &batch), "asset_tx_commit");
asset_event_batch_destroy(runtime, batch);{
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_commit",
"params": {
"actor_id": "game-setup",
"idempotency_key": "game-inventory-setup",
"metadata": {
"job_id": "job-inventory-001",
"step": "setup",
"trace_id": "trace-inventory-001"
},
"namespace_id": 1,
"operations": [
{
"args": {
"external_id": "container-32001",
"kind": {
"capacity": 25,
"grid_width": 5,
"type": "grid"
},
"owner_external_id": "owner-73",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"external_id": "container-32002",
"kind": {
"capacity": 25,
"grid_width": 5,
"type": "grid"
},
"owner_external_id": "owner-71",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"external_id": "container-32003",
"kind": {
"capacity": 25,
"grid_width": 5,
"type": "grid"
},
"owner_external_id": "owner-72",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"external_id": "container-32004",
"kind": {
"count": 4,
"type": "slots"
},
"owner_external_id": "owner-71",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"external_id": "container-32005",
"kind": {
"count": 4,
"type": "slots"
},
"owner_external_id": "owner-72",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"external_id": "container-32006",
"kind": {
"quantization_inv": 1,
"type": "balance"
},
"owner_external_id": "owner-71",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"external_id": "container-32007",
"kind": {
"quantization_inv": 1,
"type": "balance"
},
"owner_external_id": "owner-72",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"external_id": "container-32008",
"kind": {
"capacity": 25,
"grid_width": 5,
"type": "grid"
},
"owner_external_id": "owner-71",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"external_id": "container-32009",
"kind": {
"capacity": 25,
"grid_width": 5,
"type": "grid"
},
"owner_external_id": "owner-72",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"request": {
"behavior": {
"balance_scale": 1
},
"class_id": "class-42001",
"flags": 0,
"name": "item-sword"
}
},
"op": "RegisterClass"
},
{
"args": {
"request": {
"behavior": {
"balance_scale": 1
},
"class_id": "class-42002",
"flags": 0,
"name": "item-shield"
}
},
"op": "RegisterClass"
},
{
"args": {
"request": {
"behavior": {
"balance_scale": 1
},
"class_id": "class-42003",
"flags": 0,
"name": "item-bow"
}
},
"op": "RegisterClass"
},
{
"args": {
"request": {
"behavior": {
"balance_scale": 1
},
"class_id": "class-42004",
"flags": 0,
"name": "item-armor"
}
},
"op": "RegisterClass"
},
{
"args": {
"request": {
"behavior": {
"balance_scale": 1
},
"class_id": "class-42005",
"flags": 0,
"name": "item-potion"
}
},
"op": "RegisterClass"
},
{
"args": {
"request": {
"behavior": {
"balance_scale": 1
},
"class_id": "class-42006",
"flags": 0,
"name": "currency-gold"
}
},
"op": "RegisterClass"
},
{
"args": {
"request": {
"class_id": "class-42001",
"shape": {
"height": 1,
"width": 1
}
}
},
"op": "RegisterClassShape"
},
{
"args": {
"request": {
"class_id": "class-42002",
"shape": {
"height": 2,
"width": 2
}
}
},
"op": "RegisterClassShape"
},
{
"args": {
"request": {
"class_id": "class-42003",
"shape": {
"height": 1,
"width": 1
}
}
},
"op": "RegisterClassShape"
},
{
"args": {
"request": {
"class_id": "class-42004",
"shape": {
"height": 2,
"width": 2
}
}
},
"op": "RegisterClassShape"
},
{
"args": {
"request": {
"class_id": "class-42005",
"shape": {
"height": 1,
"width": 1
}
}
},
"op": "RegisterClassShape"
}
]
}
}let payload = json!({
"actor_id": "game-setup",
"idempotency_key": "game-inventory-setup",
"metadata": {
"job_id": "job-inventory-001",
"step": "setup",
"trace_id": "trace-inventory-001"
},
"operations": [
{
"args": {
"external_id": "container-32001",
"kind": {
"capacity": 25,
"grid_width": 5,
"type": "grid"
},
"owner_external_id": "owner-73",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"external_id": "container-32002",
"kind": {
"capacity": 25,
"grid_width": 5,
"type": "grid"
},
"owner_external_id": "owner-71",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"external_id": "container-32003",
"kind": {
"capacity": 25,
"grid_width": 5,
"type": "grid"
},
"owner_external_id": "owner-72",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"external_id": "container-32004",
"kind": {
"count": 4,
"type": "slots"
},
"owner_external_id": "owner-71",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"external_id": "container-32005",
"kind": {
"count": 4,
"type": "slots"
},
"owner_external_id": "owner-72",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"external_id": "container-32006",
"kind": {
"quantization_inv": 1,
"type": "balance"
},
"owner_external_id": "owner-71",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"external_id": "container-32007",
"kind": {
"quantization_inv": 1,
"type": "balance"
},
"owner_external_id": "owner-72",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"external_id": "container-32008",
"kind": {
"capacity": 25,
"grid_width": 5,
"type": "grid"
},
"owner_external_id": "owner-71",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"external_id": "container-32009",
"kind": {
"capacity": 25,
"grid_width": 5,
"type": "grid"
},
"owner_external_id": "owner-72",
"policies": null
},
"op": "CreateContainer"
},
{
"args": {
"request": {
"behavior": {
"balance_scale": 1
},
"class_id": "class-42001",
"flags": 0,
"name": "item-sword"
}
},
"op": "RegisterClass"
},
{
"args": {
"request": {
"behavior": {
"balance_scale": 1
},
"class_id": "class-42002",
"flags": 0,
"name": "item-shield"
}
},
"op": "RegisterClass"
},
{
"args": {
"request": {
"behavior": {
"balance_scale": 1
},
"class_id": "class-42003",
"flags": 0,
"name": "item-bow"
}
},
"op": "RegisterClass"
},
{
"args": {
"request": {
"behavior": {
"balance_scale": 1
},
"class_id": "class-42004",
"flags": 0,
"name": "item-armor"
}
},
"op": "RegisterClass"
},
{
"args": {
"request": {
"behavior": {
"balance_scale": 1
},
"class_id": "class-42005",
"flags": 0,
"name": "item-potion"
}
},
"op": "RegisterClass"
},
{
"args": {
"request": {
"behavior": {
"balance_scale": 1
},
"class_id": "class-42006",
"flags": 0,
"name": "currency-gold"
}
},
"op": "RegisterClass"
},
{
"args": {
"request": {
"class_id": "class-42001",
"shape": {
"height": 1,
"width": 1
}
}
},
"op": "RegisterClassShape"
},
{
"args": {
"request": {
"class_id": "class-42002",
"shape": {
"height": 2,
"width": 2
}
}
},
"op": "RegisterClassShape"
},
{
"args": {
"request": {
"class_id": "class-42003",
"shape": {
"height": 1,
"width": 1
}
}
},
"op": "RegisterClassShape"
},
{
"args": {
"request": {
"class_id": "class-42004",
"shape": {
"height": 2,
"width": 2
}
}
},
"op": "RegisterClassShape"
},
{
"args": {
"request": {
"class_id": "class-42005",
"shape": {
"height": 1,
"width": 1
}
}
},
"op": "RegisterClassShape"
}
]
});
let response = client
.post(format!("{}/v1/write/namespaces/1/commit", write_base_url))
.bearer_auth(write_api_key)
.json(&payload)
.send()
.await?;Compromís de llavor
Inventaris de jugadors de llavors, bloqueig d’emmagatzematge i saldos.
Per què és important aquest pas: La semilla és l’únic moment en què injectem articles i saldos. Després d’això, l’estat només es mou entre contenidors o disminueix els comptes.
Condicions prèvies:
- La configuració del commit ha creat contenidors i ha registrat classes.
Què cal notar en el codi:
- Les instàncies d’articles es creen directament als inventaris dels jugadors i al magatzem.
- Els saldos de cartera i les piles de pocions es financen a través d’AddFungible.
- Un bloquejador de magatzem es col·loca intencionadament per forçar la branca POSITION_OCCUPIED.
Canvi d’estat esperat:
- Els articles d’inici, els saldos i les piles de pocions existeixen als seus contenidors objectiu.
- El bloquejador de stash ocupa el seu ancoratge per forçar una col·lisió determinista.
Operacions
AddInstance(x5) - Crea una nova instància i la col·loca en una ubicació objectiu.AddFungible(x4) - Afegeix una quantitat fungible a un saldo o a una cel·la de graella explícita.
await write_client.commit_operations(
[
AddInstance(
class_id="class-42001",
client_tag="p1-sword",
key=None,
location={
"container_id": "container-32002",
"kind": "grid_cell",
"position": 3,
"rotation": None,
},
),
AddInstance(
class_id="class-42003",
client_tag="p1-bow",
key=None,
location={
"container_id": "container-32002",
"kind": "grid_cell",
"position": 5,
"rotation": None,
},
),
AddInstance(
class_id="class-42004",
client_tag="p1-armor",
key=None,
location={
"container_id": "container-32002",
"kind": "grid_cell",
"position": 7,
"rotation": None,
},
),
AddInstance(
class_id="class-42002",
client_tag="p2-shield",
key=None,
location={
"container_id": "container-32003",
"kind": "grid_cell",
"position": 8,
"rotation": None,
},
),
AddInstance(
class_id="class-42004",
client_tag="stash-blocker",
key=None,
location={
"container_id": "container-32008",
"kind": "grid_cell",
"position": 1,
"rotation": None,
},
),
AddFungible(
class_id="class-42006",
key=None,
location={"container_id": "container-32006", "kind": "balance"},
quantity="1000",
),
AddFungible(
class_id="class-42006",
key=None,
location={"container_id": "container-32007", "kind": "balance"},
quantity="800",
),
AddFungible(
class_id="class-42005",
key=None,
location={
"container_id": "container-32002",
"kind": "grid_cell",
"position": 11,
"rotation": None,
},
quantity="5",
),
AddFungible(
class_id="class-42005",
key=None,
location={
"container_id": "container-32002",
"kind": "grid_cell",
"position": 14,
"rotation": None,
},
quantity="3",
),
],
namespace_id=1,
idempotency_key="game-inventory-seed",
actor_id="world-seed",
metadata={
"job_id": "job-inventory-001",
"step": "seed",
"trace_id": "trace-inventory-001",
},
)OpaqueTransaction* tx = NULL;
check_ok(asset_tx_begin(runtime, 1, &tx), "asset_tx_begin");
AssetInstanceLocation location_0 = {0};
location_0.kind = AssetInstanceLocationKind_GridCell;
location_0.container = 32002;
location_0.position = 3;
location_0.rotation = AssetRotation_None;
{
uint64_t instance_id = 0;
check_ok(
asset_tx_add_instance_semantic(runtime, tx, 42001, 0, location_0, &instance_id),
"asset_tx_add_instance_semantic[0]");
}
AssetInstanceLocation location_1 = {0};
location_1.kind = AssetInstanceLocationKind_GridCell;
location_1.container = 32002;
location_1.position = 5;
location_1.rotation = AssetRotation_None;
{
uint64_t instance_id = 0;
check_ok(
asset_tx_add_instance_semantic(runtime, tx, 42003, 0, location_1, &instance_id),
"asset_tx_add_instance_semantic[1]");
}
AssetInstanceLocation location_2 = {0};
location_2.kind = AssetInstanceLocationKind_GridCell;
location_2.container = 32002;
location_2.position = 7;
location_2.rotation = AssetRotation_None;
{
uint64_t instance_id = 0;
check_ok(
asset_tx_add_instance_semantic(runtime, tx, 42004, 0, location_2, &instance_id),
"asset_tx_add_instance_semantic[2]");
}
AssetInstanceLocation location_3 = {0};
location_3.kind = AssetInstanceLocationKind_GridCell;
location_3.container = 32003;
location_3.position = 8;
location_3.rotation = AssetRotation_None;
{
uint64_t instance_id = 0;
check_ok(
asset_tx_add_instance_semantic(runtime, tx, 42002, 0, location_3, &instance_id),
"asset_tx_add_instance_semantic[3]");
}
AssetInstanceLocation location_4 = {0};
location_4.kind = AssetInstanceLocationKind_GridCell;
location_4.container = 32008;
location_4.position = 1;
location_4.rotation = AssetRotation_None;
{
uint64_t instance_id = 0;
check_ok(
asset_tx_add_instance_semantic(runtime, tx, 42004, 0, location_4, &instance_id),
"asset_tx_add_instance_semantic[4]");
}
AssetFungibleLocation location_5 = {0};
location_5.kind = AssetFungibleLocationKind_Balance;
location_5.container = 32006;
check_ok(asset_tx_add_fungible_semantic(runtime, tx, 42006, 0, 1000, location_5),
"asset_tx_add_fungible_semantic[5]");
AssetFungibleLocation location_6 = {0};
location_6.kind = AssetFungibleLocationKind_Balance;
location_6.container = 32007;
check_ok(asset_tx_add_fungible_semantic(runtime, tx, 42006, 0, 800, location_6),
"asset_tx_add_fungible_semantic[6]");
AssetFungibleLocation location_7 = {0};
location_7.kind = AssetFungibleLocationKind_GridCell;
location_7.container = 32002;
location_7.position = 11;
location_7.rotation = AssetRotation_None;
check_ok(asset_tx_add_fungible_semantic(runtime, tx, 42005, 0, 5, location_7),
"asset_tx_add_fungible_semantic[7]");
AssetFungibleLocation location_8 = {0};
location_8.kind = AssetFungibleLocationKind_GridCell;
location_8.container = 32002;
location_8.position = 14;
location_8.rotation = AssetRotation_None;
check_ok(asset_tx_add_fungible_semantic(runtime, tx, 42005, 0, 3, location_8),
"asset_tx_add_fungible_semantic[8]");
AssetTxMeta meta = {0};
OpaqueEventBatch* batch = NULL;
check_ok(asset_tx_commit(runtime, tx, &meta, &batch), "asset_tx_commit");
asset_event_batch_destroy(runtime, batch);{
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_commit",
"params": {
"actor_id": "world-seed",
"idempotency_key": "game-inventory-seed",
"metadata": {
"job_id": "job-inventory-001",
"step": "seed",
"trace_id": "trace-inventory-001"
},
"namespace_id": 1,
"operations": [
{
"args": {
"class_id": "class-42001",
"client_tag": "p1-sword",
"key": null,
"location": {
"container_id": "container-32002",
"kind": "grid_cell",
"position": 3,
"rotation": null
}
},
"op": "AddInstance"
},
{
"args": {
"class_id": "class-42003",
"client_tag": "p1-bow",
"key": null,
"location": {
"container_id": "container-32002",
"kind": "grid_cell",
"position": 5,
"rotation": null
}
},
"op": "AddInstance"
},
{
"args": {
"class_id": "class-42004",
"client_tag": "p1-armor",
"key": null,
"location": {
"container_id": "container-32002",
"kind": "grid_cell",
"position": 7,
"rotation": null
}
},
"op": "AddInstance"
},
{
"args": {
"class_id": "class-42002",
"client_tag": "p2-shield",
"key": null,
"location": {
"container_id": "container-32003",
"kind": "grid_cell",
"position": 8,
"rotation": null
}
},
"op": "AddInstance"
},
{
"args": {
"class_id": "class-42004",
"client_tag": "stash-blocker",
"key": null,
"location": {
"container_id": "container-32008",
"kind": "grid_cell",
"position": 1,
"rotation": null
}
},
"op": "AddInstance"
},
{
"args": {
"class_id": "class-42006",
"key": null,
"location": {
"container_id": "container-32006",
"kind": "balance"
},
"quantity": "1000"
},
"op": "AddFungible"
},
{
"args": {
"class_id": "class-42006",
"key": null,
"location": {
"container_id": "container-32007",
"kind": "balance"
},
"quantity": "800"
},
"op": "AddFungible"
},
{
"args": {
"class_id": "class-42005",
"key": null,
"location": {
"container_id": "container-32002",
"kind": "grid_cell",
"position": 11,
"rotation": null
},
"quantity": "5"
},
"op": "AddFungible"
},
{
"args": {
"class_id": "class-42005",
"key": null,
"location": {
"container_id": "container-32002",
"kind": "grid_cell",
"position": 14,
"rotation": null
},
"quantity": "3"
},
"op": "AddFungible"
}
]
}
}let payload = json!({
"actor_id": "world-seed",
"idempotency_key": "game-inventory-seed",
"metadata": {
"job_id": "job-inventory-001",
"step": "seed",
"trace_id": "trace-inventory-001"
},
"operations": [
{
"args": {
"class_id": "class-42001",
"client_tag": "p1-sword",
"key": null,
"location": {
"container_id": "container-32002",
"kind": "grid_cell",
"position": 3,
"rotation": null
}
},
"op": "AddInstance"
},
{
"args": {
"class_id": "class-42003",
"client_tag": "p1-bow",
"key": null,
"location": {
"container_id": "container-32002",
"kind": "grid_cell",
"position": 5,
"rotation": null
}
},
"op": "AddInstance"
},
{
"args": {
"class_id": "class-42004",
"client_tag": "p1-armor",
"key": null,
"location": {
"container_id": "container-32002",
"kind": "grid_cell",
"position": 7,
"rotation": null
}
},
"op": "AddInstance"
},
{
"args": {
"class_id": "class-42002",
"client_tag": "p2-shield",
"key": null,
"location": {
"container_id": "container-32003",
"kind": "grid_cell",
"position": 8,
"rotation": null
}
},
"op": "AddInstance"
},
{
"args": {
"class_id": "class-42004",
"client_tag": "stash-blocker",
"key": null,
"location": {
"container_id": "container-32008",
"kind": "grid_cell",
"position": 1,
"rotation": null
}
},
"op": "AddInstance"
},
{
"args": {
"class_id": "class-42006",
"key": null,
"location": {
"container_id": "container-32006",
"kind": "balance"
},
"quantity": "1000"
},
"op": "AddFungible"
},
{
"args": {
"class_id": "class-42006",
"key": null,
"location": {
"container_id": "container-32007",
"kind": "balance"
},
"quantity": "800"
},
"op": "AddFungible"
},
{
"args": {
"class_id": "class-42005",
"key": null,
"location": {
"container_id": "container-32002",
"kind": "grid_cell",
"position": 11,
"rotation": null
},
"quantity": "5"
},
"op": "AddFungible"
},
{
"args": {
"class_id": "class-42005",
"key": null,
"location": {
"container_id": "container-32002",
"kind": "grid_cell",
"position": 14,
"rotation": null
},
"quantity": "3"
},
"op": "AddFungible"
}
]
});
let response = client
.post(format!("{}/v1/write/namespaces/1/commit", write_base_url))
.bearer_auth(write_api_key)
.json(&payload)
.send()
.await?;Casos a vista d’ull
- Equip - Equipar un article als espais del jugador 1.
- Comerç - Comerç d’un article i moneda de manera atòmica.
- Deixar - Deixa un article a la zona de botí.
- Recollida - Recollir el botí a l’inventari del jugador 1.
- Stash Bloquejat - El stash bloquejat retorna POSITION_OCCUPIED.
- Stash Grid Free - Troba l’ancoratge de stash disponible més proper.
- Amagar - Amaga armadura en un ancoratge de graella lliure.
- Fusionar Piles - Fusionar piles de pocions a l’inventari del jugador 1.
- Consume - Consume pocions de la pila fusionada.
Disseny del Sistema: Què Vam Escollir i Per Què
Aquest escenari se centra en la correcció de l’inventari de grau MMO: cada interacció és un petit compromís atòmic que es pot reproduir i auditar.
Elecció: Jugadors com a Propietaris
L’Elecció: Els jugadors són propietaris; els inventaris i els espais són contenidors propietaris, no actors en moviment.
- Per què: La propietat aïlla l’estat, simplifica els permisos i manté el sistema centrat en l’autoritat d’inventari en lloc del moviment del jugador.
- Compensació: El moviment del jugador és gestionat pel teu motor; mapeixes esdeveniments en compromisos d’inventari quan és necessari.
- Quan és important: Jocs de servei en viu on els duplicats i el creuament de jugadors són inacceptables.
Elecció: Reixeta de Botí com a Ancoratges Deterministes
L’Elecció: El botí viu en un contenidor de graella amb ancoratges estables (per trobada/caixa/zona).
- Per què: Els ancoratges fan que les caigudes i recollides siguin deterministes i auditable sense acoblar-se a la física.
- Compensació: El renderitzat espacial és del costat del motor; Asset Core proporciona un estat de botí autoritzat.
- Quan és important: Loot de món compartit, recompenses de raid i investigacions de suport repetibles.
Elecció: Compromisos de Comerç Atòmic
L’Elecció: La transferència d’articles + la transferència de moneda es realitzen en un únic compromís.
- Per què: L’atomicitat evita duplicats i operacions parcials sota reintents o desconnexions.
- Compensació: Els clients han de compondre compromisos multi-op (el comerç és una única transacció).
- Quan és important: Comerç entre jugadors, liquidació d’ofertes i fluxos d’escrow.
Elecció: Clau d’idempotència per a la seguretat de reintents
La tria: Les claus d’idempotència són opcionals però, quan estan presents, dedupliquen les reintents basant-se en un hash canònic de la càrrega útil completa de la sol·licitud.
- Per què: La mateixa clau + la mateixa càrrega útil retorna la resposta emmagatzemada; la mateixa clau + càrrega útil diferent és rebutjada. Això evita dobles despeses en els reintents.
- Compensació: El servei autoritzat ha de generar o derivar claus estables per interacció i no ha de reenvair claus de clients no fiables.
- Quan és important: Serveis de jocs basats en cues, reconexions, entrega com a mínim una vegada, i fallades entre regions.
Què No Gestionem (i Per Què)
- Moviment del jugador: AssetCore no rastreja el moviment ni la física. El teu motor és responsable del moviment; AssetCore és responsable de l’autoritat de l’inventari.
- Combat RNG: Les probabilitats de caiguda i els llançaments de recompenses es produeixen en sistemes de joc; AssetCore registra l’estat resultant.
Aquests límits mantenen AssetCore centrat en garanties d’inventari i transaccions deterministes.
Semàntica d’idempotència
Les claus d’idempotència són opcionals i només controlen la desduplicació de reintents. Quan hi ha una clau present, Asset Core fa un hash de la càrrega completa de la sol·licitud i utilitza (namespace, key, hash) per decidir:
- Mateix clau + mateix càrrec: retorna la resposta emmagatzemada (sense re-execució).
- Mateix clau + càrrega útil diferent: rebutjar amb 409 Conflict.
- La validació fallida no reserva la clau; la reutilització després de la correcció és vàlida.
- No key: request executes normally, retries re-run. Sense clau: la sol·licitud s’executa normalment, els reintents es tornen a executar. En fluxos autoritzats pel servidor, el servei de joc genera o deriva claus i no hauria de reenviar claus de clients no fiables.
Com llegir els casos
- Els casos assumeixen que la configuració bàsica i els compromisos de llavor ja s’han aplicat.
- Cada cas és una interacció enfocada (commit o lectura) que podeu reproduir de manera independent.
- Les lectures/flux de validació es llisten per separat després dels casos.
- Els casos d’una sola operació utilitzen helpers d’acció per a la llegibilitat.
- Els casos de múltiples operacions es mantenen com a trucades de compromís per preservar l’atomicitat.
- Rust HTTP sempre utilitza el punt final de commit amb una o més operacions.
- Les pestanyes C ABI mostren una integració directa en temps d’execució per a equips de motor.
Artifacts de l’escenari
- manifest.json - Metadades i identificadors de l’escenari.
- game_inventory_actions.json - Càrregues de walkthrough amb acció com a prioritat.
- game_inventory_transcript.json - Registre complet de sol·licituds/respostes.
- http_snapshot.json - Resultats de compromís condensats.
- game_inventory_steps.json - Índex de passos determinístic.
Instantània de l’escenari
{
"scenario_id": "game_inventory",
"version": "0.1.0",
"namespace_id": 1,
"containers": {
"p1_equipment": "container-32004",
"p1_inventory": "container-32002",
"p1_stash": "container-32008",
"p1_wallet": "container-32006",
"p2_equipment": "container-32005",
"p2_inventory": "container-32003",
"p2_stash": "container-32009",
"p2_wallet": "container-32007",
"zone_loot": "container-32001"
},
"classes": {
"armor": "class-42004",
"bow": "class-42003",
"gold": "class-42006",
"potion": "class-42005",
"shield": "class-42002",
"sword": "class-42001"
},
"units": {
"fixed_point_scale": 1,
"length_unit": "cell",
"time_unit": "ms"
}
}
Failure branch
POSITION_OCCUPIED -> graella lliure -> torna a provar amb la mateixa clau d’idempotència
Audit trail (opcional)
La transcripció captura les parelles de sol·licitud i resposta exactes emeses durant l’execució. Aquí hi ha un extracte compacte que podeu utilitzar per validar el determinisme i la propagació de metadades:
{
"kind": "commit",
"name": "setup",
"request": {
"path": "/v1/write/namespaces/1/commit",
"idempotency_key": "game-inventory-setup",
"actor_id": "game-setup",
"metadata": {
"job_id": "job-inventory-001",
"step": "setup",
"trace_id": "trace-inventory-001"
},
"operations": [
"CreateContainer",
"CreateContainer",
"CreateContainer",
"CreateContainer",
"CreateContainer",
"CreateContainer",
"CreateContainer",
"CreateContainer",
"CreateContainer",
"RegisterClass",
"RegisterClass",
"RegisterClass",
"RegisterClass",
"RegisterClass",
"RegisterClass",
"RegisterClassShape",
"RegisterClassShape",
"RegisterClassShape",
"RegisterClassShape",
"RegisterClassShape"
]
},
"response": {
"commit_id": "00000000000000000000000000000007",
"world_seq_start": 1,
"world_seq_end": 20,
"event_count": 20
}
}
Nota de prevol
- Per defecte, les accions criden a enviar compromisos.
- Estableix ActionOptions(preflight=True) o crida assetcore_commit_preflight per a la validació.
SDK configuració
from assetcore_sdk import AssetCoreClient
from assetcore_sdk.actions import ActionOptions
from assetcore_sdk.operations import (
AddFungible,
AddInstance,
CreateContainer,
MergeStacks,
MoveInstance,
RegisterClass,
RegisterClassShape,
RemoveFungible,
TransferFungible,
)
# Snippets assume an async context (e.g., inside async def main()).
write_client = AssetCoreClient(
base_url="http://localhost:8080",
api_key="WRITE_API_KEY",
)
read_client = AssetCoreClient(
base_url="http://localhost:8081",
api_key="READ_API_KEY",
)#include "asset_core.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
static void check_ok(int32_t code, const char* label) {
if (code == ERROR_SUCCESS) {
return;
}
fprintf(stderr, "%s failed: %d\n", label, code);
abort();
}
OpaqueRuntime* runtime = NULL;
AssetRuntimeConfig config = {0};
config.struct_size = sizeof(AssetRuntimeConfig);
config.clock_type = AssetClockType_System;
check_ok(asset_runtime_create(&config, &runtime), "asset_runtime_create");use reqwest::Client;
use serde_json::json;
let client = Client::new();
let write_base_url = "http://localhost:8080";
let read_base_url = "http://localhost:8081";
let write_api_key = "WRITE_API_KEY";
let read_api_key = "READ_API_KEY";Casos
Cas 1: Equip
Equipar un article als espais del jugador 1.
Per què aquest pas és important: L’equipament és un moviment pur de l’inventari a la ranura. No hi ha cap estat ocult - només un canvi de col·locació determinista.
Condicions prèvies:
- L’element existeix a l’inventari del jugador des del compromís de llavor.
- El slot objectiu està buit.
Què cal notar en el codi:
- Una operació MoveInstance que té com a objectiu un contenidor de slots
- Clau d’idempotència vinculada a l’acció d’equip
Canvi d’estat esperat:
- L’element surt de la graella d’inventari i ocupa la slot objectiu.
Operacions
- MoveInstance - Mou una instància existent a una nova ubicació.
await write_client.actions.move_instance(
instance="inst-1",
to={"container_id": "container-32004", "kind": "slot", "slot_index": 1},
options=ActionOptions(
namespace_id=1,
idempotency_key="game-inventory-equip",
actor_id="player-1",
metadata={
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "equip",
"trace_id": "trace-inventory-001",
},
),
)OpaqueTransaction* tx = NULL;
check_ok(asset_tx_begin(runtime, 1, &tx), "asset_tx_begin");
AssetInstanceLocation location_0 = {0};
location_0.kind = AssetInstanceLocationKind_Slot;
location_0.container = 32004;
location_0.slot = 1;
check_ok(asset_tx_move_instance_semantic(runtime, tx, 1, location_0),
"asset_tx_move_instance_semantic[0]");
AssetTxMeta meta = {0};
OpaqueEventBatch* batch = NULL;
check_ok(asset_tx_commit(runtime, tx, &meta, &batch), "asset_tx_commit");
asset_event_batch_destroy(runtime, batch);{
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_move_instance",
"params": {
"actor_id": "player-1",
"idempotency_key": "game-inventory-equip",
"metadata": {
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "equip",
"trace_id": "trace-inventory-001"
},
"namespace_id": 1,
"operations": [
{
"args": {
"instance": "inst-1",
"to": {
"container_id": "container-32004",
"kind": "slot",
"slot_index": 1
}
},
"op": "MoveInstance"
}
]
}
}let payload = json!({
"actor_id": "player-1",
"idempotency_key": "game-inventory-equip",
"metadata": {
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "equip",
"trace_id": "trace-inventory-001"
},
"operations": [
{
"args": {
"instance": "inst-1",
"to": {
"container_id": "container-32004",
"kind": "slot",
"slot_index": 1
}
},
"op": "MoveInstance"
}
]
});
let response = client
.post(format!("{}/v1/write/namespaces/1/commit", write_base_url))
.bearer_auth(write_api_key)
.json(&payload)
.send()
.await?;Cas 2: Comerç
Intercanvia un article i moneda de manera atòmica.
Per què és important aquest pas: Les operacions han de ser atòmiques per evitar duplicats i transferències a mitges.
Condicions prèvies:
- Un article existeix a l’inventari del venedor a partir de llavors o casos anteriors.
- Ambdós saldos de cartera existeixen.
Què cal notar en el codi:
- MoveInstance + TransferFungible en un únic commit
- Un commit_id per a l’element + liquidació de moneda
Canvi d’estat esperat:
- L’element es mou a l’inventari del comprador i la moneda es mou al moneder del venedor de manera atòmica.
Operacions
- MoveInstance - Mou una instància existent a una nova ubicació.
TransferFungible- Transfereix una quantitat fungible entre contenidors estructurats.
await write_client.commit_operations(
[
MoveInstance(
instance="inst-2",
to={
"container_id": "container-32003",
"kind": "grid_cell",
"position": 2,
"rotation": None,
},
),
TransferFungible(
class_id="class-42006",
from_container="container-32007",
key=None,
quantity="50",
to_container="container-32006",
),
],
namespace_id=1,
idempotency_key="game-inventory-trade",
actor_id="trade-service",
metadata={
"from_player": "player-1",
"job_id": "job-inventory-001",
"step": "trade",
"to_player": "player-2",
"trace_id": "trace-inventory-001",
},
)OpaqueTransaction* tx = NULL;
check_ok(asset_tx_begin(runtime, 1, &tx), "asset_tx_begin");
AssetInstanceLocation location_0 = {0};
location_0.kind = AssetInstanceLocationKind_GridCell;
location_0.container = 32003;
location_0.position = 2;
location_0.rotation = AssetRotation_None;
check_ok(asset_tx_move_instance_semantic(runtime, tx, 2, location_0),
"asset_tx_move_instance_semantic[0]");
check_ok(asset_tx_transfer_fungible(runtime, tx, 32007, 32006, 42006, 0, 50),
"asset_tx_transfer_fungible[1]");
AssetTxMeta meta = {0};
OpaqueEventBatch* batch = NULL;
check_ok(asset_tx_commit(runtime, tx, &meta, &batch), "asset_tx_commit");
asset_event_batch_destroy(runtime, batch);{
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_commit",
"params": {
"actor_id": "trade-service",
"idempotency_key": "game-inventory-trade",
"metadata": {
"from_player": "player-1",
"job_id": "job-inventory-001",
"step": "trade",
"to_player": "player-2",
"trace_id": "trace-inventory-001"
},
"namespace_id": 1,
"operations": [
{
"args": {
"instance": "inst-2",
"to": {
"container_id": "container-32003",
"kind": "grid_cell",
"position": 2,
"rotation": null
}
},
"op": "MoveInstance"
},
{
"args": {
"class_id": "class-42006",
"from_container": "container-32007",
"key": null,
"quantity": "50",
"to_container": "container-32006"
},
"op": "TransferFungible"
}
]
}
}let payload = json!({
"actor_id": "trade-service",
"idempotency_key": "game-inventory-trade",
"metadata": {
"from_player": "player-1",
"job_id": "job-inventory-001",
"step": "trade",
"to_player": "player-2",
"trace_id": "trace-inventory-001"
},
"operations": [
{
"args": {
"instance": "inst-2",
"to": {
"container_id": "container-32003",
"kind": "grid_cell",
"position": 2,
"rotation": null
}
},
"op": "MoveInstance"
},
{
"args": {
"class_id": "class-42006",
"from_container": "container-32007",
"key": null,
"quantity": "50",
"to_container": "container-32006"
},
"op": "TransferFungible"
}
]
});
let response = client
.post(format!("{}/v1/write/namespaces/1/commit", write_base_url))
.bearer_auth(write_api_key)
.json(&payload)
.send()
.await?;Cas 3: Caiguda
Deixa un article a la zona de botí.
Per què aquest pas és important: Deixar caure és un moviment de l’inventari del jugador a la graella de botí de la zona. La graella és la taula de botí autoritzada per l’encontre.
Condicions prèvies:
- L’element existeix a l’inventari del jugador.
- L’ancora de la graella objectiu està lliure.
Què cal notar en el codi:
- MoveInstance només especifica l’objectiu; la font és la ubicació actual de la instància.
- L’ancoratge de la graella especifica la col·locació de botí determinista
- L’objecte surt de la inventari del jugador i es converteix en botí propietat de la zona.
Canvi d’estat esperat:
- L’objecte es treu de l’inventari del jugador i es col·loca en el botí de la zona.
Operacions
- MoveInstance - Mou una instància existent a una nova ubicació.
await write_client.actions.move_instance(
instance="inst-4",
to={
"container_id": "container-32001",
"kind": "grid_cell",
"position": 6,
"rotation": None,
},
options=ActionOptions(
namespace_id=1,
idempotency_key="game-inventory-drop",
actor_id="player-2",
metadata={
"job_id": "job-inventory-001",
"player_id": "player-2",
"step": "drop",
"trace_id": "trace-inventory-001",
},
),
)OpaqueTransaction* tx = NULL;
check_ok(asset_tx_begin(runtime, 1, &tx), "asset_tx_begin");
AssetInstanceLocation location_0 = {0};
location_0.kind = AssetInstanceLocationKind_GridCell;
location_0.container = 32001;
location_0.position = 6;
location_0.rotation = AssetRotation_None;
check_ok(asset_tx_move_instance_semantic(runtime, tx, 4, location_0),
"asset_tx_move_instance_semantic[0]");
AssetTxMeta meta = {0};
OpaqueEventBatch* batch = NULL;
check_ok(asset_tx_commit(runtime, tx, &meta, &batch), "asset_tx_commit");
asset_event_batch_destroy(runtime, batch);{
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_move_instance",
"params": {
"actor_id": "player-2",
"idempotency_key": "game-inventory-drop",
"metadata": {
"job_id": "job-inventory-001",
"player_id": "player-2",
"step": "drop",
"trace_id": "trace-inventory-001"
},
"namespace_id": 1,
"operations": [
{
"args": {
"instance": "inst-4",
"to": {
"container_id": "container-32001",
"kind": "grid_cell",
"position": 6,
"rotation": null
}
},
"op": "MoveInstance"
}
]
}
}let payload = json!({
"actor_id": "player-2",
"idempotency_key": "game-inventory-drop",
"metadata": {
"job_id": "job-inventory-001",
"player_id": "player-2",
"step": "drop",
"trace_id": "trace-inventory-001"
},
"operations": [
{
"args": {
"instance": "inst-4",
"to": {
"container_id": "container-32001",
"kind": "grid_cell",
"position": 6,
"rotation": null
}
},
"op": "MoveInstance"
}
]
});
let response = client
.post(format!("{}/v1/write/namespaces/1/commit", write_base_url))
.bearer_auth(write_api_key)
.json(&payload)
.send()
.await?;Cas 4: Recollida
Recollir el botí a l’inventari del jugador 1.
Per què és important aquest pas: Les recollides són moviments deterministes del botí de la zona a l’inventari del jugador - no hi ha ambigüitat sobre qui és el propietari de l’objecte.
Condicions prèvies:
- L’element existeix a la graella de botí de la zona (del cas de caiguda).
- L’ancora d’inventari objectiu està lliure.
Què cal notar en el codi:
- La mateixa ID d’instància es mou entre contenidors
- La metadada del compromís vincula l’acció al jugador
Canvi d’estat esperat:
- L’objecte deixa el botí de la zona i torna a una ranura d’inventari del jugador.
Operacions
- MoveInstance - Mou una instància existent a una nova ubicació.
await write_client.actions.move_instance(
instance="inst-4",
to={
"container_id": "container-32002",
"kind": "grid_cell",
"position": 19,
"rotation": None,
},
options=ActionOptions(
namespace_id=1,
idempotency_key="game-inventory-pickup",
actor_id="player-1",
metadata={
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "pickup",
"trace_id": "trace-inventory-001",
},
),
)OpaqueTransaction* tx = NULL;
check_ok(asset_tx_begin(runtime, 1, &tx), "asset_tx_begin");
AssetInstanceLocation location_0 = {0};
location_0.kind = AssetInstanceLocationKind_GridCell;
location_0.container = 32002;
location_0.position = 19;
location_0.rotation = AssetRotation_None;
check_ok(asset_tx_move_instance_semantic(runtime, tx, 4, location_0),
"asset_tx_move_instance_semantic[0]");
AssetTxMeta meta = {0};
OpaqueEventBatch* batch = NULL;
check_ok(asset_tx_commit(runtime, tx, &meta, &batch), "asset_tx_commit");
asset_event_batch_destroy(runtime, batch);{
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_move_instance",
"params": {
"actor_id": "player-1",
"idempotency_key": "game-inventory-pickup",
"metadata": {
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "pickup",
"trace_id": "trace-inventory-001"
},
"namespace_id": 1,
"operations": [
{
"args": {
"instance": "inst-4",
"to": {
"container_id": "container-32002",
"kind": "grid_cell",
"position": 19,
"rotation": null
}
},
"op": "MoveInstance"
}
]
}
}let payload = json!({
"actor_id": "player-1",
"idempotency_key": "game-inventory-pickup",
"metadata": {
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "pickup",
"trace_id": "trace-inventory-001"
},
"operations": [
{
"args": {
"instance": "inst-4",
"to": {
"container_id": "container-32002",
"kind": "grid_cell",
"position": 19,
"rotation": null
}
},
"op": "MoveInstance"
}
]
});
let response = client
.post(format!("{}/v1/write/namespaces/1/commit", write_base_url))
.bearer_auth(write_api_key)
.json(&payload)
.send()
.await?;Cas 5: Stash Bloquejat (Error esperat)
Bloqueig de l’emmagatzematge retorna POSITION_OCCUPIED.
Per què aquest pas és important: Les col·lisions de stash es validen abans de la mutació. Sense escriptures parcials, sense lògica de neteja.
Condicions prèvies:
- Un bloquejador de stash ocupa l’ancora objectiu (commit de llavor).
Què cal notar en el codi:
- Error POSITION_OCCUPIED abans de qualsevol canvi d’estat
- Retry utilitza grid_free per trobar un nou ancoratge
Canvi d’estat esperat:
- No s’apliquen canvis d’estat; aquesta és una fallada de validació determinista.
Operacions
- MoveInstance - Mou una instància existent a una nova ubicació.
Error esperat
{
"code": "POSITION_OCCUPIED",
"detail": "Position 1 in container 8 is occupied by item at anchor 1",
"hint": "Select an unoccupied position or move the existing item first.",
"retryable": false,
"status": 409,
"title": "ConflictError",
"type": "urn:assetcore:error:POSITION_OCCUPIED"
}
await write_client.actions.move_instance(
instance="inst-3",
to={
"container_id": "container-32008",
"kind": "grid_cell",
"position": 1,
"rotation": None,
},
options=ActionOptions(
namespace_id=1,
idempotency_key="game-inventory-stash",
actor_id="player-1",
metadata={
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "stash-attempt",
"trace_id": "trace-inventory-001",
},
),
)OpaqueTransaction* tx = NULL;
check_ok(asset_tx_begin(runtime, 1, &tx), "asset_tx_begin");
AssetInstanceLocation location_0 = {0};
location_0.kind = AssetInstanceLocationKind_GridCell;
location_0.container = 32008;
location_0.position = 1;
location_0.rotation = AssetRotation_None;
check_ok(asset_tx_move_instance_semantic(runtime, tx, 3, location_0),
"asset_tx_move_instance_semantic[0]");
AssetTxMeta meta = {0};
OpaqueEventBatch* batch = NULL;
check_ok(asset_tx_commit(runtime, tx, &meta, &batch), "asset_tx_commit");
asset_event_batch_destroy(runtime, batch);{
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_move_instance",
"params": {
"actor_id": "player-1",
"idempotency_key": "game-inventory-stash",
"metadata": {
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "stash-attempt",
"trace_id": "trace-inventory-001"
},
"namespace_id": 1,
"operations": [
{
"args": {
"instance": "inst-3",
"to": {
"container_id": "container-32008",
"kind": "grid_cell",
"position": 1,
"rotation": null
}
},
"op": "MoveInstance"
}
]
}
}let payload = json!({
"actor_id": "player-1",
"idempotency_key": "game-inventory-stash",
"metadata": {
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "stash-attempt",
"trace_id": "trace-inventory-001"
},
"operations": [
{
"args": {
"instance": "inst-3",
"to": {
"container_id": "container-32008",
"kind": "grid_cell",
"position": 1,
"rotation": null
}
},
"op": "MoveInstance"
}
]
});
let response = client
.post(format!("{}/v1/write/namespaces/1/commit", write_base_url))
.bearer_auth(write_api_key)
.json(&payload)
.send()
.await?;Cas 6: Stash Grid Lliure
Troba l’ancora de stash disponible més propera.
await read_client.get_container_grid_free(
container_id="container-32008",
namespace_id=1,
height=2,
width=2,
){
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_find_grid_free",
"params": {
"container_id": "container-32008",
"height": 2,
"namespace_id": 1,
"width": 2
}
}let request = client
.get(format!("{}/v1/read/namespaces/1/containers/container-32008/grid/free", read_base_url))
.query(&[("height", 2), ("width", 2)])
.bearer_auth(read_api_key)
.send()
.await?;
let body = request.text().await?;Cas 7: Stash
Emmagatzemar armadura en un ancoratge de gra lliure.
Operacions
- MoveInstance - Mou una instància existent a una nova ubicació.
await write_client.actions.move_instance(
instance="inst-3",
to={
"container_id": "container-32008",
"kind": "grid_cell",
"position": 3,
"rotation": None,
},
options=ActionOptions(
namespace_id=1,
idempotency_key="game-inventory-stash",
actor_id="player-1",
metadata={
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "stash",
"trace_id": "trace-inventory-001",
},
),
)OpaqueTransaction* tx = NULL;
check_ok(asset_tx_begin(runtime, 1, &tx), "asset_tx_begin");
AssetInstanceLocation location_0 = {0};
location_0.kind = AssetInstanceLocationKind_GridCell;
location_0.container = 32008;
location_0.position = 3;
location_0.rotation = AssetRotation_None;
check_ok(asset_tx_move_instance_semantic(runtime, tx, 3, location_0),
"asset_tx_move_instance_semantic[0]");
AssetTxMeta meta = {0};
OpaqueEventBatch* batch = NULL;
check_ok(asset_tx_commit(runtime, tx, &meta, &batch), "asset_tx_commit");
asset_event_batch_destroy(runtime, batch);{
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_move_instance",
"params": {
"actor_id": "player-1",
"idempotency_key": "game-inventory-stash",
"metadata": {
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "stash",
"trace_id": "trace-inventory-001"
},
"namespace_id": 1,
"operations": [
{
"args": {
"instance": "inst-3",
"to": {
"container_id": "container-32008",
"kind": "grid_cell",
"position": 3,
"rotation": null
}
},
"op": "MoveInstance"
}
]
}
}let payload = json!({
"actor_id": "player-1",
"idempotency_key": "game-inventory-stash",
"metadata": {
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "stash",
"trace_id": "trace-inventory-001"
},
"operations": [
{
"args": {
"instance": "inst-3",
"to": {
"container_id": "container-32008",
"kind": "grid_cell",
"position": 3,
"rotation": null
}
},
"op": "MoveInstance"
}
]
});
let response = client
.post(format!("{}/v1/write/namespaces/1/commit", write_base_url))
.bearer_auth(write_api_key)
.json(&payload)
.send()
.await?;Cas 8: Fusió de Pila
Fusiona les piles de pocions a l’inventari del jugador 1.
Per què és important aquest pas: La consolidació de les piles manté els inventaris compactes mentre preserva la quantitat total.
Condicions prèvies:
- Existeixen múltiples piles de pocions a la graella d’inventari.
Què cal notar en el codi:
- MergeStacks utilitza identificadors de pila de la projecció de lectura
Canvi d’estat esperat:
- Les piles d’origen es consoliden en una única pila amb la quantitat total preservada.
Operacions
MergeStacks- Fusiona una pila fungible dins d’una altra dins d’un contenidor.
await write_client.actions.merge_stacks(
dst_stack="stack-1",
src_stack="stack-2",
options=ActionOptions(
namespace_id=1,
idempotency_key="game-inventory-stack-merge",
actor_id="player-1",
metadata={
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "stack-merge",
"trace_id": "trace-inventory-001",
},
),
)OpaqueTransaction* tx = NULL;
check_ok(asset_tx_begin(runtime, 1, &tx), "asset_tx_begin");
check_ok(asset_tx_merge_stacks(runtime, tx, stack - 1, stack - 2),
"asset_tx_merge_stacks[0]");
AssetTxMeta meta = {0};
OpaqueEventBatch* batch = NULL;
check_ok(asset_tx_commit(runtime, tx, &meta, &batch), "asset_tx_commit");
asset_event_batch_destroy(runtime, batch);{
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_commit",
"params": {
"actor_id": "player-1",
"idempotency_key": "game-inventory-stack-merge",
"metadata": {
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "stack-merge",
"trace_id": "trace-inventory-001"
},
"namespace_id": 1,
"operations": [
{
"args": {
"dst_stack": "stack-1",
"src_stack": "stack-2"
},
"op": "MergeStacks"
}
]
}
}let payload = json!({
"actor_id": "player-1",
"idempotency_key": "game-inventory-stack-merge",
"metadata": {
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "stack-merge",
"trace_id": "trace-inventory-001"
},
"operations": [
{
"args": {
"dst_stack": "stack-1",
"src_stack": "stack-2"
},
"op": "MergeStacks"
}
]
});
let response = client
.post(format!("{}/v1/write/namespaces/1/commit", write_base_url))
.bearer_auth(write_api_key)
.json(&payload)
.send()
.await?;Cas 9: Consum
Consumeix pocions de la pila fusionada.
Per què és important aquest pas: El consum és només un decrement determinista; la pista d’auditoria mostra exactament què ha canviat.
Condicions prèvies:
- Existeix una pila de pocions a la graella d’inventari.
Què cal notar en el codi:
- RemoveFungible apunta a una ubicació de pila de graella
Canvi d’estat esperat:
- La quantitat de la pila disminueix per l’import consumit.
Operacions
RemoveFungible- Elimina la quantitat fungible d’un saldo o d’una cel·la de la graella explícita.
await write_client.actions.remove_fungible(
class_id="class-42005",
from_={
"container_id": "container-32002",
"kind": "grid_cell",
"position": 11,
"rotation": None,
},
key=None,
quantity="2",
options=ActionOptions(
namespace_id=1,
idempotency_key="game-inventory-consume",
actor_id="player-1",
metadata={
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "consume",
"trace_id": "trace-inventory-001",
},
),
)OpaqueTransaction* tx = NULL;
check_ok(asset_tx_begin(runtime, 1, &tx), "asset_tx_begin");
AssetFungibleLocation location_0 = {0};
location_0.kind = AssetFungibleLocationKind_GridCell;
location_0.container = 32002;
location_0.position = 11;
location_0.rotation = AssetRotation_None;
{
int64_t removed = 0;
check_ok(
asset_tx_remove_fungible_semantic(runtime, tx, 42005, 0, 2, location_0, &removed),
"asset_tx_remove_fungible_semantic[0]");
}
AssetTxMeta meta = {0};
OpaqueEventBatch* batch = NULL;
check_ok(asset_tx_commit(runtime, tx, &meta, &batch), "asset_tx_commit");
asset_event_batch_destroy(runtime, batch);{
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_remove_fungible",
"params": {
"actor_id": "player-1",
"idempotency_key": "game-inventory-consume",
"metadata": {
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "consume",
"trace_id": "trace-inventory-001"
},
"namespace_id": 1,
"operations": [
{
"args": {
"class_id": "class-42005",
"from": {
"container_id": "container-32002",
"kind": "grid_cell",
"position": 11,
"rotation": null
},
"key": null,
"quantity": "2"
},
"op": "RemoveFungible"
}
]
}
}let payload = json!({
"actor_id": "player-1",
"idempotency_key": "game-inventory-consume",
"metadata": {
"job_id": "job-inventory-001",
"player_id": "player-1",
"step": "consume",
"trace_id": "trace-inventory-001"
},
"operations": [
{
"args": {
"class_id": "class-42005",
"from": {
"container_id": "container-32002",
"kind": "grid_cell",
"position": 11,
"rotation": null
},
"key": null,
"quantity": "2"
},
"op": "RemoveFungible"
}
]
});
let response = client
.post(format!("{}/v1/write/namespaces/1/commit", write_base_url))
.bearer_auth(write_api_key)
.json(&payload)
.send()
.await?;Validació
Aquests lectures i fluxos confirmen l’estat final després dels casos sense mutar res.
Stream 1: Stream Trade
Llegeix el compromís comercial de SSE.
{
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_read_stream",
"params": {
"from_world_seq": 36,
"limit": 1,
"namespace_id": 1
}
}let request = client
.get(format!("{}/v1/read/namespaces/1/stream", read_base_url))
.query(&[("from_world_seq", 36), ("limit", 1)])
.bearer_auth(read_api_key)
.send()
.await?;
let body = request.text().await?;Llegir 2: Llegir les piles de pocions
Resolució dels identificadors de pila de pocions per a la fusió.
await read_client.get_container_grid(
container_id="container-32002",
namespace_id=1,
){
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_get_container_grid",
"params": {
"container_id": "container-32002",
"namespace_id": 1
}
}let request = client
.get(format!("{}/v1/read/namespaces/1/containers/container-32002/grid/cells", read_base_url))
.bearer_auth(read_api_key)
.send()
.await?;
let body = request.text().await?;Llegir 3: Llegir Slots
Verifiqueu els espais d’equipament del jugador 1.
await read_client.get_container_slots(
container_id="container-32004",
namespace_id=1,
)let request = client
.get(format!("{}/v1/read/namespaces/1/containers/container-32004/slots", read_base_url))
.bearer_auth(read_api_key)
.send()
.await?;
let body = request.text().await?;Llegir 4: Llegir l’inventari de P1
Verifiqueu la graella d’inventari del jugador 1.
await read_client.get_container_grid(
container_id="container-32002",
namespace_id=1,
){
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_get_container_grid",
"params": {
"container_id": "container-32002",
"namespace_id": 1
}
}let request = client
.get(format!("{}/v1/read/namespaces/1/containers/container-32002/grid/cells", read_base_url))
.bearer_auth(read_api_key)
.send()
.await?;
let body = request.text().await?;Llegir 5: Llegir l’inventari de P2
Verifiqueu la graella d’inventari del jugador 2.
await read_client.get_container_grid(
container_id="container-32003",
namespace_id=1,
){
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_get_container_grid",
"params": {
"container_id": "container-32003",
"namespace_id": 1
}
}let request = client
.get(format!("{}/v1/read/namespaces/1/containers/container-32003/grid/cells", read_base_url))
.bearer_auth(read_api_key)
.send()
.await?;
let body = request.text().await?;Llegir 6: Llegir el Botí de la Zona
Verifiqueu que la graella de botí de la zona estigui buida.
await read_client.get_container_grid(
container_id="container-32001",
namespace_id=1,
){
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_get_container_grid",
"params": {
"container_id": "container-32001",
"namespace_id": 1
}
}let request = client
.get(format!("{}/v1/read/namespaces/1/containers/container-32001/grid/cells", read_base_url))
.bearer_auth(read_api_key)
.send()
.await?;
let body = request.text().await?;Llegir 7: Llegir Stash
Verifiqueu les ubicacions de l’estoc del jugador 1.
await read_client.get_container_grid(
container_id="container-32008",
namespace_id=1,
){
"id": 1,
"jsonrpc": "2.0",
"method": "assetcore_get_container_grid",
"params": {
"container_id": "container-32008",
"namespace_id": 1
}
}let request = client
.get(format!("{}/v1/read/namespaces/1/containers/container-32008/grid/cells", read_base_url))
.bearer_auth(read_api_key)
.send()
.await?;
let body = request.text().await?;Llegir 8: Llegir Moneders
Verifiqueu els saldos de les carteres dels jugadors després del comerç.
let request = client
.get(format!("{}/v1/read/namespaces/1/containers/container-32006/balances", read_base_url))
.bearer_auth(read_api_key)
.send()
.await?;
let body = request.text().await?;
Llegir 9: Llegir Moneders P2
Verifiqueu els saldos de la cartera del jugador 2 després del comerç.
let request = client
.get(format!("{}/v1/read/namespaces/1/containers/container-32007/balances", read_base_url))
.bearer_auth(read_api_key)
.send()
.await?;
let body = request.text().await?;
Llegir 10: Llegir Pocions
Verifiqueu el saldo consolidat de pocions a la graella d’inventari.
let request = client
.get(format!("{}/v1/read/namespaces/1/containers/container-32002/balances", read_base_url))
.bearer_auth(read_api_key)
.send()
.await?;
let body = request.text().await?;
Què Prova Això: Inventari de Grau MMO Sense Duplicats
Aquest escenari mostra com un petit conjunt primitiu determinista pot cobrir equip, comerç, caigudes de botí, emmagatzematge i consumibles.
Punt de Prova 1: Comerços Atòmics i Garanties Anti-Dup
La transferència d’articles i la transferència de moneda es realitzen en un únic compromís. Si el compromís té èxit, ambdues modificacions d’estat es registren; si falla, cap de les dues es realitza. Aquesta és la garantia anti-duplicació que requereixen els jocs de servei en viu.
Punt de prova 2: Zona de loot com a Estat Autoritari
Les caigudes aterren en una graella de botí de zona (o graella per trobada). Les recollides són moviments deterministes d’aquesta graella cap a l’inventari d’un jugador. El motor del joc renderitza, AssetCore garanteix l’autoritat.
Punt de prova 3: Tancament complet a través d’inventari, equipament i emmagatzematge
Cada element existeix en exactament un contenidor: graella d’inventari, espai d’equipament, graella de magatzem, botí de zona o destruït. No hi ha estat d’ombra. Quan un element es mou, deixa un contenidor i entra en un altre en el mateix compromís.
Punt de prova 4: Mecàniques de Stash i Stack amb Modes de Fallada Clars
Les ubicacions bloquejades fallen abans de la mutació, grid_free troba l’ancora següent, i les piles es consoliden sense perdre quantitat. Cada canvi és auditable i reproduïble.
Punt de prova 5: Integració a nivell de motor
Cada pas inclou una pestanya C ABI perquè els equips de motor puguin integrar-se sense dependències d’HTTP o SDK mentre es mantenen garanties deterministes.
Properes passes
- Prova-ho localment: Executa l’escenari per executar el conjunt complet d’interaccions
- Amplia-ho: Afegeix creació, subhastes o inventaris estacionals amb les mateixes primitives
- Integra-ho: Utilitza la pestanya C ABI per connectar-te al teu temps d’execució del motor