diff --git a/ui/hud.py b/ui/hud.py index cee64aa..ed8daea 100644 --- a/ui/hud.py +++ b/ui/hud.py @@ -5,6 +5,7 @@ import pygame from config.constants import * from world.base.brain import CellBrain, FlexibleNeuralNetwork from world.objects import DefaultCell +import math class HUD: @@ -173,13 +174,24 @@ class HUD: LAYER_LABEL_BOTTOM_MARGIN = 15 # Distance below visualization for layer labels # Info text positioning constants - INFO_TEXT_TOP_MARGIN = 35 # Distance below visualization for info text + INFO_TEXT_TOP_MARGIN = 40 # Distance below visualization for info text INFO_TEXT_LINE_SPACING = 15 # Vertical spacing between info text lines # Activation value clamping ACTIVATION_CLAMP_MIN = -1 # Minimum activation value for visualization ACTIVATION_CLAMP_MAX = 1 # Maximum activation value for visualization + # --- Tooltip constants --- + TOOLTIP_X_OFFSET = 12 + TOOLTIP_Y_OFFSET = 8 + TOOLTIP_PADDING_X = 5 + TOOLTIP_PADDING_Y = 3 + TOOLTIP_BG_COLOR = (40, 40, 40) + TOOLTIP_BORDER_COLOR = WHITE + TOOLTIP_BORDER_WIDTH = 1 + TOOLTIP_MARGIN = 10 + TOOLTIP_LINE_SPACING = 0 # No extra spacing between lines + if not hasattr(cell, 'behavioral_model'): return @@ -374,3 +386,78 @@ class HUD: info_rect.left = viz_x info_rect.top = viz_y + VIZ_HEIGHT + INFO_TEXT_TOP_MARGIN + i * INFO_TEXT_LINE_SPACING screen.blit(info_text, info_rect) + + # --- Tooltip logic for neuron hover --- + mouse_x, mouse_y = pygame.mouse.get_pos() + tooltip_text = None + + for (layer_idx, neuron_idx), pos in neuron_positions.items(): + dx = mouse_x - pos[0] + dy = mouse_y - pos[1] + dist = math.hypot(dx, dy) + if dist <= NEURON_RADIUS + 3: + neuron = network.layers[layer_idx][neuron_idx] + label = None + value_str = None + + # Show input/output name if applicable + if neuron['type'] == 'input' and layer_idx == 0: + if neuron_idx < len(cell_brain.input_keys): + key = cell_brain.input_keys[neuron_idx] + label = f"Input: {key}" + # Show normalized input value + raw_value = cell_brain.inputs.get(key, 0.0) + normalized_value = cell_brain._normalize_input(key, raw_value) + value_str = f"Value: {normalized_value:.2f}" + elif neuron['type'] == 'output': + if neuron_idx < len(cell_brain.output_keys): + key = cell_brain.output_keys[neuron_idx] + label = f"Output: {key}" + # Show output value (already actual, not normalized) + value = cell_brain.outputs.get(key, 0.0) + value_str = f"Value: {value:.2f}" + else: + # For hidden neurons, show activation value + if layer_idx < len(activations) and neuron_idx < len(activations[layer_idx]): + value = activations[layer_idx][neuron_idx] + value_str = f"Value: {value:.2f}" + + # Show bias if present + bias = neuron.get('bias', None) + bias_str = f"Bias: {bias:.2f}" if bias is not None else None + + # Compose tooltip text + tooltip_lines = [] + if label: + tooltip_lines.append(label) + if value_str: + tooltip_lines.append(value_str) + if bias_str: + tooltip_lines.append(bias_str) + tooltip_text = "\n".join(tooltip_lines) if tooltip_lines else None + break + + if tooltip_text: + lines = tooltip_text.split('\n') + tooltip_surfs = [self.legend_font.render(line, True, WHITE) for line in lines] + width = max(surf.get_width() for surf in tooltip_surfs) + TOOLTIP_MARGIN + height = sum(surf.get_height() for surf in tooltip_surfs) + TOOLTIP_MARGIN + + # Default position: right and below cursor + tooltip_x = mouse_x + TOOLTIP_X_OFFSET + tooltip_y = mouse_y + TOOLTIP_Y_OFFSET + + # Adjust if off right edge + if tooltip_x + width > SCREEN_WIDTH: + tooltip_x = mouse_x - width - TOOLTIP_X_OFFSET + # Adjust if off bottom edge + if tooltip_y + height > SCREEN_HEIGHT: + tooltip_y = mouse_y - height - TOOLTIP_Y_OFFSET + + tooltip_rect = pygame.Rect(tooltip_x, tooltip_y, width, height) + pygame.draw.rect(screen, TOOLTIP_BG_COLOR, tooltip_rect) + pygame.draw.rect(screen, TOOLTIP_BORDER_COLOR, tooltip_rect, TOOLTIP_BORDER_WIDTH) + y = tooltip_rect.top + TOOLTIP_PADDING_Y + for surf in tooltip_surfs: + screen.blit(surf, (tooltip_rect.left + TOOLTIP_PADDING_X, y)) + y += surf.get_height() + TOOLTIP_LINE_SPACING diff --git a/world/objects.py b/world/objects.py index 984f837..7bd02d2 100644 --- a/world/objects.py +++ b/world/objects.py @@ -292,7 +292,7 @@ class DefaultCell(BaseEntity): output_data = self.behavioral_model.tick(input_data) # clamp accelerations - output_data["linear_acceleration"] = max(-0.1, min(0.02, output_data["linear_acceleration"])) + output_data["linear_acceleration"] = max(-0.1, min(0.1, output_data["linear_acceleration"])) output_data["angular_acceleration"] = max(-0.1, min(0.1, output_data["angular_acceleration"])) # 2. Apply drag force