Dynamics & world

The world state, ego physics, map geometry, pedestrians, collisions.

Duckietown._actual_center — Method
_actual_center(pos, angle) -> Vector{Float64}

Geometric centre of the agent (camera-to-wheelbase offset): pos + (CAMERA_FORWARD_DIST − ROBOT_LENGTH/2)·dir_vec (simulator._actual_center).

source
Duckietown._collision — Method
_collision(map, agent_corners, ducks) -> Bool

Simulator._collision: SAT against all stacked static objects, then a per-object SAT against every dynamic duckie.

source
Duckietown._inconvenient_spawn — Function
_inconvenient_spawn(map, pos, ducks) -> Bool

Simulator._inconvenient_spawn: any visible object is within max(|max_coords|) · 0.5 · scale + MIN_SPAWN_OBJ_DIST of the candidate pose (3D norm; max_coords is the mesh bounding-box corner).

source
Duckietown._valid_pose — Function
_valid_pose(map, pos, angle, safety_factor=1.0) -> Bool

Simulator._valid_pose: geometric centre, both wheels and the front of the agent on drivable tiles, and no collision with static or dynamic objects.

source
Duckietown.agent_boundbox — Method
agent_boundbox(true_pos, width, length, f_vec, r_vec) -> Matrix{Float64}

Agent bounding box (4×2 x/z corners; collision.agent_boundbox, verbatim). Order: rear-left, rear-right, front-right, front-left (front at the end).

source
Duckietown.calculate_safety_radius — Method
calculate_safety_radius(min_coords, max_coords, scale) -> Float64

objects.calculate_safety_radius: norm of the per-axis maximum absolute extent (x/z), scaled.

The reference mesh bounding boxes are float32 (duckietown-world obj meshes), and np.max([abs(min), abs(max)], axis=0) keeps that dtype, so the whole norm is computed in float32 before promotion by the float64 scale. The norm is evaluated as sqrt(x² + z²) (NumPy's naive reduction), not the BLAS hypot-style norm.

source
Duckietown.corner_matrix — Method
corner_matrix(corners) -> Matrix{Float64}

Convert a Vector{NTuple{2,Float64}} corner list into the 4×2 matrix form.

source
Duckietown.cov_bias — Method
cov_bias(corners) -> Matrix{Float64}

Population covariance (np.cov(rowvar=false, bias=true)) of the 4×2 corner matrix: 1/4 of the centred outer products, summed over corners.

source
Duckietown.generate_corners — Method
generate_corners(pos, min_coords, max_coords, theta, scale) -> Matrix

Object bounding-box corners (4×2, x/z columns) in the exact order of objects.generate_corners: (min,min), (max,min), (max,max), (min,max), rotated about the object anchor.

source
Duckietown.generate_norm — Method
generate_norm(corners) -> Matrix{Float64}

Two orthogonal axis normals (2×2) of a rectangle given its corners in generate_corners order.

Python computes np.cov(corners, rowvar=false, bias=true) followed by np.linalg.eig and returns vect.T (eigenvectors as rows). np.linalg.eig is LAPACK dgeev, whose eigenvector ORDER (natural, NOT sorted by eigenvalue) and SIGN conventions are part of the recorded reference state (DuckieObj.obj_norm is compared bit-level in the duckie parity fixtures), so Julia calls the same LAPACK driver rather than an analytic formula. An earlier analytic implementation produced axes that were equivalent for the SAT tests but ordered/signed differently — a real FJ3.4 state-parity bug that the fixture comparison caught.

source
Duckietown.heading_vec — Method
heading_vec(angle) -> NTuple{3,Float64}

Forward vector of an object at world heading angle: (cos, 0, -sin) (objects.heading_vec, verbatim).

source
Duckietown.intersects — Method
intersects(agent_corners, objs_stacked, agent_norm, norms_stacked) -> Bool

Tensor SAT against N stacked static objects (simulator.intersects, verbatim: every pair of projection intervals must overlap for a collision).

source
Duckietown.intersects_single_obj — Method
intersects_single_obj(agent_corners, obj_corners_T, agent_norm, obj_norm) -> Bool

SAT against one dynamic object (objects.intersects_single_obj, verbatim; obj_corners_T is the object's 2×4 corner matrix).

source
Duckietown.rotate_point — Method
rotate_point(px, py, cx, cy, theta) -> (x, z)

Rotate a 2D point around a center (graphics.rotate_point, verbatim).

source
Duckietown.tensor_sat_test — Method
tensor_sat_test(norm, corners) -> (mins, maxs)

Projection intervals of corners (4×2 or stacked) on each axis of norm (2×2), min/max over the corner axis (collision.tensor_sat_test, verbatim).

source
Duckietown.tile_corners — Method
tile_corners(pos2, width) -> Matrix{Float64}

Absolute corners of a tile given grid coordinates (i, j) and tile width (simulator.tile_corners, verbatim: [px·w − w, px·w + w, ...]).

source
Duckietown.DB18Parameters — Type
DB18Parameters

Nominal DB18 platform model: autonomous response (u1..w3), forced response gains (uar..wal), wheel radii R = 0.067/2, wheel distance D = 0.1, encoder resolution 2π/135.

source
Duckietown.DelayedCommand — Type
DelayedCommand(idx, told, used)

Selection result: 0-based idx, the timestamp at the decision boundary (told), and the command (motor_left, motor_right) that gets applied.

source
Duckietown.axis_observed_ticks — Method
axis_observed_ticks(axis_rad, p) -> (left, right)

Encoder ticks int(round(axis_rad / resolution)) (Python round = round-half-to-even), used for wheel-speed observations.

source
Duckietown.cartesian_from_weird — Method
cartesian_from_weird(pos, angle, grid_height, tile_size) -> 3×3 Matrix

pos = (x, y, z) -> SE(2) pose with cp = (x, grid_height * tile_size - z).

source
Duckietown.db18_model — Method
db18_model(p, commands, u, w) -> (x_ddot_long, x_ddot_ang)

Body acceleration from motor commands and previous (u = longitudinal, w = angular) velocities (DynamicModel.model, verbatim). Commands are (motor_left, motor_right), each clipped to [-1, 1].

source
Duckietown.ego_tick — Method
ego_tick(world, action) -> DuckieWorldState

One 1/30 s physics tick under action = (motor_left, motor_right) (clipped to [-1, 1]); returns a fresh world state (no shared mutation, branch-pure).

source
Duckietown.get_commands_at — Method
get_commands_at(history, t) -> DelayedCommand

bisect_left on the command timestamps with the reference nearest-neighbour tie-break: when the previous timestamp is strictly closer than the next, the previous command is applied. Before the first timestamp, u0 = (0, 0). history entries are (t, u_L, u_R) in increasing t order.

source
Duckietown.initial_ego — Method
initial_ego(pos, angle, grid_height, tile_size) -> DuckieEgoState

Fresh ego at rest: zero se(2) velocity, zero axis radians, empty command history, t0 = 0 (Simulator.reset state construction, verbatim).

source
Duckietown.weird_from_cartesian — Method
weird_from_cartesian(q, grid_height, tile_size) -> ((x, 0, z), angle)

Inverse mapping; angle == pi is normalised to -pi inside translation_angle_from_SE2.

source
Duckietown.LanePosition — Type
LanePosition

Lane-relative pose (simulator.LanePosition): dist signed lateral offset (right negative), dot_dir clipped heading·tangent dot product, angle_deg/angle_rad signed heading error (right negative).

source
Duckietown._normalize_1e12 — Method
_normalize_1e12(vector) -> Vector{Float64}

src/continuous_state.py::_normalize: divide by the Euclidean norm if it is > 1e-12, else return a zero vector (stricter than the > 0 variant used in state.py; both are ported faithfully).

source
Duckietown.bezier_closest — Function
bezier_closest(curve, p, t_bot=0.0, t_top=1.0, n=8) -> Float64

Recursive midpoint search for the parameter of the closest point on a cubic Bezier (graphics.bezier_closest, verbatim: bisection depth 8, d_bot < d_top keeps the lower half).

source
Duckietown.bezier_point — Method
bezier_point(curve, t) -> Vector{Float64}

Cubic Bezier point at parameter t ∈ [0, 1] for a 4×3 control-point matrix (rows = points, columns = x,y,z). Verbatim arithmetic port of gym_duckietown.graphics.bezier_point (pinned duckietown-gym-daffy-6.1.34): the same term order, so results are bit-identical for identical inputs (FJ2 parity fixture).

source
Duckietown.bezier_tangent — Method
bezier_tangent(curve, t) -> Vector{Float64}

Unit tangent at parameter t (first derivative of the cubic Bezier), ported verbatim from gym_duckietown.graphics.bezier_tangent — including the unprotected division by the norm: a zero-length segment yields NaN entries, exactly as NumPy's p /= norm does (IEEE semantics).

source
Duckietown.closest_curve_point — Method
closest_curve_point(map, pos, angle) -> (Union{Nothing,Vector{Float64}}, Union{Nothing,Vector{Float64}})

Simulator.closest_curve_point: closest point and unit tangent on the lane curve best aligned with the heading (largest dot(curve_heading, dir_vec)), or (nothing, nothing) off-drivable tiles.

curve_headings are normalised by the single Frobenius norm of the whole stack (NumPy semantics), exactly like the reference.

source
Duckietown.curve_matrix — Method
curve_matrix(curve) -> Matrix{Float64}

Convert one Vector{NTuple{3,Float64}} control polygon (the form stored in TileSpec.curves) into the 4×3 matrix the Bezier helpers expect.

source
Duckietown.curve_signed_curvature — Method
curve_signed_curvature(curve, samples=33, straight_angle_threshold=0.05) -> Float64

Average signed curvature of a directed Bezier lane: heading change between t = 0.05 and t = 0.95 divided by the arc length of samples sampled points (src/continuous_state.py::curve_signed_curvature). The sign is the y-component of the tangent cross product (left turns positive). Headings with |Δ| ≤ straight_angle_threshold are 0.0 (straight); zero arc length yields 0.0; samples < 3 raises ArgumentError (Python: ValueError). NaN propagation from degenerate control polygons is IEEE-identical.

source
Duckietown.get_dir_vec — Method
get_dir_vec(angle) -> NTuple{3,Float64}

Forward vector (cos, 0, -sin) (simulator.get_dir_vec, verbatim).

source
Duckietown.get_lane_pos2 — Method
get_lane_pos2(map, pos, angle) -> LanePosition

Simulator.get_lane_pos2: signed lateral distance and signed heading error relative to the closest point of the right-lane curve. Throws NotInLane off-lane (the reference raises it for non-drivable tiles).

source
Duckietown._get_tile — Method
_get_tile(map, i, j) -> Union{Nothing,TileSpec}

Simulator._get_tile: bounds-checked lookup with 0-based grid coordinates (i, j) (matching get_grid_coords); returns nothing for tiles outside the map.

source
Duckietown.get_grid_coords — Method
get_grid_coords(map, pos) -> (Int, Int)

Simulator.get_grid_coords: (floor(x/ts), floor(z/ts)), potentially outside the grid.

source
Duckietown.interpret_object_desc — Method
interpret_object_desc(; kind, pos, rotate_deg, height=nothing, scale=nothing,
    static=true, optional=false) -> MapObjectData

DuckietownEnv.interpret_object for the descriptors produced by duckduck.src.duck_controller.prepare_task_map_data: pos is already the world-frame anchor, rotate_deg the world yaw in degrees, and scale is height / mesh.max_coords[2] when height is given (else scale). Throws ArgumentError when both height and scale are given (Python: assert ... cannot specify both height and scale).

source
Duckietown.map_kind_symbol — Method
map_kind_symbol(kind_s) -> Symbol

Simulator tile-kind string → canonical symbol: 3way_left/3way_right/ 4way become :three_way_left/:three_way_right/:four_way. (Not to be confused with state_projection.classify_tile → TileType, the FJ2 lane-class projection.)

source
Duckietown.object_world_pose — Method
object_world_pose(grid_width, grid_height, tile_size, tile_pos, rotate_deg)
    -> (pos3, angle)

Map-frame object descriptor (pos in tile units, rotate in degrees) to the simulator's world anchor, verbatim per Simulator.interpret_object -> duckietown_world.map_loading.get_transform -> weird_from_cartesian:

x_cart = pos[1] * ts
y_cart = (grid_width - 1 - pos[2]) * ts     # non-righthanded frame
angle  = -deg2rad(rotate_deg)
world  = (x_cart, 0, grid_height * ts - y_cart)

For the square small_loop (3x3) this reduces to (pos[1] * ts, 0, (pos[2] + 1) * ts), which is where the injected stop sign [1.20, 2.10] -> (0.702, 1.8135) and duckie [1.62, 0.50] -> (0.9477, 0.8775) come from.

source
Duckietown.parse_map_tiles — Method
parse_map_tiles(tiles, tile_size) -> Matrix{TileSpec}

Simulator._interpret_map for the grid only: each tile string ("curve_left/W", "asphalt", "empty", ...) becomes a TileSpec with its directed lane curves (drivable tiles only).

source
Duckietown.small_loop_map — Method
small_loop_map() -> RoadMap

The canonical experiment map: small_loop, tilesize 0.585, plus the injected stop sign at stopspawnpos [1.20, 2.10] rotated 180° (duckduck controller defaults; the duckie itself is dynamic state created by [`initialstate`](@ref)).

source
Duckietown.small_loop_tiles — Method
small_loop_tiles() -> Matrix{String}

The small_loop map as tile strings, transcribed verbatim from duckietown_world/data/gd1/maps/small_loop.yaml (3×3, tilesize 0.585). Rows are j (z) rows, matching `interpret_map'stiles` layout.

source
Duckietown.tile_curves — Method
tile_curves(kind, angle_index, i, j, tile_size) -> Array{Float64,3}

Simulator._get_curve: the tile's directed lane curves in world coordinates. angle_index is the tile orientation index (0..3, directions.index). The 4way template is rotated 4 times (rot·π/2 for rot ∈ 0:3); all others once by (angle_index·π)/2. Returns an (N,4,3) stack.

source
Duckietown.activate_duck — Method
activate_duck(world, i) -> DuckieWorldState

Controller activation: the duck starts walking (time = 0) and crossings_started[i] increments (DuckController.before_step core).

source
Duckietown.before_step — Method
before_step(world, cfg, rng) -> DuckieWorldState
before_step(world, cfg)      -> DuckieWorldState

Decision-start trigger pass (DuckController.before_step, verbatim): pins wait_time to Inf for every inactive duck, then for each inactive, under-limit duck computes the crossing midpoint start + 0.5 * walk_distance * heading * sign(vel) and activates it when its distance to the ego is inside [trigger_min, trigger_max], the lane tangent at the ego pose says the crossing lies ahead, and the p_cross draw passes. Re-arming follows repeat_rearm_distance (0 disarms the limit test only via max_crossings_per_episode).

The 3-argument form draws from the external rng (mutating it) and never touches world.controller_rng — this is the generative-MDP semantics used by simulate_decision: stochasticity is supplied by the caller, not stored in the state. The RNG is consumed only on a fully eligible duck (inactive, under limit, armed, in the trigger window, crossing ahead), matching the reference call semantics draw-for-draw. The 2-argument form keeps the FJ3.5 stream-in-state behaviour: it draws from a copy of world.controller_rng and returns the advanced copy in the new world.

source
Duckietown.duck_step — Function
duck_step(world, i, dt=EGO_DT) -> DuckieWorldState

One physics tick of duck i (DuckieObj.step, verbatim). Returns a fresh world state; inactive ducks only count down wait_time.

source
Duckietown.replace_duck — Method
replace_duck(world, i, duck) -> DuckieWorldState

Fresh world with ducks[i] replaced; every other field is shared (the transition chain copies ducks before mutating, keeping branches pure).

source
Duckietown.DuckieEgoState — Type
DuckieEgoState

Minimal Markov-sufficient ego state of the simulator (FJ0 audit section C).

The DB18 motor model runs delayed dynamics (ApplyDelay, 0.15 s): the command applied at time t is the wheel command issued at t - 0.15. The delay window spans roughly 5 physics ticks (dt = 1/30 s), and therefore reaches into the previous decision when frame_skip = 6. Without the command history, the generative process would not be Markov — this field is the reason DuckieWorldState is branchable for MCTS/DPW.

  • pos: simulator cur_pos, (x, 0, z) in world metres.
  • angle: simulator cur_angle, world heading (rad).
  • v_long, omega: DB18 body-state linear/angular velocity of the underlying DynamicModel.v0.
  • speed: simulator self.speed = norm(cur_pos - prev_pos) / delta_time (displacement speed of the last tick, NOT v_long) — this is the v the raw-state extraction observes (env.speed in get_raw_state).
  • step_count: physics ticks elapsed; timestamp: seconds elapsed.
  • command_history: (t, u_L, u_R) wheel-command log over the delay window.
  • q0: 3×3 SE(2) pose in cartesian coordinates (DB18 axis), i.e. the reference DynamicModel.q0.
  • v0: 3×3 se(2) body velocity of the last tick (the reference v0).
  • axis_left_rad/axis_right_rad: accumulated wheel angles in radians.
source
Duckietown.DuckieState — Type
DuckieState

Full pedestrian (DuckieObj) sub-state.

  • pos, center, start: object anchor, walking position, walking origin.
  • angle, heading: object yaw and forward vector (cos, 0, -sin).
  • vel: walking speed magnitude (0.02 m/s in this setup); walk_distance: distance after which finish_walk flips heading and velocity.
  • pedestrian_active: walking flag; pedestrian_wait_time: countdown that the DuckController pins to Inf before every decision (no autonomous activation); time: seconds since activation.
  • obj_corners: 2D (x, z) SAT corners (shifted during walking and restored by the controller reset — hence state, not derived data); obj_norm: 2×2 SAT axis normals; scale/safety_radius/min_coords/max_coords: mesh extent used by spawn checks and proximity rewards.
source
Duckietown.DuckieWorldState — Type
DuckieWorldState

The canonical branchable latent dynamics state for generative planning (FJ0 audit section C; user constraint #2). It contains everything needed to reproduce the next decision deterministically:

  • the ego (including the delayed-command window),
  • all duckie objects and the crossing controller counters,
  • the stop-sign poses, the stop memory, and the lane-fallback memory,
  • the static map, and the controller RNG stream.

Projections onto the solver-facing states are pure functions: RawState = f_tab(world) and ContinuousState = f_cont(world) (FJ2/FJ3).

controller_rng is stored as a native MersenneTwister for now. Note: the Python stream is np.random.RandomState (MT19937 with legacy seeding); the exact stream compatibility needed for seed-exact parity is scheduled for FJ3 dynamics work.

source
Duckietown.MapObjectData — Type
MapObjectData

Static object descriptor resolved during initial-state construction (FJ3.1).

Mirrors the fields the simulator carries per collidable WorldObj:

  • pos, angle, scale: object anchor pose (world frame).
  • static, optional, visible: map-level flags (visible flips only under domain randomization).
  • min_coords, max_coords: mesh bounding-box corners; safety_radius: SAFETY_RAD_MULT · norm(max(|min|, |max|) on x/z) · scale.
  • corners: 4×2 SAT corners (objects.generate_corners order); norm: 2×2 axis normals; heading: forward vector (cos, 0, -sin).
source
Duckietown.RoadMap — Type
RoadMap

Static map data of one world (small_loop in all four experiments): tile size, grid of TileSpec, and the static collidable objects (sign_stop; duckies are dynamic state in DuckieWorldState). The map is immutable and shared across branch copies.

source
Duckietown.StopMemory — Type
StopMemory

Wrapper-side stop memory needed by the reward process:

  • sigma_stop, hold_steps: the StopTracker dwell state (mirrored here so the world state is self-contained for branching).
  • last_stop_id, last_d_stop: previous decision's stop-candidate identity and distance, required by StopTracker.update(prev, curr, prev_id, curr_id) for the "passed" test.
source
Duckietown.StopSignState — Type
StopSignState

Pose of one static sign_stop object. Collision corners derive from the static mesh bounding box (FJ2 collision module).

source
Duckietown.TileSpec — Type
TileSpec

One map tile: kind (Python tile kind, lowercased; e.g. :straight, :curve_left, :curve_right, :3way_left, :4way, :asphalt), rotation in degrees, drivable flag, and the static directed lane curves (each a cubic Bézier control polygon of 4×3 points, in world coordinates).

source
Duckietown.branch — Method
branch(world, ego, ducks) -> DuckieWorldState

Copy a world state for simulated branching, deep-copying every mutable field (command history, duckie corners, counters, RNG stream) so that branch rollouts never alias shared mutation.

source