// dump_catalog // // Loads the whole catalog and writes one JSON document for // tests/game_data/oracle/compare.py: // * every weapon / section with its typed fields (`typed`) and the raw block // rendered in the reference reader's dict shape (`raw`): keys folded, // repeats -> lists, barewords typed; // * the tech tree (nodes, edges, groups), the turret table, id registries; // * the cross_check() report. // Strings are cp1252 in the files; they are decoded to Unicode here because // the oracle JSON was written from decoded text. #include #include #include #include #include #include #include "game/data/catalog.h" #include "mars/parse/value.h" using namespace game::data; namespace { // ---- minimal JSON writer -------------------------------------------------- std::string json_string(std::string_view bytes) { static const unsigned cp1252[32] = { 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F, 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178}; std::string out = "\""; for (unsigned char c : bytes) { switch (c) { case '"': out += "\\\""; break; case '\\': out += "\\\\"; break; case '\n': out += "\\n"; break; case '\r': out += "\\r"; break; case '\t': out += "\\t"; break; default: if (c < 0x20 || c > 0x7e) { unsigned cp = c; if (c >= 0x80 && c <= 0x9f) cp = cp1252[c - 0x80]; char buf[8]; std::snprintf(buf, sizeof buf, "\\u%04x", cp); out += buf; } else { out += static_cast(c); } } } out += '"'; return out; } std::string json_double(double v) { char buf[64]; std::snprintf(buf, sizeof buf, "%.17g", v); std::string s(buf); if (s.find_first_of(".eEn") == std::string::npos) s += ".0"; // keep it a float for Python return s; } struct J { std::string s; }; J jnull() { return {"null"}; } J jbool(bool b) { return {b ? "true" : "false"}; } J jint(long long v) { return {std::to_string(v)}; } J jnum(double v) { return {json_double(v)}; } J jstr(std::string_view v) { return {json_string(v)}; } template J jopt(const std::optional& o); template <> J jopt(const std::optional& o) { return o ? jnum(*o) : jnull(); } template <> J jopt(const std::optional& o) { return o ? jint(*o) : jnull(); } template <> J jopt(const std::optional& o) { return o ? jint(*o) : jnull(); } template <> J jopt(const std::optional& o) { return o ? jbool(*o) : jnull(); } template <> J jopt(const std::optional& o) { return o ? jstr(*o) : jnull(); } struct JArr { std::string s = "["; bool first = true; JArr& push(const J& v) { if (!first) s += ','; first = false; s += v.s; return *this; } J done() const { return {s + "]"}; } }; struct JObj { std::string s = "{"; bool first = true; JObj& set(std::string_view key, const J& v) { if (!first) s += ','; first = false; s += json_string(key); s += ':'; s += v.s; return *this; } J done() const { return {s + "}"}; } }; J jstrs(const std::vector& v) { JArr a; for (const auto& s : v) a.push(jstr(s)); return a.done(); } // ---- raw block in the reference dict shape -------------------------------- J typed_bareword(const std::string& tok) { using mars::parse::ScalarKind; switch (mars::parse::classify(tok)) { case ScalarKind::Int: if (auto v = mars::parse::as_int(tok)) return jint(*v); return jstr(tok); case ScalarKind::Float: return jnum(*mars::parse::as_double(tok)); case ScalarKind::Bool: return jbool(*mars::parse::as_bool(tok)); case ScalarKind::Text: break; } return jstr(tok); } J raw_json(const Block& b) { struct Slot { std::string key; std::vector vals; }; std::vector slots; auto add = [&](const std::string& key, const std::string& val) { for (Slot& s : slots) if (s.key == key) { s.vals.push_back(val); return; } slots.push_back({key, {val}}); }; // merge attrs and sub-blocks in file order std::size_t ai = 0, bi = 0; while (ai < b.attrs.size() || bi < b.blocks.size()) { bool take_attr = bi >= b.blocks.size() || (ai < b.attrs.size() && b.attrs[ai].ord < b.blocks[bi].ord); if (take_attr) { const Attr& a = b.attrs[ai++]; add(fold(a.key), a.quoted ? jstr(a.value).s : typed_bareword(a.value).s); } else { const Block& sub = b.blocks[bi++]; add(fold(sub.name), raw_json(sub).s); } } for (const std::string& it : b.items) add("_items", jstr(it).s); JObj o; for (const Slot& s : slots) { if (s.vals.size() == 1) { o.set(s.key, {s.vals[0]}); } else { JArr a; for (const auto& v : s.vals) a.push({v}); o.set(s.key, a.done()); } } return o.done(); } // ---- typed views ---------------------------------------------------------- J rangetable_json(const RangeTable& t) { JObj o; auto band = [&](const RangeTable::Band& b, const char* p) { std::string pre(p); o.set(pre + "_range", jopt(b.range)); o.set(pre + "_range_dev", jopt(b.deviation)); o.set(pre + "_range_dam", jopt(b.damage)); }; band(t.point_blank, "pb"); band(t.effective, "eff"); band(t.maximum, "max"); return o.done(); } J planet_json(const PlanetDamage& p) { return JObj().set("dam_pop", jopt(p.pop)).set("dam_infra", jopt(p.infra)).set("dam_terra", jopt(p.terra)).done(); } J weapon_json(const WeaponDef& w) { JObj t; t.set("name", jstr(w.name)) .set("weaponclass", jstr(w.weapon_class)) .set("weaponfamily", jstr(w.weapon_family)) .set("weapondamagetype", jstr(w.weapon_damage_type)) .set("requires", jstrs(w.requires_tech)) .set("compatible_section", jstrs(w.compatible_section)) .set("exclusive_species", jstr(w.exclusive_species)) .set("cost", jopt(w.cost)) .set("turretsize", jstr(w.turret_size)) .set("turretclass", jstr(w.turret_class)) .set("trackspeed_mod", jopt(w.track_speed_mod)) .set("burst_volleys", jopt(w.burst_volleys)) .set("recharge_time", jopt(w.recharge_time)) .set("volley_period", jopt(w.volley_period)) .set("volley_duration", jopt(w.volley_duration)) .set("buildup_delay", jopt(w.buildup_delay)) .set("solution_tolerance", jopt(w.solution_tolerance)) .set("range", jopt(w.range)) .set("range_planet", jopt(w.range_planet)) .set("muzzle_speed", jopt(w.muzzle_speed)) .set("hpbonus", jopt(w.hpbonus)) .set("dam_est", jopt(w.dam_est)) .set("hidden", jopt(w.hidden)) .set("pinpoint", jopt(w.pinpoint)) .set("blindfire", jopt(w.blindfire)) .set("secondary_pd", jopt(w.secondary_pd)) .set("model1", jstr(w.model1)) .set("model2", jstr(w.model2)) .set("model3", jstr(w.model3)) .set("muzzle_effect", jstr(w.muzzle_effect)) .set("muzzle_sound", jstr(w.muzzle_sound)) .set("icon_file", jstr(w.icon_file)) .set("icon_rect", jstr(w.icon_rect)) .set("fc_requires_los", jopt(w.fc.requires_los)) .set("fc_requires_inrange", jopt(w.fc.requires_inrange)) .set("fc_requires_enemycolony", jopt(w.fc.requires_enemycolony)) .set("fc_manual_target", jopt(w.fc.manual_target)) .set("fc_manual_toggle", jopt(w.fc.manual_toggle)) .set("fc_manual_launch", jopt(w.fc.manual_launch)) .set("fc_controllable", jopt(w.fc.controllable)) .set("fc_holdsfire", jopt(w.fc.holdsfire)) .set("fc_explicit_target", jopt(w.fc.explicit_target)) .set("fc_exclusive_launch", jopt(w.fc.exclusive_launch)) .set("fc_targets_expire", jopt(w.fc.targets_expire)) .set("rating_frate", jopt(w.ratings.fire_rate)) .set("rating_dam", jopt(w.ratings.damage)) .set("rating_acc", jopt(w.ratings.accuracy)) .set("rating_range", jopt(w.ratings.range)) .set("behavior_kind", jstr(w.behavior_kind)) .set("planet_damage", planet_json(w.planet_damage)) .set("rangetable", w.rangetable ? rangetable_json(*w.rangetable) : jnull()); if (w.bolt) { JObj b; b.set("rangetable", rangetable_json(w.bolt->rangetable)) .set("dam_pop", jopt(w.bolt->planet.pop)) .set("dam_infra", jopt(w.bolt->planet.infra)) .set("dam_terra", jopt(w.bolt->planet.terra)) .set("mass", jopt(w.bolt->mass)) .set("beam_origin", jopt(w.bolt->beam_origin)) .set("beam_length", jopt(w.bolt->beam_length)) .set("ricochet_mod", jopt(w.bolt->ricochet_mod)) .set("effect", jstr(w.bolt->effect)) .set("impact_effect", jstr(w.bolt->impact_effect)) .set("expire_effect", jstr(w.bolt->expire_effect)); t.set("bolt", b.done()); } else { t.set("bolt", jnull()); } JObj o; o.set("stem", jstr(w.stem)) .set("file", jstr(w.file)) .set("scope", jstr(w.scope == WeaponScope::Player ? "player" : "NPC")) .set("id", jopt(w.id)) .set("display_name", jopt(w.display_name)) .set("typed", t.done()) .set("raw", raw_json(w.raw)); return o.done(); } J section_json(const ShipSectionDef& s) { JObj t; t.set("model", jstr(s.model)) .set("dam_model", jstr(s.dam_model)) .set("requires", jstrs(s.requires_tech)) .set("section_type", jstr(s.section_type_text)) .set("section_type_enum", jstr(section_type_name(s.section_type))) .set("section_class", jstr(s.section_class_text)) .set("section_class_enum", jstr(section_class_name(s.section_class))) .set("design_class", jstr(s.design_class)) .set("entity_class", jstr(s.entity_class)) .set("health", jopt(s.health)) .set("mass", jopt(s.mass)) .set("cost", jopt(s.cost)) .set("cpoints", jopt(s.cpoints)) .set("crew", jopt(s.crew)) .set("command_cost", jopt(s.command_cost)) .set("maintenance_cost", jopt(s.maintenance_cost)) .set("command_quota", jopt(s.command_quota)) .set("socket_fore", jstr(s.socket_fore)) .set("socket_aft", jstr(s.socket_aft)) .set("dam_socket_fore", jstr(s.dam_socket_fore)) .set("dam_socket_aft", jstr(s.dam_socket_aft)) .set("ftlspeed", jopt(s.ftlspeed)) .set("nodespeed", jopt(s.nodespeed)) .set("range", jopt(s.range)) .set("scanrange", jopt(s.scanrange)) .set("tacticalsensorrange", jopt(s.tactical_sensor_range)) .set("engine_techera", jstr(s.engine_techera)) .set("exclude", jstrs(s.exclude)) .set("explicit_command_section", jstr(s.explicit_command_section)) .set("explicit_engine_section", jstr(s.explicit_engine_section)) .set("explicit_section", jopt(s.explicit_section)) .set("autonomous", jopt(s.autonomous)) .set("nodesign", jopt(s.nodesign)); { JArr groups; for (const OptionGroup& g : s.options) groups.push(JObj().set("members", jstrs(g.members)).set("scalar", jbool(g.scalar)).done()); t.set("option", groups.done()); t.set("optiondef", s.optiondef ? jstrs(s.optiondef->members) : jnull()); } { JArr banks; for (const BankDef& b : s.banks) { JArr mounts; for (const MountDef& m : b.mounts) mounts.push(JObj() .set("node", jstr(m.node)) .set("min_azimuth", jopt(m.min_azimuth)) .set("max_azimuth", jopt(m.max_azimuth)) .set("min_inclination", jopt(m.min_inclination)) .set("max_inclination", jopt(m.max_inclination)) .set("home_azimuth", jopt(m.home_azimuth)) .set("home_inclination", jopt(m.home_inclination)) .done()); banks.push(JObj() .set("turretclass", jstr(b.turret_class)) .set("turretsize", jstr(b.turret_size)) .set("weapon", jstr(b.weapon)) .set("showturrets", jopt(b.show_turrets)) .set("invincible", jopt(b.invincible)) .set("repeated_turret_spec", jbool(b.repeated_turret_spec)) .set("mount", mounts.done()) .done()); } t.set("bank", banks.done()); } if (s.netforcelimits) { const NetForceLimits& n = *s.netforcelimits; t.set("netforcelimits", JObj() .set("force_forward", jopt(n.force_forward)) .set("force_right", jopt(n.force_right)) .set("force_up", jopt(n.force_up)) .set("torque_yaw", jopt(n.torque_yaw)) .set("torque_pitch", jopt(n.torque_pitch)) .set("torque_roll", jopt(n.torque_roll)) .set("speed", jopt(n.speed)) .set("rotspeed", jopt(n.rotspeed)) .done()); } else { t.set("netforcelimits", jnull()); } { JArr th; for (const ThrusterDef& d : s.thrusters) th.push(JObj().set("node", jstr(d.node)).set("effect", jstr(d.effect)).set("idle_effect", jstr(d.idle_effect)).done()); t.set("thruster", th.done()); } JObj o; o.set("race", jstr(s.race)) .set("species", jstr(species_name(s.species))) .set("stem", jstr(s.stem)) .set("file", jstr(s.file)) .set("id", jopt(s.id)) .set("display_name", jopt(s.display_name)) .set("description", jopt(s.description)) .set("unlocked_by", jstrs(s.unlocked_by)) .set("typed", t.done()) .set("raw", raw_json(s.raw)); return o.done(); } J tech_json(const TechTree& t) { JArr nodes; for (const TechNode& n : t.nodes) { JArr allows; for (std::size_t ei : n.allows) allows.push(jstr(t.edges[ei].to)); nodes.push(JObj() .set("name", jstr(n.name)) .set("display_name", jopt(n.display_name)) .set("description", jopt(n.description)) .set("family", n.family.empty() ? jnull() : jstr(n.family)) .set("family_inferred", jstr(n.family_inferred)) .set("type", n.type.empty() ? jnull() : jstr(n.type)) .set("threat", jopt(n.threat)) .set("group", n.group.empty() ? jnull() : jstr(n.group)) .set("option_cost", jopt(n.option_cost)) .set("unlock_explicitly", jopt(n.unlock_explicitly)) .set("requires", jstrs(n.requires_tech)) .set("benefits_inc", jstrs(n.benefits_inc)) .set("benefits_dec", jstrs(n.benefits_dec)) .set("sections", jstrs(n.sections)) .set("weapons", jstrs(n.weapon_files)) .set("allows", allows.done()) .done()); } JArr edges; for (const AllowsEdge& e : t.edges) { JObj pct, all; for (int i = 0; i < kSpeciesCount; ++i) { auto s = static_cast(i); all.set(species_name(s), jint(e.percent(s))); if (e.pct_written[static_cast(i)]) pct.set(species_name(s), jint(e.percent(s))); } edges.push(JObj() .set("from", jstr(e.from)) .set("to", jstr(e.to)) .set("rp", jopt(e.rp)) .set("pct", pct.done()) .set("pct_effective", all.done()) .set("unparsed", jstrs(e.unparsed)) .done()); } JObj groups; for (const auto& kv : t.groups) groups.set(kv.first, jstrs(kv.second)); return JObj().set("nodes", nodes.done()).set("edges", edges.done()).set("groups", groups.done()).done(); } J turrets_json(const TurretTable& t) { JArr rows; for (const TurretRow& r : t.rows()) rows.push(JObj() .set("mount_size", jstr(r.mount_size)) .set("weapon_size", jstr(r.weapon_size)) .set("class", jstr(r.turret_class)) .set("health", jopt(r.health)) .set("track_speed", jopt(r.track_speed)) .set("azimuth_scale", jopt(r.azimuth_scale)) .set("inclination_scale", jopt(r.inclination_scale)) .set("model", jstr(r.model)) .done()); return rows.done(); } J refs_json(const std::vector& v) { JArr a; for (const auto& r : v) a.push(JArr().push(jstr(r.from)).push(jstr(r.ref)).done()); return a.done(); } J registry_json(const IdRegistry& r) { JArr ids; for (const IdEntry& e : r.entries()) ids.push(JArr().push(jint(e.id)).push(jstr(e.name)).done()); JArr del; for (int d : r.deleted()) del.push(jint(d)); return JObj().set("ids", ids.done()).set("deleted", del.done()).done(); } J cross_json(const CrossCheck& x) { JArr gaps; for (const auto& g : x.manifest_ids_without_file) gaps.push(JObj().set("scope", jstr(g.scope)).set("id", jint(g.id)).set("name", jstr(g.name)).done()); return JObj() .set("weapon_requires_dangling", refs_json(x.weapon_requires_dangling)) .set("weapon_requires_case_mismatch", refs_json(x.weapon_requires_case_mismatch)) .set("weapons_without_requires", jstrs(x.weapons_without_requires)) .set("section_requires_dangling", refs_json(x.section_requires_dangling)) .set("section_requires_case_mismatch", refs_json(x.section_requires_case_mismatch)) .set("section_option_dangling", refs_json(x.section_option_dangling)) .set("tech_ship_section_dangling", refs_json(x.tech_ship_section_dangling)) .set("tech_weapon_file_dangling", refs_json(x.tech_weapon_file_dangling)) .set("tech_requires_dangling", refs_json(x.tech_requires_dangling)) .set("tech_allows_dangling", refs_json(x.tech_allows_dangling)) .set("tech_allows_unparsed", refs_json(x.tech_allows_unparsed)) .set("bank_weapon_dangling", refs_json(x.bank_weapon_dangling)) .set("manifest_ids_without_file", gaps.done()) .set("files_without_manifest_id", refs_json(x.files_without_manifest_id)) .set("weapon_turret_pairs_without_row", refs_json(x.weapon_turret_pairs_without_row)) .set("bank_turret_pairs_without_row", refs_json(x.bank_turret_pairs_without_row)) .set("unresolved_weapon_names", refs_json(x.unresolved_weapon_names)) .set("missing_techname", jstrs(x.missing_techname)) .set("missing_techdesc", jstrs(x.missing_techdesc)) .set("missing_sectionname", jstrs(x.missing_sectionname)) .set("missing_sectiondesc", jstrs(x.missing_sectiondesc)) .set("tech_roots", jstrs(x.tech_roots)) .set("strings_available", jbool(x.strings_available)) .set("dangling_count", jint(static_cast(x.dangling_count()))) .done(); } } // namespace int main(int argc, char** argv) { if (argc != 3) { std::fprintf(stderr, "usage: dump_catalog \n"); return 2; } Catalog cat = load_catalog(argv[1]); JArr weapons; for (const WeaponDef& w : cat.weapons) weapons.push(weapon_json(w)); JArr sections; for (const ShipSectionDef& s : cat.sections) sections.push(section_json(s)); JObj manifests; manifests.set("Weapons", registry_json(cat.weapon_ids)); for (const auto& kv : cat.section_ids) manifests.set(kv.first, registry_json(kv.second)); JArr problems; for (const Problem& p : cat.problems) problems.push(JObj() .set("kind", jstr(problem_kind_name(p.kind))) .set("file", jstr(p.file)) .set("line", jint(p.line)) .set("key", jstr(p.key)) .set("message", jstr(p.message)) .done()); JObj doc; doc.set("weapons", weapons.done()) .set("sections", sections.done()) .set("tech", tech_json(cat.tech)) .set("turrets", turrets_json(cat.turrets)) .set("manifests", manifests.done()) .set("strings_loaded", jbool(cat.strings_loaded)) .set("string_count", jint(static_cast(cat.strings.size()))) .set("races", jstrs(cat.races)) .set("problems", problems.done()) .set("cross_check", cross_json(cat.cross_check())); std::ofstream out(argv[2], std::ios::binary); if (!out) { std::fprintf(stderr, "cannot write %s\n", argv[2]); return 1; } out << doc.done().s << '\n'; std::printf("dumped %zu weapons, %zu sections, %zu techs, %zu edges, %zu problems -> %s\n", cat.weapons.size(), cat.sections.size(), cat.tech.nodes.size(), cat.tech.edges.size(), cat.problems.size(), argv[2]); return 0; }