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.
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.
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.
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.
Move BossButtons, LockedLine and UnlockedLine out of the ScrollContainer
so it only contains the HBoxContainer that drives its scroll size.
BossButtons now tracks scrolling via a manual position offset in _process,
mirroring what the lines and parallax background already do.