mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
feat: render shards, the objects hidden at a place (kind 3330)
DECK-0003 §3.2 is two containers for one format, not two formats, so a shard reads through the same parser and draws through the same card as a standalone object. SnoShardEvent adds the container and the C-tag coordinate claim and delegates every rule to SnoParser. What is not implemented is opening a bag. A shard published today is an item inside a kind 33330 bag whose payload is AES-256-GCM ciphertext keyed to a cyberspace region, so reading one means §2 coordinates, §7.2 key derivation and §7.4 discovery scanning — the protocol, not a renderer. §7.6 is explicit that a failed decryption is not an error in the bag. A reader for the pre-deck tag form was written and then removed. Every 3330 publicly reachable on a relay predates the deck: 21 events, one pubkey, all inside one minute on 2025-01-16, empty content, geometry in vertices/colors/indices tags. Rendering the corpus showed what the decoder bought — one black triangle and one black dot, two shapes across 21 events, every colour 0,0,0 — against the cyberspace readme saying v1 drafts "are not a valid basis for new implementations". Reverse- engineering a deprecated encoding no current spec describes was not worth that, so it went. The consequence, stated rather than discovered later: no publicly reachable 3330 renders today. The current ones are sealed in bags and the old ones are v1. Note the two unrelated version numbers. The payload's own `v: 1` (DECK-0003 §2) stays supported because the deck requires it — "a reader MUST support both" — and is the SNO format's version, not Cyberspace v1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JwXApJjoZYtkD3sPRWbPNa
This commit is contained in:
co-authored by
Claude Opus 5
parent
7dc3d5caa8
commit
94bc8864d6
@@ -467,7 +467,7 @@ Built in this order, each step verified before the next:
|
||||
angle would otherwise be a half-megabyte cache entry), `SnoObjectCard` /
|
||||
`SnoAvatarCard`, and a new `PlatformImage.toComposeImageBitmap()` beside the
|
||||
existing Coil bridge.
|
||||
4. `amethyst/…/note/types/SnoObject.kt` + `SnoAvatar.kt`, dispatched from
|
||||
4. `amethyst/…/note/types/SnoObject.kt`, `SnoAvatar.kt` + `SnoShard.kt`, dispatched from
|
||||
`NoteCompose` and `ThreadFeedView`, stored addressably/replaceably in
|
||||
`LocalCache`, fetcher registered in both image loaders.
|
||||
|
||||
@@ -514,11 +514,31 @@ are where the risk is, and they are testable without a device.
|
||||
of our code will check the wrong arbiter first.
|
||||
- **D1b — `name`.** Required in §1.1's table, enforced by neither reference
|
||||
implementation (§2.6). Treat as optional with a fallback.
|
||||
- **D2 — `kind 3330` bag items.** Recommend **defer**, on `CYBERSPACE_V2.md` §7.6
|
||||
rather than on the DECK: a current shard is an item inside an AES-256-GCM bag keyed
|
||||
to a Cyberspace region, so reading one means implementing §2 coordinates, §7.2 key
|
||||
derivation and §7.4 discovery. That is the protocol, not a renderer. The two 3330s
|
||||
sitting on relays are v1-era leftovers.
|
||||
- **D2 — `kind 3330` bag items. Settled: the container ships, the bag does not.**
|
||||
`SnoShardEvent` reads a shard handed over directly — quoted in a note, or fetched
|
||||
by id — through the same parser and the same card as a standalone object, because
|
||||
§3.2 is two containers for one format rather than two formats. What is *not*
|
||||
implemented is opening a `kind 33330` bag: its payload is AES-256-GCM ciphertext
|
||||
keyed to a Cyberspace region, so reading one means §2 coordinates, §7.2 key
|
||||
derivation and §7.4 discovery scanning. That is the protocol, not a renderer, and
|
||||
§7.6 is explicit that a failed decryption "MUST NOT be treated as an error in the
|
||||
bag".
|
||||
|
||||
**The v1 tag form was built and then removed, deliberately.** Every `kind 3330`
|
||||
publicly reachable on a relay predates the deck: 21 events, one pubkey, all inside
|
||||
one minute on 2025-01-16, empty content, geometry in `vertices`/`colors`/`indices`
|
||||
tags as flat comma-separated decimals. A reader for it took about 200 lines and
|
||||
worked — and then rendering the whole corpus showed what it buys: **one black
|
||||
triangle and one black dot**, two distinct shapes between the 21 events, every
|
||||
colour `0,0,0`. The cyberspace readme settles it: *"Cyberspace v1 drafts are
|
||||
deprecated and archived. They are not a valid basis for new implementations."* A
|
||||
reverse-engineered decoder for a deprecated encoding that no current spec
|
||||
describes is a maintenance liability priced against two black shapes, so it went.
|
||||
|
||||
The consequence, stated plainly: **no publicly reachable 3330 renders today.** The
|
||||
current ones are sealed in bags and the old ones are v1. The code is right for the
|
||||
shards the current client writes, and is exercised by tests rather than by the
|
||||
network.
|
||||
- **D3 — publishing.** Recommend **read-only first**. Appendix B.4 is right that the
|
||||
deliverable is software that emits the format, but a modeling tool is a screen, not
|
||||
a feature, and the reader is what makes objects visible at all. Build the writer
|
||||
|
||||
@@ -215,6 +215,7 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderRootNappletEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderRootSiteEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderSnoAvatar
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderSnoObject
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderSnoShard
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderSoftwareApplication
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderSoftwareAsset
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderSoftwareRelease
|
||||
@@ -261,6 +262,7 @@ import com.vitorpamplona.quartz.buzz.notifications.MemberAddedNotificationEvent
|
||||
import com.vitorpamplona.quartz.buzz.stream.StreamMessageV2Event
|
||||
import com.vitorpamplona.quartz.cyberspace.deck0003Sno.SnoAvatarEvent
|
||||
import com.vitorpamplona.quartz.cyberspace.deck0003Sno.SnoObjectEvent
|
||||
import com.vitorpamplona.quartz.cyberspace.deck0003Sno.SnoShardEvent
|
||||
import com.vitorpamplona.quartz.experimental.agora.FundraiserEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.proficiency.AttestorProficiencyEvent
|
||||
@@ -1379,6 +1381,10 @@ private fun RenderNoteRow(
|
||||
RenderSnoAvatar(baseNote)
|
||||
}
|
||||
|
||||
is SnoShardEvent -> {
|
||||
RenderSnoShard(baseNote)
|
||||
}
|
||||
|
||||
is ChessGameEvent -> {
|
||||
RenderChessGame(
|
||||
baseNote,
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.types
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.sno.ui.SnoObjectViewer
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.SnoObjectCard
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.SnoObjectUnreadableCard
|
||||
import com.vitorpamplona.quartz.cyberspace.deck0003Sno.SnoResult
|
||||
import com.vitorpamplona.quartz.cyberspace.deck0003Sno.SnoShardEvent
|
||||
|
||||
private val VIEWER_HEIGHT = 360.dp
|
||||
|
||||
/**
|
||||
* Entry for a shard: an object someone hid at a place (DECK-0003 §3.2, kind
|
||||
* 3330).
|
||||
*
|
||||
* The same payload and the same rules as a standalone object — only the
|
||||
* container differs — so this renders through the same card. What it does not
|
||||
* do is open a bag: a shard still sealed in its `kind 33330` bag is ciphertext
|
||||
* until its region key is derived, which is the cyberspace protocol rather than
|
||||
* a renderer.
|
||||
*/
|
||||
@Composable
|
||||
fun RenderSnoShard(baseNote: Note) {
|
||||
val noteEvent = baseNote.event as? SnoShardEvent ?: return
|
||||
val parsed = remember(noteEvent) { noteEvent.shard() }
|
||||
|
||||
when (parsed) {
|
||||
is SnoResult.Invalid -> SnoObjectUnreadableCard(parsed.rule)
|
||||
is SnoResult.Valid -> {
|
||||
var turning by remember(noteEvent) { mutableStateOf(false) }
|
||||
|
||||
SnoObjectCard(
|
||||
payload = parsed.payload,
|
||||
eventId = noteEvent.id,
|
||||
onClick = { turning = true },
|
||||
)
|
||||
|
||||
if (turning) {
|
||||
Dialog(onDismissRequest = { turning = false }) {
|
||||
SnoObjectViewer(
|
||||
payload = parsed.payload,
|
||||
eventId = noteEvent.id,
|
||||
contentDescription = parsed.payload.name.ifBlank { null },
|
||||
modifier = Modifier.fillMaxWidth().height(VIEWER_HEIGHT),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
@@ -227,6 +227,7 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderRoadEventReport
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderRootSiteEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderSnoAvatar
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderSnoObject
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderSnoShard
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderSoftwareApplication
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderSoftwareAsset
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderSoftwareRelease
|
||||
@@ -268,6 +269,7 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.selectedNote
|
||||
import com.vitorpamplona.quartz.cyberspace.deck0003Sno.SnoAvatarEvent
|
||||
import com.vitorpamplona.quartz.cyberspace.deck0003Sno.SnoObjectEvent
|
||||
import com.vitorpamplona.quartz.cyberspace.deck0003Sno.SnoShardEvent
|
||||
import com.vitorpamplona.quartz.experimental.agora.FundraiserEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.proficiency.AttestorProficiencyEvent
|
||||
@@ -1072,6 +1074,8 @@ private fun FullBleedNoteCompose(
|
||||
RenderSnoObject(baseNote)
|
||||
} else if (noteEvent is SnoAvatarEvent) {
|
||||
RenderSnoAvatar(baseNote)
|
||||
} else if (noteEvent is SnoShardEvent) {
|
||||
RenderSnoShard(baseNote)
|
||||
} else if (noteEvent is Ps1SaveEvent) {
|
||||
RenderPs1Save(baseNote)
|
||||
} else if (noteEvent is GeocacheListingEvent) {
|
||||
|
||||
+2
@@ -140,6 +140,7 @@ import com.vitorpamplona.quartz.concord.cord03Channels.ConcordChannelId
|
||||
import com.vitorpamplona.quartz.concord.cord03Channels.ConcordChatEditEvent
|
||||
import com.vitorpamplona.quartz.cyberspace.deck0003Sno.SnoAvatarEvent
|
||||
import com.vitorpamplona.quartz.cyberspace.deck0003Sno.SnoObjectEvent
|
||||
import com.vitorpamplona.quartz.cyberspace.deck0003Sno.SnoShardEvent
|
||||
import com.vitorpamplona.quartz.experimental.agora.FundraiserEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.proficiency.AttestorProficiencyEvent
|
||||
@@ -3958,6 +3959,7 @@ open class EventCache :
|
||||
is GitPullRequestEvent,
|
||||
is GitPullRequestUpdateEvent,
|
||||
is GitStatusEvent,
|
||||
is SnoShardEvent,
|
||||
is ChessGameEvent,
|
||||
is JesterEvent,
|
||||
is HighlightEvent,
|
||||
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.cyberspace.deck0003Sno
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
|
||||
/**
|
||||
* DECK-0003 §3.2 — an object hidden at a place: a `kind 3330` bag item, whose
|
||||
* `content` is the payload of §1 exactly as a `kind 33331` object carries it.
|
||||
*
|
||||
* Regular, and deliberately so. An item in a bag is a thing someone hid at a
|
||||
* place and someone else found there; it must be exactly what it was when it
|
||||
* was found, and its id must keep meaning what it meant. The same payload
|
||||
* therefore travels under two kinds according to what is being done with it:
|
||||
* 33331 for an object its author is still working on, 3330 for one that has
|
||||
* been put somewhere. That is two containers for one format, not two ways of
|
||||
* writing the format — which is why this class adds a container and a
|
||||
* coordinate and delegates every rule to [SnoParser].
|
||||
*
|
||||
* **Most shards are not reachable, and that is by design.** A shard is an item
|
||||
* inside a `kind 33330` bag (`CYBERSPACE_V2.md` §7.6) whose payload is
|
||||
* AES-256-GCM ciphertext keyed to the region it was hidden in, so finding one
|
||||
* means deriving that region's key: the coordinate system of §2, the key
|
||||
* derivation of §7.2 and the discovery scanning of §7.4. Amethyst implements
|
||||
* none of that and a reader without the key sees base64 and nothing else — §7.6
|
||||
* is explicit that a failed decryption "MUST NOT be treated as an error in the
|
||||
* bag". What this class reads is a shard handed over directly: quoted in a
|
||||
* note, or fetched by id.
|
||||
*
|
||||
* **An item MAY be unsigned** (§7.6, and §6 of the deck), in which case its
|
||||
* `pubkey` is a claim and a client MUST NOT present it as verified authorship.
|
||||
* Placement is attributable to the bag's author; authorship of an item's
|
||||
* content only to the item's own pubkey, and only when the item is signed.
|
||||
*/
|
||||
@Immutable
|
||||
class SnoShardEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
fun shard(fetchedPalette: SnoPalette? = null): SnoResult = SnoParser.parse(content, fetchedPalette)
|
||||
|
||||
fun shardOrNull(fetchedPalette: SnoPalette? = null): SnoPayload? = shard(fetchedPalette).payloadOrNull()
|
||||
|
||||
/**
|
||||
* The exact cyberspace coordinate this shard claims, if it carries one.
|
||||
*
|
||||
* §7.6: an item MAY carry a `C` tag, which lets a client render it at a
|
||||
* point rather than somewhere in the region. Where a shard came out of a
|
||||
* bag that coordinate MUST lie inside the region the bag is encrypted to;
|
||||
* this class has no bag to check it against, so it only reports the claim.
|
||||
*/
|
||||
fun coordinate(): String? = tags.firstOrNull { it.size > 1 && it[0] == "C" }?.get(1)
|
||||
|
||||
companion object {
|
||||
const val KIND = 3330
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,7 @@ import com.vitorpamplona.quartz.concord.cord05Invites.ConcordInviteListEvent
|
||||
import com.vitorpamplona.quartz.concord.cord05Invites.bundle.ConcordInviteBundleEvent
|
||||
import com.vitorpamplona.quartz.cyberspace.deck0003Sno.SnoAvatarEvent
|
||||
import com.vitorpamplona.quartz.cyberspace.deck0003Sno.SnoObjectEvent
|
||||
import com.vitorpamplona.quartz.cyberspace.deck0003Sno.SnoShardEvent
|
||||
import com.vitorpamplona.quartz.experimental.agora.FundraiserEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.proficiency.AttestorProficiencyEvent
|
||||
@@ -651,6 +652,7 @@ class EventFactory {
|
||||
GenericRepostEvent.KIND -> GenericRepostEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
SnoObjectEvent.KIND -> SnoObjectEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
SnoAvatarEvent.KIND -> SnoAvatarEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
SnoShardEvent.KIND -> SnoShardEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GeocacheListingEvent.KIND -> GeocacheListingEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GeocacheFoundLogEvent.KIND -> GeocacheFoundLogEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GeocacheVerificationEvent.KIND -> GeocacheVerificationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.cyberspace.deck0003Sno
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* A `kind 3330` shard reads in either encoding, and the rules of §1 still apply
|
||||
* to the one the deck specifies.
|
||||
*/
|
||||
class SnoShardEventTest {
|
||||
private fun shard(
|
||||
content: String = "",
|
||||
tags: Array<Array<String>> = emptyArray(),
|
||||
) = SnoShardEvent("aa".repeat(32), "11".repeat(32), 0L, tags, content, "22".repeat(64))
|
||||
|
||||
private val payloadJson =
|
||||
"""{"v":2,"name":"hidden","unit":0,"mode":"solid","vertices":[[0,0,0],[2,0,0],[1,0,2],[1,2,1]],"colors":[238,235,239,225],"faces":[[0,1,2]]}"""
|
||||
|
||||
@Test
|
||||
fun aShardCarriesThePayloadInContent() {
|
||||
val payload = shard(content = payloadJson).shardOrNull()
|
||||
assertNotNull(payload)
|
||||
assertEquals("hidden", payload.name)
|
||||
assertEquals(2, payload.version)
|
||||
assertEquals(4, payload.vertexCount)
|
||||
assertEquals(1, payload.faceCount)
|
||||
assertEquals(0xFFFF0000.toInt(), payload.colors[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aShardIsHeldToTheSameRulesAsAnObject() {
|
||||
// The container changed; §1.9 did not. This is one format in two
|
||||
// containers, not two ways of writing the format.
|
||||
val result = shard(content = """{"v":2,"name":"x","unit":0,"mode":"wireframe","vertices":[],"colors":[],"faces":[]}""").shard()
|
||||
assertTrue(result is SnoResult.Invalid)
|
||||
assertEquals("4", result.rule)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun anEmptyShardIsNotAShape() {
|
||||
assertTrue(shard().shard() is SnoResult.Invalid)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun theCoordinateIsReadButIsOnlyAClaim() {
|
||||
val coordinate = "3d5ffa91f5c8c13d0bc82ecfe2e546020b3d51763333589829c9c3fdc24fe74a"
|
||||
val withC = shard(content = payloadJson, tags = arrayOf(arrayOf("C", coordinate)))
|
||||
assertEquals(coordinate, withC.coordinate())
|
||||
assertNull(shard(content = payloadJson).coordinate())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user