135{
139
140 const std::string json_path = base_path + ".json";
141 const std::string exr_path = base_path + ".exr";
142
143 IO::JSONSerializer ser;
144 auto schema_opt = ser.read<State::FabricSchema>(json_path);
145 if (!schema_opt) {
146 m_last_error =
"Failed to load schema: " + ser.last_error();
148 return false;
149 }
150 const auto& schema = *schema_opt;
151
153 m_last_error =
"Unsupported schema version: " + std::to_string(schema.version)
156 return false;
157 }
158
159 if (schema.entities.empty()) {
160 m_last_error =
"Schema contains no entities: " + json_path;
162 return false;
163 }
164
166 auto pv_opt = load_exr(exr_path,
static_cast<uint32_t
>(schema.entities.size()), expected_rows,
m_last_error);
167 if (!pv_opt) {
169 return false;
170 }
171 auto& pv = *pv_opt;
172 const auto*
pixels = pv.image.as_float();
173 const uint32_t
width = pv.width;
174 const auto& r = schema.ranges;
175
176 for (size_t i = 0; i < schema.entities.size(); ++i) {
177 const auto& entry = schema.entities[i];
178
181 "StateDecoder: unknown kind '{}' for id {}, skipping", entry.kind, entry.id);
183 continue;
184 }
185
189
190 const glm::vec3 position {
194 };
195
198 auto e = fabric.get_emitter(entry.id);
199 if (!e) {
201 "StateDecoder: id {} not found as Emitter, skipping", entry.id);
203 continue;
204 }
205 if (!entry.influence_fn_name.empty() && e->fn_name() != entry.influence_fn_name) {
207 "StateDecoder: Emitter {} fn_name mismatch: schema='{}' live='{}'",
208 entry.id, entry.influence_fn_name, e->fn_name());
209 }
210 e->set_position(position);
212 if (entry.color) {
213 e->set_color(glm::vec3 {
217 });
218 }
219 if (entry.size) {
221 }
223 break;
224 }
226 auto s = fabric.get_sensor(entry.id);
227 if (!s) {
229 "StateDecoder: id {} not found as Sensor, skipping", entry.id);
231 continue;
232 }
233 if (!entry.perception_fn_name.empty() && s->fn_name() != entry.perception_fn_name) {
235 "StateDecoder: Sensor {} fn_name mismatch: schema='{}' live='{}'",
236 entry.id, entry.perception_fn_name, s->fn_name());
237 }
238 s->set_position(position);
240 break;
241 }
243 auto a = fabric.get_agent(entry.id);
246 "StateDecoder: id {} not found as Agent, skipping", entry.id);
248 continue;
249 }
250 if (!entry.perception_fn_name.empty() &&
a->perception_fn_name() != entry.perception_fn_name) {
252 "StateDecoder: Agent {} perception_fn_name mismatch: schema='{}' live='{}'",
253 entry.id, entry.perception_fn_name,
a->perception_fn_name());
254 }
255 if (!entry.influence_fn_name.empty() &&
a->influence_fn_name() != entry.influence_fn_name) {
257 "StateDecoder: Agent {} influence_fn_name mismatch: schema='{}' live='{}'",
258 entry.id, entry.influence_fn_name,
a->influence_fn_name());
259 }
260 a->set_position(position);
262 if (entry.color) {
263 a->set_color(glm::vec3 {
267 });
268 }
269 if (entry.size) {
271 }
274
275 if (entry.locus_nav) {
276 if (!apply_locus_nav(
a, *entry.locus_nav)) {
278 "StateDecoder: Agent {} has locus_nav in schema but is not a Locus at runtime",
279 entry.id);
280 }
281 }
282
283 if (
auto presence = std::dynamic_pointer_cast<Presence>(
a)) {
284 if (!entry.falloff_curve_name.empty()) {
285 if (auto fc = Reflect::string_to_enum_case_insensitive<Presence::FalloffCurve>(entry.falloff_curve_name))
286 presence->set_falloff_curve(*fc);
287 }
288 if (entry.falloff_radius)
289 presence->set_falloff_radius(*entry.falloff_radius);
290 }
291 break;
292 }
293 }
294
296 }
297
298 for (const auto& xrec : schema.expanses) {
299 if (xrec.fn_name.empty()) {
301 "StateDecoder: Expanse {} has no fn_name, skipping", xrec.id);
302 continue;
303 }
304 auto contains_fn = fabric.resolve_expanse_fn(xrec.fn_name);
305 if (!contains_fn || !*contains_fn) {
307 "StateDecoder: Expanse {} fn '{}' not in registry, skipping",
308 xrec.id, xrec.fn_name);
309 continue;
310 }
311 auto on_enter_fn = xrec.on_enter_fn_name.empty()
313 : [
ptr = fabric.resolve_crossing_fn(xrec.on_enter_fn_name)](uint32_t id) {
315 (*ptr)(id);
316 };
317 auto on_exit_fn = xrec.on_exit_fn_name.empty()
319 : [
ptr = fabric.resolve_crossing_fn(xrec.on_exit_fn_name)](uint32_t id) {
321 (*ptr)(id);
322 };
323 auto expanse = std::make_shared<Expanse>(
324 xrec.fn_name,
325 xrec.on_enter_fn_name,
326 xrec.on_exit_fn_name,
327 *contains_fn,
328 std::move(on_enter_fn),
329 std::move(on_exit_fn));
330 fabric.add_expanse(std::move(expanse));
331 }
332
334 "StateDecoder: patched {} entities ({} missing) from {} + {}",
336
337 return true;
338}
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
const std::vector< float > * pixels
std::function< void(uint32_t)> CrossingFn
@ FileIO
Filesystem I/O operations.
@ Runtime
General runtime operations (default fallback)
@ Nexus
Spatial indexing and scheduling for user-defined behaviour.
constexpr T denormalize(T t, T lo, T hi) noexcept
Denormalize t from [0, 1] to [lo, hi].
constexpr uint32_t k_schema_version
Current schema version written by StateEncoder and accepted by StateDecoder.
bool kind_known(std::string_view s)
Return true if s maps to a known Fabric::Kind token.
Fabric::Kind parse_kind(std::string_view s)
Parse a JSON kind token to Fabric::Kind (case-insensitive).
constexpr uint32_t k_exr_rows
RGBA32F EXR layout constants shared between encoder and decoder.
constexpr uint32_t k_channels