Repository navigation
Expand file tree
/
Copy pathhost.cpp
More file actions
116 lines (101 loc) · 5.53 KB
/
Copy pathhost.cpp
File metadata and controls
116 lines (101 loc) · 5.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// ─────────────────────────────────────────────────────────────────────────────
// A hand-written host for the generated drivetrain model.
//
// This file is the regression test for properties that are otherwise easy to
// break silently:
//
// 1. The public interface is a real seam. `settings()` / `init()` /
// `inputs()` / `tick()` / `outputs()` / `finish()` are what the emitted
// main.cpp uses, so a host written against Drivetrain.hpp cannot drift
// away from what the example host does.
// 2. The root outputs are the ports, read through their wires: `ws` and
// `torque_cut` as Corner declares them, updated every tick.
// 3. The block offset tables (§15.6) address the blocks they describe.
//
// 3 is about Sim::Impl, which the public header leaves incomplete, so
// this program #includes Drivetrain.cpp and is compiled on its own: the white
// box and the public interface in one translation unit. The settings loader's
// own checks are in settings_host.cpp.
//
// It also drives the root boundary, which the example host does not: the sim
// file has no syntax for supplying `axle_torque`, so a batch run leaves it at
// zero and the wheel never turns.
//
// Built and run by tests/run.ps1. Exit status 0 means every check passed.
// ─────────────────────────────────────────────────────────────────────────────
#include "Drivetrain.cpp"
#include <cmath>
#include <cstdio>
#include <string>
namespace {
int failures = 0;
void check(bool ok, const char* what) {
if (ok) return;
std::fprintf(stderr, "host: FAIL %s\n", what);
++failures;
}
} // namespace
int main() {
se_rt::logs().configure("", se_rt::Level::Warn);
se_rt::realtime().configure(0.0, 0.0); // batch: do not spin on frames
// ── the block layout is self-describing (§15.6) ──────────────────────────
{
static drivetrain::Sim::Impl blocks;
std::size_t n_sig = 0;
const se_rt::Slot* sigs = blocks.signal_map(n_sig);
check(n_sig == 6, "signal_map has one slot per scalar wire and boundary input");
for (std::size_t i = 0; i < n_sig; ++i)
check(sigs[i].offset + sigs[i].size <= sizeof(drivetrain::Sim::Impl::Signals),
"every signal slot lies inside the block");
// The offsets are real: writing through the table must be visible
// through the named member, which is the whole promise an inspector
// relies on.
for (std::size_t i = 0; i < n_sig; ++i) {
if (std::string(sigs[i].path) != "in.ground_speed") continue;
auto* p = reinterpret_cast<double*>(reinterpret_cast<unsigned char*>(&blocks.sig) +
sigs[i].offset);
*p = 12.25;
check(blocks.sig.in.ground_speed == 12.25, "signal_map offsets address the block");
check(std::string(sigs[i].unit) == "m/s", "signal_map carries the declared unit");
}
std::size_t n_dis = 0;
const se_rt::Slot* discretes = blocks.discrete_map(n_dis);
check(n_dis == 1, "discrete_map lists tc.cut");
check(std::string(discretes[0].path) == "tc.cut", "discrete slot is addressed by path");
}
// ── the public interface, exactly as a host sees it ──────────────────────
// Units are the header's: axle_torque in N*m, ground_speed in m/s.
drivetrain::Sim sim;
check(sim.settings().wheel_inertia == 0.95, "settings() start at the .sim's values");
check(sim.init(), "init() starts the run");
check(sim.time() == 0.0, "time starts at zero");
check(!sim.load_settings("whl.radius = 0.3 (m);"), "a load after init() is refused (§6.2b)");
check(std::string(sim.error()).find("after init()") != std::string::npos,
"and error() says configuration is over");
double cut_at_last_change = 0.0;
std::uint64_t changes_total = 0, clock_slips = 0;
while (!sim.done()) {
const std::uint64_t k = sim.tick_index();
if (sim.time() != static_cast<double>(k) * drivetrain::Sim::step_seconds) ++clock_slips;
sim.inputs().axle_torque = 250.0;
sim.inputs().ground_speed = 22.0;
const double before = sim.outputs().torque_cut;
sim.tick();
const double after = sim.outputs().torque_cut;
if (after != before) {
++changes_total;
cut_at_last_change = after;
}
}
check(sim.finish() == 0, "a clean run finishes with 0");
check(clock_slips == 0, "time() is the instant the next tick() samples");
check(changes_total > 0, "the traction controller actually did something");
check(cut_at_last_change > 0.0, "a positive slip produced a positive torque cut");
// With the boundary driven, the wheel integrates and slip is finite —
// the failure the example host cannot avoid.
check(std::isfinite(sim.outputs().ws.slip), "slip is finite once ground_speed is driven");
check(sim.outputs().ws.speed > 0.0, "the wheel spun up under a positive axle torque");
check(std::fabs(sim.time() - 5.0) < 1e-9, "the run reached its configured duration");
if (failures == 0) std::printf("host: all checks passed\n");
return failures == 0 ? 0 : 1;
}