Reputation economy: rep-scaled buy/refuel discounts + job reward quality (ISS-072, v0.38.0)
CI / build + unit + e2e (pull_request) Successful in 9m3s
CI / build + unit + e2e (pull_request) Successful in 9m3s
This commit is contained in:
+11
-2
@@ -3,7 +3,7 @@
|
||||
import { useCallback, useEffect, useRef, useState, type ChangeEvent } from 'react';
|
||||
import type { GeoJsonFeatureCollection } from '@/lib/airports';
|
||||
import type { PlaneState, StateSnapshot } from '@/lib/game/state';
|
||||
import { PLANE_TYPES } from '@/lib/game/catalog';
|
||||
import { FUEL_DISCOUNT_PER_REP, MAX_FUEL_DISCOUNT, PLANE_TYPES, repRate } from '@/lib/game/catalog';
|
||||
import { abandonPenalty } from '@/lib/game/penalty';
|
||||
import { formatCash, formatDeadlineRelative, formatFuel, formatSimClock, formatSpecialLabel } from '@/lib/format';
|
||||
import { TIME_SCALES, scaleToSlider, sliderToScale } from '@/lib/timeScale';
|
||||
@@ -217,7 +217,12 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh
|
||||
(state?.planes.find((p) => p.id === refuelPlaneId && p.status === 'available' && p.fuel < p.maxFuel)) ??
|
||||
refuelable[0] ??
|
||||
null;
|
||||
const refuelCost = refuelPlane && state ? (refuelPlane.maxFuel - refuelPlane.fuel) * state.fuelPricePerL : null;
|
||||
// v0.38.0 (ISS-072): rep-scaled preview — same formula as the server's refuelPlane 'full' mode.
|
||||
const fuelDiscountRate = state ? repRate(state.account.reputation, FUEL_DISCOUNT_PER_REP, MAX_FUEL_DISCOUNT) : 0;
|
||||
const refuelCost =
|
||||
refuelPlane && state
|
||||
? Math.round((refuelPlane.maxFuel - refuelPlane.fuel) * state.fuelPricePerL * (1 - fuelDiscountRate) * 100) / 100
|
||||
: null;
|
||||
|
||||
// Slider commit (v0.9.1): the displayed value follows the drag optimistically via
|
||||
// pendingScale; POST /api/sim fires after a 250 ms debounce, or immediately when the
|
||||
@@ -566,6 +571,10 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh
|
||||
<span data-testid="market-refuel-cost" className="market-preview">
|
||||
{refuelCost !== null ? formatCash(refuelCost) : ''}
|
||||
</span>
|
||||
{/* v0.38.0 (ISS-072): rep discount hint — only when the account actually gets one. */}
|
||||
{fuelDiscountRate > 0 && (
|
||||
<span data-testid="refuel-discount" className="special-badge">−{Math.round(fuelDiscountRate * 100)}%</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import type { PackageState, PlaneState, StateSnapshot } from '@/lib/game/state';
|
||||
import { PLANE_TYPES, SELL_FRACTION } from '@/lib/game/catalog';
|
||||
import { MAX_PLANE_DISCOUNT, PLANE_DISCOUNT_PER_REP, PLANE_TYPES, repRate, SELL_FRACTION } from '@/lib/game/catalog';
|
||||
import { maxRangeKm } from '@/lib/sim/movement';
|
||||
import { formatCargo, formatCash, formatDeadlineRelative, formatSpecialLabel } from '@/lib/format';
|
||||
import { useGameAction } from '@/lib/useGameAction';
|
||||
@@ -151,6 +151,9 @@ export default function RightPane({ state, loading, applySnapshot, onHover, onEr
|
||||
// Previews computed client-side from the snapshot's catalog + economy constants (v0.10.0).
|
||||
const sellEntry = sellPlane ? catalog.find((e) => e.model === sellPlane.model) : undefined;
|
||||
const sellPayout = sellEntry ? Math.round(sellEntry.price * state.sellFraction * 100) / 100 : null;
|
||||
// v0.38.0 (ISS-072): the buy row shows the EFFECTIVE price — same rep-scaled formula as the server's buyPlane.
|
||||
const planeDiscountRate = repRate(state.account.reputation, PLANE_DISCOUNT_PER_REP, MAX_PLANE_DISCOUNT);
|
||||
const buyPrice = buyEntry ? Math.round(buyEntry.price * (1 - planeDiscountRate) * 100) / 100 : null;
|
||||
|
||||
return (
|
||||
<aside data-testid="right-pane" className="pane right-pane">
|
||||
@@ -265,8 +268,12 @@ export default function RightPane({ state, loading, applySnapshot, onHover, onEr
|
||||
))}
|
||||
</select>
|
||||
<span data-testid="market-buy-price" className="market-preview">
|
||||
{buyEntry ? formatCash(buyEntry.price) : ''}
|
||||
{buyPrice !== null ? formatCash(buyPrice) : ''}
|
||||
</span>
|
||||
{/* v0.38.0 (ISS-072): rep discount hint — only when the account actually gets one. */}
|
||||
{planeDiscountRate > 0 && (
|
||||
<span data-testid="market-buy-discount" className="special-badge">−{Math.round(planeDiscountRate * 100)}%</span>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
data-testid="market-buy"
|
||||
|
||||
Reference in New Issue
Block a user