diff --git a/apps/game/src/gameplay/galaxy/mod.rs b/apps/game/src/gameplay/galaxy/mod.rs index 8083e83..cdc7d3d 100644 --- a/apps/game/src/gameplay/galaxy/mod.rs +++ b/apps/game/src/gameplay/galaxy/mod.rs @@ -357,7 +357,7 @@ fn generate_disk( let r = inner + rng.gen::().powf(0.62) * span; let angle = std::f32::consts::TAU * arm as f32 / arm_count as f32 + ((r - inner) / span) * disk.twist - + (rng.gen::() - 0.5) * 0.72; + + (rng.gen::() - 0.5) * arm_scatter(arm_count); let y = (rng.gen::() - 0.5) * 20.0; let mut candidate = Vec3::new(angle.cos() * r, y, angle.sin() * r); @@ -491,6 +491,20 @@ fn generate_beam( } } +/// Angular scatter width per system, scaled to the inter-arm gap. +/// Each arm occupies `TAU / arm_count` radians; systems scatter within +/// ~30% of that gap so arms remain visually distinct regardless of +/// how many there are. Single-arm disks use a fixed wide scatter +/// since there are no adjacent arms to bleed into. +fn arm_scatter(arm_count: u32) -> f32 { + if arm_count <= 1 { + // No adjacent arms — generous scatter for a natural-looking blob. + 1.5 + } else { + std::f32::consts::TAU / arm_count as f32 * 0.30 + } +} + /// Spacing relaxation curve — same shape as the previous single-pass /// generator: full spacing for the first 60 attempts, 82% until attempt /// 120, then 68% as a fallback to guarantee placement. diff --git a/apps/game/src/gameplay/galaxy/params.rs b/apps/game/src/gameplay/galaxy/params.rs index 0fb8c7d..1910bf6 100644 --- a/apps/game/src/gameplay/galaxy/params.rs +++ b/apps/game/src/gameplay/galaxy/params.rs @@ -33,7 +33,7 @@ const DEFAULT_CORE_RADIUS: f32 = 38.0; // keeping the same arms/twist as the docs prototype. const DEFAULT_DISK_COUNT: usize = 100; const DEFAULT_DISK_ARMS: u32 = 4; -const DEFAULT_DISK_TWIST: f32 = 3.0; +const DEFAULT_DISK_TWIST: f32 = 12.0; // ~1.9 full turns // Beam defaults: small population so the two polar columns read clearly // without dominating the scene. Equal top/bottom for symmetry. @@ -66,9 +66,9 @@ pub const DISK_COUNT_MAX: usize = 220; pub const DISK_COUNT_STEP: usize = 4; pub const DISK_ARMS_MIN: u32 = 1; pub const DISK_ARMS_MAX: u32 = 6; -pub const DISK_TWIST_MIN: f32 = 1.0; -pub const DISK_TWIST_MAX: f32 = 6.0; -pub const DISK_TWIST_STEP: f32 = 0.2; +pub const DISK_TWIST_MIN: f32 = 4.0; // ~0.6 turns — bare minimum curvature +pub const DISK_TWIST_MAX: f32 = 18.0; // ~2.9 turns — tight logarithmic spiral +pub const DISK_TWIST_STEP: f32 = 0.4; /// Max tilt is 45° — disks should never be parallel to beams. pub const DISK_TILT_MIN: f32 = 0.0; diff --git a/apps/game/src/gameplay/galaxy/ui.rs b/apps/game/src/gameplay/galaxy/ui.rs index 1077771..8b85afc 100644 --- a/apps/game/src/gameplay/galaxy/ui.rs +++ b/apps/game/src/gameplay/galaxy/ui.rs @@ -1300,14 +1300,35 @@ fn spawn_scroll_contents_with_tabs( // ── Helpers ───────────────────────────────────────────────────────────────── /// Randomize all fields of a [`DiskParams`] to random valid values within -/// their respective bounds, rounded to the UI step. +/// their respective bounds, rounded to the UI step. Biased toward +/// spiral-friendly combinations: arms are weighted toward 2–4, twist is +/// floored at `arms × 4.0` radians (ensuring at least ~0.6 turns per arm), +/// and outer radius defaults to "auto" most of the time so disks fill the +/// galaxy. fn randomize_disk(rng: &mut impl rand::Rng) -> DiskParams { let mut disk = DiskParams::default(); disk.count = rng.gen_range(DISK_COUNT_MIN..=DISK_COUNT_MAX); disk.count = (disk.count / DISK_COUNT_STEP) * DISK_COUNT_STEP; - disk.arms = rng.gen_range(DISK_ARMS_MIN..=DISK_ARMS_MAX); - disk.twist = rng.gen_range(DISK_TWIST_MIN..=DISK_TWIST_MAX); + + // Weight arms toward 2–4 for clear spiral structure. 1 and 5–6 are + // allowed but less likely. + disk.arms = match rng.gen_range(0..10) { + 0 => 1, + 1..=3 => 2, + 4..=6 => 3, + 7..=8 => 4, + 9 => rng.gen_range(5..=DISK_ARMS_MAX), + _ => DISK_ARMS_MIN, + }; + + // Floor twist at arms × 4.0 so multi-arm disks always wind enough to + // show distinct spiral structure. The floor ensures at least ~0.6 turns + // between adjacent arms. + let twist_floor = disk.arms as f32 * 4.0; + let effective_min = DISK_TWIST_MIN.max(twist_floor); + disk.twist = rng.gen_range(effective_min..=DISK_TWIST_MAX); disk.twist = (disk.twist / DISK_TWIST_STEP).round() * DISK_TWIST_STEP; + disk.tilt = rng.gen_range(DISK_TILT_MIN..=DISK_TILT_MAX); disk.tilt = (disk.tilt / DISK_TILT_STEP).round() * DISK_TILT_STEP; disk.rotation_offset = rng.gen_range(DISK_ROTATION_MIN..=DISK_ROTATION_MAX); @@ -1319,8 +1340,8 @@ fn randomize_disk(rng: &mut impl rand::Rng) -> DiskParams { disk.outer_radius = rng.gen_range(DISK_OUTER_RADIUS_MIN..=DISK_OUTER_RADIUS_MAX); disk.outer_radius = (disk.outer_radius / DISK_OUTER_RADIUS_STEP).round() * DISK_OUTER_RADIUS_STEP; - // Allow 0 (auto) ~50% of the time. - if rng.gen_bool(0.5) { + // Allow 0 (auto) ~60% of the time so disks fill the galaxy by default. + if rng.gen_bool(0.6) { disk.outer_radius = 0.0; } disk