@dcl/protocol 1.0.0-29286492043.commit-0010e70 → 1.0.0-29287211316.commit-e44c721

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +217 -0
  2. package/out-js/decentraland/kernel/comms/rfc4/comms.gen.d.ts +21 -0
  3. package/out-js/decentraland/kernel/comms/rfc4/comms.gen.js +124 -4
  4. package/out-js/decentraland/kernel/comms/rfc4/comms.gen.js.map +1 -1
  5. package/out-js/decentraland/sdk/components/avatar_shape.gen.d.ts +8 -0
  6. package/out-js/decentraland/sdk/components/avatar_shape.gen.js +35 -1
  7. package/out-js/decentraland/sdk/components/avatar_shape.gen.js.map +1 -1
  8. package/out-ts/decentraland/kernel/comms/rfc4/comms.gen.ts +166 -2
  9. package/out-ts/decentraland/sdk/components/avatar_shape.gen.ts +34 -0
  10. package/package.json +9 -6
  11. package/proto/decentraland/common/options.proto +51 -0
  12. package/proto/decentraland/common/quantization_example.proto +164 -0
  13. package/proto/decentraland/kernel/comms/rfc4/comms.proto +11 -0
  14. package/proto/decentraland/pulse/pulse_client.proto +79 -0
  15. package/proto/decentraland/pulse/pulse_server.proto +144 -0
  16. package/proto/decentraland/pulse/pulse_shared.proto +57 -0
  17. package/proto/decentraland/sdk/components/avatar_shape.proto +5 -0
  18. package/proto/decentraland/sdk/components/light_source.proto +1 -1
  19. package/proto/decentraland/sdk/components/virtual_camera.proto +2 -0
  20. package/protoc-gen-bitwise/generator_csharp.js +248 -0
  21. package/protoc-gen-bitwise/options.js +139 -0
  22. package/protoc-gen-bitwise/plugin.js +87 -0
  23. package/protoc-gen-bitwise/runtime/cs/Quantize.cs +70 -0
  24. package/protoc-gen-bitwise/wire.js +239 -0
  25. package/proto/buf.yaml +0 -47
  26. package/proto/google/LICENSE +0 -27
  27. package/proto/google/README.md +0 -1
  28. package/proto/google/api/annotations.json +0 -83
  29. package/proto/google/api/annotations.proto +0 -11
  30. package/proto/google/api/http.json +0 -86
  31. package/proto/google/api/http.proto +0 -31
  32. package/proto/google/protobuf/api.json +0 -118
  33. package/proto/google/protobuf/api.proto +0 -34
  34. package/proto/google/protobuf/descriptor.json +0 -739
  35. package/proto/google/protobuf/descriptor.proto +0 -286
  36. package/proto/google/protobuf/source_context.json +0 -20
  37. package/proto/google/protobuf/source_context.proto +0 -7
  38. package/proto/google/protobuf/type.json +0 -202
  39. package/proto/google/protobuf/type.proto +0 -89
@@ -137,6 +137,36 @@ export interface PlayerEmote {
137
137
  incrementalId: number;
138
138
  urn: string;
139
139
  timestamp: number;
140
+ /** true means the emote has been stopped in the sender's client */
141
+ isStopping?:
142
+ | boolean
143
+ | undefined;
144
+ /** true when it is not the first time the looping animation plays */
145
+ isRepeating?:
146
+ | boolean
147
+ | undefined;
148
+ /** identifies an interaction univocaly, established when the start animation is triggered */
149
+ interactionId?:
150
+ | number
151
+ | undefined;
152
+ /** -1 means it does not use an outcome animation */
153
+ socialEmoteOutcome?:
154
+ | number
155
+ | undefined;
156
+ /** to a social emote started by other user */
157
+ isReacting?:
158
+ | boolean
159
+ | undefined;
160
+ /** wallet address of the user that initiated social emote */
161
+ socialEmoteInitiator?:
162
+ | string
163
+ | undefined;
164
+ /** wallet address of the user whose avatar is the target of a directed emote */
165
+ targetAvatar?:
166
+ | string
167
+ | undefined;
168
+ /** mask for which bones an animation applies to. */
169
+ mask?: number | undefined;
140
170
  }
141
171
 
142
172
  export interface SceneEmote {
@@ -161,6 +191,11 @@ export interface ProfileResponse {
161
191
  export interface Chat {
162
192
  message: string;
163
193
  timestamp: number;
194
+ /**
195
+ * Extension: optional forwarded_from to identify the original sender when
196
+ * messages are forwarded through an SFU
197
+ */
198
+ forwardedFrom?: string | undefined;
164
199
  }
165
200
 
166
201
  export interface Scene {
@@ -1287,7 +1322,19 @@ export namespace MovementCompressed {
1287
1322
  }
1288
1323
 
1289
1324
  function createBasePlayerEmote(): PlayerEmote {
1290
- return { incrementalId: 0, urn: "", timestamp: 0 };
1325
+ return {
1326
+ incrementalId: 0,
1327
+ urn: "",
1328
+ timestamp: 0,
1329
+ isStopping: undefined,
1330
+ isRepeating: undefined,
1331
+ interactionId: undefined,
1332
+ socialEmoteOutcome: undefined,
1333
+ isReacting: undefined,
1334
+ socialEmoteInitiator: undefined,
1335
+ targetAvatar: undefined,
1336
+ mask: undefined,
1337
+ };
1291
1338
  }
1292
1339
 
1293
1340
  export namespace PlayerEmote {
@@ -1301,6 +1348,30 @@ export namespace PlayerEmote {
1301
1348
  if (message.timestamp !== 0) {
1302
1349
  writer.uint32(29).float(message.timestamp);
1303
1350
  }
1351
+ if (message.isStopping !== undefined) {
1352
+ writer.uint32(32).bool(message.isStopping);
1353
+ }
1354
+ if (message.isRepeating !== undefined) {
1355
+ writer.uint32(40).bool(message.isRepeating);
1356
+ }
1357
+ if (message.interactionId !== undefined) {
1358
+ writer.uint32(48).int32(message.interactionId);
1359
+ }
1360
+ if (message.socialEmoteOutcome !== undefined) {
1361
+ writer.uint32(56).int32(message.socialEmoteOutcome);
1362
+ }
1363
+ if (message.isReacting !== undefined) {
1364
+ writer.uint32(64).bool(message.isReacting);
1365
+ }
1366
+ if (message.socialEmoteInitiator !== undefined) {
1367
+ writer.uint32(74).string(message.socialEmoteInitiator);
1368
+ }
1369
+ if (message.targetAvatar !== undefined) {
1370
+ writer.uint32(82).string(message.targetAvatar);
1371
+ }
1372
+ if (message.mask !== undefined) {
1373
+ writer.uint32(88).uint32(message.mask);
1374
+ }
1304
1375
  return writer;
1305
1376
  }
1306
1377
 
@@ -1332,6 +1403,62 @@ export namespace PlayerEmote {
1332
1403
 
1333
1404
  message.timestamp = reader.float();
1334
1405
  continue;
1406
+ case 4:
1407
+ if (tag !== 32) {
1408
+ break;
1409
+ }
1410
+
1411
+ message.isStopping = reader.bool();
1412
+ continue;
1413
+ case 5:
1414
+ if (tag !== 40) {
1415
+ break;
1416
+ }
1417
+
1418
+ message.isRepeating = reader.bool();
1419
+ continue;
1420
+ case 6:
1421
+ if (tag !== 48) {
1422
+ break;
1423
+ }
1424
+
1425
+ message.interactionId = reader.int32();
1426
+ continue;
1427
+ case 7:
1428
+ if (tag !== 56) {
1429
+ break;
1430
+ }
1431
+
1432
+ message.socialEmoteOutcome = reader.int32();
1433
+ continue;
1434
+ case 8:
1435
+ if (tag !== 64) {
1436
+ break;
1437
+ }
1438
+
1439
+ message.isReacting = reader.bool();
1440
+ continue;
1441
+ case 9:
1442
+ if (tag !== 74) {
1443
+ break;
1444
+ }
1445
+
1446
+ message.socialEmoteInitiator = reader.string();
1447
+ continue;
1448
+ case 10:
1449
+ if (tag !== 82) {
1450
+ break;
1451
+ }
1452
+
1453
+ message.targetAvatar = reader.string();
1454
+ continue;
1455
+ case 11:
1456
+ if (tag !== 88) {
1457
+ break;
1458
+ }
1459
+
1460
+ message.mask = reader.uint32();
1461
+ continue;
1335
1462
  }
1336
1463
  if ((tag & 7) === 4 || tag === 0) {
1337
1464
  break;
@@ -1346,6 +1473,14 @@ export namespace PlayerEmote {
1346
1473
  incrementalId: isSet(object.incrementalId) ? Number(object.incrementalId) : 0,
1347
1474
  urn: isSet(object.urn) ? String(object.urn) : "",
1348
1475
  timestamp: isSet(object.timestamp) ? Number(object.timestamp) : 0,
1476
+ isStopping: isSet(object.isStopping) ? Boolean(object.isStopping) : undefined,
1477
+ isRepeating: isSet(object.isRepeating) ? Boolean(object.isRepeating) : undefined,
1478
+ interactionId: isSet(object.interactionId) ? Number(object.interactionId) : undefined,
1479
+ socialEmoteOutcome: isSet(object.socialEmoteOutcome) ? Number(object.socialEmoteOutcome) : undefined,
1480
+ isReacting: isSet(object.isReacting) ? Boolean(object.isReacting) : undefined,
1481
+ socialEmoteInitiator: isSet(object.socialEmoteInitiator) ? String(object.socialEmoteInitiator) : undefined,
1482
+ targetAvatar: isSet(object.targetAvatar) ? String(object.targetAvatar) : undefined,
1483
+ mask: isSet(object.mask) ? Number(object.mask) : undefined,
1349
1484
  };
1350
1485
  }
1351
1486
 
@@ -1354,6 +1489,14 @@ export namespace PlayerEmote {
1354
1489
  message.incrementalId !== undefined && (obj.incrementalId = Math.round(message.incrementalId));
1355
1490
  message.urn !== undefined && (obj.urn = message.urn);
1356
1491
  message.timestamp !== undefined && (obj.timestamp = message.timestamp);
1492
+ message.isStopping !== undefined && (obj.isStopping = message.isStopping);
1493
+ message.isRepeating !== undefined && (obj.isRepeating = message.isRepeating);
1494
+ message.interactionId !== undefined && (obj.interactionId = Math.round(message.interactionId));
1495
+ message.socialEmoteOutcome !== undefined && (obj.socialEmoteOutcome = Math.round(message.socialEmoteOutcome));
1496
+ message.isReacting !== undefined && (obj.isReacting = message.isReacting);
1497
+ message.socialEmoteInitiator !== undefined && (obj.socialEmoteInitiator = message.socialEmoteInitiator);
1498
+ message.targetAvatar !== undefined && (obj.targetAvatar = message.targetAvatar);
1499
+ message.mask !== undefined && (obj.mask = Math.round(message.mask));
1357
1500
  return obj;
1358
1501
  }
1359
1502
 
@@ -1366,6 +1509,14 @@ export namespace PlayerEmote {
1366
1509
  message.incrementalId = object.incrementalId ?? 0;
1367
1510
  message.urn = object.urn ?? "";
1368
1511
  message.timestamp = object.timestamp ?? 0;
1512
+ message.isStopping = object.isStopping ?? undefined;
1513
+ message.isRepeating = object.isRepeating ?? undefined;
1514
+ message.interactionId = object.interactionId ?? undefined;
1515
+ message.socialEmoteOutcome = object.socialEmoteOutcome ?? undefined;
1516
+ message.isReacting = object.isReacting ?? undefined;
1517
+ message.socialEmoteInitiator = object.socialEmoteInitiator ?? undefined;
1518
+ message.targetAvatar = object.targetAvatar ?? undefined;
1519
+ message.mask = object.mask ?? undefined;
1369
1520
  return message;
1370
1521
  }
1371
1522
  }
@@ -1642,7 +1793,7 @@ export namespace ProfileResponse {
1642
1793
  }
1643
1794
 
1644
1795
  function createBaseChat(): Chat {
1645
- return { message: "", timestamp: 0 };
1796
+ return { message: "", timestamp: 0, forwardedFrom: undefined };
1646
1797
  }
1647
1798
 
1648
1799
  export namespace Chat {
@@ -1653,6 +1804,9 @@ export namespace Chat {
1653
1804
  if (message.timestamp !== 0) {
1654
1805
  writer.uint32(17).double(message.timestamp);
1655
1806
  }
1807
+ if (message.forwardedFrom !== undefined) {
1808
+ writer.uint32(26).string(message.forwardedFrom);
1809
+ }
1656
1810
  return writer;
1657
1811
  }
1658
1812
 
@@ -1677,6 +1831,13 @@ export namespace Chat {
1677
1831
 
1678
1832
  message.timestamp = reader.double();
1679
1833
  continue;
1834
+ case 3:
1835
+ if (tag !== 26) {
1836
+ break;
1837
+ }
1838
+
1839
+ message.forwardedFrom = reader.string();
1840
+ continue;
1680
1841
  }
1681
1842
  if ((tag & 7) === 4 || tag === 0) {
1682
1843
  break;
@@ -1690,6 +1851,7 @@ export namespace Chat {
1690
1851
  return {
1691
1852
  message: isSet(object.message) ? String(object.message) : "",
1692
1853
  timestamp: isSet(object.timestamp) ? Number(object.timestamp) : 0,
1854
+ forwardedFrom: isSet(object.forwardedFrom) ? String(object.forwardedFrom) : undefined,
1693
1855
  };
1694
1856
  }
1695
1857
 
@@ -1697,6 +1859,7 @@ export namespace Chat {
1697
1859
  const obj: any = {};
1698
1860
  message.message !== undefined && (obj.message = message.message);
1699
1861
  message.timestamp !== undefined && (obj.timestamp = message.timestamp);
1862
+ message.forwardedFrom !== undefined && (obj.forwardedFrom = message.forwardedFrom);
1700
1863
  return obj;
1701
1864
  }
1702
1865
 
@@ -1708,6 +1871,7 @@ export namespace Chat {
1708
1871
  const message = createBaseChat();
1709
1872
  message.message = object.message ?? "";
1710
1873
  message.timestamp = object.timestamp ?? 0;
1874
+ message.forwardedFrom = object.forwardedFrom ?? undefined;
1711
1875
  return message;
1712
1876
  }
1713
1877
  }
@@ -5,6 +5,40 @@ import { Color3 } from "../../common/colors.gen";
5
5
 
6
6
  export const protobufPackage = "decentraland.sdk.components";
7
7
 
8
+ /** Mask for which bones an animation applies to. */
9
+ export enum AvatarEmoteMask {
10
+ AEM_FULL_BODY = 0,
11
+ AEM_UPPER_BODY = 1,
12
+ UNRECOGNIZED = -1,
13
+ }
14
+
15
+ export function avatarEmoteMaskFromJSON(object: any): AvatarEmoteMask {
16
+ switch (object) {
17
+ case 0:
18
+ case "AEM_FULL_BODY":
19
+ return AvatarEmoteMask.AEM_FULL_BODY;
20
+ case 1:
21
+ case "AEM_UPPER_BODY":
22
+ return AvatarEmoteMask.AEM_UPPER_BODY;
23
+ case -1:
24
+ case "UNRECOGNIZED":
25
+ default:
26
+ return AvatarEmoteMask.UNRECOGNIZED;
27
+ }
28
+ }
29
+
30
+ export function avatarEmoteMaskToJSON(object: AvatarEmoteMask): string {
31
+ switch (object) {
32
+ case AvatarEmoteMask.AEM_FULL_BODY:
33
+ return "AEM_FULL_BODY";
34
+ case AvatarEmoteMask.AEM_UPPER_BODY:
35
+ return "AEM_UPPER_BODY";
36
+ case AvatarEmoteMask.UNRECOGNIZED:
37
+ default:
38
+ return "UNRECOGNIZED";
39
+ }
40
+ }
41
+
8
42
  /**
9
43
  * The AvatarShape component contains the information required to draw and animate avatar, acting as
10
44
  * a simplified GLTF container for this specific case.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcl/protocol",
3
- "version": "1.0.0-29286492043.commit-0010e70",
3
+ "version": "1.0.0-29287211316.commit-e44c721",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,10 +13,11 @@
13
13
  "license": "Apache-2.0",
14
14
  "main": "index.js",
15
15
  "scripts": {
16
- "test": "./scripts/check-proto-compabitility.sh"
16
+ "test": "./scripts/check-proto-compabitility.sh",
17
+ "gen:test": "node protoc-gen-bitwise/test/generator.test.js"
17
18
  },
18
19
  "devDependencies": {
19
- "@protobuf-ts/protoc": "^2.8.1",
20
+ "@protobuf-ts/protoc": "^2.11.0",
20
21
  "proto-compatibility-tool": "^1.1.2-20220925163655.commit-25bd040",
21
22
  "typescript": "^5.0.4"
22
23
  },
@@ -28,10 +29,12 @@
28
29
  "protobufjs": "7.2.4"
29
30
  },
30
31
  "files": [
31
- "proto",
32
+ "proto/decentraland",
32
33
  "out-ts",
33
34
  "out-js",
34
- "public"
35
+ "public",
36
+ "protoc-gen-bitwise/*.js",
37
+ "protoc-gen-bitwise/runtime"
35
38
  ],
36
- "commit": "0010e703bec8702bbe2d5e5282c619220c2c1ca6"
39
+ "commit": "e44c721a034709fc47b820be81b5c977f9085ae6"
37
40
  }
@@ -0,0 +1,51 @@
1
+ syntax = "proto3";
2
+
3
+ package decentraland.common;
4
+
5
+ import "google/protobuf/descriptor.proto";
6
+
7
+ // Options for quantizing a float value stored as a uint32 field.
8
+ // The float is clamped to [min, max] and uniformly quantized to N bits;
9
+ // protobuf encodes the resulting uint32 as a varint on the wire.
10
+ // The generator emits a float {Name}Quantized accessor in the partial class.
11
+ message QuantizedFloatOptions {
12
+ float min = 1;
13
+ float max = 2;
14
+ uint32 bits = 3;
15
+ }
16
+
17
+ // Options for quantizing a signed float with a power-law curve, stored as a uint32 field.
18
+ // The value is split into an (bits-1)-bit linear unorm magnitude `u in [0, 1]` plus a sign,
19
+ // and reconstructed as `value = sign * max * u^pow`. Because `u = 0` maps to exactly `0`, zero is
20
+ // representable (unlike the linear quantizer, whose codes straddle zero), and `pow > 1` concentrates
21
+ // resolution near zero — useful for fields like velocity that need fine detail at low magnitudes and
22
+ // only coarse detail near the extremes. The range is symmetric ([-max, max]); there is no `min`.
23
+ // The encoded layout puts the magnitude in the high bits and the sign in the LSB
24
+ // (`(magnitude << 1) | sign`), so the varint cost tracks magnitude, not direction: a small `|value|`
25
+ // of either sign stays in one varint byte. Zero canonicalizes to `0`, so proto3 omits a stopped field.
26
+ // The generator emits a float {Name}Quantized accessor, same as the linear option.
27
+ message QuantizedPowerFloatOptions {
28
+ float max = 1;
29
+ float pow = 2;
30
+ uint32 bits = 3;
31
+ }
32
+
33
+ // Options for bit-packing an integer field into fewer than 32 bits.
34
+ message BitPackedOptions {
35
+ uint32 bits = 1;
36
+ }
37
+
38
+ extend google.protobuf.FieldOptions {
39
+ // Apply to uint32 fields to enable quantized float encoding.
40
+ // Example: uint32 dx = 1 [(decentraland.common.quantized) = { min: -100.0, max: 100.0, bits: 16 }];
41
+ QuantizedFloatOptions quantized = 50001;
42
+
43
+ // Apply to uint32 fields to pack into fewer bits.
44
+ // Example: uint32 entity_id = 4 [(decentraland.common.bit_packed) = { bits: 20 }];
45
+ BitPackedOptions bit_packed = 50002;
46
+
47
+ // Apply to uint32 fields to enable power-law quantized float encoding (sign + magnitude curve).
48
+ // A field carries either `quantized` or `quantized_power`, not both.
49
+ // Example: uint32 velocity_x = 9 [(decentraland.common.quantized_power) = { max: 50.0, pow: 2.0, bits: 8 }];
50
+ QuantizedPowerFloatOptions quantized_power = 50003;
51
+ }
@@ -0,0 +1,164 @@
1
+ syntax = "proto3";
2
+
3
+ package decentraland.common;
4
+
5
+ import "decentraland/common/options.proto";
6
+
7
+ // High-frequency player state messages sent on Channel 1 (unreliable sequenced).
8
+ //
9
+ // Every message below uses the protoc-gen-bitwise annotations to minimise
10
+ // wire size. Wire costs are listed per-message so the trade-offs are clear.
11
+ //
12
+ // Annotation cheat-sheet:
13
+ // [(decentraland.common.quantized) = { min: F, max: F, bits: N }]
14
+ // → stores a float as a uint32 quantized to N bits over [min, max].
15
+ // → protobuf encodes the uint32 as a varint: values ≤ 2^14-1 cost 2 bytes,
16
+ // values ≤ 2^21-1 cost 3 bytes; the generated partial class adds a
17
+ // float {Name}Quantized accessor that encodes/decodes transparently.
18
+ // [(decentraland.common.quantized_power) = { max: F, pow: F, bits: N }]
19
+ // → stores a signed float over [-max, max] as an (N-1)-bit linear unorm
20
+ // magnitude (high bits) plus a sign (LSB), reconstructed as sign·max·u^pow.
21
+ // Zero is exact, and pow>1 buys fine resolution near zero at the cost of
22
+ // coarse near ±max. Putting the sign in the LSB keeps a small |value| of
23
+ // either direction in one varint byte. Same generated float {Name}Quantized
24
+ // accessor as the linear option.
25
+ // [(decentraland.common.bit_packed) = { bits: N }]
26
+ // → documents that this uint32 uses at most N bits; protobuf encodes it
27
+ // as a varint (same savings, no generated accessor needed).
28
+ // (no annotation)
29
+ // → standard protobuf encoding (bool/double/etc. at natural width).
30
+ //
31
+ // Varint byte costs at worst-case (all bits set):
32
+ // ≤ 7 bits → 1 byte (max 127)
33
+ // ≤ 14 bits → 2 bytes (max 16 383)
34
+ // ≤ 21 bits → 3 bytes (max 2 097 151)
35
+ // Proto3 omits fields equal to their default value (0), so average cost is lower.
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // PositionDelta — Δ position relative to last acknowledged full snapshot.
39
+ //
40
+ // Sent every client tick (~10 Hz) on Channel 1.
41
+ //
42
+ // Field | Type | Range | Q bits | Wire worst-case
43
+ // ------------|--------|----------------|--------|-----------------------
44
+ // dx | uint32 | [-100, 100] | 16 | tag 1B + varint 3B = 4B
45
+ // dy | uint32 | [-100, 100] | 16 | tag 1B + varint 3B = 4B
46
+ // dz | uint32 | [-100, 100] | 16 | tag 1B + varint 3B = 4B
47
+ // entity_id | uint32 | [0, 1 048 575] | 20 | tag 1B + varint 3B = 4B
48
+ // sequence | uint32 | [0, 4 095] | 12 | tag 1B + varint 2B = 3B
49
+ // ---------------------------------------------------------------------------
50
+ // Worst-case: 19 B (vs. 20 B raw: 3×float + 2×uint32)
51
+ // Step: dx/dy/dz ≈ 0.003 units
52
+ // ---------------------------------------------------------------------------
53
+ message PositionDelta {
54
+ uint32 dx = 1 [(decentraland.common.quantized) = { min: -100.0, max: 100.0, bits: 16 }];
55
+ uint32 dy = 2 [(decentraland.common.quantized) = { min: -100.0, max: 100.0, bits: 16 }];
56
+ uint32 dz = 3 [(decentraland.common.quantized) = { min: -100.0, max: 100.0, bits: 16 }];
57
+ uint32 entity_id = 4 [(decentraland.common.bit_packed) = { bits: 20 }];
58
+ uint32 sequence = 5 [(decentraland.common.bit_packed) = { bits: 12 }];
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // PlayerInput — client input snapshot for server-side reconciliation.
63
+ //
64
+ // Sent every client frame (~30 Hz) on Channel 1.
65
+ //
66
+ // Field | Type | Range | Q bits | Wire worst-case
67
+ // ------------|--------|----------------|--------|-----------------------
68
+ // move_x | uint32 | [-1, 1] | 8 | tag 1B + varint 2B = 3B
69
+ // move_z | uint32 | [-1, 1] | 8 | tag 1B + varint 2B = 3B
70
+ // yaw | uint32 | [-180, 180] | 12 | tag 1B + varint 2B = 3B
71
+ // buttons | uint32 | bitmask | 8 | tag 1B + varint 2B = 3B
72
+ // sequence | uint32 | [0, 4 095] | 12 | tag 1B + varint 2B = 3B
73
+ // ---------------------------------------------------------------------------
74
+ // Worst-case: 15 B (vs. 20 B raw: 3×float + 2×uint32)
75
+ // Step: move_x/move_z ≈ 0.008; yaw ≈ 0.088°
76
+ // ---------------------------------------------------------------------------
77
+ message PlayerInput {
78
+ // Normalised joystick axes in [-1, 1].
79
+ uint32 move_x = 1 [(decentraland.common.quantized) = { min: -1.0, max: 1.0, bits: 8 }];
80
+ uint32 move_z = 2 [(decentraland.common.quantized) = { min: -1.0, max: 1.0, bits: 8 }];
81
+
82
+ // Horizontal look direction in degrees.
83
+ uint32 yaw = 3 [(decentraland.common.quantized) = { min: -180.0, max: 180.0, bits: 12 }];
84
+
85
+ // Bitmask of active buttons (see ButtonFlags below).
86
+ uint32 buttons = 4 [(decentraland.common.bit_packed) = { bits: 8 }];
87
+
88
+ // Rolling input sequence number used by the server for reconciliation.
89
+ uint32 sequence = 5 [(decentraland.common.bit_packed) = { bits: 12 }];
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // VelocityState — per-axis velocity, demonstrating the power-law quantizer.
94
+ //
95
+ // Velocity needs an exact zero (a stopped avatar must read 0, not a quantization
96
+ // residual, or it drifts) and fine resolution at locomotion speeds, but only
97
+ // coarse resolution near the ±50 m/s extremes. The linear quantizer can give
98
+ // none of these from 8 bits: its codes straddle zero (±0.196) and spend uniform
99
+ // resolution on speeds that never occur. quantized_power solves all three.
100
+ //
101
+ // Field | Type | Range | pow | bits | Wire worst-case
102
+ // -------|--------|------------|-----|------|-----------------------
103
+ // vx | uint32 | [-50, 50] | 2 | 8 | tag 1B + varint 1–2B = 2–3B
104
+ // vy | uint32 | [-50, 50] | 2 | 8 | tag 1B + varint 1–2B = 2–3B
105
+ // vz | uint32 | [-50, 50] | 2 | 8 | tag 1B + varint 1–2B = 2–3B
106
+ // ---------------------------------------------------------------------------
107
+ // Step: ≈ 0.003 m/s near zero, ≈ 0.78 m/s near ±50 (vs. 0.392 uniform linear)
108
+ // Wire: sign in the LSB, so |v| ≲ 12.5 m/s (magnitude code ≤ 63) costs a single
109
+ // varint byte regardless of direction; only faster axes spill to 2 bytes.
110
+ // ---------------------------------------------------------------------------
111
+ message VelocityState {
112
+ uint32 vx = 1 [(decentraland.common.quantized_power) = { max: 50.0, pow: 2.0, bits: 8 }];
113
+ uint32 vy = 2 [(decentraland.common.quantized_power) = { max: 50.0, pow: 2.0, bits: 8 }];
114
+ uint32 vz = 3 [(decentraland.common.quantized_power) = { max: 50.0, pow: 2.0, bits: 8 }];
115
+ }
116
+
117
+ // Bitmask values for PlayerInput.buttons.
118
+ enum ButtonFlags {
119
+ BF_NONE = 0;
120
+ BF_JUMP = 1; // bit 0
121
+ BF_SPRINT = 2; // bit 1
122
+ BF_INTERACT = 4; // bit 2
123
+ BF_EMOTE = 8; // bit 3
124
+ BF_CROUCH = 16; // bit 4
125
+ // bits 5-7 reserved
126
+ }
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // AvatarStateSnapshot — full authoritative state, sent on Channel 0 (reliable)
130
+ // or on resync requests. Demonstrates wider ranges and mixed encodings.
131
+ //
132
+ // Field | Type | Range | Q bits | Wire worst-case
133
+ // -----------------|--------|------------------|--------|-----------------------
134
+ // x | uint32 | [-4096, 4096] | 16 | tag 1B + varint 3B = 4B
135
+ // y | uint32 | [-256, 256] | 14 | tag 1B + varint 2B = 3B
136
+ // z | uint32 | [-4096, 4096] | 16 | tag 1B + varint 3B = 4B
137
+ // pitch | uint32 | [-90, 90] | 10 | tag 1B + varint 2B = 3B
138
+ // yaw | uint32 | [-180, 180] | 12 | tag 1B + varint 2B = 3B
139
+ // entity_id | uint32 | [0, 1 048 575] | 20 | tag 1B + varint 3B = 4B
140
+ // animation_state | uint32 | [0, 63] | 6 | tag 1B + varint 1B = 2B
141
+ // is_grounded | bool | — | — | tag 1B + varint 1B = 2B
142
+ // timestamp | double | — | — | tag 1B + fixed64 8B = 9B
143
+ // ---------------------------------------------------------------------------
144
+ // Worst-case: 34 B (vs. 45 B raw: 5×float + 2×uint32 + bool + double)
145
+ // Step: x/z ≈ 0.125 units; y ≈ 0.031 units; pitch ≈ 0.176°; yaw ≈ 0.088°
146
+ // ---------------------------------------------------------------------------
147
+ message AvatarStateSnapshot {
148
+ // World-space position.
149
+ uint32 x = 1 [(decentraland.common.quantized) = { min: -4096.0, max: 4096.0, bits: 16 }];
150
+ uint32 y = 2 [(decentraland.common.quantized) = { min: -256.0, max: 256.0, bits: 14 }];
151
+ uint32 z = 3 [(decentraland.common.quantized) = { min: -4096.0, max: 4096.0, bits: 16 }];
152
+
153
+ // View angles.
154
+ uint32 pitch = 4 [(decentraland.common.quantized) = { min: -90.0, max: 90.0, bits: 10 }];
155
+ uint32 yaw = 5 [(decentraland.common.quantized) = { min: -180.0, max: 180.0, bits: 12 }];
156
+
157
+ // Identity and animation state.
158
+ uint32 entity_id = 6 [(decentraland.common.bit_packed) = { bits: 20 }];
159
+ uint32 animation_state = 7 [(decentraland.common.bit_packed) = { bits: 6 }];
160
+
161
+ // Un-annotated fields — encoded at their natural width.
162
+ bool is_grounded = 8;
163
+ double timestamp = 9; // server epoch milliseconds
164
+ }
@@ -94,6 +94,14 @@ message PlayerEmote {
94
94
  uint32 incremental_id = 1;
95
95
  string urn = 2;
96
96
  float timestamp = 3;
97
+ optional bool is_stopping = 4; // true means the emote has been stopped in the sender's client
98
+ optional bool is_repeating = 5; // true when it is not the first time the looping animation plays
99
+ optional int32 interaction_id = 6; // identifies an interaction univocaly, established when the start animation is triggered
100
+ optional int32 social_emote_outcome = 7; // -1 means it does not use an outcome animation
101
+ optional bool is_reacting = 8; // to a social emote started by other user
102
+ optional string social_emote_initiator = 9; // wallet address of the user that initiated social emote
103
+ optional string target_avatar = 10; // wallet address of the user whose avatar is the target of a directed emote
104
+ optional uint32 mask = 11; // mask for which bones an animation applies to.
97
105
  }
98
106
 
99
107
  message SceneEmote {
@@ -126,6 +134,9 @@ message ProfileResponse {
126
134
  message Chat {
127
135
  string message = 1;
128
136
  double timestamp = 2;
137
+ // Extension: optional forwarded_from to identify the original sender when
138
+ // messages are forwarded through an SFU
139
+ optional string forwarded_from = 3;
129
140
  }
130
141
 
131
142
  message Scene {
@@ -0,0 +1,79 @@
1
+ syntax = "proto3";
2
+
3
+ package decentraland.pulse;
4
+
5
+ import "decentraland/pulse/pulse_shared.proto";
6
+ import "decentraland/common/options.proto";
7
+
8
+ message HandshakeRequest {
9
+ bytes auth_chain = 1;
10
+ int32 profile_version = 2;
11
+ optional PlayerInitialState initial_state = 3;
12
+ }
13
+
14
+ // Describes the initial state of the player if (re-)connected in the middle of the session
15
+ message PlayerInitialState {
16
+ PlayerState state = 1;
17
+ optional string emote_id = 2;
18
+ optional uint32 emote_duration_ms = 3;
19
+ // Indicates how many milliseconds ago the emote was started
20
+ optional uint32 emote_start_offset_ms = 4;
21
+ // Non-empty realm identifier. Rejected if empty.
22
+ string realm = 5;
23
+ optional int32 emote_mask = 6;
24
+ }
25
+
26
+ // Similarly to the LiveKit pipeline, a peer announces the version of its profile but
27
+ // it only does it when it's changed as it's sent reliably and stored on the server for other peers
28
+ message ProfileVersionAnnouncement {
29
+ int32 version = 1;
30
+ }
31
+
32
+ // Since the server doesn't simulate the scenes state, it trusts the values from the client
33
+ message PlayerStateInput {
34
+ PlayerState state = 1;
35
+ }
36
+
37
+ // Client sends resync request if it has a gap in the known sequences and, thus, can't apply a delta.
38
+ // It's sent reliably to prevent further desynchronization
39
+ message ResyncRequest {
40
+ uint32 subject_id = 1;
41
+ // highest seq the client actually has for this subject
42
+ uint32 known_seq = 2;
43
+ }
44
+
45
+ // Client → Server. Client requests to start an emote.
46
+ message EmoteStart {
47
+ string emote_id = 1;
48
+ optional uint32 duration_ms = 2;
49
+ PlayerState player_state = 3;
50
+ optional int32 mask = 4;
51
+ }
52
+
53
+ // Client → Server. Client requests to stop a looping emote.
54
+ // One-shot emotes are terminated by the server timer.
55
+ message EmoteStop {
56
+ }
57
+
58
+ // Client → Server. Also announces the peer's realm; peers in different realms never see each
59
+ // other. Must be the first gameplay message after handshake. Same-realm re-teleports are valid.
60
+ message TeleportRequest {
61
+ int32 parcel_index = 1;
62
+ uint32 position_x = 2 [(decentraland.common.quantized) = { min: 0, max: 16, bits: 8 }];
63
+ uint32 position_y = 3 [(decentraland.common.quantized) = { min: 0, max: 200, bits: 13 }];
64
+ uint32 position_z = 4 [(decentraland.common.quantized) = { min: 0, max: 16, bits: 8 }];
65
+ // Non-empty realm identifier. Rejected if empty.
66
+ string realm = 5;
67
+ }
68
+
69
+ message ClientMessage {
70
+ oneof message {
71
+ HandshakeRequest handshake = 1;
72
+ PlayerStateInput input = 2;
73
+ ResyncRequest resync = 3;
74
+ ProfileVersionAnnouncement profile_announcement = 4;
75
+ EmoteStart emote_start = 5;
76
+ EmoteStop emote_stop = 6;
77
+ TeleportRequest teleport = 7;
78
+ }
79
+ }