Skip to content

chore: merge upstream/main and release v4.5.0 - #113

Merged
Lyrex merged 58 commits into
mainfrom
backmerge-4.5.0
Oct 1, 2026
Merged

Lyrex merged 58 commits into
mainfrom
backmerge-4.5.0

Conversation

@Lyrex

@Lyrex Lyrex commented Oct 1, 2026

Copy link
Copy Markdown
Owner

No description provided.

Oost-hash and others added 30 commits September 16, 2026 14:03
Rust (src-tauri/src/):
- blob_capture.rs: memory scanner thread, blob processing, state sync
- reward_watcher.rs: EE.log tail, OCR trigger, relic reward detection
- monitor.rs: monitor catalog, inventory state, crafting jobs
- catalogue.rs: item catalog, blueprint names, weapon catalog
- companion_api.rs: Warframe companion API integration
- diagnostics.rs: debug tools, memory probe, overlay diagnostics
- log_watcher.rs: EE.log parsing, trade detection, riven events
- rivens.rs: riven analysis, OCR, memory watcher
- settings.rs: settings load/save, window state management
- wfm_commands.rs: warframe.market API commands
- wfm_queue.rs: price fetch queue with priority support
- worldstate.rs: worldstate alerts, syndicate alerts
- Additional: credentials, db, image_cache, inventory_state, paths,
  platform, pricing, refresh, relic_pick, stats, syndicates,
  trade_log, updater, wfcd, wfm, wfm_top

TypeScript (src/):
- hooks/: useCatalog, useInventoryData, useMarketData, useSettings,
  useOverlays, useModularWindow, useFissureNotifications, useTimerPreferences
- constants/: categories, tauri
- shared/: ResizeHandle component
- lib/: rivenWindow helper
- Refactored App.tsx, MarketHelper, Reports, Foundry, and others

lib.rs reduced from ~10k to ~700 lines.
Centralize all Windows-specific code into platform/ and ocr/ modules with
trait-based abstractions for Linux portability.

Platform module (platform/mod.rs, windows.rs, linux.rs):
- CredentialStore trait: save/load/delete credentials
- ProcessAccess trait: find PID, open process handle
- ProcessHandle trait: query regions, read memory, enumerate regions
- WindowManager trait: find HWND, get window rect, set overlay topmost
- LocaleProvider trait: get system locale
- ComInit trait: initialize COM for WinRT/OCR
- FileWatcher trait: watch files for changes
- MemoryRegionInfo struct with is_committed, is_readable, is_writable,
  is_executable, is_image fields

OCR module (ocr/mod.rs, windows.rs, linux.rs):
- Business logic in mod.rs: preprocessing, BMP conversion, matching, scoring
- Platform-specific capture and OCR engine in windows.rs
- Linux stubs in linux.rs
- Removed circular dependency between platform and ocr

Consumer refactoring:
- credentials.rs: Platform::CredentialStore
- diagnostics.rs: Platform::WindowManager, ProcessHandle
- settings.rs: Platform::CredentialStore
- memory_scanner.rs: Platform::ProcessAccess, ProcessHandle, RegionSource
- monitor.rs: Platform::ProcessAccess, ComInit
- rivens.rs: Platform::ProcessHandle
- companion_api.rs: Platform::ProcessHandle
- log_watcher.rs: ocr:: capture functions
- mem_regions.rs: open_region_source factory, RegionSource::stats()

Result: 52+ #[cfg] blocks consolidated, all 4 OCR tests pass.
Upstream splits its `lib.rs` into domain modules, which the fork had already
done along different boundaries. The fork's split stands; what the merge takes
is the new platform abstraction.

`platform.rs` becomes `platform/`, with the Linux implementations wired to the
code that already existed: the `/proc` PID lookup, the Secret Service store and
the X11 window rect. `CredentialStore` needs a sync entry point, so
`credentials` grows one and its async commands go through the trait.

Game-running status now follows the PID instead of waiting for a blob parse to
succeed, which is upstream's fix for the status reading "not running" while the
game was already up.

Upstream's `blob_capture` and `reward_watcher` modules are left out; `monitor`
holds both loops here. `ocr.rs` stays one file rather than adopting upstream's
`ocr/` split.
- Fix read_at resume address bug (short reads no longer skip gaps)
- Replace enumerate_regions_from Vec with regions_from iterator
- Add read_into borrowing method to ProcessHandle trait
- Add max_len parameter to RegionSource::read_at
- Route WindowsRegionSource through Platform::open_process
- Replace is_image bool with RegionBacking enum
- Change open_process to return Result with error details
- Exclude volatile sections (RecentVendorPurchases) from baseline rejection
- Add content hash deduplication for unchanged blobs
- ProcessRegions: chunked reading for large regions (> read_cap)
- ProcessRegions: deadline parameter for timed walks
- ProcessRegions: caller-supplied filter for region selection
- RegionSource: region_at() default method to check if addr is mapped
- RegionSource: read_at contract documented (bytes must live at addr)
- RegionBacking::Kernel: per-platform semantics documented
- MEM_MAPPED intentionally maps to Anonymous (not File)
- CredentialStore: blocking warning doc for locked keyring
- try_cached_blob: use region_at for fast unmapped-address detection
`ProcessHandle` now lends into a caller-owned buffer through `read_into`,
enumerates mappings through `regions_from`, and `open_process` reports why
it failed rather than returning `None`.

Move the Linux process and mapping code out of `memory_scanner_linux` into
`platform::linux` behind that trait, mapping `/proc/pid/maps` entries onto
`MemoryRegionInfo` and `RegionBacking`. The scanner keeps its own
`RegionSource`: `ProcessRegions` reads only the first `read_cap` bytes of a
region, which loses a blob sitting past that offset in a large `[heap]`
mapping.
`ProcessRegions` now chunks large regions, takes a region filter and a
deadline, and can answer `region_at`, so the Linux scanner drops its own
region source and drives the shared one.

The two-tier walk opens one `ProcessRegions` per tier instead of
partitioning a mapping snapshot, and the cached-blob probe wraps the
source in `StitchGuard`, which stops the stitch at a mapping whose bytes
must not be spliced into the blob. `read_at` reads blind, so without the
guard the stitch runs out of the heap into code or a mapped file.

Tests fake the process handle rather than the region source, which keeps
the chunking, the minimum region size and the tier filters under test.
Splits the ~10k-line lib.rs into focused domain modules (app_state,
blob_capture, catalogue, monitor, diagnostics, settings, wfm_commands,
worldstate, and more) and introduces a platform abstraction layer
(ProcessAccess, ProcessHandle, WindowManager, CredentialStore,
LocaleProvider, ComInit, FileWatcher) so Windows-specific code funnels
through platform::windows instead of being scattered across the
backend. OCR is split the same way into ocr/windows.rs and ocr/linux.rs
behind ocr/mod.rs. Preserves the v4.4 section-baseline blob validation
and background catalogue refresh behavior.

Includes fixes from two rounds of review by Lyrex (Linux fork
maintainer), who exercised the new traits against a real second
implementation rather than reading the diff in isolation:
- read_at resume-address bug: a short/faulted read returned the region
  end as the next address, letting the stitcher splice non-adjacent
  memory into one blob
- region enumeration switched from an eager Vec to a lazy iterator
  (regions_from), restoring the early-exit behavior of the pre-refactor
  per-region loops (notably the 200ms trigger-scan loop)
- read_memory replaced with a borrowing read_into to drop per-read
  allocation in hot loops
- read_at gained a max_len bound tied to the caller's remaining budget
- WindowsRegionSource now opens its process handle through
  Platform::open_process instead of calling OpenProcess directly
- is_image: bool replaced with a RegionBacking enum (Anonymous / File /
  Kernel) with documented per-platform semantics
- open_process returns Result<_, String> with GetLastError() detail
  instead of discarding the failure reason
- read_at's contract now documents that returned bytes must live at
  the requested address, and added a region_at() method so callers can
  detect unmapped holes instead of skipping over them
- regions larger than read_cap are now read in chunks across multiple
  next_region() calls instead of silently losing everything past the
  cap (was breaking on large single-mapping heaps)
- documented that CredentialStore implementations may block on user
  interaction (locked keyring prompts)

PR #75 (branch feat/backend-refactor, Oost-hash), squash-merged
locally. Not pushed to GitHub.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
WFCD's warframe-items repo stopped putting a `name` field on
components[] entries (only uniqueName + itemCount remain, verified
live against Warframes.json/Primary.json/Pets.json). This broke three
call sites in wfcd.rs that expected it:

- Pass 2 component flattening skipped every single component via
  `continue`, so Chassis/Systems/Neuroptics/Barrel/etc. never made it
  into `items`/`display_names` at all.
- The top-level recipe tree builder and build_recipe_node's WFCD
  fallback branch then had nothing to fall back to but the literal
  string "Unknown", which is what showed up in the Foundry ingredient
  list (affects both v4.4.0 and v4.4.1 — this predates the PR #75
  refactor, it's an upstream data change).

Fix: derive the missing name from the component's own uniqueName path
relative to its parent's (".../AshPrimeChassisComponent" under parent
".../AshPrime" -> "Chassis" -> "Ash Prime Chassis"), used only when
WFCD's own `name` field is absent so a future reversal upstream is
still honored. Verified against live Ash Prime, Braton Prime and a
Kubrow/Kavat part — output matches the historical naming convention
exactly.

cargo test --lib: 76/77 (same pre-existing unrelated paths.rs failure
as before this change). pnpm build clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Following up on e0ab70f: drop the middle fallback that trusted
components[]'s raw `name` string when the catalog lookup missed.
WFCD no longer sends that field at all (confirmed: 0 components
anywhere carry it), and even if it comes back it shouldn't outrank
resolving the component's own uniqueName against the catalog — that's
the canonical identifier either way. Now it's strictly: catalog
lookup by uniqueName first, then path-derivation, nothing else.

cargo test --lib: 76/77 (same pre-existing unrelated paths.rs
failure). cargo check clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e most complete one

Multiple copies of the FULL_ACCOUNT blob can be readable in memory at
once (e.g. a buffer left over from before a re-serialize, still fully
intact and not yet overwritten, alongside the live one).
blob_seed_offsets()'s `{"` check only catches a stale copy whose
opening brace was already clobbered — it says nothing about a stale
copy that's merely older but still completely valid JSON.

capture_all_blobs/stitch_blobs previously committed to whichever
candidate scan finished stitching first and immediately dropped every
other in-flight scan once that happened, including ones opened before
it. That's a pure timing race with no check that the winner is
actually the current state — a still-intact stale snapshot can win
over the live blob just by having its end marker turn up sooner,
silently rolling the displayed inventory back ("items dropped" after a
scan, reported recurring on 4.4.1).

Fix: outside diagnostic `save` mode, every scan already in flight is
left to resolve (succeed or get dropped for growing too large) instead
of being killed off the moment another one completes, and new seeds
keep being opened for a bounded window after the first candidate
resolves (MAX_CANDIDATE_SEEDS / EXTRA_SEED_BUDGET) instead of stopping
dead. Once nothing is left in flight, the candidate with the most
total items/mods is sent — not whichever happened to finish first.
`save` mode (blob debug dumps) is unchanged.

Added two regression tests exercising both scan orderings; verified
the lower-address-first one reproduces the bug (credits: 1 instead of
2) against the pre-fix logic before restoring the fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Includes the stitch_blobs race-condition fix (843c437) for the
recurring "items dropped after scan" bug reported on 4.4.1.

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

component_suffix_from_path assumed a component's own path tail was always
literally prefixed by its parent's path tail. That's false whenever DE's
path segments use a different word order than the parent's own tail (e.g.
parent tail "PrimeAkstiletto" vs. component tail "AkstilettoPrimeBarrel"),
producing garbled catalog names like "Akstiletto Prime AkstilettoPrimeBarrel"
instead of "Akstiletto Prime Barrel". Confirmed via live WFCD data for
Akstiletto Prime, Vectis Prime, and Aksomati Prime.

Fix: split both the component's and parent's path tails (plus the parent's
display name) into words and subtract the parent's words from the
component's, order-independently, instead of a literal prefix-strip. Falls
back to the untouched (now word-split) tail when nothing lines up, same as
before.

component_suffix_from_path/component_name_from_path now take the parent's
display name so they can subtract on it too. camel_to_words in catalogue.rs
made pub(crate) so wfcd.rs can reuse the same PascalCase word-splitter.

Added 3 unit tests: reversed word order, the original matching-prefix case,
and the no-overlap fallback. cargo test --lib: 82/82 except the one
pre-existing unrelated paths.rs failure. pnpm build clean.

This was found while investigating a user report of relic reward icons
showing a grey "?" placeholder. Static/cache analysis of the current
(already-fixed, 4.4.2) catalog data shows the specific flagged item
(Akstiletto Prime Barrel) actually resolves to a valid image via the
parent-weapon-image fallback already in Pass 2 — so the garbled name
alone likely wasn't the direct cause of that screenshot (a stale
cache/build predating e0ab70f/4c7330e is the more likely explanation).
This fix is still worth having independently: it removes a fragile
dependency on fuzzy substring matching accidentally still finding these
components by their garbled name, and fixes the display name shown
anywhere it surfaces (Foundry lists, search, tooltips).

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

WFCD's components[] sub-objects stopped carrying ducats (same upstream
change that dropped name/imageName), but unlike those two fields there
was no fallback: comp_ducats was pushed as None unconditionally, with
no parent-item value to fall back to (ducats aren't stored on the
parent item either).

This silently broke the item.ducats.is_some() tradeable-item heuristic
used in wfm_queue.rs (gates the entire background WFM price-fetch
queue) and wfm_commands.rs (gates whether a fetched price persists to
disk), plus the ducat-value maps in monitor.rs and relic_pick.rs
(ducat_score is the default sort order for the relic-pick overlay's
relic list, not just the opt-in "ducat" best-pick priority).

Fix: ducat value is a pure function of drop rarity (Common=15,
Uncommon=45, Rare=100, mixed=25/65) — the Warframe Wiki computes it
the same way rather than storing it independently, and we already
have the rarity data from relic_rewards. Backfill it locally from
data already fetched, no live wiki dependency added. A small ~25-entry
bundled exceptions table (resources/ducat_exceptions.json, fetched
once from the wiki's Module:Void/data DUCAT_EXCEPTIONS table) covers
the handful of parts whose value doesn't follow the formula.

Runs after relic_rewards is built and before blueprint_names reads
item.ducats, so both components and blueprint entries get backfilled
in one pass. wfm_queue.rs/wfm_commands.rs/monitor.rs/relic_pick.rs
all read the same backfilled wfcd_items list, so no changes needed
there.

cargo test --lib: 85/86 (pre-existing unrelated paths.rs failure).
pnpm build clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Includes the ducat-value backfill fix (1442b1c).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
`ProcessRegions` re-read `/proc/pid/maps` for every region it visited,
which stretched a Linux walk of a Proton game to seconds per tier. A
failed chunk read also dropped the rest of its region, so a fault early
in a large mapping hid any blob further in.
- Resolve quantities/mods authority contradiction between memory scanner
  and companion API gap-fill (companion API is fully suspended, so this
  is build-correctness only, not a live behavior change)
- Merge the two independent EE.log tailers into one read loop
- Centralize Tauri event names in events.rs, drop confirmed-dead events
- Add a shared read-modify-write persistence helper and migrate
  inventory_state/companion_api/catalogue writers onto it
- Bump version to 4.4.6 across package.json, Cargo.toml, tauri.conf.json
- Add CONTRIBUTING.md documenting the dev -> main branch workflow now
  that main is protected by a ruleset (PR + 1 approval required)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
MiscAccountData can legitimately disappear from the FULL_ACCOUNT blob
between scans (same class of behavior as RecentVendorPurchases), so it
was being wrongly treated as a truncated-capture signal. Add it to
VOLATILE_SECTIONS so its absence no longer triggers a rejection.

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

Component names were derived from uniqueName path tails after WFCD dropped
components[].name; DE's path token for Neuroptics is "Helmet". Names now come
from ExportResources.json + dict.en.json, ExportRecipes resultType, and
Relics.json reward items. Path derivation is removed; unresolved components
are skipped and logged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Trade detection (log_watcher.rs):
- Restore the single-stage `received_raw` extraction in parse_trade_dialog.
  The Sep 22 backend-modularization refactor (eaaaa42) turned it into a
  two-stage extraction that re-searched the already-stripped substring for
  "the following:" — a string that could never be found there again by
  construction. This silently zeroed every trade's received side: plat
  purchases logged nothing at all, sales lost their received platinum and
  were misclassified as generic "trade".
- Strip trailing private-use-area glyphs (e.g. U+E000 rank/status icons)
  from the parsed player name so whisper auto-matching isn't broken by an
  invisible character.
- Added regression tests using real (redacted) EE.log dialog text from a
  user report, covering both the sale and purchase cases.

Item icons (wfcd.rs):
- Parts and blueprints (e.g. "Neuroptics", "Prime Neuroptics Blueprint")
  were showing their parent item's icon because WFCD's components[] lost
  imageName in an upstream schema change. Added an ExportResources-based
  icon lookup by uniqueName (NameResolver.icons/icon()), falling back to
  the blueprint's ExportRecipes resultType, and use it to override
  Parts/Blueprints images. ~1200 items get their correct icon back.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Closes the second half of the trade-logging bug fixed in f709769. The
clippy `too_many_arguments` refactor in d0a6c74 grouped several Tauri
command args (add_trade, wfm_create_riven_auction, wfm_receive_tokens)
into structs, which changed the expected IPC shape, but the frontend
invoke() calls were never updated to send { params: {...} } and the
failures were silently swallowed by an empty .catch(). Trades parsed
correctly from EE.log but never reached the database.

- Add #[serde(rename_all = "camelCase")] to AddTradeParams,
  RivenAuctionParams, ReceiveTokensParams
- Wrap frontend invoke() calls in { params: args } and log failures
  instead of swallowing them
- Emit a trades-updated event so the Reports tab refreshes live

Also bundles two disclosed cleanup commits from the same PR: a clippy
warning pass (24 warnings, mostly mechanical + parameter-grouping
structs, no behavior change) and a build-warning pass (static vs.
dynamic imports in 3 frontend files).

Squash-merged from PR #77 (Oost-hash), authored by the original
contributor.
Includes PR #77 (trade-logging IPC params/camelCase fix, squash-merged
in 4add56d).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fix: preserve trade dialogs across log reads
Includes PR #78 (trade dialog reconstruction across split EE.log reads,
merged in 320e022).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lyrex and others added 28 commits September 29, 2026 20:29
Upstream squashed PR 75 into eaaaa42. Its tree is identical to the PR
head 03e67da, which 59eb817 already merged. Recording it with the ours
strategy makes eaaaa42 the base for merging the rest of upstream dev.
WFCD dropped name, imageName and ducats from components[], so the
catalogue skipped every component on live data. Components now take
their names and icons from DE's ExportResources and dict.en tables,
and their ducats from relic drop rarity plus a bundled exceptions
table. add_trade and wfm_create_riven_auction now receive their
arguments wrapped in params with camelCase keys. The frontend used to
send them flat, Tauri rejected the call and the error was swallowed,
so in-game trades never reached the database.

Upstream's single EE.log tailer and its reward_watcher, blob_capture
and console login modules do not fit the fork's layout and stay out.
The frontend still starts the fork's log watcher. The section baseline
ignores RecentVendorPurchases again, a filter the PR 75 merge had
lost, and now MiscAccountData too.

Three upstream fixes need rework in the fork's layout and follow
separately: picking the most complete blob candidate (843c437), trade
dialogs split across log reads (2048b64) and the low-confidence reward
retry gate (b1f762e).
A stale copy of the inventory blob can stay intact in memory next to
the live one. The walk sent whichever copy finished stitching first,
so a stale copy at a lower address could roll the inventory back.

The walk now collects every candidate and sends the one with the most
items. After the first candidate it keeps reading a bounded stretch of
memory to catch a second copy.

Ports upstream 843c437.
The log watcher only kept a trade dialog when the whole dialog arrived
in one read. When the game was still writing it as the tailer woke,
the trade was never recorded. The watcher now keeps a rolling window
of recent log text and parses the last dialog once the success line
arrives.

The game sometimes appends an icon glyph to the trading partner's
name. It is now stripped, so the name matches the whisper sender.

Ports upstream 2048b64 and the name fix from f709769.
A reward card could pass the score threshold on one or two OCR words,
which cannot tell "Wisp Prime Neuroptics" from "Wisp Prime Chassis".
Such a card is now low confidence unless its icon confirmed it, as is
a card the catalog could not match at all. The overlay then waits for
the soft retries before it locks the result.

Upstream only replaces the best read on a higher card count, so its
retries could never change the shown result. Here a confident read of
as many cards replaces a low-confidence one.

Ports the gate from upstream b1f762e.
* fix: complete WFM set sales as transactions

* fix: refresh item catalogue on startup

* fix: make WFM sale recording opt-in

* fix: avoid duplicate catalogue startup refresh

* fix: refresh catalogue daily without a second startup probe
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…fix; bump to 4.4.13

Kitguns (Pistols/LotusModularSecondary*) shared a generic wrapper
ItemType across all variants and were never given the same
"key by defining component" treatment already used for Amps and
Zaws. Every owned Kitgun collapsed onto at most 3 shared map keys
with amount overwritten (not accumulated) and no resolvable name.
Now keyed by the Barrel component path, same pattern as Amps/Zaws.

Also includes the component-icon fix from wfcd.rs (parts/blueprints
were showing their parent item's icon since WFCD dropped
components[].imageName upstream; now resolved via ExportResources
icon lookup by uniqueName).

Version bumped to 4.4.13 across package.json, Cargo.toml, tauri.conf.json.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Upstream keeps the trade dialog parser and `handle_trade_completion`
in log_watcher.rs. The fork now carries the same code there, and
trade_log.rs keeps only the trade commands.
Blob capture runs from `spawn_blob_capture_thread` in its own module,
and monitor.rs gains the catalog, startup-state and thread helpers
under upstream's names. Behaviour is unchanged: the fork keeps its
probe/walk scheduling and inventory cache model.
ocr/mod.rs keeps the shared reward matching, and ocr/linux.rs holds
the X11 capture and Tesseract code behind upstream's function names.
ocr/windows.rs is upstream's file unchanged and is not built.
`OcrParams` moves to the crate root, where upstream keeps it.
The reward tailer in `start_monitor` and the always-on
`start_log_watcher` both read EE.log and both emitted `wfm-whisper`,
so each whisper showed up twice while the monitor ran.

One thread, spawned at startup, tails EE.log for the app's lifetime
from `reward_watcher.rs` and calls handlers in `log_watcher.rs` under
upstream's names. A panic in one handler skips that chunk and the
thread keeps running. The relic-reward OCR trigger fires only while
the monitor is on; the riven unveil event fires with it off too.
`start_monitor` moves to lib.rs.
Debug builds keep settings, database, caches and logs in .frameforge-dev/
next to the project instead of the installed app's directories, and are
titled FrameForge Dev so the two environments are always distinguishable.
The root resolution, window title and update-check skip compile out of
release builds; production keeps using the same paths as before.
scan_log.txt was added to LEGACY_CACHE_FILES in v4.1.0, so the
migration deletes it by design and the emptied legacy directory is
removed. Seed an unrecognised file instead: that is the case the test
exists to protect.
Amps, zaws and kitguns are keyed by their component path (prism, tip,
barrel) when the inventory cache is built, but the change-detection state
keyed them by the generic wrapper item_type. The cache therefore seeded a
quantity the first scanned blob could not reproduce, so every app start
reported the seeded count dropping to zero (Mote Prism 1 -> 0 was logged
once per session) even though nothing was consumed.

Extract inventory_state::modular_component_path() as the single source of
keying for both, replacing the three duplicated blocks in
build_inventory_from_blob.
fix: modular items reported as flipping to 0 on every scan
chore: isolate debug builds from production data
…ents

WFCD hasn't picked up these new item paths yet, so add manual name
corrections so they display correctly in the catalogue in the meantime.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
WFCD does not list these item paths yet, so the bundled corrections add
them until it does.
Undo re-creates a fully sold listing, and warframe.market rejects a mod
order that has no rank. The listing's rank now travels with the undo
info.
With the new "Record automatic sales" setting on, a detected sale closes
the order through warframe.market's close endpoint, so the sale counts
in the seller's statistics. The setting is off by default, and
FrameForge cannot undo a recorded sale.

EE.log lists a traded set as its parts. The matcher now tries the
listing named in the buyer's whisper first and falls back to the parts.
A development build reads and writes the installed app's settings,
database, caches and warframe.market login. Setting FRAMEFORGE_ROOT moves
all four roots under that directory. That run also stores its own
login, keeps its factory-reset marker to itself, titles the window
"FrameForge Dev" and skips the launch update check. Without the
variable nothing changes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The merge keeps the fork's tree and takes only the version. Earlier fork
commits already ported sale recording, the part name corrections, the
mod rank on undo and the separate dev data root, the last one as opt-in.
The fork already covers catalogue ETags, Kitgun keying, modular
quantities and the debug file removal, and upstream's blueprint icon
fallback matches no item in the current catalogue.
Hand-ported and skipped upstream changes left their commits outside
main, so GitHub kept counting the fork as behind. A sync now always ends
with the tag merge, and upstream's tags are fetched under
refs/upstream-tags/ so their names no longer clash with ours.

Fork-delta: fork-only
@Lyrex
Lyrex merged commit 56394ff into main Oct 1, 2026
5 checks passed
@Lyrex
Lyrex deleted the backmerge-4.5.0 branch October 1, 2026 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant