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 testimony this is, unique across every recording anywhere: the day, then who saw it ("1964-04-24-ZamoraLonnie"), or where for an anonymous witness ("1964-04-24-Socorro"). What a case names it by |
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 |
durationSeconds | An alternative to endTime, and it wins if both are given |
utcOffsetHours | The LEGAL time the witness'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 |
place | [{ lat, lng, name }] — name is the fully qualified place name the coordinates were resolved from |
witness | { id, title, lastName, firstNames }, all optional; omit entirely for an anonymous witness. 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) |
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 |
Several witnesses: the case#
Each witness has a recording of their own, and a recording does not say which case it
belongs to: a testimony 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 testimonies, each pointing
at one witness's recording:
{
"id": "ChilesWhitted",
"title": "Chiles et Whitted",
"time": "1948-07-24 02:45",
"events": [
{ "type": "event", "eventType": "sighting", "url": "witness-chiles.json" },
{ "type": "event", "eventType": "sighting", "url": "witness-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 others (an analysis, an article, a film, a confession) are RR0's |
Give it to <rr0-sighting src> or to the player, and each witness 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 témoignage de Lonnie Zamora, d'un seul tenant…",
"en": "Lonnie Zamora's whole testimony, 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 witness 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 }
}
| Field | Meaning |
|---|---|
kind | oval, or polygon, which then also takes points |
color | Any CSS colour |
angle | Radians |
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 witness said the edges looked |
angular | Its apparent size in degrees — see below |
angular is the authority. bounds is that angle
projected onto the fixed 640×360 canvas at the pose's own field of view and through the
recording's own instrument; it is re-derived on load, so a file survives a change of canvas,
of field of view or of instrument. If the two ever disagree, the angle wins.
timeline.order is the back-to-front paint order, timeline.groups the
grouped source ids. Both optional.
Everything around it#
| Field | Meaning |
|---|---|
witnessTrack | { keyframes: [{ t, pose }] } — pose holds lat, lng, elevationM (above the local ground), headingDeg, pitchDeg, rollDeg, fovDeg, and for a camera fNumber and focusDistanceM |
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 witness's own account |
soundTrack | { keyframes: [{ t, sound }] } — kind (none/hum/whistle/rumble/crackle), volume, pitchHz, optional src of a real recording |
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, and the landmarks it was lined up on (id, label, picture as { u, v } from the top-left corner, scene as { azimuthDeg, altitudeDeg }) |
instrument, exposureSeconds | What it was observed through, and how long the shutter was open. Absent means the naked eye |
decor | Scenery at a real eastM/northM from the witness: buildings (with floors, windows), trees, streetlights, vehicles, other witnesses, aircraft — optionally with a track and lights whose pattern carries a real flash rate |
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 witness stood: it casts its shadow, the ground can hide it, and its outline is measured
against what the witness said at every instant. An interpretation is shown alone, as the
world it claims; asked to compare (the ◌ button, or compare-testimony on
<rr0-sighting>), the player draws everything the witness 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 a witness could not have been that far off.
The witness's own reading goes in the recording, as interpretation. An
analyst's goes in the case, as an event of type interpretation naming the
recording by its id, with who claims it in by
({ "people": id }, { "org": id }, or a person described in value)
and its bodies inline or in a file at url. A testimony whose witness 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), a model of the catalogue by id, or a glTF file at url with its credit. Stretched to sizeM whichever it is |
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 witness stood at the start, like the decor, with onGround or altitudeAboveGroundM) or from the witness 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) hold until a later keyframe restates them. 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 |
outlineNode | The node of the model that is what the witness 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 |
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 |
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 a witness who climbs; a witness 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 witness'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.
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
}
}
]
}
]
},
"witnessTrack": {
"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 player, whose editor 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 |
|---|---|
witness-chiles.json | A real case: a witness, 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 witnessTrack 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 and an exposureSeconds, and a decor aircraft with a track and seven lights at their real flash rates |
instrument-instamatic.json | The same sighting as witness-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; one whose first keyframe is at five seconds is already
painted, in that state, from zero. To make something stop being visible, keyframe it at
transparency: 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 witness. 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 witness reported hearing nothing. The same distinction runs through the weather and the ice cloud.
The README carries the full field-by-field reference, including the reasoning behind each choice, and is the canonical source if this page and it ever disagree.