The sighting file
One recording is one JSON file, whether the editor wrote it or you did. This is what it can hold, and what each field means.
A recording is a plain JSON file. Nothing in it is a binary blob, an id into a database, or a reference to this site — you can write one by hand, generate one from your own archive, or diff two of them in a code review.
Every excerpt below is read-only, and each knows the format: put the caret inside an object and press Ctrl+Space (⌥+I on a Mac) to list every key that could go there, with what the model says of each.
The observation#
| Field | Meaning |
|---|---|
version | Always 1 |
id | Which account this is, unique across every recording anywhere: the day, then who saw it ("1964-04-24-ZamoraLonnie"), or where for an anonymous observer ("1964-04-24-Socorro"). What a case names it by is not this id but its event's own (see below). |
time, endTime | { year, month, day, hour, minute, second, raw }, every part optional — that is how the format states “1954” or “around 05:00”. raw is the date as written in EDTF, and it is what the date means: "1948-07-24T02:45~" (approximate), "2025-06?" (uncertain), "1965-07-01%" (both), "19XX" (a masked year), or "05:00" alone for a time of day remembered without its date. The numbers are kept in step with it for what computes (the sky, the clock). It is a subset of EDTF (level 0, these qualifiers on the whole date, masked years); @rr0/time is RR0's full EDTF model, into which UFO@home's own tooling converts a recording's dates season (spring, summer, autumn or winter) states a date known only as “in the spring of 2022” (EDTF 2022-21), where month and day are left out. |
durationSeconds | An alternative to endTime, and it wins if both are given |
durationText | The length as it was stated when it was not exact: an ISO 8601 duration with a doubt (PT10M~) or a range (PT5M/PT10M). durationSeconds is then the one length played at, chosen from it |
utcOffsetHours | The LEGAL time the observer's clock was on (+1 for France in 1965). Absent means it is approximated from the longitude, which cannot know legal time or a daylight-saving switch |
timeZone | The IANA zone the offset was derived from ("Europe/Paris"): the rule, where utcOffsetHours is the number it gave at that date. Only the number is read to place the sky; the zone is what lets the offset be derived again when the date changes |
place | [{ lat, lng, name }] — name is the fully qualified place name the coordinates were resolved from |
observer | { id, title, lastName, firstNames }, all optional; omit entirely for an anonymous observer. id is a reference to the person (on RR0, their directory: "ZamoraLonnie"); the other fields describe them when nobody has given them one |
description | The account in prose — one string, or one per language (see below) |
image, summary, summaryTime | What stands for the observation where it is only linked to and never played: the card a shared link shows (see Sharing). image is a picture, an address (absolute, relative to the file, or an RR0 path from the site root); summary a sentence or two, one string or one per language. Absent, the card shows the logo and says who saw it, in which case and when, as far as the file tells. summaryTime is the second of the recording at which the picture of the card is photographed (0 by default: the first image), for a recording whose first image does not show the phenomenon; only the project's capture tool reads it, never the player |
tags | A list of strings, written in English: they are technical terms, and two recordings that share one have to match on it. Each reader is shown them in their own language where a translation is known, which is the case today for landing, trace, aerial observation, project sign, paralysis, contact, occupants, close encounter, photograph, radar, electromagnetic effect. The list is not closed: anything else is shown as written, and classification codes ("RR3", "NL") or case references ("Blue Book 8729") are written as they are. One changes the replay: paralysis keeps the observer's view still |
account | Who saw it and how the account travelled: observerAgeYears (at the time), observerOccupation, source (how it reached whoever wrote the recording: on-site, interview, telephone, questionnaire, letter, social-media, press) and followedUp (whether the observer was gone back to afterwards) |
sources | Where the account can be read as it was given, in the shape of an RR0 source: [{ type, title, authors, url, publication: { publisher, time }, index }], type being book or article, or nothing for a web page or a post, which is then simply its url. Shown in the player's ?panel, linked when it has an address; copied unchanged into an RR0 case |
milestones | The named moments of the account, shown on the seek bar: [{ t, label, note }], label being the letter or number the account itself uses ("A", "B") or a couple of words, note what happened then, in the account's words where possible (one string, or one per language) |
roads | The roads or paths the account's own plan draws, when the map does not have them: [{ id, title, surface, widthM, path, source }], surface being paved, gravel or dirt, path the centre line as [{ eastM, northM }] from the observer's place at the start, and source the plan or survey it was read off |
Several observers: the case#
Each observer has a recording of their own, and a recording does not say which case it
belongs to: an account stands on its own. What shows them together is the
case, which names them: the
case.json of an RR0 dossier, which states the case's
title, date and classification, and lists everything that happened in it as
events. Its events of type sighting are its accounts, each pointing
at one observer's recording:
{
"id": "ChilesWhitted",
"title": "Chiles et Whitted",
"time": "1948-07-24 02:45",
"events": [
{ "type": "event", "eventType": "sighting", "url": "observer-chiles.json" },
{ "type": "event", "eventType": "sighting", "url": "observer-whitted.json" }
]
}
| Field | Meaning |
|---|---|
id | The case's own identifier. On rr0.org it is the dossier's directory and may be left out; a case file standing alone states it |
title, time | The case's name, and when it happened as RR0 writes a time ("1948-07-24 02:45", "1954"). The player names a case it opens by its title |
events | The case's chronology. Only the sighting ones are replayed, each by its url, read relative to the case file's own address (so the same case works from its dossier's page and from anywhere else); the ones marked interpretationOf are readings of one of them (see below); the others (an article, a film, a confession) are RR0's An event bears an id when a reading names it, and only then: it belongs to the case, so it has to be unique among the case's events and nowhere else, and a reference is read in the file that writes it. An interpretationOf that no event bears is read, as it used to be, as the id a recording the case lists states; one that matches neither is a fault of the case, which the player reports on its console, the editor above its recordings, and the skill's validate.py when it is given the case file. |
image, summary | What a shared link to the case shows, as for a recording (see Sharing): a picture, and a sentence or two, one string or one per language |
Give it to <rr0-sighting src> or to the player, and each observer can be
picked from a list. One recording can be given directly, with no case, but a case with one
sighting works the same way and names what it shows. Try it with
case-chiles-whitted.json
(play it).
Saying it in more than one language#
A recording is handed from one reader to another, so every field an author writes can hold
one string per language instead of one: description, a shape's or a decor
object's title, and a milestone's label and note.
{
"description": {
"fr": "Tout le compte rendu de Lonnie Zamora, d'un seul tenant…",
"en": "Lonnie Zamora's whole account, of a piece…"
}
}
Keys are language tags as a browser gives them (fr, en,
pt-BR), and none of them is required. A plain string stays perfectly valid and
means “in whatever language it was written in” — which every recording made before this
is. A reader whose languages are none of the ones present gets what the file DOES have rather
than an empty field: a missing translation must never turn something the observer said into
something they did not.
Which language a reader gets is their browser's, unless the page says otherwise: a
lang on the element itself, or on anything around it, is taken first — an
article that declares its own language has already stated what language its reader is reading
it in. The browser's list is what follows, so declaring one forces a choice without throwing
away the others.
The editor shows one language, the reader's own, and writing back touches only that one — so opening a file in the other language and typing is how a translation gets added, and one author cannot delete another's.
What was seen#
timeline.keyframes is a list of { t, shapes }, t in
milliseconds from the start. Each shape carries a sourceId — several shapes can
share one timeline (the phenomenon, a trailing flame, a second light) — and a shape:
{
"kind": "oval",
"bounds": { "x": 0, "y": 0, "width": 0, "height": 0 },
"color": "#39ff14",
"angle": 0,
"transparency": 0,
"haloScale": 1.5,
"brightness": 0,
"blur": 0,
"selected": false,
"title": "the phenomenon",
"angular": { "widthDeg": 1.2, "heightDeg": 0.4 },
"aim": { "azimuthDeg": 353.6, "altitudeDeg": 0.6 }
}
| Field | Meaning |
|---|---|
kind | oval, or polygon, which then also takes points: [{ x, y }] in pixels from the top-left corner of bounds, spanning its width and height, so that the outline is stretched with it when the angle resizes the box |
title | Its name, shown when the pointer is over it; one string, or one per language |
color | Any CSS colour |
angle | Its tilt, in radians about its own centre, positive clockwise on screen |
transparency | 0 opaque to 1 invisible |
haloScale | The glow around it; 0 is none |
brightness | How dazzling: a veil, aperture spikes, a core clipped to white |
blur | How indistinct the observer said the edges looked |
angular | Its apparent size in degrees — see below |
aim | Where it was in the observer's sky: the direction of its centre, azimuthDeg clockwise from true north and altitudeDeg above the horizon. Not the way the observer was facing, which is the pose's headingDeg and pitchDeg — see below |
aim places it, angular sizes it. bounds
is those two projected onto the fixed 640×360 canvas at the pose's own heading and field of
view, through the recording's own instrument; it is re-derived on load, so a file survives a
change of canvas, of field of view, of instrument, or of where the observer was looking. If the
pixels and the angles ever disagree, the angles win: moving bounds in a file does
nothing while aim is there. A file written by hand can leave bounds
out entirely.
timeline.order is the back-to-front paint order, timeline.groups the
grouped source ids. Both optional.
Everything around it#
| Field | Meaning |
|---|---|
observerTrack | { keyframes: [{ t, pose }] } — pose holds lat, lng, elevationM (above the local ground), headingDeg, pitchDeg, rollDeg, fovDeg, and for a camera fNumber and focusDistanceM. Played the way a person moves: exactly the stated pose at each keyframe, and between them a movement that gathers speed, carries it through the keyframes that keep moving, and slows down into a pause — two keyframes with the same place, or the same heading. A look starts and ends still; a walk or drive already under way at the first keyframe carries on. Stopping and setting off on foot add their own jolt, a nod of the head that dies within a second and a half, turned into the image as much as the instrument lets it (an eye hardly, a hand-held camera fully). And a body at rest is never quite still: standing or waiting, the view sways by millimetres and drifts by a tenth of a degree, slowly, the same way at the same instant — except for an observer the account says was paralysed (tag paralysis). The pitchDeg stated is how the observer looked in the frame of what carried them: while they travel, the player adds the grade of the road under the track (read from the relief, uphill positive, faded as the view turns off the way they go), so a climb raises the view and the Tiltchip states the sum filter is what was held in front of the instrument, a FILTERS id such as solar-visual for an eclipse. It is held between keyframes, not blended (nobody wears half a pair of glasses), and taken off by stating "none". |
weatherTrack | { keyframes: [{ t, weather }] } — the sky's conditions along the recording: precipitation, wind, storm, and the clouds as layers with real heights, each able to hold individual clouds placed in metres. Every field of a weather is in the next section |
weatherSource | { id, name, url } of the record the weather was looked up from. Its presence means the recording is replayed exactly as authored and never looked up again. Absent means the observer's own account |
lightPollution | How bright the night sky of the place is, towns and all: the zenith of a moonless night in magnitudes per square arcsecond, as a Sky Quality Meter reads it or as the World Atlas of the artificial night sky brightness gives it (its “SQM” figure, Falchi et al. 2016, which lightpollutionmap.info shows for any place). 22 is a natural sky, a suburb about 19, a city centre 17. Absent means a natural sky. The towns' share, what is left once the natural 22 is taken out, is added to the sky as light: brighter toward the horizon, where the low air scatters more of it back, and drawn in the warm white of a 3000 K lamp. It drowns the Milky Way and the zodiacal light, and takes stars away, by the difference in the faintest star an eye can pick out (Crumey 2014): about two magnitudes at 19. The Moon now does the same. "derived" when looked up in the atlas, "stated" when the observer measured it. The editor fills it in from the atlas for an observation from 2015 on, as "derived" with the atlas named, and never replaces a figure typed by hand; before 2015 it asks nothing, the sky having been darker than the atlas's |
observerMap | Whether the map of where the observer stood starts open when the recording is played or edited: true or false. Absent says nothing, and the player decides: open for an observer who went somewhere, closed for one who stayed put. A page embedding the player has the last word (its show-observer-map attribute). The editor writes it when the map is opened or closed there, so that a reader is shown what the author left |
soundTrack | { keyframes: [{ t, sound }] } — kind (none/hum/whistle/rumble/crackle), volume, pitchHz, optional src of a real recording. volume and pitchHz glide between keyframes, kind and src change at the keyframe. A src on another site must be served to any origin (CORS) |
references | Pictures of the place laid over the scene: src (an address, or a data: URL for a picture added from a disk), kind (photo/panorama), registration (headingDeg, pitchDeg, rollDeg, fovDeg), opacity, credit/creditUrl, optional t and drawing, from ({ lat, lng }, where it was taken: the player fades it out as the observer walks away from that point; absent, it is never faded), and the landmarks it was lined up on (id, label, picture as { u, v } from the top-left corner, scene as { azimuthDeg, altitudeDeg }) |
traces | What an investigator drew over the place, imported from their Google Earth file (KML or KMZ) and drawn over the scene as theirs, never as part of the reconstruction: [{ id, title, kind, points, altitude, color, source }], kind being point, line or polygon, points the vertices as [{ lat, lng, altM }] exactly as the file held them, altitude how altM is read (ground, the default, relative to the ground under each point, or absolute above sea level), color a #rrggbb and source whose it is |
instrument, exposureSeconds | What it was observed through, and how long the shutter was open. Absent means the naked eye. instrument is one of eye, rectilinear-lens (a camera of unknown make), roamer-1-120, instamatic-126, slr-35mm-50, slr-35mm-zoom, phone-landscape, phone-portrait; exposureSeconds is one value for the whole recording, held to that device's own range |
iso | The speed of the film or sensor the picture was taken on, when it is known: 400 for a 400 ISO film. Absent means the device's own (an SLR's colour negative is 100, an Instamatic's 64). A camera's picture is not answered like an eye's: a film does not adapt to the sky, it receives an exposure — the light, times the shutter, over the square of the f-number — and answers it with its own curve (a negative's soft one, a slide's steeper one, a sensor's straight line that stops at white). So a night pose comes out with a black sky and whatever was bright on it standing out |
sway | How much the body holding the instrument moves the view when not walking: 1 for a person standing or sitting, 0 for a camera on a tripod, anything between or above for less or more. Absent means 1 — except for an observer the account says was paralysed (tag paralysis), for whom it is 0, and for an exposure longer than half a second, which no hand holds still: that is a tripod, and 0 too |
vehicle | The vehicle the observer was in, when the decor does not draw it: { kind, windowsOpen, noise }, kind being car, van, truck, motorcycle or generic. Heard from inside: its engine follows the observer's own journey — the gear from the speed, the revs from the gear, the effort from speeding up or slowing down — with the road and the wind, muffled by a closed cabin, let in by open windows; noise is its loudness against an ordinary one of its kind (1 by default). It is also what keeps the player's sound button active when the file has no soundTrack: the button answers for everything the scene plays — this engine, an engine in the decor, rain and wind — not only for the track |
decor | Scenery at a real eastM/northM from the observer: buildings (with floors, windows), trees, shrubs, streetlights, vehicles, bridges, overhead lines, other observers, aircraft — optionally with a track and lights whose pattern carries a real flash rate. See below |
A decor object is stated in metres, like everything that is not the phenomenon:
| Field | Meaning |
|---|---|
eastM, northM, headingDeg | Where it stands from the observer, and which way its front faces, clockwise from true north |
sizeM | { widthM, lengthM, heightM } along its own axes, length being the way it faces. Each axis is optional: one nobody measured keeps the built-in shape's own proportion |
model | A real 3D model standing in for the built-in shape: a catalogue entry by id, or a glTF/GLB file at url (which wins, must be readable from any origin, and then needs its credit; a relative one is read from the file that states it). It never decides the size: it is scaled, keeping its proportions, to the first measured axis among length, height and width, or to the real size the catalogue gives it. Seen from inside, and whenever the model cannot be had, the built-in shape is drawn instead. An observer or an entity that names none is never drawn as a built-in shape: it is one of the catalogue's realistic people, picked by its id (a child, or a being a statement describes, names its own model) headingOffsetDeg turns the loaded model about its vertical axis so that its nose points the way the scene expects (0 faces north at a heading of 0); a catalogue entry brings its own. credit is { title, author, license, sourceUrl }: license as its author states it ("CC BY 4.0") and sourceUrl where it came from, so that the claim can be checked. |
bridge | For a "bridge": how it is built. sizeM.lengthM is its whole length along the road, embankments included, widthM the deck's width and heightM the height of the road on the deck above the ground; spanM is the open span under the deck, the road running on a bank of earth sloping down to the ground on each side of it; deckThicknessM the slab's thickness (1.2 by default); railing { heightM, postSpacingM, rails } the railing along both edges, posts and rails whose openings are rectangles (1.05 m, 1.5 m and 2 rails by default). Drawn at those measurements, never stretched |
wire | For a "wire" (an electric or telephone line): sizeM.lengthM is the span between its poles, along its headingDeg, and heightM the height of the lowest strand; strands [{ heightM, acrossM }] hangs several (stacked ones share an acrossM, a crossarm's differ); poles is both (default), start or end (the end its heading points away from or towards: the other is anchored on a building) or none; diameterM (8 mm by default) and poleHeightM (half a metre over the highest strand). Straight, without sag, and drawn at those measurements, never stretched |
track | [{ t, eastM, northM, altitudeM, headingDeg }] when it moves, altitudeM above the observer. Position blends between keyframes; the heading is held from one to the next |
lights | Its lamps: id, offsetM { x, y, z } from its centre (right, up, front), color, intensity (1 is an ordinary navigation light) and a pattern: { "kind": "steady" }, or { "kind": "flash", perMinute, dutyCycle, phase }, the rate as regulations state it, the lit fraction of each cycle (about 0.5 for a filament flasher, 0.01 for a strobe) and an offset from 0 to 1 between lamps. The editor fills them from presets: airliner, helicopter, car headlights, car hazard flashers, emergency beacons, streetlamp |
engine | A vehicle heard running: { kind, noise }, as vehicle above. Its engine follows its own track, heard from where the observer stands, fainter and duller with distance |
aircraftSound | An aircraft (kind aircraft) heard while it flies, which it does when its track leaves the ground: the sort of noise it makes, an aircraft kind (airliner-narrow by default, airliner-wide, regional-jet, helicopter-light, light-piston…) or silent for one that makes none. What its model says applies when this is absent (the stand-ins for a phenomenon are silent). It is heard like the aircraft of a record of air traffic: fainter and duller with distance, late by the time sound takes to come (about three seconds per kilometre) so that it is heard where it was, higher in pitch as it comes and lower as it leaves, and masked by the noise of the place |
occludesSourceIds | The phenomena the observer said it stood in front of — see the rules below |
facade | Kind building only: { color?, plain? }. color is the wall, "#rrggbb"; unless plain is true, windows are drawn on every floor and a door on the ground floor of the front face. Drawn at the stated sizeM (all three sides must be stated), never stretched, and not for a building with an observer inside. The editor takes the colour from the material the map states for the walls (stone, brick, concrete, breeze block, wood), or a plaster where it states none. Absent is the plain box. |
roof | Kind building only: { shape, riseM, color?, ridge? }. shape is "flat" (the box), "gable" (two slopes to a ridge along the longer side, or along the depth with ridge: "depth") or "hip" (four slopes); riseM is how far the ridge stands above the eaves, so the walls are sizeM.heightM less that; color is the roof seen from above, "#rrggbb". The editor reads them off the laser and the aerial photograph of the survey. Absent is a flat roof the colour of the walls. |
origin | The survey this object was taken from, by the id of its provider ("ign-lidar-topo"), when the editor took it from the survey of the place and the author did not place it by hand: a tree, a building, a bridge or a span of line. Choosing another survey, or none, or another place replaces or removes what carries an origin, and never what does not |
surveyedOn | The day (YYYY-MM-DD) of the newest survey this object was drawn from, where it comes from one (see origin): the editor warns when it is later than the observation. |
lit, litKeyframes | Whether a streetlight's lamp or a vehicle's headlights are on. litKeyframes is [{ t, lit }], sorted by t, for one that is switched on or off during the recording; without it lit holds throughout. Ignored for a building, a tree or an observer |
observerSide, observerFacing, observerKeyframes, occupiedFloor | The observer inside the object, for a building or a vehicle (at most one object holds them). observerSide is where they are: front, behind, left, right, front-left, front-right, behind-left or behind-right, a vehicle's occupant being at one of its four doors. observerFacing is the window they look through when it is not their seat's: a driver sits at front-left and looks through the windshield, front. observerKeyframes, [{ t, side, facing }], says where they sit over time, each held until the next, and a keyframe without side means outside from then on, as a driver who gets out. occupiedFloor is the floor of a building they are on, 0 (ground floor) up to floors |
modelParameters | The settings of the model the object is drawn as, by the key the catalogue entry publishes each under: { "costumeColor": "#5a6b3a" } for the clothes of a person, foliageColor for a tree, hullColor for a craft, gearUp for an airliner in flight. A model brings its own (the editor draws a field for each of them); an object drawn as a built-in shape has none, it is painted by color |
sightingUrl | For an observer: the address of that observer's own recording, when it is known, so that the scene's menu can offer to view their account |
What it was: interpretations#
A recording states angles, and a body in metres is never part of what was seen. It is a
claim about it, and it is tested by standing it in the scene and looking at it from where
the observer stood: it casts its shadow, the ground can hide it, and its outline is measured
against what the observer said at every instant. An interpretation is shown alone, as the
world it claims; asked to compare (the ◌ button, or compare-account on
<rr0-sighting>), the player draws everything the observer saw beside it as
dashed outlines and lists how far off the direction is and how many times wider and taller
each body looks, in red when an observer could not have been that far off.
The observer's own reading goes in the recording, as interpretation. Someone else's reading is not part of the account: it is another sighting, listed in the case (see below). A account whose observer said what
it was is drawn in the round, as they said; one that says nothing in metres is drawn as the
angles it states. The player offers it and each analyst's interpretation, one at a time.
{
"interpretation": {
"title": "A craft standing on its legs",
"bodies": [{
"id": "craft",
"explains": ["ufo-1"],
"model": { "id": "ellipsoid" },
"track": [
{ "t": 52000, "eastM": -571.6, "northM": -965.5, "onGround": true,
"sizeM": { "widthM": 3.36, "lengthM": 3.36, "heightM": 1.73 },
"appearance": { "color": "#e8e6df", "albedo": 0.7 } },
{ "t": 83000, "azimuthDeg": 195.9, "altitudeDeg": 4.1, "distanceM": 44 }
]
}]
}
}
| Field | Meaning |
|---|---|
explains | The sourceIds of the phenomena this body claims to be |
model | A shape built here (ellipsoid, sphere, disc, cylinder, cone, box, torus, figure), a model of the catalogue by id, or a glTF file at url with its credit; a relative url is read from the file that states it, not from the page. Stretched to sizeM whichever it is headingOffsetDeg and credit ({ title, author, license, sourceUrl }) are those of a decor object's model. |
track | Where it is and what it looks like at each t. A position is stated either in the world (eastM/northM from where the observer stood at the start, like the decor, with onGround or altitudeAboveGroundM) or from the observer at that instant (azimuthDeg, altitudeDeg, distanceM). A body onGround stands on the relief; a direction with no distance then meets the ground where that line does. sizeM, attitude (headingDeg, pitchDeg, rollDeg) and appearance (color, albedo, luminanceCdM2, transparency from 0 to 1, haloScale as a multiple of the glare of its own light) hold until a later keyframe restates them, and are blended between two keyframes. present: false takes the body out of the scene from that keyframe on, and present: true brings it back. A flame (lengthM, widthM, color at the nozzle, tipColor, luminanceCdM2) is lit at the keyframe that states it, comes out of the model's node named exhaust (or the one its node names), lights what is around it, raises dust where it meets the ground when raisesDust says so, and is put out by a luminanceCdM2 of 0 |
motions | In a keyframe: how far along each of its model's own movements it is, by the movement's name — the glTF file's animations. 0 is a movement's start, 1 its end, and one that repeats goes on past 1: { "legs-turn": 7 } is seven turns. The model says what moves and how; the track says when. Each movement blends between the keyframes that state it, whatever other keyframes move the body in between, holds after the last and stands at 0 before the first. Valensole's departure is written so: { "t": 246000, "motions": { "pivot-retract": 0 } }, { "t": 248000, "motions": { "pivot-retract": 1, "legs-turn": 0 } }, … { "t": 262000, "motions": { "legs-turn": 7 } } |
lights | In a keyframe: the luminance, cd/m², of each of its model's own lights, by the name of the light's material in the glTF file — so that one body carries lights that each do their own thing, a steady white at each end and a red one flashing between. The model says where each light is, how big and what colour; the track says how bright and when. Each light blends between the keyframes that name it and holds after the last, so a switch is two keyframes a millisecond apart; a light no keyframe names glows at its share of appearance.luminanceCdM2. A light too small to show is seen by its glare, and whatever stands between it and the eye — the body's own hull, a bridge — hides it. Silly-le-Long's red light: { "t": 499, "lights": { "front-red": 1500 } }, { "t": 500, "lights": { "front-red": 0 } } |
outlineNode | The node of the model that is what the observer drew ("hull" for a craft whose legs are not in the drawing): what its outline is measured by |
smoke | On the interpretation itself: what it sets burning on the ground, as { eastM, northM, fromT, untilT? }, seen by its smoke carried off by the recording's wind halfLifeS is how fast it dies down, in seconds to half its thickness (20 by default): a brush fire flares and smoulders. It never quite stops before untilT. |
reentries | On the interpretation itself: the atmospheric re-entries it claims were seen, a satellite or rocket stage breaking up into burning pieces. Not bodies: a re-entry burns 70 to 100 km up and is seen from hundreds of kilometres, so it is placed on the Earth and seen through its curvature from wherever the observer stood. Its track gives the leading point at instants of the recording, { t, lat, lng, altitudeKm }, joined by great circles; each of its fragments follows the same path lagS seconds behind, is seen from fromT until it burns out at untilT, shines with its absoluteMagnitude (at 100 km, as for a fireball) moved to its real distance, in its color, with a tail of the last trainS seconds of its path. Its light is its own: it shows whether or not the Sun reaches it. object names what came down (name, cosparId, norad). Nothing is propagated from orbital elements, which are off by minutes in the last orbit: the path is what the sources establish, with its provenance |
launches | On the interpretation itself: the rocket launches whose exhaust it claims was seen, lit by a Sun that has set for the ground but not for the plume, the "space jellyfish". Placed on the Earth like a re-entry: its track gives the stage at instants of the recording, { t, lat, lng, altitudeKm } (it may begin before the recording does), and burnFromT and burnUntilT the seconds the engine burned. The plume is a string of puffs let go along that path, each spreading with age; what shows of each is the sunlight it scatters towards the observer. The Earth's shadow (where the Sun is lit or not at the puff's height, and how much air its light grazed, which reddens or blues it), the scattering angle and the spreading are computed from the real Sun at the real date, so nothing is stated of them. An optional plume overrides the defaults: exhaustKgPerS (273, a Merlin Vacuum), condensedShare (0.1, the one number nobody has measured), particleRadiusUm, expansionKmPerS, initialRadiusKm, lifetimeS, and for the collar (exhaust just let go, wide and soon gone, beside the narrow trail that stays) collarShare (0.85), collarExpansionKmPerS (2.5), collarLifetimeS (20). vehicle names what flew and, in engine, the engine whose exhaust it is: thrustKN and specificImpulseS (from which the exhaust per second and its velocity follow, hence how wide the collar spreads) and propellant (rp1-lox, methane-lox, hydrogen-lox, hypergolic or solid, which says how much of it turns into particles and how big). What plume states beats what the engine implies. The plume needs the recording's date: without one there is no Sun to light it vehicle.mission names the mission it flew ("Starlink Group 10-49"). |
Another reading: an interpretation is another sighting#
Someone else's reading of an account (an analyst's, an investigator's, a comparison made to test it)
is not part of the account: it is another observation, the one that took place as its author holds
it. So it is listed in the case like the original, as an event of type sighting, marked as a
reading by interpretationOf, and has a recording of its own at url:
{
"id": "McMinnville1950",
"events": [
{ "type": "event", "eventType": "sighting", "id": "trent", "title": "Paul Trent", "url": "observer-mcminnville.json" },
{ "type": "event", "eventType": "sighting",
"interpretationOf": "trent",
"time": "2013-06",
"by": [{ "people": "CousynAntoine" }, { "people": "LouangeFrancois" }],
"title": "A model on a thread under the line",
"url": "interpretation-mcminnville-2.json" }
]
}
| Field | Meaning |
|---|---|
interpretationOf | The id of the case's event it is a reading of ("trent"), or a list of ids when one reading serves several accounts (the photographs and a close-up of them). What tells it from an account: the events without it are the case's observers |
time | When the interpretation was elaborated, as RR0 writes a time ("2013-06"): not when the sighting happened |
by | Who claims it: { "people": id } or { "org": id }, which name an entry that lives elsewhere (on RR0, a people or an org directory), or a person described in value. Absent for a comparison nobody claims |
title | What the reading is called in the player's menu and the editor's picker: a few words, one string or one per language |
url | Its recording, read relative to the case file's own address, like an observer's |
The recording at url is a recording like any other. It shares the place, the time and the observer's
pose of the account it reads, so that a body placed from the observer, or in metres from where they stood,
means the same in both; it draws no shape of its own (an empty timeline); and it holds the bodies in
its own interpretation, described above. A body's explains names the shapes of the account.
{
"version": 1,
"id": "1950-05-11-TrentPaul-interpretation-2",
"time": { "year": 1950, "month": 5, "day": 11, "hour": 19, "minute": 45 },
"utcOffsetHours": -7,
"place": [{ "value": { "lat": 45.104167, "lng": -123.330556 } }],
"observerTrack": { "keyframes": [{ "t": 0, "pose": { "lat": 45.104167, "lng": -123.330556, "headingDeg": 41.3, "pitchDeg": 14.85, "fovDeg": 33.4 } }] },
"tags": ["interpretation"],
"timeline": { "keyframes": [], "order": [], "groups": [] },
"interpretation": {
"title": "A model on a thread under the line",
"bodies": [{
"id": "model",
"explains": ["ufo-1"],
"model": { "id": "disc" },
"track": [
{ "t": 0, "eastM": -52, "northM": 118, "altitudeAboveGroundM": 7.5,
"sizeM": { "widthM": 0.3, "lengthM": 0.3, "heightM": 0.1 } }
]
}]
}
}
The player lists the case's observers from the events with no interpretationOf and offers each
reading beside the account it names, one at a time (the ◌ button measures it against the account). A recording
standing alone has no reading but its own interpretation. The older form, an event of type
interpretation naming the recording in sighting with its bodies inline or in a file at
url, is still read. The editor opens a case: its picker above the render lists the accounts and
the readings, each opening as the recording it is, with a button to add a reading (a new recording that shares the
account's place, time and pose, and an event for it), one to delete it, and one to export the case and every
recording added or changed as a zip. Try it with
case-mcminnville.json
(play it,
edit it).
The weather, and its clouds#
A weather keyframe states the sky's conditions at one moment of the recording's
clock; between two keyframes every number is blended, and precipitation type and storm are
held. It carries:
| Field | Meaning |
|---|---|
cloudLayers | The clouds, as a list of layers — see below. Absent means the older fields on this row's neighbours describe them, and they are adapted into one water layer and one cirrus veil; an empty list means a clear sky somebody looked at |
cloudCover, lowerCloudCover, highCloudCover | Fractions of sky (0–1): the total, the water decks alone, and the icy veil alone. Written by recordings made before there were layers, and still kept in step by the editor as a summary of them |
cloudBaseM, cloudDarkness | The same era's one base, in metres above the reference ground, and one shade (0 white, 1 very dark) |
iceCrystalAlignment | 0–1, how steadily the ice crystals fell — what turns a bare ring into sundogs, arcs and a pillar. No record measures it; a cirrus layer carries its own |
relativeHumidity | 0–1, near the ground. It decides how milky the clear sky is: haze swells with water as the air nears saturation. A looked-up record carries it (from ERA5's temperature and dew point); absent means a typical haze |
precipitationType, precipitationIntensity | none/rain/snow/hail, and 0–1. Stated at a keyframe, played the way it falls: a shower begins at the keyframe that starts it with its first drops, and reaches the stated rate no faster than real rain does (nothing to heaviest in twenty seconds); it stops the same way. A change the keyframes spread over longer is followed exactly precipitationAmount (0–1, absent meaning all of it) says how much of that precipitation is falling yet. |
windDirectionDeg, windSpeed | The general wind: the bearing it blows TOWARD, clockwise from north, and metres per second. It is what carries the clouds — from time zero, so seeking and replaying give the same sky |
storm | Lightning and thunder, at the right delay |
Each layer of cloudLayers is a deck of clouds at a real
height, and stays itself from one keyframe to the next:
| Field | Meaning |
|---|---|
id | Stable across keyframes — layers are matched by it, never by position in the list. A layer present in one keyframe and absent from the next fades out; reordering them changes nothing |
type | cumulus, stratus, stratocumulus, cirrus or unknown. It decides the shape of the tops and how thin the veil is; a cirrus is also the one that refracts haloes. It switches at the keyframe, it is not blended |
baseM, thicknessM | Metres. The base is above the recording's REFERENCE ground, not above an observer who climbs; an observer above the base is inside or over the deck, and the sky is drawn accordingly |
coverage | 0–1, and it means what it says: the fraction of the sky this layer covers, whatever the size of its clouds |
sizeM | The characteristic width of one cloud, in metres. Separate from coverage: the same fraction of sky can be many small clouds or a few large ones |
density | 0–2, how opaque the cloud matter is; 0 is transparent. Separate from coverage too |
darkness | 0 white to 1 very dark. Absent means the keyframe's cloudDarkness |
seed | Which pattern, out of the endless ones the same numbers can draw. Absent means one derived from the id, which is why the id must not change |
windDirectionDeg, windSpeed | This layer's own wind, when it differs from the general one — the high deck usually does. Absent means the general wind |
iceCrystalAlignment | For a cirrus only |
instances | Individual clouds inside this layer — see below |
An individual cloud in instances is one cloud of its layer that the
file places exactly, because the account did: the one the phenomenon went behind, the one that
was there and nowhere else. It is drawn as one of its layer's own — the same texture, the same
threshold — told apart from its neighbours by nothing but where it stands and how big it is,
and it stands even when the layer's coverage is nought. It rides the layer's wind
like the rest, and it hides a phenomenon it passes in front of.
| Field | Meaning |
|---|---|
id | Stable across keyframes, same rule as a layer's |
eastM, northM | Where its centre was at time zero, in metres from the observer's starting point. The wind carries it from there |
baseM, thicknessM | Its own base and height, metres — a cloud can sit lower or stand taller than its deck |
widthM, depthM, rotationDeg | Its footprint, metres, and the bearing that footprint is turned to |
density, darkness | Its own; darkness absent means the layer's |
{
"weather": {
"cloudLayers": [
{
"id": "low", "type": "cumulus",
"baseM": 1500, "thicknessM": 800,
"coverage": 0.55, "sizeM": 1400, "density": 1, "darkness": 0.15,
"instances": [
{ "id": "the-one", "eastM": 0, "northM": 4200,
"baseM": 1500, "thicknessM": 800,
"widthM": 1900, "depthM": 1300, "rotationDeg": 12, "density": 1 }
]
},
{ "id": "high", "type": "cirrus", "baseM": 8000, "thicknessM": 400,
"coverage": 0.2, "sizeM": 2200, "density": 0.35, "iceCrystalAlignment": 0.65 }
],
"precipitationType": "none", "precipitationIntensity": 0,
"windDirectionDeg": 90, "windSpeed": 5, "storm": false
}
}
A recording whose weather was looked up (it has a weatherSource)
holds the record's answer, not a link to it: ERA5 gives the low, middle and high bands as three
layers named record-low, record-mid and record-high, the
low base estimated from the spread between temperature and dew point, the other two at 3 500 m
and 8 000 m. Their type is unknown (cirrus for the high one), their size and
density are drawing assumptions: a reanalysis knows how much of each band was covered, not what
the clouds looked like. Ask the record again from the editor and the layers are rewritten; edit
a layer by hand and the recording becomes the author's, the source dropped.
Where each value comes from#
Any value in a recording can be written bare, or wrapped with where it came from:
{
"durationSeconds": {
"value": 15,
"basis": "derived",
"rationale": "13 to 18 s in the investigator's synthesis; the middle taken"
}
}
basis is stated (the observer said it, and what a bare value
means), derived (worked out from what they said plus something checkable: a
road's width, a map, a measured drawing; rationale gives the working) or
assumed (chosen so the reconstruction has a value at all, on nothing the observer
said). The list of assumed values is the list of what to go back to the observer
or the file for, which is why it is worth writing even when nothing else is.
Checking a file#
The format is also published as a JSON Schema, generated from the same types as the player: every key it knows, what each may hold, and the words a closed list accepts. A misspelt key or an unknown value fails it. It says nothing about what may be left out, which is a question of meaning this page answers. To see the result, open the file in the player, from a link, or from your disk with the pictures and models it names. The player checks it the same way on loading, and says what it could not play as written behind a ⚠ over the picture: a key nothing reads, a word outside its list, and what it had to make up because a shape's first keyframe left it out (a field held from the keyframe before is the rule above, not a problem).
A whole file#
The smallest recording that still states something — one silent oval crossing the sky over twelve seconds, on a real date at a real place. Everything else in the format is optional, and everything below is doing work:
{
"version": 1,
"description": "The smallest recording that still states something: one silent orange oval, a degree and a half wide, crossing the south-western sky from left to right over twelve seconds.",
"time": { "year": 2024, "month": 8, "day": 12, "hour": 22, "minute": 30 },
"durationSeconds": 12,
"utcOffsetHours": 2,
"place": [{ "lat": 45.19, "lng": 5.72, "name": "Grenoble, Isère, France" }],
"timeline": {
"keyframes": [
{
"t": 0,
"shapes": [
{
"sourceId": "ufo-1",
"shape": {
"kind": "oval",
"bounds": { "x": 150, "y": 150, "width": 10, "height": 3 },
"angular": { "widthDeg": 1.6, "heightDeg": 0.5 },
"color": "#ff8c1a",
"angle": 0,
"transparency": 0,
"haloScale": 1.2,
"selected": false
}
}
]
},
{
"t": 12000,
"shapes": [
{
"sourceId": "ufo-1",
"shape": {
"kind": "oval",
"bounds": { "x": 480, "y": 130, "width": 10, "height": 3 },
"angular": { "widthDeg": 1.6, "heightDeg": 0.5 },
"color": "#ff8c1a",
"angle": 0,
"transparency": 0,
"haloScale": 1.2,
"selected": false
}
}
]
}
]
},
"observerTrack": {
"keyframes": [
{
"t": 0,
"pose": { "lat": 45.19, "lng": 5.72, "elevationM": 0, "headingDeg": 215, "pitchDeg": 20, "fovDeg": 60 }
}
]
},
"soundTrack": { "keyframes": [{ "t": 0, "sound": { "kind": "none", "volume": 0, "pitchHz": 100 } }] }
}
To change it and see it play, paste it into the editor, which completes on every key the format has, offers the words each one accepts, and says what the model says about it.
It is /demo-data/example-minimal.json
on this site, so you can fetch it, and
play it before changing
anything. Note that angular and bounds both appear: the angle is what
the file MEANS, and the pixels are re-derived from it on load — write the angle, and let a
wrong guess at the pixels be corrected for you.
Larger ones to read#
Every demo on this site is a plain file you can open. These four are the ones worth reading to see how a real recording is put together:
| File | What to look at in it |
|---|---|
observer-chiles.json | A real case: an observer, a case id shared with a second recording, ten keyframes, a looked-up weatherTrack with its weatherSource |
sky-test-halos.json | No phenomenon at all — a sky set up by a weatherTrack whose keyframes change the crystals' alignment, the cirrus cover and a cumulus deck, watched through a observerTrack that pans across the display and then holds |
sky-test-clouds.json | Three cloud layers with metre-based altitude, thickness, size, density and wind, evolving on the weather timeline — and in the first one an instances entry: one cloud of the field, placed and sized in metres, that grows and darkens over the two minutes |
sky-test-aircraft.json | An instrument, an exposureSeconds and an iso, and a decor aircraft with a track and nine lights at their real flash rates, landing lights included |
sky-test-reentry.json | No phenomenon: an interpretation holding no body and one of reentries, the launcher of Zond 4 breaking up over Kentucky in 1968, its path derived and its timing, heights and brightness assumed, each value with its provenance |
sky-test-launch-plume.json | No phenomenon: an interpretation holding no body and one of launches, the exhaust of Starlink Group 10-49 (Falcon 9, 25 August 2026, 05:33 EDT) lit in the twilight and seen from Hoboken, New Jersey, the stage's path assumed with its provenance and the shadow, spreading and light computed |
instrument-instamatic.json | The same sighting as observer-socorro.json, changed in one field. Diff the two |
Four rules that decide what a file means#
- Discrete fields are held, continuous ones are blended. A shape left out of
a later keyframe stays as it was, and so does any field a keyframe leaves out of a shape it
does restate: a keyframe that only gives a new
aimmoves the shape and keeps everything else (restatingboundswithoutaimorangularis taken as moving it by its pixels); one whose first keyframe is at five seconds is already painted, in that state, from zero. To make something stop being visible, keyframe it attransparency: 1. - Angles only. No real size and no real distance is stored anywhere. Metres
are derived, as inequalities, from what the phenomenon was stated to pass behind or in front of
(
decor[].occludesSourceIds). - Declared outranks deduced.
occludesSourceIdsrecords statements by the observer. Nothing in this format can deduce them: it describes an appearance on a field of view, not a position in space. - Absent is not zero. No sound track means nobody was asked;
kind: "none"means the observer reported hearing nothing. The same distinction runs through the weather and the ice cloud.
This page is the reference for the format. The reasoning behind each field is in the doc comments of its type, which the excerpts above complete with, and in the source.