Visualization
The scene contract, figures, animation, and the native renderer.
Duckietown.ANIMATION_ABSENT — Constant
ANIMATION_ABSENTWhat the decision log cannot support, reported rather than reconstructed.
duck world position is the one that would be easy to fake: the reset duck pose is seed-invariant and available, but ducks move during an episode and the log stores only the lane-relative pair. Drawing a stationary duck for 150 frames would be a fabrication that looks like data, so the world view omits it and the lane-relative panel shows what was actually recorded.
Duckietown.ANIMATION_LAYOUT_VERSION — Constant
ANIMATION_LAYOUT_VERSIONPart of every frame's identity. A layout change makes different frames from the same evidence, and the fingerprint should say so.
Duckietown.ANIMATION_TIMELINE_LABEL — Constant
ANIMATION_TIMELINE_LABELWhat the x-axis and the caption must say. The artefact records no wall-clock time, so this is not real-time playback and must not be presented as such.
Duckietown.AnimationFrame — Type
AnimationFrameOne logged decision, in playback form. Every dynamic value here is a copy of one row of the decision log; nothing is recomputed from a model.
events holds the flags that fired at this decision, so a consumer cannot accidentally reveal a later one.
Duckietown.AnimationSequence — Type
AnimationSequenceOne episode's playback, with the provenance that ties it to the artefact.
fingerprint is a function of the ordered frame identities, so changing one row of evidence changes the identity of the animation.
Duckietown.EpisodeSelection — Type
EpisodeSelectionA stated selection rule and the episode it picked. FJ9.4 forbade choosing a representative episode by eye; the rule is recorded so the choice is reproducible and arguable.
Duckietown.FrameScene — Type
FrameSceneThe drawable world at one frame. Every dynamic quantity is computed from the logged pose by the same geometry the physics uses — get_agent_corners for the footprint, closest_curve_point for the lane frame — and from nothing else.
ducks is deliberately absent; see ANIMATION_ABSENT.
Duckietown.StaticWorld — Type
StaticWorldThe read-only track description: tiles, lane centrelines, stop-sign positions.
FJ8.0 certified map and stop_signs as the shared-by-design, never-written part of a world state, and they are seed-invariant — a test asserts that two different reset seeds produce the same StaticWorld. So this is map loading, not simulation, and nothing episode-specific can leak in through it.
Everything that moves comes from the decision log instead.
Duckietown.animation_absent_lines — Method
animation_absent_lines() -> Vector{String}Duckietown.animation_provenance — Method
animation_provenance(seq) -> Vector{String}Duckietown.animation_sequence — Method
animation_sequence(log, solver, seed) -> AnimationSequenceBuild the playback for one episode from the validated decision log.
horizon is the episode's own length, not a protocol constant: an episode that terminated at 86 decisions has 86 frames and no more. Padding it out to 150 would invent stationary frames that never happened.
Duckietown.events_through — Method
events_through(seq, t) -> Vector{Pair{String,Int}}Every event logged at or before frame t, as name => decision. An event at decision 28 is invisible at frame 27 and visible from frame 28 onward.
Duckietown.frame_caption — Method
frame_caption(seq, t) -> Vector{String}The lines a frame must carry to be re-derivable, including the terminal banner once the episode has ended.
Duckietown.frame_index — Method
frame_index(seq, t) -> IntThe frame of seq to show at absolute decision t: t while the episode is running, and its final frame once it has ended.
Duckietown.frame_scene — Method
frame_scene(static, seq, t; roadmap) -> FrameSceneThe world at frame t, with the ground track grown to exactly t points.
The stop line is drawn in the ego's lane frame, which is what the model measures d_stop against — the FJ9.2 correction, applied per frame because the frame moves with the vehicle.
Duckietown.is_frozen — Method
is_frozen(seq, t) -> BoolWhether seq has already ended at absolute decision t, so the panel is showing a held terminal state rather than a live one.
Duckietown.model_time — Method
model_time(seq, t; frame_skip, dt) -> Float64Model time at frame t, in seconds, by the exact identity (t - 1) * frame_skip * dt.
This is model time, not recorded wall-clock time — the artefact has none. frame_skip and dt are not in the log either, so the caller must supply them from the configuration the protocol used; there is no default that could silently be wrong.
Duckietown.paired_frames — Method
paired_frames(a, b) -> IntFrame count for a side-by-side playback: the longer of the two episodes.
The timeline is the absolute decision index, so decision 40 in one panel is decision 40 in the other. A panel whose episode has ended freezes on its terminal frame with the banner frame_caption produced; it is never looped, restarted, or stretched to match. Normalised progress would align DPW's decision 40 of 80 with TD3's decision 75 of 150, which are not the same decision and were not taken under the same conditions.
Duckietown.select_episode — Method
select_episode(log, solver; rule) -> EpisodeSelectionPick one episode of a solver by an explicit rule, never by inspection.
:median_return— the seed whose episode return is the lower median.:median_length_terminating— among episodes the environment terminated, the lower-median length. Errors if the solver never terminates.:first_stagnation— the lowest seed that performs a full stop and never passes the sign. Errors if no episode does.
Duckietown.series_through — Method
series_through(seq, t, name) -> Vector{Union{Float64,Missing}}The history of one quantity up to frame t. d_stop keeps its missing, so a frame before the sign became a candidate draws a gap rather than a zero.
Duckietown.static_world — Method
static_world(mdp, reference_state; lane_samples) -> StaticWorldExtract the track description. Only map and stop_signs are read from reference_state; its ego pose, ducks, stop memory and RNG are ignored.
view_extent is fixed here, from the map and the signs, and never from the trajectory. A camera fitted to the episode's full extent would hold the vehicle's future in the empty space around it, and a camera refitted per frame would jitter; neither is what the viewer should be reading.
Duckietown.trajectory_through — Method
trajectory_through(seq, t) -> Vector{NTuple{2,Float64}}The ground track as far as frame t, and no further. This is the whole point: at frame 20 the viewer must not see where the vehicle ends up.
Duckietown.DECISION_LOG_REQUIRED — Constant
DECISION_LOG_REQUIREDColumns without which the log cannot be interpreted. A renderer must not be the layer that tolerates a missing column.
Duckietown.DECISION_QUANTITY_CONTRACT — Constant
DECISION_QUANTITY_CONTRACTThe FJ9.6a wish list: what a diagnostic figure would want, and the column expected to supply it. decision_log_audit probes the artefact against this rather than trusting it.
Duckietown.DIAGNOSTIC_EVENT_COLUMNS — Constant
DIAGNOSTIC_EVENT_COLUMNSPoint events. Markers come from these logged indices and from nothing else — FJ9.1 showed how convincing an inferred marker looks while corresponding to nothing the model computes.
Duckietown.AxisMode — Type
AxisModeABSOLUTE_DECISION — x is the decision index, 1..T. NORMALIZED_PROGRESS — x is (k-1)/T, episode progress.
Which one a figure uses changes what it claims, so it is a stated property of the figure, never a rendering detail. Normalised progress is not time: two episodes at progress 0.5 have taken different numbers of decisions.
Duckietown.DecisionFieldItem — Type
DecisionFieldItemOne line of the FJ9.6a contract: a quantity a diagnostic figure would want, the column that supplies it, and what the artefact actually holds.
blanks is the count of rows where the column is empty — a number rather than a sentence, so a consumer never has to read the prose to find out how much of a column is missing.
Duckietown.DecisionLog — Type
DecisionLogThe enriched log, parsed, validated, and fingerprinted by content.
Duckietown.DiagnosticSeries — Type
DiagnosticSeriesOne named quantity over one episode, carrying everything a renderer needs and nothing it has to infer.
Duckietown.EpisodeDiagnostics — Type
EpisodeDiagnosticsOne episode's series, its logged events, its outcome and the provenance that ties a figure back to the artefact it came from.
Duckietown.FieldAvailability — Type
FieldAvailabilityLOGGED — the artefact records the quantity directly. DERIVED_IDENTITY — computed here from logged columns by an exact identity that is part of the schema (a running sum), never by inference. FIELD_ABSENT — the experiment never stored it. Reported as ABSENT and left absent; FJ9.4 and FJ9.5a set the precedent.
Duckietown.SeriesCategory — Type
SeriesCategoryWhich panel a series belongs on. Quantities with different units must not share an axis: normalising heading, speed, reward and model calls onto one axis looks compact and destroys every physical scale on it.
Duckietown.SeriesKind — Type
SeriesKindINSTANTANEOUS — the value at that decision. CUMULATIVE — a running total, derived here and labelled as derived. FLAG — 0/1 state, to be shaded rather than drawn as a line.
Duckietown.decision_audit_table — Method
decision_audit_table(items) -> StringDuckietown.decision_log_audit — Method
decision_log_audit(log) -> Vector{DecisionFieldItem}What the enriched log actually supports (FJ9.6a), probed rather than assumed, one line per wanted quantity plus the derived and absent entries.
The audit is executable and therefore self-invalidating: if a future artefact drops a column, its line changes from LOGGED to ABSENT on its own.
Duckietown.diagnostics_fingerprint — Method
diagnostics_fingerprint(ep; fields, mode) -> StringFigure identity: the source artefact, the episode, the series selected, and the x-axis mode. The same selection over a different artefact is a different figure, and so is a different selection over the same one.
Duckietown.diagnostics_provenance — Method
diagnostics_provenance(ep) -> Vector{String}The lines a figure must carry to be re-derivable.
Duckietown.episode_diagnostics — Method
episode_diagnostics(log, solver, seed) -> EpisodeDiagnosticsExtract one episode. Values are copied from the artefact, never recomputed, and d_stop keeps its missing where the log recorded no candidate.
The outcome is read from the record rather than assumed: a terminal row means ENV_TERMINATED; a final row still reading in_progress means the evaluator stopped at its horizon, HORIZON_REACHED. In this artefact truncated is false on every one of the 16 522 rows, so it carries no signal and is not used to make the distinction.
Duckietown.episode_lengths — Method
episode_lengths(log, solver) -> Vector{Int}Duckietown.load_decision_log — Method
load_decision_log(path) -> DecisionLogRead and validate decisions.csv.
Validation is strict, as in FJ9.4. Every required column must be present; every row must have the full width; and within each (solver, seed) episode the decision indices must run contiguously from 1, with the terminal flag — if it appears at all — appearing only on the final decision. An episode with a gap in it is a corrupt record, not a short episode.
Duckietown.progress_bins — Method
progress_bins(log, solver, column; bins) -> Vector{NamedTuple}Aggregate one column across every episode of a solver by normalised episode progress, using bin membership only.
There is no interpolation: stretching a 42-decision DPW episode onto a 150-point grid would manufacture values that never occurred. Each bin reports its own n, so a bin only short episodes reach is visibly thinner evidence rather than an equally confident line.
Duckietown.series_in — Method
series_in(ep, category) -> Vector{DiagnosticSeries}Duckietown.series_named — Method
series_named(ep, name) -> DiagnosticSeriesDuckietown.NATIVE_RENDER_NOTE — Constant
NATIVE_RENDER_NOTEThe disclaimer every native-render entry point carries: lookalike output for casual use, never parity evidence. Kept as a constant so captions and tests can assert the exact wording.
Duckietown.NativeMeshGroup — Type
NativeMeshGroupOne material group of an OBJ mesh: corner-expanded positions, triangle faces, uv coordinates, and either a texture path or a solid color. Plain tuples so no geometry package is needed in the core.
Duckietown.NativeObject — Type
NativeObjectOne placed OBJ instance: its material groups plus the world transform — uniform scale, rotation angle about +y, translation offset (computed so the mesh's footprint centre lands on the requested position with its base on the floor). A backend applies p -> R(angle) * (scale * p) + offset.
Duckietown.NativeWorld — Type
NativeWorldEverything a 3D backend needs to draw one DuckieWorldState: the textured floor tiles, the placed objects (visible duckies, stop signs, and the ego robot mesh for top-down views), and the two reference camera poses.
Duckietown.duckietown_assets_root — Method
duckietown_assets_root() -> StringRoot of the reference asset tree (the .../duckietown_world/data/gd1 directory holding textures/ and meshes/). Resolution order:
ENV["DUCKIETOWN_ASSETS"], when set;- the conventional
ddm-refconda location under the user's home.
Throws with instructions when neither exists. The assets are the reference implementation's own files; this package does not ship them.
Duckietown.load_obj_groups — Method
load_obj_groups(objpath) -> Vector{NativeMeshGroup}Minimal OBJ + MTL reader. Vertices are expanded per face corner (position and uv duplicated), which tolerates OBJs whose uv indexing is partial — the reference's duckie.obj has 94 uv-carrying faces against 132 position faces, which stricter loaders reject. Faces are grouped by usemtl; materials with map_Kd carry the resolved texture path, others their diffuse Kd color. Quads and larger polygons are fan-triangulated.
When the mtllib target does not exist — the reference's sign_stop.obj names a sign_001.mtl that was never shipped — the reader falls back to the same-basename .mtl next to the OBJ, which is how the reference loader resolves it too.
Duckietown.native_world — Method
native_world(w::DuckieWorldState; assets=duckietown_assets_root()) -> NativeWorldPure scene description of one world state for the native lookalike renderer (render_native in the Makie extension). Duck heights use each duckie's own reference scale; the stop sign and the ego robot mesh use the reference injection heights.
Duckietown.place_object — Method
place_object(objpath, pos, angle, height) -> NativeObjectLoad an OBJ and compute the transform that scales it to height, rotates it about +y by angle, grounds its base, and centres its footprint on pos (the reference convention for map objects).
Duckietown.tile_texture_file — Method
tile_texture_file(spec::TileSpec) -> (filename, rot)Texture file (in the reference's default photos style) and the number of 90° texture rotations for one tile. The straight/curve rotations were calibrated against the reference renderer's own top-down view of small_loop; the curve textures need a half-turn relative to the straight mapping.
Duckietown.PUBLICATION_LAYOUT_VERSION — Constant
PUBLICATION_LAYOUT_VERSIONDuckietown.CaptionRule — Type
CaptionRuleWhat a caption must and must not say. forbidden exists because FJ9.6 found a false claim living happily beside correct numbers; a rule that only requires content cannot catch that.
Duckietown.FigureRole — Type
FigureRoleMAIN_FIGURE — carries an argument the paper cannot make without it. SUPPLEMENTARY — supports a main figure; a video belongs here and never in the main body, because a printed paper has to stand on its own. DIAGNOSTIC_ONLY — an internal check that was never meant for a reader.
Duckietown.PanelSpec — Type
PanelSpecOne panel of a composite: its data, and the metadata its caption is generated from. payload is a live scene/data object — a WorldScene, PolicySlice, SearchSnapshot, EpisodeDiagnostics — never an image.
Duckietown.PublicationArtifact — Type
PublicationArtifactOne line of the FJ9.8a inventory: what exists, what it is for, and whether it is actually on disk.
Duckietown.PublicationComposite — Type
PublicationCompositeA figure: its panels, the caption generated from them, and machine-readable metadata carrying every fingerprint, selection rule and semantic qualifier a reader needs to reproduce or contest it.
Duckietown.caption_rule — Method
caption_rule(figure_id) -> CaptionRuleDuckietown.check_caption — Method
check_caption(composite) -> NamedTupleValidate a caption against its rule. Returns (ok, absent, present), where present lists forbidden phrases that appeared.
Duckietown.figure_episode — Method
figure_episode(summary, td3, dpw, bins, selections) -> PublicationCompositeFigure 4 — episode behaviour and failure mechanisms. This is where the FJ9.6 correction has to survive into print.
Duckietown.figure_model — Method
figure_model(world, projection) -> PublicationCompositeFigure 1 — model and representation. What is modelled, and the separation FJ10 established between the latent world, the privileged projection the policies consume, and the observation/belief layer that does not exist yet.
Duckietown.figure_policy — Method
figure_policy(tabular, td3_v, td3_omega) -> PublicationCompositeFigure 2 — policy structure and ambiguity. A selected-action map alone makes a table look more decisive than it is; the tie and margin surfaces are what show the ambiguity.
Duckietown.figure_search — Method
figure_search(mcts, dpw) -> PublicationCompositeFigure 3 — search behaviour. Both snapshots come from the same frozen state, which is the only reason the panels are comparable at all.
Duckietown.grid_layout — Method
grid_layout(n; columns) -> Vector{NTuple{4,Int}}The default placement: n panels down a fixed number of columns, each one cell. Figures whose panels differ in natural shape override it — a text table in a square cell is mostly whitespace.
Duckietown.inventory_table — Method
inventory_table(items) -> StringDuckietown.provenance_block — Method
provenance_block(composite) -> StringDuckietown.publication_inventory — Method
publication_inventory(root) -> Vector{PublicationArtifact}Every artefact the project has produced (the FJ9.8a inventory), classified before any layout is chosen, and probed for existence rather than assumed.
Not everything belongs in the main body. A composite assembled from "everything we have" is a contact sheet, not an argument.
Duckietown.wrap_text — Method
wrap_text(text, width) -> StringHard-wrap on word boundaries. Makie does not reflow, so an unwrapped caption runs off the canvas and the end of the sentence is simply lost — which for a caption means losing the part that qualifies the claim.
Duckietown.SLICE_FEATURE_SPACE_CAVEAT — Constant
SLICE_FEATURE_SPACE_CAVEATThe sentence a feature-space figure must carry. Held in the core so a renderer cannot forget it or reword it into something weaker.
Duckietown.ContinuousSliceCell — Type
ContinuousSliceCellOne cell of a continuous slice: the two commanded outputs, kept as separate fields rather than folded into one ambiguous symbol.
Duckietown.PolicySlice — Type
PolicySliceA slice as data. fixed is part of the slice's identity, not an annotation: two slices over the same grid with different fixed context are different objects and get different fingerprints, so a stale artefact cannot be mistaken for a current one.
Duckietown.SliceAxis — Type
SliceAxisDuckietown.SliceMode — Type
SliceModeFEATURE_SPACE — axes are policy input features, swept independently. Combinations are not guaranteed to correspond to any reachable latent world state. A future REACHABLE_STATES mode would sample from the world instead; it does not exist yet and must not be implied.
Duckietown.TabularSliceCell — Type
TabularSliceCellOne cell of a tabular slice.
selected_action comes from decide — the FJ7-validated near-tie rule (|Q - max| <= TIE_ATOL, lowest action id) — and never from a plain argmax. qmax is deliberately the raw maximum over the allowed actions, with no tie-breaking mixed in, so the value surface and the policy overlay stay separable. tie_count and q_margin are the ambiguity diagnostics: on the shipped checkpoints 8 689 of 9 000 states are near-tied, so a one-action-per-cell map looks far more decisive than the table actually is.
Duckietown.action_surface — Method
action_surface(slice) -> Matrix{Int}Duckietown.continuous_state_grid — Method
continuous_state_grid(x, y, xs, ys, fixed) -> Matrix{ContinuousState}Duckietown.fixed_context_lines — Method
fixed_context_lines(slice) -> Vector{String}The fixed dimensions, rendered for display. Every dimension not on an axis appears here — a two-dimensional figure that hides the other dimensions is not an honest depiction of a seven- or fifteen-dimensional policy.
Duckietown.margin_surface — Method
margin_surface(slice) -> Matrix{Float64}Q_(1) - Q_(2) over the allowed actions; 0.0 where the top two are tied.
Duckietown.omega_surface — Method
omega_surface(slice) -> Matrix{Float64}Duckietown.policy_slice — Method
policy_slice(policy::QTablePolicy, x, y; xs, ys, fixed, name) -> PolicySliceTabular policy / value / ambiguity slice.
Each cell's RawState goes through the real discretizer — including tracking_error = phi + d — and then through the validated decide. The visualisation therefore inherits FJ7's near-tie semantics instead of re-deriving them, and distinct_states records how many tabular states the grid actually reached, which on a d x phi grid is far fewer than the number of cells.
Duckietown.policy_slice — Method
policy_slice(policy, cfg, x, y; xs, ys, fixed, name) -> PolicySliceContinuous policy slice for a SAC or TD3 actor.
Each cell is encoded with the same encode_continuous_state the policy sees at run time and evaluated through the FJ7-validated actor, so the surface is the policy's own mapping rather than a re-implementation. The two commanded outputs stay in separate fields — v_cmd and omega_cmd — because folding them into one symbol makes the figure ambiguous.
Duckietown.raw_state_grid — Method
raw_state_grid(x, y, xs, ys, fixed) -> Matrix{RawState}Build the RawState of every cell by varying two fields and holding the rest at fixed, defaulting to a straight-lane, no-stop, no-duck situation. The defaults are reported as fixed context, never left implicit.
Duckietown.slice_fingerprint — Method
slice_fingerprint(slice) -> StringStable identity of a slice, including the fixed context. Changing one fixed value changes the fingerprint, which is what stops an old figure from being reused under new assumptions.
Duckietown.slice_summary — Method
slice_summary(slice) -> StringDuckietown.tie_surface — Method
tie_surface(slice) -> Matrix{Int}Number of actions within TIE_ATOL of the best. A one-action-per-cell policy map cannot show this, and without it the map looks more decisive than the table is.
Duckietown.v_surface — Method
v_surface(slice) -> Matrix{Float64}Duckietown.value_surface — Method
value_surface(slice) -> Matrix{Float64}V(s) = max_a Q(s, a) over the allowed actions — the raw maximum, with no tie-breaking applied.
Duckietown.ArtifactProvenance — Type
ArtifactProvenanceWhere the data came from and what it is. Two figures that look alike but come from different experiments must not be interchangeable, so provenance travels with the data rather than in a filename.
Duckietown.EpisodeOutcome — Type
EpisodeOutcomeHow an episode ended. The distinction is load-bearing: FJ8.4b's in_progress means the evaluation horizon was reached while the environment was still running, which is not the same event as the environment terminating, and must not share a marker with it.
Duckietown.EpisodeRecord — Type
EpisodeRecordOne row of the frozen artefact, typed. Field names match the CSV header, so a schema change is a load error rather than a silent shift.
Duckietown.RolloutAggregate — Type
RolloutAggregateEvery episode of the frozen experiment, with its provenance.
Duckietown.RolloutComparison — Type
RolloutComparisonThe six solvers at ONE seed — a paired comparison on one initial condition, which is the structure FJ8.4b was designed around.
Duckietown.artifact_fingerprint — Method
artifact_fingerprint(x) -> StringContent fingerprint of the loaded artefact. Perturbing any value in the file changes it, so a figure cannot be silently re-attributed to different evidence.
Duckietown.comparison_at_seed — Method
comparison_at_seed(aggregate, seed) -> RolloutComparisonThe paired set of episodes at one seed. Throws if the seed is absent — the caller must name a seed that exists rather than receive a quietly empty figure.
Duckietown.comparison_table — Method
comparison_table(aggregate) -> StringDuckietown.load_rollout_artifact — Method
load_rollout_artifact(path; experiment_id, horizon) -> RolloutAggregateRead and validate the frozen episode artefact.
Validation is strict on purpose — a renderer must not be the layer that forgives malformed evidence: the header must match ROLLOUT_ARTIFACT_SCHEMA exactly, every solver must have run the same seed set, and no episode may exceed the declared horizon.
Duckietown.median_return_seed — Method
median_return_seed(aggregate, solver) -> IntThe seed whose return is closest to that solver's median. Offered so a "representative seed" can be chosen by a stated rule rather than by which picture looks most dramatic; the rule used must be recorded with the figure.
Duckietown.outcome — Method
outcome(record) -> EpisodeOutcomeHORIZON_REACHED when the run stopped because the evaluation horizon expired with the environment still in progress; ENV_TERMINATED when the environment itself ended the episode.
Duckietown.paired_metric — Method
paired_metric(aggregate, metric) -> (seeds, Dict{String,Vector{Float64}})One vector per solver, aligned to a shared, sorted seed list. Column k of every vector is the same initial condition, which is what makes a paired difference meaningful.
Duckietown.provenance_lines — Method
provenance_lines(p) -> Vector{String}The provenance block a figure must carry.
Duckietown.solver_summary — Method
solver_summary(aggregate, solver) -> NamedTupleEverything the episode-level artefact supports for one solver, with not-applicable preserved.
Duckietown.stop_compliance_of — Method
stop_compliance_of(records) -> Union{Nothing,Float64}Compliant stop encounters over stop encounters. nothing when no stop sign was ever reached — TD3 in FJ8.4b — and the renderer must carry that through as N/A. Turning a missing rate into 0.0 would invent a failure; turning it into 1.0 would invent a success.
Duckietown.PROJECTION_PANEL_TITLE — Constant
PROJECTION_PANEL_TITLEThe label the projection panel must carry. FJ10 established that the 15-D vector is a privileged policy input, not an observation; a panel that omits this quietly undoes that distinction.
Duckietown.ProjectionCategory — Type
ProjectionCategoryWhich subsystem a component belongs to. Orthogonal to its privilege class: sigma_stop is part of the stop subsystem and is agent memory.
Duckietown.ProjectionEntry — Type
ProjectionEntryOne row of the panel, fully specified by the core: display order, the field it came from, its raw value, a rendered string, its unit, its subsystem and its FJ10 privilege class.
The backend must not decide any of these. Labels, units and especially the privilege classification are package semantics, not visual style — a renderer that invented them could quietly disagree with FJ10.
Duckietown.ProjectionScene — Type
ProjectionSceneThe panel as data. source records provenance: the values are read from the ContinuousState object given, the same one the SAC/TD3 encoder consumes, and are never recomputed from the world state here.
Duckietown.TilePatch — Type
TilePatchOne map tile as a closed polygon in world coordinates, with the classification a renderer needs to colour it.
Duckietown.WorldScene — Type
WorldSceneEverything needed to draw one frame, in world coordinates (x, z), computed from the model. A backend consumes this and adds nothing of its own.
ego_footprint is the true collision polygon (get_agent_corners), not a decorative rectangle, so what is drawn is what the physics uses.
Duckietown._view_extent — Method
_view_extent(map_extent, groups...; margin) -> (xmin, xmax, zmin, zmax)An extent covering the map AND everything else drawn.
This matters on small_loop: the injected stop sign sits at z = 1.8135 while the 3x3 grid ends at 1.755, so the sign and its stop line are OUTSIDE the tile grid. That is the reference's own placement — validated in FJ3 map loading and exercised live in FJ5/FJ6 — and a view clipped to the map would silently hide it. Showing it is the honest choice.
Duckietown.lane_centrelines — Method
lane_centrelines(map; samples) -> Vector{Vector{NTuple{2,Float64}}}Every drivable tile's lane curves, sampled in world coordinates. These are the same Bezier curves the observer uses for d and phi, so the drawn lane is the lane the model measures against.
Duckietown.projection_rows — Method
projection_rows(raw, cont) -> Vector{NamedTuple}Flat view of projection_scene, kept for callers that only want label/value/class triples.
Duckietown.projection_scene — Method
projection_scene(raw, cont) -> ProjectionSceneBuild the panel from the projections the model already computed.
Values are read straight off cont — the very object encode_continuous_state turns into the SAC/TD3 input vector — so the panel cannot drift from what the policy actually saw. The display order is fieldnames(ContinuousState), which is the order of the encoded vector, so row k of the panel is component k of the policy input.
Duckietown.render_animation — Function
render_animation(static, sequence, path; kwargs...)Write the playback of one episode to path (.mp4 or .gif). (Validated in FJ9.7.)
Duckietown.render_composite — Function
render_composite(composite; kwargs...)A publication composite, drawn from the same data objects the individual renderers consume. Never an assembly of pre-rendered images. (Validated in FJ9.8.)
Duckietown.render_diagnostics — Function
render_diagnostics(episode; kwargs...)One episode's diagnostic time series, as five separate panels. Takes an EpisodeDiagnostics built from the frozen decision log, so the figure cannot be produced by running the environment. (Validated in FJ9.6c.)
Duckietown.render_diagnostics_aggregate — Function
render_diagnostics_aggregate(log, solvers; kwargs...)Paired and aggregate diagnostics across every episode of each solver, binned by normalised progress with no interpolation. (Validated in FJ9.6d.)
Duckietown.render_frame — Function
render_frame(static, sequence, t; kwargs...)One animation frame: the world at decision t, the history up to t, and nothing after it. Takes recorded evidence only. (Validated in FJ9.7b.)
Duckietown.render_native — Function
render_native(w::DuckieWorldState; view=:ego, size=(800, 600), kwargs...)Native LOOKALIKE render of the latent world using the reference simulator's own texture and mesh assets: view = :ego gives the robot's forward camera (reference constants: fov 75°, height 0.108 m), view = :bev the top-down view. Requires a rasterising Makie backend (using GLMakie) — CairoMakie cannot texture-map meshes per pixel. See NATIVE_RENDER_NOTE: this output is for casual, Python-free use and is never parity evidence.
Duckietown.render_paired_animation — Function
render_paired_animation(static, a, b, path; kwargs...)Two solvers on the same seed, side by side on the absolute decision index. A panel whose episode has ended freezes on its terminal frame. (Validated in FJ9.7d.)
Duckietown.render_policy — Function
render_policy(policy, mdp; kwargs...)Policy or value slice over two chosen state dimensions, with every other dimension reported as fixed context.
Duckietown.render_projection — Function
render_projection(raw, cont; kwargs...)Panel of the model's own projections. These are privileged quantities, not sensor observations — FJ10 measured that only 6 of the 15 continuous components could come from a sensor at all — and the panel must say so.
Duckietown.render_rollout — Function
render_rollout(aggregate_or_comparison; kwargs...)Draw a rollout comparison built from a FROZEN experiment artefact. Takes the loaded, validated data — never a path, never a model — so a figure cannot be produced by re-running anything.
Duckietown.render_search — Function
render_search(snapshot; kwargs...)Draw a SearchSnapshot. Takes the solver-neutral snapshot, never a solver's own tree type, so this function never imports a planning library.
Duckietown.render_search_action_plane — Function
render_search_action_plane(snapshot; kwargs...)The continuous root actions a search actually sampled. Only sampled points are drawn; a smoothed surface would imply the planner evaluated action combinations it never tried. (Validated in FJ9.5d.)
Duckietown.render_world — Function
render_world(mdp, state; kwargs...)Top-down view of the latent world. Requires a Makie backend to be loaded (using CairoMakie); without one this throws a method error, which is the intended behaviour for a package that does not depend on a plotting library.
Duckietown.stop_line_segment — Method
stop_line_segment(sign, forward, offset, half_width) -> (x1, z1, x2, z2)The line the model actually measures d_stop against.
There is no stop-line object in the model. next_stop_candidate computes
rel = sign.pos - ego.pos
ahead = dot(rel, forward) # forward = the EGO's lane frame
d_stop = max(0, ahead - sign_to_line_offset)so the "stop line" is the locus of points at along-track offset sign_to_line_offset before the sign, measured along the ego's direction of travel — not along the sign's own facing. Its width is the model's own acceptance gate, stop_lateral_limit, since a sign only counts while |dot(rel, right)| <= stop_lateral_limit.
Getting this wrong is the exact failure this gate exists to prevent: an earlier version of this function offset along the sign's facing, which produced a plausible red line in a plausible place that corresponded to nothing the model computes.
Duckietown.tile_patches — Method
tile_patches(map) -> Vector{TilePatch}Duckietown.trajectory_points — Method
trajectory_points(states) -> Vector{NTuple{2,Float64}}Ego ground track of a sequence of world states, for the trajectory overlay.
Duckietown.world_scene — Method
world_scene(mdp, state; trajectory) -> WorldSceneExtract every drawable quantity from the model and the latent state. Pure: it reads the state and returns geometry, and mutates nothing.
Duckietown.SearchDataItem — Type
SearchDataItemOne quantity a search figure might need, with what the project actually holds.
Duckietown.SearchDataStatus — Type
SearchDataStatusPERSISTED — recoverable from a stored artefact. AGGREGATE_ONLY — only a summary statistic exists; the underlying structure does not, and cannot be recovered from the summary. ABSENT — not recorded anywhere.
Duckietown.SearchNode — Type
SearchNodeOne node. parent == 0 marks the root. value is missing when the solver does not define one for that node. action is the action that led here from the parent (nothing at the root) and is stored as Any so a snapshot can hold MacroActions or continuous DuckieActions without the renderer caring.
Duckietown.SearchSnapshot — Type
SearchSnapshotA planner's search, in a form no solver owns, with the provenance needed to tie it to one identified decision.
state_fingerprint, config_fingerprint and planner_seed exist so a figure cannot be silently re-attributed: two snapshots of the same shape from different states or configurations are different evidence.
extra carries solver-specific scalars, the same open-slot convention as PlanningDiagnostics, so this type never needs a field for the next planner.
Duckietown.capture_search — Function
capture_search(planner, state; id, planner_seed) -> SearchSnapshotConvert a solver-specific search tree into a SearchSnapshot. Given methods by a solver extension; the core declares it so the conversion has a home without the core knowing any solver type.
Duckietown.check_snapshot — Method
check_snapshot(snapshot; action_space) -> NamedTupleStructural validation, so a malformed snapshot fails where it is built rather than inside a renderer.
Checks: ids are 1:n; exactly one root and it is node 1; every non-root parent exists and precedes its child (no dangling edges); depths follow the parent chain; visits are non-negative; the root carries no action and every other node carries one; and, when a selected_action is recorded, it appears among the root's children.
Pass action_space to also check that continuous actions lie inside the reference box.
Duckietown.load_snapshot — Method
load_snapshot(path) -> SearchSnapshotRead a snapshot back. The stored fingerprint is verified against the recomputed one, so a tampered or truncated file is an error rather than a plausible figure.
Duckietown.root_children — Method
root_children(snapshot) -> Vector{SearchNode}The actions considered at the root, which is what "what did the planner think?" usually means.
Duckietown.save_snapshot — Method
save_snapshot(path, snapshot)Write a snapshot as JSON. missing values are preserved as null and read back as missing, never as 0.0.
Duckietown.search_artifact_audit — Method
search_artifact_audit(dir) -> Vector{SearchDataItem}Probe artifacts/fj8 for everything FJ9.5 would need to draw a search.
Executable rather than prose, like the FJ10 audit: if FJ9.5b later captures snapshots, this reports PERSISTED and the test pinning the current answer fails until the finding is updated.
Duckietown.search_audit_table — Method
search_audit_table(items) -> StringDuckietown.search_max_depth — Method
search_max_depth(snapshot) -> IntDuckietown.search_statistics — Method
search_statistics(snapshot) -> NamedTupleHow the search spent its budget at the root, computed from the snapshot alone.
single_visit_fraction is the quantity that distinguishes a search which evaluated its actions from one which merely sampled them: the share of root actions visited exactly once.
Duckietown.search_summary — Method
search_summary(snapshot) -> StringDuckietown.search_visualisation_supported — Method
search_visualisation_supported(items) -> BoolWhether a faithful search figure can be drawn from what exists today.
Duckietown.snapshot_fingerprint — Method
snapshot_fingerprint(snapshot) -> StringContent fingerprint over the tree AND its provenance. Changing any parent, action, visit count or value changes it.
Duckietown.state_fingerprint — Method
state_fingerprint(state) -> StringDeterministic fingerprint of a latent world state, so a snapshot can name the state it searched from without embedding it.
Duckietown.visible_nodes — Method
visible_nodes(snapshot; max_depth, min_visits, top_k) -> Vector{Int}Node ids a renderer should draw. Display filtering only — the snapshot is never modified, and every filter is a property of the figure rather than of the evidence. A node is kept only if its parent is kept, so the drawn subtree stays connected.