Commit Graph

436 Commits

Author SHA1 Message Date
Adrien Ufferte 30c1a9863f Optimization global 2026-06-11 11:16:03 +02:00
Adrien Ufferte 239643c325 Fix directory deletion failing on hidden files, breaking the pack swap
The new extract/verify/swap flow surfaced a long-standing bug in
Utils.clean_dir(). On macOS, browsing a folder in the Finder drops a
hidden .DS_Store file in it, and clean_dir() had two flaws around that:

- DirAccess listings skip hidden files by default, so .DS_Store was
  never listed nor removed and the directory was never actually empty.
- The return value of dir.remove(subfolder) was ignored, so the failed
  removal of a subdirectory (non-empty because of its own hidden file)
  was silently swallowed and clean_dir() still reported success.

As a result, delete_directory_recursive() emptied the old language pack
but could not delete its directory, the rename of the new pack onto
that still-existing directory failed, and the downloader showed the
download-error popup. Since the version on disk was never updated, the
app re-downloaded and failed again in a loop on every launch. The old
pre-swap flow had the same deletion failure but masked it: it extracted
directly into the existing directory and ignored rename errors.

- clean_dir() now lists hidden files (include_hidden) and checks every
  removal, trying to remove everything (best effort, so callers like
  clear_all_local_data still wipe as much as possible) and reporting
  the first error encountered instead of a false OK.
- The downloader verifies that the previous pack directory is really
  gone before renaming the new one into place; if not, it aborts the
  swap, keeps the temporary directory and shows the error instead of
  attempting a rename that cannot succeed.
- Add regression tests covering hidden-file deletion, full content
  cleanup and the missing-directory error path.

Verified with the GUT test suite (65/65 passing).
2026-06-11 10:15:40 +02:00
Adrien Ufferte c77e9b90ec Fix synchronization reliability: freed popup refs and unvalidated payloads
The UserDatabaseSynchronizer outlives the scenes that use it, which made
the periodic background synchronization fragile in two ways:

1. A freed loading popup permanently broke the sync.
   The teacher settings screen injects its LoadingPopup into the
   synchronizer but never cleared the reference on scene exit. Since a
   freed Object is not equal to null in Godot 4, the next timer-driven
   synchronization called into the freed node, crashed the coroutine
   after `synchronizing` had been set to true, and every subsequent
   synchronize() call was then silently cancelled by the re-entrancy
   guard until the app restarted.
   - All popup guards now use is_instance_valid() (plus is_inside_tree()
     where get_tree() is needed).
   - The teacher settings screen clears the synchronizer's popup
     reference in _exit_tree().

2. Server payloads were applied without shape validation.
   _apply_server_response() indexed JSON fields (lesson unlock data,
   remediation score tuples, confusion matrix entries) without checking
   their types, as flagged by the five "TODO ADD SECURITY" comments. A
   single malformed or legacy record raised a script error mid-sync,
   which also wedged the `synchronizing` flag as described above.
   - Lesson unlock entries, remediation score pairs and confusion matrix
     entries are now validated before use; malformed records are logged
     and skipped individually so one bad record cannot abort the whole
     synchronization or corrupt local student data.
   - The three copy-pasted remediation parsing blocks are factored into
     a single _parse_score_remediation() helper.

Verified with the GUT test suite (58/58 passing).
2026-06-10 17:00:31 +02:00
Adrien Ufferte 2db21b597b Minigames wheel version 2 2026-06-06 11:13:22 +02:00
Adrien Ufferte d55c05b53d Debug clouds "world bounds" 2026-05-28 09:31:11 +02:00
Adrien Ufferte 8f4a1be939 Cleaning code for cloud manager 2026-05-26 16:40:56 +02:00
Adrien Ufferte bebc449f56 cleaning 2026-04-30 16:15:13 +02:00
Adrien Ufferte 6f0163a8db Stratify garden clouds in scroll-space, not world-space
Initial spawning placed clouds uniformly across [0, spawn_width], which
broke down at the edges of the scroll range whenever the parallax factor
was anything other than exactly 1.0:

- With parallax < 1.0 (background feel), a cloud is only ever visible at
  scrolls in [(drift_x - screen_w) / parallax, drift_x / parallax]. So a
  far cloud at drift_x near spawn_width (e.g. 28000 with parallax 0.5)
  needs scroll ~56000 to be seen, far beyond max_scroll. About half the
  spawn range was a dead zone the user could never reach.

- With parallax > 1.0 (foreground feel — what the gardens scene now uses),
  the opposite happens: clouds visible at the right edge of the scroll
  range live at drift_x = max_scroll * parallax + screen_w, which is well
  past spawn_width. The right side of the gardens stayed empty because no
  cloud was ever spawned that far out.

Add a max_scroll parameter to configure_world() and stratify in scroll
space: each cloud gets a target_scroll value in its own slot of
[0, max_scroll], and its drift_x is placed at target_scroll * parallax +
horiz_jitter. The cloud's drift_x range now matches the scroll positions
where it can actually be visible, regardless of whether parallax is below
or above 1.0. _reset_cloud is updated correspondingly to wrap clouds to
the right edge of their individual visibility range rather than the
world's right edge.

Also have configure_world refresh screen_width every call so the manager
picks up the correct viewport size even when the parent invokes it later
in the ready sequence.
2026-04-30 16:15:13 +02:00
Adrien Ufferte ad571da545 Spread initial cloud spawn positions via stratified sampling
Replace pure randf_range across the spawn width with stratified placement:
the spawn range is split into N equal slots (N = number of clouds) and each
cloud is placed at a random position inside its own slot. This caps the
maximum gap between clouds at one slot width, eliminating the "all clouds
on one side" failure mode of independent random draws while keeping each
cloud's exact position random within its band so the layout still feels
natural rather than grid-regular.

The sprite list is shuffled before slot assignment so identical duplicated
templates don't end up in adjacent slots in scene-tree order.
2026-04-30 16:15:13 +02:00
Adrien Ufferte 63436f1782 Anchor garden clouds to world coordinates for recycling
Previously clouds in the gardens recycled based on their rendered (viewport)
position. When the user scrolled to the far right, parallax pushed every
cloud's rendered X far into the negative, triggering all of them to wrap
to the right viewport edge. Their drift_x was thus warped to roughly
scroll_offset + screen_width — so when the user scrolled back left, every
cloud sat in the world to the right of the visible area, leaving the left
side of the gardens empty.

Switch CloudsManager to use world-anchored recycling whenever spawn_width
is set: a cloud is only recycled when its drift_x (its world coordinate)
falls past the world's left edge, and it respawns at the world's right edge
rather than the viewport's. The legacy viewport-anchored path is preserved
for the minigames that don't set spawn_width.
2026-04-30 16:15:13 +02:00
Adrien Ufferte de395ab036 Add scrolling clouds to the gardens scene
Extend CloudsManager with optional scroll-driven parallax and auto-population:

- New scroll_offset property feeds an external horizontal scroll value;
  each cloud's rendered X is drift_x - scroll_offset * parallax_factor.
- min_parallax_factor / max_parallax_factor are interpolated by the same
  depth factor that drives speed and scale, so closer clouds parallax more.
- spawn_width + clouds_per_screen + configure_world() let the manager
  duplicate its template children to cover wider scrollable worlds.
- Defaults preserve the previous behavior, so parakeets / penguin / monkeys
  / boss minigames keep their existing per-frame drift unchanged.

In gardens.tscn, add a CloudsLayer (CanvasLayer, layer = 1) on top of the
existing UI with three template cloud sprites (transparent via modulate
alpha, mouse passthrough by virtue of being Sprite2D). gardens.gd configures
the cloud world width to garden_count * GARDEN_SIZE after lessons are set
up, then forwards scroll_container.scroll_horizontal to clouds.scroll_offset
each frame so clouds drift naturally and parallax with garden scrolling.
2026-04-30 16:15:13 +02:00
Adrien Ufferte 790260e2fd Add CHANGE_LANGUAGE feature to teacher settings
Reorders the left sidebar so risky actions sit at the bottom with a gap:
Dashboard, Synchronize, Logout | Change Language (red), Delete Account.

The new Change Language button opens a warning popup with a language
dropdown. On confirm, if the selected locale differs from the current
one, the client calls POST /reset_language. On success it wipes the
local teacher folder (progression, remediation, confusion matrix…),
applies the new language to device and teacher settings, logs out and
reloads from the splash screen. On error the user sees an error popup
and nothing local changes.
2026-04-17 16:38:00 +02:00
Adrien Ufferte c6f7ef833d Cleaning 2026-04-17 10:10:58 +02:00
Adrien Ufferte e1c8c65717 Remove unused variables and dead code 2026-03-03 10:25:15 +01:00
Adrien Ufferte 6876798046 Remove unused variables 2026-02-26 14:59:46 +01:00
Adrien Ufferte 9bf3994eb3 Merge pull request #258 from Excello-Recherche-Education/remove-useless-functions
Remove useless functions
2026-02-24 16:17:33 +01:00
Adrien Ufferte 8ccc15dd29 Remove useless functions 2026-02-23 16:20:01 +01:00
Adrien Ufferte 04b19c32d3 Coroutine handle join_either with no futures 2026-02-23 10:10:31 +01:00
Adrien Ufferte 641fcc30fa security 2026-02-20 13:04:44 +01:00
Adrien Ufferte ca7d9bc410 Merge pull request #245 from Excello-Recherche-Education/add-unicode-normalizer
Add unicode normalizer character
2026-02-20 12:46:50 +01:00
Adrien Ufferte 9519450a55 Security 2026-02-20 12:17:35 +01:00
Adrien Ufferte c1f8dd64e5 Improve display of version number 2026-02-20 11:14:08 +01:00
Adrien Ufferte 9c26fb2041 Fixed a Unicode normalization bug by adding the missing uppercase Vietnamese Ớ decomposition entry 2026-02-20 09:40:56 +01:00
Adrien Ufferte 9c65b7be19 Merge pull request #242 from Excello-Recherche-Education/debug-user-sync-timestamp
Prevents a brand-new local placeholder from incorrectly winning over …
2026-02-20 09:22:17 +01:00
Adrien Ufferte 31c4e44dc3 Prevents a brand-new local placeholder from incorrectly winning over older-but-real server progression during sync for returning users 2026-02-19 15:32:06 +01:00
Adrien Ufferte 2958b773e0 Make previous log visible in dev settings 2026-02-19 13:23:34 +01:00
Adrien Ufferte d127328319 Add italian language 2026-02-13 11:06:09 +01:00
Adrien Ufferte 22a39dd286 Add log 2026-02-12 11:12:34 +01:00
Adrien Ufferte f524bbfb7f Merge branch '3.0.0' into prof_tool/1.3.7 2026-02-12 10:47:50 +01:00
Adrien Ufferte da077461aa Update to Godot 4.6 and prevent data deletion 2026-02-10 21:35:05 +01:00
Adrien Ufferte 1c269bb159 Fixed a normalization bug in the Unicode map by adding the missing lowercase Vietnamese ờ decomposition (o + horn + grave) to to_nfd_basic, which previously only had the uppercase Ờ equivalent in that branch of mappings. 2026-02-10 11:19:27 +01:00
Adrien Ufferte 7e4c1f6091 Merge pull request #223 from Excello-Recherche-Education/unit-test-control-binder
Add unit test for control binder
2026-02-10 09:06:07 +01:00
Adrien Ufferte c7a9b49e0d Merge pull request #222 from Excello-Recherche-Education/boss-minigame-feedbacks
Boss minigame feedbacks
2026-02-06 16:48:10 +01:00
Adrien Ufferte 09faa9537e Add unit test for control binder 2026-02-06 14:59:51 +01:00
Adrien Ufferte 77affb11bf Boss minigame feedbacks 2026-02-06 14:05:18 +01:00
Adrien Ufferte e7ef6f9e48 Add "clear" option for local data 2026-02-05 11:08:12 +01:00
Adrien Ufferte 5e30c638bd Merge pull request #214 from Excello-Recherche-Education/teacher-settings-bug
Teacher settings debug
2026-02-03 14:11:29 +01:00
Adrien Ufferte 05b7ad34de Merge pull request #213 from Excello-Recherche-Education/boss-new-graphisms
Boss new graphisms
2026-02-03 14:11:14 +01:00
Adrien Ufferte cfeec1e2f2 Workaround for warnings for autoloads of class instead of scripts 2026-02-03 11:52:00 +01:00
Adrien Ufferte 61095c4e99 Debug Teacher Settings 2026-02-03 11:33:43 +01:00
Adrien Ufferte 5217cda4a9 Merge pull request #211 from Excello-Recherche-Education/unit-test-bezier
Unit test Bezier
2026-02-03 10:56:51 +01:00
Adrien Ufferte e65f0f2be3 Boss new graphisms 2026-02-03 10:55:18 +01:00
Adrien Ufferte 17e9b5572a Add unit test for log resource 2026-02-02 14:02:57 +01:00
Adrien Ufferte 6b89293a3f Bezier full tests 2026-02-02 14:01:24 +01:00
Adrien Ufferte 8aece8342d Merge pull request #206 from Excello-Recherche-Education/unit-tests
Unit tests
2026-02-02 14:00:06 +01:00
Adrien Ufferte 3e84a86adb Add logs for user progression 2026-01-29 16:30:30 +01:00
Adrien Ufferte 0fa31c48ac Prevent orphan Node to be created by autoload UserDataManager 2026-01-29 11:30:51 +01:00
Adrien Ufferte dfd584d17c Merge pull request #205 from Excello-Recherche-Education/godot-upgrade-4.6
Upgrade Godot version to 4.6
2026-01-29 11:28:57 +01:00
Adrien Ufferte 847b96ec30 Merge pull request #203 from Excello-Recherche-Education/boss-data-collection
Add data collection to boss games
2026-01-29 11:28:12 +01:00
Adrien Ufferte f3e3d4bfcd Upgrade Godot version to 4.6 2026-01-27 14:52:22 +01:00