Skip to content

Restore statistics tracking with verified Qt lifecycle - #44

Merged
ViciousSquid merged 13 commits into
ViciousSquid:2.6.5.0_LatestVersionfrom
snowyukitty:fix/statistics-tab-lifecycle
Aug 1, 2026
Merged

ViciousSquid merged 13 commits into
ViciousSquid:2.6.5.0_LatestVersionfrom
snowyukitty:fix/statistics-tab-lifecycle

Conversation

@snowyukitty

@snowyukitty snowyukitty commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

I'm sincerely sorry for #41. It made the Statistics tab worse and cost you time to merge, test, and revert it. You were right to revert it. I should have tested the real desktop Qt startup path before asking you to merge the change, and I did not.

Root cause

Both desktop entrypoints create SquidBrainWindow before TamagotchiLogic, then attach the completed logic later through SquidBrainWindow.set_tamagotchi_logic().

When I changed StatisticsTab into a read-only view of the canonical SquidStatistics model, I failed to update that late-injection path. Its propagation loop omitted statistics_tab and personality_tab and referenced learning_tab instead of the real nn_viz_tab. The Statistics tab never received the model it had been changed to display, so its refresh methods returned early and its labels remained at their initial placeholder values.

The tests submitted with #41 covered model, event-delivery, persistence, and reset behavior with fakes, but never constructed the real seven-tab startup sequence. I mistook unit coverage for GUI integration coverage. That validation failure was mine.

Repair

This restores the complete reverted statistics change rather than applying a lifecycle-only patch: the issue requires the statistics to be recorded and persisted correctly, not merely for the tab to display a model.

  • propagate the late-created logic to all seven real brain tabs, including clear and replacement rebinds
  • immediately synchronize and render StatisticsTab after binding
  • keep lifetime statistics model-owned and the tab read-only
  • make partial, legacy, and missing statistics payloads replace the current model through both desktop load entrypoints
  • record actual post-boundary Euclidean movement for normal, direct, diagonal, clipped, and sleeping paths
  • count a poop only after its scene insertion succeeds and count an accepted click wake exactly once
  • disconnect a stopped game's neurogenesis listener so a replacement game cannot double-handle one birth
  • preserve exactly-once sickness, event, persistence, reset, and neuron-maximum behavior

Final R4 review fixes

CodeRabbit's completed review of the submitted R3 head found two additional real defects, and both are fixed in this update:

  • observe peak_novelty, peak_stress, and peak_reward from the current production neurogenesis triggers immediately after they update, using the same stress scale as the neurogenesis brain state and preserving lifetime maxima while live values decay
  • sanitize persisted numeric values at the model boundary so None, booleans, negatives, NaN/Infinity, malformed strings, containers, and fractional neuron counts cannot poison or crash the model through either desktop loader

The update also preserves the former zero-argument SquidStatistics.update() and one-argument track_neuron_creation() APIs, derives the standard neuron count from the real eight-position BrainWidget, and drives the real synchronous neurogenesis method instead of stubbing the behavior under test.

Final takeover review fixes

A fresh final-head audit found one more production writer outside track_neurogenesis_triggers(): eating could raise the live reward trigger from 4 to its cap of 5, then the same simulation callback could decay it to 4.8 before the lifetime peak was observed. The food-consumption boundary now records the canonical live maximum immediately after writing it. A regression drives the real Squid.eat() and TamagotchiLogic.track_neurogenesis_triggers() methods through that exact sequence and proves the live value may decay to 4.8 while peak_reward remains 5.

A subsequent exact-head audit found that the direct ZIP loader's generic squid-state loop could replace the canonical SquidStatistics instance with a nested legacy statistics dictionary, then fail when it called the model loader. The loader now preserves the model owner, prefers the current top-level statistics.json, and falls back to the supported nested legacy payload when that file is absent. A regression drives the real ZIP loader and proves loading succeeds, the model identity is preserved, and the nested value is restored.

Final R7 review fixes

CodeRabbit's review of the exact R6 head identified an unsafe legacy poop-target
guard. If current_poop_target exists but is None, the simulation callback
tested only whether the attribute existed and then called
sceneBoundingRect() on None. The repository currently has no production
assignment of a poop item to this legacy target attribute, so the review's
proposed successful-throw sequence is not reachable as described. The guard is
still hardened for a missing, cleared, or externally supplied target, and a
regression invokes the real interaction method with both absent and None
states.

The same review found that the legacy update_distance(dx, dy) entrypoint
duplicated older single-rollover logic. A sufficiently large step could cross
multiple rollover boundaries but increment the multiplier only once. It now
delegates to the validated add_distance() implementation and preserves its
existing rollover notification. A regression crosses three boundaries in one
step and verifies the exact remainder, multiplier, and message.

Unsupported event keys still return False without changing state. They now
also emit a debug log so a future typo or incomplete event mapping is visible
during development.

The review's remaining test-only findings were handled without changing
production semantics: default-neuron assertions use
DEFAULT_NEURON_COUNT, and the two public malformed-statistics loader
scenarios now report independently. The proposed __init__ reset refactor was
not applied because no defect was present and preserving the old start_time
across a load would risk counting pre-load session time again.

Regression evidence

A focused reproduction invokes the production late-injection method against a real StatisticsTab. It fails on the exact merged #41 tree and on the audited upstream base because StatisticsTab.tamagotchi_logic remains None; it passes with this repair.

A separate offscreen Qt regression constructs the real SquidBrainWindow, BrainWidget, QTabWidget, all seven real tabs, and their QLabel values. It exercises logic1 -> None -> logic2, verifies every real tab reference at each stage, proves the window has no ghost learning_tab, derives the eight-neuron baseline from the real widget, and asserts all 17 displayed statistics with distinct values on both initial binding and replacement rebinding.

Additional integration tests exercise both real save-loading entrypoints with malformed, partial, missing, and nested legacy statistics payloads. Movement tests cover horizontal, vertical, boundary-clipped, sleeping, direct, and diagonal Euclidean displacement. Event tests cover accepted and rejected poop creation, repeated click wake, signal cleanup, real synchronous neurogenesis creation, the food-writer reward peak, and compatibility call forms.

Fifteen earlier targeted mutation checks cover the R6 repair's model, load,
peak, lifecycle, and exactly-once invariants. Three additional R7 mutation
checks confirm that the new regression tests fail if the cleared-target guard,
multi-rollover delegation, or unknown-event debug logging is removed.

Testing

  • python -m pytest tests -q (57 passed, 87 subtests passed)
  • python -m pytest android/tests -q (7 passed)
  • python -m compileall -q src tests android
  • Ruff import/error checks on all R7-changed model and test files
  • no additional Ruff diagnostics in the changed production files
  • CRLF-aware R7 added-line whitespace check
  • 3/3 R7-targeted mutants killed

Two fresh packet-only reviews found the food-boundary peak loss in R4 and the legacy-loader model replacement in R5. Both findings were independently reproduced and fixed in transparent successor commits. The exact R7 packet is reviewed separately before submission; reviewer execution limitations and the verdict are preserved in the submission evidence rather than represented as local test execution here.

The GUI lifecycle regression is offscreen and stubs background workers, bridge I/O, and task-manager UI. It uses the real window, widget, tabs, labels, injection order, model, and relevant production methods. No interactive visual smoke was performed for this model-boundary R7 update.

Closes #26

Summary by CodeRabbit

  • New Features

    • Centralized gameplay statistics for actions, movement, sickness, memories, sleep, and neuron growth.
    • Improved statistics persistence, save/load compatibility, resetting, and display synchronization.
    • Added accurate movement-distance and successful-poop tracking.
    • Improved neuron-creation notifications and live neuron-count updates.
  • Bug Fixes

    • Prevented duplicate event recording and corrected startle, sickness, and interaction counts.
    • Ensured statistics remain accurate when the statistics panel is unavailable.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Statistics tracking is centralized in SquidStatistics. Gameplay, neurogenesis, persistence, and brain UI paths now use the canonical model. New tests cover statistic behavior, event wiring, save/load compatibility, and late UI logic binding.

Changes

Canonical statistics migration

Layer / File(s) Summary
Statistics model and persistence
src/squid_statistics.py, tests/test_squid_statistics.py, tests/test_statistics_persistence.py
Defines canonical events, neuron defaults, elapsed-time updates, maxima observation, reset behavior, persistence conversion, and loading validation.
Gameplay event tracking
src/squid.py, src/tamagotchi_logic.py, src/interactions.py, src/interactions2.py, src/brain_about_tab.py, tests/test_statistics_event_wiring.py
Routes movement, interactions, startles, sickness, color changes, food, and poop creation through canonical statistics tracking.
Neurogenesis event flow
src/neurogenesis.py, src/brain_widget.py, src/tamagotchi_logic.py, tests/test_statistics_event_wiring.py
Adds neuron-created notifications, avoids duplicate synchronous handling, records neuron births, and refreshes live neuron counts.
Statistics UI wiring
src/brain_statistics_tab.py, src/brain_tool.py, tests/test_statistics_tab_lifecycle.py
Makes the statistics tab synchronize from squid statistics and supports late logic binding across brain tabs.
Statistics save and load integration
src/tamagotchi_logic.py, tests/test_statistics_persistence.py
Saves and restores canonical statistics, sickness state, and post-load neuron counts across archive and partial-payload scenarios.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Gameplay
  participant TamagotchiLogic
  participant SquidStatistics
  participant StatisticsTab
  Gameplay->>TamagotchiLogic: report gameplay event or movement
  TamagotchiLogic->>SquidStatistics: update canonical statistics
  TamagotchiLogic->>StatisticsTab: refresh synchronized display
Loading

Possibly related PRs

  • ViciousSquid/Dosidicus#41: Modifies the same statistics canonicalization, event-wiring, neurogenesis, and UI projection paths.

Suggested reviewers: vicioussquid

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes restore model-owned tracking, persistence, display, and lifecycle behavior for sleep time and sickness episodes required by [#26].
Out of Scope Changes check ✅ Passed The code and tests stay within the stated statistics tracking, persistence, event wiring, and Qt lifecycle objectives.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the PR's main changes: restoring statistics tracking and validating Qt lifecycle behavior.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (8)
src/squid_statistics.py (2)

123-134: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider having update_distance delegate here.

add_distance handles multi-rollover correctly via divmod, while the older update_distance only subtracts one limit. Routing update_distance through add_distance(math.sqrt(dx*dx + dy*dy)) removes the duplicate rollover logic and the behavioral divergence between the two entry points.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/squid_statistics.py` around lines 123 - 134, Update update_distance to
calculate the Euclidean distance and delegate to add_distance, removing its
duplicate rollover handling. Preserve the existing input behavior while ensuring
multi-rollover accumulation uses add_distance’s divmod logic.

17-64: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Annotate the lookup tables as ClassVar to silence RUF012.

These are immutable-intent lookup tables; ClassVar documents that and clears the three Ruff warnings.

♻️ Proposed annotation
+from typing import ClassVar, Dict
+
 class SquidStatistics:
@@
-    _PERSISTENCE_KEYS = {
+    _PERSISTENCE_KEYS: ClassVar[Dict[str, str]] = {
@@
-    _EVENT_KEYS = {
+    _EVENT_KEYS: ClassVar[Dict[str, str]] = {
@@
-    _NEURON_BIRTH_KEYS = {
+    _NEURON_BIRTH_KEYS: ClassVar[Dict[str, str]] = {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/squid_statistics.py` around lines 17 - 64, Annotate the class-level
lookup tables _PERSISTENCE_KEYS, _EVENT_KEYS, and _NEURON_BIRTH_KEYS as ClassVar
with their existing dictionary value types. Add the required ClassVar import if
needed, while preserving all table contents and behavior.

Source: Linters/SAST tools

tests/test_squid_statistics.py (1)

81-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for add_distance rollover and increment legacy-name mapping.

Both are new/reworked public APIs in this layer with branch logic (multi-rollover via divmod, unknown-name False return) and no test here. load_statistics / to_dict round-tripping a partial payload is also worth a case unless it is fully covered by the persistence tests in the later layer.

💚 Suggested additional cases
    def test_add_distance_rolls_over_multiple_times(self):
        from src.squid_statistics import DISTANCE_ROLLOVER_LIMIT

        self.statistics.add_distance(DISTANCE_ROLLOVER_LIMIT * 2 + 5)

        self.assertEqual(self.statistics.distance_swam, 5)
        self.assertEqual(self.statistics.distance_swam_multiplier, 3)

    def test_add_distance_rejects_invalid_values(self):
        with self.assertRaises(ValueError):
            self.statistics.add_distance(-1)
        with self.assertRaises(ValueError):
            self.statistics.add_distance(float("inf"))

    def test_increment_maps_legacy_names_and_reports_unknown(self):
        self.assertTrue(self.statistics.increment("cheese_eaten"))
        self.assertEqual(self.statistics.cheese_consumed, 1)
        self.assertFalse(self.statistics.increment("not_a_real_event"))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_squid_statistics.py` around lines 81 - 89, Add tests covering the
public APIs in the statistics test suite: verify add_distance handles multiple
DISTANCE_ROLLOVER_LIMIT rollovers, rejects negative and non-finite values, and
verify increment maps the legacy cheese_eaten name while returning False for
unknown event names. Add a load_statistics/to_dict partial-payload round-trip
case only if persistence tests do not already cover it.
tests/test_statistics_tab_lifecycle.py (2)

74-81: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

FakeSquid omits the core stat attributes.

It only defines anxiety, happiness, satisfaction, is_sleeping, is_sick. That is enough for the statistics path, but any future assertion that reaches SquidBrainWindow.sync_state_from_squid (which reads hunger, cleanliness, sleepiness, curiosity, squid_x/y) will fail with AttributeError rather than a meaningful assertion. Worth adding the missing defaults now.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_statistics_tab_lifecycle.py` around lines 74 - 81, Update the
FakeSquid fixture to define default values for hunger, cleanliness, sleepiness,
curiosity, squid_x, and squid_y alongside its existing attributes. Ensure these
defaults satisfy SquidBrainWindow.sync_state_from_squid so future assertions
fail on behavior rather than missing attributes.

89-298: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Optional: split this into three tests (initial bind / live event refresh / unbind+rebind).

At ~210 lines it exercises four distinct phases behind one name, so a failure in the rebind assertions reports as a failure of "late logic wiring". A shared helper that builds the patched window plus one logic factory would keep the setup cost identical.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_statistics_tab_lifecycle.py` around lines 89 - 298, Split
test_late_logic_wiring_projects_model_statistics_into_real_tab into focused
tests for initial binding, live event refresh, and unbind/rebind behavior.
Extract the repeated patched-window setup and TamagotchiLogic construction into
shared helpers, preserving the existing assertions and setup behavior while
giving each phase its own descriptive test name.
src/brain_statistics_tab.py (2)

267-270: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Nit: "Max Neurons" is the only unlocalised row in the export.

Every other line uses loc.get(...). Consider adding a key (e.g. stat_max_neurons) and reusing it for the label at Line 153 too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/brain_statistics_tab.py` around lines 267 - 270, Localize the “Max
Neurons” export label in the statistics export code by adding and using a
localization key such as stat_max_neurons through loc.get(...). Reuse the same
key for the corresponding label near the existing line-153 display so both
occurrences remain consistent with the other localized rows.

187-194: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optional: gate the 1 Hz refresh on is_visible.

is_visible is now tracked in showEvent/hideEvent but never read, so the timer keeps re-syncing and repainting 17 labels while the tab is hidden. Cheap, but the flag suggests the intent was to skip hidden updates.

♻️ Proposed change
     def update_statistics(self):
         """Refresh the UI from the canonical squid statistics model."""
         if not self.tamagotchi_logic or not self.tamagotchi_logic.squid:
             return
 
         self._sync_from_squid_statistics()
         self.statistics['last_update_time'] = time.time()
-        self.update_display()
+        if self.is_visible:
+            self.update_display()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/brain_statistics_tab.py` around lines 187 - 194, Optionally update
update_statistics to return early when is_visible is false, while preserving the
existing tamagotchi_logic and squid guard. This should prevent
_sync_from_squid_statistics, timestamp updates, and update_display from running
while the tab is hidden; continue the current refresh flow when the tab is
visible.
tests/test_statistics_persistence.py (1)

15-44: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Good schema-lock test. Mirroring the mapping as a literal and asserting equality means any accidental rename or dropped persisted field fails loudly. Note it does couple the suite to the private _PERSISTENCE_KEYS; if the model ever exposes a public accessor, prefer that here and at Line 400.

Also applies to: 91-107

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_statistics_persistence.py` around lines 15 - 44, The schema-lock
test currently depends directly on the private _PERSISTENCE_KEYS mapping. If a
public accessor for the persistence schema exists or is introduced, update
EXPECTED_PERSISTENCE_KEYS and the corresponding assertions around the additional
referenced range to use that public accessor instead, while preserving the exact
mapping equality check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/squid_statistics.py`:
- Around line 209-216: Update the invalid-event branch in the increment method
to emit a debug-level log identifying the unknown event_name before returning
False. Reuse the class’s existing logger or logging path, and leave valid event
increments and return values unchanged.
- Around line 285-300: The neurogenesis peak metrics are never updated before
persistence. Add observation logic for peak_novelty, peak_stress, and
peak_reward at the neuron creation/output point, updating each persisted peak
with the corresponding neuron value while preserving existing initialization and
persistence behavior.
- Around line 171-188: Update load_statistics’s _PERSISTENCE_KEYS assignment to
defensively coerce each loaded persistence value to the appropriate numeric
type, falling back to its existing model default when the value is None or
non-numeric. Preserve the current_neurons and max_neurons_reached handling while
ensuring all canonical counters are numeric before setattr persists them.

---

Nitpick comments:
In `@src/brain_statistics_tab.py`:
- Around line 267-270: Localize the “Max Neurons” export label in the statistics
export code by adding and using a localization key such as stat_max_neurons
through loc.get(...). Reuse the same key for the corresponding label near the
existing line-153 display so both occurrences remain consistent with the other
localized rows.
- Around line 187-194: Optionally update update_statistics to return early when
is_visible is false, while preserving the existing tamagotchi_logic and squid
guard. This should prevent _sync_from_squid_statistics, timestamp updates, and
update_display from running while the tab is hidden; continue the current
refresh flow when the tab is visible.

In `@src/squid_statistics.py`:
- Around line 123-134: Update update_distance to calculate the Euclidean
distance and delegate to add_distance, removing its duplicate rollover handling.
Preserve the existing input behavior while ensuring multi-rollover accumulation
uses add_distance’s divmod logic.
- Around line 17-64: Annotate the class-level lookup tables _PERSISTENCE_KEYS,
_EVENT_KEYS, and _NEURON_BIRTH_KEYS as ClassVar with their existing dictionary
value types. Add the required ClassVar import if needed, while preserving all
table contents and behavior.

In `@tests/test_squid_statistics.py`:
- Around line 81-89: Add tests covering the public APIs in the statistics test
suite: verify add_distance handles multiple DISTANCE_ROLLOVER_LIMIT rollovers,
rejects negative and non-finite values, and verify increment maps the legacy
cheese_eaten name while returning False for unknown event names. Add a
load_statistics/to_dict partial-payload round-trip case only if persistence
tests do not already cover it.

In `@tests/test_statistics_persistence.py`:
- Around line 15-44: The schema-lock test currently depends directly on the
private _PERSISTENCE_KEYS mapping. If a public accessor for the persistence
schema exists or is introduced, update EXPECTED_PERSISTENCE_KEYS and the
corresponding assertions around the additional referenced range to use that
public accessor instead, while preserving the exact mapping equality check.

In `@tests/test_statistics_tab_lifecycle.py`:
- Around line 74-81: Update the FakeSquid fixture to define default values for
hunger, cleanliness, sleepiness, curiosity, squid_x, and squid_y alongside its
existing attributes. Ensure these defaults satisfy
SquidBrainWindow.sync_state_from_squid so future assertions fail on behavior
rather than missing attributes.
- Around line 89-298: Split
test_late_logic_wiring_projects_model_statistics_into_real_tab into focused
tests for initial binding, live event refresh, and unbind/rebind behavior.
Extract the repeated patched-window setup and TamagotchiLogic construction into
shared helpers, preserving the existing assertions and setup behavior while
giving each phase its own descriptive test name.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 77eb7817-8ba8-46a1-9f1f-92887e720c08

📥 Commits

Reviewing files that changed from the base of the PR and between 4ac97f0 and 2fa3622.

📒 Files selected for processing (14)
  • src/brain_about_tab.py
  • src/brain_statistics_tab.py
  • src/brain_tool.py
  • src/brain_widget.py
  • src/interactions.py
  • src/interactions2.py
  • src/neurogenesis.py
  • src/squid.py
  • src/squid_statistics.py
  • src/tamagotchi_logic.py
  • tests/test_squid_statistics.py
  • tests/test_statistics_event_wiring.py
  • tests/test_statistics_persistence.py
  • tests/test_statistics_tab_lifecycle.py

Comment thread src/squid_statistics.py Resolved
Comment thread src/squid_statistics.py Resolved
Comment thread src/squid_statistics.py Outdated
Comment on lines +285 to +300
def update(self, elapsed_seconds):
"""Advance continuous statistics by an explicit elapsed duration."""
elapsed_seconds = float(elapsed_seconds)
if elapsed_seconds < 0 or not math.isfinite(elapsed_seconds):
raise ValueError("elapsed_seconds must be a finite non-negative number")

def update(self):
# Update peak mental states
if self.squid.anxiety > self.highest_anxiety:
self.highest_anxiety = self.squid.anxiety
if self.squid.happiness < self.lowest_happiness:
self.lowest_happiness = self.squid.happiness
if self.squid.satisfaction > self.highest_satisfaction:
self.highest_satisfaction = self.squid.satisfaction

if self.squid.is_sleeping:
self.time_spent_asleep += 1 # update is called every second

# --- Check and update peak neurogenesis values ---
if self.squid.tamagotchi_logic and self.squid.tamagotchi_logic.brain_window:
brain_widget = self.squid.tamagotchi_logic.brain_window.brain_widget
if brain_widget:
# 1. Update Neurogenesis Stats
if hasattr(brain_widget, 'neurogenesis_data'):
neuro_data = brain_widget.neurogenesis_data
current_novelty = neuro_data.get('novelty_counter', 0)
current_stress = neuro_data.get('stress_counter', 0)
current_reward = neuro_data.get('reward_counter', 0)

if current_novelty > self.peak_novelty:
self.peak_novelty = current_novelty
if current_stress > self.peak_stress:
self.peak_stress = current_stress
if current_reward > self.peak_reward:
self.peak_reward = current_reward

# 2. Update Max Neurons Reached (FIX)
if hasattr(brain_widget, 'neuron_positions'):
actual_count = len(brain_widget.neuron_positions)
if actual_count > self.max_neurons_reached:
self.max_neurons_reached = actual_count
self.current_neurons = actual_count
self.time_spent_asleep += elapsed_seconds

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find any writer for the peak neurogenesis stats outside the model.
rg -nP --type=py -C3 '\b(peak_novelty|peak_stress|peak_reward)\b' src tests

Repository: ViciousSquid/Dosidicus

Length of output: 2207


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== squid_statistics outline =="
ast-grep outline src/squid_statistics.py || true

echo "== squid_statistics persistence/stat fields =="
sed -n '1,130p' src/squid_statistics.py
echo "== squid_statistics update =="
sed -n '240,330p' src/squid_statistics.py

echo "== references to statistics object / peak fields outside exact previous matches =="
rg -n --type=py -C2 '\b(SquidStatistics|statistics|stats)\b|peak_novelty|peak_stress|peak_reward' src tests || true

echo "== recent commit diff stat/name-only =="
git diff --stat HEAD~1..HEAD 2>/dev/null || true
git diff --name-only HEAD~1..HEAD 2>/dev/null || true

Repository: ViciousSquid/Dosidicus

Length of output: 50378


Restore the neurogenesis peak observers.

peak_novelty, peak_stress, and peak_reward have no writer besides initialization outside tests, yet they persist via _PERSISTENCE_KEYS, so persisted saves will always read back at 0. Add observation at neuron creation/output time for these three neurons.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/squid_statistics.py` around lines 285 - 300, The neurogenesis peak
metrics are never updated before persistence. Add observation logic for
peak_novelty, peak_stress, and peak_reward at the neuron creation/output point,
updating each persisted peak with the corresponding neuron value while
preserving existing initialization and persistence behavior.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/tamagotchi_logic.py (1)

3349-3383: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Fix the crash after current_poop_target is cleared.

The new line sets self.squid.current_poop_target = None after a successful throw. Line 3354 guards check_poop_interaction() with if not hasattr(self.squid, 'current_poop_target'): return. hasattr() returns True even when the attribute value is None, since the attribute still exists on the object.

On the very next simulation tick, check_poop_interaction() runs again. The guard passes (the attribute exists), so execution reaches poop = self.squid.current_poop_target (now None) and then poop_rect = poop.sceneBoundingRect() unconditionally, before the status branch check. This raises AttributeError: 'NoneType' object has no attribute 'sceneBoundingRect'.

update_rock_interaction() guards the equivalent rock state correctly at Line 3261: if not hasattr(self.squid, 'current_rock_target') or not self.squid.current_rock_target: return. It checks both presence and truthiness. The poop guard at Line 3354 only checks presence.

update_simulation() calls self.check_poop_interaction() every tick (Line 1431) with no surrounding try/except. Per PyQt5's default behavior (5.5+), an unhandled exception raised inside a timer-driven slot aborts the whole process, not just the tick.

Fix the guard at Line 3354 to match the rock guard's pattern:

🐛 Proposed fix for the crash on the next tick after a poop throw
-        if not hasattr(self.squid, 'current_poop_target'):
+        if not getattr(self.squid, 'current_poop_target', None):
             return

No test in tests/test_statistics_event_wiring.py calls check_poop_interaction() a second time after a successful throw, so this regression is not covered. Consider adding a regression test that calls check_poop_interaction() again after throw_poop() succeeds. Let me know if you want me to draft that test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tamagotchi_logic.py` around lines 3349 - 3383, Update the guard at the
start of check_poop_interaction to return when current_poop_target is absent or
falsy, matching the existing current_rock_target guard pattern. Preserve the
existing interaction logic for valid poop targets.
🧹 Nitpick comments (5)
src/squid_statistics.py (3)

123-134: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider routing update_distance through add_distance.

Rollover logic now exists in two places. update_distance at lines 107-121 keeps the older single-subtraction form, which under-counts if one step crosses more than one rollover limit. add_distance handles that case with divmod. Delegation removes the duplication and makes both paths behave the same.

♻️ Proposed delegation
     def update_distance(self, dx, dy):
         '''Track distance traveled by squid with rollover protection'''
         distance = math.sqrt(dx*dx + dy*dy)
-        self.distance_swam += distance
-
-        # Check for rollover and reset with multiplier
-        if self.distance_swam >= DISTANCE_ROLLOVER_LIMIT:
-            self.distance_swam = self.distance_swam - DISTANCE_ROLLOVER_LIMIT
-            self.distance_swam_multiplier += 1
-
+        previous_multiplier = self.distance_swam_multiplier
+        self.add_distance(distance)
+        if self.distance_swam_multiplier != previous_multiplier:
             # Log the rollover event
             if hasattr(self.squid, 'tamagotchi_logic'):
                 self.squid.tamagotchi_logic.show_message(
                     f"🌊 Distance counter rolled over! Now at {self.distance_swam_multiplier}x"
                 )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/squid_statistics.py` around lines 123 - 134, Update update_distance to
delegate its distance accumulation and rollover handling to add_distance,
removing the duplicated single-subtraction rollover logic while preserving any
update_distance-specific state or behavior that is still required.

192-197: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract counter initialization instead of calling __init__ again.

load_statistics and reset both call self.__init__(self.squid) to clear counters. That call also resets self.squid and self.start_time, and it will silently clear any non-counter attribute added to __init__ later. Move the counter assignments into a private method and call it from __init__, load_statistics, and reset. The reset semantics stay the same, and the intent becomes explicit.

♻️ Proposed extraction
     def __init__(self, squid):
         self.squid = squid
         self.start_time = time.time()
+        self._reset_counters()
+        self._last_sickness_state = bool(getattr(squid, 'is_sick', False))
+
+    def _reset_counters(self):
+        """Return every tracked counter to its initial value."""
         self.total_age_seconds = 0
         self.sushi_consumed = 0
         # ... remaining counter assignments ...
         self.max_neurons_reached = DEFAULT_NEURON_COUNT
         self.current_neurons = DEFAULT_NEURON_COUNT
-        self._last_sickness_state = bool(getattr(squid, 'is_sick', False))
     def load_statistics(self, data):
         """Load canonical statistics from a desktop ``statistics.json`` dict."""
         # Loading replaces the current model. Without this reset, fields absent
         # from an older or partial save leak in from the live session and create
         # a hybrid of two different states.
-        self.__init__(self.squid)
+        self._reset_counters()
+        self.start_time = time.time()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/squid_statistics.py` around lines 192 - 197, Extract the counter-field
initialization from __init__ into a private helper, then invoke that helper from
__init__, load_statistics, and reset. Replace the self.__init__(self.squid)
calls in load_statistics and reset so they clear only counters while preserving
self.squid, self.start_time, and other non-counter state.

291-300: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The max_poops_cleaned name does not match what observe_poop_count records.

The method records the largest number of poop items present at one time. The attribute name states cleaned poops. Because max_poops_cleaned is a persisted key and a statistics tab label, renaming it needs a save-schema and UI change. If you keep the name, add a short comment that states the value is a simultaneous-count maximum.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/squid_statistics.py` around lines 291 - 300, Keep the persisted
max_poops_cleaned name unchanged for compatibility, and add a concise comment
near its update in observe_poop_count clarifying that it stores the maximum
simultaneous poop-item count, not the number cleaned.
tests/test_statistics_persistence.py (2)

243-244: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert against DEFAULT_NEURON_COUNT instead of the literal 8.

The expected fallback is the model default. src/squid_statistics.py exports DEFAULT_NEURON_COUNT, and tests/test_statistics_tab_lifecycle.py already imports it. If the default changes, these assertions and the ones at lines 163-164 and 180-181 fail for a reason unrelated to the behavior under test.

♻️ Proposed change
-                self.assertEqual(statistics.current_neurons, 8)
-                self.assertEqual(statistics.max_neurons_reached, 8)
+                self.assertEqual(
+                    statistics.current_neurons,
+                    DEFAULT_NEURON_COUNT,
+                )
+                self.assertEqual(
+                    statistics.max_neurons_reached,
+                    DEFAULT_NEURON_COUNT,
+                )

Add the import near the existing SquidStatistics import:

from src.squid_statistics import DEFAULT_NEURON_COUNT, SquidStatistics
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_statistics_persistence.py` around lines 243 - 244, Replace the
hard-coded expected value 8 in the statistics persistence assertions with
DEFAULT_NEURON_COUNT, importing it alongside SquidStatistics from
src.squid_statistics. Apply the same update to the corresponding assertions at
lines 163-164 and 180-181.

246-317: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Split the two loader scenarios into separate tests.

This method verifies _apply_save_data and load_game in sequence. If the first block of assertions fails at line 280, the ZIP loader path never runs, so one defect hides the other. Two test methods that share the malformed_statistics fixture, or a subTest per loader, report both paths independently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_statistics_persistence.py` around lines 246 - 317, Split
test_both_public_loaders_accept_malformed_numeric_statistics into separate tests
covering _apply_save_data and load_game independently. Share the
malformed_statistics fixture/setup without duplicating its definition, and
preserve all existing assertions so each loader reports failures even when the
other path is broken.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/tamagotchi_logic.py`:
- Around line 3349-3383: Update the guard at the start of check_poop_interaction
to return when current_poop_target is absent or falsy, matching the existing
current_rock_target guard pattern. Preserve the existing interaction logic for
valid poop targets.

---

Nitpick comments:
In `@src/squid_statistics.py`:
- Around line 123-134: Update update_distance to delegate its distance
accumulation and rollover handling to add_distance, removing the duplicated
single-subtraction rollover logic while preserving any update_distance-specific
state or behavior that is still required.
- Around line 192-197: Extract the counter-field initialization from __init__
into a private helper, then invoke that helper from __init__, load_statistics,
and reset. Replace the self.__init__(self.squid) calls in load_statistics and
reset so they clear only counters while preserving self.squid, self.start_time,
and other non-counter state.
- Around line 291-300: Keep the persisted max_poops_cleaned name unchanged for
compatibility, and add a concise comment near its update in observe_poop_count
clarifying that it stores the maximum simultaneous poop-item count, not the
number cleaned.

In `@tests/test_statistics_persistence.py`:
- Around line 243-244: Replace the hard-coded expected value 8 in the statistics
persistence assertions with DEFAULT_NEURON_COUNT, importing it alongside
SquidStatistics from src.squid_statistics. Apply the same update to the
corresponding assertions at lines 163-164 and 180-181.
- Around line 246-317: Split
test_both_public_loaders_accept_malformed_numeric_statistics into separate tests
covering _apply_save_data and load_game independently. Share the
malformed_statistics fixture/setup without duplicating its definition, and
preserve all existing assertions so each loader reports failures even when the
other path is broken.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6fab7e2d-151a-4779-817a-1e41f65a1ac7

📥 Commits

Reviewing files that changed from the base of the PR and between 2fa3622 and 4bdbc43.

📒 Files selected for processing (7)
  • src/squid.py
  • src/squid_statistics.py
  • src/tamagotchi_logic.py
  • tests/test_squid_statistics.py
  • tests/test_statistics_event_wiring.py
  • tests/test_statistics_persistence.py
  • tests/test_statistics_tab_lifecycle.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/test_squid_statistics.py
  • src/squid.py
  • tests/test_statistics_tab_lifecycle.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/test_statistics_event_wiring.py (1)

562-568: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Do not mask StatisticsTab mutations in the rock test.

statistics_tab.increment_stat currently delegates to squid.statistics.increment. If RockInteractionManager.throw_rock() regresses to the obsolete statistics-tab mutation path, this test still records one event and passes. Replace the lambda with a Mock and assert that it is not called. Keep the model counter assertion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_statistics_event_wiring.py` around lines 562 - 568, Update the
statistics_tab fixture in the rock interaction test so increment_stat is a Mock
rather than delegating to squid.statistics.increment, then assert that this mock
is not called after RockInteractionManager.throw_rock(). Preserve the existing
model counter assertion.
tests/test_squid_statistics.py (1)

122-128: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert that no event counter changes for an unknown event.

The test checks only cheese_consumed. A regression that changes sushi_consumed, poops_created, or another mapped counter can still pass. Snapshot all event-backed counters before and after the call.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_squid_statistics.py` around lines 122 - 128, Update
test_unknown_event_is_logged_without_mutating_counters to snapshot all
event-backed counters before calling increment("misspelled_event"), then assert
the complete counter state is unchanged afterward instead of checking only
cheese_consumed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/test_squid_statistics.py`:
- Around line 122-128: Update
test_unknown_event_is_logged_without_mutating_counters to snapshot all
event-backed counters before calling increment("misspelled_event"), then assert
the complete counter state is unchanged afterward instead of checking only
cheese_consumed.

In `@tests/test_statistics_event_wiring.py`:
- Around line 562-568: Update the statistics_tab fixture in the rock interaction
test so increment_stat is a Mock rather than delegating to
squid.statistics.increment, then assert that this mock is not called after
RockInteractionManager.throw_rock(). Preserve the existing model counter
assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 110a6aff-0a2f-4ec8-ad64-15e716c844a4

📥 Commits

Reviewing files that changed from the base of the PR and between 4bdbc43 and 2f06b87.

📒 Files selected for processing (5)
  • src/squid_statistics.py
  • src/tamagotchi_logic.py
  • tests/test_squid_statistics.py
  • tests/test_statistics_event_wiring.py
  • tests/test_statistics_persistence.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/test_statistics_persistence.py
  • src/tamagotchi_logic.py
  • src/squid_statistics.py

@ViciousSquid

Copy link
Copy Markdown
Owner

All changes tested and verified. Thank you very much! I am really grateful for your contributions to my personal project x x

@ViciousSquid
ViciousSquid merged commit e573317 into ViciousSquid:2.6.5.0_LatestVersion Aug 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(non serious bug) Some squid statistics not working

3 participants