///|
/// HIL session state export to JSON trace.
///
/// C-14: Exports HilSession state (SimTime + step_log + schedule info)
/// as a structured JSON trace for post-analysis and visualization.
///
/// Output format:
/// {
/// "version": "1.0",
/// "session": { ... },
/// "schedule": { ... },
/// "steps": [ { "index": N, "result": "...", "sim_time_ns": N }, ... ]
/// }
///|
/// Export a HilSession to a JSON trace string.
pub fn hil_session_to_json(session : HilSession) -> String {
let buf = StringBuilder::new()
buf.write_string("{\n")
buf.write_string(" \"version\": \"1.0\",\n")
// Session metadata
buf.write_string(" \"session\": {\n")
buf.write_string(
" \"sim_time_ns\": " + session.sim_time.to_ns().to_string() + ",\n",
)
buf.write_string(
" \"cycle_period_ns\": " + session.cycle_period_ns.to_string() + ",\n",
)
buf.write_string(
" \"terminated\": " + session.terminated.to_string() + ",\n",
)
buf.write_string(
" \"termination_reason\": \"" +
escape_json(session.termination_reason) +
"\",\n",
)
buf.write_string(
" \"total_steps\": " + session.step_log.length().to_string() + "\n",
)
buf.write_string(" },\n")
// Schedule
buf.write_string(" \"schedule\": {\n")
buf.write_string(
" \"base_period_ns\": " +
session.schedule.base_period_ns.to_string() +
",\n",
)
buf.write_string(" \"tasks\": [\n")
for i, t in session.schedule.tasks {
buf.write_string(" {\n")
buf.write_string(" \"name\": \"" + escape_json(t.name) + "\",\n")
buf.write_string(" \"priority\": " + t.priority.to_string() + ",\n")
buf.write_string(
" \"cycle_divisor\": " + t.cycle_divisor.to_string() + "\n",
)
buf.write_string(" }")
if i < session.schedule.tasks.length() - 1 {
buf.write_string(",")
}
buf.write_string("\n")
}
buf.write_string(" ]\n")
buf.write_string(" },\n")
// Step log
buf.write_string(" \"steps\": [\n")
for i, step in session.step_log {
buf.write_string(" {\n")
buf.write_string(" \"index\": " + i.to_string() + ",\n")
let (status, detail, time_ns) = step_result_to_parts(step)
buf.write_string(" \"status\": \"" + status + "\",\n")
if detail.length() > 0 {
buf.write_string(" \"detail\": \"" + escape_json(detail) + "\",\n")
}
buf.write_string(" \"sim_time_ns\": " + time_ns.to_string() + "\n")
buf.write_string(" }")
if i < session.step_log.length() - 1 {
buf.write_string(",")
}
buf.write_string("\n")
}
buf.write_string(" ]\n")
buf.write_string("}")
buf.to_string()
}
///|
fn step_result_to_parts(
result : @simulation.SimStepResult,
) -> (String, String, Int64) {
match result {
@simulation.SimStepResult::Ok(st) => ("ok", "", st.to_ns())
@simulation.SimStepResult::Terminated(reason) => ("terminated", reason, 0L)
@simulation.SimStepResult::Error(msg) => ("error", msg, 0L)
}
}
///|
fn escape_json(s : String) -> String {
let buf = StringBuilder::new()
let chars = s.to_array()
for c in chars {
match c {
'"' => buf.write_string("\\\"")
'\\' => buf.write_string("\\\\")
'\n' => buf.write_string("\\n")
'\r' => buf.write_string("\\r")
'\t' => buf.write_string("\\t")
_ => buf.write_char(c)
}
}
buf.to_string()
}
///|
/// Summary statistics extracted from a HilSession step log.
pub(all) struct HilSessionSummary {
total_steps : Int
ok_steps : Int
error_steps : Int
terminated : Bool
termination_reason : String
final_sim_time_ns : Int64
} derive(Debug)
///|
pub fn hil_session_summary(session : HilSession) -> HilSessionSummary {
let mut ok = 0
let mut err = 0
let mut final_time = 0L
for step in session.step_log {
match step {
@simulation.SimStepResult::Ok(st) => {
ok = ok + 1
final_time = st.to_ns()
}
@simulation.SimStepResult::Terminated(_) => ok = ok + 1
@simulation.SimStepResult::Error(_) => err = err + 1
}
}
{
total_steps: session.step_log.length(),
ok_steps: ok,
error_steps: err,
terminated: session.terminated,
termination_reason: session.termination_reason,
final_sim_time_ns: final_time,
}
}