SOURCE / PINNED RELEASE
Made of little things.
Black Site
- Release
- 666bc7c74ebd…
- Author-recorded commit
- 05e95af79676…
- License
- LICENSE
- Author’s source reference
- nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-d2225ddc391
Archive hash verified: 1544e072e245c4a8…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
# Remixes and lineage (v1)
A player can remix any published [map](map-event.md) (kind `35858`) or [campaign](campaign-format.md) (kind `35859`). A remix is a new event under the remixer's own address, and it keeps credit for every map or campaign it came from. Its lineage tags follow the [NIP-22](https://github.com/nostr-protocol/nips/blob/master/22.md) pattern:
- **Uppercase** tags (`A`, `E`, `K`, `P`) point at the **root**: the original at the top of the remix tree.
- **Lowercase** tags (`a`, `e`, `k`, `p`) point at the **parent**: the event this remix was made from.
- Every author in the lineage gets a `p` tag.
| Piece | Where |
|---|---|
| Reference code | [`lib/lineage.mjs`](lib/lineage.mjs): `remixTags`, `parseLineage`. `mapEventTemplate` and `campaignEventTemplate` take `{ remixOf, remixRelay }`, and `parseMapEvent` and `parseCampaignEvent` return `lineage` |
| Tests | `tests/lineage.test.mjs` in the game's repository |
## Tags
Take three maps: Alice's original `outpost`, Bob's `outpost-night` remixed from it, and Carol's `outpost-storm` remixed from Bob's. Carol's event carries:
```jsonc
["A", "35858:<alice>:outpost", "wss://relay.example.com"], // REQUIRED: root address, with a relay hint
["E", "<id of the alice event remixed first>", "wss://…", "<alice>"], // root event id at that time
["K", "35858"], // root kind
["P", "<alice>"], // REQUIRED: root author
["a", "35858:<bob>:outpost-night", "wss://relay.example.com"], // REQUIRED: parent address, with a relay hint
["e", "<id of the bob event Carol remixed>", "wss://…", "<bob>"], // the exact parent version remixed
["k", "35858"], // parent kind
["p", "<bob>"], // REQUIRED: parent author, first
["p", "<alice>"] // every other author in the lineage
```
- Bob's own event (a remix of the original) has `A` and `a` both pointing at Alice's `outpost`.
- A remix always has the **same kind** as its parent: maps remix maps, and campaigns remix campaigns.
- Lineage `A` and `a` tags are the ones whose address has the event's own kind. A campaign's other `a` tags (kind `35858`, one per stage map) are only for discovery and never count as a parent.
- An original has no lineage tags.
### Rules
| Tag | Rule |
|---|---|
| `A`, `a` | Exactly one of each, or neither. Each holds `<kind>:<pubkey>:<d>` with the event's own kind. Neither may be the event's own address |
| `E`, `e` | Optional, but editors should add them. They pin the exact version that was remixed, because an addressable event can change after the remix is made. A 4th value, if present, must be the author of that address |
| `K`, `k` | Optional. If present, they must equal the event's kind |
| `P` | Required in a remix. It must be the author in the `A` address |
| `p` | Required in a remix, and must include the author in the `a` address. The parent author comes first, then the parent's own `p` tags in order, then the root author. Duplicates are dropped. Lineages longer than 50 authors keep the nearest 49 plus the root author |
`parseMapEvent` and `parseCampaignEvent` reject an event whose lineage tags break these rules, so credit can't be quietly dropped. Each returns `lineage: null` for an original, or:
```jsonc
{
"root": { "address": "35858:<alice>:outpost", "pubkey": "<alice>", "id": "outpost", "eventId": "…" | null, "relay": "wss://…" | null },
"parent": { "address": "35858:<bob>:outpost-night", "pubkey": "<bob>", "id": "outpost-night", "eventId": "…" | null, "relay": "wss://…" | null },
"authors": ["<bob>", "<alice>"]
}
```
## Making a remix (editor)
1. Load the parent event, keeping the **signed event** (it has the `id` the `e` tag needs) and the relay it came from.
2. Load the parent's map JSON or campaign JSON and let the player edit it.
3. Give the remix its own `d` (the map JSON `id` for a map). Another author may keep the parent's id, because the address includes the pubkey. Remixing **your own** map or campaign needs a new id. Otherwise you'd overwrite the parent, and the parser rejects an event that remixes itself.
4. Publish as usual, passing the parent:
- map: `mapEventTemplate(map, upload, { remixOf: parentEvent, remixRelay })`
- campaign: `campaignEventTemplate(id, campaign, { remixOf: parentEvent, remixRelay })`
Both call `remixTags(parentEvent, { relay })`. It copies the root (`A`, `E`, `P`) and the authors from the parent's own lineage, or uses the parent as the root when the parent is an original.
5. **Keep the lineage on updates.** Republishing the same `d` must carry the same lineage tags, so editors should store the parent event (or the tags) with the draft. The lineage records where the remix came from, and editing the remix later doesn't change that.
Remixing a built-in map or the built-in campaign adds no lineage tags, because they aren't Nostr events. Editors may credit them in `content` (maps) or `desc` (campaigns).
## Reading lineage (game and browsers)
- **Show credit**: "Remix of *outpost-night* by Bob, original *outpost* by Alice". Look up the parent and root with `{ kinds: [kind], authors: [pubkey], '#d': [id] }`, using the relay hints. If either is gone (updated, deleted or unreachable), still show the credit from the tags, using the address and the author's profile name.
- **Direct remixes** of an event: `{ kinds: [kind], '#a': [address] }`.
- **The whole remix tree** under an original: `{ kinds: [kind], '#A': [address] }`. The original itself isn't in the results, so query it separately.
- **Everything an author inspired**: `{ kinds: [35858, 35859], '#p': [pubkey] }`.
- **What changed**: when `e` is present, fetch that exact parent version (`ids: [e]`) to diff against it, because the parent's address may now point at a newer version.
For campaigns, `{ kinds: [35859], '#a': ['35858:…'] }` still finds every campaign that uses a map. Only `a` tags of kind `35859` are lineage.
Lineage is untrusted metadata. Show titles and names from it as escaped plain text. It's credit, not proof: anyone can tag a parent they never remixed. A client may compare the remix with the `e` version before showing a "remixed from" badge.
