Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Whitestone
7fde7569e4 fix: [VISITOR] Distinguish visitor mode from operator mode in the Nexus UI (closes #710) 2026-04-10 20:10:33 -04:00
7 changed files with 143 additions and 263 deletions

66
app.js
View File

@@ -48,6 +48,9 @@ let chatOpen = true;
let loadProgress = 0;
let performanceTier = 'high';
// ═══ UI MODE (VISITOR / OPERATOR) ═══
let uiMode = 'visitor'; // 'visitor' | 'operator'
// ═══ HERMES WS STATE ═══
let hermesWs = null;
let wsReconnectTimer = null;
@@ -1867,6 +1870,9 @@ function setupControls() {
if (e.key.toLowerCase() === 'v' && document.activeElement !== document.getElementById('chat-input')) {
cycleNavMode();
}
if (e.key.toLowerCase() === 'o' && document.activeElement !== document.getElementById('chat-input')) {
toggleUIMode();
}
if (e.key.toLowerCase() === 'f' && activePortal && !portalOverlayActive) {
activatePortal(activePortal);
}
@@ -1964,18 +1970,9 @@ function setupControls() {
});
document.getElementById('chat-send').addEventListener('click', () => sendChatMessage());
// Add MemPalace mining button
// Add MemPalace mining button (operator-only)
document.querySelector('.chat-quick-actions').innerHTML += `
<button class="quick-action-btn" onclick="mineMemPalaceContent()">Mine Chat</button>
<div id="mem-palace-stats" class="mem-palace-stats">
<div>Compression: <span id="compression-ratio">--</span>x</div>
<div>Docs: <span id="docs-mined">0</span></div>
<div>AAAK: <span id="aaak-size">0B</span></div>
<div>Compression: <span id="compression-ratio">--</span>x</div>
<div>Docs: <span id="docs-mined">0</span></div>
<div>AAAK: <span id="aaak-size">0B</span></div>
<div class="mem-palace-logs" style="margin-top:4px; font-size:10px; color:#4af0c0;">Logs: <span id="mem-logs">0</span></div>
</div>
<button class="quick-action-btn operator-only" onclick="mineMemPalaceContent()">Mine Chat</button>
`;
// Chat quick actions
@@ -2006,6 +2003,53 @@ function setupControls() {
document.getElementById('atlas-toggle-btn').addEventListener('click', openPortalAtlas);
document.getElementById('atlas-close-btn').addEventListener('click', closePortalAtlas);
// ═══ VISITOR / OPERATOR MODE TOGGLE ═══
// Restore saved mode from localStorage
const savedMode = localStorage.getItem('nexus-ui-mode');
if (savedMode === 'operator') {
uiMode = 'operator';
}
applyUIMode();
// Create and append mode toggle button to the HUD controls area
const modeBtn = document.createElement('button');
modeBtn.id = 'mode-toggle-btn';
modeBtn.className = 'mode-toggle-btn';
modeBtn.setAttribute('data-mode', uiMode);
modeBtn.innerHTML = modeToggleLabel();
modeBtn.title = uiMode === 'visitor' ? 'Switch to Operator mode' : 'Switch to Visitor mode';
modeBtn.addEventListener('click', toggleUIMode);
const hudEl = document.getElementById('hud');
if (hudEl) hudEl.appendChild(modeBtn);
}
function toggleUIMode() {
uiMode = uiMode === 'visitor' ? 'operator' : 'visitor';
localStorage.setItem('nexus-ui-mode', uiMode);
applyUIMode();
// Update button
const btn = document.getElementById('mode-toggle-btn');
if (btn) {
btn.setAttribute('data-mode', uiMode);
btn.innerHTML = modeToggleLabel();
btn.title = uiMode === 'visitor' ? 'Switch to Operator mode' : 'Switch to Visitor mode';
}
addChatMessage('system', `Mode: ${uiMode.toUpperCase()}. ${uiMode === 'operator' ? 'Operator panels enabled.' : 'Visitor view. Clean and focused.'}`);
}
function modeToggleLabel() {
if (uiMode === 'visitor') {
return '<span class="mode-icon">👁</span> VISITOR';
}
return '<span class="mode-icon">⚙</span> OPERATOR';
}
function applyUIMode() {
document.body.classList.toggle('visitor-mode', uiMode === 'visitor');
}
function sendChatMessage(overrideText = null) {

View File

@@ -157,7 +157,8 @@
<!-- Controls hint + nav mode -->
<div class="hud-controls">
<span>WASD</span> move &nbsp; <span>Mouse</span> look &nbsp; <span>Enter</span> chat &nbsp;
<span>V</span> mode: <span id="nav-mode-label">WALK</span>
<span>V</span> nav: <span id="nav-mode-label">WALK</span> &nbsp;
<span>O</span> toggle mode &nbsp;
<span id="nav-mode-hint" class="nav-mode-hint"></span>
&nbsp; <span class="ws-hud-status">HERMES: <span id="ws-status-dot" class="chat-status-dot"></span></span>
</div>

View File

@@ -1,112 +0,0 @@
# Bannerlord Local Install Guide (macOS / Apple Silicon)
## Goal
Run the GOG Mount & Blade II: Bannerlord build natively on Alexander's Mac (arm64, macOS Sequoia+).
## Prerequisites
- macOS 14+ on Apple Silicon (arm64)
- ~60 GB free disk space (game + Wine prefix)
- GOG installer files in `~/Downloads/`:
- `setup_mount__blade_ii_bannerlord_1.3.15.109797_(64bit)_(89124).exe`
- `setup_mount__blade_ii_bannerlord_1.3.15.109797_(64bit)_(89124)-1.bin` through `-13.bin`
## Step 1: Install Porting Kit
Porting Kit (free) wraps Wine/GPTK for macOS. It has a GUI but we automate what we can.
```bash
brew install --cask porting-kit
```
Launch it once to complete first-run setup:
```bash
open -a "Porting Kit"
```
## Step 2: Create Wine Prefix + Install Game
**Option A: Via Porting Kit GUI (recommended)**
1. Open Porting Kit
2. Click "Install Game" → "Custom Port" or search for Bannerlord
3. Point it at: `~/Downloads/setup_mount__blade_ii_bannerlord_1.3.15.109797_(64bit)_(89124).exe`
4. Follow the GOG installer wizard
5. Install to default path inside the Wine prefix
6. When done, note the prefix path (usually `~/Library/Application Support/PortingKit/...`)
**Option B: Manual Wine prefix (advanced)**
If you have Homebrew Wine (or GPTK) installed:
```bash
# Create prefix
export WINEPREFIX="$HOME/Games/Bannerlord"
wine64 boot /init
# Run the GOG installer (it auto-chains the .bin files)
cd ~/Downloads
wine64 setup_mount__blade_ii_bannerlord_1.3.15.109797_\(64bit\)_\(89124\).exe
```
Follow the GOG installer wizard. Default install path is fine.
## Step 3: Locate the Game Binary
After installation, the game executable is at:
```
$WINEPREFIX/drive_c/GOG Games/Mount & Blade II Bannerlord/bin/Win64_Shipping_Client/Bannerlord.exe
```
Or inside Porting Kit's prefix at:
```
~/Library/Application Support/PortingKit/<prefix-name>/drive_c/GOG Games/Mount & Blade II Bannerlord/bin/Win64_Shipping_Client/Bannerlord.exe
```
## Step 4: First Launch
```bash
# Find the actual path first, then:
cd "$HOME/Games/Bannerlord/drive_c/GOG Games/Mount & Blade II Bannerlord/bin/Win64_Shipping_Client"
wine64 Bannerlord.exe
```
Or use the launcher script:
```bash
./portal/bannerlord/launch.sh
```
## Step 5: Proof (Operator Checklist)
- [ ] Game window opens and is visible on screen
- [ ] At least the main menu renders (TaleWorlds logo, "Campaign", "Custom Battle", etc.)
- [ ] Screenshot taken: save to `portal/bannerlord/proof/`
- [ ] Launch command recorded below for repeatability
**Launch command (fill in after install):**
```
# Repeatable launch:
./portal/bannerlord/launch.sh
```
## Troubleshooting
**Black screen on launch:**
- Try: `wine64 Bannerlord.exe -force-d3d11` or `-force-vulkan`
- Set Windows version: `winecfg` → set to Windows 10
**Missing DLLs:**
- Install DirectX runtime: `winetricks d3dx9 d3dx10 d3dx11 vcrun2019`
**Performance:**
- GPTK/Rosetta overhead is expected; 30-60 FPS is normal on M1/M2
- Lower in-game graphics settings to "Medium" for first run
**Installer won't chain .bin files:**
- Make sure all .bin files are in the same directory as the .exe
- Verify with: `ls -la ~/Downloads/setup_mount__blade_ii_bannerlord_*`
## References
- GamePortal Protocol: `GAMEPORTAL_PROTOCOL.md`
- Portal config: `portals.json` (entry: "bannerlord")
- GOG App ID: Mount & Blade II: Bannerlord
- Steam App ID: 261550 (for Steam stats integration)

View File

@@ -1,115 +0,0 @@
#!/usr/bin/env bash
# Bannerlord Launcher for macOS (Apple Silicon via Wine/GPTK)
# Usage: ./portal/bannerlord/launch.sh [--wine-prefix PATH] [--exe PATH]
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# Defaults — override with flags or environment
WINEPREFIX="${WINEPREFIX:-$HOME/Games/Bannerlord}"
BANNERLORD_EXE=""
WINE_CMD=""
# Parse args
while [[ $# -gt 0 ]]; do
case "$1" in
--wine-prefix) WINEPREFIX="$2"; shift 2 ;;
--exe) BANNERLORD_EXE="$2"; shift 2 ;;
--help)
echo "Usage: $0 [--wine-prefix PATH] [--exe PATH]"
echo ""
echo "Defaults:"
echo " Wine prefix: $WINEPREFIX"
echo " Auto-discovers Bannerlord.exe in the prefix"
exit 0
;;
*) echo "Unknown arg: $1"; exit 1 ;;
esac
done
# Find wine command
find_wine() {
if command -v wine64 &>/dev/null; then
echo "wine64"
elif command -v wine &>/dev/null; then
echo "wine"
elif [ -f "/Applications/Whisky.app/Contents/Resources/WhiskyCmd" ]; then
echo "/Applications/Whisky.app/Contents/Resources/WhiskyCmd"
else
echo ""
fi
}
WINE_CMD="$(find_wine)"
if [ -z "$WINE_CMD" ]; then
echo "ERROR: No Wine runtime found."
echo "Install one of:"
echo " brew install --cask porting-kit"
echo " brew install --cask crossover"
echo " brew tap apple/apple && brew install game-porting-toolkit"
exit 1
fi
echo "Wine runtime: $WINE_CMD"
echo "Wine prefix: $WINEPREFIX"
# Find Bannerlord.exe if not specified
if [ -z "$BANNERLORD_EXE" ]; then
# Search common GOG install paths
SEARCH_PATHS=(
"$WINEPREFIX/drive_c/GOG Games/Mount & Blade II Bannerlord/bin/Win64_Shipping_Client/Bannerlord.exe"
"$WINEPREFIX/drive_c/GOG Games/Mount Blade II Bannerlord/bin/Win64_Shipping_Client/Bannerlord.exe"
"$WINEPREFIX/drive_c/Program Files/Mount & Blade II Bannerlord/bin/Win64_Shipping_Client/Bannerlord.exe"
)
# Also search PortingKit prefixes
while IFS= read -r -d '' exe; do
SEARCH_PATHS+=("$exe")
done < <(find "$HOME/Library/Application Support/PortingKit" -name "Bannerlord.exe" -print0 2>/dev/null || true)
for path in "${SEARCH_PATHS[@]}"; do
if [ -f "$path" ]; then
BANNERLORD_EXE="$path"
break
fi
done
fi
if [ -z "$BANNERLORD_EXE" ] || [ ! -f "$BANNERLORD_EXE" ]; then
echo "ERROR: Bannerlord.exe not found."
echo "Searched:"
echo " $WINEPREFIX/drive_c/GOG Games/"
echo " ~/Library/Application Support/PortingKit/"
echo ""
echo "Run the install first. See: portal/bannerlord/INSTALL.md"
exit 1
fi
echo "Game binary: $BANNERLORD_EXE"
echo "Launching..."
echo ""
# Log the launch for proof
LAUNCH_LOG="$SCRIPT_DIR/proof/launch_$(date +%Y%m%d_%H%M%S).log"
mkdir -p "$SCRIPT_DIR/proof"
{
echo "=== Bannerlord Launch ==="
echo "Date: $(date -Iseconds)"
echo "Wine: $WINE_CMD"
echo "Prefix: $WINEPREFIX"
echo "Binary: $BANNERLORD_EXE"
echo "User: $(whoami)"
echo "macOS: $(sw_vers -productVersion)"
echo "Arch: $(uname -m)"
echo "========================="
} > "$LAUNCH_LOG"
echo "Launch log: $LAUNCH_LOG"
echo ""
# Set the prefix and launch
export WINEPREFIX
EXE_DIR="$(dirname "$BANNERLORD_EXE")"
cd "$EXE_DIR"
exec "$WINE_CMD" "Bannerlord.exe" "$@"

View File

@@ -1,16 +0,0 @@
# Bannerlord Proof
Screenshots and launch logs proving the game runs locally on the Mac.
## How to capture proof
1. Launch the game: `./portal/bannerlord/launch.sh`
2. Wait for main menu to render
3. Take screenshot: `screencapture -x portal/bannerlord/proof/main_menu_$(date +%Y%m%d).png`
4. Save launch log (auto-generated by launch.sh)
## Expected proof files
- `main_menu_*.png` — screenshot of game main menu
- `launch_*.log` — launch command + environment details
- `ingame_*.png` — optional in-game screenshots

View File

@@ -23,21 +23,18 @@
"rotation": { "y": 0.5 },
"portal_type": "game-world",
"world_category": "strategy-rpg",
"environment": "local",
"environment": "production",
"access_mode": "operator",
"readiness_state": "active",
"telemetry_source": "local-desktop:bannerlord",
"telemetry_source": "hermes-harness:bannerlord",
"owner": "Timmy",
"app_id": 261550,
"window_title": "Mount & Blade II: Bannerlord",
"install_source": "gog",
"gog_version": "1.3.15.109797",
"launcher_script": "portal/bannerlord/launch.sh",
"install_guide": "portal/bannerlord/INSTALL.md",
"destination": {
"type": "local-launch",
"url": "https://bannerlord.timmy.foundation",
"type": "harness",
"action_label": "Enter Calradia",
"params": { "world": "calradia", "runtime": "wine/gptk" }
"params": { "world": "calradia" }
}
},
{

View File

@@ -1580,3 +1580,84 @@ canvas#nexus-canvas {
text-transform: uppercase;
}
/* === VISITOR / OPERATOR MODE TOGGLE === */
.mode-toggle-btn {
position: absolute;
bottom: var(--space-3);
right: var(--space-3);
pointer-events: auto;
background: rgba(10, 15, 40, 0.7);
border: 1px solid var(--color-primary);
color: var(--color-primary);
padding: 6px 14px;
font-family: var(--font-display);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.12em;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
backdrop-filter: blur(5px);
transition: all var(--transition-ui);
z-index: 20;
}
.mode-toggle-btn:hover {
background: var(--color-primary);
color: var(--color-bg);
box-shadow: 0 0 15px var(--color-primary);
}
.mode-toggle-btn .mode-icon {
font-size: 14px;
}
.mode-toggle-btn[data-mode="operator"] {
border-color: var(--color-gold);
color: var(--color-gold);
box-shadow: 0 0 8px rgba(255, 215, 0, 0.15);
}
.mode-toggle-btn[data-mode="operator"]:hover {
background: var(--color-gold);
color: var(--color-bg);
box-shadow: 0 0 15px var(--color-gold);
}
/* Visitor mode: hide operator-only surfaces */
body.visitor-mode .gofai-hud,
body.visitor-mode .hud-agent-log,
body.visitor-mode .hud-debug,
body.visitor-mode .bannerlord-hud,
body.visitor-mode #mem-palace-status,
body.visitor-mode #mem-palace-controls,
body.visitor-mode #mempalace-results,
body.visitor-mode .mem-palace-ui,
body.visitor-mode .mem-palace-stats,
body.visitor-mode .ws-hud-status,
body.visitor-mode .chat-quick-actions .operator-only,
body.visitor-mode .nexus-footer {
display: none !important;
}
/* Visitor mode: simplify controls hint */
body.visitor-mode .hud-controls {
font-size: var(--text-xs);
opacity: 0.6;
}
/* Visitor mode: cleaner chat */
body.visitor-mode .chat-panel {
width: 320px;
max-height: 300px;
}
/* Operator badge in operator mode */
body:not(.visitor-mode) .hud-controls::after {
content: ' OPERATOR';
color: var(--color-gold);
font-weight: 700;
letter-spacing: 0.1em;
}