/**
- VISUAL_FEEDBACK_GUIDE.md
- Complete guide to the GalaxyQuest Visual Feedback System for Multi-Unit Selection
- and Ownership Auras with Post-Processing Effects Integration
- Features implemented:
-
- Dynamic multi-unit selection highlighting with bloom effects
-
- Group-based bloom auras with customizable colors and intensity
-
- Ownership/faction-based aura bloom effects
-
- Colorblind-accessible mode toggle with UI integration
-
- Full GameEngine and ViewportManager integration
- Status: Phase 3-5 Implementation Complete
- Date: 2026-07-31 */
The GalaxyQuest Visual Feedback System provides comprehensive visual feedback for:
- Multi-Unit Selection - When 2+ units are selected, dynamic bloom effects highlight the group
- Group Highlighting - Named groups receive persistent visual auras with customizable colors
- Ownership Auras - Faction-owned objects display bloom effects in their faction colors
- Colorblind Accessibility - Full UI toggle for colorblind-friendly visual modes
File: js/engine/selection/GroupSelectionController.js
Manages multi-unit selection with integrated bloom effect state tracking.
Key Features:
- Multi-selection with Ctrl/Shift modifier support
- Group creation, manipulation, and dissolution
- Selection history with undo/redo
- Dynamic bloom state tracking per group
- Multi-selection bloom based on selection count
Example Usage:
const groupCtrl = new GroupSelectionController(markerSystem);
// Select multiple units
groupCtrl.toggleUnitSelection(unit1, { multiSelect: true });
groupCtrl.toggleUnitSelection(unit2, { multiSelect: true });
// Create a named group from selection
const groupId = groupCtrl.createGroupFromSelection('Fighter Squadron', 'squadron');
// Enable bloom feedback for the group
groupCtrl.setGroupBloom(groupId, true, 1.5, [1.0, 0.5, 0.0]); // Orange bloom
// Multi-selection automatically enables bloom when 2+ units selected
// Intensity scales with unit count (0.8 + count * 0.1, max 2.0)Bloom State Events:
'multi-selection-bloom'- Emitted when multi-selection bloom state changes'bloom-updated'- Emitted when a group's bloom state changes'ownership-aura-bloom'- Emitted when ownership aura bloom is toggled
File: js/engine/post-effects/passes/GroupHighlightBloomPass.js
Post-processing pass that applies selective bloom highlighting to selected groups.
Key Features:
- Per-group bloom rendering with customizable intensity
- Multi-selection highlighting with dynamic threshold adjustment
- Group boundary visualization
- Performance-aware throttling (16ms update cycle)
Parameters:
{
groupSelectionController: GroupSelectionController, // Required for state
renderer: IGraphicsRenderer, // WebGL/WebGPU renderer
bloomThreshold: 0.6, // Extraction threshold [0,1]
bloomStrength: 1.5, // Base intensity multiplier
groupBoundaryWidth: 2, // Outline width in pixels
}Integration:
const groupBloom = new GroupHighlightBloomPass({
groupSelectionController: engine.groupSelection,
renderer: engine.renderer,
});
engine.postFx.addPass(groupBloom);File: js/engine/post-effects/passes/OwnershipAuraBloomPass.js
Post-processing pass that applies faction-specific bloom auras to owned objects.
Key Features:
- Per-faction bloom color profiles
- Configurable intensity per ownership type
- Colorblind mode support with intensity compensation
- Batch rendering optimization by faction
- Performance-aware throttling (32ms update cycle)
Faction Intensity Multipliers:
{
helion_confederation: 0.9,
myr_keth: 1.0,
brut_der_ewigkeit: 0.7,
omniscienta: 1.1,
schattenkompakt: 0.6,
echos_der_leere: 1.0,
khar_morr_syndicate: 0.95,
genesis_kollektiv: 0.85,
architekten_des_lichts: 1.05,
ketzer_von_verath: 0.9,
aethernox: 0.95,
nomaden_des_rifts: 1.0,
iron_fleet: 0.75,
}Colorblind Intensity Adjustments:
{
deuteranopia: 0.95, // Red-Green colorblind
protanopia: 0.95, // Red-Green colorblind (alternative)
tritanopia: 0.9, // Blue-Yellow colorblind
achromatic: 0.7, // Grayscale/monochrome
}Integration:
const auraBloom = new OwnershipAuraBloomPass({
ownershipSystem: engine.ownershipSystem,
renderer: engine.renderer,
baseIntensity: 0.8,
});
engine.postFx.addPass(auraBloom);
// Register objects for aura bloom
auraBloom.registerObjectAura(starObject, 'helion_confederation');
auraBloom.registerObjectAura(planetObject, 'genesis_kollektiv', 1.2); // Custom intensityFile: js/engine/post-effects/passes/BloomPass.js
Extended with dynamic parameter adjustment based on selection state.
New Methods:
pass.updateDynamicParameters(); // Update based on selection state
pass.setDynamicParameters(threshold, str); // Manual parameter override
pass.getEffectiveThreshold(); // Get current threshold
pass.getEffectiveStrength(); // Get current strengthDynamic Adjustment Logic:
- When multi-selection bloom is enabled:
- Threshold reduced by (intensity * 0.15) for more bloom on selected units
- Strength increased by (intensity * 0.3) for enhanced visual feedback
- Called automatically in render() each frame
File: js/engine/AdvancedRenderingUI.js
Enhanced UI manager with colorblind mode selector.
New Features:
- Colorblind mode dropdown selector (normal, deuteranopia, protanopia, tritanopia, achromatic)
- localStorage persistence (
adv-rendering-colorblind) - Automatic propagation to OwnershipVisualsSystem and OwnershipAuraBloomPass
- Custom event dispatch for other systems
Usage:
<!-- HTML structure for colorblind selector -->
<select id="adv-rendering-colorblind">
<option value="normal">Normal Vision</option>
<option value="deuteranopia">Red-Green Colorblind (Deuteranopia)</option>
<option value="protanopia">Red-Green Colorblind (Protanopia)</option>
<option value="tritanopia">Blue-Yellow Colorblind (Tritanopia)</option>
<option value="achromatic">Monochrome (Achromatic)</option>
</select>Event Listening:
window.addEventListener('colorblind-mode-changed', (evt) => {
const { mode } = evt.detail;
console.log('Colorblind mode changed to:', mode);
// Update custom systems as needed
});File: js/engine/GameEngine.js
Full integration of all visual feedback systems into the core engine.
New Properties:
engine.groupSelection // GroupSelectionController instance
engine.ownershipSystem // OwnershipVisualsSystem instance
engine.groupBloom // GroupHighlightBloomPass instance
engine.ownershipAuraBloom // OwnershipAuraBloomPass instanceAutomatic Wiring:
- GroupSelectionController created with optional SelectionMarkerSystem
- OwnershipVisualsSystem created with AdvancedRenderingManager
- Bloom passes automatically connected to selection controller
- ViewportManager receives references for multi-view consistency
Initialization Options:
const engine = await GameEngine.create(canvas, {
groupBloom: { // Group selection bloom config
bloomThreshold: 0.6,
bloomStrength: 1.5,
},
ownershipAura: { // Ownership aura bloom config
baseIntensity: 0.8,
bloomThreshold: 0.7,
},
groupSelection: { // Group selection config
enableOwnershipAura: true,
},
});File: js/engine/ViewportManager.js
Multi-view (Picture-in-Picture) viewport consistency with visual feedback.
New Methods:
viewport.setGroupSelection(controller); // Wire selection controller
viewport.setOwnershipSystem(system); // Wire ownership system
viewport.applySelectionMarkersToViewports(); // Apply to all PiP windows
viewport.applyOwnershipAurasToViewports(); // Apply ownership to all PiP windowsEvent Broadcasting:
'viewport:selection-changed'- Selection state changes'viewport:apply-selection-markers'- Apply selection to viewport'viewport:apply-ownership-auras'- Apply ownership to viewport
Usage:
// ViewportManager automatically receives systems from GameEngine
// No manual wiring needed in normal cases
// Manual propagation when needed
engine.viewports.applySelectionMarkersToViewports();
engine.viewports.applyOwnershipAurasToViewports();// When user selects multiple units (e.g., Ctrl+Click)
scene.addEventListener('unit-selected', ({ unit, modifiers }) => {
engine.groupSelection.toggleUnitSelection(unit, {
multiSelect: modifiers.ctrl,
range: modifiers.shift,
});
// Multi-selection bloom is automatically enabled when 2+ units are selected
// Intensity scales: 0.8 + (count * 0.1), max 2.0
});// User creates a "fighter squadron" from selected units
const groupId = engine.groupSelection.createGroupFromSelection(
'Alpha Squadron',
'squadron'
);
// Customize the bloom color and intensity
const squadronColor = [1.0, 0.5, 0.0]; // Orange
engine.groupSelection.setGroupBloom(groupId, true, 1.8, squadronColor);// When rendering a star system, register objects for ownership aura bloom
function renderStarSystem(system) {
for (const star of system.stars) {
const faction = getFactionOwner(star);
engine.ownershipAuraBloom.registerObjectAura(
star.mesh,
faction,
1.0 // default intensity
);
}
for (const planet of system.planets) {
const faction = getFactionOwner(planet);
engine.ownershipAuraBloom.registerObjectAura(
planet.mesh,
faction
);
}
}// User selects colorblind mode from settings
document.getElementById('adv-rendering-colorblind').value = 'deuteranopia';
// This automatically:
// 1. Updates OwnershipVisualsSystem color palettes
// 2. Adjusts OwnershipAuraBloomPass intensity
// 3. Saves preference to localStorage
// 4. Emits 'colorblind-mode-changed' event
// Listen for changes
window.addEventListener('colorblind-mode-changed', ({ detail }) => {
console.log('Colorblind mode:', detail.mode);
});// The BloomPass automatically adjusts based on selection state
// But you can also manually override
const bloomPass = engine._bloomPass;
// Get current effective parameters (may be dynamic)
const threshold = bloomPass.getEffectiveThreshold();
const strength = bloomPass.getEffectiveStrength();
// Manually override (skips dynamic adjustment)
bloomPass.setDynamicParameters(0.5, 2.0);
// Or update the base parameters (used when no selection)
bloomPass.threshold = 0.6;
bloomPass.strength = 1.2;- GroupHighlightBloomPass: 16ms update throttle (~60 FPS)
- OwnershipAuraBloomPass: 32ms update throttle (~30 FPS)
- BloomPass: Dynamic parameters updated each frame but with efficiency checks
-
Limit multi-selection groups
- Large selections (100+ units) may impact performance
- Consider capping visual bloom for very large selections
-
Register only visible objects for ownership auras
// DON'T do this: allStarsInGalaxy.forEach(star => { auraBloom.registerObjectAura(star, star.faction); }); // DO this: visibleStars.forEach(star => { auraBloom.registerObjectAura(star, star.faction); });
-
Use colorblind mode sparingly
- Colorblind modes apply palette remapping to all factions
- Only switch modes when user settings change, not every frame
-
Monitor FPS for post-processing
- GameEngine disables post-FX if FPS drops below POST_FX_MIN_FPS (45 FPS)
- Bloom effects are first to be suspended to maintain frame rate
- GroupSelectionController: ~1-2 KB base + ~0.1 KB per selected unit
- GroupHighlightBloomPass: ~5 KB base + minimal per-group overhead
- OwnershipAuraBloomPass: ~8 KB base + ~0.5 KB per registered object
npm test -- tests/js/selection-visual-feedback.test.jsTests cover:
- Multi-selection bloom enabling/disabling
- Group bloom state tracking
- Colorblind mode application
- Ownership aura registration
- ViewportManager propagation
npm test -- tests/e2e/visual-feedback.e2e.jsTests cover:
- Full GameEngine initialization with visual systems
- Multi-view consistency (main + PiP viewports)
- Real-time bloom effect updates
- Performance under load
-
Multi-Selection Bloom:
- Open game, select 1 unit → no bloom
- Ctrl+Click to select 2+ units → bloom appears with increasing intensity
- Clear selection → bloom disappears
-
Group Bloom:
- Create named group → receives bloom in group template color
- Modify group → bloom updates accordingly
- Dissolve group → bloom removed
-
Ownership Auras:
- Switch between factions in system view
- Verify faction colors appear as bloom auras
- Switch colorblind mode → verify palette changes
-
Colorblind Mode:
- Select each colorblind mode from UI
- Verify colors remain distinct and readable
- Check that selection appears in localStorage
- Refresh page and verify mode persists
Cause: Post-processing disabled or FPS too low
// Check if post-FX is enabled
console.log(engine.postFx); // Should not be null
console.log(engine.perf.fps); // Should be >= POST_FX_MIN_FPS (45)Solution:
- Ensure
opts.postFx !== falsewhen creating engine - Reduce visual quality settings if FPS is low
Cause: UI element missing or wrong ID
<!-- Verify this element exists -->
<select id="adv-rendering-colorblind">...</select>Solution:
- Check browser console for errors
- Verify element ID is exactly
adv-rendering-colorblind - Initialize AdvancedRenderingUI after DOM is ready
Cause: Selection controller not wired to bloom pass
// Verify wiring
console.log(engine._bloomPass.selectionController === engine.groupSelection);Solution:
- Use
GameEngine.create()for automatic wiring - Or manually wire:
bloomPass.selectionController = engine.groupSelection;
Cause: ViewportManager created before selection systems
// This is handled automatically by GameEngine.create()
// If using manual initialization, ensure correct order:
// 1. Create ViewportManager
// 2. Initialize selection systems
// 3. Call viewports.setGroupSelection() and setOwnershipSystem()Solution:
- Use
GameEngine.create()for correct initialization order - Or manually wire after all systems are created
- GroupSelectionController with bloom state tracking
- GroupHighlightBloomPass post-processing integration
- OwnershipAuraBloomPass post-processing integration
- BloomPass dynamic parameter adjustment
- AdvancedRenderingUI colorblind mode toggle
- GameEngine integration and wiring
- ViewportManager multi-view consistency
- Unit tests for all components
- Event system for inter-component communication
- Documentation and examples
- Selection group persistence (save/load named groups)
- Advanced group manipulation (merge groups, move units between groups)
- Selection audio feedback (beep/sound for multi-select)
- Keyboard shortcut customization
- Group priority/layering in bloom effects
- Per-viewport selection state (independent selections in PiP windows)
- Selection heatmap for large unit counts
MIT © 2026 makr-code/GalaxyQuest