{
"id": "net.anisota.chronicle.walk",
"defs": {
"main": {
"key": "tid",
"type": "record",
"record": {
"type": "object",
"required": [
"status",
"signature",
"createdAt"
],
"properties": {
"light": {
"ref": "#walkLight",
"type": "ref"
},
"status": {
"enum": [
"active",
"completed"
],
"type": "string",
"description": "Current walk status. A walk is 'active' while open and 'completed' once it closes; there is no 'abandoned' state because a walk never fails."
},
"closedBy": {
"type": "string",
"description": "How the walk closed. Known values: 'user' (manual 'that's enough for now' tap), 'auto' (idle auto-close after the inactivity window), and 'lamp' (light depleted). Free string with no enum so future close reasons need no lexicon change. Unset while the walk is active."
},
"duration": {
"type": "integer",
"minimum": 0,
"description": "Total walk duration in minutes"
},
"findings": {
"ref": "#walkFindings",
"type": "ref"
},
"createdAt": {
"type": "string",
"format": "datetime",
"description": "When the record was created"
},
"heartbeat": {
"ref": "#walkHeartbeat",
"type": "ref"
},
"signature": {
"ref": "#chronicleSignature",
"type": "ref"
},
"startedAt": {
"type": "string",
"format": "datetime",
"description": "When the walk started (first engagement)"
},
"updatedAt": {
"type": "string",
"format": "datetime",
"description": "When the record was last updated"
},
"campsCount": {
"type": "integer",
"minimum": 0,
"description": "Number of camps made during the walk"
},
"conditions": {
"ref": "#walkConditions",
"type": "ref"
},
"completedAt": {
"type": "string",
"format": "datetime",
"description": "When the walk ended. For idle auto-closes this is the last tracked activity, clamped to be no earlier than startedAt."
},
"restDuration": {
"type": "integer",
"minimum": 0,
"description": "Total camp/rest time in minutes"
},
"activeDuration": {
"type": "integer",
"minimum": 0,
"description": "Active exploring time in minutes (excludes camp/rest time)"
},
"elsewhereActivity": {
"ref": "#walkElsewhereActivity",
"type": "ref"
}
}
},
"description": "Walk record. Created with status 'active' at first engagement (first tracked action on the feed), kept current with putRecord, and updated to 'completed' when the walk closes (idle auto-close, light depletion, or a manual wrap-up). rkey is a TID. Walks are the everyday unit and are never numbered; identity is the record key and display identity is the walk's time. Tracks the full lifecycle of a walk including camps, gathered findings, light consumption, and conditions."
},
"walkLight": {
"type": "object",
"properties": {
"atEnd": {
"type": "integer",
"minimum": 0,
"description": "Light level when the walk ended (in minutes)"
},
"status": {
"enum": [
"bright",
"steady",
"dim",
"flickering",
"dying",
"fullCharge",
"charged",
"low",
"critical",
"dead"
],
"type": "string",
"description": "Light status at start of the walk"
},
"atStart": {
"type": "integer",
"minimum": 0,
"description": "Light level when the walk started (in minutes)"
},
"consumed": {
"type": "integer",
"minimum": 0,
"description": "Total light consumed during the walk"
},
"drainRate": {
"type": "integer",
"description": "Effective drain rate as a percentage (80 = 0.8x drain). Multiply by 0.01 to get the float multiplier."
},
"regenerated": {
"type": "integer",
"minimum": 0,
"description": "Total light regenerated during the walk's camps"
}
},
"description": "Light resource tracking for the walk"
},
"walkFindings": {
"type": "object",
"properties": {
"xpGained": {
"type": "integer",
"minimum": 0,
"description": "XP gained from the walk"
},
"likesSent": {
"type": "integer",
"minimum": 0,
"description": "Likes sent during the walk"
},
"mothsSeen": {
"type": "integer",
"minimum": 0,
"description": "Total moth encounters (caught + escaped)"
},
"postsRead": {
"type": "integer",
"minimum": 0,
"description": "Posts read during the walk"
},
"repostsSent": {
"type": "integer",
"minimum": 0,
"description": "Reposts sent during the walk"
},
"postsCreated": {
"type": "integer",
"minimum": 0,
"description": "Posts created during the walk"
},
"itemsCollected": {
"type": "integer",
"minimum": 0,
"description": "Number of items collected"
},
"specimensFound": {
"type": "integer",
"minimum": 0,
"description": "Number of specimens successfully caught"
}
},
"description": "What was found/gathered during the walk"
},
"walkHeartbeat": {
"type": "object",
"properties": {
"light": {
"ref": "#heartbeatLight",
"type": "ref"
},
"progress": {
"type": "array",
"items": {
"ref": "#deviceProgress",
"type": "ref"
},
"description": "Per-device progress buckets, one entry per device that has contributed to this walk. In-app totals are the SUM across buckets, not a max-merge: per-device buckets exist deliberately so multi-device in-app activity is never undercounted (an undercount would falsely inflate the elsewhere figure)."
},
"updatedAt": {
"type": "string",
"format": "datetime",
"description": "When this heartbeat was written"
},
"lastActivityAt": {
"type": "string",
"format": "datetime",
"description": "Timestamp of the last tracked activity. Drives the idle clock and is max-merged across devices (heartbeats themselves fire on a timer, so idle detection must key on this field, not updatedAt)."
}
},
"description": "Periodic snapshot of walk progress for cross-device sync"
},
"deviceProgress": {
"type": "object",
"properties": {
"counts": {
"ref": "#heartbeatProgress",
"type": "ref"
},
"deviceId": {
"type": "string",
"description": "Random per-install device id from userScopedStorage. No fingerprinting."
}
},
"description": "Per-device progress bucket. Buckets are summed across devices (not max-merged) so in-app activity spread over multiple devices is fully counted."
},
"heartbeatLight": {
"type": "object",
"properties": {
"current": {
"type": "integer",
"description": "Current light level"
},
"drainRate": {
"type": "integer",
"description": "Current drain rate as percentage"
},
"drainActive": {
"type": "boolean",
"description": "Whether drain is actively ticking (true while exploring, false while resting or camping). Walks have no frozen state."
}
},
"description": "Light state at heartbeat time"
},
"walkConditions": {
"type": "object",
"properties": {
"biome": {
"type": "string",
"description": "Forest biome type"
},
"weather": {
"enum": [
"clear",
"overcast",
"rainy",
"stormy",
"foggy"
],
"type": "string",
"description": "Weather condition during the walk"
},
"moonPhase": {
"enum": [
"full_moon",
"new_moon"
],
"type": "string",
"description": "Moon phase condition (nighttime only)"
},
"userState": {
"enum": [
"well_rested",
"tired",
"exhausted",
"first_expedition"
],
"type": "string",
"description": "User fatigue state at walk start"
},
"lightLevel": {
"enum": [
"dawn",
"morning",
"midday",
"afternoon",
"dusk",
"night",
"deepnight"
],
"type": "string",
"description": "Time-of-day light condition at walk start"
}
},
"description": "Environmental conditions during the walk"
},
"heartbeatProgress": {
"type": "object",
"properties": {
"xpGained": {
"type": "integer",
"minimum": 0
},
"likesSent": {
"type": "integer",
"minimum": 0
},
"postsRead": {
"type": "integer",
"minimum": 0
},
"itemsCount": {
"type": "integer",
"minimum": 0
},
"repostsSent": {
"type": "integer",
"minimum": 0
},
"postsCreated": {
"type": "integer",
"minimum": 0
},
"mothsSeenCount": {
"type": "integer",
"minimum": 0
},
"specimensCount": {
"type": "integer",
"minimum": 0
}
},
"description": "Session progress counters at heartbeat time (per-device bucket contents)"
},
"chronicleSignature": {
"type": "object",
"required": [
"sig",
"alg",
"kid",
"signedAt",
"nonce",
"version"
],
"properties": {
"alg": {
"type": "string",
"description": "Signing algorithm (ES256)"
},
"kid": {
"type": "string",
"description": "Key identifier for the signing key"
},
"sig": {
"type": "string",
"description": "Base64-encoded ES256 signature"
},
"nonce": {
"type": "string",
"description": "Unique random nonce to prevent replay"
},
"version": {
"type": "integer",
"description": "Signature schema version"
},
"signedAt": {
"type": "string",
"format": "datetime",
"description": "When the record was signed"
}
},
"description": "ES256 cryptographic signature proving record authenticity"
},
"walkElsewhereActivity": {
"type": "object",
"properties": {
"likesSent": {
"type": "integer",
"minimum": 0,
"description": "Likes sent elsewhere during the walk window (scan minus in-app; at-least estimate)"
},
"repostsSent": {
"type": "integer",
"minimum": 0,
"description": "Reposts sent elsewhere during the walk window (scan minus in-app; at-least estimate)"
},
"postsCreated": {
"type": "integer",
"minimum": 0,
"description": "Posts created elsewhere during the walk window (scan minus in-app)"
}
},
"description": "Cross-client activity during the walk window that did NOT happen in anisota: the PDS scan of app.bsky posts/likes/reposts in the walk window minus the in-app per-device summed totals, floored at 0 per field. Because likes and reposts carry no client marker this is a subtraction estimate, so the elsewhere panel labels it 'at least'. This block NEVER earns XP or findings; it is data for the panel only. Optional; unset when there was no elsewhere activity or the scan was unavailable."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}