Skip to content
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,36 @@ GIT_RAIN_GLOBAL_RISKY_MODE=true git-rain
GIT_RAIN_GLOBAL_SCAN_PATH=/tmp/repos git-rain
```

### Garden mode tuning (advanced)

Picking `rain_animation_mode = "garden"` swaps the rain background for a
slow-paced lifecycle: seeds drift down with the rain, plants progress through
sprout, bud, and bloom, then wither and scatter 2–3 new seeds nearby. When the
visible plants cover roughly 80% of the rain strip, the storm clears, the sun
comes out, and the surviving flowers stay forever.

The defaults aim for a calm pace, but a few advanced TOML keys are available
under `[ui]` for tweaking. They are intentionally **not** surfaced in the
in-app settings TUI — leave any unset (or set to `0`) to keep the default.

```toml
[ui]
rain_animation_mode = "garden"

# garden_seed_rate = 0.055 # fraction of new sky drops that fall as seeds (0..1)
# garden_growth_pace = 1.0 # multiplier on stage moisture thresholds (>1 = slower)
# garden_bloom_duration_base = 60 # min frames a flower lingers in full bloom
# garden_bloom_duration_jitter = 40 # extra random frames added to bloom lifetime
# garden_wither_duration = 28 # frames a withered plant lingers before re-seeding
# garden_offspring_min = 2 # minimum seeds a dying plant scatters
# garden_offspring_max = 3 # maximum seeds a dying plant scatters
# garden_offspring_spread = 3 # X-jitter half-width around the parent column
```

`garden_growth_pace` is the most useful single dial: set it to `2.0` to roughly
halve growth speed, or `0.5` to roughly double it. The other knobs trade
visual density (more or fewer seeds, longer or shorter blooms) for clarity.

### Config file, locks, and crashes

**Registry (`repos.toml`)** — Writes use a cross-process lock file (`repos.toml.lock`), atomic replace, and stale-lock detection (owner PID). If a process dies mid-run you may still see a leftover lock: the CLI prompts to remove it when safe, or you can use **`--force-unlock-registry`** in scripts. This is the same class of “stale lock / don’t corrupt the database” problem as other multi-repo tools; treat lock removal like any other forced unlock — only when you are sure no other `git-rain` is running.
Expand Down
17 changes: 16 additions & 1 deletion internal/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ mainline_patterns = []
show_rain_animation = true

# Animation mode: "basic" (rain drops), "advanced" (clouds + rain + flowers),
# or "matrix" (falling code characters)
# "matrix" (falling code characters), or "garden" (seeds bloom into a meadow,
# then the rain stops and the sun comes out)
rain_animation_mode = "basic"

# Show flavor quotes in the TUI banner
Expand All @@ -131,5 +132,19 @@ rain_tick_ms = 150

# Color profile: "storm", "drizzle", "monsoon", "rainbow", "synthwave"
color_profile = "storm"

# --- Garden mode tuning (advanced) -----------------------------------------
# These keys only affect rain_animation_mode = "garden". Leave them unset
# (or at 0) to use the built-in defaults; tweak to make growth slower or
# offspring more (or less) prolific.
#
# garden_seed_rate = 0.055 # sky seed density (0..1); runtime caps bursts per frame
# garden_growth_pace = 1.0 # multiplier on stage moisture thresholds (>1 = slower)
# garden_bloom_duration_base = 60 # min frames a flower lingers in full bloom
# garden_bloom_duration_jitter = 40 # extra random frames added to bloom lifetime
# garden_wither_duration = 28 # frames a withered plant lingers before re-seeding
# garden_offspring_min = 1 # minimum seeds a dying plant scatters
# garden_offspring_max = 2 # maximum seeds a dying plant scatters
# garden_offspring_spread = 2 # X-jitter half-width around the parent column
`
}
11 changes: 11 additions & 0 deletions internal/config/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,17 @@ func setDefaults(v *viper.Viper) {
v.SetDefault("ui.startup_quote_interval_sec", defaults.UI.StartupQuoteIntervalSec)
v.SetDefault("ui.rain_tick_ms", defaults.UI.RainTickMS)
v.SetDefault("ui.color_profile", defaults.UI.ColorProfile)

// Garden tuning keys default to zero so the runtime can detect "unset"
// and substitute the built-in defaults from DefaultGardenTuning().
v.SetDefault("ui.garden_seed_rate", defaults.UI.GardenSeedRate)
v.SetDefault("ui.garden_growth_pace", defaults.UI.GardenGrowthPace)
v.SetDefault("ui.garden_bloom_duration_base", defaults.UI.GardenBloomDurationBase)
v.SetDefault("ui.garden_bloom_duration_jitter", defaults.UI.GardenBloomDurationJitter)
v.SetDefault("ui.garden_wither_duration", defaults.UI.GardenWitherDuration)
v.SetDefault("ui.garden_offspring_min", defaults.UI.GardenOffspringMin)
v.SetDefault("ui.garden_offspring_max", defaults.UI.GardenOffspringMax)
v.SetDefault("ui.garden_offspring_spread", defaults.UI.GardenOffspringSpread)
}

// Bounded lock acquisition for config.toml: SaveConfig runs from the TUI on
Expand Down
38 changes: 37 additions & 1 deletion internal/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ type UIConfig struct {
ShowRainAnimation bool `mapstructure:"show_rain_animation" toml:"show_rain_animation"`

// Animation mode: "basic" (rain drops), "advanced" (clouds + rain + flowers),
// or "matrix" (falling code glyphs in the same column pattern).
// "matrix" (falling code glyphs in the same column pattern), or "garden"
// (seeds, rain, growth, then sun).
RainAnimationMode string `mapstructure:"rain_animation_mode" toml:"rain_animation_mode"`

// Show flavor quotes: TUI banner plus CLI motivation lines.
Expand All @@ -76,6 +77,40 @@ type UIConfig struct {
// Color profile for rain and TUI accents.
// Options: "storm", "drizzle", "monsoon", "rainbow", "synthwave".
ColorProfile string `mapstructure:"color_profile" toml:"color_profile"`

// Garden mode tuning (used when rain_animation_mode = "garden").
// In the settings TUI, three rows appear under Rain animation mode; any
// field left at zero here still falls back to built-in defaults at runtime.

Comment on lines +81 to +84
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Stale doc: "three rows" claim.

The comment asserts that "three rows appear under Rain animation mode" in the settings TUI, but this block introduces eight garden tuning fields. Since the README explicitly states these are not surfaced in the in-app settings TUI, this inline comment is likely stale and misleading for future maintainers.

✏️ Suggested wording
 	// Garden mode tuning (used when rain_animation_mode = "garden").
-	// In the settings TUI, three rows appear under Rain animation mode; any
-	// field left at zero here still falls back to built-in defaults at runtime.
+	// These are TOML-only (not exposed in the in-app settings TUI). Any field
+	// left at zero falls back to built-in defaults at runtime.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Garden mode tuning (used when rain_animation_mode = "garden").
// In the settings TUI, three rows appear under Rain animation mode; any
// field left at zero here still falls back to built-in defaults at runtime.
// Garden mode tuning (used when rain_animation_mode = "garden").
// These are TOML-only (not exposed in the in-app settings TUI). Any field
// left at zero falls back to built-in defaults at runtime.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/config/types.go` around lines 80 - 83, Update the stale comment
above the Garden mode tuning block to remove the "three rows" claim and reflect
reality: mention that this block defines eight garden tuning fields for
rain_animation_mode = "garden" and that these fields are not surfaced in the
in-app settings TUI (they fall back to built-in defaults if left zero). Edit the
comment near the Garden mode tuning declaration in types.go to describe the
correct number of fields and current TUI visibility instead of the old "three
rows" wording.

// GardenSeedRate is the target probability (0..1) for sky seeds; the
// simulator caps seeds per frame, so this reads as density, not i.i.d.
// rolls. Lower = rarer; default ~0.055.
GardenSeedRate float64 `mapstructure:"garden_seed_rate" toml:"garden_seed_rate,omitempty"`

// GardenGrowthPace multiplies the moisture thresholds for every growth
// stage. >1 slows growth; <1 speeds it up; 0 = use defaults.
GardenGrowthPace float64 `mapstructure:"garden_growth_pace" toml:"garden_growth_pace,omitempty"`

// GardenBloomDurationBase is the minimum bloom lifetime in animation
// frames before a flower starts to wither. 0 = default.
GardenBloomDurationBase int `mapstructure:"garden_bloom_duration_base" toml:"garden_bloom_duration_base,omitempty"`

// GardenBloomDurationJitter is added on top of the base bloom duration
// (uniform random in [0, jitter)). 0 = default.
GardenBloomDurationJitter int `mapstructure:"garden_bloom_duration_jitter" toml:"garden_bloom_duration_jitter,omitempty"`

// GardenWitherDuration is how many frames a withered plant lingers
// before crumbling and seeding offspring. 0 = default.
GardenWitherDuration int `mapstructure:"garden_wither_duration" toml:"garden_wither_duration,omitempty"`

// GardenOffspringMin/Max bound how many seeds a dying plant scatters.
// 0 = use defaults (currently 1 and 2).
GardenOffspringMin int `mapstructure:"garden_offspring_min" toml:"garden_offspring_min,omitempty"`
GardenOffspringMax int `mapstructure:"garden_offspring_max" toml:"garden_offspring_max,omitempty"`
Comment on lines +106 to +109
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Stale doc: offspring defaults.

The comment says "currently 1 and 2" but the README example and defaults.go example TOML list garden_offspring_min = 2 and garden_offspring_max = 3. Please reconcile the doc with the actual DefaultGardenTuning() values so users don't get surprised by non-zero overrides behaving differently than documented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/config/types.go` around lines 105 - 108, The inline comment for
GardenOffspringMin and GardenOffspringMax is stale (mentions "currently 1 and
2") and should be reconciled with the actual defaults returned by
DefaultGardenTuning() and the README example; update the comment to reflect the
real defaults (e.g., "currently 2 and 3") or adjust DefaultGardenTuning()/README
to match the comment so they are consistent, ensuring references to
GardenOffspringMin and GardenOffspringMax in defaults.go and README are all
aligned.


// GardenOffspringSpread is the half-width X jitter applied around the
// parent column when scattering offspring seeds. 0 = default.
GardenOffspringSpread int `mapstructure:"garden_offspring_spread" toml:"garden_offspring_spread,omitempty"`
}

const (
Expand All @@ -91,6 +126,7 @@ const (
UIRainAnimationBasic = "basic"
UIRainAnimationAdvanced = "advanced"
UIRainAnimationMatrix = "matrix"
UIRainAnimationGarden = "garden"
)

// UIColorProfiles returns valid built-in UI color profile names.
Expand Down
Loading
Loading