iv: fix route selection and otium den deduction in Forum.jsx

This commit is contained in:
otivm
2026-05-03 18:54:40 +00:00
parent e13b54a697
commit 7aed095f69

View File

@@ -2,6 +2,10 @@
// FORUM context screen. Replaces Ledger.jsx. // FORUM context screen. Replaces Ledger.jsx.
// Renders two divs directly into Shell's two-col layout grid. // Renders two divs directly into Shell's two-col layout grid.
// All game logic migrated from Ledger.jsx. // All game logic migrated from Ledger.jsx.
//
// Fixes from initial version:
// - isRouteUnlocked called with route.id (string), not route (object)
// - applyOtium result has 8 dn deducted to match server-side debit
import { useState, useEffect, useRef } from 'react' import { useState, useEffect, useRef } from 'react'
import Section from '../components/Section.jsx' import Section from '../components/Section.jsx'
@@ -27,7 +31,6 @@ export default function Forum({ state, onStateChange, onNewGame }) {
const [otium, setOtium] = useState(null) const [otium, setOtium] = useState(null)
const [message, setMessage] = useState('') const [message, setMessage] = useState('')
const [journal, setJournal] = useState(getSeenJournalEntries(state)) const [journal, setJournal] = useState(getSeenJournalEntries(state))
const [newEntryKey, setNewEntryKey] = useState(null)
const tickRef = useRef(null) const tickRef = useRef(null)
const msgRef = useRef(null) const msgRef = useRef(null)
@@ -48,11 +51,7 @@ export default function Forum({ state, onStateChange, onNewGame }) {
const newState = applyDispatch(state, dispatch.routeId) const newState = applyDispatch(state, dispatch.routeId)
const route = ROUTES.find(r => r.id === dispatch.routeId) const route = ROUTES.find(r => r.id === dispatch.routeId)
showMessage(`Galley returned. +${route.profit} denarii.`) showMessage(`Galley returned. +${route.profit} denarii.`)
if (entry) { if (entry) setJournal(j => [entry, ...j])
setJournal(j => [entry, ...j])
setNewEntryKey(`${dispatch.routeId}-${newState.route_dispatches[dispatch.routeId]}`)
setTimeout(() => setNewEntryKey(null), 5000)
}
setSelectedRoute(null) setSelectedRoute(null)
onStateChange(newState) onStateChange(newState)
} }
@@ -61,8 +60,10 @@ export default function Forum({ state, onStateChange, onNewGame }) {
const elapsed = now - otium.startMs const elapsed = now - otium.startMs
if (elapsed >= OTIUM_DURATION_MS) { if (elapsed >= OTIUM_DURATION_MS) {
setOtium(null) setOtium(null)
const newState = applyOtium(state) // Apply otium aut gain, then deduct 8 dn to match server-side debit
showMessage('Otium complete. Auctoritas recorded.') const withAut = applyOtium(state)
const newState = { ...withAut, den: Math.max(0, withAut.den - OTIUM_CYCLE_TOTAL_DN) }
showMessage(`Otium complete. ${OTIUM_CYCLE_TOTAL_DN} dn. Auctoritas recorded.`)
onStateChange(newState) onStateChange(newState)
} }
} }
@@ -74,6 +75,7 @@ export default function Forum({ state, onStateChange, onNewGame }) {
function handleSelectRoute(routeId) { function handleSelectRoute(routeId) {
if (busy) return if (busy) return
// Fix: pass route.id string to isRouteUnlocked, not the route object
if (!isRouteUnlocked(state, routeId)) return if (!isRouteUnlocked(state, routeId)) return
setSelectedRoute(prev => prev === routeId ? null : routeId) setSelectedRoute(prev => prev === routeId ? null : routeId)
} }
@@ -103,19 +105,14 @@ export default function Forum({ state, onStateChange, onNewGame }) {
} }
// Progress // Progress
let progressPct = 0, progressLabel = '', progressSub = '' let progressPct = 0
if (dispatch) { if (dispatch) {
progressPct = Math.min(((Date.now()-dispatch.startMs)/dispatch.durationMs)*100, 100) progressPct = Math.min(((Date.now()-dispatch.startMs)/dispatch.durationMs)*100, 100)
const route = ROUTES.find(r=>r.id===dispatch.routeId)
progressLabel = `${WAYPOINTS[route.from].name}${WAYPOINTS[route.to].name}`
progressSub = `${Math.round(progressPct)}%`
} else if (otium) { } else if (otium) {
progressPct = Math.min(((Date.now()-otium.startMs)/OTIUM_DURATION_MS)*100, 100) progressPct = Math.min(((Date.now()-otium.startMs)/OTIUM_DURATION_MS)*100, 100)
progressLabel = 'resting...'
progressSub = `${Math.round(progressPct)}%`
} }
// Section data // Section data — active venture
const activeVentureItems = dispatch ? (() => { const activeVentureItems = dispatch ? (() => {
const route = ROUTES.find(r => r.id === dispatch.routeId) const route = ROUTES.find(r => r.id === dispatch.routeId)
return [ return [
@@ -130,8 +127,9 @@ export default function Forum({ state, onStateChange, onNewGame }) {
{ key: 'Cost', value: `${OTIUM_CYCLE_TOTAL_DN} dn`, cls: 'bad' }, { key: 'Cost', value: `${OTIUM_CYCLE_TOTAL_DN} dn`, cls: 'bad' },
] : [{ key: 'Status', value: 'No galley at sea', cls: '' }] ] : [{ key: 'Status', value: 'No galley at sea', cls: '' }]
// Route list — fix: pass route.id to isRouteUnlocked
const routeItems = ROUTES.map(route => { const routeItems = ROUTES.map(route => {
const unlocked = isRouteUnlocked(state, route) const unlocked = isRouteUnlocked(state, route.id)
const vectura = Math.round(route.cost * 0.60 * 10) / 10 const vectura = Math.round(route.cost * 0.60 * 10) / 10
const portoria = Math.round(route.cost * 0.25 * 10) / 10 const portoria = Math.round(route.cost * 0.25 * 10) / 10
const other = Math.round((route.cost - vectura - portoria) * 10) / 10 const other = Math.round((route.cost - vectura - portoria) * 10) / 10
@@ -145,7 +143,11 @@ export default function Forum({ state, onStateChange, onNewGame }) {
profit: route.profit, profit: route.profit,
vectura, portoria, other, vectura, portoria, other,
locked: !unlocked, locked: !unlocked,
lock_reason: !unlocked ? (state.den<route.unlock_den ? `${route.unlock_den.toLocaleString()} dn` : `Auctoritas ${route.unlock_aut}`) : null, lock_reason: !unlocked
? (state.den < route.unlock_den
? `${route.unlock_den.toLocaleString()} dn`
: `Auctoritas ${route.unlock_aut}`)
: null,
} }
}) })
@@ -181,7 +183,6 @@ export default function Forum({ state, onStateChange, onNewGame }) {
background: 'var(--otivm-ink)', color: 'var(--otivm-parch)', background: 'var(--otivm-ink)', color: 'var(--otivm-parch)',
padding: '10px 18px', borderRadius: '3px', padding: '10px 18px', borderRadius: '3px',
fontSize: '0.8rem', fontStyle: 'italic', zIndex: 300, fontSize: '0.8rem', fontStyle: 'italic', zIndex: 300,
gridColumn:'1/-1',
}}> }}>
{message} {message}
</div> </div>