fix: repair the negotiation system (pricing, wallet guard, stable NPC) + one-shot-per-car rule - #62
Open
wpinrui wants to merge 16 commits into
Open
fix: repair the negotiation system (pricing, wallet guard, stable NPC) + one-shot-per-car rule#62wpinrui wants to merge 16 commits into
wpinrui wants to merge 16 commits into
Conversation
This was referenced Jul 13, 2026
wpinrui
added a commit
that referenced
this pull request
Jul 13, 2026
Tricia previously guided the owner through long manual checklists. Her last plan for PR #62 ran to ~20 items and required a specific stat build, a page-refresh trick to escape a screen, and carried a warning that one check might never become reachable. That is not a viable use of the owner's time. Automation is now the default. Every check is triaged Automated or Manual; Manual requires a stated reason, and the only valid ones are spatial layout, colour/legibility, responsive reflow, clipping, animation/audio, and feel. "Awkward to automate" is explicitly not a reason -- it now routes to the Implementer as a testability defect rather than becoming owner homework. The manual pass is capped at roughly eight items, each requiring exact navigation, exact setup via the debug panel rather than grinding, and a stated pass condition. Feel questions are separated from the checklist and routed to the PM as product signal. Tests may never be weakened, skipped, or deleted to get a green run. Permission boundaries widen accordingly: Tricia may write test files, test config, and test-only devDependencies, and may commit and push tests to the PR branch under test. She may not touch application source, app dependencies, or build config. Project notes record that src/engine/ is pure and the RNG is seeded and injectable, so negotiation and listing-lifecycle tests are deterministic with zero new dependencies, and that bare `npm test` starts watch mode and hangs.
First automated tests in the repo. They assert the five fixes this PR claims, plus the new one-shot-per-car rule: - the wallet guard: no purchase path can drive money negative - Accept deducts exactly the asking price printed on the button - NPC pricing is anchored to listing.askingPrice, not to market value - the walkaway floor never exceeds 90% of asking, for every legal trait set - counter-offers stay inside the seller's own band - the seller is stable per listing across intervening days and actions - a failed negotiation removes the listing from the market Each test was checked against a reintroduction of the bug it covers, so a regression turns it red rather than leaving it a happy-path pass. vitest.config.ts is kept separate from vite.config.ts so test runs don't drag in the Electron plugins. Engine data is served from data/ through a window.electronAPI stub in the setup file. No new dependencies.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR actually is
This is the repair for the negotiation system, which is currently shipped and broken on
mainin both directions at once — you can buy cars you cannot afford (going to negative money) and be silently overcharged on Accept. It is not a polish PR.Scope: 15 commits, 19 files,
src/only. No data, config, or docs touched.Bugs on
mainthat this fixesmaincloseNegotiationdeducted the price with no affordability check — you could buy a car you couldn't afford and go to negative money.submitOffer,acceptAtListPrice, andcloseNegotiation, each raising a "You can't afford this." toast. The Accept button is alsodisabledwhen the player can't cover the asking price.listing.askingPrice; the modal labellednpc.targetPriceas "Asking price". These are unrelated numbers (targetPricewas market value × ~1.15). The player was quoted two different prices for the same car.listing.askingPrice.calculateNpcPricing()now takes ananchorPrice, andNegotiationItemcarriesaskingPrice.Accept $<last counter>butacceptListPrice()chargednpc.targetPrice— always higher. Silent overcharge.item.askingPrice.impatientinstead ofImpatient.getTraitDefinition(traitId).name.This is new, it is not a bug fix, and it was not in the original description.
closeNegotiationnow removes the listing from the market whenever the negotiation ends in anything other than an accepted sale (store/index.ts). Because Walk Away is wired directly tocloseNegotiation, this means:You get one negotiation per car, ever. This is a real design decision that deserves a real decision — flagging it loudly rather than letting it merge as an implementation detail.
DebugPanel.tsx/.cssship in this branch: an on-screen money setter, plus adebugSetMoneystore action. It is gated onconst DEBUG_MODE = true— hardcoded instore/index.ts, notimport.meta.env.DEV— so it renders in a production build as written.The owner has seen this and chosen to keep it. Noted here so nobody merges it by accident.
Pricing overhaul detail
The load-bearing change; everything else in negotiation sits on top of it.
targetMultiplierbase 1.15 → 1.0 — the NPC now targets the asking price rather than a markup over market value.walkAwayPriceis clamped to at most 90% of the anchor:min(anchor × walkAwayMultiplier, anchor × 0.90). BasewalkAwayMultiplieris still 0.80 and traits still move it; the clamp is an upper bound only, guaranteeing at least a 10% negotiation band on every car.marketValueis still carried onNegotiationItembut no longer drives pricing.UI polish
::afterspacer (Newspaper, Browse Results).overflow: hiddenfrom.loc,border-radiusmoved onto the photo, responsive grid minimummin(390px, 100%),min-height: 0on.map-body.Closes #52.
Notes for the reviewer
traitVisibilityPerPointis net-zero here. One commit raised it 0.01 → 0.10, a later commit reverted it. No data file is touched. Don't chase it through the commit list — the balance question is being ticketed separately.git diff main..) will show spurious.claude/deletions. The branch predates PR chore: track .claude agent config in git #63; it never touched those files. A three-dot diff (git diff main...) — which is what GitHub's Files-changed tab shows — confirmssrc/-only, 19 files.Known gaps this does not close
Real, on
main, and out of scope — do not block on them, they have their own tickets:negotiate_junker_purchaseis a permanently dead activity card.Test plan
Money path — the two financial bugs:
Negotiation mechanics:
Impatient, notimpatient."One shot per car" (new rule — assess how it feels, not just whether it works):
UI: