FJ1 STATUS — Package skeleton, typed data model, config hierarchy, interfaces
Status: READY FOR REVIEW · 2026-08-18 · package Duckietown.jl v0.1.0 (UUID b9206cf4-d873-49ff-bdb0-b4cd6bc56877)
Reference contract: duckduck/docs/src/validation/FJ0_repository_audit.md (accepted audit). FJ1 scope is strictly: skeleton + typed data model + config hierarchy + interface boundaries. No dynamics, MCTS, or visualization implementation is included.
Deliverables
- Package skeleton:
Project.toml(Julia 1.10 compat; POMDPs.jl v1, YAML.jl), main module with dependency-ordered includes, fullsrc/tree with gate-marked stubs (FJ2–FJ4, FJ7–FJ8). - Typed data model mirroring the Python reference:
RawState(7-D tabular) +StateConfig+TileTypeContinuousState(15-D,stop_hold_progresslast) +ContinuousStateConfig+DuckRelativeState+OBSERVATION_NAMESMacroAction(7 macro actions) +DuckieAction+ActionConfigEventFlags,StopTracker(+hold_progress,reset_tracker)RewardConfig(23 fields) +RewardBreakdownDuckieWorldState(canonical branchable dynamics state, mutable),DuckieEgoState(withcommand_historydelay window),DuckieState,StopSignState,StopMemory,TileSpec,RoadMap,branch()deep-copy
- Config hierarchy with authoritative precedence experiment YAML > Python source defaults per missing key, exactly the Python
Config(**yaml_dict)semantics:load_config(path)— typed parse of onetraining_config.yamldefault_config(algorithm)— Python source defaults- validation mirrors Python constructors (
spawn_route_direction,spawn_min_route_alignment ∈ [0,1], spawn bounds min ≤ max) - unknown keys ignored (Python loader behaviour)
- Interface boundaries:
AbstractBackend—reset!,step!,get_raw_state,get_continuous_state; docstring locks the reference transition orderAbstractPolicy—act
Locked constraints (user-approved, binding for all later gates)
- Structural equivalence. The native Julia backend aims for structural equivalence with the audited simulator implementation; numerical equivalence must be established by runtime parity testing (FJ6). Docs must not claim "float rounding level" parity.
- Canonical state.
DuckieWorldStateis the canonical branchable dynamics state;RawStateandContinuousStateare projections. Never define the POMDP canonically asMDP{ContinuousState, ...}. - Reward semantics. SAC/TD3 steering penalty uses pre-action
kappawith clippedomega_cmd;reward(m, s, a, sp)never readssp.kappa. - Transition order (locked): previous state →
before_step→ action → wheels →frame_skipdelayed-DB18 ticks → raw extraction → StopTracker → collision/termination → reward.
Verification
Pkg.instantiate()clean on the UNC path (Windows Julia 1.10.11 driving the WSL checkout); POMDPs.jl v1.0.0.Pkg.test(): 259 assertions pass, 0 fail:load_config parses all four reference configs— 186 assertions pinning every field ofq_learning/sarsa/sac/td3against the real YAMLs (seeds, map, spawn, state/continuous-state blocks, duck controller, rewards, solvers, lane teacher, transition model, training, evaluation)default_config matches Python source defaults— 26 assertions (e.g.stop_orientation_cos 0.70710678,duck_corridor_width 0.35,duck_max_distance 2.0,goal 50.0,max_steer_command 1.5)validation mirrors Python constructors— 4enums match Python integer values— 8OBSERVATION_NAMES is 15 features, hold progress last— 2RawState / ContinuousState construction— 6StopTracker state semantics— 14DuckieWorldState is branchable— 9EventFlags defaults— 4
Notable port decisions (documented in source docstrings)
DuckieWorldStateis mutable sostop_memory/ego/duckscan be swapped on reset and updated in place, mirroring the Python environment;branch()still deep-copies all mutable fields for rollouts.initial_q_tablelives under thetraining:block in the YAMLs, not the solver block; it is therefore aTrainingConfigfield (tabular adapters read it from there in FJ7).DuckieWorldState.controller_rngis a placeholder nativeMersenneTwister; the Python stream isnp.random.RandomState(MT19937 legacy seeding) — exact stream compatibility is an FJ3 concern.- Enums carry the Python integer values (
TileType0–2,DuckThreat0–4,MacroAction0–6) for cross-language encoding parity.
What FJ1 does NOT cover
Native DB18 dynamics, duckie motion, RNG stream parity, POMDPs.jl gen, PythonCall reference backend, solver adapters, visualization, experiments. Those are FJ2–FJ10.
Gate exit criteria
- [x] Skeleton compiles and precompiles cleanly
- [x] All four reference configs parse with full field pinning
- [x] Python source defaults replicated and tested
- [x] Data model constructs, branches, and matches Python integer semantics
- [x] Interface boundaries + transition order documented
- [ ] User acceptance of FJ1 (this document)