Skip to content

Commit 7c6ee29

Browse files
committed
feat: add highlight_reel (Sticker field 11), make paintwear nullable, add csfloat test vectors
1 parent 5a01f2d commit 7c6ee29

4 files changed

Lines changed: 132 additions & 5 deletions

File tree

src/InspectLink.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ final class InspectLink
3232
*/
3333
public static function serialize(ItemPreviewData $data): string
3434
{
35-
if ($data->paintwear < 0.0 || $data->paintwear > 1.0) {
35+
if ($data->paintwear !== null && ($data->paintwear < 0.0 || $data->paintwear > 1.0)) {
3636
throw new \InvalidArgumentException(
3737
sprintf('paintwear must be in [0.0, 1.0], got %f', $data->paintwear),
3838
);
@@ -216,6 +216,10 @@ private static function encodeSticker(Sticker $s): string
216216

217217
$w->writeUint32(10, $s->pattern);
218218

219+
if ($s->highlightReel !== null) {
220+
$w->writeUint32(11, $s->highlightReel);
221+
}
222+
219223
return $w->toBytes();
220224
}
221225

@@ -236,6 +240,7 @@ private static function decodeSticker(string $data): Sticker
236240
8 => $s->offsetY = (float) unpack('f', $f['value'])[1],
237241
9 => $s->offsetZ = (float) unpack('f', $f['value'])[1],
238242
10 => $s->pattern = $f['value'],
243+
11 => $s->highlightReel = $f['value'],
239244
default => null,
240245
};
241246
}
@@ -257,9 +262,11 @@ private static function encodeItem(ItemPreviewData $item): string
257262
$w->writeUint32(5, $item->rarity);
258263
$w->writeUint32(6, $item->quality);
259264

260-
// paintwear: float32 reinterpreted as uint32 varint
261-
$pwUint32 = self::float32ToUint32($item->paintwear);
262-
$w->writeUint32(7, $pwUint32);
265+
// paintwear: float32 reinterpreted as uint32 varint (only written if set)
266+
if ($item->paintwear !== null) {
267+
$pwUint32 = self::float32ToUint32($item->paintwear);
268+
$w->writeUint32(7, $pwUint32);
269+
}
263270

264271
$w->writeUint32(8, $item->paintseed);
265272
$w->writeUint32(9, $item->killeaterscoretype);

src/ItemPreviewData.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function __construct(
2828
public int $paintindex = 0,
2929
public int $rarity = 0,
3030
public int $quality = 0,
31-
public float $paintwear = 0.0,
31+
public ?float $paintwear = null,
3232
public int $paintseed = 0,
3333
public int $killeaterscoretype = 0,
3434
public int $killeatervalue = 0,

src/Sticker.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ public function __construct(
2323
public ?float $offsetY = null,
2424
public ?float $offsetZ = null,
2525
public int $pattern = 0,
26+
public ?int $highlightReel = null,
2627
) {}
2728
}

tests/InspectLinkTest.php

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,4 +451,123 @@ public function testSerializeCustomnameMaxLength(): void
451451
$result = InspectLink::serialize(new ItemPreviewData(customname: str_repeat('A', 100)));
452452
$this->assertStringStartsWith('00', $result);
453453
}
454+
455+
// -----------------------------------------------------------------------
456+
// New test vectors from csfloat/cs-inspect-serializer gen.test.ts
457+
// -----------------------------------------------------------------------
458+
459+
/** CSFloat vector A — no stickers, paintwear ≈ 0.6337 */
460+
private const CSFLOAT_A = '00180720DA03280638FBEE88F90340B2026BC03C96';
461+
462+
/** CSFloat vector B — 4 stickers id=76 each, paintwear ≈ 0.99 */
463+
private const CSFLOAT_B = '00180720C80A280638A4E1F5FB03409A0562040800104C62040801104C62040802104C62040803104C6D4F5E30';
464+
465+
/** CSFloat vector C — keychain item (defindex=1355), highlight_reel=345 */
466+
private const CSFLOAT_C = 'A2B2A2BA69A882A28AA192AECAA2D2B700A3A5AAA2B286FA7BA0D684BE72';
467+
468+
public function testCsfloatA_Defindex(): void
469+
{
470+
$this->assertSame(7, InspectLink::deserialize(self::CSFLOAT_A)->defindex);
471+
}
472+
473+
public function testCsfloatA_Paintindex(): void
474+
{
475+
$this->assertSame(474, InspectLink::deserialize(self::CSFLOAT_A)->paintindex);
476+
}
477+
478+
public function testCsfloatA_Paintseed(): void
479+
{
480+
$this->assertSame(306, InspectLink::deserialize(self::CSFLOAT_A)->paintseed);
481+
}
482+
483+
public function testCsfloatA_Rarity(): void
484+
{
485+
$this->assertSame(6, InspectLink::deserialize(self::CSFLOAT_A)->rarity);
486+
}
487+
488+
public function testCsfloatA_Paintwear(): void
489+
{
490+
$item = InspectLink::deserialize(self::CSFLOAT_A);
491+
$this->assertNotNull($item->paintwear);
492+
$this->assertEqualsWithDelta(0.6337, $item->paintwear, 0.001);
493+
}
494+
495+
public function testCsfloatB_StickerCount(): void
496+
{
497+
$this->assertCount(4, InspectLink::deserialize(self::CSFLOAT_B)->stickers);
498+
}
499+
500+
public function testCsfloatB_StickerIds(): void
501+
{
502+
$stickers = InspectLink::deserialize(self::CSFLOAT_B)->stickers;
503+
foreach ($stickers as $s) {
504+
$this->assertSame(76, $s->stickerId);
505+
}
506+
}
507+
508+
public function testCsfloatB_Paintindex(): void
509+
{
510+
$this->assertSame(1352, InspectLink::deserialize(self::CSFLOAT_B)->paintindex);
511+
}
512+
513+
public function testCsfloatB_Paintwear(): void
514+
{
515+
$item = InspectLink::deserialize(self::CSFLOAT_B);
516+
$this->assertNotNull($item->paintwear);
517+
$this->assertEqualsWithDelta(0.99, $item->paintwear, 0.001);
518+
}
519+
520+
public function testCsfloatC_Defindex(): void
521+
{
522+
$this->assertSame(1355, InspectLink::deserialize(self::CSFLOAT_C)->defindex);
523+
}
524+
525+
public function testCsfloatC_Quality(): void
526+
{
527+
$this->assertSame(12, InspectLink::deserialize(self::CSFLOAT_C)->quality);
528+
}
529+
530+
public function testCsfloatC_KeychainHighlightReel(): void
531+
{
532+
$keychains = InspectLink::deserialize(self::CSFLOAT_C)->keychains;
533+
$this->assertCount(1, $keychains);
534+
$this->assertSame(345, $keychains[0]->highlightReel);
535+
}
536+
537+
public function testCsfloatC_NoPaintwear(): void
538+
{
539+
// keychain items have no wear — field 7 not present in proto
540+
$this->assertNull(InspectLink::deserialize(self::CSFLOAT_C)->paintwear);
541+
}
542+
543+
// -----------------------------------------------------------------------
544+
// Roundtrip: highlight_reel and nullable paintwear
545+
// -----------------------------------------------------------------------
546+
547+
public function testRoundtrip_HighlightReel(): void
548+
{
549+
$data = new ItemPreviewData(
550+
defindex: 7,
551+
keychains: [new Sticker(slot: 0, stickerId: 36, highlightReel: 345)],
552+
);
553+
$result = InspectLink::deserialize(InspectLink::serialize($data));
554+
$this->assertCount(1, $result->keychains);
555+
$this->assertSame(345, $result->keychains[0]->highlightReel);
556+
}
557+
558+
public function testRoundtrip_NullPaintwear(): void
559+
{
560+
$data = new ItemPreviewData(defindex: 7, paintwear: null);
561+
$result = InspectLink::deserialize(InspectLink::serialize($data));
562+
$this->assertNull($result->paintwear);
563+
}
564+
565+
public function testSerialize_NullPaintwearProducesFewerBytes(): void
566+
{
567+
// An item with null paintwear omits field 7 entirely; a non-zero float value writes it.
568+
// (0.0 also omits field 7 due to proto3 default-value skipping, so compare against 0.5.)
569+
$withNull = InspectLink::serialize(new ItemPreviewData(defindex: 7, paintwear: null));
570+
$withFloat = InspectLink::serialize(new ItemPreviewData(defindex: 7, paintwear: 0.5));
571+
$this->assertLessThan(strlen($withFloat), strlen($withNull));
572+
}
454573
}

0 commit comments

Comments
 (0)