Skip to content

Merge release/2.2.1 into release/2.3.0 - #289

Merged
Osiris-DevWorks merged 53 commits into
Osiris-DevWorks:release/2.3.0from
Stealrull:merge/2.2.1-into-2.3.0
Jul 26, 2026
Merged

Merge release/2.2.1 into release/2.3.0#289
Osiris-DevWorks merged 53 commits into
Osiris-DevWorks:release/2.3.0from
Stealrull:merge/2.2.1-into-2.3.0

Conversation

@Stealrull

Copy link
Copy Markdown
Collaborator

Summary

release/2.3.0 was branched before the full 2.2.1 bug-fix bundle (#251, #255, #259, #261, #267, #269, #273, #281) merged, so every 2.3.0 feature/fix branch built on top of it was silently missing all of it. Most visibly: a fresh 2.3.0 portable build today didn't auto-detect the Star Citizen install path at all — the #269 fix simply wasn't there yet.

This brings release/2.3.0 fully current with release/2.2.1 so nothing built on top of it going forward repeats this. Clean merge, no conflicts. VERSION.TXT corrected back to 2.3.0 after the merge (git took 2.2.1's value since release/2.3.0 never independently touched that file after branching).

Testing Checklist

  • PR is scoped to one concern (housekeeping merge, no new behavior)
  • pytest tests/ passes locally (1276 passed, 16 skipped)
  • Target branch is release/2.3.0
  • VERSION.TXT matches the active release branch (2.3.0)

Testing performed

Full suite green after the merge and after the VERSION.TXT correction.


🤖 Generated with Claude Code

Stealrull and others added 30 commits July 17, 2026 12:16
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A 2.2.0 user's Generate Enhancements crashed with UnicodeDecodeError:
byte 0xa0 — their cached base.ini wasn't clean UTF-8, on a stock
English install. 0xA0 is the Windows-1252 non-breaking space; CIG's
text carries plenty of characters that cp1252 encodes as single high
bytes (é / ™ / ° / em dash / NBSP), so a base.ini re-saved by an
external editor in ANSI (legacy Notepad's default), mangled by a
sync/AV tool, or a community-translated base saved as ANSI all decode
as invalid UTF-8 at the first such character.

Both readers opened the file with a strict utf-8-sig decode:

- scripts/generate_enhancements_ini.py's parse_ini crashed the whole
  enhancements run (the reported traceback).
- src/parser/ini_parser.py's parse_ini_file was quietly worse: its
  broad except caught the mid-iteration decode error and returned a
  silently TRUNCATED result — every key after the bad byte vanished
  with only a log warning.

Each now decodes the whole file up front — UTF-8 (BOM-aware) first,
falling back to cp1252 with errors="replace" (guards cp1252's five
undefined bytes) and a warning. Line splitting uses split('\n') +
rstrip('\r') rather than str.splitlines() so values containing
U+2028/U+0085 don't split where file iteration never did. The helper
is duplicated in both files deliberately: the generator script stays
stdlib+lxml-only (it also runs standalone), same shape as the existing
parse_ini/parse_ini_file parallelism.

Fixes Osiris-DevWorks#251

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lback

Follow-up hardening pass on Osiris-DevWorks#251 after cross-checking the original
Discord/Facebook report: a healthy install's extraction of the same
LIVE data decodes as perfectly valid UTF-8, so the affected user's
base.ini was machine-local corruption (possibly in their own Data.p4k,
meaning re-extraction can reintroduce it) — one bad byte in an
otherwise-valid file, not a wholesale ANSI re-save.

That finding exposed a flaw in the initial fix: a blanket cp1252
fallback would mojibake every legitimate multi-byte character in a
10 MB UTF-8 file to repair one corrupt byte ("é" -> "é", thousands of
times). The decode fallback now discriminates by measuring whether the
UTF-8 replacement count tracks the file's high-byte count: corrupted
UTF-8 gets errors="replace" (only the bad byte degrades to U+FFFD);
a genuinely ANSI file still gets the cp1252 decode.

The helper moved to src/utils/ini_io.read_ini_text so the merger can
share it (merger already imports src.utils; parser->merger import
direction forbids the reverse). The generator script keeps its own
mirror to stay stdlib+lxml-only, documented as tolerated duplication.

Audit of every other read of user-mutable/corruptible state found five
more members of the same bug class, all fixed:

- src/merger/ini_merger.py: merge_ini_files read base.ini with a
  strict open() — the SAME corrupt file also failed Apply to Game.
  Now routes through read_ini_text (newline shape preserved; existing
  merger tests plus two new ones pin it).
- src/utils/user_ini_manager.py: _backup_if_changing runs on EVERY
  save and read_text raised UnicodeDecodeError past its OSError-only
  guard — one bad byte in user.ini bricked all saves. Now compares
  newline-normalized bytes (no decode at all); a corrupt file counts
  as "changing" and gets snapshotted before the clean rewrite.
- src/utils/json_settings.py: a settings.json with corrupt bytes
  escaped the (OSError, JSONDecodeError) guard as UnicodeDecodeError
  and crashed the PORTABLE app at startup. Guard widened to ValueError
  (covers both).
- src/utils/dataforge_diff.py: dirty_categories loaded the diff
  manifest unguarded — a corrupt manifest crashed the enhancements
  run. Now treated as "no manifest": regenerate everything and let
  update_manifest rewrite a clean one (self-healing).
- src/utils/settings.py + scripts/generate_enhancements_ini.py stamp
  reads and src/utils/dataforge_patcher.py's locstring-workarounds
  loop: OSError-only (or JSONDecodeError-only) guards widened to also
  catch ValueError.

Paths audited and already safe: blueprint_log_scanner (errors=
"replace"), user_cfg (broad except; skipping is safer than rewriting a
mangled decode into the game's own config), i18n/tutorial/docs loads
(guarded with fallbacks), pak_extractor stamps (ValueError caught),
applied_file_validator (broad except), should_autosave_user_ini
(stat only, no decode). Registry-mode settings live in the Windows
registry and have no file decode to corrupt.

Fixes Osiris-DevWorks#251

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…g as @keyname

Same fix as issue/261-global-ini-missing-bom (release/2.2.1), reapplied
here since this branch predates the Osiris-DevWorks#251 encoding rewrite (that's PR Osiris-DevWorks#253,
separately targeting this same branch) and still has the older
merge_ini_files shape (plain open() calls, not read_ini_text).

Critical bug, confirmed on a tester's real Star Citizen install (not yet
published to end users): after Apply, the entire in-game UI — main menu,
mobiGlas, contacts, everything — showed raw loc keys instead of resolved
text. Deleting Smart Citizen's global.ini restored correct text
immediately.

Root cause: Data.p4k's own extracted global.ini ships with a UTF-8 BOM
(confirmed directly — raw unp4k output starts with \xef\xbb\xbf). Star
Citizen's own localization loader appears to need that BOM to reliably
detect the file's encoding; without it, the engine can fail to resolve
the ENTIRE loc table rather than degrading per-key. merge_ini_files has
written plain UTF-8 with no BOM in every prior release.

Two-part fix:
- merge_ini_files now opens both the source (utf-8-sig, so a source BOM
  doesn't leak into the first key's name either — a related bug this
  branch's older code shape also had) and the output (utf-8-sig, so the
  written file gets the BOM) with BOM-aware encoding.
- validate_applied_file now treats a missing BOM as a first-class
  validation failure (not just key presence), triggering the existing
  rollback-to-backup path.

Fixes Osiris-DevWorks#261

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…me corruption

Ported from issue/257-starmap-name-corruption (originally targeted
release/2.2.1, now retired in favor of consolidating everything onto
release/2.3.0). Same fix, same tests — see Osiris-DevWorks#255/Osiris-DevWorks#257 for the full
root-cause writeup.

Critical bug: applying Smart Citizen's output renamed the Crusader planet
to "Stanton (Star)" on the in-game starmap. Root cause: sync_key_variants()
canonicalized every key in the ~90k-key table (strip _SCItem, lowercase,
strip all underscores) to reconcile CIG's inconsistent ship-component
naming, but two completely unrelated keys — Stanton2 (the planet) and
Stanton_2 (the star) — collapsed to the same canonical string by
coincidence, and the longest-value tie-break overwrote the planet's name.

A real base.ini audit found 14 total live corruptions of this shape
(comm-array button labels, a mission title, Options-menu turret labels,
two distinct thrusters collapsed to one name, a hardpoint slot losing its
distinguishing suffix, and more) — not an isolated incident.

Fix: scope the canonical-key grouping to item_name/item_desc-prefixed
keys only — the exact domain this sync was ever designed and tested for.

Fixes Osiris-DevWorks#255

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ow show

XenoThreat's "Purgatory Camo" armor/weapon/component set was announced
purely on CIG's website for a limited-time event, never advertised via
any mission's POTENTIAL BLUEPRINTS text -- so mission-text scanning
alone could never surface it, leaving these items visible only in the
Owned list (via the log scanner's independent "Received Blueprint: ..."
matching) and never in Available.

Adds a manually-curated MANUAL_BLUEPRINT_ITEMS list folded into
build_blueprint_metadata()'s output: real tag/class/size/grade if a
matching item_Name entry is actually loaded, a bare fallback entry
otherwise, never overwriting a real mission-derived entry. Tooltip
reads "Limited time event reward" instead of a mission name.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…local drives

The only auto-detection fallback checked two hardcoded C:\Program
Files\... paths, so a fresh profile (or a portable build's first run,
no saved settings yet) showed the "Star Citizen Path Required" dialog
even when a real install existed -- a tester's real install lives at
E:\Games\Roberts Space Industries\StarCitizen, a shape neither
hardcoded candidate could ever match.

_scan_common_sc_install_locations() now checks every local drive
letter against RSI Launcher's own default shape, both Program Files
bitness variants, a bare secondary-drive install, and one nested under
a personal "Games" folder -- validated the same way every other
candidate already is. Wired into both get_sc_install_root() and
get_game_install_path(), persisting the result once found so the scan
only ever runs once per profile.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…Osiris-DevWorks#273)

_enhancements_dirty (the sole gate on the Generate Enhancements
button's enabled state) was only ever computed once at tab
construction, then nudged True/False by discrete UI events afterward
(a checkbox toggle, clicking Generate itself) -- never recomputed
wholesale. Switching channels changes what "needs regeneration" even
means, but the flag was left untouched: if the previous channel ended
clean, the button stayed disabled for a new channel that actually did
need (re)generation, silently swallowing clicks until the user
happened to retoggle some checkbox that forces it back on (the
"untick and retick" workaround reported in the issue).

Adds EnhancementsTab.refresh_enhancements_dirty_state(), a public
entrypoint (same shape as the existing mark_enhancements_dirty())
that re-derives the flag from the new channel's real disk state, and
calls it from MainWindow._on_channel_changed right alongside the
existing refresh_enhancements_status() call.
…siris-DevWorks#259)

Toggling "Underline Direct" (and 6 other checkboxes on this page)
updated self.config and refreshed the preview but never emitted
config_changed, so MainWindow's _mark_tag_dirty() never fired and the
Save Tag Changes button stayed disabled despite a real pending change.
Every other control on this page (the 5 dropdowns, General Tags/
Scanning checkboxes) already emitted the signal correctly.

Found a 4th affected handler while fixing the reported 3:
_on_mt_standardize_toggle (the "Standardize hauling mission names"
checkbox) had the exact same bug — 7 checkboxes total, not 6.

Redo of the original fix from PR Osiris-DevWorks#260, which targeted the abandoned
release/2.2.1 branch and was closed unmerged; recreated here against
release/2.3.0 after confirming the bug and the widget attribute names
are unchanged.
…/266-blueprint-general-fixes

# Conflicts:
#	VERSION.TXT
…annotations

Two compounding bugs found while wiring up Scraper Module tagging,
both affecting far more than that one item:

- _bp_section_span mistook a reputation-tiered contract's "Awarded
  from X level variants" sub-header for a genuine section boundary
  (it doesn't start with "[" like a region label does), truncating
  the scan before any bullets were ever reached. Confirmed via the
  fixture data that this pattern spans dozens of missions (Adagio
  Industrial, Bounty Hunters Guild, Security, ...), not just salvage
  contracts.
- normalize_item_name never stripped the "(Category)" annotation CIG
  appends to some bullets (Bendix (Fuel Nozzle), Arbor MH1 Mining
  Laser (Mining Laser), 5CA 'Akura' (Shield), Trawler Scraper Module
  (Salvage Mod)), so even a correctly-scanned bullet never matched
  its real item_Name entry.

Both silently kept affected items out of the Blueprint Tracker
entirely (not just untagged -- absent), regardless of any Tag
Builder configuration.
…rks#266 follow-up)

CIG uses a second, entirely different section header -- "MULTIPLE
BLUEPRINT POOLS" -- for missions that offer more than one blueprint
pool (e.g. mining-laser purchase-order contracts awarding a
weapon/armor Pool 1 alongside a mining-laser/radar Pool 2). Confirmed
via tests/fixtures/kraken_global_latest.ini: 35 missions use it
against 237 using "POTENTIAL BLUEPRINTS". Only the latter was
recognised at all, so these missions' blueprint bullets were never
scanned -- not just untagged, entirely absent from the Blueprint
Tracker and from the String Editor's "BP Descriptions" filter.

Adds has_bp_section() as the single header-detection entry point
(replacing three separate raw substring checks in blueprint_meta.py
and entry_filter.py) and recognises "Pool 1"/"Pool 2"/... sub-labels
inside the section as non-boundary markers, the same way region
labels and reputation-tier labels already were.
…Osiris-DevWorks#266 follow-up)

Live mission body ("Crew Hasn't Checked In") reported bullets that
match no real item_Name value at all, keeping those items out of the
Blueprint Tracker entirely -- two distinct root causes:

- CIG's own bug: some bullets list the de-slugified loc KEY instead
  of the item's localized name -- "Nozzle Fuelgiver Grin
  Nozzleveryfast" for a key whose real value is "Lindstrom"
  (Nozzle_FuelGiver_GRIN_NozzleVeryFast_Name). Deterministic
  (title-case each underscore segment, dropping a trailing "_Name"
  first), so resolved generically via _key_slug rather than one
  alias per affected item.
- A one-off mismatch with no deterministic pattern: "Helix" bears no
  relation to its key's slug, just an informal short name a mission
  author typed; the real item is "S0 Helix"
  (item_NameMining_Head_S00_Helix_SCItem). Added as a small explicit
  alias, same precedent as Osiris-DevWorks#267's MANUAL_BLUEPRINT_ITEMS.

Both only apply as a fallback when the bullet name doesn't already
match a real item directly.
…s-DevWorks#266 follow-up)

Hofstede reported with the same "Helix" mismatch (mission bullet uses
the bare manufacturer/model name, real item is "S00 Hofstede" via
item_NameMining_Head_S00_Hofstede_SCItem) -- confirms this is a
whole-family pattern, not a one-off. Adds Arbor and Klein preemptively
too, same key convention (item_NameMining_Head_S00_<Name>_SCItem),
rather than waiting for each to be reported individually. Klein's real
name ("Lawson Mining Laser") breaks the "S0 <Name>" pattern entirely,
so it couldn't have been caught by any general rule either way.
…vWorks#266 follow-up)

Confirmed via a live Blueprint Tracker screenshot: 5 of 8 fuel nozzle
variants resolved correctly through the generic _key_slug reversal,
but Norfield, Harkin, and RN-7s still showed up as separate untagged
"Nozzle Fuelgiver ..." entries -- their real underlying keys
apparently don't follow the Nozzle_FuelGiver_<MFR>_Nozzle<Variant>_Name
pattern the other five do. Added as explicit aliases, same as the
Mining Head family.
…DevWorks#266 general fix)

Nozzles ship under item_fuelnozzle_*/Nozzle_FuelGiver_* prefixes, not
item_Name*/vehicle_Name*, so Pass 1 never saw these keys as Name entries
at all -- the alias table and _key_slug fallback added by the prior two
commits had nothing to resolve against, and every nozzle still showed up
as a separate untagged/garbled bullet. Scoped to the two fuel-nozzle
prefixes only; the ship-mounted mining-laser and scraper-module prefixes
are new-tag-feature territory for 2.3.0, not a 2.2.1 bug fix.
…Works#251)

The JSONDecodeError->ValueError exception widening in
load_locstring_workarounds was the one Osiris-DevWorks#251-class change shipped
without a test exercising the new branch -- its three sibling files
(json_settings, dataforge_diff, user_ini_manager) all got the
identical change with matching tests in TestCorruptStateFileGuards.
Found by the pre-release test-coverage audit.
Testers on the 2.2.1 preview build need plan items covering Osiris-DevWorks#251, Osiris-DevWorks#261,
Osiris-DevWorks#257, Osiris-DevWorks#267, Osiris-DevWorks#269, Osiris-DevWorks#273, Osiris-DevWorks#259, Osiris-DevWorks#247, and the general Blueprint Tracker
parsing fixes ported from Osiris-DevWorks#266 -- none of it was represented in
TEST_SECTIONS yet.
… and Apply Owned Tags

Reported while live-testing the first Osiris-DevWorks#273 fix: the same
stale-dirty-state class hit two more buttons on a channel switch.

- Save Tag Changes: tag configs are global but the generated INIs are
  per-channel, and nothing records which configs a channel's INIs were
  last generated with. After a switch the button kept the previous
  channel's clean state, leaving the user unable to stamp their tags
  onto the new channel at all. Now lit on switch (erring on clickable —
  worst case is one redundant regeneration).
- Apply Owned Tags: only the button's own click ever cleared its flag,
  so after a switch it sat grey ("no changes") and the user couldn't
  force a re-weave for the new channel. Now marked via the existing
  public mark_owned_dirty() on switch; the flag survives the subsequent
  reload since nothing in that path clears it.
Save Tag Changes and Apply Owned Tags now also refresh on a channel
switch; testers need an item exercising all three buttons, not just
Generate Enhancements.
Crash report: opening the Custom... resolution in Import INI and entering
a value raised TypeError: tr() got multiple values for argument 'key'.
import_dialog.py's custom_value_prompt call already existed on this
branch as tr("import_dialog.custom_value_prompt", key=key) -- passing a
loc key as an interpolation variable is a very natural thing to do, but
tr(key: str, **kwargs) has its own first parameter also named "key", so
Python rejects the call before the function body ever runs.

Renames tr()'s own parameter to i18n_key so "key" is free for any caller,
and fixes the one existing crashing call site (custom_value_prompt) plus
its {key} -> {loc_key} placeholder across all four locale files.

This is scoped to the one pre-existing crash. A second, unrelated
tr()-with-key= collision in main_window.py's "Copy Key" action only
exists once Osiris-DevWorks#247's hardcoded-string sweep converts that line from a
plain f-string to a tr() call -- that one is fixed alongside the sweep
itself in release/2.3.0, not here.
…siris-DevWorks#281)

CIG bug: all 8 fuel nozzle blueprint rewards miss both the UUID and
filename entity-name resolution tiers in build_blueprint_pool_lookup,
falling through to _name_from_blueprint_filename's title-cased-slug
fallback. Confirmed live in-game via an "URGENT REFUEL REQUEST: Drake
Cutter" mission body listing all 8 variants as raw garbled text (e.g.
"Nozzle Fuelgiver Grin Nozzlefast") instead of their real manufacturer
names in the generated global.ini that Apply to Game writes.

Adds a known-alias correction keyed by the exact fallback string this
function produces, scoped to the 8 confirmed fuel nozzle variants --
does not touch resolution for any other blueprint type. This is a
generation-time fix (changes what's written to global.ini), distinct
from blueprint_meta.py's alias table which only affects Smart Citizen's
own Blueprint Tracker tab display and never touches in-game text.
Leftover from before this branch was retargeted from release/2.3.0 to
release/2.2.1 — the version-bump commit never got updated, so the PR
diff wrongly showed VERSION.TXT changing to 2.3.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Leftover from before this branch was retargeted from release/2.3.0 to
release/2.2.1 — the version-bump commit never got updated, so the PR
diff wrongly showed VERSION.TXT changing to 2.3.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Leftover from before this branch was retargeted from release/2.3.0 to
release/2.2.1 — the version-bump commit never got updated, so the PR
diff wrongly showed VERSION.TXT changing to 2.3.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Leftover from before this branch was retargeted from release/2.3.0 to
release/2.2.1 — the version-bump commit never got updated, so the PR
diff wrongly showed VERSION.TXT changing to 2.3.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Leftover from before this branch was retargeted from release/2.3.0 to
release/2.2.1 — the version-bump commit never got updated, so the PR
diff wrongly showed VERSION.TXT changing to 2.3.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stealrull added a commit to Stealrull/smart-citizen that referenced this pull request Jul 22, 2026
Brings in the full 2.2.1 bug-fix bundle (Osiris-DevWorks#251, Osiris-DevWorks#255, Osiris-DevWorks#259, Osiris-DevWorks#261, Osiris-DevWorks#267,
Osiris-DevWorks#269, Osiris-DevWorks#273, Osiris-DevWorks#281) so this 2.3.0 branch doesn't repeat the
missing-2.2.1-fixes problem release/2.3.0 itself had before Osiris-DevWorks#289.
Stealrull added a commit to Stealrull/smart-citizen that referenced this pull request Jul 22, 2026
Brings in the full 2.2.1 bug-fix bundle (Osiris-DevWorks#251, Osiris-DevWorks#255, Osiris-DevWorks#259, Osiris-DevWorks#261, Osiris-DevWorks#267,
Osiris-DevWorks#269, Osiris-DevWorks#273, Osiris-DevWorks#281) so this 2.3.0 branch doesn't repeat the
missing-2.2.1-fixes problem release/2.3.0 itself had before Osiris-DevWorks#289.

Resolved conflicts in blueprint_meta.py/owned_items.py (and their
tests) by combining both sides: kept this branch's Osiris-DevWorks#266 bare-type-key
Name recognition (_EXTRA_NAME_KEY_PREFIXES for fuel nozzles/mining
lasers/scraper) alongside 2.2.1's Osiris-DevWorks#267 manual-blueprint-items and
MULTIPLE BLUEPRINT POOLS section-header handling, and reordered the
test file so TestBareTypeKeyConventions, TestManualBlueprintItems, and
TestBulletNameMismatches each stay intact as complete classes.

VERSION.TXT restored to 2.3.0 (the merge took 2.2.1's value).
Stealrull added a commit to Stealrull/smart-citizen that referenced this pull request Jul 22, 2026
Brings in the full 2.2.1 bug-fix bundle (Osiris-DevWorks#251, Osiris-DevWorks#255, Osiris-DevWorks#259, Osiris-DevWorks#261, Osiris-DevWorks#267,
Osiris-DevWorks#269, Osiris-DevWorks#273, Osiris-DevWorks#281) so this 2.3.0 branch doesn't repeat the
missing-2.2.1-fixes problem release/2.3.0 itself had before Osiris-DevWorks#289.
Stealrull added a commit that referenced this pull request Jul 22, 2026
Brings in the full 2.2.1 bug-fix bundle (#251, #255, #259, #261, #267,
#269, #273, #281) so this 2.3.0 branch doesn't repeat the
missing-2.2.1-fixes problem release/2.3.0 itself had before #289.
Stealrull added a commit that referenced this pull request Jul 22, 2026
Brings in the full 2.2.1 bug-fix bundle (#251, #255, #259, #261, #267,
#269, #273, #281) so this 2.3.0 branch doesn't repeat the
missing-2.2.1-fixes problem release/2.3.0 itself had before #289.

Resolved a comment-only conflict in i18n.py's tr() docstring (both
sides documented the same i18n_key rename for the same reason, worded
differently) by keeping 2.2.1's wording.

VERSION.TXT restored to 2.3.0 (the merge took 2.2.1's value).
Stealrull added a commit that referenced this pull request Jul 22, 2026
Brings in the full 2.2.1 bug-fix bundle (#251, #255, #259, #261, #267,
#269, #273, #281) so this 2.3.0 branch doesn't repeat the
missing-2.2.1-fixes problem release/2.3.0 itself had before #289.
@Stealrull Stealrull self-assigned this Jul 24, 2026
@Stealrull Stealrull added 2.3.0 Slated for the 2.3.0 release Awaiting Review and removed Awaiting-Osiris-review labels Jul 24, 2026
@Ishikudeska

Copy link
Copy Markdown
Collaborator

@claude review

@Osiris-DevWorks Osiris-DevWorks left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Clean forward-merge of release/2.2.1 into release/2.3.0.

  • VERSION.TXT correctly held at 2.3.0 (base and head carry the identical blob; the merge's transient 2.2.1 value was corrected on-branch).
  • No conflicts; no 2.3.0-only work reverted — the diff is purely additive 2.2.1 bug-fix content over the branch point.
  • Merging this first collapses the missing-2.2.1 delta that was bloating the stacked 2.3.0 PRs (#252, #254, #272, #280, #284, #290, #294, #295, #304).

Note: CI hasn't reported on this head SHA (total_count: 0) — relying on the author's local green suite (1276 passed).


Generated by Claude Code

@Osiris-DevWorks
Osiris-DevWorks merged commit 7f96a5b into Osiris-DevWorks:release/2.3.0 Jul 26, 2026
4 checks passed
Stealrull added a commit to Stealrull/smart-citizen that referenced this pull request Jul 27, 2026
Portable users re-do their whole setup on every update: each release
unzips into a fresh versioned folder with an empty data/, stranding the
previous one. This adds a Settings Backup group to the Config tab that
snapshots everything that makes an install theirs into one small zip
(a few KB) and restores it on another PC, after a portable update, or to
undo a misconfiguration.

What travels: every backend setting (preferences, tag configs, category
toggles), the Star Citizen install path, and each channel's user.ini.
What doesn't: the DataForge / base.ini cache (tens of MB, rebuilds from
the game), backups/, and the data-root / cache-dir overrides — pointing
those at a drive that doesn't exist on the target machine would strand
every path derived from them.

The zip carries a manifest (schema + exporting version + source mode +
channels), so a backup made by any build imports into any other; import
only refuses a schema NEWER than the running build understands. The
filename deliberately omits the app version so it doesn't imply
otherwise.

Import validates before it writes: it snapshots the current user.ini
files into the channel's rotating backups (Osiris-DevWorks#172 machinery) so the import
is reversible, then reconciles the Star Citizen path — kept when the
folder really exists here, dropped in favour of auto-detection when it
doesn't, so a backup from another machine can't pin the user to a dead
path. It then sets a one-shot flag and offers a restart; the next launch
offers to regenerate + apply enhancements, reusing the Simple-mode
generate-then-apply continuation (Osiris-DevWorks#180).

Two lifecycle details the feature has to get right:

- Export flushes on-screen Tag Builder edits first, so a backup captures
  what the user is looking at rather than the last-saved state. Same
  "what you see is what you save" rule Generate Enhancements follows
  (Osiris-DevWorks#215).
- Import refreshes the Tag Builder pages and category checkboxes from
  settings afterwards. Those widgets cache settings at construction, so
  an import that rewrites the tag_builder/* keys underneath a live tab
  left the pages holding the pre-import config — and the next Save Tag
  Changes / Generate / Export wrote that stale state straight back over
  the import. _reset_to_defaults is refactored into apply_config() so
  the refresh reuses the existing widget-resync logic; Reset keeps
  persisting General Tags defaults, the import path only mirrors them.

Works identically in registry and portable builds: export/import go
through the AppSettings backend abstraction, so one zip format moves a
setup between the installer and portable variants in either direction.

Note: import calls get_sc_install_root(), so it inherits whatever
detection that method offers. Full multi-drive auto-detection (Osiris-DevWorks#269)
reaches this branch with the release/2.2.1 merge (Osiris-DevWorks#289); until then the
fallback is the two hardcoded Program Files paths.

New: src/utils/settings_profile.py (Qt-free zip pack/unpack + manifest
validation, same split as locpack_exporter.py).
Tests: tests/test_settings_profile.py (round trip, validation, key
filtering, install-path reconciliation, cross-version import) and
tests/test_tag_builder_import_refresh.py, which also locks the clobber
failure mode itself so a refactor can't silently undo the fix.

Co-Authored-By: Claude <noreply@anthropic.com>
Stealrull added a commit that referenced this pull request Jul 27, 2026
Rebased onto release/2.3.0 tip -- both cherry-picks applied cleanly,
no conflicts (the 2.2.1 bundle this branch briefly carried is already
on release via #289).

Re-verified the combined classification ordering after #290's
mining-head carve-out merged: component-code -> Ammo suffix ->
FPS-weapon/armor -> mining-head carve-out -> ship-weapon heuristic.
Added a locking test since neither existed as a regression guard: an
ammo key that also happens to contain "mining_head" still classifies
as Ammo (the suffix check runs first), and a real mining-head key
with no ammo suffix still lands in Other, not double-matched into
either bucket.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2.3.0 Slated for the 2.3.0 release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants