HANSOL commited on
Commit
2afa69c
ยท
0 Parent(s):

Geo-Lab v4.1 - Clean deployment

Browse files
This view is limited to 50 files because it contains too many changes. ย  See raw diff
.gitignore ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ env/
8
+ venv/
9
+ .env
10
+
11
+ # Streamlit
12
+ .streamlit/
13
+
14
+ # IDE
15
+ .vscode/
16
+ .idea/
17
+ *.swp
18
+ *.swo
19
+
20
+ # OS
21
+ .DS_Store
22
+ Thumbs.db
23
+
24
+ # Logs
25
+ *.log
26
+
27
+ # Binary assets (too large for HuggingFace)
28
+ assets/reference/
29
+ *.png
30
+ *.jpg
31
+ *.jpeg
00_Master_Integration_Plan.md ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ๐ŸŒ [Geo-Lab AI] ํ†ตํ•ฉ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„์„œ (Master Integration Plan)
2
+
3
+ **Project Name:** Geo-Lab AI: The Living Earth
4
+ **Version:** Global Build 1.0 (Integration Phase)
5
+ **Scope:** Full-stack Geomorphology Simulation (All Modules)
6
+ **Director:** [User Name]
7
+
8
+ ---
9
+
10
+ ## 0. ํ†ตํ•ฉ ๊ฐœ๋ฐœ ์ฒ ํ•™ (System Philosophy)
11
+
12
+ **"์ง€ํ˜•์€ ๊ณ ๋ฆฝ๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค (No Landform is an Island)."**
13
+ ๋ณธ ํ†ตํ•ฉ ์‹œ์Šคํ…œ์€ 7๊ฐœ์˜ ๋…๋ฆฝ ๋ชจ๋“ˆ(ํ•˜์ฒœ, ํ•ด์•ˆ, ์นด๋ฅด์ŠคํŠธ, ํ™”์‚ฐ, ๋น™ํ•˜, ๊ฑด์กฐ, ํ‰์•ผ)์„ **'๊ฐ€์ด์•„ ์—”์ง„(Gaia Engine)'**์ด๋ผ๋Š” ํ•˜๋‚˜์˜ ๋ฌผ๋ฆฌ ์„œ๋ฒ„ ์œ„์—์„œ ์œ ๊ธฐ์ ์œผ๋กœ ๊ตฌ๋™ํ•œ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์กฐ์ ˆํ•˜๋Š” ๊ธฐํ›„์™€ ์ง€๊ฐ ์—๋„ˆ์ง€๊ฐ€ ๋‚˜๋น„ ํšจ๊ณผ์ฒ˜๋Ÿผ ์ „ ์ง€๊ตฌ์  ์ง€ํ˜• ๋ณ€ํ™”๋ฅผ ์ผ์œผํ‚ค๋Š” **์ˆœํ™˜ ์‹œ์Šคํ…œ(Feedback Loop)** ๊ตฌํ˜„์„ ๋ชฉํ‘œ๋กœ ํ•œ๋‹ค.
14
+
15
+ ---
16
+
17
+ ## 1. ์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜: ๊ฐ€์ด์•„ ์—”์ง„ (The Gaia Engine)
18
+
19
+ ๋ชจ๋“  ๊ฐœ๋ณ„ ๋ชจ๋“ˆ์€ ์•„๋ž˜ 3๊ฐ€์ง€ **๊ธ€๋กœ๋ฒŒ ๋ณ€์ˆ˜(Global Variables)**์— ์ข…์†๋˜์–ด ์ž‘๋™ํ•œ๋‹ค.
20
+
21
+ ### ๐ŸŽ›๏ธ ๊ธ€๋กœ๋ฒŒ ์ปจํŠธ๋กค๋Ÿฌ (Global Controllers)
22
+ 1. **์ง€๊ฐ ์—๋„ˆ์ง€ ($E_{tectonics}$):**
23
+ * *Role:* ํŒ์˜ ์ด๋™ ์†๋„ ๋ฐ ๋งˆ๊ทธ๋งˆ ํ™œ๋™์„ฑ ์ œ์–ด.
24
+ * *Impact:* $E \uparrow$ $\Rightarrow$ ์‹ ๊ธฐ ์Šต๊ณก ์‚ฐ์ง€ ์œต๊ธฐ, ํ™”์‚ฐ ํญ๋ฐœ ๋นˆ๋„ ์ฆ๊ฐ€, ์ง€์ง„ ๋ฐœ์ƒ.
25
+ 2. **๊ธฐํ›„ ๋ ˆ๋ฒจ ($C_{climate}$):**
26
+ * *Role:* ์ง€๊ตฌ ํ‰๊ท  ๊ธฐ์˜จ($T_{avg}$) ๋ฐ ๊ฐ•์ˆ˜๋Ÿ‰($P_{avg}$) ์ œ์–ด.
27
+ * *Impact:* $T \downarrow$ $\Rightarrow$ ๋น™ํ•˜ ํ™•์žฅ (๋น™๊ธฐ). $T \uparrow$ $\Rightarrow$ ๋น™ํ•˜ ํ›„ํ‡ด ๋ฐ ํ•ด์ˆ˜๋ฉด ์ƒ์Šน (๊ฐ„๋น™๊ธฐ).
28
+ 3. **ํ•ด์ˆ˜๋ฉด ๊ณ ๋„ ($H_{sea}$):**
29
+ * *Role:* ์นจ์‹ ๊ธฐ์ค€๋ฉด(Base Level) ์„ค์ •.
30
+ * *Impact:* $H \downarrow$ $\Rightarrow$ ํ•˜์ฒœ ํ•˜๋ฐฉ ์นจ์‹ ๊ฐ•ํ™”(๊ฐ์ž… ๊ณก๋ฅ˜). $H \uparrow$ $\Rightarrow$ ํ•˜๊ตฌ ํ‡ด์  ๊ฐ•ํ™”(์‚ผ๊ฐ์ฃผ, ์„ํ˜ธ).
31
+
32
+ ---
33
+
34
+ ## 2. ๋ชจ๋“ˆ ๊ฐ„ ์ƒํ˜ธ์ž‘์šฉ ๋กœ์ง (Cross-Module Interaction)
35
+
36
+ ์ง€ํ˜• ๋ชจ๋“ˆ๋ผ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›์œผ๋ฉฐ ์ƒˆ๋กœ์šด ์ง€ํ˜•์„ ์ƒ์„ฑํ•˜๋Š” **์ธ๊ณผ๊ด€๊ณ„ ์ฒด์ธ(Chain)**์„ ์ •์˜ํ•œ๋‹ค.
37
+
38
+ ### ๐Ÿ”— Chain A. [์‚ฐ์ง€] $\leftrightarrow$ [๊ฑด์กฐ]: ๋น„๊ทธ๋Š˜ ํšจ๊ณผ (Rain Shadow)
39
+ * **Logic:** ๊ฑฐ๋Œ€ํ•œ ์Šต๊ณก ์‚ฐ๋งฅ์ด ์œต๊ธฐํ•˜์—ฌ ์ˆ˜์ฆ๊ธฐ๋ฅผ ๋ง‰์Œ.
40
+ * **Process:**
41
+ 1. **[์‚ฐ์ง€]** ๋ชจ๋“ˆ: ํ•ด๋ฐœ๊ณ ๋„ $H > 3,000m$ ์‚ฐ๋งฅ ์ƒ์„ฑ.
42
+ 2. **[๊ธฐ์ƒ]** ์—”์ง„: ํŽธ์„œํ’์ด ์‚ฐ๋งฅ์— ๋ถ€๋”ชํ˜€ ๋น„๋ฅผ ๋ฟŒ๋ฆผ (๋ฐ”๋žŒ๋ฐ›์ด ์‚ฌ๋ฉด).
43
+ 3. **[๊ฑด์กฐ]** ๋ชจ๋“ˆ ํŠธ๋ฆฌ๊ฑฐ: ์‚ฐ๋งฅ ๋ฐ˜๋Œ€ํŽธ์— ๊ฑด์กฐ ๊ธฐํ›„ ํ˜•์„ฑ $\rightarrow$ ์‚ฌ๋ง‰/์‚ฌ๊ตฌ ์ƒ์„ฑ.
44
+
45
+ ### ๐Ÿ”— Chain B. [๋น™ํ•˜] $\leftrightarrow$ [ํ‰์•ผ]: ์œต๋น™์ˆ˜์™€ ๋ฒ”๋žŒ (Meltwater Pulse)
46
+ * **Logic:** ๋น™ํ•˜๊ธฐ๊ฐ€ ๋๋‚˜๊ณ  ๊ฐ„๋น™๊ธฐ๊ฐ€ ์˜ค๋ฉด ๋ง‰๋Œ€ํ•œ ๋ฌผ์ด ํ‰์•ผ๋กœ ์Ÿ์•„์ง.
47
+ * **Process:**
48
+ 1. **[๋น™ํ•˜]** ๋ชจ๋“ˆ: ๊ธฐ์˜จ ์ƒ์Šน($T \uparrow$)์œผ๋กœ ๋น™ํ•˜ ํ›„ํ‡ด.
49
+ 2. **[์ˆ˜๋ฌธ]** ์—”์ง„: ์œต๋น™์ˆ˜ ์œ ์ž…๋Ÿ‰($Q_{melt}$) ๊ธ‰์ฆ.
50
+ 3. **[ํ‰์•ผ]** ๋ชจ๋“ˆ ํŠธ๋ฆฌ๊ฑฐ: ํ•˜์ฒœ ์œ ๋Ÿ‰ ํญ์ฆ $\rightarrow$ ๋ฒ”๋žŒ์› ํ™•๋Œ€, ๋ฐฐํ›„์Šต์ง€ ํ˜•์„ฑ, ํ•˜๊ตฌ ์‚ผ๊ฐ์ฃผ ์„ฑ์žฅ ๊ฐ€์†.
51
+
52
+ ### ๐Ÿ”— Chain C. [ํ™”์‚ฐ] $\leftrightarrow$ [์นด๋ฅด์ŠคํŠธ]: ์‚ฐ์„ฑ๊ณผ ์—ผ๊ธฐ์„ฑ (Chemical Weathering)
53
+ * **Logic:** ํ™”์‚ฐ ํ™œ๋™์œผ๋กœ ์ธํ•œ ์‚ฐ์„ฑ๋น„๊ฐ€ ์„ํšŒ์•” ์šฉ์‹์„ ๊ฐ€์†ํ™”.
54
+ * **Process:**
55
+ 1. **[ํ™”์‚ฐ]** ๋ชจ๋“ˆ: ํ™”์‚ฐ ํญ๋ฐœ๋กœ ๋Œ€๊ธฐ ์ค‘ $SO_2, CO_2$ ๋†๋„ ์ฆ๊ฐ€.
56
+ 2. **[ํ™˜๊ฒฝ]** ์—”์ง„: ๋น—๋ฌผ์˜ $pH$ ๋†๋„ ํ•˜๋ฝ (์‚ฐ์„ฑ๋น„).
57
+ 3. **[์นด๋ฅด์ŠคํŠธ]** ๋ชจ๋“ˆ ํŠธ๋ฆฌ๊ฑฐ: ๊ธฐ๋ฐ˜์•”์ด ์„ํšŒ์•”์ธ ์ง€์—ญ์˜ ์šฉ์‹ ์†๋„($Rate_{dissolution}$) 2๋ฐฐ ๊ฐ€์† $\rightarrow$ ๊ฑฐ๋Œ€ ๋™๊ตด ์ƒ์„ฑ.
58
+
59
+ ### ๐Ÿ”— Chain D. [๋น™ํ•˜] $\leftrightarrow$ [ํ•ด์•ˆ]: ํ”ผ์˜ค๋ฅด์˜ ํƒ„์ƒ (Fjord Formation)
60
+ * **Logic:** ๋น™ํ•˜๊ฐ€ ๊นŽ์•„๋‚ธ U์ž๊ณก์— ๋ฐ”๋‹ท๋ฌผ์ด ๋“ค์–ด์ฐจ ๊นŠ๊ณ  ์ข์€ ๋งŒ์„ ํ˜•์„ฑ.
61
+ * **Process:**
62
+ 1. **[๋น™ํ•˜]** ๋ชจ๋“ˆ: ๋น™ํ•˜๊ธฐ์— ํ•ด์•ˆ๊นŒ์ง€ ๋„๋‹ฌํ•˜๋Š” ๊ฑฐ๋Œ€ ๋น™ํ•˜๊ฐ€ U์ž๊ณก์„ ๊นŠ๊ฒŒ ์นจ์‹.
63
+ 2. **[๊ธฐํ›„]** ์—”์ง„: ๊ฐ„๋น™๊ธฐ ๋„๋ž˜๋กœ ํ•ด์ˆ˜๋ฉด ์ƒ์Šน($H_{sea} \uparrow$).
64
+ 3. **[ํ•ด์•ˆ]** ๋ชจ๋“ˆ ํŠธ๋ฆฌ๊ฑฐ: ๋ฐ”๋‹ท๋ฌผ์ด U์ž๊ณก์œผ๋กœ ์นจํˆฌ $\rightarrow$ ํ”ผ์˜ค๋ฅด(Fjord) ํ˜•์„ฑ.
65
+
66
+ ### ๐Ÿ”— Chain E. [ํ™”์‚ฐ] $\leftrightarrow$ [ํ•ด์•ˆ]: ํ˜„๋ฌด์•” ํ•ด์•ˆ (Volcanic Coast)
67
+ * **Logic:** ์šฉ์•”์ด ๋ฐ”๋‹ค์— ๋‹ฟ์œผ๋ฉฐ ๊ธ‰๊ฒฉํžˆ ๋ƒ‰๊ฐ๋˜์–ด ๋…ํŠนํ•œ ํ•ด์•ˆ ์ง€ํ˜• ํ˜•์„ฑ.
68
+ * **Process:**
69
+ 1. **[ํ™”์‚ฐ]** ๋ชจ๋“ˆ: ํ•ด์•ˆ ์ธ๊ทผ์—์„œ ํ˜„๋ฌด์•”์งˆ ์šฉ์•” ๋ถ„์ถœ.
70
+ 2. **[๋ƒ‰๊ฐ]** ์—”์ง„: ๋ฐ”๋‹ท๋ฌผ๊ณผ ์ ‘์ด‰ํ•˜๋ฉฐ ๊ธ‰๋žญ $\rightarrow$ ์ฃผ์ƒ์ ˆ๋ฆฌ ํ˜•์„ฑ.
71
+ 3. **[ํ•ด์•ˆ]** ๋ชจ๋“ˆ ํŠธ๋ฆฌ๊ฑฐ: ์ฃผ์ƒ์ ˆ๋ฆฌ ํ•ด์•ˆ ์ ˆ๋ฒฝ ์ƒ์„ฑ (์˜ˆ: ์ œ์ฃผ ์ค‘๋ฌธ ๋Œ€ํฌํ•ด์•ˆ).
72
+
73
+ ### ๐Ÿ”— Chain F. [ํ•˜์ฒœ] $\leftrightarrow$ [ํ•ด์•ˆ]: ์‚ผ๊ฐ์ฃผ vs ์‚ผ๊ฐ๊ฐ• (Delta vs Estuary)
74
+ * **Logic:** ํ•˜๊ตฌ์—์„œ ํ•˜์ฒœ ์—๋„ˆ์ง€์™€ ํ•ด์–‘ ์—๋„ˆ์ง€์˜ ์ƒ๋Œ€์  ํฌ๊ธฐ์— ๋”ฐ๋ผ ํ‡ด์  ๋˜๋Š” ์นจ์‹ ๋ฐœ์ƒ.
75
+ * **Process:**
76
+ 1. **[ํ•˜์ฒœ]** ๋ชจ๋“ˆ: ํ•˜๊ตฌ์— ํ‡ด์ ๋ฌผ ๊ณต๊ธ‰ (Sediment Load).
77
+ 2. **[ํ•ด์•ˆ]** ์—”์ง„: ํŒŒ๋ž‘/์กฐ๋ฅ˜์˜ ํ‡ด์ ๋ฌผ ์žฌ๋ถ„๋ฐฐ ๋Šฅ๋ ฅ ๊ณ„์‚ฐ.
78
+ 3. **๊ฒฐ๊ณผ:** ํ•˜์ฒœ ์šฐ์„ธ โ†’ ์‚ผ๊ฐ์ฃผ(Delta), ์กฐ๋ฅ˜ ์šฐ์„ธ โ†’ ์‚ผ๊ฐ๊ฐ•(Estuary).
79
+
80
+ ---
81
+
82
+ ## 3. ์บ ํŽ˜์ธ ์‹œ๋‚˜๋ฆฌ์˜ค (Campaign Mode Missions)
83
+
84
+ ํ•™์ƒ๋“ค์ด ์ง€ํ˜• ํ˜•์„ฑ ์›๏ฟฝ๏ฟฝ๏ฟฝ๋ฅผ ๊ฒŒ์ž„์ฒ˜๋Ÿผ ์ตํž ์ˆ˜ ์žˆ๋Š” ๋ฏธ์…˜ ๋ชฉ๋ก.
85
+
86
+ | ์‹œ๋‚˜๋ฆฌ์˜ค (Title) | ๋ชฉํ‘œ (Mission Objective) | ํ•„์š” ๋ชจ๋“ˆ ์กฐํ•ฉ | ๋‚œ์ด๋„ |
87
+ | :--- | :--- | :--- | :--- |
88
+ | **๋ถˆ๊ณผ ์–ผ์Œ์˜ ๋…ธ๋ž˜** | ํ™”์‚ฐ ํญ๋ฐœ๋กœ ์ƒ๊ธด ์‚ฐ ์ •์ƒ์— ๋งŒ๋…„์„ค๊ณผ ๋น™ํ•˜๋ฅผ ํ˜•์„ฑํ•˜๋ผ. (์•„์ด์Šฌ๋ž€๋“œ, ํ‚ฌ๋ฆฌ๋งŒ์ž๋กœ ํ˜•) | **ํ™”์‚ฐ + ๋น™ํ•˜** | โญโญโญ |
89
+ | **์‚ฌ๋ง‰์˜ ๊ธฐ์ ** | ๊ฑด์กฐํ•œ ์‚ฌ๋ง‰์— ์™ธ๋ž˜ ํ•˜์ฒœ์„ ํ๋ฅด๊ฒŒ ํ•˜์—ฌ ํ•˜๊ตฌ์— ๊ฑฐ๋Œ€ ์‚ผ๊ฐ์ฃผ๋ฅผ ๊ฑด์„คํ•˜๋ผ. (์ด์ง‘ํŠธ ๋‚˜์ผ๊ฐ• ํ˜•) | **๊ฑด์กฐ + ํ‰์•ผ** | โญโญโญโญ |
90
+ | **์‹œ๊ฐ„์˜ ๋™๊ตด** | ์œต๊ธฐ๋œ ์‚ฐ์ง€๋ฅผ ์นจ์‹์‹œ์ผœ ์ง€ํ•˜์ˆ˜๋ฉด์„ ๋‚ฎ์ถ”๊ณ , ์„ํšŒ๋™๊ตด์„ ์œก์ƒ์— ๋…ธ์ถœ์‹œ์ผœ๋ผ. (๋‹จ์–‘ ๊ณ ์ˆ˜๋™๊ตด ํ˜•) | **์‚ฐ์ง€ + ์นด๋ฅด์ŠคํŠธ** | โญโญ |
91
+ | **๋Œ€ํ™์ˆ˜ (The Flood)** | ๋น™ํ•˜๊ธฐ๋ฅผ ๋๋‚ด๊ณ  ํ•ด์ˆ˜๋ฉด์„ 100m ์ƒ์Šน์‹œ์ผœ, U์ž๊ณก์„ ํ”ผ์˜ค๋ฅด๋กœ, ํ•˜๊ตฌ๋ฅผ ์„ํ˜ธ๋กœ ๋งŒ๋“ค์–ด๋ผ. | **๋น™ํ•˜ + ํ‰์•ผ + ํ•ด์•ˆ** | โญโญโญโญโญ |
92
+
93
+ ---
94
+
95
+ ## 4. UI/UX: ๋งˆ์Šคํ„ฐ ๋Œ€์‹œ๋ณด๋“œ (Master Dashboard Design)
96
+
97
+ ### ๐Ÿ–ฅ๏ธ Viewport Layout
98
+ 1. **Main Globe (Center):** 3D ์ง€๊ตฌ๋ณธ ๋ทฐ. ํœ  ์Šคํฌ๋กค๋กœ ์šฐ์ฃผ(์œ„์„ฑ ์‚ฌ์ง„)์—์„œ ์ง€ํ•˜(๋‹จ๋ฉด๋„)๊นŒ์ง€ ์‹ฌ๋ฆฌ์Šค ์คŒ์ธ/์•„์›ƒ.
99
+ 2. **Control Deck (Bottom):** ํƒ€์ž„๋ผ์ธ ์Šฌ๋ผ์ด๋”. (๊ณ ์ƒ๋Œ€ $\leftrightarrow$ ํ˜„์„ธ). ์‹œ๊ฐ„์„ ๋Œ๋ฆฌ๋ฉฐ ์ง€ํ˜• ๋ณ€ํ™” ๊ด€์ฐฐ.
100
+ 3. **Mini-Map (Right-Top):** ํ˜„์žฌ ์ง€ํ˜•์˜ ํ˜•์„ฑ ์›๋ฆฌ(์ž‘์šฉ)๋ฅผ ์•„์ด์ฝ˜์œผ๋กœ ํ‘œ์‹œ (๐ŸŒŠ์œ ์ˆ˜, ๐ŸŒฌ๏ธ๋ฐ”๋žŒ, ๐ŸŒ‹๋งˆ๊ทธ๋งˆ, โ„๏ธ๋น™ํ•˜).
101
+
102
+ ### ๐Ÿ“Š Data Visualization
103
+ * **Cross-Section Overlay:** ์ง€ํ‘œ๋ฉด ์•„๋ž˜ ์ง€์งˆ ๊ตฌ์กฐ(์Šต๊ณก, ๋‹จ์ธต, ์ง€ํ•˜์ˆ˜, ๋งˆ๊ทธ๋งˆ ๋ฐฉ)๋ฅผ ์—‘์Šค๋ ˆ์ด์ฒ˜๋Ÿผ ํˆฌ์‹œ.
104
+ * **Eco-System Status:** ํ˜„์žฌ ์ง€ํ˜•์— ์ ํ•ฉํ•œ ๋†์—…(๋ฒผ๋†์‚ฌ vs ๋ฐญ๋†์‚ฌ vs ๋ชฉ์ถ•) ๋ฐ ๊ฑฐ์ฃผ ์ ํ•ฉ๋„ ์ ์ˆ˜ ํ‘œ์‹œ.
105
+
106
+ ---
107
+
108
+ ## 5. ๋ฐ์ดํ„ฐ ํ†ต์‹  ๊ทœ๊ฒฉ (JSON Data Structure)
109
+
110
+ ํ†ตํ•ฉ ์—”์ง„์ด ์ฒ˜๋ฆฌํ•  ์ตœ์ข… ์›”๋“œ ์ƒํƒœ ๋ฐ์ดํ„ฐ ์˜ˆ์‹œ.
111
+
112
+ ```json
113
+ {
114
+ "World_State": {
115
+ "Timestamp": "Epoch_Holocene_Early",
116
+ "Global_Temp": 14.5,
117
+ "Sea_Level_Base": 0
118
+ },
119
+ "Active_Regions": [
120
+ {
121
+ "Region_ID": "NE_Asia",
122
+ "Base_Rock": "Granite",
123
+ "Landforms": [
124
+ {"Type": "Mountain", "Age": "Old", "Feature": "Erosion_Dome"},
125
+ {"Type": "Plain", "Feature": "Alluvial_Fan", "Source": "Mountain_Runoff"}
126
+ ],
127
+ "Climate_Effect": "Monsoon"
128
+ },
129
+ {
130
+ "Region_ID": "Pacific_Rim",
131
+ "Base_Rock": "Basalt",
132
+ "Landforms": [
133
+ {"Type": "Volcano", "Shape": "Shield", "Status": "Active"}
134
+ ],
135
+ "Tectonic_Activity": "High"
136
+ }
137
+ ]
138
+ }
01_River_Landforms_Spec.md ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ๐Ÿ“˜ [Geo-Lab AI] ๊ฐœ๋ฐœ ๋ฐฑ์„œ (Development White Paper)
2
+
3
+ **Project Name:** Geo-Lab AI
4
+ **Version:** Final Release 1.0
5
+ **Module:** Fluvial Landforms (ํ•˜์ฒœ ์ง€ํ˜•)
6
+ **Target:** High School Geography ~ Undergraduate Geomorphology
7
+ **Director:** [User Name]
8
+
9
+ ---
10
+
11
+ ## 0. ๊ฐœ๋ฐœ ํ‘œ์ค€ ๋ฐ ์ œ์•ฝ์‚ฌํ•ญ (Standard Protocols)
12
+
13
+ ### ๐ŸŽจ 1. ๋น„์ฃผ์–ผ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ (Visual Style: Schematic Education)
14
+ * **Tone:** Clean, Minimalist, Textbook-style 3D. (๊ตฐ๋”๋”๊ธฐ ์—†๋Š” ๊น”๋”ํ•œ ๊ต๊ณผ์„œ ์‚ฝํ™” ์Šคํƒ€์ผ)
15
+ * **Color Palette:**
16
+ * *Water:* ๋ฐ˜ํˆฌ๋ช…ํ•œ ๋ฐ์€ ํŒŒ๋ž‘ (๋‚ด๋ถ€ ์œ ์† ํ๋ฆ„ ๊ฐ€์‹œํ™”).
17
+ * *Sediment:* ์ž…์ž ํฌ๊ธฐ๋ณ„ ์ƒ‰์ƒ ๊ตฌ๋ถ„ (์ž๊ฐˆ=ํšŒ์ƒ‰, ๋ชจ๋ž˜=๋…ธ๋ž‘, ์ ํ† =๊ฐˆ์ƒ‰).
18
+ * *Bedrock:* ์ธต๋ฆฌ(Layer)๊ฐ€ ๋ช…ํ™•ํžˆ ๋ณด์ด๋Š” ๋‹จ๋ฉด ํ…์Šค์ฒ˜.
19
+ * **Key Feature:** ๋ชจ๋“  ์ง€ํ˜•์€ '์ผ€์ดํฌ ์ž๋ฅด๋“ฏ' ๋‹จ๋ฉด์„ ๋ณด์—ฌ์ฃผ๋Š” **Cutaway View**๊ฐ€ ๊ธฐ๋ณธ ์ง€์›๋˜์–ด์•ผ ํ•จ.
20
+
21
+ ### ๐Ÿ› ๏ธ 2. ๊ธฐ์ˆ  ์Šคํƒ (Tech Stack)
22
+ * **Backend:** Python 3.9+ (Libraries: `NumPy`, `SciPy`, `Taichi` for Physics).
23
+ * **Frontend:** WebGL via `Three.js` or `PyVista`.
24
+ * **App Framework:** `Streamlit` (Web-based Interface).
25
+
26
+ ---
27
+
28
+ ## 1. ํ•˜์ฒœ ์ง€ํ˜• ๋ชจ๋“ˆ ์„ธ๋ถ€ ๋ช…์„ธ (Module Specifications)
29
+
30
+ ### ๐ŸŒŠ Chapter 1. ์ƒ๋ฅ˜ (Upper Course): ํŒŒ๊ดด์™€ ๊ฐœ์ฒ™
31
+ **ํ•ต์‹ฌ ๋กœ์ง:** ํ•˜๋ฐฉ ์นจ์‹(Vertical Erosion), ๋‘๋ถ€ ์นจ์‹(Headward Erosion), ์‚ฌ๋ฉด ๋ถ•๊ดด.
32
+
33
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
34
+ | :--- | :--- | :--- | :--- |
35
+ | **V์ž๊ณก** | ์นจ์‹๋ ฅ($E$), ์•”์„ ๊ฐ•๋„($K$), ๊ฒฝ์‚ฌ($S$) | ๋ฐ”๋‹ฅ ํ•˜๋ฐฉ ์นจ์‹ ํ›„ **์–‘์˜† ์‚ฌ๋ฉด์ด ๋ถ•๊ดด(Mass Wasting)**๋˜์–ด V์ž๊ฐ€ ์™„์„ฑ๋˜๋Š” ๊ณผ์ • ๊ฐ•์กฐ. | $E = K \cdot A^m \cdot S^n$ (Stream Power Law) |
36
+ | **ํญํฌ & ํ˜‘๊ณก** | ์œ ๋Ÿ‰($Q$), ๋‚™์ฐจ ๋†’์ด($h$) | ํ•˜๋‹จ๋ถ€ ๊ตด์ฐฉ(Undercutting) โ†’ ์ƒ๋‹จ ๋ถ•๊ดด โ†’ ํญํฌ๊ฐ€ ๋’ค๋กœ ๋ฌผ๋Ÿฌ๋‚˜๋ฉฐ **ํ˜‘๊ณก(Gorge)** ํ˜•์„ฑ. | Crosby's Knickpoint Retreat Model |
37
+ | **ํ•˜์ฒœ ์Ÿํƒˆ** | ์นจ์‹ ์†๋„ ์ฐจ์ด($\Delta E$), ๊ฑฐ๋ฆฌ | ํ•œ์ชฝ ๊ฐ•์ด ๋‘๋ถ€ ์นจ์‹์œผ๋กœ ๋‹ค๋ฅธ ๊ฐ•์˜ ์˜†๊ตฌ๋ฆฌ๋ฅผ ํ„ฐํŠธ๋ ค ๋ฌผ์„ ๋บ์–ด์˜ค๊ณ , ๋บ๊ธด ๊ฐ•์ด ๋ง๋ผ๊ฐ€๋Š” ๊ณผ์ •. | Stream Capture Ratio |
38
+ | **๋Œ๊ฐœ๊ตฌ๋ฉ** | ์™€๋ฅ˜ ๊ฐ•๋„(Vortex), ์ž๊ฐˆ ๊ฒฝ๋„ | **[X-ray View]** ๊ฐ•๋ฐ”๋‹ฅ ํˆฌ์‹œ. ์ž๊ฐˆ์ด ์†Œ์šฉ๋Œ์ด์น˜๋ฉฐ ์•”๋ฐ˜์„ ๋“œ๋ฆด์ฒ˜๋Ÿผ ๋šซ๋Š” ๋‚ด๋ถ€ ๋ชจ์Šต. | Abrasion Rate $\propto$ Kinetic Energy |
39
+ | **๊ฐ์ž… ๊ณก๋ฅ˜ (์ƒ๋ฅ˜ํ˜•)** | ์•”์„ ์ €ํ•ญ์„ฑ, ์ตœ๋‹จ ๊ฒฝ๋กœ | ๋‹จ๋‹จํ•œ ์•”๋ฐ˜์„ ํ”ผํ•ด ๊ตฝ์ด์น˜๋ฉฐ ํ๋ฅด๋Š” ๋ฌผ๊ธธ๊ณผ ๊น์ง€ ๋‚€ ๋Šฅ์„ (Interlocking Spurs). | - |
40
+
41
+ ### โžฐ Chapter 2. ์ค‘๋ฅ˜ (Middle Course): ์šด๋ฐ˜๊ณผ ๊ณก๋ฅ˜
42
+ **ํ•ต์‹ฌ ๋กœ์ง:** ์ธก๋ฐฉ ์นจ์‹(Lateral Erosion), ๋ถ„๊ธ‰(Sorting), ์œ ์† ์ฐจ์ด์— ์˜ํ•œ ํ‡ด์ .
43
+
44
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
45
+ | :--- | :--- | :--- | :--- |
46
+ | **์„ ์ƒ์ง€** | ์ž…์ž ํฌ๊ธฐ($d$), ์œ ์† ๊ฐ์†Œ์œจ($-\Delta v$) | ์„ ์ •(์ž๊ฐˆ)-์„ ์•™(๋ชจ๋ž˜)-์„ ๋‹จ(์ ํ† ) ๋ถ„๊ธ‰ ๋ฐ **๋ณต๋ฅ˜์ฒœ(์ง€ํ•˜์ˆ˜ ํ๋ฆ„)** ๋‹จ๋ฉด๋„. | Stokes' Law (Settling Velocity) |
47
+ | **๊ณก๋ฅ˜ & ์šฐ๊ฐํ˜ธ** | ์›์‹ฌ๋ ฅ($F_c$), ์œ ๋กœ ๊ตด๊ณก๋„ | ๊ณต๊ฒฉ์‚ฌ๋ฉด(์ ˆ๋ฒฝ) vs ํ‡ด์ ์‚ฌ๋ฉด(๋ฐฑ์‚ฌ์žฅ). ํ™์ˆ˜ ์‹œ ์œ ๋กœ ์ ˆ๋‹จ(Cutoff) ๋ฐ ์šฐ๊ฐํ˜ธ ๊ณ ๋ฆฝ ์• ๋‹ˆ๋ฉ”์ด์…˜. | Helical Flow Model |
48
+ | **๊ฐ์ž… ๊ณก๋ฅ˜ (์œต๊ธฐํ˜•)** | ์œต๊ธฐ ์†๋„($U$), ๊ธฐ์กด ๊ตด๊ณก๋„ | **[Comparison Mode]** ์ž์œ  ๊ณก๋ฅ˜(์ ‘์‹œํ˜• ๋‹จ๋ฉด) vs ๊ฐ์ž… ๊ณก๋ฅ˜(๋ฐฅ๊ทธ๋ฆ‡ํ˜• ๊นŠ์€ ๋‹จ๋ฉด) ๋น„๊ต. | Incision Rate = Erosion - Uplift |
49
+ | **ํ•˜์•ˆ๋‹จ๊ตฌ** | ์ง€๋ฐ˜ ์œต๊ธฐ($U$), ๊ธฐ์ €๋ฉด ๋ณ€๋™ | ์œต๊ธฐ ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์‹œ ๊ฐ•๋ฐ”๋‹ฅ์ด ๊นŠ์–ด์ง€๋ฉฐ ๋ฒ”๋žŒ์›์ด ๊ณ„๋‹จ ๋ชจ์–‘ ์–ธ๋•(Terrace)์œผ๋กœ ๋‚จ์Œ. | - |
50
+ | **๋ฒ”๋žŒ์›** | ํ™์ˆ˜ ๋นˆ๋„, ๋ถ€์œ  ํ•˜์ค‘ | ํ™์ˆ˜ ์‹œ ์ œ๋ฐฉ(๋‘๊บผ์›€/๋ชจ๋ž˜)๊ณผ ๋ฐฐํ›„์Šต์ง€(์–‡์Œ/์ ํ† )์˜ ๊ฑฐ๋ฆฌ๋ณ„ ํ‡ด์  ์ฐจ์ด. | Overbank Sedimentation |
51
+
52
+ ### โš“ Chapter 3. ํ•˜๋ฅ˜ (Lower Course): ๋ฐ”๋‹ค์™€์˜ ๊ท ํ˜•
53
+ **ํ•ต์‹ฌ ๋กœ์ง:** ์œ ์† ์†Œ๋ฉธ, ํŒŒ๋ž‘/์กฐ๋ฅ˜ ์—๋„ˆ์ง€ ์ƒํ˜ธ์ž‘์šฉ.
54
+
55
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
56
+ | :--- | :--- | :--- | :--- |
57
+ | **์‚ผ๊ฐ์ฃผ** | ๊ฐ• ์—๋„ˆ์ง€ vs ํŒŒ๋ž‘ vs ์กฐ๋ฅ˜ | **[Triangle Mixer UI]** 3๊ฐ€์ง€ ํž˜ ์กฐ์ ˆ โ†’ ์กฐ์กฑ์ƒ(๋ฏธ์‹œ์‹œํ”ผ) โ†” ์›ํ˜ธ์ƒ(๋‚˜์ผ) ๋ชจ์–‘ ๋ณ€ํ™˜. | Galloway's Classification |
58
+ | **์‚ผ๊ฐ๊ฐ• (Estuary)** | ์กฐ์ฐจ(Tidal Range), ์กฐ๋ฅ˜ ์†๋„ | ๊ฐ•ํ•œ ์กฐ๋ฅ˜๊ฐ€ ํ‡ด์ ๋ฌผ์„ ์“ธ์–ด๊ฐ€๋ฉฐ ํ•˜๊ตฌ๊ฐ€ ๋‚˜ํŒ” ๋ชจ์–‘์œผ๋กœ ํ™•์žฅ๋˜๋Š” ๊ณผ์ •. | Tidal Scour Equation |
59
+ | **ํ•˜์ค‘๋„** | ์œ ์† ์‚ฌ๊ฐ์ง€๋Œ€, ์‹์ƒ ๋ฐ€๋„ | ๊ฐ•ํญ์ด ๋„“์€ ๊ณณ์— ๋ชจ๋ž˜์„ฌ ํ˜•์„ฑ ํ›„ ์‹์ƒ์ด ์ž๋ผ๋ฉฐ ๊ณ ์ •๋˜๋Š” ๊ณผ์ •. | Vegetation Stabilization |
60
+
61
+ ---
62
+
63
+ ## 2. ์ •๋‹ต์ง€ ์ด๋ฏธ์ง€ ๋ชฉ๋ก (Reference Images to Load)
64
+ *AI ํ•™์Šต ์‹œ ์•„๋ž˜ ํŒŒ์ผ๋ช…๊ณผ ๋งค์นญ๋˜๋Š” ์ด๋ฏธ์ง€๋ฅผ ํ•จ๊ป˜ ์—…๋กœ๋“œํ•  ๊ฒƒ.*
65
+
66
+ 1. `v_shaped_valley_diagram.jpg` (V์ž๊ณก ๋ฐ ์‚ฌ๋ฉด ๋ถ•๊ดด ๋‹จ๋ฉด)
67
+ 2. `alluvial_fan_structure.jpg` (์„ ์ƒ์ง€ ํ‰๋ฉด ๋ฐ ์ง€ํ•˜ ๋‹จ๋ฉด)
68
+ 3. `meander_cross_section.jpg` (๊ณต๊ฒฉ์‚ฌ๋ฉด/ํ‡ด์ ์‚ฌ๋ฉด ๋น„๋Œ€์นญ ๋‹จ๋ฉด)
69
+ 4. `river_terrace_formation.jpg` (ํ•˜์•ˆ๋‹จ๊ตฌ ํ˜•์„ฑ ๋‹จ๊ณ„)
70
+ 5. `delta_classification.jpg` (์‚ผ๊ฐ์ฃผ 3๊ฐ€์ง€ ์œ ํ˜• ๋น„๊ต)
02_Coastal_Landforms_Spec.md ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ๐Ÿ“˜ [Geo-Lab AI] ๊ฐœ๋ฐœ ๋ฐฑ์„œ (Development White Paper)
2
+
3
+ **Project Name:** Geo-Lab AI
4
+ **Version:** Final Release 1.0
5
+ **Module:** Coastal Landforms (ํ•ด์•ˆ ์ง€ํ˜•)
6
+ **Target:** High School Geography ~ Undergraduate Geomorphology
7
+ **Director:** [User Name]
8
+
9
+ ---
10
+
11
+ ## 0. ๊ฐœ๋ฐœ ํ‘œ์ค€ ๋ฐ ์ œ์•ฝ์‚ฌํ•ญ (Standard Protocols)
12
+
13
+ ### ๐ŸŽจ 1. ๋น„์ฃผ์–ผ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ (Visual Style: Schematic Education)
14
+ * **Tone:** Clean, Minimalist, Textbook-style 3D.
15
+ * **Color Palette:**
16
+ * *Sea:* ๊นŠ์ด์— ๋”ฐ๋ฅธ ํŒŒ๋ž€์ƒ‰ ๊ทธ๋ผ๋ฐ์ด์…˜ (์–•์€ ๊ณณ=ํ•˜๋Š˜์ƒ‰, ๊นŠ์€ ๊ณณ=๋‚จ์ƒ‰).
17
+ * *Sand:* ๋ฐ์€ ๋ฒ ์ด์ง€์ƒ‰ (์ด๋™ ๊ฒฝ๋กœ๊ฐ€ ์ž˜ ๋ณด์—ฌ์•ผ ํ•จ).
18
+ * *Rocks:* ์ง™์€ ํšŒ์ƒ‰/๊ฐˆ์ƒ‰ (์ ˆ๋ฒฝ์˜ ์งˆ๊ฐ ํ‘œํ˜„).
19
+ * **Key Feature:** ํŒŒ๋„์˜ ๊ตด์ ˆ๊ณผ ๋ชจ๋ž˜์˜ ์ด๋™ ๊ฒฝ๋กœ(Vector Arrow)๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œ์‹œ.
20
+
21
+ ### ๐Ÿ› ๏ธ 2. ๊ธฐ์ˆ  ์Šคํƒ (Tech Stack)
22
+ * **Backend:** Python 3.9+ (Libraries: `NumPy`, `SciPy`, `Taichi`).
23
+ * **Frontend:** WebGL via `Three.js` or `PyVista`.
24
+ * **App Framework:** `Streamlit`.
25
+
26
+ ---
27
+
28
+ ## 1. ํ•ด์•ˆ ์ง€ํ˜• ๋ชจ๋“ˆ ์„ธ๋ถ€ ๋ช…์„ธ (Module Specifications)
29
+
30
+ ### ๐ŸŒŠ Chapter 1. ๊ณถ(Headland)๊ณผ ์นจ์‹: ํŒŒ๋„์˜ ๊ณต๊ฒฉ
31
+ **ํ•ต์‹ฌ ๋กœ์ง:** **ํŒŒ๋ž‘์˜ ๊ตด์ ˆ (Wave Refraction)**. ํŒŒ๋„์˜ ์—๋„ˆ์ง€๊ฐ€ ํŠ€์–ด๋‚˜์˜จ ๋•…(๊ณถ)์œผ๋กœ ์ง‘์ค‘๋จ.
32
+
33
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
34
+ | :--- | :--- | :--- | :--- |
35
+ | **ํ•ด์‹์•  & ํŒŒ์‹๋Œ€** | ํŒŒ๋ž‘ ์—๋„ˆ์ง€($P$), ์•”์„ ๊ฒฝ๋„ | ํŒŒ๋„๊ฐ€ ์ ˆ๋ฒฝ ๋ฐ‘์„ ๋•Œ๋ ค(Notch) ์œ—๋ถ€๋ถ„์ด ๋ฌด๋„ˆ์ง€๊ณ , ์ ˆ๋ฒฝ์ด ํ›„ํ‡ดํ•˜๋ฉฐ ํ‰ํ‰ํ•œ ๋ฐ”๋‹ฅ(ํŒŒ์‹๋Œ€) ๋…ธ์ถœ. | Cliff Retreat Rate $\propto P$ ($P \propto H^2T$) |
36
+ | **์‹œ์Šคํƒ & ์•„์น˜** | ์ฐจ๋ณ„ ์นจ์‹, ์‹œ๊ฐ„($t$) | **[Evolution Time-lapse]** ํ•ด์‹๋™๊ตด โ†’ ์•„์น˜(๊ตฌ๋ฉ) โ†’ ์‹œ์Šคํƒ(๊ธฐ๋‘ฅ) โ†’ ์‹œ์Šคํ…€ํ”„(๋ฐ”์œ„) 4๋‹จ๊ณ„ ๋ณ€ํ™”. | - |
37
+ | **ํ•ด์•ˆ๋‹จ๊ตฌ** | ์ง€๋ฐ˜ ์œต๊ธฐ($U$), ํ•ด์ˆ˜๋ฉด ๋ณ€๋™ | ํŒŒ์‹๋Œ€๊ฐ€ ์œต๊ธฐํ•˜์—ฌ ๊ณ„๋‹จ ๋ชจ์–‘์˜ ์–ธ๋•์ด ๋˜๋Š” ๊ณผ์ • (๊ฐ•์˜ ํ•˜์•ˆ๋‹จ๊ตฌ์™€ ๋น„๊ต). | Uplift Event Trigger |
38
+
39
+ ### ๐Ÿ–๏ธ Chapter 2. ๋งŒ(Bay)๊ณผ ํ‡ด์ : ๋ชจ๋ž˜์˜ ์—ฌํ–‰
40
+ **ํ•ต์‹ฌ ๋กœ์ง:** **์—ฐ์•ˆ๋ฅ˜ (Longshore Drift)**. ํŒŒ๋„๊ฐ€ ๋น„์Šค๋“ฌํžˆ ์น  ๋•Œ ๋ชจ๋ž˜๊ฐ€ ์ง€๊ทธ์žฌ๊ทธ๋กœ ์ด๋™.
41
+
42
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
43
+ | :--- | :--- | :--- | :--- |
44
+ | **์‚ฌ๋นˆ (Beach)** | ํŒŒ๋ž‘ ์ž…์‚ฌ๊ฐ($\theta$), ์ž…์ž ํฌ๊ธฐ | ๋งŒ(Bay) ์•ˆ์ชฝ์œผ๋กœ ์—๋„ˆ์ง€๊ฐ€ ๋ถ„์‚ฐ๋˜๋ฉฐ ๋ชจ๋ž˜๊ฐ€ ์Œ“์ž„. ์—ฌ๋ฆ„(ํ‡ด์ ) vs ๊ฒจ์šธ(์นจ์‹) ํ•ด๋ณ€ ๋น„๊ต. | Sediment Transport (CERC Formula) |
45
+ | **์‚ฌ๊ตฌ (Sand Dune)** | ํ’์†($V_{wind}$), ์‹์ƒ ๋ฐ€๋„ | ์‚ฌ๋นˆ์˜ ๋ชจ๋ž˜๊ฐ€ ๋ฐ”๋žŒ์— ๋‚ ๋ ค ์ด๋™. **[Windbreak Effect]** ์‹์ƒ ์„ค์น˜ ์‹œ ์‚ฌ๊ตฌ ์„ฑ์žฅ ์‹œ๋ฎฌ๋ ˆ์ด์…˜. | Aeolian Transport Rate |
46
+ | **์‚ฌ์ทจ & ์‚ฌ์ฃผ** | ์—ฐ์•ˆ๋ฅ˜ ์†๋„, ํ•ด์•ˆ์„  ๊ตด๊ณก | ๋ชจ๋ž˜๊ฐ€ ๋ฐ”๋‹ค ์ชฝ์œผ๋กœ ๋ป—์–ด ๋‚˜๊ฐ€๋ฉฐ(์‚ฌ์ทจ) ๋งŒ์˜ ์ž…๊ตฌ๋ฅผ ๋ง‰์•„๋ฒ„๋ฆฌ๋Š”(์‚ฌ์ฃผ) ๊ณผ์ •. | - |
47
+ | **์„ํ˜ธ (Lagoon)** | ์‚ฌ์ฃผ ํ˜•์„ฑ ์—ฌ๋ถ€, ์œ ์ž… ์ˆ˜๋Ÿ‰ | ์‚ฌ์ฃผ๊ฐ€ ๋ฐ”๋‹ค๋ฅผ ๋ง‰์•„ ํ˜ธ์ˆ˜๊ฐ€ ๋˜๊ณ , ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉฐ ํ‡ด์ ๋ฌผ๋กœ ๋ฉ”์›Œ์ ธ ๋Šช์ด ๋˜๋Š” ์ƒ์• ์ฃผ๊ธฐ. | Water Balance Equation |
48
+
49
+ ### ๐Ÿฆ€ Chapter 3. ์กฐ๋ฅ˜์™€ ๊ฐฏ๋ฒŒ: ๋‹ฌ์˜ ์ธ๋ ฅ
50
+ **ํ•ต์‹ฌ ๋กœ์ง:** **์กฐ์„ ์ฃผ๊ธฐ (Tidal Cycle)** & ๋ฏธ๋ฆฝ์งˆ ํ‡ด์ .
51
+
52
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
53
+ | :--- | :--- | :--- | :--- |
54
+ | **๊ฐฏ๋ฒŒ (Mudflat)** | ์กฐ์ฐจ(Tidal Range), ๋ฏธ๋ฆฝ์งˆ ๋น„์œจ | **[Tidal Simulation]** ๋ฐ€๋ฌผ/์ฐ๋ฌผ ์ˆ˜์œ„ ๋ณ€ํ™”์™€ ๊ฐฏ๊ณจ(Tidal Channel)์˜ ํ”„๋ž™ํƒˆ ๊ตฌ์กฐ ํ˜•์„ฑ. | Tidal Prism / Hydrodynamics |
55
+ | **๊ฐ„์ฒ™ ์‚ฌ์—…** | ์ œ๋ฐฉ ๊ฑด์„ค, ์‹œ๊ฐ„ ๊ฒฝ๊ณผ | ๊ฐฏ๋ฒŒ์— ๋‘‘์„ ์Œ“์€ ํ›„(User Action), ํ‡ด์ ๋ฌผ์ด ๋งˆ๋ฅด๊ณ  ์œก์ง€ํ™”๋˜๋ฉฐ ์—ผ๋ถ„์ด ๋น ์ง€๋Š” ๊ณผ์ •. | Soil Desalination Model |
56
+
57
+ ---
58
+
59
+ ## 2. ์ •๋‹ต์ง€ ์ด๋ฏธ์ง€ ๋ชฉ๋ก (Reference Images to Load)
60
+ *AI ํ•™์Šต ์‹œ ์•„๋ž˜ ํŒŒ์ผ๋ช…๊ณผ ๋งค์นญ๋˜๋Š” ์ด๋ฏธ์ง€๋ฅผ ํ•จ๊ป˜ ์—…๋กœ๋“œํ•  ๊ฒƒ.*
61
+
62
+ 1. `sea_cliff_erosion.jpg` (ํ•ด์‹์• , ํŒŒ์‹๋Œ€, ๋…ธ์น˜ ๊ตฌ์กฐ ๋‹จ๋ฉด)
63
+ 2. `sea_stack_formation.jpg` (๋™๊ตด-์•„์น˜-์‹œ์Šคํƒ ๋ณ€ํ™” ๊ณผ์ •)
64
+ 3. `sand_spit_bar_tombolo.jpg` (์‚ฌ์ทจ, ์‚ฌ์ฃผ, ์„ํ˜ธ ์ง€๋„ํ˜• ๊ทธ๋ฆผ)
65
+ 4. `tidal_flat_zonation.jpg` (๊ฐฏ๋ฒŒ ๋‹จ๋ฉด ๋ฐ ์กฐ์ˆ˜ ์ˆ˜์œ„)
03_Karst_Landforms_Spec.md ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ๐Ÿ“˜ [Geo-Lab AI] ๊ฐœ๋ฐœ ๋ฐฑ์„œ (Development White Paper)
2
+
3
+ **Project Name:** Geo-Lab AI
4
+ **Version:** Final Release 1.0
5
+ **Module:** Karst Landforms (์นด๋ฅด์ŠคํŠธ ์ง€ํ˜•)
6
+ **Target:** High School Geography ~ Undergraduate Geology
7
+ **Director:** [User Name]
8
+
9
+ ---
10
+
11
+ ## 0. ๊ฐœ๋ฐœ ํ‘œ์ค€ ๋ฐ ์ œ์•ฝ์‚ฌํ•ญ (Standard Protocols)
12
+
13
+ ### ๐ŸŽจ 1. ๋น„์ฃผ์–ผ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ (Visual Style: Subsurface & Texture)
14
+ * **Tone:** Cross-sectional, Chemically Reactive, Hydrological.
15
+ * **Color Palette:**
16
+ * *Limestone:* ๋ฐ์€ ํšŒ๋ฐฑ์ƒ‰ (๊ธฐ๋ฐ˜์•”).
17
+ * *Soil (Terra Rossa):* ๋ถ‰์€ ๊ฐˆ์ƒ‰ (์‚ฐํ™”์ฒ  ์„ฑ๋ถ„ ๊ฐ•์กฐ).
18
+ * *Water:* ํˆฌ๋ช…ํ•œ ์ฒญ๋ก์ƒ‰ (์ง€ํ•˜์ˆ˜ ์œ„์ฃผ).
19
+ * *Vegetation:* ์ง™์€ ๋…น์ƒ‰ (๋Œ๋ฆฌ๋„ค ๋‚ด๋ถ€ ์‹์ƒ).
20
+ * **Key Feature:** ์ง€ํ‘œ๋ฉด(Surface)๊ณผ ์ง€ํ•˜(Subsurface)๋ฅผ ๋™์‹œ์— ๋ณด์—ฌ์ฃผ๋Š” **์ด์ค‘ ๋ ˆ์ด์–ด(Dual-Layer) ๋ทฐ** ์ง€์›. ์šฉ์‹ ๋ฐ˜์‘ ์‹œ ๊ฑฐํ’ˆ(Reaction Bubble) ์ดํŽ™ํŠธ ์ ์šฉ.
21
+
22
+ ### ๐Ÿ› ๏ธ 2. ๊ธฐ์ˆ  ์Šคํƒ (Tech Stack)
23
+ * **Core Engine:** Chemical Weathering Simulator based on $pH$ & $Temperature$.
24
+ * **Rendering:** Voxel-based Terrain (๋™๊ตด ํ˜•์„ฑ์„ ์œ„ํ•œ ๋‚ด๋ถ€ ๊ตด์ฐฉ ํ‘œํ˜„).
25
+ * **Physics:** Fluid Dynamics (์ง€ํ•˜์ˆ˜ ํ๋ฆ„ ๋ฐ ์นจํˆฌ).
26
+
27
+ ---
28
+
29
+ ## 1. ์นด๋ฅด์ŠคํŠธ ์ง€ํ˜• ๋ชจ๋“ˆ ์„ธ๋ถ€ ๋ช…์„ธ (Module Specifications)
30
+
31
+ ### ๐Ÿ’ง Chapter 1. ์ง€ํ‘œ ์นด๋ฅด์ŠคํŠธ: ๋ฌผ์ด ์กฐ๊ฐํ•œ ๋Œ€์ง€
32
+ **ํ•ต์‹ฌ ๋กœ์ง:** **ํƒ„์‚ฐ์นผ์Š˜ ์šฉ์‹ ์ž‘์šฉ (Dissolution)**. ๋น—๋ฌผ์ด ์„ํšŒ์•”์„ ๋…น์—ฌ ์ง€ํ‘œ๋ฉด์ด ํ•จ๋ชฐ๋จ.
33
+
34
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
35
+ | :--- | :--- | :--- | :--- |
36
+ | **์šฉ์‹ ๋ฉ”์ปค๋‹ˆ์ฆ˜** | ๋น—๋ฌผ ์‚ฐ๋„($pH$), ํƒ„์‚ฐ๊ฐ€์Šค ๋†๋„ | ๋น—๋ฌผ์ด ๋‹ฟ๋Š” ์ฆ‰์‹œ ์•”์„ ํ‘œ๋ฉด์ด ๋…น์•„๋‚ด๋ฆฌ๋Š” **[Shader Effect]**. ํ™”ํ•™์‹ ์˜ค๋ฒ„๋ ˆ์ด. | $CaCO_3 + H_2O + CO_2 \leftrightarrow Ca(HCO_3)_2$ |
37
+ | **๋Œ๋ฆฌ๋„ค (Doline)** | ์ ˆ๋ฆฌ ๋ฐ€๋„, ์‹œ๊ฐ„($t$) | ์ง€ํ‘œ๋ฉด์— ๋น—๋ฌผ์ด ๊ณ ์ด๋‹ค๊ฐ€ ๋ฐฐ์ˆ˜๊ตฌ์ฒ˜๋Ÿผ ๋น ์ง€๋ฉฐ ์›ํ˜•์œผ๋กœ ์›€ํ‘น ๊บผ์ง€๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜. | Sinkhole Radius $R(t)$ |
38
+ | **์šฐ๋ฐœ๋ผ & ํด๋ฆฌ์—** | ๋Œ๋ฆฌ๋„ค ๊ฒฐํ•ฉ ํ™•๋ฅ , ๊ธฐ๋ฐ˜์•” ๊นŠ์ด | ์ธ์ ‘ํ•œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋Œ๋ฆฌ๋„ค๊ฐ€ ํ•ฉ์ณ์ ธ ๊ฑฐ๋Œ€ ๋ถ„์ง€(์šฐ๋ฐœ๋ผ)๊ฐ€ ๋˜๊ณ , ํ‰ํ‰ํ•œ ๋“คํŒ(ํด๋ฆฌ์—)์ด ๋˜๋Š” ๊ณผ์ •. | Merge Function $f(D_1, D_2)$ |
39
+ | **ํ…Œ๋ผ๋กœ์‚ฌ (Terra Rossa)** | ๋ถˆ์šฉ์„ฑ ์ž”๋ฅ˜๋ฌผ ๋น„์œจ | ์„ํšŒ์•”์ด ๋…น๊ณ  ๋‚จ์€ ๋ถ‰์€ ํ™์ด ์ง€ํ‘œ๋ฉด์„ ๋ฎ๋Š” ํ…์Šค์ฒ˜ ๋งคํ•‘ ๋ณ€ํ™” (White โ†’ Red). | Residue Accumulation |
40
+
41
+ ### ๐Ÿฆ‡ Chapter 2. ์ง€ํ•˜ ์นด๋ฅด์ŠคํŠธ: ์–ด๋‘  ์†์˜ ์˜ˆ์ˆ 
42
+ **ํ•ต์‹ฌ ๋กœ์ง:** **์นจ์ „ ์ž‘์šฉ (Precipitation)**. ๋…น์•˜๋˜ ์„ํšŒ์งˆ์ด ๋‹ค์‹œ ๊ตณ์–ด์ง.
43
+
44
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
45
+ | :--- | :--- | :--- | :--- |
46
+ | **์„ํšŒ ๋™๊ตด** | ์ง€ํ•˜์ˆ˜ ์œ ๋Ÿ‰, ์ˆ˜์œ„ ๋ณ€ํ™” | **[X-Ray Mode]** ์ง€ํ•˜์ˆ˜๋ฉด ์•„๋ž˜์—์„œ ์„ํšŒ์•”์ด ๋…น์•„ ๋นˆ ๊ณต๊ฐ„(Cavity)์ด ํ™•์žฅ๋˜๋Š” ๊ณผ์ •. | Cave Volume Expansion |
47
+ | **์ข…์œ ์„ & ์„์ˆœ** | ๋‚™์ˆ˜ ์†๋„($V_{drip}$), ์ฆ๋ฐœ๋ฅ  | ์ฒœ์žฅ์—์„œ ๋ฌผ๋ฐฉ์šธ์ด ๋–จ์–ด์ง€๋ฉฐ ๊ณ ๋“œ๋ฆ„(์ข…์œ ์„)์ด ์ž๋ผ๊ณ , ๋ฐ”๋‹ฅ์—์„œ ์ฃฝ์ˆœ(์„์ˆœ)์ด ์†Ÿ์•„์˜ค๋ฆ„. | Growth Rate $\approx mm/year$ |
48
+ | **์„์ฃผ (Column)** | ์„ฑ์žฅ ์†๋„, ๋™๊ตด ๋†’์ด($H$) | ์œ„(์ข…์œ ์„)์™€ ์•„๋ž˜(์„์ˆœ)๊ฐ€ ๋งŒ๋‚˜ ๊ธฐ๋‘ฅ์œผ๋กœ ์—ฐ๊ฒฐ๋˜๋Š” ์ˆœ๊ฐ„ **[Highlight Effect]**. | Connection Event |
49
+
50
+ ### ๐Ÿž๏ธ Chapter 3. ํƒ‘ ์นด๋ฅด์ŠคํŠธ: ์—ด๋Œ€์˜ ๊ธฐ์•”๊ดด์„ (Bonus)
51
+ **ํ•ต์‹ฌ ๋กœ์ง:** **๊ณ ์˜จ ๋‹ค์Šต & ์ฐจ๋ณ„ ์นจ์‹**.
52
+
53
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
54
+ | :--- | :--- | :--- | :--- |
55
+ | **ํƒ‘ ์นด๋ฅด์ŠคํŠธ** | ๊ฐ•์ˆ˜๋Ÿ‰($P_{high}$), ๊ธฐ์˜จ($T_{high}$) | ํ‰์ง€์— ํƒ‘์ฒ˜๋Ÿผ ์†Ÿ์€ ๋ด‰์šฐ๋ฆฌ ํ˜•์„ฑ (๋ฒ ํŠธ๋‚จ ํ•˜๋กฑ๋ฒ ์ด, ์ค‘๊ตญ ๊ตฌ์ด๋ฆฐ ์Šคํƒ€์ผ). ์ธก๋ฉด ์นจ์‹ ๊ฐ•์กฐ. | Tropical Erosion Rate |
56
+
57
+ ---
58
+
59
+ ## 2. ์ •๋‹ต์ง€ ์ด๋ฏธ์ง€ ๋ชฉ๋ก (Reference Images to Load)
60
+ *AI ๋ชจ๋ธ ํ•™์Šต ๋ฐ UI ๋ Œ๋”๋ง ์‹œ ๋งค์นญํ•  ๋ ˆํผ๋Ÿฐ์Šค ๋ฐ์ดํ„ฐ.*
61
+
62
+ 1. `chemical_weathering_reaction.png` (ํƒ„์‚ฐ์นผ์Š˜ ์šฉ์‹ ํ™”ํ•™์‹ ๋ฐ ๋ชจ์‹๋„)
63
+ 2. `doline_uvala_polje_progression.jpg` (๋Œ๋ฆฌ๋„ค์—์„œ ํด๋ฆฌ์—๋กœ ์ปค์ง€๋Š” ๋‹จ๊ณ„๋ณ„ ์ง€๋„)
64
+ 3. `cave_interior_structure.jpg` (์ข…์œ ์„, ์„์ˆœ, ์„์ฃผ๊ฐ€ ์žˆ๋Š” ๋™๊ตด ๋‚ด๋ถ€ ๋‹จ๋ฉด๋„)
65
+ 4. `tower_karst_landscape.jpg` (ํƒ‘ ์นด๋ฅด์ŠคํŠธ์˜ ์ง€ํ˜•์  ํŠน์ง•)
66
+
67
+ ---
68
+
69
+ **[Director's Note]**
70
+ ์นด๋ฅด์ŠคํŠธ ๋ชจ๋“ˆ์€ **'ํ™”ํ•™ ๋ฐ˜์‘'**์ด ์ง€ํ˜•์„ ์–ด๋–ป๊ฒŒ ๋ฐ”๊พธ๋Š”์ง€ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. $CaCO_3$ ํ™”ํ•™์‹์ด ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋‚ด์—์„œ ํŠธ๋ฆฌ๊ฑฐ๋กœ ์ž‘๋™ํ•˜๋„๋ก ๋กœ์ง์„ ์ ๊ฒ€ํ•˜์‹ญ์‹œ์˜ค.
71
+ ์Šน์ธํ•˜์‹œ๋ฉด ๋‹ค์Œ **'ํ™”์‚ฐ ์ง€ํ˜•'**์œผ๋กœ ๋„˜์–ด๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค.
04_Volcanic_Landforms_Spec.md ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ๐Ÿ“˜ [Geo-Lab AI] ๊ฐœ๋ฐœ ๋ฐฑ์„œ (Development White Paper)
2
+
3
+ **Project Name:** Geo-Lab AI
4
+ **Version:** Final Release 1.0
5
+ **Module:** Volcanic Landforms (ํ™”์‚ฐ ์ง€ํ˜•)
6
+ **Target:** High School Geography ~ Undergraduate Volcanology
7
+ **Director:** [User Name]
8
+
9
+ ---
10
+
11
+ ## 0. ๊ฐœ๋ฐœ ํ‘œ์ค€ ๋ฐ ์ œ์•ฝ์‚ฌํ•ญ (Standard Protocols)
12
+
13
+ ### ๐ŸŽจ 1. ๋น„์ฃผ์–ผ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ (Visual Style: Magmatic & Explosive)
14
+ * **Tone:** Dynamic, Thermal-coded, Catastrophic.
15
+ * **Color Palette:**
16
+ * *Magma/Lava:* ์˜จ๋„์— ๋”ฐ๋ฅธ Black Body Radiation ์ƒ‰์ƒ (White Hot โ†’ Yellow โ†’ Red โ†’ Dark Crust).
17
+ * *Ash/Rock:* ํ˜„๋ฌด์•”(๊ฒ€์€์ƒ‰) vs ์กฐ๋ฉด์•”/์•ˆ์‚ฐ์•”(ํšŒ๋ฐฑ์ƒ‰) ๊ตฌ๋ถ„.
18
+ * *Terrain:* ๊ตณ์€ ์šฉ์•” ์œ„ 1์ฐจ ์ฒœ์ด ์‹์ƒ(์ด๋ผ, ๋ค๋ถˆ)์˜ ๋“ฌ์„ฑ๋“ฌ์„ฑํ•œ ๋…น์ƒ‰.
19
+ * **Key Feature:** ๋งˆ๊ทธ๋งˆ์˜ **$SiO_2$ ํ•จ๋Ÿ‰(์ด์‚ฐํ™”๊ทœ์†Œ)**์— ๋”ฐ๋ฅธ ์ ์„ฑ ๋ณ€ํ™”๋ฅผ ์Šฌ๋ผ์ด๋”(Slider)๋กœ ์กฐ์ ˆํ•˜๋ฉด ํ™”์‚ฐ์˜ ๋ชจ์–‘์ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณ€ํ˜•๋˜๋Š” **[Morphing Terrain]** ๊ตฌํ˜„.
20
+
21
+ ### ๐Ÿ› ๏ธ 2. ๊ธฐ์ˆ  ์Šคํƒ (Tech Stack)
22
+ * **Physics Engine:** SPH (Smoothed-Particle Hydrodynamics) for Lava Flow Simulation.
23
+ * **Rendering:** Volumetric Fog (ํ™”์‚ฐ์žฌ ๋ฐ ์—ฐ๊ธฐ ํ‘œํ˜„).
24
+ * **Algorithm:** Cooling Crystallization Model (์šฉ์•” ๋ƒ‰๊ฐ ์†๋„์— ๋”ฐ๋ฅธ ์ ˆ๋ฆฌ ํ˜•์„ฑ ๊ณ„์‚ฐ).
25
+
26
+ ---
27
+
28
+ ## 1. ํ™”์‚ฐ ์ง€ํ˜• ๋ชจ๋“ˆ ์„ธ๋ถ€ ๋ช…์„ธ (Module Specifications)
29
+
30
+ ### ๐ŸŒ‹ Chapter 1. ๋งˆ๊ทธ๋งˆ์˜ ์„ฑ๊ฒฉ: ์ ์„ฑ(Viscosity)๊ณผ ํ™”์‚ฐ์ฒด
31
+ **ํ•ต์‹ฌ ๋กœ์ง:** **$SiO_2$ ํ•จ๋Ÿ‰ $\propto$ ์ ์„ฑ $\propto$ ํ™”์‚ฐ ๊ฒฝ์‚ฌ๋„**.
32
+
33
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
34
+ | :--- | :--- | :--- | :--- |
35
+ | **์ˆœ์ƒ ํ™”์‚ฐ (Shield)** | ์ ์„ฑ($\mu_{low}$), ์˜จ๋„($T_{high}$), ์œ ๋™์„ฑ(High) | ๊ฟ€์ฒ˜๋Ÿผ ๋ฌฝ์€ ์šฉ์•”์ด ๋„“๊ฒŒ ํผ์ ธ๋‚˜๊ฐ€๋ฉฐ ์™„๋งŒํ•œ ๊ฒฝ์‚ฌ(๋ฐฉํŒจ ๋ชจ์–‘) ํ˜•์„ฑ. (์˜ˆ: ํ•˜์™€์ด, ์ œ์ฃผ๋„ ํ•œ๋ผ์‚ฐ ์‚ฐ๋ก) | Slope Angle $\theta \approx 2^{\circ} \sim 10^{\circ}$ |
36
+ | **์ข…์ƒ ํ™”์‚ฐ (Lava Dome)** | ์ ์„ฑ($\mu_{high}$), ์˜จ๋„($T_{low}$), ์œ ๋™์„ฑ(Low) | ์น˜์•ฝ์ฒ˜๋Ÿผ ๋œ ์šฉ์•”์ด ๋ถ„ํ™”๊ตฌ ์œ„๋กœ ์†Ÿ๊ตฌ์ณ ์ข… ๋ชจ์–‘ ํ˜•์„ฑ. (์˜ˆ: ์ œ์ฃผ๋„ ์‚ฐ๋ฐฉ์‚ฐ, ์šธ๋ฆ‰๋„ ๋‚˜๋ฆฌ๋ถ„์ง€ ๋‚ด ์•Œ๋ด‰) | Slope Angle $\theta > 30^{\circ}$ |
37
+ | **์„ฑ์ธต ํ™”์‚ฐ (Stratovolcano)** | ๋ถ„์ถœ ์ฃผ๊ธฐ, ํญ๋ฐœ์„ฑ | ์šฉ์•” ๋ถ„์ถœ(Flow)๊ณผ ํ™”์‚ฐ์žฌ ํญ๋ฐœ(Explosion)์ด ๊ต๋Œ€๋กœ ์ผ์–ด๋‚˜ ์ธต์ธต์ด ์Œ“์ธ ์›๋ฟ”ํ˜• ๊ตฌ์กฐ. (์˜ˆ: ํ›„์ง€์‚ฐ, ํ•„๋ฆฌํ•€ ๋งˆ์š˜) | Layering Index |
38
+ | **์šฉ์•” ๋Œ€์ง€ (Lava Plateau)** | ์—ดํ•˜ ๋ถ„์ถœ(Fissure), $SiO_2$ < 52% | ์ง€๊ฐ์˜ ํ‹ˆ์—์„œ ๋ฌฝ์€ ์šฉ์•”์ด ๋Œ€๋Ÿ‰์œผ๋กœ ํ˜๋Ÿฌ๋‚˜์™€ ๊ธฐ์กด ๊ณ„๊ณก์„ ๋ฉ”์šฐ๊ณ  ํ‰ํƒ„๋ฉด ํ˜•์„ฑ. (์˜ˆ: ์ฒ ์›, ๊ฐœ๋งˆ๊ณ ์›) | Area Coverage Rate |
39
+
40
+ ### ๐Ÿ’ฅ Chapter 2. ๋ถ„ํ™”๊ตฌ์˜ ๋ณ€ํ˜•: ํ•จ๋ชฐ๊ณผ ํ™•์žฅ
41
+ **ํ•ต์‹ฌ ๋กœ์ง:** **์งˆ๋Ÿ‰ ๊ฒฐ์†(Mass Deficit)์— ์˜ํ•œ ๋ถ•๊ดด**.
42
+
43
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
44
+ | :--- | :--- | :--- | :--- |
45
+ | **ํ™”๊ตฌ (Crater)** | ํญ๋ฐœ ์—๋„ˆ์ง€($E_{exp}$) | ๋งˆ๊ทธ๋งˆ๊ฐ€ ๋ถ„์ถœ๋œ ๋‹จ์ˆœํ•œ ๊ตฌ๋ฉ. | Diameter $D < 1km$ |
46
+ | **์นผ๋ฐ๋ผ (Caldera)** | ๋งˆ๊ทธ๋งˆ ๋ฐฉ ๊ณต๋™ํ™” ๋น„์œจ, ์ง€๋ฐ˜ ํ•˜์ค‘ | **[Collapse Event]** ๋Œ€ํญ๋ฐœ ํ›„ ์ง€ํ•˜ ๋งˆ๊ทธ๋งˆ ๋ฐฉ์ด ๋น„๋ฉด์„œ ์‚ฐ์ •๋ถ€๊ฐ€ ์™€๋ฅด๋ฅด ๋ฌด๋„ˆ์ ธ ๋‚ด๋ฆฌ๋Š” ์‹œ๋„ค๋งˆํ‹ฑ ์ปท. ๋ฌผ์ด ์ฐจ๋ฉด ์นผ๋ฐ๋ผ ํ˜ธ. (์˜ˆ: ๋ฐฑ๋‘์‚ฐ ์ฒœ์ง€, ๋‚˜๋ฆฌ๋ถ„์ง€) | Collapse Volume $V_c$ |
47
+ | **์ด์ค‘ ํ™”์‚ฐ** | 1์ฐจ ๋ถ„์ถœ ํ›„ ํœด์ง€๊ธฐ, 2์ฐจ ๋ถ„์ถœ | ์นผ๋ฐ๋ผ(ํฐ ๊ทธ๋ฆ‡) ์•ˆ์— ์ƒˆ๋กœ์šด ํ™”์‚ฐ(์ž‘์€ ์ปต)์ด ์ƒ๊ธฐ๋Š” ๊ตฌ์กฐ. (์˜ˆ: ์šธ๋ฆ‰๋„ ์„ฑ์ธ๋ด‰-๋‚˜๋ฆฌ๋ถ„์ง€-์•Œ๋ด‰) | Nested Structure |
48
+
49
+ ### โ„๏ธ Chapter 3. ๋ƒ‰๊ฐ์˜ ๊ธฐํ•˜ํ•™: 1์ฐจ ์ง€ํ˜•
50
+ **ํ•ต์‹ฌ ๋กœ์ง:** **์ˆ˜์ถ•(Contraction)๊ณผ ๊ท ์—ด**.
51
+
52
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
53
+ | :--- | :--- | :--- | :--- |
54
+ | **์ฃผ์ƒ์ ˆ๋ฆฌ (Columnar Jointing)** | ๋ƒ‰๊ฐ ์†๋„($dT/dt$), ์ˆ˜์ถ• ์ค‘์‹ฌ์  | ์šฉ์•”์ด ๊ธ‰๊ฒฉํžˆ ์‹์œผ๋ฉฐ ๋ถ€ํ”ผ๊ฐ€ ์ค„์–ด๋“ค ๋•Œ ํ˜•์„ฑ๋˜๋Š” **์œก๊ฐํ˜•(Hexagonal) ๊ธฐ๋‘ฅ** ํŒจํ„ด ์ƒ์„ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜. | Fracture Spacing $S \propto (dT/dt)^{-1/2}$ |
55
+ | **์šฉ์•” ๋™๊ตด (Lava Tube)** | ํ‘œ๋ฉด ๋ƒ‰๊ฐ์œจ vs ๋‚ด๋ถ€ ์œ ์† | ์šฉ์•”์˜ ๊ฒ‰๋ถ€๋ถ„์€ ๊ตณ๊ณ (์ง€๋ถ•), ์†์€ ๊ณ„์† ํ˜๋Ÿฌ(ํ„ฐ๋„) ๋น ์ ธ๋‚˜๊ฐ„ ๋’ค ๋‚จ์€ ๋นˆ ๊ณต๊ฐ„. | Tube Formation Logic |
56
+
57
+ ---
58
+
59
+ ## 2. ์ •๋‹ต์ง€ ์ด๋ฏธ์ง€ ๋ชฉ๋ก (Reference Images to Load)
60
+ *AI ๋ชจ๋ธ ํ•™์Šต ๋ฐ UI ๋ Œ๋”๋ง ์‹œ ๋งค์นญํ•  ๋ ˆํผ๋Ÿฐ์Šค ๋ฐ์ดํ„ฐ.*
61
+
62
+ 1. `volcano_shape_viscosity.png` (์ˆœ์ƒ vs ์„ฑ์ธต vs ์ข…์ƒ ํ™”์‚ฐ์˜ ๋‹จ๋ฉด ๋น„๊ต๋„)
63
+ 2. `caldera_formation_steps.gif` (๋งˆ๊ทธ๋งˆ ๋ฐฉ ๋น„์›€ โ†’ ๋ถ•๊ดด โ†’ ํ˜ธ์ˆ˜ ํ˜•์„ฑ 3๋‹จ๊ณ„)
64
+ 3. `columnar_jointing_hex.jpg` (์ฃผ์ƒ์ ˆ๋ฆฌ์˜ ์œก๊ฐํ˜• ๋‹จ๋ฉด ๋ฐ ์ธก๋ฉด ๊ธฐ๋‘ฅ ๊ตฌ์กฐ)
65
+ 4. `korea_volcanic_map.png` (๋ฐฑ๋‘์‚ฐ, ์ œ์ฃผ๋„, ์šธ๋ฆ‰๋„, ์ฒ ์› ์šฉ์•”๋Œ€์ง€ ์œ„์น˜ ์ง€๋„)
66
+
67
+ ---
68
+
69
+ **[Director's Note]**
70
+ ํ™”์‚ฐ ๋ชจ๋“ˆ์€ **'์ ์„ฑ($\mu$)'** ๋ณ€์ˆ˜ ํ•˜๋‚˜๊ฐ€ ์ง€ํ˜•์˜ ๋ชจ์–‘(์ˆœ์ƒ/์ข…์ƒ)์„ ๊ฒฐ์ •์ง“๋Š”๋‹ค๋Š” ์ธ๊ณผ๊ด€๊ณ„๋ฅผ ๋ช…ํ™•ํžˆ ๋ณด์—ฌ์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ ํ•œ๊ตญ ๏ฟฝ๏ฟฝ๋ฆฌ ์ˆ˜ํ—˜์ƒ์„ ํƒ€๊ฒŸ์œผ๋กœ ํ•œ๋‹ค๋ฉด **์ฒ ์› ์šฉ์•”๋Œ€์ง€**์™€ **์ œ์ฃผ๋„**์˜ ํ˜•์„ฑ ๊ณผ์ • ์ฐจ์ด๋ฅผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์œผ๋กœ ๋น„๊ตํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๋ฐ˜๋“œ์‹œ ํฌํ•จํ•˜์‹ญ์‹œ์˜ค.
71
+
72
+ ์Šน์ธํ•˜์‹œ๋ฉด ๋‹ค์Œ์€ ๊ฐ€์žฅ ๋‹ค์ด๋‚ด๋ฏนํ•œ **'๋น™ํ•˜ ์ง€ํ˜•'**์œผ๋กœ ์ด๋™ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
05_Glacial_Landforms_Spec.md ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ๐Ÿ“˜ [Geo-Lab AI] ๊ฐœ๋ฐœ ๋ฐฑ์„œ (Development White Paper)
2
+
3
+ **Project Name:** Geo-Lab AI
4
+ **Version:** Final Release 1.0
5
+ **Module:** Glacial Landforms (๋น™ํ•˜ ์ง€ํ˜•)
6
+ **Target:** High School Geography ~ Undergraduate Glaciology
7
+ **Director:** [User Name]
8
+
9
+ ---
10
+
11
+ ## 0. ๊ฐœ๋ฐœ ํ‘œ์ค€ ๋ฐ ์ œ์•ฝ์‚ฌํ•ญ (Standard Protocols)
12
+
13
+ ### ๐ŸŽจ 1. ๋น„์ฃผ์–ผ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ (Visual Style: Cryosphere)
14
+ * **Tone:** Majestic, Cold, High-Contrast.
15
+ * **Color Palette:**
16
+ * *Ice:* ๋ฐ˜ํˆฌ๋ช…ํ•œ ์•„์ด์Šค ๋ธ”๋ฃจ(Ice Blue) ~ ์••์ถ•๋œ ์ง™์€ ํŒŒ๋ž‘ (Deep Azure).
17
+ * *Bedrock:* ๊นŽ์—ฌ ๋‚˜๊ฐ„ ๊ฑฐ์นœ ํšŒ์ƒ‰ ํ™”๊ฐ•์•” ์งˆ๊ฐ (Exposed Granite).
18
+ * *Water (Fjord):* ๊นŠ๊ณ  ์–ด๋‘์šด ๋‚จ์ƒ‰ (Deep Navy).
19
+ * **Key Feature:** ๋น™ํ•˜๊ฐ€ ํ๋ฅผ ๋•Œ ๋ฐ”๋‹ฅ์„ ๊ธ์–ด๋‚ด๋Š”(Scouring) ํšจ๊ณผ์™€ ๋…น์„ ๋•Œ ํ™๋”๋ฏธ๋ฅผ ์Ÿ์•„๋†“๋Š”(Dumping) ํšจ๊ณผ๋ฅผ ๋ฌผ๋ฆฌ ์—”์ง„์œผ๋กœ ๊ตฌํ˜„. **"๋ถ„๊ธ‰(Sorting) ์—†์Œ"** ์‹œ๊ฐํ™” ํ•„์ˆ˜.
20
+
21
+ ### ๐Ÿ› ๏ธ 2. ๊ธฐ์ˆ  ์Šคํƒ (Tech Stack)
22
+ * **Physics Engine:** Non-Newtonian Fluid Dynamics (๋น„๋‰ดํ„ด ์œ ์ฒด ์—ญํ•™ - ์–ผ์Œ์˜ ๋А๋ฆฐ ํ๋ฆ„ ๊ตฌํ˜„).
23
+ * **Rendering:** Subsurface Scattering (์–ผ์Œ ๋‚ด๋ถ€์˜ ๋น› ํˆฌ๊ณผ ํšจ๊ณผ).
24
+ * **Algorithm:** Voxel Carving (๋น™ํ•˜ ์ด๋™ ๊ฒฝ๋กœ์— ๋”ฐ๋ฅธ ์ง€ํ˜• ๊นŽ๊ธฐ).
25
+
26
+ ---
27
+
28
+ ## 1. ๋น™ํ•˜ ์ง€ํ˜• ๋ชจ๋“ˆ ์„ธ๋ถ€ ๋ช…์„ธ (Module Specifications)
29
+
30
+ ### ๐ŸงŠ Chapter 1. ๋น™ํ•˜ ์นจ์‹: ๊ฑฐ๋Œ€ํ•œ ๋ถˆ๋„์ € (Erosion)
31
+ **ํ•ต์‹ฌ ๋กœ์ง:** **U์žํ˜• ์นจ์‹ (U-shaped Profile)** & ๊ตด์‹ ์ž‘์šฉ(Plucking).
32
+
33
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
34
+ | :--- | :--- | :--- | :--- |
35
+ | **U์ž๊ณก (U-shaped Valley)** | ๋น™ํ•˜ ๋‘๊ป˜($H_{ice}$), ๋งˆ์ฐฐ๋ ฅ | ์ข๊ณ  ๊นŠ์€ V์ž๊ณก(ํ•˜์ฒœ)์ด ๊ฑฐ๋Œ€ํ•œ ๋น™ํ•˜์— ์˜ํ•ด ๋„“๊ณ  ๋‘ฅ๊ทผ U์ž ํ˜•ํƒœ๋กœ ๊นŽ์—ฌ๋‚˜๊ฐ€๋Š” **[Cross-section Transition]**. | Erosion Rate $E \propto U_{sliding}^2$ |
36
+ | **๊ถŒ๊ณก (Cirque)** | ์„ค์„  ๊ณ ๋„(ELA), ๊ตด์‹ ๊ฐ•๋„ | ์‚ฐ ์ •์ƒ๋ถ€ ์˜ค๋ชฉํ•œ ๊ณณ์— ๋ˆˆ์ด ์Œ“์—ฌ ์–ผ์Œ์ด ๋˜๊ณ , ์•”์„์„ ๋œฏ์–ด๋‚ด๋ฉฐ(Plucking) ๋ฐ˜์›ํ˜• ์˜์ž ๋ชจ์–‘์„ ๋งŒ๋“œ๋Š” ๊ณผ์ •. | - |
37
+ | **ํ˜ธ๋ฅธ (Horn)** | ๊ถŒ๊ณก์˜ ๊ฐœ์ˆ˜($N \ge 3$) | 3๊ฐœ ์ด์ƒ์˜ ๊ถŒ๊ณก์ด ๋’ค๋กœ ํ›„ํ‡ดํ•˜๋ฉฐ ์ •์ƒ์„ ๊นŽ์•„ ๋งŒ๋“ค์–ด์ง„ ํ”ผ๋ผ๋ฏธ๋“œํ˜• ๋พฐ์กฑ ๋ด‰์šฐ๋ฆฌ (์˜ˆ: ๋งˆํ„ฐํ˜ธ๋ฅธ). | Peak Sharpness Index |
38
+ | **ํ”ผ์˜ค๋ฅด (Fjord)** | ํ•ด์ˆ˜๋ฉด ์ƒ์Šน($\Delta SeaLevel$), ์นจ์‹ ๊นŠ์ด | ๋น™๊ธฐ๊ฐ€ ๋๋‚˜๊ณ  ํ•ด์ˆ˜๋ฉด์ด ์ƒ์Šนํ•˜์—ฌ U์ž๊ณก์— ๋ฐ”๋‹ท๋ฌผ์ด ๋“ค์–ด์ฐจ๋Š” **[Flooding Animation]**. ๋‚ด๋ฅ™ ๊นŠ์ˆ™์ด ์ข๊ณ  ๊ธด ๋ฐ”๋‹ค ํ˜•์„ฑ. | Depth Profile $D(x)$ |
39
+ | **ํ˜„๊ณก (Hanging Valley)** | ๋ณธ๋ฅ˜ ๋น™ํ•˜ vs ์ง€๋ฅ˜ ๋น™ํ•˜ ๊นŠ์ด ์ฐจ์ด | ๋ณธ๋ฅ˜๊ฐ€ ๊นŠ๊ฒŒ ๊นŽ๊ณ  ์ง€๋‚˜๊ฐ„ ๋’ค, ์–•์€ ์ง€๋ฅ˜ ๊ณจ์งœ๊ธฐ๊ฐ€ ์ ˆ๋ฒฝ ์œ„์— ๊ฑธ๋ ค ํญํฌ๊ฐ€ ๋˜๋Š” ์ง€ํ˜•. | Drop Height $H_{drop}$ |
40
+
41
+ ### ๐Ÿชจ Chapter 2. ๋น™ํ•˜ ํ‡ด์ : ๋ฌด์งˆ์„œ์˜ ๋ฏธํ•™ (Deposition)
42
+ **ํ•ต์‹ฌ ๋กœ์ง:** **๋ถ„๊ธ‰ ๋ถˆ๋Ÿ‰ (Poor Sorting)**. ํฌ๊ณ  ์ž‘์€ ์ž๊ฐˆ๊ณผ ํ™์ด ๋’ค์„ž์ž„.
43
+
44
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
45
+ | :--- | :--- | :--- | :--- |
46
+ | **๋น™ํ‡ด์„ (Moraine)** | ๋น™ํ•˜ ํ›„ํ‡ด ์†๋„, ํ‡ด์ ๋Ÿ‰ | ๋น™ํ•˜๊ฐ€ ๋…น๋Š” ๋๋ถ€๋ถ„(Terminus)์—์„œ ์ปจ๋ฒ ์ด์–ด ๋ฒจํŠธ์ฒ˜๋Ÿผ ์•”์„ ๋ถ€์Šค๋Ÿฌ๊ธฐ๋ฅผ ์Ÿ์•„๋†“์•„ ์–ธ๋•์„ ๋งŒ๋“œ๋Š” ๊ณผ์ •. | Sediment Flux $Q_s$ |
47
+ | **๋“œ๋Ÿผ๋ฆฐ (Drumlin)** | ๋น™ํ•˜ ์ด๋™ ๋ฐฉํ–ฅ, ๊ธฐ์ € ํ•˜์ค‘ | ์ˆŸ๊ฐ€๋ฝ์„ ์—Ž์–ด๋†“์€ ๋“ฏํ•œ ์œ ์„ ํ˜• ์–ธ๋•. **[Direction Indicator]** ์™„๋งŒํ•œ ์ชฝ์ด ๋น™ํ•˜๊ฐ€ ํ˜๋Ÿฌ๊ฐ„ ๋ฐฉํ–ฅ์ž„์„ ํ™”์‚ดํ‘œ๋กœ ํ‘œ์‹œ. | Shape Factor (Elongation) |
48
+ | **์—์Šค์ปค (Esker)** | ์œต๋น™์ˆ˜ ์œ ๋Ÿ‰($Q_{water}$), ํ„ฐ๋„ ํฌ๊ธฐ | ๋น™ํ•˜ ๋ฐ‘์„ ํ๋ฅด๋Š” ๋ฌผ(์œต๋น™์ˆ˜) ํ„ฐ๋„์— ํ‡ด์ ๋ฌผ์ด ์Œ“์—ฌ, ๋น™ํ•˜๊ฐ€ ๋…น์€ ๋’ค ๊ตฌ๋ถˆ๊ตฌ๋ถˆํ•œ ์ œ๋ฐฉ ๋ชจ์–‘์ด ๋“œ๋Ÿฌ๋‚จ. | Sinuosity Index |
49
+
50
+ ### โ„๏ธ Chapter 3. ์ฃผ๋น™ํ•˜ ์ง€ํ˜•: ์–ผ์—ˆ๋‹ค ๋…น์•˜๋‹ค (Periglacial - Bonus)
51
+ **ํ•ต์‹ฌ ๋กœ์ง:** **๋™๊ฒฐ ์œตํ•ด (Freeze-Thaw Cycle)**.
52
+
53
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
54
+ | :--- | :--- | :--- | :--- |
55
+ | **๊ตฌ์กฐํ†  (Patterned Ground)** | ํ† ์–‘ ์ž…์ž ํฌ๊ธฐ, ๋™๊ฒฐ ํšŸ์ˆ˜ | ์ž๊ฐˆ๋“ค์ด ์–ผ์Œ์˜ ์๊ธฐ ์ž‘์šฉ์œผ๋กœ ๋ฐ€๋ ค๋‚˜์™€ ๋‹ค๊ฐํ˜•(Polygon) ๋ฌด๋Šฌ๋ฅผ ๋งŒ๋“œ๋Š” **[Time-lapse]**. | Convection Cell Model |
56
+ | **์†”๋ฆฌํ”Œ๋Ÿญ์…˜ (Solifluction)** | ๊ฒฝ์‚ฌ๋„, ํ™œ๋™์ธต ์œตํ•ด | ์˜๊ตฌ๋™ํ† ์ธต ์œ„ ๋…น์€ ํ™์ด ํ˜๋Ÿฌ๋‚ด๋ฆฌ๋Š” ํ˜„์ƒ. | Creep Velocity |
57
+
58
+ ---
59
+
60
+ ## 2. ์ •๋‹ต์ง€ ์ด๋ฏธ์ง€ ๋ชฉ๋ก (Reference Images to Load)
61
+ *AI ๋ชจ๋ธ ํ•™์Šต ๋ฐ UI ๋ Œ๋”๋ง ์‹œ ๋งค์นญํ•  ๋ ˆํผ๋Ÿฐ์Šค ๋ฐ์ดํ„ฐ.*
62
+
63
+ 1. `valley_transformation_V_to_U.gif` (V์ž๊ณก์—์„œ U์ž๊ณก์œผ๋กœ ๋ณ€ํ˜•๋˜๋Š” 3D ๋ชจ๋ธ)
64
+ 2. `fjord_cross_section.jpg` (๋ฐ”๋‹ท๋ฌผ์— ์ž ๊ธด U์ž๊ณก ๋‹จ๋ฉด๋„)
65
+ 3. `glacial_till_unsorted.png` (๋น™ํ‡ด์„์˜ ๋’ค์„ž์ธ ์ž๊ฐˆ ๋‹จ๋ฉด - ๋ถ„๊ธ‰ ๋ถˆ๋Ÿ‰ ์˜ˆ์‹œ)
66
+ 4. `drumlin_ice_flow.jpg` (๋“œ๋Ÿผ๋ฆฐ์˜ ํ˜•ํƒœ์™€ ๋น™ํ•˜ ์ด๋™ ๋ฐฉํ–ฅ ๊ด€๊ณ„๋„)
67
+
68
+ ---
69
+
70
+ **[Director's Note]**
71
+ ๋น™ํ•˜ ์ง€ํ˜•์˜ ํ‚ฌ๋Ÿฌ ๋ฌธํ•ญ ํฌ์ธํŠธ๋Š” **"๊ฐ•๋ฌผ(์œ ์ˆ˜)์— ์˜ํ•œ ํ‡ด์ (๋ถ„๊ธ‰ ์–‘ํ˜ธ)"**๊ณผ **"๋น™ํ•˜์— ์˜ํ•œ ํ‡ด์ (๋ถ„๊ธ‰ ๋ถˆ๋Ÿ‰)"**์„ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋‚ด์—์„œ ์ž…์ž ํฌ๊ธฐ ํ•„ํ„ฐ(Sorting Filter)๊ฐ€ ๋น™ํ•˜ ๋ชจ๋“œ์—์„œ๋Š” ์ž‘๋™ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ์‹œ๊ฐ์ ์œผ๋กœ ๊ฐ•์กฐํ•ด์ฃผ์„ธ์š”.
72
+
73
+ ์Šน์ธํ•˜์‹œ๋ฉด ๋‹ค์Œ์€ ๊ฐ€์žฅ ์ฒ™๋ฐ•ํ•˜์ง€๋งŒ ์•„๋ฆ„๋‹ค์šด **'๊ฑด์กฐ ์ง€ํ˜•'**์œผ๋กœ ๋„˜์–ด๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค.
06_Arid_Landforms_Spec.md ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ๐Ÿ“˜ [Geo-Lab AI] ๊ฐœ๋ฐœ ๋ฐฑ์„œ (Development White Paper)
2
+
3
+ **Project Name:** Geo-Lab AI
4
+ **Version:** Final Release 1.0
5
+ **Module:** Arid Landforms (๊ฑด์กฐ ์ง€ํ˜•)
6
+ **Target:** High School Geography ~ Undergraduate Geomorphology
7
+ **Director:** [User Name]
8
+
9
+ ---
10
+
11
+ ## 0. ๊ฐœ๋ฐœ ํ‘œ์ค€ ๋ฐ ์ œ์•ฝ์‚ฌํ•ญ (Standard Protocols)
12
+
13
+ ### ๐ŸŽจ 1. ๋น„์ฃผ์–ผ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ (Visual Style: Desert & Erosion)
14
+ * **Tone:** High Contrast, Dusty, Heat-haze.
15
+ * **Color Palette:**
16
+ * *Sand/Dust:* ์˜ค์ปค(Ochre), ๋ฒˆํŠธ ์‹œ์—๋‚˜(Burnt Sienna) ๋“ฑ ํ™ฉํ† ์ƒ‰ ๊ณ„์—ด.
17
+ * *Sky:* ์ง™์€ ์ฝ”๋ฐœํŠธ ๋ธ”๋ฃจ(๊ฑด์กฐํ•ด์„œ ๋Œ€๊ธฐ๊ฐ€ ๊นจ๋—ํ•จ) ๋˜๋Š” ๋ชจ๋ž˜ํญํ’ ์‹œ ๋ฟŒ์—ฐ ํ™ฉ์ƒ‰.
18
+ * *Salt Lake (Playa):* ๋ˆˆ๋ถ€์‹  ํฐ์ƒ‰ (์†Œ๊ธˆ ๊ฒฐ์ • ๋ฐ˜์‚ฌ).
19
+ * **Key Feature:** ์ง€ํ‘œ๋ฉด์˜ ์•„์ง€๋ž‘์ด(Heat Haze) ํšจ๊ณผ์™€ ๋ฐ”๋žŒ์˜ ๋ฐฉํ–ฅ์„ ๋ณด์—ฌ์ฃผ๋Š” **์ž…์ž ํ๋ฆ„(Particle Flow)** ์‹œ๊ฐํ™”.
20
+
21
+ ### ๐Ÿ› ๏ธ 2. ๊ธฐ์ˆ  ์Šคํƒ (Tech Stack)
22
+ * **Physics Engine:** Granular Physics (๋ชจ๋ž˜ ์ž…์ž ๊ฐ„ ๋งˆ์ฐฐ ๋ฐ ์•ˆ์‹๊ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜).
23
+ * **Rendering:** PBR Materials (์•”์„์˜ ๊ฑฐ์นœ ์งˆ๊ฐ vs ์†Œ๊ธˆ ์‚ฌ๋ง‰์˜ ๋งค๋„๋Ÿฌ์šด ์งˆ๊ฐ).
24
+ * **Algorithm:** Cellular Automata (์‚ฌ๊ตฌ์˜ ์ด๋™ ๋ฐ ์„ฑ์žฅ ํŒจํ„ด ๊ณ„์‚ฐ).
25
+
26
+ ---
27
+
28
+ ## 1. ๊ฑด์กฐ ์ง€ํ˜• ๋ชจ๋“ˆ ์„ธ๋ถ€ ๋ช…์„ธ (Module Specifications)
29
+
30
+ ### ๐ŸŒช๏ธ Chapter 1. ๋ฐ”๋žŒ์˜ ์กฐ๊ฐ: ์นจ์‹๊ณผ ํ‡ด์  (Aeolian Process)
31
+ **ํ•ต์‹ฌ ๋กœ์ง:** **๋„์•ฝ ์šด๋™(Saltation)**. ๋ฌด๊ฑฐ์šด ๋ชจ๋ž˜๋Š” ์ง€๋ฉด ๊ฐ€๊นŒ์ด์„œ ํŠ€๋ฉฐ ์ด๋™ํ•œ๋‹ค.
32
+
33
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
34
+ | :--- | :--- | :--- | :--- |
35
+ | **๋ฒ„์„ฏ๋ฐ”์œ„ (Mushroom Rock)** | ๋ชจ๋ž˜ ๋น„์‚ฐ ๋†’์ด($H_{max} \approx 1m$), ์•”์„ ๊ฒฝ๋„ | ๋ฐ”๋žŒ์— ๋‚ ๋ฆฐ ๋ชจ๋ž˜์•Œ์ด ๋ฐ”์œ„์˜ **๋ฐ‘๋ถ€๋ถ„๋งŒ ์ง‘์ค‘ ๊ณต๊ฒฉ**ํ•˜์—ฌ ๊นŽ์•„๋‚ด๋Š” ๊ณผ์ •. (์œ—๋ถ€๋ถ„์€ ์นจ์‹ ์•ˆ ๋จ). | Erosion Rate $E(z)$ |
36
+ | **์‚ฌ๊ตฌ (Sand Dune)** | ํ’ํ–ฅ ๋ฒกํ„ฐ($\vec{V}$), ๋ชจ๋ž˜ ๊ณต๊ธ‰๋Ÿ‰ | **[Dune Morphing]** ํ’ํ–ฅ์— ๋”ฐ๋ผ ๋ฐ”๋ฅดํ•œ(์ดˆ์Šน๋‹ฌํ˜•, ๋‹จ์ผํ’) $\leftrightarrow$ ์„ฑ์‚ฌ๊ตฌ(๊ธด ์นผํ˜•, ์–‘๋ฐฉํ–ฅํ’) ๋ณ€ํ™˜. | Bagnold Formula |
37
+ | **์‚ผ๋ฆ‰์„ (Ventifact)** | ์ฃผํ’ํ–ฅ์˜ ๊ฐœ์ˆ˜ | ์ž๊ฐˆ์ด ๋ฐ”๋žŒ์„ ๋ฐ›์•„ ๊นŽ์ด๋ฉด์„œ 3๊ฐœ์˜ ๋ฉด๊ณผ ๋ชจ์„œ๋ฆฌ๊ฐ€ ์ƒ๊ธฐ๋Š” ๊ณผ์ •. | Facet Formation |
38
+ | **์‚ฌ๋ง‰ ํฌ์žฅ (Desert Pavement)** | ์ž…์ž ํฌ๊ธฐ๋ณ„ ๋ฌด๊ฒŒ | ๋ฐ”๋žŒ์ด ๊ณ ์šด ๋ชจ๋ž˜๋งŒ ๋‚ ๋ ค ๋ณด๋‚ด๊ณ (Deflation), ๋ฌด๊ฑฐ์šด ์ž๊ฐˆ๋งŒ ๋ฐ”๋‹ฅ์— ๋‚จ๋Š” **[Sorting Filter]** ํšจ๊ณผ. | Threshold Friction Velocity |
39
+
40
+ ### ๐ŸŒง๏ธ Chapter 2. ๋ฌผ์˜ ์—ญ์„ค: ์œ ์ˆ˜ ์ง€ํ˜• (Fluvial in Desert)
41
+ **ํ•ต์‹ฌ ๋กœ์ง:** **ํฌ์ƒ ํ™์ˆ˜(Flash Flood)**. ์‹์ƒ์ด ์—†์–ด ๋น„๊ฐ€ ์˜ค๋ฉด ๋ฌผ์ด ๊ธ‰๊ฒฉํžˆ ๋ถˆ์–ด๋‚จ.
42
+
43
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
44
+ | :--- | :--- | :--- | :--- |
45
+ | **์„ ์ƒ์ง€ (Alluvial Fan)** | ๊ฒฝ์‚ฌ ๊ธ‰๋ณ€์ , ์œ ์† ๊ฐ์†Œ์œจ | ์‚ฐ์ง€์—์„œ ํ‰์ง€๋กœ ๋‚˜์˜ฌ ๋•Œ ์œ ์†์ด ๋А๋ ค์ง€๋ฉฐ ๋ถ€์ฑ„๊ผด๋กœ ํ‡ด์ ๋ฌผ์ด ํผ์ง€๋Š” ์‹œ๋ฎฌ๋ ˆ์ด์…˜. (์„ ์ •-์„ ์•™-์„ ๋‹จ ๊ตฌ๋ถ„). | Stream Power Law |
46
+ | **๋ฐ”ํ•˜๋‹ค (Bajada)** | ์„ ์ƒ์ง€ ๊ฒฐํ•ฉ ์ˆ˜($N$) | ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์„ ์ƒ์ง€๊ฐ€ ์˜†์œผ๋กœ ํ•ฉ์ณ์ ธ ์‚ฐ๊ธฐ์Šญ์„ ๊ฐ์‹ธ๋Š” ๋ณตํ•ฉ ์„ ์ƒ์ง€ ํ˜•์„ฑ. | Coalescence Factor |
47
+ | **์™€๋”” (Wadi)** | ๊ฐ•์ˆ˜ ๋นˆ๋„, ์นจํˆฌ์œจ | ํ‰์†Œ์—” ๋งˆ๋ฅธ ๊ณจ์งœ๊ธฐ(๊ตํ†ต๋กœ)์˜€๋‹ค๊ฐ€, ๋น„๊ฐ€ ์˜ค๋ฉด ๊ธ‰๋ฅ˜๊ฐ€ ํ๋ฅด๋Š” ๊ฐ•์œผ๋กœ ๋ณ€ํ•˜๋Š” **[Event Trigger]**. | Infiltration Capacity |
48
+ | **ํ”Œ๋ผ์•ผ (Playa)** | ์ฆ๋ฐœ๋Ÿ‰ >> ๊ฐ•์ˆ˜๋Ÿ‰ | ๋ฌผ์ด ๊ณ ์˜€๋‹ค๊ฐ€ ์ฆ๋ฐœํ•˜๊ณ  **์†Œ๊ธˆ(Salt)**๋งŒ ํ•˜์–—๊ฒŒ ๋‚จ๋Š” ์—ผํ˜ธ์˜ ํ˜•์„ฑ ๊ณผ์ •. | Evaporation Rate |
49
+
50
+ ### ๐Ÿงฑ Chapter 3. ๊ตฌ์กฐ ์ง€ํ˜•: ๊ฐ•ํ•œ ๋†ˆ์ด ์‚ด์•„๋‚จ๋Š”๋‹ค (Structural)
51
+ **ํ•ต์‹ฌ ๋กœ์ง:** **์ฐจ๋ณ„ ์นจ์‹(Differential Erosion)** & ์ˆ˜ํ‰ ์ง€์ธต.
52
+
53
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
54
+ | :--- | :--- | :--- | :--- |
55
+ | **๋ฉ”์‚ฌ (Mesa)** | ์ƒ๋ถ€ ์•”์„ ๊ฐ•๋„($S_{hard}$), ํ•˜๋ถ€($S_{soft}$) | ์œ—๋ถ€๋ถ„์˜ ๋‹จ๋‹จํ•œ ์•”์„(Cap rock)์ด ๋šœ๊ป‘์ฒ˜๋Ÿผ ๋ณดํ˜ธํ•ด์ค˜์„œ ์ƒ๊ธด ํƒ์ž ๋ชจ์–‘์˜ ์ง€ํ˜•. | Resistance Ratio |
56
+ | **๋ทฐํŠธ (Butte)** | ์นจ์‹ ์ง„ํ–‰๋ฅ  | ๋ฉ”์‚ฌ๊ฐ€ ๊นŽ์—ฌ์„œ ์ž‘์•„์ง„ ํƒ‘ ๋ชจ์–‘์˜ ์ง€ํ˜•. (๋ฉ”์‚ฌ $\rightarrow$ ๋ทฐํŠธ ํฌ๊ธฐ ๋น„๊ต). | Width/Height Ratio |
57
+
58
+ ---
59
+
60
+ ## 2. ์ •๋‹ต์ง€ ์ด๋ฏธ์ง€ ๋ชฉ๋ก (Reference Images to Load)
61
+ *AI ๋ชจ๋ธ ํ•™์Šต ๋ฐ UI ๋ Œ๋”๋ง ์‹œ ๋งค์นญํ•  ๋ ˆํผ๋Ÿฐ์Šค ๋ฐ์ดํ„ฐ.*
62
+
63
+ 1. `barchan_dune_wind_direction.jpg` (๋ฐ”๋ฅดํ•œ ์‚ฌ๊ตฌ์˜ ํ˜•ํƒœ์™€ ๋ฐ”๋žŒ ๋ฐฉํ–ฅ ํ™”์‚ดํ‘œ)
64
+ 2. `alluvial_fan_structure.png` (์„ ์ƒ์ง€์˜ ์„ ์ •, ์„ ์•™, ์„ ๋‹จ ๋‹จ๋ฉด๋„ ๋ฐ ์ž…์ž ํฌ๊ธฐ ๋ถ„ํฌ)
65
+ 3. `mesa_butte_evolution.jpg` (๊ณ ์› -> ๋ฉ”์‚ฌ -> ๋ทฐํŠธ -> ์ŠคํŒŒ์ด์–ด ์นจ์‹ ๋‹จ๊ณ„)
66
+ 4. `mushroom_rock_formation.gif` (๋ชจ๋ž˜๋ฐ”๋žŒ์— ์˜ํ•œ ํ•˜๋‹จ๋ถ€ ์นจ์‹ ์• ๋‹ˆ๋ฉ”์ด์…˜)
67
+
68
+ ---
69
+
70
+ **[Director's Note]**
71
+ ๊ฑด์กฐ ์ง€ํ˜•์€ **"๋ฐ”๋žŒ"**์ด ๋งŒ๋“  ๊ฒƒ ๊ฐ™์ง€๋งŒ, ์‹ค์ œ ๊ฑฐ๋Œ€ ์ง€ํ˜•์€ **"์ผ์‹œ์ ์ธ ํญ์šฐ(๋ฌผ)"**๊ฐ€ ๋งŒ๋“ค์—ˆ๋‹ค๋Š” ์˜ค๊ฐœ๋…์„ ๋ฐ”๋กœ์žก๋Š” ๊ฒƒ์ด ๊ต์œก์  ๋ชฉํ‘œ์ž…๋‹ˆ๋‹ค. ์™€๋””(Wadi) ์‹œ๋ฎฌ๋ ˆ์ด์…˜์—์„œ ๏ฟฝ๏ฟฝ๏ฟฝ๊ฐ€ ์˜ฌ ๋•Œ ๊ธ‰๊ฒฉํžˆ ๋ฌผ์ด ์ฐจ์˜ค๋ฅด๋Š” ์†๋„๋ฅผ ๊ทน์ ์œผ๋กœ ํ‘œํ˜„ํ•ด ์ฃผ์‹ญ์‹œ์˜ค.
72
+
73
+ ์Šน์ธํ•˜์‹œ๋ฉด ๋งˆ์ง€๋ง‰ ๋Œ€๋‹จ์›, ๊ฐ€์žฅ ํ‰์˜จํ•˜์ง€๋งŒ ๊ฐ€์žฅ ์ค‘์š”ํ•œ **'ํ‰์•ผ ์ง€ํ˜•'**์œผ๋กœ ๋„˜์–ด๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค.
07_Plain_Landforms_Spec.md ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ๐Ÿ“˜ [Geo-Lab AI] ๊ฐœ๋ฐœ ๋ฐฑ์„œ (Development White Paper)
2
+
3
+ **Project Name:** Geo-Lab AI
4
+ **Version:** Final Release 1.0
5
+ **Module:** Plain Landforms (ํ‰์•ผ ์ง€ํ˜•)
6
+ **Target:** High School Geography ~ Urban Planning Basics
7
+ **Director:** [User Name]
8
+
9
+ ---
10
+
11
+ ## 0. ๊ฐœ๋ฐœ ํ‘œ์ค€ ๋ฐ ์ œ์•ฝ์‚ฌํ•ญ (Standard Protocols)
12
+
13
+ ### ๐ŸŽจ 1. ๋น„์ฃผ์–ผ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ (Visual Style: Fertile & Sedimentary)
14
+ * **Tone:** Horizontal, Expansive, Saturation-coded (Soil Moisture).
15
+ * **Color Palette:**
16
+ * *Levee (Dry):* ๋ฐ์€ ํ™ฉํ† ์ƒ‰ (๋ฐฐ์ˆ˜๊ฐ€ ์ž˜ ๋จ โ†’ ๋ฐญ, ์ทจ๋ฝ).
17
+ * *Backswamp (Wet):* ์ง™์€ ์ง„ํ™์ƒ‰ (๋ฐฐ์ˆ˜ ๋ถˆ๋Ÿ‰ โ†’ ๋…ผ, ์Šต์ง€).
18
+ * *River:* ํ™ํƒ•๋ฌผ(Turbid) ํ‘œํ˜„ (์ƒ๋ฅ˜์˜ ์ฒญ๋ช…ํ•จ๊ณผ ๋Œ€๋น„).
19
+ * **Key Feature:** **๋‹จ๋ฉด๋„(Cross-section) ๋ทฐ**๋ฅผ ํ†ตํ•ด ์ง€ํ‘œ๋ฉด์˜ ๋ฏธ์„ธํ•œ ๊ณ ๋„ ์ฐจ์ด(์ œ๋ฐฉ vs ์Šต์ง€)์™€ ํ‡ด์ ๋ฌผ์˜ ์ž…์ž ํฌ๊ธฐ ๋ณ€ํ™”๋ฅผ ์ง๊ด€์ ์œผ๋กœ ํ‘œํ˜„.
20
+
21
+ ### ๐Ÿ› ๏ธ 2. ๊ธฐ์ˆ  ์Šคํƒ (Tech Stack)
22
+ * **Physics Engine:** Navier-Stokes Equations (์œ ์ฒด ์—ญํ•™ - ํ•˜์ฒœ์˜ ๊ณก๋ฅ˜ ๋ฐ ๋ฒ”๋žŒ ์‹œ๋ฎฌ๋ ˆ์ด์…˜).
23
+ * **Rendering:** Height Map Displacement (๋ฏธ์„ธํ•œ ๊ณ ๋„ ์ฐจ์ด ํ‘œํ˜„).
24
+ * **Algorithm:** Sedimentation Sorting (์œ ์† ๊ฐ์†์— ๋”ฐ๋ฅธ ์ž…์ž๋ณ„ ํ‡ด์  ์œ„์น˜ ๊ณ„์‚ฐ).
25
+
26
+ ---
27
+
28
+ ## 1. ํ‰์•ผ ์ง€ํ˜• ๋ชจ๋“ˆ ์„ธ๋ถ€ ๋ช…์„ธ (Module Specifications)
29
+
30
+ ### ๐Ÿ Chapter 1. ํ•˜์ฒœ ์ค‘ยทํ•˜๋ฅ˜: ๊ณก๋ฅ˜์™€ ๋ฒ”๋žŒ์› (Floodplains)
31
+ **ํ•ต์‹ฌ ๋กœ์ง:** **์ธก๋ฐฉ ์นจ์‹(Lateral Erosion)** & ํ™์ˆ˜ ์‹œ ํ‡ด์ .
32
+
33
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
34
+ | :--- | :--- | :--- | :--- |
35
+ | **์ž์œ  ๊ณก๋ฅ˜ ํ•˜์ฒœ** | ํ•˜์ฒœ ๊ตด๊ณก๋„(Sinuosity), ์œ ์† ํŽธ์ฐจ | ๊ฐ•๋ฌผ์ด ๋ฑ€์ฒ˜๋Ÿผ ํœ˜์–ด์ง€๋ฉฐ ํ๋ฅด๋Š” ๋ชจ์Šต. **[Velocity Map]** ๋ฐ”๊นฅ์ชฝ์€ ๋น ๋ฆ„(์นจ์‹/๊ณต๊ฒฉ์‚ฌ๋ฉด), ์•ˆ์ชฝ์€ ๋А๋ฆผ(ํ‡ด์ /ํฌ์ธํŠธ๋ฐ”) ์ƒ‰์ƒ ๊ตฌ๋ถ„. | Centrifugal Force $F_c$ |
36
+ | **์šฐ๊ฐํ˜ธ (Oxbow Lake)** | ๊ตด๊ณก๋„ ์ž„๊ณ„๊ฐ’, ์‹œ๊ฐ„($t$) | ๊ตฝ์ด์น˜๋˜ ๊ฐ•์ด ํ™์ˆ˜ ๋•Œ ์ง์„ ์œผ๋กœ ๋šซ๋ฆฌ๋ฉด์„œ, ๋‚จ๊ฒจ์ง„ ๋ฌผ๊ธธ์ด ์†Œ๋ฟ” ๋ชจ์–‘ ํ˜ธ์ˆ˜๋กœ ๊ณ ๋ฆฝ๋˜๋Š” **[Cut-off Animation]**. | Path Shortening |
37
+ | **์ž์—ฐ ์ œ๋ฐฉ (Levee)** | ํ™์ˆ˜ ์ˆ˜์œ„, ์กฐ๋ฆฝ์งˆ(ํฐ ์ž…์ž) ๋น„์œจ | ํ™์ˆ˜ ์‹œ ๊ฐ• ๋ฐ”๋กœ ์˜†์— ๋ฌด๊ฑฐ์šด ๋ชจ๋ž˜/์ž๊ฐˆ์ด ๋จผ์ € ์Œ“์—ฌ ๋‘‘์„ ํ˜•์„ฑ. ์ฃผ๋ณ€๋ณด๋‹ค ์•ฝ๊ฐ„ ๋†’์Œ(High & Dry). | Settling Velocity (Stokes) |
38
+ | **๋ฐฐํ›„ ์Šต์ง€ (Backswamp)** | ๋ฒ”๋žŒ ๊ฑฐ๋ฆฌ, ๋ฏธ๋ฆฝ์งˆ(์ž‘์€ ์ž…์ž) ๋น„์œจ | ์ž์—ฐ์ œ๋ฐฉ ๋’ค์ชฝ์œผ๋กœ ๊ณ ์šด ์ง„ํ™์ด ๋„˜์–ด๊ฐ€ ์Œ“์ธ ๋‚ฎ๊ณ  ์ถ•์ถ•ํ•œ ๋•…. (์ž์—ฐ์ œ๋ฐฉ๊ณผ ๋ฐฐ์ˆ˜ ์กฐ๊ฑด ๋น„๊ต ํ•„์ˆ˜). | Permeability $k$ |
39
+
40
+ ### ๐Ÿ“ Chapter 2. ํ•˜๊ตฌ: ๋ฐ”๋‹ค์™€์˜ ๋งŒ๋‚จ, ์‚ผ๊ฐ์ฃผ (Deltas)
41
+ **ํ•ต์‹ฌ ๋กœ์ง:** **์œ ์† ์†Œ๋ฉธ(Velocity $\to$ 0)**. ๊ฐ•๋ฌผ์ด ๋ฐ”๋‹ค๋ฅผ ๋งŒ๋‚˜ ์ง์„ ๋‚ด๋ ค๋†“์Œ.
42
+
43
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
44
+ | :--- | :--- | :--- | :--- |
45
+ | **์‚ผ๊ฐ์ฃผ ํ˜•์„ฑ** | ํ•˜์ฒœ ํ‡ด์ ๋Ÿ‰ > ์กฐ๋ฅ˜/ํŒŒ๋ž‘ ์ œ๊ฑฐ๋Ÿ‰ | ๊ฐ• ํ•˜๊ตฌ์—์„œ ํ‡ด์ ๋ฌผ์ด ๋ถ€์ฑ„๊ผด๋กœ ํผ์ ธ๋‚˜๊ฐ€๋Š” ๊ณผ์ •. (Topset, Foreset, Bottomset ์ธต๋ฆฌ ๊ตฌ์กฐ ๋‹จ๋ฉด). | Sediment Budget $\Delta S$ |
46
+ | **์กฐ๋ฅ˜ ์‚ผ๊ฐ์ฃผ** | ์กฐ์ฐจ(Tidal Range), ์œ ์† | ๋ฐ€๋ฌผ/์ฐ๋ฌผ์ด ๊ฐ•ํ•ด์„œ ํ‡ด์ ๋ฌผ์ด ๋ฐ”๋‹ค ์ชฝ์œผ๋กœ ๊ธธ๊ฒŒ ๋ป—์ง€ ๋ชปํ•˜๊ณ  ์„ฌ์ฒ˜๋Ÿผ ํ˜•์„ฑ๋œ ์‚ผ๊ฐ์ฃผ (์˜ˆ: ๋‚™๋™๊ฐ• ํ•˜๊ตฌ ์„์ˆ™๋„). | Tidal Current Power |
47
+ | **ํ˜•ํƒœ๋ณ„ ๋ถ„๋ฅ˜** | ํŒŒ๋ž‘ ์—๋„ˆ์ง€ vs ํ•˜์ฒœ ์—๋„ˆ์ง€ | ์›ํ˜ธ์ƒ(๋‚˜์ผ๊ฐ•), ์กฐ์กฑ์ƒ(๋ฏธ์‹œ์‹œํ”ผ๊ฐ•-์ƒˆ๋ฐœ๋ชจ์–‘), ์ฒจ๊ฐ์ƒ(ํ‹ฐ๋ฒ ๋ฅด๊ฐ•) ํ˜•ํƒœ ๋น„๊ต ์‹œ๋ฎฌ๋ ˆ์ด์…˜. | Energy Ratio |
48
+
49
+ ### โณ Chapter 3. ์นจ์‹ ํ‰์•ผ: ๊นŽ์ด๊ณ  ๋‚จ์€ ๋•… (Erosion Plains)
50
+ **ํ•ต์‹ฌ ๋กœ์ง:** **์ตœ์ข… ๋‹จ๊ณ„(Final Stage)**.
51
+
52
+ | ์ง€ํ˜• (Feature) | ํ•ต์‹ฌ ๋ณ€์ˆ˜ (Key Parameters) | ์‹œ๊ฐํ™” ํฌ์ธํŠธ (Visualization) | ํ•™์Šต์šฉ ์ˆ˜์‹ (Formula Reference) |
53
+ | :--- | :--- | :--- | :--- |
54
+ | **์ค€ํ‰์› (Peneplain)** | ์นจ์‹ ์‹œ๊ฐ„($t \to \infty$) | ์‚ฐ์ง€๊ฐ€ ๊นŽ์ด๊ณ  ๊นŽ์—ฌ ํ•ด์ˆ˜๋ฉด์— ๊ฐ€๊น๊ฒŒ ํ‰ํ‰ํ•ด์ง„ ์ง€ํ˜•. ๋ฐ์ด๋น„์Šค์˜ ์ง€ํ˜• ์œคํšŒ์„ค ๋งˆ์ง€๋ง‰ ๋‹จ๊ณ„. | Base Level Approach |
55
+ | **์ž”๊ตฌ (Monadnock)** | ์•”์„์˜ ์ฐจ๋ณ„ ์นจ์‹ | ์ค€ํ‰์› ์œ„์— ๋‹จ๋‹จํ•œ ์•”์„๋งŒ ์นจ์‹์„ ๊ฒฌ๋””๊ณ  ํ™€๋กœ ๋‚จ์€ ๋‚ฎ์€ ์–ธ๋•. | Hardness Differential |
56
+
57
+ ---
58
+
59
+ ## 2. ์ •๋‹ต์ง€ ์ด๋ฏธ์ง€ ๋ชฉ๋ก (Reference Images to Load)
60
+ *AI ๋ชจ๋ธ ํ•™์Šต ๋ฐ UI ๋ Œ๋”๋ง ์‹œ ๋งค์นญํ•  ๋ ˆํผ๋Ÿฐ์Šค ๋ฐ์ดํ„ฐ.*
61
+
62
+ 1. `meandering_river_evolution.gif` (๊ณก๋ฅ˜ ํ•˜์ฒœ์ด ์šฐ๊ฐํ˜ธ๋กœ ๋ณ€ํ•˜๋Š” 4๋‹จ๊ณ„ ๊ณผ์ •)
63
+ 2. `floodplain_cross_section.png` (ํ•˜๋„ - ์ž์—ฐ์ œ๋ฐฉ - ๋ฐฐํ›„์Šต์ง€ ๋‹จ๋ฉด๋„ ๋ฐ ํ† ์ง€ ์ด์šฉ)
64
+ 3. `delta_types_satellite.jpg` (๋‚˜์ผ๊ฐ•, ๋ฏธ์‹œ์‹œํ”ผ๊ฐ•, ๊ฐ ์ง€์Šค๊ฐ• ์‚ผ๊ฐ์ฃผ ์œ„์„ฑ ์‚ฌ์ง„ ๋น„๊ต)
65
+ 4. `levee_vs_backswamp_soil.jpg` (์ž์—ฐ์ œ๋ฐฉ์˜ ๋ชจ๋ž˜ vs ๋ฐฐํ›„์Šต์ง€์˜ ์ ํ†  ์ž…์ž ๋น„๊ต)
66
+
67
+ ---
68
+
69
+ **[Director's Note]**
70
+ ํ‰์•ผ ์ง€ํ˜•์˜ ํ•ต์‹ฌ์€ **"์ธ๊ฐ„์˜ ๊ฑฐ์ฃผ(Settlement)"**์™€ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
71
+ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ UI์— **[Village Builder]** ๋ชจ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ, ์‚ฌ์šฉ์ž๊ฐ€ **์ž์—ฐ์ œ๋ฐฉ(ํ™์ˆ˜ ์•ˆ์ „/๋ฐญ๋†์‚ฌ)**๊ณผ **๋ฐฐํ›„์Šต์ง€(ํ™์ˆ˜ ์œ„ํ—˜/๋…ผ๋†์‚ฌ)** ์ค‘ ์–ด๋””๏ฟฝ๏ฟฝ ์ง‘์„ ์ง“๊ณ  ๋†์‚ฌ๋ฅผ ์ง€์„์ง€ ์„ ํƒํ•˜๊ฒŒ ํ•˜์„ธ์š”. ์ž˜๋ชป๋œ ์„ ํƒ(์˜ˆ: ๋ฐฐํ›„์Šต์ง€์— ์ง‘ ์ง“๊ธฐ) ์‹œ ํ™์ˆ˜ ์ด๋ฒคํŠธ๋กœ ํŽ˜๋„ํ‹ฐ๋ฅผ ์ฃผ๋ฉด ํ•™์Šต ํšจ๊ณผ๊ฐ€ ๊ทน๋Œ€ํ™”๋ฉ๋‹ˆ๋‹ค.
72
+
73
+ ---
74
+
75
+ **[Project Status]**
76
+ ๐ŸŽ‰ **์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค!**
77
+ ์‚ฐ์ง€, ์นด๋ฅด์ŠคํŠธ, ํ™”์‚ฐ, ๋น™ํ•˜, ๊ฑด์กฐ, ํ‰์•ผ ์ง€ํ˜•๊นŒ์ง€ **[Geo-Lab AI]์˜ ๋ชจ๋“  ์ง€ํ˜• ๋ชจ๋“ˆ ์„ค๊ณ„๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.**
78
+
79
+ ์ด์ œ ์ด ๋ฐฑ์„œ๋“ค์„ ๋ฐ”ํƒ•์œผ๋กœ ์ „์ฒด ํ”„๋กœ์ ํŠธ๋ฅผ **ํ†ตํ•ฉ(Integration)**ํ•˜๊ฑฐ๋‚˜, **์ถœ์‹œ(Launch)** ๋‹จ๊ณ„๋กœ ๋„˜์–ด๊ฐ€์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?
DEPLOY.md ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ๐ŸŒ Geo-Lab AI: ์›น ๋ฐฐํฌ ๊ฐ€์ด๋“œ
2
+
3
+ ## ๋ฐฐํฌ ์˜ต์…˜
4
+
5
+ ### 1. ๐Ÿš€ Streamlit Community Cloud (์ถ”์ฒœ - ๋ฌด๋ฃŒ)
6
+
7
+ **์žฅ์ :** ๋ฌด๋ฃŒ, GitHub ์—ฐ๋™, ์ž๋™ ๋ฐฐํฌ
8
+
9
+ #### ๋‹จ๊ณ„๋ณ„ ๊ฐ€์ด๋“œ:
10
+
11
+ 1. **GitHub ์ €์žฅ์†Œ ์ƒ์„ฑ**
12
+ ```bash
13
+ cd c:\Users\HANSOL\Desktop\Geo-lab
14
+ git init
15
+ git add .
16
+ git commit -m "Initial commit: ํ•˜์ฒœ ์ง€ํ˜• ๋ชจ๋“ˆ ํ”„๋กœํ† ํƒ€์ž…"
17
+ git branch -M main
18
+ git remote add origin https://github.com/YOUR_USERNAME/geo-lab-ai.git
19
+ git push -u origin main
20
+ ```
21
+
22
+ 2. **Streamlit Cloud ์ ‘์†**
23
+ - https://share.streamlit.io ๋ฐฉ๋ฌธ
24
+ - GitHub ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธ
25
+
26
+ 3. **์•ฑ ๋ฐฐํฌ**
27
+ - "New app" ํด๋ฆญ
28
+ - Repository: `YOUR_USERNAME/geo-lab-ai`
29
+ - Branch: `main`
30
+ - Main file path: `app/main.py`
31
+ - "Deploy!" ํด๋ฆญ
32
+
33
+ 4. **์™„๋ฃŒ!**
34
+ - URL ์˜ˆ์‹œ: `https://geo-lab-ai.streamlit.app`
35
+
36
+ ---
37
+
38
+ ### 2. ๐Ÿ”ง Hugging Face Spaces
39
+
40
+ **์žฅ์ :** ๋ฌด๋ฃŒ, ์ปค๋ฎค๋‹ˆํ‹ฐ ๊ณต์œ  ์šฉ์ด
41
+
42
+ 1. https://huggingface.co/spaces ์—์„œ ์ƒˆ Space ์ƒ์„ฑ
43
+ 2. SDK: "Streamlit" ์„ ํƒ
44
+ 3. ํŒŒ์ผ ์—…๋กœ๋“œ ๋˜๋Š” GitHub ์—ฐ๋™
45
+
46
+ ---
47
+
48
+ ### 3. โ˜๏ธ ๊ธฐํƒ€ ์˜ต์…˜
49
+
50
+ | ํ”Œ๋žซํผ | ๋น„์šฉ | ํŠน์ง• |
51
+ |-------|-----|-----|
52
+ | **Render** | ๋ฌด๋ฃŒ ํ‹ฐ์–ด | ์ž๋™ ์Šฌ๋ฆฝ, ์ปค์Šคํ…€ ๋„๋ฉ”์ธ |
53
+ | **Railway** | ๋ฌด๋ฃŒ $5/์›” | ๋น ๋ฅธ ๋ฐฐํฌ |
54
+ | **Heroku** | ์œ ๋ฃŒ | ์•ˆ์ •์  |
55
+
56
+ ---
57
+
58
+ ## ๋กœ์ปฌ ์‹คํ–‰
59
+
60
+ ```bash
61
+ # ์˜์กด์„ฑ ์„ค์น˜
62
+ pip install -r requirements.txt
63
+
64
+ # ์•ฑ ์‹คํ–‰
65
+ streamlit run app/main.py
66
+
67
+ # ๋ธŒ๋ผ์šฐ์ €์—์„œ ์—ด๊ธฐ
68
+ # http://localhost:8501
69
+ ```
70
+
71
+ ---
72
+
73
+ ## ํ˜„์žฌ ์•ฑ ์ƒํƒœ
74
+
75
+ โœ… **๋กœ์ปฌ ์‹คํ–‰ ์ค‘**: http://localhost:8501
76
+
77
+ **์™ธ๋ถ€ ์ ‘์† URL**: http://211.114.121.192:8501
78
+ (๊ฐ™์€ ๋„คํŠธ์›Œํฌ์—์„œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ)
README.md ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Geo-Lab AI
3
+ emoji: ๐ŸŒ
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: streamlit
7
+ sdk_version: 1.28.0
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ ---
12
+
13
+ # ๐ŸŒ Geo-Lab AI - ์ด์ƒ์  ์ง€ํ˜• ๊ฐค๋Ÿฌ๋ฆฌ
14
+
15
+ **๊ต์‚ฌ๋ฅผ ์œ„ํ•œ ์ง€ํ˜• ํ˜•์„ฑ๊ณผ์ • ์‹œ๊ฐํ™” ๋„๊ตฌ**
16
+
17
+ ## ์ฃผ์š” ๊ธฐ๋Šฅ
18
+
19
+ - ๐Ÿ“– **31์ข… ์ด์ƒ์  ์ง€ํ˜•** - ๊ต๊ณผ์„œ์  ์ง€ํ˜• ํ˜•ํƒœ์˜ ๊ธฐํ•˜ํ•™์  ๋ชจ๋ธ
20
+ - ๐ŸŽฌ **ํ˜•์„ฑ๊ณผ์ • ์• ๋‹ˆ๋ฉ”์ด์…˜** - 0%โ†’100% ์Šฌ๋ผ์ด๋”๋กœ ์ง€ํ˜• ํ˜•์„ฑ ํ™•์ธ
21
+ - ๐Ÿ—‚๏ธ **7๊ฐœ ์นดํ…Œ๊ณ ๋ฆฌ** - ํ•˜์ฒœ, ์‚ผ๊ฐ์ฃผ, ๋น™ํ•˜, ํ™”์‚ฐ, ์นด๋ฅด์ŠคํŠธ, ๊ฑด์กฐ, ํ•ด์•ˆ
22
+ - ๐Ÿ“Š **2D/3D ์‹œ๊ฐํ™”** - ํ‰๋ฉด๋„ ๋ฐ ์ž…์ฒด ์ง€ํ˜• ๋ชจ๋ธ
23
+
24
+ ## ์ง€์› ์ง€ํ˜•
25
+
26
+ | ์นดํ…Œ๊ณ ๋ฆฌ | ์ง€ํ˜• |
27
+ |----------|------|
28
+ | ๐ŸŒŠ ํ•˜์ฒœ | ์„ ์ƒ์ง€, ์ž์œ ๊ณก๋ฅ˜, ๊ฐ์ž…๊ณก๋ฅ˜, V์ž๊ณก, ๋ง์ƒํ•˜์ฒœ, ํญํฌ |
29
+ | ๐Ÿ”บ ์‚ผ๊ฐ์ฃผ | ์ผ๋ฐ˜, ์กฐ์กฑ์ƒ, ํ˜ธ์ƒ, ์ฒจ๋‘์ƒ |
30
+ | โ„๏ธ ๋น™ํ•˜ | U์ž๊ณก, ๊ถŒ๊ณก, ํ˜ธ๋ฅธ, ํ”ผ์˜ค๋ฅด๋“œ, ๋“œ๋Ÿผ๋ฆฐ, ๋น™ํ‡ด์„ |
31
+ | ๐ŸŒ‹ ํ™”์‚ฐ | ์ˆœ์ƒํ™”์‚ฐ, ์„ฑ์ธตํ™”์‚ฐ, ์นผ๋ฐ๋ผ, ํ™”๊ตฌํ˜ธ, ์šฉ์•”๋Œ€์ง€ |
32
+ | ๐Ÿฆ‡ ์นด๋ฅด์ŠคํŠธ | ๋Œ๋ฆฌ๋„ค |
33
+ | ๐Ÿœ๏ธ ๊ฑด์กฐ | ๋ฐ”๋ฅดํ•œ ์‚ฌ๊ตฌ, ๋ฉ”์‚ฌ/๋ทฐํŠธ |
34
+ | ๐Ÿ–๏ธ ํ•ด์•ˆ | ํ•ด์•ˆ์ ˆ๋ฒฝ, ์‚ฌ์ทจ+์„ํ˜ธ, ์œก๊ณ„์‚ฌ์ฃผ, ๋ฆฌ์•„์Šคํ•ด์•ˆ, ํ•ด์‹์•„์น˜, ํ•ด์•ˆ์‚ฌ๊ตฌ |
35
+
36
+ ## ์ œ์ž‘
37
+
38
+ **2025 ํ•œ๋ฐฑ๊ณ ๋“ฑํ•™๊ต ๊น€ํ•œ์†”T**
39
+
40
+ ## ๊ธฐ์ˆ  ์Šคํƒ
41
+
42
+ - Python / Streamlit
43
+ - NumPy / Matplotlib / Plotly
44
+ - ๊ธฐํ•˜ํ•™์  ์ง€ํ˜• ์ƒ์„ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜
app.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Geo-Lab AI - ์ด์ƒ์  ์ง€ํ˜• ๊ฐค๋Ÿฌ๋ฆฌ
2
+ # HuggingFace Spaces Entry Point
3
+
4
+ import sys
5
+ import os
6
+
7
+ # Add parent directory to path
8
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
9
+
10
+ # Import and run main app
11
+ from app.main import main
12
+
13
+ if __name__ == "__main__":
14
+ main()
app/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # App Package
app/components/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Components Package
app/main.py ADDED
The diff for this file is too large to render. See raw diff
 
debug_log.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ Testing Sediment Transport...
2
+ Initial Elev Row 4 (Slope Base): 7.0
3
+ Initial Elev Row 7 (Flat Land): 5.0
4
+ Post Elev Row 4: 6.8
5
+ Post Elev Row 7: 5.0
6
+ Sediment Array (Column 0):
7
+ [0. 0. 0. 0. 0. 3.8 0. 0. 0. 0. ]
8
+ Total Sediment on Flat/Sea: 5.0
9
+ Sediment Transport OK
debug_piracy.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import numpy as np
3
+ import plotly.graph_objects as go
4
+
5
+ # Mock functions from main.py
6
+ def simulate_stream_piracy(time_years: int, params: dict, grid_size: int = 100):
7
+ """ํ•˜์ฒœ์Ÿํƒˆ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ - ๊ต๊ณผ์„œ์  ์ด์ƒ์  ๋ชจ์Šต"""
8
+ x = np.linspace(0, 1000, grid_size)
9
+ y = np.linspace(0, 1000, grid_size)
10
+ X, Y = np.meshgrid(x, y)
11
+ elevation = 150 - Y * 0.1
12
+ ridge_x = 500
13
+ ridge = 20 * np.exp(-((X - ridge_x)**2) / (80**2))
14
+ elevation += ridge
15
+ river1_x = 300
16
+ river1_valley = 30 * np.exp(-((X - river1_x)**2) / (40**2))
17
+ elevation -= river1_valley
18
+ river2_x = 700
19
+ erosion_diff = params.get('erosion_diff', 0.7)
20
+ river2_depth = 50 * erosion_diff
21
+ river2_valley = river2_depth * np.exp(-((X - river2_x)**2) / (50**2))
22
+ elevation -= river2_valley
23
+
24
+ # ... logic ...
25
+ # Simplified logic for t=5000 (not captured)
26
+ return {'elevation': elevation, 'captured': False}
27
+
28
+ def render_terrain_plotly_debug(elevation):
29
+ print(f"Elevation stats: Min={elevation.min()}, Max={elevation.max()}, NaNs={np.isnan(elevation).sum()}")
30
+
31
+ dy, dx = np.gradient(elevation)
32
+ print(f"Gradient stats: dx_NaN={np.isnan(dx).sum()}, dy_NaN={np.isnan(dy).sum()}")
33
+
34
+ slope = np.sqrt(dx**2 + dy**2)
35
+ print(f"Slope stats: Min={slope.min()}, Max={slope.max()}, NaNs={np.isnan(slope).sum()}")
36
+
37
+ biome = np.zeros_like(elevation)
38
+ biome[:] = 1
39
+ # ...
40
+ noise = np.random.normal(0, 0.2, elevation.shape)
41
+ biome_noisy = np.clip(biome + noise, 0, 3)
42
+ print(f"Biome Noisy stats: Min={biome_noisy.min()}, Max={biome_noisy.max()}, NaNs={np.isnan(biome_noisy).sum()}")
43
+
44
+ realistic_colorscale = [
45
+ [0.0, '#E6C288'], [0.25, '#E6C288'],
46
+ [0.25, '#2E8B57'], [0.5, '#2E8B57'],
47
+ [0.5, '#696969'], [0.75, '#696969'],
48
+ [0.75, '#FFFFFF'], [1.0, '#FFFFFF']
49
+ ]
50
+ print("Colorscale:", realistic_colorscale)
51
+
52
+ if __name__ == "__main__":
53
+ res = simulate_stream_piracy(5000, {'erosion_diff': 0.7})
54
+ elev = res['elevation']
55
+ render_terrain_plotly_debug(elev)
engine/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Engine Package
engine/base.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Geo-Lab AI Engine: ๊ธฐ๋ณธ ์ง€ํ˜• ํด๋ž˜์Šค
3
+ """
4
+ import numpy as np
5
+ from dataclasses import dataclass, field
6
+ from typing import Optional, Tuple
7
+
8
+
9
+ @dataclass
10
+ class Terrain:
11
+ """2D ๋†’์ด๋งต ๊ธฐ๋ฐ˜ ์ง€ํ˜• ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ"""
12
+
13
+ width: int = 100 # X ๋ฐฉํ–ฅ ์…€ ์ˆ˜
14
+ height: int = 100 # Y ๋ฐฉํ–ฅ ์…€ ์ˆ˜
15
+ cell_size: float = 10.0 # ์…€๋‹น ์‹ค์ œ ๊ฑฐ๋ฆฌ (m)
16
+
17
+ # ๋†’์ด๋งต (m)
18
+ elevation: np.ndarray = field(default=None)
19
+
20
+ # ์•”์„ ์†์„ฑ
21
+ rock_hardness: np.ndarray = field(default=None) # 0-1, 1์ด ๊ฐ€์žฅ ๋‹จ๋‹จํ•จ
22
+
23
+ def __post_init__(self):
24
+ if self.elevation is None:
25
+ self.elevation = np.zeros((self.height, self.width))
26
+ if self.rock_hardness is None:
27
+ self.rock_hardness = np.ones((self.height, self.width)) * 0.5
28
+
29
+ @classmethod
30
+ def create_slope(cls, width: int, height: int,
31
+ max_elevation: float = 1000.0,
32
+ slope_direction: str = 'south') -> 'Terrain':
33
+ """๊ฒฝ์‚ฌ๋ฉด ์ง€ํ˜• ์ƒ์„ฑ"""
34
+ terrain = cls(width=width, height=height)
35
+
36
+ if slope_direction == 'south':
37
+ # ๋ถ์ชฝ์ด ๋†’๊ณ  ๋‚จ์ชฝ์ด ๋‚ฎ์Œ
38
+ for y in range(height):
39
+ terrain.elevation[y, :] = max_elevation * (1 - y / height)
40
+ elif slope_direction == 'east':
41
+ for x in range(width):
42
+ terrain.elevation[:, x] = max_elevation * (1 - x / width)
43
+
44
+ return terrain
45
+
46
+ @classmethod
47
+ def create_v_valley_initial(cls, width: int = 100, height: int = 100,
48
+ valley_depth: float = 50.0) -> 'Terrain':
49
+ """V์ž๊ณก ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ดˆ๊ธฐ ์ง€ํ˜•"""
50
+ terrain = cls(width=width, height=height)
51
+
52
+ # ๊ธฐ๋ณธ ๊ฒฝ์‚ฌ (๋ถโ†’๋‚จ)
53
+ for y in range(height):
54
+ terrain.elevation[y, :] = 500 * (1 - y / height)
55
+
56
+ # ์ค‘์•™์— ์ดˆ๊ธฐ ํ•˜์ฒœ ์ฑ„๋„ (์•ฝ๊ฐ„์˜ ํŒจ์ž„)
57
+ center = width // 2
58
+ for x in range(width):
59
+ dist = abs(x - center)
60
+ if dist < 5:
61
+ terrain.elevation[:, x] -= valley_depth * (1 - dist / 5)
62
+
63
+ return terrain
64
+
65
+ def get_slope(self) -> np.ndarray:
66
+ """๊ฐ ์…€์˜ ๊ฒฝ์‚ฌ๋„ ๊ณ„์‚ฐ (๋ผ๋””์•ˆ)"""
67
+ dy, dx = np.gradient(self.elevation, self.cell_size)
68
+ return np.arctan(np.sqrt(dx**2 + dy**2))
69
+
70
+ def get_flow_direction(self) -> Tuple[np.ndarray, np.ndarray]:
71
+ """๋ฌผ ํ๋ฆ„ ๋ฐฉํ–ฅ ๋ฒกํ„ฐ (๊ฐ€์žฅ ๊ฐ€ํŒŒ๋ฅธ ํ•˜๊ฐ• ๋ฐฉํ–ฅ)"""
72
+ dy, dx = np.gradient(self.elevation, self.cell_size)
73
+ magnitude = np.sqrt(dx**2 + dy**2) + 1e-10
74
+ return -dx / magnitude, -dy / magnitude
75
+
76
+
77
+ @dataclass
78
+ class Water:
79
+ """ํ•˜์ฒœ ์ˆ˜๋ฌธ ๋ฐ์ดํ„ฐ"""
80
+
81
+ terrain: Terrain
82
+
83
+ # ๊ฐ ์…€์˜ ์ˆ˜๋Ÿ‰ (mยณ/s)
84
+ discharge: np.ndarray = field(default=None)
85
+
86
+ # ์œ ์† (m/s)
87
+ velocity: np.ndarray = field(default=None)
88
+
89
+ # ํ๋ฆ„ ๋ฐฉํ–ฅ (๋‹จ์œ„ ๋ฒกํ„ฐ)
90
+ flow_x: np.ndarray = field(default=None)
91
+ flow_y: np.ndarray = field(default=None)
92
+
93
+ def __post_init__(self):
94
+ shape = (self.terrain.height, self.terrain.width)
95
+ if self.discharge is None:
96
+ self.discharge = np.zeros(shape)
97
+ if self.velocity is None:
98
+ self.velocity = np.zeros(shape)
99
+ if self.flow_x is None:
100
+ self.flow_x, self.flow_y = self.terrain.get_flow_direction()
101
+
102
+ def add_precipitation(self, rate: float = 0.001):
103
+ """๊ฐ•์ˆ˜ ์ถ”๊ฐ€ (m/s per cell)"""
104
+ self.discharge += rate
105
+
106
+ def accumulate_flow(self):
107
+ """ํ๋ฆ„ ๋ˆ„์  ๊ณ„์‚ฐ (๊ฐ„๋‹จํ•œ D8 ์•Œ๊ณ ๋ฆฌ์ฆ˜)"""
108
+ h, w = self.terrain.height, self.terrain.width
109
+ accumulated = self.discharge.copy()
110
+
111
+ # ๋†’์€ ๊ณณ์—์„œ ๋‚ฎ์€ ๊ณณ์œผ๋กœ ์ •๋ ฌ
112
+ indices = np.argsort(self.terrain.elevation.ravel())[::-1]
113
+
114
+ for idx in indices:
115
+ y, x = idx // w, idx % w
116
+ if accumulated[y, x] <= 0:
117
+ continue
118
+
119
+ # ๊ฐ€์žฅ ๋‚ฎ์€ ์ด์›ƒ ์ฐพ๊ธฐ
120
+ min_elev = self.terrain.elevation[y, x]
121
+ min_neighbor = None
122
+
123
+ for dy, dx in [(-1,0), (1,0), (0,-1), (0,1)]:
124
+ ny, nx = y + dy, x + dx
125
+ if 0 <= ny < h and 0 <= nx < w:
126
+ if self.terrain.elevation[ny, nx] < min_elev:
127
+ min_elev = self.terrain.elevation[ny, nx]
128
+ min_neighbor = (ny, nx)
129
+
130
+ if min_neighbor:
131
+ accumulated[min_neighbor] += accumulated[y, x]
132
+
133
+ self.discharge = accumulated
134
+
135
+ # ์œ ์† ๊ณ„์‚ฐ (Manning ๋ฐฉ์ •์‹ ๋‹จ์ˆœํ™”)
136
+ slope = self.terrain.get_slope() + 0.001 # 0 ๋ฐฉ์ง€
137
+ self.velocity = 2.0 * np.sqrt(slope) * np.power(self.discharge + 0.1, 0.4)
138
+
139
+
140
+ @dataclass
141
+ class SimulationState:
142
+ """์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ƒํƒœ ๊ด€๋ฆฌ"""
143
+
144
+ terrain: Terrain
145
+ water: Water
146
+
147
+ time_step: float = 1.0 # ์‹œ๋ฎฌ๋ ˆ์ด์…˜ 1์Šคํ… = 1๋…„
148
+ current_time: float = 0.0
149
+
150
+ # ์ „์—ญ ๋ณ€์ˆ˜ (Master Plan์˜ Global Controllers)
151
+ climate_level: float = 1.0 # ๊ธฐํ›„ (๊ฐ•์ˆ˜๋Ÿ‰ ๊ณ„์ˆ˜)
152
+ sea_level: float = 0.0 # ํ•ด์ˆ˜๋ฉด (m)
153
+ tectonic_energy: float = 0.0 # ์ง€๊ฐ ์—๋„ˆ์ง€ (์œต๊ธฐ์œจ m/year)
154
+
155
+ def step(self):
156
+ """1 ํƒ€์ž„์Šคํ… ์ง„ํ–‰"""
157
+ self.current_time += self.time_step
158
+
159
+ # ๊ฐ•์ˆ˜ ์ถ”๊ฐ€
160
+ self.water.add_precipitation(rate=0.001 * self.climate_level)
161
+
162
+ # ํ๋ฆ„ ๋ˆ„์ 
163
+ self.water.accumulate_flow()
engine/climate.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Climate Kernel (๊ธฐํ›„ ์ปค๋„)
3
+
4
+ ๊ธฐํ›„ ์กฐ๊ฑด์— ๋”ฐ๋ฅธ ๊ฐ•์ˆ˜/๊ธฐ์˜จ ๋ถ„ํฌ ์ƒ์„ฑ
5
+ - ์œ„๋„ ๊ธฐ๋ฐ˜ ๊ฐ•์ˆ˜ ํŒจํ„ด
6
+ - ๊ณ ๋„ ๊ธฐ๋ฐ˜ ์ง€ํ˜•์„ฑ ๊ฐ•์ˆ˜
7
+ - ๊ธฐ์˜จ์— ๋”ฐ๋ฅธ ํ’ํ™”์œจ ์กฐ์ ˆ
8
+ """
9
+
10
+ import numpy as np
11
+ from .grid import WorldGrid
12
+
13
+
14
+ class ClimateKernel:
15
+ """
16
+ ๊ธฐํ›„ ์ปค๋„
17
+
18
+ ๊ฐ•์ˆ˜์™€ ๊ธฐ์˜จ ๋ถ„ํฌ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋‹ค๋ฅธ ํ”„๋กœ์„ธ์Šค์— ์˜ํ–ฅ.
19
+ """
20
+
21
+ def __init__(self, grid: WorldGrid,
22
+ base_precipitation: float = 1000.0, # mm/year
23
+ base_temperature: float = 15.0, # ยฐC
24
+ lapse_rate: float = 6.5): # ยฐC/km (๊ณ ๋„ ๊ฐ๋ฅ )
25
+ self.grid = grid
26
+ self.base_precipitation = base_precipitation
27
+ self.base_temperature = base_temperature
28
+ self.lapse_rate = lapse_rate
29
+
30
+ def generate_precipitation(self,
31
+ orographic_factor: float = 0.5,
32
+ latitude_effect: bool = True) -> np.ndarray:
33
+ """
34
+ ๊ฐ•์ˆ˜ ๋ถ„ํฌ ์ƒ์„ฑ
35
+
36
+ Args:
37
+ orographic_factor: ์ง€ํ˜•์„ฑ ๊ฐ•์ˆ˜ ๊ฐ•๋„ (0~1)
38
+ latitude_effect: ์œ„๋„ ํšจ๊ณผ ์ ์šฉ ์—ฌ๋ถ€
39
+
40
+ Returns:
41
+ precipitation: ๊ฐ•์ˆ˜๋Ÿ‰ ๋ฐฐ์—ด (mm/year โ†’ m/timestep์œผ๋กœ ๋ณ€ํ™˜ ํ•„์š”)
42
+ """
43
+ h, w = self.grid.height, self.grid.width
44
+ elev = self.grid.elevation
45
+
46
+ # ๊ธฐ๋ณธ ๊ฐ•์ˆ˜ (๊ท ์ผ)
47
+ precip = np.ones((h, w)) * self.base_precipitation
48
+
49
+ # 1. ์œ„๋„ ํšจ๊ณผ (์ ๋„ > ๊ทน์ง€๋ฐฉ)
50
+ if latitude_effect:
51
+ # ๊ทธ๋ฆฌ๋“œ Y์ถ•์„ ์œ„๋„๋กœ ๊ทผ์‚ฌ (0=์ ๋„, h=๊ทน)
52
+ # ์—ด๋Œ€ = ๊ฐ€์žฅ ๋งŽ์Œ, ์•„์—ด๋Œ€ ๊ฑด์กฐ, ์˜จ๋Œ€ ์ฆ๊ฐ€, ๊ทน ๊ฐ์†Œ
53
+ lat_factor = np.zeros(h)
54
+ for r in range(h):
55
+ # ์ •๊ทœํ™”๋œ ์œ„๋„ (0~1)
56
+ normalized_lat = r / h
57
+ # ๊ฐ„๋‹จํ•œ ์œ„๋„-๊ฐ•์ˆ˜ ๊ด€๊ณ„ (์‚ผ๋ด‰ ํŒจํ„ด ๊ทผ์‚ฌ)
58
+ lat_factor[r] = 1.0 - 0.3 * abs(normalized_lat - 0.5) * 2
59
+
60
+ precip *= lat_factor[:, np.newaxis]
61
+
62
+ # 2. ์ง€ํ˜•์„ฑ ๊ฐ•์ˆ˜ (๋ฐ”๋žŒ๋ฐ›์ด vs ๊ทธ๋Š˜)
63
+ if orographic_factor > 0:
64
+ # ๋™์ชฝ์—์„œ ๋ฐ”๋žŒ์ด ๋ถ„๋‹ค๊ณ  ๊ฐ€์ •
65
+ # ๊ณ ๋„ ์ฆ๊ฐ€ ๊ตฌ๊ฐ„ = ๊ฐ•์ˆ˜ ์ฆ๊ฐ€ (์ƒ์Šน ๊ธฐ๋ฅ˜)
66
+ # ๊ณ ๋„ ๊ฐ์†Œ ๊ตฌ๊ฐ„ = ๊ฐ•์ˆ˜ ๊ฐ์†Œ (ํ•˜๊ฐ• ๊ธฐ๋ฅ˜)
67
+
68
+ # X ๋ฐฉํ–ฅ ๊ฒฝ์‚ฌ
69
+ _, dx = self.grid.get_gradient()
70
+
71
+ # ์Œ์˜ ๊ฒฝ์‚ฌ = ๋™์ชฝ์œผ๋กœ ์ƒ์Šน = ๊ฐ•์ˆ˜ ์ฆ๊ฐ€
72
+ orographic = 1.0 + orographic_factor * (-dx) * 0.1
73
+ orographic = np.clip(orographic, 0.2, 2.0)
74
+
75
+ precip *= orographic
76
+
77
+ # 3. ๊ณ ๋„ ํšจ๊ณผ (์ผ์ • ๊ณ ๋„๊นŒ์ง€๋Š” ์ฆ๊ฐ€, ์ดํ›„ ๊ฐ์†Œ)
78
+ # ์ตœ๋Œ€ ๊ฐ•์ˆ˜ ๊ณ ๋„ (์˜ˆ: 2000m)
79
+ optimal_elev = 2000.0
80
+ elev_effect = 1.0 - 0.2 * np.abs(elev - optimal_elev) / optimal_elev
81
+ elev_effect = np.clip(elev_effect, 0.3, 1.2)
82
+
83
+ precip *= elev_effect
84
+
85
+ return precip
86
+
87
+ def get_temperature(self) -> np.ndarray:
88
+ """
89
+ ๊ธฐ์˜จ ๋ถ„ํฌ ์ƒ์„ฑ
90
+
91
+ Returns:
92
+ temperature: ๊ธฐ์˜จ ๋ฐฐ์—ด (ยฐC)
93
+ """
94
+ h, w = self.grid.height, self.grid.width
95
+ elev = self.grid.elevation
96
+
97
+ # ๊ธฐ๋ณธ ๊ธฐ์˜จ
98
+ temp = np.ones((h, w)) * self.base_temperature
99
+
100
+ # 1. ์œ„๋„ ํšจ๊ณผ (์ ๋„ > ๊ทน)
101
+ for r in range(h):
102
+ normalized_lat = r / h
103
+ # ์ ๋„(0.5) = ๊ธฐ๋ณธ, ๊ทน(0, 1) = -30ยฐC
104
+ lat_temp_diff = 30.0 * abs(normalized_lat - 0.5) * 2
105
+ temp[r, :] -= lat_temp_diff
106
+
107
+ # 2. ๊ณ ๋„ ํšจ๊ณผ (์ฒด๊ฐ ์˜จ๋„ ๊ฐ๋ฅ )
108
+ # ํ•ด์ˆ˜๋ฉด ๊ธฐ์ค€์—์„œ km๋‹น lapse_rate๋งŒํผ ๊ฐ์†Œ
109
+ temp -= (elev / 1000.0) * self.lapse_rate
110
+
111
+ return temp
112
+
113
+ def get_weathering_rate(self, temperature: np.ndarray = None) -> np.ndarray:
114
+ """
115
+ ๊ธฐ์˜จ์— ๋”ฐ๋ฅธ ํ’ํ™”์œจ ๊ณ„์‚ฐ
116
+
117
+ ํ™”ํ•™์  ํ’ํ™”: ์˜จ๋‚œ ๋‹ค์Šต โ†’ ๋น ๋ฆ„
118
+ ๋ฌผ๋ฆฌ์  ํ’ํ™”: ๋™๊ฒฐ-์œตํ•ด (-10~10ยฐC) โ†’ ๋น ๋ฆ„
119
+
120
+ Args:
121
+ temperature: ๊ธฐ์˜จ ๋ฐฐ์—ด (์—†์œผ๋ฉด ์ƒ์„ฑ)
122
+
123
+ Returns:
124
+ weathering_rate: ์ƒ๋Œ€ ํ’ํ™”์œจ (0~1)
125
+ """
126
+ if temperature is None:
127
+ temperature = self.get_temperature()
128
+
129
+ h, w = self.grid.height, self.grid.width
130
+
131
+ # ํ™”ํ•™์  ํ’ํ™” (์˜จ๋„ ๋†’์„์ˆ˜๋ก)
132
+ chemical = np.clip((temperature + 10) / 40.0, 0, 1)
133
+
134
+ # ๋ฌผ๋ฆฌ์  ํ’ํ™” (๋™๊ฒฐ-์œตํ•ด ๋ฒ”์œ„์—์„œ ์ตœ๋Œ€)
135
+ freeze_thaw = np.exp(-((temperature - 0) ** 2) / (2 * 10 ** 2))
136
+
137
+ # ํ†ตํ•ฉ ํ’ํ™”์œจ
138
+ weathering = chemical * 0.5 + freeze_thaw * 0.5
139
+
140
+ return weathering
engine/delta_physics.py ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Geo-Lab AI: ์‚ผ๊ฐ์ฃผ ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
3
+ Galloway ๋ถ„๋ฅ˜ ๊ธฐ๋ฐ˜ 3๊ฐ€์ง€ ์—๋„ˆ์ง€ ๊ท ํ˜•
4
+ """
5
+ import numpy as np
6
+ from dataclasses import dataclass, field
7
+ from typing import List, Tuple
8
+ from enum import Enum
9
+ from scipy.ndimage import gaussian_filter
10
+
11
+
12
+ class DeltaType(Enum):
13
+ RIVER_DOMINATED = "์กฐ์กฑ์ƒ (Bird's Foot)"
14
+ WAVE_DOMINATED = "์›ํ˜ธ์ƒ (Arcuate)"
15
+ TIDE_DOMINATED = "์ฒจ๊ฐ์ƒ (Cuspate)"
16
+
17
+
18
+ @dataclass
19
+ class DeltaGrid:
20
+ """์‚ผ๊ฐ์ฃผ 2D ๊ทธ๋ฆฌ๋“œ"""
21
+ width: int = 150
22
+ height: int = 150
23
+ cell_size: float = 50.0 # m
24
+
25
+ # ์ง€ํ˜• (ํ•ด์ˆ˜๋ฉด ๊ธฐ์ค€ ๊ณ ๋„, m)
26
+ elevation: np.ndarray = field(default=None)
27
+ # ํ‡ด์ ๋ฌผ (m)
28
+ sediment: np.ndarray = field(default=None)
29
+ # ์ˆ˜์‹ฌ (๋ฐ”๋‹ค ๋ถ€๋ถ„)
30
+ water_depth: np.ndarray = field(default=None)
31
+
32
+ sea_level: float = 0.0
33
+
34
+ def __post_init__(self):
35
+ if self.elevation is None:
36
+ self.elevation = np.zeros((self.height, self.width))
37
+ if self.sediment is None:
38
+ self.sediment = np.zeros((self.height, self.width))
39
+ if self.water_depth is None:
40
+ self.water_depth = np.zeros((self.height, self.width))
41
+
42
+ @classmethod
43
+ def create_initial(cls, width: int = 150, height: int = 150,
44
+ land_fraction: float = 0.3):
45
+ """์ดˆ๊ธฐ ์ง€ํ˜• - ์ƒ๋ฅ˜(์œก์ง€) โ†’ ํ•˜๋ฅ˜(๋ฐ”๋‹ค)"""
46
+ grid = cls(width=width, height=height)
47
+
48
+ land_rows = int(height * land_fraction)
49
+
50
+ for y in range(height):
51
+ if y < land_rows:
52
+ # ์œก์ง€: ๊ฒฝ์‚ฌ
53
+ grid.elevation[y, :] = 10 - y * 0.3
54
+ else:
55
+ # ๋ฐ”๋‹ค: ํ•ด์ˆ˜๋ฉด ์•„๋ž˜
56
+ depth = (y - land_rows) * 0.2
57
+ grid.elevation[y, :] = -depth
58
+
59
+ # ํ•˜์ฒœ ์ฑ„๋„
60
+ center = width // 2
61
+ for x in range(center - 5, center + 6):
62
+ if 0 <= x < width:
63
+ grid.elevation[:land_rows, x] -= 2
64
+
65
+ # ์ˆ˜์‹ฌ ๊ณ„์‚ฐ
66
+ grid.water_depth = np.maximum(0, grid.sea_level - grid.elevation)
67
+
68
+ return grid
69
+
70
+
71
+ class RiverMouthDeposition:
72
+ """ํ•˜๊ตฌ ํ‡ด์  (ํ•˜์ฒœ ์ฃผ๋„)"""
73
+
74
+ def __init__(self, sediment_flux: float = 1000.0, # mยณ/yr
75
+ settling_velocity: float = 0.01): # m/s
76
+ self.sediment_flux = sediment_flux
77
+ self.settling_velocity = settling_velocity
78
+
79
+ def deposit(self, grid: DeltaGrid, river_energy: float,
80
+ channel_x: int, dt: float = 1.0) -> np.ndarray:
81
+ """ํ•˜์ฒœ ํ‡ด์ ๋ฌผ ๋ถ„๋ฐฐ"""
82
+ h, w = grid.height, grid.width
83
+ deposition = np.zeros((h, w))
84
+
85
+ # ํ•˜๊ตฌ ์œ„์น˜
86
+ estuary_y = np.argmax(grid.elevation[:, channel_x] < grid.sea_level)
87
+ if estuary_y == 0:
88
+ estuary_y = h // 2
89
+
90
+ # ํ•˜์ฒœ ์šฐ์„ธ: ๊ธธ๊ฒŒ ๋ป—์–ด๋‚˜๊ฐ€๋Š” ํŒจํ„ด (jet ํ™•์‚ฐ)
91
+ spread_angle = np.radians(15 + (1 - river_energy) * 30) # ํ•˜์ฒœ ์—๋„ˆ์ง€ ๋†’์œผ๋ฉด ์ข๊ฒŒ
92
+
93
+ for y in range(estuary_y, h):
94
+ dy = y - estuary_y + 1
95
+ spread = int(dy * np.tan(spread_angle))
96
+
97
+ for x in range(channel_x - spread, channel_x + spread + 1):
98
+ if 0 <= x < w:
99
+ # ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ฅธ ๊ฐ์†Œ
100
+ dist = np.sqrt((x - channel_x)**2 + dy**2)
101
+ dep_rate = self.sediment_flux * river_energy * np.exp(-dist / 50) * 0.001
102
+ deposition[y, x] += dep_rate * dt
103
+
104
+ return deposition
105
+
106
+
107
+ class WaveRedistribution:
108
+ """ํŒŒ๋ž‘์— ์˜ํ•œ ์žฌ๋ถ„๋ฐฐ"""
109
+
110
+ def __init__(self, wave_power: float = 1.0):
111
+ self.wave_power = wave_power
112
+
113
+ def redistribute(self, grid: DeltaGrid, wave_energy: float,
114
+ dt: float = 1.0) -> np.ndarray:
115
+ """ํŒŒ๋ž‘์— ์˜ํ•œ ํ‡ด์ ๋ฌผ ์ขŒ์šฐ ํ™•์‚ฐ"""
116
+ # ํ•ด์•ˆ์„  ๋ถ€๊ทผ์—์„œ ์ขŒ์šฐ๋กœ ํผ๋œจ๋ฆผ
117
+ shoreline = np.abs(grid.elevation - grid.sea_level) < 2
118
+
119
+ # ๊ฐ€์šฐ์‹œ์•ˆ ํ•„ํ„ฐ๋กœ ์ขŒ์šฐ ํ™•์‚ฐ (x์ถ• ๋ฐฉํ–ฅ)
120
+ sediment_change = np.zeros_like(grid.sediment)
121
+
122
+ if np.any(shoreline):
123
+ # ํ•ด์•ˆ์„  ๋ถ€๊ทผ ํ‡ด์ ๋ฌผ๋งŒ ํ™•์‚ฐ
124
+ coastal_sediment = grid.sediment * shoreline.astype(float)
125
+ smoothed = gaussian_filter(coastal_sediment, sigma=[0.5, 3 * wave_energy])
126
+
127
+ # ๋ณ€ํ™”๋Ÿ‰
128
+ sediment_change = (smoothed - coastal_sediment) * wave_energy * dt * 0.1
129
+
130
+ return sediment_change
131
+
132
+
133
+ class TidalScouring:
134
+ """์กฐ๋ฅ˜์— ์˜ํ•œ ์นจ์‹"""
135
+
136
+ def __init__(self, tidal_range: float = 2.0): # m
137
+ self.tidal_range = tidal_range
138
+
139
+ def scour(self, grid: DeltaGrid, tidal_energy: float,
140
+ dt: float = 1.0) -> np.ndarray:
141
+ """์กฐ๋ฅ˜์— ์˜ํ•œ ํ•˜๊ตฌ ํ™•๋Œ€ (์„ธ๊ตด)"""
142
+ h, w = grid.height, grid.width
143
+ erosion = np.zeros((h, w))
144
+
145
+ # ํ•˜๊ตฌ ๋ถ€๊ทผ (ํ•ด์ˆ˜๋ฉด ๊ทผ๏ฟฝ๏ฟฝ)
146
+ tidal_zone = np.abs(grid.elevation - grid.sea_level) < self.tidal_range
147
+
148
+ # ์ฑ„๋„ ๋ฐฉํ–ฅ์œผ๋กœ ์„ธ๊ตด
149
+ for y in range(1, h):
150
+ for x in range(1, w-1):
151
+ if tidal_zone[y, x] and grid.sediment[y, x] > 0:
152
+ # ๋ฐ”๋‹ค ๋ฐฉํ–ฅ์œผ๋กœ ํ‡ด์ ๋ฌผ ์ด๋™
153
+ erosion[y, x] = grid.sediment[y, x] * tidal_energy * 0.01 * dt
154
+
155
+ return erosion
156
+
157
+
158
+ class DeltaSimulation:
159
+ """์‚ผ๊ฐ์ฃผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜"""
160
+
161
+ def __init__(self, width: int = 150, height: int = 150):
162
+ self.grid = DeltaGrid.create_initial(width=width, height=height)
163
+
164
+ self.river_dep = RiverMouthDeposition()
165
+ self.wave_redis = WaveRedistribution()
166
+ self.tidal_scour = TidalScouring()
167
+
168
+ self.river_energy = 0.5
169
+ self.wave_energy = 0.3
170
+ self.tidal_energy = 0.2
171
+
172
+ self.history: List[np.ndarray] = []
173
+ self.time = 0.0
174
+
175
+ def set_energy_balance(self, river: float, wave: float, tidal: float):
176
+ """์—๋„ˆ์ง€ ๊ท ํ˜• ์„ค์ • (0-1๋กœ ์ •๊ทœํ™”)"""
177
+ total = river + wave + tidal + 0.01
178
+ self.river_energy = river / total
179
+ self.wave_energy = wave / total
180
+ self.tidal_energy = tidal / total
181
+
182
+ def step(self, dt: float = 1.0):
183
+ """1 ํƒ€์ž„์Šคํ…"""
184
+ center_x = self.grid.width // 2
185
+
186
+ # 1. ํ•˜์ฒœ ํ‡ด์ 
187
+ river_dep = self.river_dep.deposit(
188
+ self.grid, self.river_energy, center_x, dt
189
+ )
190
+
191
+ # 2. ํŒŒ๋ž‘ ์žฌ๋ถ„๋ฐฐ
192
+ wave_change = self.wave_redis.redistribute(
193
+ self.grid, self.wave_energy, dt
194
+ )
195
+
196
+ # 3. ์กฐ๋ฅ˜ ์„ธ๊ตด
197
+ tidal_erosion = self.tidal_scour.scour(
198
+ self.grid, self.tidal_energy, dt
199
+ )
200
+
201
+ # ์ ์šฉ
202
+ self.grid.sediment += river_dep + wave_change - tidal_erosion
203
+ self.grid.sediment = np.maximum(0, self.grid.sediment)
204
+
205
+ # ์ง€ํ˜• ์—…๋ฐ์ดํŠธ
206
+ self.grid.elevation = self.grid.elevation + self.grid.sediment * 0.01
207
+ self.grid.water_depth = np.maximum(0, self.grid.sea_level - self.grid.elevation)
208
+
209
+ self.time += dt
210
+
211
+ def run(self, total_time: float, save_interval: float = 100.0, dt: float = 1.0):
212
+ """์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹คํ–‰"""
213
+ steps = int(total_time / dt)
214
+ save_every = max(1, int(save_interval / dt))
215
+
216
+ self.history = [self.grid.elevation.copy()]
217
+
218
+ for i in range(steps):
219
+ self.step(dt)
220
+ if (i + 1) % save_every == 0:
221
+ self.history.append(self.grid.elevation.copy())
222
+
223
+ return self.history
224
+
225
+ def get_delta_type(self) -> DeltaType:
226
+ """ํ˜„์žฌ ์‚ผ๊ฐ์ฃผ ์œ ํ˜• ํŒ๋ณ„"""
227
+ if self.river_energy >= self.wave_energy and self.river_energy >= self.tidal_energy:
228
+ return DeltaType.RIVER_DOMINATED
229
+ elif self.wave_energy >= self.tidal_energy:
230
+ return DeltaType.WAVE_DOMINATED
231
+ else:
232
+ return DeltaType.TIDE_DOMINATED
233
+
234
+ def get_delta_area(self) -> float:
235
+ """์‚ผ๊ฐ์ฃผ ๋ฉด์  (ํ•ด์ˆ˜๋ฉด ์œ„ ์ƒˆ ๋•…)"""
236
+ initial_land = self.history[0] > self.grid.sea_level
237
+ current_land = self.grid.elevation > self.grid.sea_level
238
+ new_land = current_land & ~initial_land
239
+ return float(np.sum(new_land)) * self.grid.cell_size**2 / 1e6 # kmยฒ
240
+
241
+
242
+ # ํ”„๋ฆฌ์ปดํ“จํŒ…
243
+ def precompute_delta(max_time: int = 10000,
244
+ river_energy: float = 60,
245
+ wave_energy: float = 25,
246
+ tidal_energy: float = 15,
247
+ save_every: int = 100) -> dict:
248
+ """์‚ผ๊ฐ์ฃผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํ”„๋ฆฌ์ปดํ“จํŒ…"""
249
+ sim = DeltaSimulation()
250
+ sim.set_energy_balance(river_energy, wave_energy, tidal_energy)
251
+
252
+ history = sim.run(
253
+ total_time=max_time,
254
+ save_interval=save_every,
255
+ dt=1.0
256
+ )
257
+
258
+ return {
259
+ 'history': history,
260
+ 'delta_type': sim.get_delta_type().value,
261
+ 'delta_area': sim.get_delta_area()
262
+ }
263
+
264
+
265
+ if __name__ == "__main__":
266
+ print("์‚ผ๊ฐ์ฃผ ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํ…Œ์ŠคํŠธ")
267
+ print("=" * 50)
268
+
269
+ # ์‹œ๋‚˜๋ฆฌ์˜ค 1: ํ•˜์ฒœ ์šฐ์„ธ
270
+ sim1 = DeltaSimulation()
271
+ sim1.set_energy_balance(80, 10, 10)
272
+ sim1.run(5000, save_interval=1000)
273
+ print(f"ํ•˜์ฒœ ์šฐ์„ธ: {sim1.get_delta_type().value}, ๋ฉด์  {sim1.get_delta_area():.2f} kmยฒ")
274
+
275
+ # ์‹œ๋‚˜๋ฆฌ์˜ค 2: ํŒŒ๋ž‘ ์šฐ์„ธ
276
+ sim2 = DeltaSimulation()
277
+ sim2.set_energy_balance(30, 60, 10)
278
+ sim2.run(5000, save_interval=1000)
279
+ print(f"ํŒŒ๋ž‘ ์šฐ์„ธ: {sim2.get_delta_type().value}, ๋ฉด์  {sim2.get_delta_area():.2f} kmยฒ")
280
+
281
+ print("=" * 50)
282
+ print("ํ…Œ์ŠคํŠธ ์™„๋ฃŒ!")
engine/deposition.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Geo-Lab AI Engine: ํ‡ด์  ๋กœ์ง
3
+ ์œ ์† ๊ฐ์†Œ์— ๋”ฐ๋ฅธ ์ž…์ž๋ณ„ ํ‡ด์  ๊ตฌํ˜„
4
+ """
5
+ import numpy as np
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from .base import Terrain, Water
10
+
11
+
12
+ def settling_deposition(terrain: 'Terrain', water: 'Water',
13
+ sediment_load: np.ndarray,
14
+ critical_velocity: float = 0.5,
15
+ dt: float = 1.0) -> tuple:
16
+ """
17
+ ์œ ์† ๊ฐ์†Œ์— ๋”ฐ๋ฅธ ํ‡ด์  (Stokes' Law ๊ธฐ๋ฐ˜)
18
+ ์œ ์†์ด ์ž„๊ณ„๊ฐ’ ์ดํ•˜๋กœ ๋–จ์–ด์ง€๋ฉด ํ‡ด์ ๋ฌผ์ด ์Œ“์ž„
19
+
20
+ Parameters:
21
+ -----------
22
+ terrain : Terrain
23
+ ์ง€ํ˜• ๊ฐ์ฒด
24
+ water : Water
25
+ ์ˆ˜๋ฌธ ๊ฐ์ฒด
26
+ sediment_load : np.ndarray
27
+ ํ˜„์žฌ ์šด๋ฐ˜ ์ค‘์ธ ํ‡ด์ ๋ฌผ๋Ÿ‰
28
+ critical_velocity : float
29
+ ํ‡ด์ ์ด ์‹œ์ž‘๋˜๋Š” ์ž„๊ณ„ ์œ ์†
30
+ dt : float
31
+ ์‹œ๊ฐ„ ๋‹จ์œ„
32
+
33
+ Returns:
34
+ --------
35
+ deposition_amount : np.ndarray
36
+ ํ‡ด์ ๋Ÿ‰
37
+ remaining_sediment : np.ndarray
38
+ ๋‚จ์€ ํ‡ด์ ๋ฌผ๋Ÿ‰
39
+ """
40
+ # ์œ ์†์ด ๋‚ฎ์€ ๊ณณ์—์„œ ํ‡ด์ 
41
+ velocity_ratio = water.velocity / (critical_velocity + 0.01)
42
+ deposition_rate = np.maximum(0, 1 - velocity_ratio)
43
+
44
+ deposition_amount = sediment_load * deposition_rate * dt
45
+ remaining_sediment = sediment_load - deposition_amount
46
+
47
+ return deposition_amount, np.maximum(remaining_sediment, 0)
48
+
49
+
50
+ def alluvial_fan_deposition(terrain: 'Terrain', water: 'Water',
51
+ sediment_load: np.ndarray,
52
+ slope_threshold: float = 0.1,
53
+ dt: float = 1.0) -> np.ndarray:
54
+ """
55
+ ์„ ์ƒ์ง€(Alluvial Fan) ํ‡ด์ 
56
+ ๊ธ‰๊ฒฉํ•œ ๊ฒฝ์‚ฌ ๋ณ€ํ™” ์ง€์ (์‚ฐ์ง€โ†’ํ‰์ง€)์—์„œ ๋ถ€์ฑ„๊ผด ํ‡ด์ 
57
+
58
+ Returns:
59
+ --------
60
+ deposition_amount : np.ndarray
61
+ """
62
+ slope = terrain.get_slope()
63
+
64
+ # ๊ฒฝ์‚ฌ ๋ณ€ํ™”์œจ ๊ณ„์‚ฐ
65
+ slope_change = np.gradient(slope, axis=0) + np.gradient(slope, axis=1)
66
+
67
+ # ๊ฒฝ์‚ฌ๊ฐ€ ๊ธ‰๊ฒฉํžˆ ์ค„์–ด๋“œ๋Š” ๊ณณ(์Œ์˜ ๋ณ€ํ™”์œจ)์—์„œ ํ‡ด์ 
68
+ fan_zone = slope_change < -slope_threshold
69
+
70
+ deposition = np.zeros_like(sediment_load)
71
+ deposition[fan_zone] = sediment_load[fan_zone] * 0.8 * dt
72
+
73
+ return deposition
74
+
75
+
76
+ def levee_backswamp_deposition(terrain: 'Terrain', water: 'Water',
77
+ flood_level: float = 1.0,
78
+ dt: float = 1.0) -> tuple:
79
+ """
80
+ ์ž์—ฐ์ œ๋ฐฉ(Levee) & ๋ฐฐํ›„์Šต์ง€(Backswamp) ํ‡ด์ 
81
+ ํ™์ˆ˜ ์‹œ ํ•˜์ฒœ ๊ฐ€๊นŒ์ด์— ๊ตต์€ ์ž…์ž, ๋ฉ€๋ฆฌ์— ๋ฏธ๋ฆฝ ์ž…์ž ํ‡ด์ 
82
+
83
+ Returns:
84
+ --------
85
+ levee_deposition : np.ndarray
86
+ ์ž์—ฐ์ œ๋ฐฉ ํ‡ด์ ๋Ÿ‰ (๋ชจ๋ž˜ - ๋‘๊บผ์›€)
87
+ backswamp_deposition : np.ndarray
88
+ ๋ฐฐํ›„์Šต์ง€ ํ‡ด์ ๋Ÿ‰ (์ ํ†  - ์–‡์Œ)
89
+ """
90
+ h, w = terrain.height, terrain.width
91
+
92
+ # ํ•˜์ฒœ ์œ„์น˜ (๋†’์€ ์œ ๋Ÿ‰)
93
+ channel_mask = water.discharge > np.percentile(water.discharge, 80)
94
+
95
+ levee = np.zeros((h, w))
96
+ backswamp = np.zeros((h, w))
97
+
98
+ # ํ•˜์ฒœ์œผ๋กœ๋ถ€ํ„ฐ์˜ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ (๊ฐ„๋‹จํ•œ ํ™•์‚ฐ)
99
+ from scipy.ndimage import distance_transform_edt
100
+ if np.any(channel_mask):
101
+ distance = distance_transform_edt(~channel_mask)
102
+
103
+ # ์ž์—ฐ์ œ๋ฐฉ: ํ•˜์ฒœ ๋ฐ”๋กœ ์˜† (2-5์…€)
104
+ levee_zone = (distance > 1) & (distance < 5)
105
+ levee[levee_zone] = flood_level * 0.3 * (5 - distance[levee_zone]) / 4 * dt
106
+
107
+ # ๋ฐฐํ›„์Šต์ง€: ๋” ๋จผ ๊ณณ (5-15์…€)
108
+ backswamp_zone = (distance >= 5) & (distance < 15)
109
+ backswamp[backswamp_zone] = flood_level * 0.05 * dt
110
+
111
+ return levee, backswamp
112
+
113
+
114
+ def delta_deposition(terrain: 'Terrain', water: 'Water',
115
+ river_energy: float = 1.0,
116
+ wave_energy: float = 0.5,
117
+ tidal_energy: float = 0.3,
118
+ sea_level: float = 0.0,
119
+ dt: float = 1.0) -> np.ndarray:
120
+ """
121
+ ์‚ผ๊ฐ์ฃผ(Delta) ํ‡ด์ 
122
+ 3๊ฐ€์ง€ ์—๋„ˆ์ง€(ํ•˜์ฒœ/ํŒŒ๋ž‘/์กฐ๋ฅ˜) ๊ท ํ˜•์— ๋”ฐ๋ผ ํ˜•ํƒœ ๊ฒฐ์ •
123
+
124
+ - ํ•˜์ฒœ ์šฐ์„ธ: ์กฐ์กฑ์ƒ(Bird's foot) - ๋ฏธ์‹œ์‹œํ”ผํ˜•
125
+ - ํŒŒ๋ž‘ ์šฐ์„ธ: ์›ํ˜ธ์ƒ(Arcuate) - ๋‚˜์ผํ˜•
126
+ - ์กฐ๋ฅ˜ ์šฐ์„ธ: ์ฒจ๊ฐ์ƒ(Cuspate) - ํ‹ฐ๋ฒ ๋ฅดํ˜•
127
+
128
+ Returns:
129
+ --------
130
+ deposition_amount : np.ndarray
131
+ """
132
+ h, w = terrain.height, terrain.width
133
+ deposition = np.zeros((h, w))
134
+
135
+ # ํ•ด์ˆ˜๋ฉด ๊ทผ์ฒ˜ (ํ•˜๊ตฌ)
136
+ estuary_zone = (terrain.elevation > sea_level - 5) & (terrain.elevation < sea_level + 10)
137
+ channel_mask = water.discharge > np.percentile(water.discharge, 70)
138
+ delta_zone = estuary_zone & channel_mask
139
+
140
+ if not np.any(delta_zone):
141
+ return deposition
142
+
143
+ # ์—๋„ˆ์ง€ ๋น„์œจ ์ •๊ทœํ™”
144
+ total_energy = river_energy + wave_energy + tidal_energy + 0.01
145
+ r_ratio = river_energy / total_energy
146
+ w_ratio = wave_energy / total_energy
147
+ t_ratio = tidal_energy / total_energy
148
+
149
+ # ํ•˜์ฒœ ์šฐ์„ธ: ๊ธธ๊ฒŒ ๋ป—์–ด๋‚˜๊ฐ€๋Š” ํŒจํ„ด
150
+ if r_ratio > 0.5:
151
+ # ํ๋ฆ„ ๏ฟฝ๏ฟฝ๏ฟฝํ–ฅ์œผ๋กœ ํ‡ด์ ๋ฌผ ํ™•์žฅ
152
+ deposition[delta_zone] = water.discharge[delta_zone] * r_ratio * 0.1 * dt
153
+
154
+ # ํŒŒ๋ž‘ ์šฐ์„ธ: ๋„“๊ฒŒ ํผ์ง€๋Š” ์›ํ˜ธ ํŒจํ„ด
155
+ elif w_ratio > 0.4:
156
+ # ์ขŒ์šฐ๋กœ ํผ์ง€๊ฒŒ
157
+ from scipy.ndimage import gaussian_filter
158
+ base = np.zeros((h, w))
159
+ base[delta_zone] = water.discharge[delta_zone] * 0.1
160
+ deposition = gaussian_filter(base, sigma=3) * w_ratio * dt
161
+
162
+ # ์กฐ๋ฅ˜ ์šฐ์„ธ: ์ž‘์€ ์„ฌ ํ˜•ํƒœ
163
+ else:
164
+ # ์ข์€ ์˜์—ญ์— ์ง‘์ค‘
165
+ deposition[delta_zone] = water.discharge[delta_zone] * t_ratio * 0.05 * dt
166
+
167
+ return deposition
168
+
169
+
170
+ def apply_deposition(terrain: 'Terrain', deposition_amount: np.ndarray):
171
+ """
172
+ ์ง€ํ˜•์— ํ‡ด์  ์ ์šฉ
173
+ """
174
+ terrain.elevation += deposition_amount
engine/erosion.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Geo-Lab AI Engine: ์นจ์‹ ๋กœ์ง
3
+ Stream Power Law ๊ธฐ๋ฐ˜ ํ•˜๋ฐฉ/์ธก๋ฐฉ ์นจ์‹ ๊ตฌํ˜„
4
+ """
5
+ import numpy as np
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from .base import Terrain, Water
10
+
11
+
12
+ def vertical_erosion(terrain: 'Terrain', water: 'Water',
13
+ k_erosion: float = 0.0001,
14
+ m_exponent: float = 0.5,
15
+ n_exponent: float = 1.0,
16
+ dt: float = 1.0) -> np.ndarray:
17
+ """
18
+ ํ•˜๋ฐฉ ์นจ์‹ (Vertical/Downcutting Erosion)
19
+ Stream Power Law: E = K * A^m * S^n
20
+
21
+ Parameters:
22
+ -----------
23
+ terrain : Terrain
24
+ ์ง€ํ˜• ๊ฐ์ฒด
25
+ water : Water
26
+ ์ˆ˜๋ฌธ ๊ฐ์ฒด
27
+ k_erosion : float
28
+ ์นจ์‹ ๊ณ„์ˆ˜ (์•”์„ ๊ฒฝ๋„์˜ ์—ญ์ˆ˜)
29
+ m_exponent : float
30
+ ์œ ๋Ÿ‰ ์ง€์ˆ˜ (๋ณดํ†ต 0.4-0.6)
31
+ n_exponent : float
32
+ ๊ฒฝ์‚ฌ ์ง€์ˆ˜ (๋ณดํ†ต 1.0)
33
+ dt : float
34
+ ์‹œ๊ฐ„ ๋‹จ์œ„ (๋…„)
35
+
36
+ Returns:
37
+ --------
38
+ erosion_amount : np.ndarray
39
+ ๊ฐ ์…€์˜ ์นจ์‹๋Ÿ‰ (m)
40
+ """
41
+ # ๊ฒฝ์‚ฌ ๊ณ„์‚ฐ
42
+ slope = terrain.get_slope()
43
+
44
+ # Stream Power Law
45
+ # E = K * Q^m * S^n
46
+ # ์•”์„ ๊ฒฝ๋„๋กœ K ์กฐ์ ˆ (๊ฒฝ๋„๊ฐ€ ๋†’์œผ๋ฉด ์นจ์‹์ด ์ ์Œ)
47
+ effective_k = k_erosion * (1 - terrain.rock_hardness * 0.9)
48
+
49
+ erosion_rate = effective_k * np.power(water.discharge + 0.1, m_exponent) * np.power(slope + 0.001, n_exponent)
50
+
51
+ erosion_amount = erosion_rate * dt
52
+
53
+ # ์นจ์‹๋Ÿ‰ ์ œํ•œ (๋„ˆ๋ฌด ๊ธ‰๊ฒฉํ•œ ๋ณ€ํ™” ๋ฐฉ์ง€)
54
+ max_erosion = 10.0 # ์ตœ๋Œ€ 10m/yr
55
+ erosion_amount = np.clip(erosion_amount, 0, max_erosion)
56
+
57
+ return erosion_amount
58
+
59
+
60
+ def lateral_erosion(terrain: 'Terrain', water: 'Water',
61
+ k_lateral: float = 0.00005,
62
+ curvature_factor: float = 1.0,
63
+ dt: float = 1.0) -> np.ndarray:
64
+ """
65
+ ์ธก๋ฐฉ ์นจ์‹ (Lateral Erosion)
66
+ ๊ณก๋ฅ˜ ํ•˜์ฒœ์—์„œ ๋ฐ”๊นฅ์ชฝ(๊ณต๊ฒฉ์‚ฌ๋ฉด)์„ ๊นŽ์Œ
67
+
68
+ Parameters:
69
+ -----------
70
+ terrain : Terrain
71
+ ์ง€ํ˜• ๊ฐ์ฒด
72
+ water : Water
73
+ ์ˆ˜๋ฌธ ๊ฐ์ฒด
74
+ k_lateral : float
75
+ ์ธก๋ฐฉ ์นจ์‹ ๊ณ„์ˆ˜
76
+ curvature_factor : float
77
+ ๊ณก๋ฅ  ๊ฐ•์กฐ ๊ณ„์ˆ˜
78
+ dt : float
79
+ ์‹œ๊ฐ„ ๋‹จ์œ„ (๋…„)
80
+
81
+ Returns:
82
+ --------
83
+ erosion_amount : np.ndarray
84
+ ๊ฐ ์…€์˜ ์นจ์‹๋Ÿ‰ (m)
85
+ """
86
+ h, w = terrain.height, terrain.width
87
+ erosion = np.zeros((h, w))
88
+
89
+ # ์œ ๋กœ ๊ณก๋ฅ  ๊ณ„์‚ฐ (ํ๋ฆ„ ๋ฐฉํ–ฅ์˜ 2์ฐจ ๋ฏธ๋ถ„)
90
+ flow_x, flow_y = water.flow_x, water.flow_y
91
+
92
+ # ๊ณก๋ฅ  ๊ทผ์‚ฌ: ํ๋ฆ„ ๋ฐฉํ–ฅ์˜ ๋ณ€ํ™”์œจ
93
+ curvature_x = np.gradient(flow_x, axis=1)
94
+ curvature_y = np.gradient(flow_y, axis=0)
95
+ curvature = np.sqrt(curvature_x**2 + curvature_y**2)
96
+
97
+ # ์ธก๋ฐฉ ์นจ์‹ = ์œ ๋Ÿ‰ * ์œ ์† * ๊ณก๋ฅ 
98
+ # ๊ณก๋ฅ ์ด ํฐ ๊ณณ(๊ธ‰์ปค๋ธŒ) = ๋ฐ”๊นฅ์ชฝ ์นจ์‹ ๊ฐ•ํ•จ
99
+ erosion = k_lateral * water.discharge * water.velocity * curvature * curvature_factor * dt
100
+
101
+ # ํ•˜์ฒœ์ด ์žˆ๋Š” ๊ณณ์—์„œ๋งŒ ์นจ์‹ (์œ ๋Ÿ‰ ์ž„๊ณ„๊ฐ’)
102
+ channel_mask = water.discharge > 0.1
103
+ erosion = erosion * channel_mask
104
+
105
+ return np.clip(erosion, 0, 5.0)
106
+
107
+
108
+ def headward_erosion(terrain: 'Terrain', water: 'Water',
109
+ k_headward: float = 0.0002,
110
+ dt: float = 1.0) -> np.ndarray:
111
+ """
112
+ ๋‘๋ถ€ ์นจ์‹ (Headward Erosion)
113
+ ํ•˜์ฒœ์˜ ์ƒ๋ฅ˜ ๋์ด ์ ์  ๋’ค๋กœ ๋ฌผ๋Ÿฌ๋‚จ
114
+ ํญํฌ, ํ˜‘๊ณก ํ˜•์„ฑ์˜ ํ•ต์‹ฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜
115
+
116
+ Returns:
117
+ --------
118
+ erosion_amount : np.ndarray
119
+ ๊ฐ ์…€์˜ ์นจ์‹๋Ÿ‰ (m)
120
+ """
121
+ h, w = terrain.height, terrain.width
122
+ erosion = np.zeros((h, w))
123
+
124
+ # ๊ธ‰๊ฒฝ์‚ฌ ์ง€์ (Knickpoint) ์ฐพ๊ธฐ
125
+ slope = terrain.get_slope()
126
+ steep_mask = slope > np.percentile(slope[slope > 0], 90) # ์ƒ์œ„ 10% ๊ธ‰๊ฒฝ์‚ฌ
127
+
128
+ # ๊ธ‰๊ฒฝ์‚ฌ + ์œ ๋Ÿ‰์ด ์žˆ๋Š” ๊ณณ์—์„œ ๋‘๋ถ€ ์นจ์‹ ๋ฐœ์ƒ
129
+ channel_mask = water.discharge > 0.5
130
+ knickpoint_mask = steep_mask & channel_mask
131
+
132
+ # ์ƒ๋ฅ˜ ๋ฐฉํ–ฅ์œผ๋กœ ์นจ์‹ ํ™•์žฅ
133
+ erosion[knickpoint_mask] = k_headward * water.discharge[knickpoint_mask] * dt
134
+
135
+ return np.clip(erosion, 0, 2.0)
136
+
137
+
138
+ def apply_erosion(terrain: 'Terrain', erosion_amount: np.ndarray,
139
+ min_elevation: float = 0.0):
140
+ """
141
+ ์ง€ํ˜•์— ์นจ์‹ ์ ์šฉ
142
+
143
+ Parameters:
144
+ -----------
145
+ terrain : Terrain
146
+ ์ˆ˜์ •ํ•  ์ง€ํ˜• ๊ฐ์ฒด
147
+ erosion_amount : np.ndarray
148
+ ์นจ์‹๋Ÿ‰ ๋ฐฐ์—ด
149
+ min_elevation : float
150
+ ์ตœ์†Œ ๊ณ ๋„ (ํ•ด์ˆ˜๋ฉด)
151
+ """
152
+ terrain.elevation -= erosion_amount
153
+ terrain.elevation = np.maximum(terrain.elevation, min_elevation)
154
+
155
+
156
+ def mass_wasting(terrain: 'Terrain',
157
+ critical_slope: float = 0.7, # ~35๋„
158
+ transfer_rate: float = 0.3) -> np.ndarray:
159
+ """
160
+ ์‚ฌ๋ฉด ๋ถ•๊ดด (Mass Wasting)
161
+ V์ž๊ณก ํ˜•์„ฑ ์‹œ ์–‘์˜† ์‚ฌ๋ฉด์ด ๋ฌด๋„ˆ์ง€๋Š” ๊ณผ์ •
162
+
163
+ Returns:
164
+ --------
165
+ elevation_change : np.ndarray
166
+ ๊ณ ๋„ ๋ณ€ํ™”๋Ÿ‰ (๋†’์€ ๊ณณ -, ๋‚ฎ์€ ๊ณณ +)
167
+ """
168
+ h, w = terrain.height, terrain.width
169
+ change = np.zeros((h, w))
170
+
171
+ slope = terrain.get_slope()
172
+
173
+ # ์ž„๊ณ„ ๊ฒฝ์‚ฌ ์ดˆ๊ณผ ์ง€์ ์—์„œ ๋ฌผ์งˆ ์ด๋™
174
+ unstable = slope > critical_slope
175
+
176
+ for y in range(1, h-1):
177
+ for x in range(1, w-1):
178
+ if unstable[y, x]:
179
+ # ์ฃผ๋ณ€์œผ๋กœ ๋ฌผ์งˆ ๋ถ„๋ฐฐ
180
+ elev = terrain.elevation[y, x]
181
+ neighbors = [
182
+ (y-1, x), (y+1, x), (y, x-1), (y, x+1)
183
+ ]
184
+
185
+ for ny, nx in neighbors:
186
+ if terrain.elevation[ny, nx] < elev:
187
+ transfer = (elev - terrain.elevation[ny, nx]) * transfer_rate * 0.25
188
+ change[y, x] -= transfer
189
+ change[ny, nx] += transfer
190
+
191
+ return change
engine/erosion_process.py ADDED
@@ -0,0 +1,328 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from .grid import WorldGrid
3
+
4
+ class ErosionProcess:
5
+ """
6
+ ์ง€ํ˜• ๋ณ€๊ฒฝ ์ปค๋„ (Erosion/Deposition Kernel)
7
+
8
+ ๋ฌผ๋ฆฌ ๊ณต์‹์„ ์ ์šฉํ•˜์—ฌ ์ง€ํ˜•(๊ณ ๋„)์„ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
9
+ 1. Stream Power Law: ํ•˜์ฒœ ์นจ์‹ (E = K * A^m * S^n)
10
+ 2. Hillslope Diffusion: ์‚ฌ๋ฉด ๋ถ•๊ดด/ํ™•์‚ฐ (dz/dt = D * del^2 z)
11
+ """
12
+
13
+ def __init__(self, grid: WorldGrid, K: float = 1e-4, m: float = 0.5, n: float = 1.0, D: float = 0.01):
14
+ self.grid = grid
15
+ self.K = K # ์นจ์‹ ๊ณ„์ˆ˜
16
+ self.m = m # ์œ ๋Ÿ‰ ์ง€์ˆ˜
17
+ self.n = n # ๊ฒฝ์‚ฌ ์ง€์ˆ˜
18
+ self.D = D # ํ™•์‚ฐ ๊ณ„์ˆ˜ (์‚ฌ๋ฉด)
19
+
20
+ def stream_power_erosion(self, discharge: np.ndarray, dt: float = 1.0) -> np.ndarray:
21
+ """Stream Power Law ๊ธฐ๋ฐ˜ ํ•˜์ฒœ ์นจ์‹"""
22
+ slope, _ = self.grid.get_gradient()
23
+
24
+ # E = K * Q^m * S^n
25
+ # (์œ ๋Ÿ‰ Q๋ฅผ ์œ ์—ญ๋ฉด์  A ๋Œ€์‹  ์‚ฌ์šฉ)
26
+ erosion_rate = self.K * np.power(discharge, self.m) * np.power(slope, self.n)
27
+
28
+ # ์‹ค์ œ ์นจ์‹๋Ÿ‰ = rate * time
29
+ erosion_amount = erosion_rate * dt
30
+
31
+ # ๊ธฐ๋ฐ˜์•” ์ดํ•˜๋กœ๋Š” ์นจ์‹ ๋ถˆ๊ฐ€ (available sediment first, then bedrock)
32
+ # ์—ฌ๊ธฐ์„œ๋Š” ๋‹จ์ˆœํ™”๋ฅผ ์œ„ํ•ด Topography(elevation)์„ ๋ฐ”๋กœ ๊นŽ์Œ.
33
+ # ๋‹จ, ํ•ด์ˆ˜๋ฉด ์•„๋ž˜๋Š” ์นจ์‹ ์ž‘์šฉ ๊ฐ์†Œ (๋ฌผ ์†์—์„œ๋Š” Stream Power๊ฐ€ ์•„๋‹˜)
34
+ underwater = self.grid.is_underwater()
35
+ erosion_amount[underwater] *= 0.1
36
+
37
+ # ์ง€ํ˜• ์—…๋ฐ์ดํŠธ
38
+ self.grid.elevation -= erosion_amount
39
+
40
+ # ๋‹จ์ˆœํ™”: ๊นŽ์ธ ๋งŒํผ ํ‡ด์ ๋ฌผ๋กœ ๋ณ€ํ™˜๋˜์–ด ์–ด๋”˜๊ฐ€๋กœ ๊ฐ€์•ผ ํ•˜์ง€๋งŒ,
41
+ # Stream Power Model(SPL)์€ ๋ณดํ†ต Detachment-limited ๋ชจ๋ธ์ด๋ผ ํ‡ด์ ์„ ๋ช…์‹œ์ ์œผ๋กœ ๋‹ค๋ฃจ์ง€ ์•Š์Œ.
42
+ # ํ†ตํ•ฉ ๋ชจ๋ธ์„ ์œ„ํ•ด, ์นจ์‹๋œ ์–‘์„ ํ‡ด์ ๋ฌผ ํ”Œ๋Ÿญ์Šค์— ๋”ํ•ด์ค„ ์ˆ˜ ์žˆ์Œ (๊ตฌํ˜„ ์˜ˆ์ •)
43
+
44
+ return erosion_amount
45
+
46
+ def hillslope_diffusion(self, dt: float = 1.0) -> np.ndarray:
47
+ """์‚ฌ๋ฉด ํ™•์‚ฐ ํ”„๋กœ์„ธ์Šค (Linear Diffusion)"""
48
+ elev = self.grid.elevation
49
+
50
+ # Laplacian calculation (์ด์‚ฐํ™”)
51
+ # del^2 z = (z_up + z_down + z_left + z_right - 4*z) / dx^2
52
+
53
+ # Numpy roll์„ ์ด์šฉํ•œ ๋น ๋ฅธ ๊ณ„์‚ฐ
54
+ up = np.roll(elev, -1, axis=0)
55
+ down = np.roll(elev, 1, axis=0)
56
+ left = np.roll(elev, -1, axis=1)
57
+ right = np.roll(elev, 1, axis=1)
58
+
59
+ dx2 = self.grid.cell_size ** 2
60
+ laplacian = (up + down + left + right - 4 * elev) / dx2
61
+
62
+ # ๊ฒฝ๊ณ„ ์กฐ๊ฑด ์ฒ˜๋ฆฌ (๊ฐ€์žฅ์ž๋ฆฌ๋Š” ๊ณ„์‚ฐ ์ œ์™ธ or 0)
63
+ laplacian[0, :] = 0
64
+ laplacian[-1, :] = 0
65
+ laplacian[:, 0] = 0
66
+ laplacian[:, -1] = 0
67
+
68
+ # dz/dt = D * del^2 z
69
+ change = self.D * laplacian * dt
70
+
71
+ self.grid.elevation += change
72
+ return change
73
+
74
+ def overbank_deposition(self, discharge: np.ndarray,
75
+ bankfull_capacity: float = 100.0,
76
+ decay_rate: float = 0.1,
77
+ dt: float = 1.0) -> np.ndarray:
78
+ """
79
+ ๋ฒ”๋žŒ์› ํ‡ด์  (Overbank Deposition)
80
+
81
+ ํ•˜์ฒœ ์šฉ๋Ÿ‰ ์ดˆ๊ณผ ์‹œ ๋ฒ”๋žŒํ•˜์—ฌ ์ฃผ๋ณ€์— ์„ธ๋ฆฝ์งˆ ํ‡ด์ .
82
+ ํ•˜๋„์—์„œ ๋ฉ€์–ด์งˆ์ˆ˜๋ก ํ‡ด์ ๋Ÿ‰ ๊ฐ์†Œ (์ž์—ฐ์ œ๋ฐฉ ํ˜•์„ฑ).
83
+
84
+ Args:
85
+ discharge: ์œ ๋Ÿ‰ ๋ฐฐ์—ด
86
+ bankfull_capacity: ํ•˜๋„ ์šฉ๋Ÿ‰ (์ดˆ๊ณผ ์‹œ ๋ฒ”๋žŒ)
87
+ decay_rate: ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ฅธ ํ‡ด์  ๊ฐ์‡ ์œจ
88
+ dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
89
+
90
+ Returns:
91
+ deposition: ํ‡ด์ ๋Ÿ‰ ๋ฐฐ์—ด
92
+ """
93
+ from scipy.ndimage import distance_transform_edt
94
+
95
+ h, w = self.grid.height, self.grid.width
96
+
97
+ # 1. ๋ฒ”๋žŒ ์ง€์  ์‹๋ณ„ (์šฉ๋Ÿ‰ ์ดˆ๊ณผ)
98
+ overflow = np.maximum(0, discharge - bankfull_capacity)
99
+ flood_mask = overflow > 0
100
+
101
+ if not np.any(flood_mask):
102
+ return np.zeros((h, w))
103
+
104
+ # 2. ํ•˜๋„๋กœ๋ถ€ํ„ฐ์˜ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ
105
+ # flood_mask๊ฐ€ ์žˆ๋Š” ๊ณณ์ด ํ•˜๋„
106
+ channel_mask = discharge > bankfull_capacity * 0.5
107
+
108
+ if not np.any(channel_mask):
109
+ return np.zeros((h, w))
110
+
111
+ # Distance Transform (ํ•˜๋„๋กœ๋ถ€ํ„ฐ์˜ ๊ฑฐ๋ฆฌ)
112
+ distance = distance_transform_edt(~channel_mask) * self.grid.cell_size
113
+
114
+ # 3. ํ‡ด์ ๋Ÿ‰ ๊ณ„์‚ฐ (์ง€์ˆ˜ ๊ฐ์‡ )
115
+ # Deposition = overflow * exp(-k * distance)
116
+ # ํ•˜๋„ ๊ทผ์ฒ˜(์ž์—ฐ์ œ๋ฐฉ)์— ๋งŽ์ด, ๋ฉ€์ˆ˜๋ก(๋ฐฐํ›„์Šต์ง€) ์ ๊ฒŒ
117
+ max_overflow = overflow.max()
118
+ if max_overflow <= 0:
119
+ return np.zeros((h, w))
120
+
121
+ normalized_overflow = overflow / max_overflow
122
+
123
+ # ๋ฒ”๋žŒ ์˜ํ–ฅ ๋ฒ”์œ„ (์ตœ๋Œ€ 50 ์…€)
124
+ max_distance = 50 * self.grid.cell_size
125
+ influence = np.exp(-decay_rate * distance / self.grid.cell_size)
126
+ influence[distance > max_distance] = 0
127
+
128
+ # ํ‡ด์ ๏ฟฝ๏ฟฝ๏ฟฝ
129
+ deposition = normalized_overflow.max() * influence * 0.1 * dt
130
+
131
+ # ํ•ด์ˆ˜๋ฉด ์•„๋ž˜๋Š” ์ œ์™ธ
132
+ underwater = self.grid.is_underwater()
133
+ deposition[underwater] = 0
134
+
135
+ # ํ•˜๋„ ์ž์ฒด๋Š” ์ œ์™ธ (ํ•˜๋„๋Š” ์นจ์‹์ด ์šฐ์„ธ)
136
+ deposition[channel_mask] = 0
137
+
138
+ # 4. ํ‡ด์ ์ธต์— ์ถ”๊ฐ€
139
+ self.grid.add_sediment(deposition)
140
+
141
+ return deposition
142
+
143
+ def transport_and_deposit(self, discharge: np.ndarray, dt: float = 1.0, Kf: float = 0.01) -> np.ndarray:
144
+ """
145
+ ํ‡ด์ ๋ฌผ ์šด๋ฐ˜ ๋ฐ ํ‡ด์  (Sediment Transport & Deposition)
146
+
147
+ Transport Capacity Law:
148
+ Q_cap = Kf * Q^m * S^n
149
+
150
+ - Q_cap > Q_sed: ์นจ์‹ (Erosion) -> ํ‡ด์ ๋ฌผ ์ฆ๊ฐ€
151
+ - Q_cap < Q_sed: ํ‡ด์  (Deposition) -> ํ‡ด์ ๋ฌผ ๊ฐ์†Œ, ์ง€ํ˜• ์ƒ์Šน
152
+
153
+ Args:
154
+ discharge: ์œ ๋Ÿ‰
155
+ dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
156
+ Kf: ์šด๋ฐ˜ ํšจ์œจ ๊ณ„์ˆ˜ (Transport Efficiency)
157
+ """
158
+ slope, _ = self.grid.get_gradient()
159
+ # ๊ฒฝ์‚ฌ๊ฐ€ 0์ด๋ฉด ๋ฌดํ•œ ํ‡ด์  ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด ์ตœ์†Œ๊ฐ’ ์„ค์ •
160
+ slope = np.maximum(slope, 0.001)
161
+
162
+ # 1. ์šด๋ฐ˜ ๋Šฅ๋ ฅ (Transport Capacity) ๊ณ„์‚ฐ
163
+ # ์นจ์‹ ๊ณ„์ˆ˜ K ๋Œ€์‹  ์šด๋ฐ˜ ๊ณ„์ˆ˜ Kf ์‚ฌ์šฉ (์ผ๋ฐ˜์ ์œผ๋กœ K๋ณด๋‹ค ํผ)
164
+ capacity = Kf * np.power(discharge, self.m) * np.power(slope, self.n)
165
+
166
+ # 2. ํ˜„์žฌ ๋ถ€์œ ์‚ฌ(Suspended Sediment) ๊ฐ€์ •
167
+ # ์ƒ๋ฅ˜์—์„œ ๋“ค์–ด์˜ค๋Š” ์œ ์‚ฌ๋Ÿ‰์€ ์ด์ „ ๋‹จ๊ณ„์˜ ์นจ์‹๋Ÿ‰์ด๋‚˜ ๊ธฐ์œ ์ž…๋Ÿ‰์— ์˜์กด
168
+ # ์—ฌ๊ธฐ์„œ๋Š” ๋‹จ์ˆœํ™”๋ฅผ ์œ„ํ•ด 'Local Equilibrium'์„ ๊ฐ€์ •ํ•˜์ง€ ์•Š๊ณ ,
169
+ # ์œ ๋Ÿ‰์— ๋น„๋ก€ํ•˜๋Š” ์ดˆ๊ธฐ ์œ ์‚ฌ๋Ÿ‰์„ ๊ฐ€์ •ํ•˜๊ฑฐ๋‚˜,
170
+ # ์ด์ „ ์Šคํ…์˜ ์นจ์‹ ๊ฒฐ๊ณผ๋ฅผ ์ด์šฉํ•ด์•ผ ํ•จ.
171
+ # ํ†ตํ•ฉ ๋ชจ๋ธ์„ ์œ„ํ•ด: "Erosion" ํ•จ์ˆ˜๊ฐ€ ๊นŽ์•„๋‚ธ ํ™์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•˜๊ณ ,
172
+ # ์ด๋ฅผ capacity์™€ ๋น„๊ตํ•˜์—ฌ ์žฌํ‡ด์ ์‹œํ‚ค๊ฑฐ๋‚˜ ํ•˜๋ฅ˜๋กœ ๋ณด๋ƒ„.
173
+
174
+ # ํ•˜์ง€๋งŒ D8 ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ƒ ํ•˜๋ฅ˜๋กœ์˜ '์ „๋‹ฌ(Routing)'์ด ํ•„์š”ํ•จ.
175
+ # ์—ฌ๊ธฐ์„œ๋Š” Simplified Landform Evolution Model (SLEM) ๋ฐฉ์‹ ์ ์šฉ:
176
+ # dZs/dt = U - E + D
177
+ # D = (Q_cap - Q_sed) / Length_scale (if Q_sed > Q_cap)
178
+ # E = Stream Power (detachment limited)
179
+
180
+ # Delta ์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ์œ„ํ•œ ์ ‘๊ทผ:
181
+ # ํ‡ด์ ๋ฌผ ํ”Œ๋Ÿญ์Šค(Flux)๋ฅผ ํ•˜๋ฅ˜๋กœ ๋ฐ€์–ด๋‚ด๋Š” ๋กœ์ง ์ถ”๊ฐ€.
182
+
183
+ h, w = self.grid.height, self.grid.width
184
+ sediment_flux = np.zeros((h, w))
185
+
186
+ # ์œ ๋Ÿ‰ ์ˆœ์„œ๋Œ€๋กœ ์ฒ˜๋ฆฌ (Upstream -> Downstream)
187
+ # discharge๊ฐ€ ๋‚ฎ์€ ๊ณณ(์ƒ๋ฅ˜)์—์„œ ๋†’์€ ๊ณณ(ํ•˜๋ฅ˜)์œผ๋กœ?
188
+ # D8 ํ๋ฆ„ ๋ฐฉํ–ฅ์„ ๋‹ค์‹œ ์ถ”์ ํ•ด์•ผ ์ •ํ™•ํ•จ.
189
+ # ์—ฌ๊ธฐ์„œ๋Š” ๊ฐ„๋‹จํžˆ 'Capacity ์ดˆ๊ณผ๋ถ„ ํ‡ด์ '๋งŒ ๊ตฌํ˜„ํ•˜๊ณ ,
190
+ # Flux Routing์€ HydroKernel๊ณผ ์—ฐ๋™๋˜์–ด์•ผ ํ•จ.
191
+
192
+ # ์ž„์‹œ: Capacity based deposition only (Local)
193
+ # ํ‡ด์ ๋Ÿ‰ = (ํ˜„์žฌ ์œ ์‚ฌ๋Ÿ‰ - ์šฉ๋Ÿ‰) * ๋น„์œจ
194
+ # ํ˜„์žฌ ์œ ์‚ฌ๋Ÿ‰์ด ์—†์œผ๋ฏ€๋กœ, ์นจ์‹๋œ ํ™(Stream Power ๊ฒฐ๊ณผ)์ด
195
+ # ํ•ด๋‹น ์…€์˜ Capacity๋ฅผ ๋„˜์œผ๋ฉด ์ฆ‰์‹œ ํ‡ด์ ๋œ๋‹ค๊ณ  ๊ฐ€์ •.
196
+
197
+ pass
198
+ # TODO: Flux Routing ๊ตฌํ˜„ ํ•„์š”. ํ˜„์žฌ ๊ตฌ์กฐ์—์„œ๋Š” ์–ด๋ ค์›€.
199
+ # ErosionProcess๋ฅผ ์ˆ˜์ •ํ•˜์—ฌ 'simulate_transport' ๋ฉ”์„œ๋“œ๋กœ ํ†ตํ•ฉ.
200
+
201
+ return capacity
202
+
203
+ def simulate_transport(self, discharge: np.ndarray, dt: float = 1.0,
204
+ sediment_influx_map: np.ndarray = None) -> np.ndarray:
205
+ """
206
+ ํ†ตํ•ฉ ํ‡ด์ ๋ฌผ ์ด์†ก ์‹œ๋ฎฌ๋ ˆ์ด์…˜ (Flux-based)
207
+ 1. ์ƒ๋ฅ˜์—์„œ ํ‡ด์ ๋ฌผ ์œ ์ž… (Flux In)
208
+ 2. ๋กœ์ปฌ ์นจ์‹/ํ‡ด์  (Erosion/Deposition)
209
+ 3. ํ•˜๋ฅ˜๋กœ ๋ฐฐ์ถœ (Flux Out)
210
+ """
211
+ h, w = self.grid.height, self.grid.width
212
+ elev = self.grid.elevation
213
+
214
+ # 1. ์ •๋ ฌ (๋†’์€ ๊ณณ -> ๋‚ฎ์€ ๊ณณ)
215
+ indices = np.argsort(elev.ravel())[::-1]
216
+
217
+ # ํ‡ด์ ๋ฌผ ํ”Œ๋Ÿญ์Šค ์ดˆ๊ธฐํ™” (์œ ์ž…์› ๋ฐ˜์˜)
218
+ flux = np.zeros((h, w))
219
+ if sediment_influx_map is not None:
220
+ flux += sediment_influx_map
221
+
222
+ slope, _ = self.grid.get_gradient()
223
+ slope = np.maximum(slope, 0.001)
224
+
225
+ change = np.zeros((h, w))
226
+
227
+ # D8 Neighbors (Lookup for flow_dir)
228
+ d8_dr = [-1, -1, -1, 0, 0, 1, 1, 1]
229
+ d8_dc = [-1, 0, 1, -1, 1, -1, 0, 1]
230
+
231
+ # Check if flow_dir is available
232
+ use_flow_dir = (self.grid.flow_dir is not None)
233
+
234
+ for idx in indices:
235
+ r, c = idx // w, idx % w
236
+
237
+
238
+
239
+ # ํ•ด์ˆ˜๋ฉด ์•„๋ž˜ ๊นŠ์€ ๊ณณ์€ ํ‡ด์  ์œ„์ฃผ
240
+ underwater = self.grid.is_underwater()[r, c]
241
+
242
+ # A. ์šด๋ฐ˜ ๋Šฅ๋ ฅ (Capacity)
243
+ # ๋ฌผ ์†์—์„œ๋Š” ์œ ์†์ด ๊ธ‰๊ฐํ•œ๋‹ค๊ณ  ๊ฐ€์ • -> Capacity ๊ฐ์†Œ
244
+ # eff_slope calculation fix: slope can be very small on flat land
245
+ eff_slope = slope[r, c] if not underwater else slope[r, c] * 0.01
246
+
247
+ # Kf (Transportation efficiency) should be high enough
248
+ # Use 'self.K * 100' or similar
249
+ qs_cap = self.K * 500 * np.power(discharge[r, c], self.m) * np.power(eff_slope, self.n)
250
+
251
+ # B. ํ˜„์žฌ ํ”Œ๋Ÿญ์Šค (์ƒ๋ฅ˜์—์„œ ๋“ค์–ด์˜จ ๊ฒƒ + ๋กœ์ปฌ ์นจ์‹ ์ž ์žฌ๋Ÿ‰)
252
+ qs_in = flux[r, c]
253
+
254
+ # C. ์นจ์‹ vs ํ‡ด์  ๊ฒฐ์ •
255
+ # ๊ธฐ๊ณ„์  ์นจ์‹ (Stream Power)
256
+ potential_erosion = self.K * np.power(discharge[r, c], self.m) * np.power(slope[r, c], self.n) * dt
257
+
258
+ # ๋งŒ์•ฝ ๋“ค์–ด์˜จ ํ™(qs_in)์ด ์šฉ๋Ÿ‰(qs_cap)๋ณด๋‹ค ๋งŽ์œผ๋ฉด -> ํ‡ด์ 
259
+ if qs_in > qs_cap:
260
+ # ํ‡ด์ ๋Ÿ‰ = ์ดˆ๊ณผ๋ถ„ * 1.0 (์ผ๋‹จ 100% ํ‡ด์  ๊ฐ€์ •ํ•˜์—ฌ ํšจ๊ณผ ํ™•์ธ)
261
+ deposition_amount = (qs_in - qs_cap) * 1.0
262
+ change[r, c] += deposition_amount
263
+ qs_out = qs_cap # ๋‚˜๋จธ์ง€๋Š” ํ•˜๋ฅ˜๋กœ? ์•„๋‹ˆ, ํ‡ด์  ํ›„ ๋‚จ์€๊ฑด qs_cap์ž„ (Transport-limited)
264
+ else:
265
+ # ์šฉ๋Ÿ‰์ด ๋‚จ์œผ๋ฉด -> ์นจ์‹ํ•˜์—ฌ ํ™์„ ๋” ์‹ฃ๊ณ ๊ฐ
266
+ # ์‹ค์ œ ์นจ์‹ = ์ž ์žฌ ์นจ์‹ (๊ธฐ๋ฐ˜์•”๋„ ์นจ์‹ ๊ฐ€๋Šฅ)
267
+ erosion_amount = potential_erosion
268
+
269
+ change[r, c] -= erosion_amount
270
+ qs_out = qs_in + erosion_amount
271
+
272
+ # D. ํ•˜๋ฅ˜๋กœ ์ „๋‹ฌ (Qs Out Routing)
273
+ # Use pre-calculated flow direction if available
274
+ target_r, target_c = -1, -1
275
+
276
+ if use_flow_dir:
277
+ k = self.grid.flow_dir[r, c]
278
+ # k could be default 0 even if no flow?
279
+ # Usually sink nodes have special value (e.g. -1 or point to self)
280
+ # But here we initialized to 0.
281
+ # Need to check constraints.
282
+ # If discharge[r,c] > 0, flow_dir should be valid.
283
+ if discharge[r, c] > 0:
284
+ nr = r + d8_dr[k]
285
+ nc = c + d8_dc[k]
286
+ if 0 <= nr < h and 0 <= nc < w:
287
+ target_r, target_c = nr, nc
288
+ else:
289
+ # Fallback: Local Seek (Slow)
290
+ min_z = elev[r, c]
291
+ for k in range(8):
292
+ nr = r + d8_dr[k]
293
+ nc = c + d8_dc[k]
294
+ if 0 <= nr < h and 0 <= nc < w:
295
+ if elev[nr, nc] < min_z:
296
+ min_z = elev[nr, nc]
297
+ target_r, target_c = nr, nc
298
+
299
+ if target_r != -1:
300
+ flux[target_r, target_c] += qs_out
301
+ else:
302
+ # ๊ฐ‡ํžŒ ๊ณณ(Sink) -> ๊ทธ ์ž๋ฆฌ์— ํ‡ด์ 
303
+ # ์นจ์‹์ด ๋ฐœ์ƒํ–ˆ๋‹ค๋ฉด ๋˜๋Œ๋ ค๋†“๊ณ  ํ‡ด์ 
304
+ change[r, c] += qs_out
305
+
306
+ # ์ง€ํ˜• ์—…๋ฐ์ดํŠธ
307
+ # ์นจ์‹์€ elevation ๊ฐ์†Œ, ํ‡ด์ ์€ sediment ์ฆ๊ฐ€์ด์ง€๋งŒ
308
+ # ์—ฌ๊ธฐ์„œ๋Š” ํ†ตํ•ฉํ•˜์—ฌ elevation/sediment ์กฐ์ •
309
+
310
+ # ํ‡ด์ ๋ถ„: sediment ์ธต์— ์ถ”๊ฐ€
311
+ self.grid.add_sediment(np.maximum(change, 0))
312
+
313
+ # ์นจ์‹๋ถ„: elevation ๊ฐ์†Œ (grid.add_sediment๊ฐ€ ์Œ์ˆ˜๋„ ์ฒ˜๋ฆฌํ•˜๋‚˜? ์•„๋‹˜)
314
+ # ์นจ์‹์€ bedrock์ด๋‚˜ sediment๋ฅผ ๊นŽ์•„์•ผ ํ•จ.
315
+ # erosion_process.py์˜ ์—ญํ• ์ƒ ์ง์ ‘ grid ์ˆ˜์ •์„ ํ•ด๋„ ๋จ.
316
+ erosion_mask = change < 0
317
+ loss = -change[erosion_mask]
318
+
319
+ # ํ‡ด์ ์ธต ๋จผ์ € ๊นŽ๊ณ  ๊ธฐ๋ฐ˜์•” ๊นŽ๊ธฐ
320
+ sed_thickness = self.grid.sediment[erosion_mask]
321
+ sed_loss = np.minimum(loss, sed_thickness)
322
+ rock_loss = loss - sed_loss
323
+
324
+ self.grid.sediment[erosion_mask] -= sed_loss
325
+ self.grid.bedrock[erosion_mask] -= rock_loss
326
+ self.grid.update_elevation()
327
+
328
+ return change
engine/fluids.py ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from .grid import WorldGrid
3
+
4
+ try:
5
+ from numba import jit
6
+ HAS_NUMBA = True
7
+ except ImportError:
8
+ HAS_NUMBA = False
9
+ # Dummy decorator if numba is missing
10
+ def jit(*args, **kwargs):
11
+ def decorator(func):
12
+ return func
13
+ return decorator
14
+
15
+ @jit(nopython=True)
16
+ def _d8_flow_kernel(elev, discharge, flow_dir, underwater, h, w):
17
+ """
18
+ Numba-optimized D8 Flow Routing
19
+ """
20
+ # Flatten elevation to sort
21
+ flat_elev = elev.ravel()
22
+ # Sort indices descending (Source -> Sink)
23
+ # Note: argsort in numba supports 1D array
24
+ flat_indices = np.argsort(flat_elev)[::-1]
25
+
26
+ # 8-neighbor offsets
27
+ # Numba doesn't like list of tuples in loops sometimes, simple arrays are better
28
+ dr = np.array([-1, -1, -1, 0, 0, 1, 1, 1])
29
+ dc = np.array([-1, 0, 1, -1, 1, -1, 0, 1])
30
+
31
+ for i in range(len(flat_indices)):
32
+ idx = flat_indices[i]
33
+ r = idx // w
34
+ c = idx % w
35
+
36
+ # Check underwater
37
+ if underwater[r, c]:
38
+ continue
39
+
40
+ current_z = elev[r, c]
41
+ min_z = current_z
42
+ target_r = -1
43
+ target_c = -1
44
+ target_k = -1
45
+
46
+ # Find steepest descent
47
+ for k in range(8):
48
+ nr = r + dr[k]
49
+ nc = c + dc[k]
50
+
51
+ if 0 <= nr < h and 0 <= nc < w:
52
+ n_elev = elev[nr, nc]
53
+ if n_elev < min_z:
54
+ min_z = n_elev
55
+ target_r = nr
56
+ target_c = nc
57
+ target_k = k
58
+
59
+ # Pass flow to lowest neighbor
60
+ if target_r != -1:
61
+ discharge[target_r, target_c] += discharge[r, c]
62
+ flow_dir[r, c] = target_k # Store direction (0-7)
63
+
64
+ class HydroKernel:
65
+ """
66
+ ์ˆ˜๋ ฅํ•™ ์ปค๋„ (Hydro Kernel)
67
+
68
+ ๋ฌผ์˜ ํ๋ฆ„๊ณผ ๋ถ„ํฌ๋ฅผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•ฉ๋‹ˆ๋‹ค.
69
+ - D8 ์•Œ๊ณ ๋ฆฌ์ฆ˜: ํ•˜์ฒœ ๋„คํŠธ์›Œํฌ ํ˜•์„ฑ (Numba ๊ฐ€์† ์ ์šฉ)
70
+ - Shallow Water (๊ฐ„์†Œํ™”): ํ™์ˆ˜ ๋ฐ ํ•ด์ˆ˜๋ฉด ์นจ์ˆ˜
71
+ """
72
+
73
+ def __init__(self, grid: WorldGrid):
74
+ self.grid = grid
75
+
76
+ def route_flow_d8(self, precipitation: float = 0.001) -> np.ndarray:
77
+ """
78
+ D8 ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ์œ ๋Ÿ‰(Discharge) ๊ณ„์‚ฐ (Numba ๊ฐ€์†)
79
+ """
80
+ h, w = self.grid.height, self.grid.width
81
+ elev = self.grid.elevation
82
+
83
+ # 1. ์ดˆ๊ธฐ ๊ฐ•์ˆ˜ ๋ถ„ํฌ
84
+ discharge = np.full((h, w), precipitation * (self.grid.cell_size ** 2), dtype=np.float64)
85
+
86
+ # 2. ํ•ด์ˆ˜๋ฉด ๋งˆ์Šคํฌ
87
+ underwater = self.grid.is_underwater()
88
+
89
+ # 3. Numba Kernel ํ˜ธ์ถœ
90
+ if HAS_NUMBA:
91
+ _d8_flow_kernel(elev, discharge, self.grid.flow_dir, underwater, h, w)
92
+ else:
93
+ # Fallback (Slow Python) if numba somehow fails to import
94
+ self._route_flow_d8_python(discharge, self.grid.flow_dir, elev, underwater, h, w)
95
+
96
+ return discharge
97
+
98
+ def route_flow_mfd(self, precipitation: float = 0.001, p: float = 1.1) -> np.ndarray:
99
+ """
100
+ MFD (Multiple Flow Direction) ์œ ๋Ÿ‰ ๋ถ„๋ฐฐ
101
+
102
+ D8๊ณผ ๋‹ฌ๋ฆฌ ๋‚ฎ์€ ๋ชจ๋“  ์ด์›ƒ์—๊ฒŒ ๊ฒฝ์‚ฌ ๋น„๋ก€๋กœ ์œ ๋Ÿ‰ ๋ถ„๋ฐฐ.
103
+ ๋ง๋ฅ˜(Braided Stream) ๋ฐ ๋ถ„๊ธฐ๋ฅ˜ ํ‘œํ˜„์— ์ ํ•ฉ.
104
+
105
+ Args:
106
+ precipitation: ๊ฐ•์ˆ˜๋Ÿ‰
107
+ p: ๋ถ„๋ฐฐ ์ง€์ˆ˜ (1.0=์„ ํ˜•, >1.0=๊ฐ€ํŒŒ๋ฅธ ๊ณณ์— ์ง‘์ค‘)
108
+
109
+ Returns:
110
+ discharge: ์œ ๋Ÿ‰ ๋ฐฐ์—ด
111
+ """
112
+ h, w = self.grid.height, self.grid.width
113
+ elev = self.grid.elevation
114
+
115
+ # ์ดˆ๊ธฐ ๊ฐ•์ˆ˜
116
+ discharge = np.full((h, w), precipitation * (self.grid.cell_size ** 2), dtype=np.float64)
117
+
118
+ # ํ•ด์ˆ˜๋ฉด ๋งˆ์Šคํฌ
119
+ underwater = self.grid.is_underwater()
120
+
121
+ # D8 ๋ฐฉํ–ฅ ๋ฒกํ„ฐ
122
+ dr = np.array([-1, -1, -1, 0, 0, 1, 1, 1])
123
+ dc = np.array([-1, 0, 1, -1, 1, -1, 0, 1])
124
+ dist = np.array([1.414, 1.0, 1.414, 1.0, 1.0, 1.414, 1.0, 1.414]) # ๋Œ€๊ฐ ๊ฑฐ๋ฆฌ
125
+
126
+ # ์ •๋ ฌ (๋†’์€ ๊ณณ -> ๋‚ฎ์€ ๊ณณ)
127
+ flat_indices = np.argsort(elev.ravel())[::-1]
128
+
129
+ for idx in flat_indices:
130
+ r, c = idx // w, idx % w
131
+
132
+ if underwater[r, c]:
133
+ continue
134
+
135
+ current_z = elev[r, c]
136
+ current_q = discharge[r, c]
137
+
138
+ if current_q <= 0:
139
+ continue
140
+
141
+ # ๋‚ฎ์€ ์ด์›ƒ๋“ค์˜ ๊ฒฝ์‚ฌ ๊ณ„์‚ฐ
142
+ slopes = []
143
+ targets = []
144
+
145
+ for k in range(8):
146
+ nr, nc = r + dr[k], c + dc[k]
147
+ if 0 <= nr < h and 0 <= nc < w:
148
+ dz = current_z - elev[nr, nc]
149
+ if dz > 0: # ํ•˜๊ฐ•ํ•˜๋Š” ๋ฐฉํ–ฅ๋งŒ
150
+ slope = dz / (dist[k] * self.grid.cell_size)
151
+ slopes.append(slope ** p)
152
+ targets.append((nr, nc))
153
+
154
+ if not slopes:
155
+ continue
156
+
157
+ # ๊ฒฝ์‚ฌ ๋น„๋ก€ ๋ถ„๋ฐฐ
158
+ total_slope = sum(slopes)
159
+ for i, (nr, nc) in enumerate(targets):
160
+ fraction = slopes[i] / total_slope
161
+ discharge[nr, nc] += current_q * fraction
162
+
163
+ return discharge
164
+
165
+ def _route_flow_d8_python(self, discharge, flow_dir, elev, underwater, h, w):
166
+ """Legacy Python implementation for fallback"""
167
+ flat_indices = np.argsort(elev.ravel())[::-1]
168
+ neighbors = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)]
169
+
170
+ for idx in flat_indices:
171
+ r, c = idx // w, idx % w
172
+ if underwater[r, c]: continue
173
+
174
+ min_z = elev[r, c]
175
+ target = None
176
+ target_k = -1
177
+
178
+ for k, (dr, dc) in enumerate(neighbors):
179
+ nr, nc = r + dr, c + dc
180
+ if 0 <= nr < h and 0 <= nc < w:
181
+ if elev[nr, nc] < min_z:
182
+ min_z = elev[nr, nc]
183
+ target = (nr, nc)
184
+ target_k = k
185
+
186
+ if target:
187
+ tr, tc = target
188
+ discharge[tr, tc] += discharge[r, c]
189
+ flow_dir[r, c] = target_k
190
+
191
+ def calculate_water_depth(self, discharge: np.ndarray, manning_n: float = 0.03) -> np.ndarray:
192
+ """
193
+ Manning ๊ณต์‹์„ ์ด์šฉํ•œ ํ•˜์ฒœ ์ˆ˜์‹ฌ ์ถ”์ • (์ •์ƒ ๋“ฑ๋ฅ˜ ๊ฐ€์ •)
194
+ Depth = (Q * n / (Width * S^0.5))^(3/5)
195
+
196
+ * ๊ฒฝ์‚ฌ(S)๊ฐ€ 0์ธ ๊ฒฝ์šฐ ์ตœ์†Œ ๊ฒฝ์‚ฌ ์ ์šฉ
197
+ * ํ•˜ํญ(W)์€ ์œ ๋Ÿ‰(Q)์˜ ํ•จ์ˆ˜๋กœ ๊ฐ€์ • (W ~ Q^0.5)
198
+ """
199
+ slope, _ = self.grid.get_gradient()
200
+ slope = np.maximum(slope, 0.001) # ์ตœ์†Œ ๊ฒฝ์‚ฌ ์„ค์ •
201
+
202
+ # ํ•˜ํญ ์ถ”์ •: W = 5 * Q^0.5 (๊ฒฝํ—˜์‹)
203
+ # Q๊ฐ€ ๋งค์šฐ ์ž‘์œผ๋ฉด W๋„ ์ž‘์•„์ง
204
+ width = 5.0 * np.sqrt(discharge)
205
+ width = np.maximum(width, 1.0) # ์ตœ์†Œ ํญ 1m
206
+
207
+ # ์ˆ˜์‹ฌ ๊ณ„์‚ฐ
208
+ # Q = V * Area = (1/n * R^(2/3) * S^(1/2)) * (W * D)
209
+ # ์ง์‚ฌ๊ฐํ˜• ๋‹จ๋ฉด ๊ฐ€์ • ์‹œ R approx D (๋„“์€ ํ•˜์ฒœ)
210
+ # Q = (1/n) * D^(5/3) * W * S^(1/2)
211
+ # D = (Q * n / (W * S^0.5)) ^ (3/5)
212
+
213
+ val = (discharge * manning_n) / (width * np.sqrt(slope))
214
+ depth = np.power(val, 0.6)
215
+
216
+ return depth
217
+
218
+ def simulate_inundation(self):
219
+ """ํ•ด์ˆ˜๋ฉด ์ƒ์Šน์— ๋”ฐ๋ฅธ ์นจ์ˆ˜ ์‹œ๋ฎฌ๋ ˆ์ด์…˜"""
220
+ # ํ•ด์ˆ˜๋ฉด๋ณด๋‹ค ๋‚ฎ์€ ๊ณณ์€ ๋ฐ”๋‹ค๋กœ ๊ฐ„์ฃผํ•˜๊ณ  ์ˆ˜์‹ฌ์„ ์ฑ„์›€
221
+ underwater = self.grid.is_underwater()
222
+
223
+ # ๋ฐ”๋‹ค ์ˆ˜์‹ฌ = ํ•ด์ˆ˜๋ฉด - ์ง€ํ‘œ๋ฉด๊ณ ๋„
224
+ sea_depth = np.maximum(0, self.grid.sea_level - self.grid.elevation)
225
+
226
+ # ๊ธฐ์กด ์ˆ˜์‹ฌ(ํ•˜์ฒœ)๊ณผ ๋ฐ”๋‹ค ์ˆ˜์‹ฌ ์ค‘ ํฐ ๊ฐ’ ์„ ํƒ
227
+ # (ํ•˜์ฒœ์ด ๋ฐ”๋‹ค๋กœ ๋“ค์–ด๊ฐ€๋ฉด ๋ฐ”๋‹ค ์ˆ˜์‹ฌ์— ๋ฌปํž˜)
228
+ self.grid.water_depth = np.where(underwater, sea_depth, self.grid.water_depth)
229
+
230
+ def fill_sinks(self, max_iterations: int = 100, tolerance: float = 0.001):
231
+ """
232
+ ์‹ฑํฌ(์›…๋ฉ์ด) ์ฑ„์šฐ๊ธฐ - ํ˜ธ์ˆ˜ ํ˜•์„ฑ
233
+
234
+ ๋ฌผ์ด ๊ฐ‡ํžˆ๋Š” ๊ณณ์„ ์ฐพ์•„ ์ฑ„์›Œ์„œ ์›”๋ฅ˜(Overflow)๊ฐ€ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•จ.
235
+ ๊ฐ„๋‹จํ•œ ๋ฐ˜๋ณต ์Šค๋ฌด๋”ฉ ๋ฐฉ์‹ (Priority-Flood ๊ทผ์‚ฌ)
236
+
237
+ Args:
238
+ max_iterations: ์ตœ๋Œ€ ๋ฐ˜๋ณต ํšŸ์ˆ˜
239
+ tolerance: ์ˆ˜๋ ด ํ—ˆ์šฉ ์˜ค์ฐจ
240
+ """
241
+ h, w = self.grid.height, self.grid.width
242
+ elev = self.grid.elevation.copy()
243
+
244
+ # ๊ฒฝ๊ณ„๋Š” ๊ณ ์ • (๋ฌผ์ด ๋น ์ ธ๋‚˜๊ฐ)
245
+ # ๋‚ด๋ถ€ ์‹ฑํฌ๋งŒ ์ฑ„์›€
246
+
247
+ dr = [-1, -1, -1, 0, 0, 1, 1, 1]
248
+ dc = [-1, 0, 1, -1, 1, -1, 0, 1]
249
+
250
+ for iteration in range(max_iterations):
251
+ changed = False
252
+ new_elev = elev.copy()
253
+
254
+ for r in range(1, h - 1):
255
+ for c in range(1, w - 1):
256
+ current = elev[r, c]
257
+
258
+ # ์ด์›ƒ ์ค‘ ์ตœ์†Œ๊ฐ’ ์ฐพ๊ธฐ
259
+ min_neighbor = current
260
+ for k in range(8):
261
+ nr, nc = r + dr[k], c + dc[k]
262
+ if 0 <= nr < h and 0 <= nc < w:
263
+ min_neighbor = min(min_neighbor, elev[nr, nc])
264
+
265
+ # ๋ชจ๋“  ์ด์›ƒ๋ณด๋‹ค ๋‚ฎ์œผ๋ฉด (์‹ฑํฌ) โ†’ ์ตœ์†Œ ์ด์›ƒ ๋†’์ด๋กœ ๋งž์ถค
266
+ if current < min_neighbor:
267
+ # ์‚ด์ง ๋†’์—ฌ์„œ ํ๋ฆ„ ์œ ๋„
268
+ new_elev[r, c] = min_neighbor + tolerance
269
+ changed = True
270
+
271
+ elev = new_elev
272
+
273
+ if not changed:
274
+ break
275
+
276
+ # ์ฑ„์›Œ์ง„ ์–‘ = ์ƒˆ ๊ณ ๋„ - ๊ธฐ์กด ๊ณ ๋„
277
+ fill_amount = elev - self.grid.elevation
278
+
279
+ # ๋ฌผ๋กœ ์ฑ„์›Œ์ง„ ๊ฒƒ์œผ๋กœ ์ฒ˜๋ฆฌ (water_depth ์ฆ๊ฐ€)
280
+ self.grid.water_depth += np.maximum(fill_amount, 0)
281
+
282
+ # bedrock์€ ๊ทธ๋Œ€๋กœ, sediment๋„ ๊ทธ๋Œ€๋กœ (๋ฌผ๋งŒ ์ฑ„์›€)
283
+ # ๋˜๋Š” sediment๋กœ ์ฑ„์šธ ์ˆ˜๋„ ์žˆ์Œ (ํ˜ธ์ˆ˜ ํ‡ด์ )
284
+
285
+ return fill_amount
286
+
engine/glacier.py ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Glacier Kernel (๋น™ํ•˜ ์ปค๋„)
3
+
4
+ ๋น™ํ•˜ ์ง€ํ˜• ํ˜•์„ฑ ํ”„๋กœ์„ธ์Šค
5
+ - ๋น™ํ•˜ ์นจ์‹: Plucking (๋œฏ์–ด๋‚ด๊ธฐ), Abrasion (๋งˆ์‹)
6
+ - U์ž๊ณก ํ˜•์„ฑ
7
+ - ๋ชจ๋ ˆ์ธ(Moraine) ํ‡ด์ 
8
+
9
+ ํ•ต์‹ฌ:
10
+ - ๋น™ํ•˜ ๋‘๊ป˜์— ๋น„๋ก€ํ•œ ์นจ์‹
11
+ - ์ธก๋ฉด๋ณด๋‹ค ์ค‘์•™ ์นจ์‹์ด ๊ฐ•ํ•จ โ†’ U์ž ํ˜•ํƒœ
12
+ """
13
+
14
+ import numpy as np
15
+ from .grid import WorldGrid
16
+
17
+
18
+ class GlacierKernel:
19
+ """
20
+ ๋น™ํ•˜ ์ปค๋„
21
+
22
+ ๋น™ํ•˜๊ฐ€ ์กด์žฌํ•˜๋Š” ์ง€์—ญ์—์„œ ์นจ์‹๊ณผ ํ‡ด์ ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜.
23
+ """
24
+
25
+ def __init__(self, grid: WorldGrid,
26
+ ice_threshold_temp: float = 0.0, # ๋น™ํ•˜ ํ˜•์„ฑ ๊ธฐ์˜จ (ยฐC)
27
+ K_erosion: float = 0.0001,
28
+ sliding_velocity: float = 10.0): # m/year
29
+ self.grid = grid
30
+ self.ice_threshold = ice_threshold_temp
31
+ self.K = K_erosion
32
+ self.sliding_velocity = sliding_velocity
33
+
34
+ # ๋น™ํ•˜ ๋‘๊ป˜ ๋ฐฐ์—ด
35
+ self.ice_thickness = np.zeros((grid.height, grid.width))
36
+
37
+ def accumulate_ice(self, temperature: np.ndarray,
38
+ precipitation: np.ndarray,
39
+ dt: float = 1.0):
40
+ """
41
+ ๋น™ํ•˜ ์ถ•์ 
42
+
43
+ ๊ธฐ์˜จ < ์ž„๊ณ„๊ฐ’์ธ ๊ณณ์— ๋ˆˆ/๋น™ํ•˜ ์ถ•์ 
44
+
45
+ Args:
46
+ temperature: ๊ธฐ์˜จ ๋ฐฐ์—ด
47
+ precipitation: ๊ฐ•์ˆ˜๋Ÿ‰ ๋ฐฐ์—ด
48
+ dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
49
+ """
50
+ h, w = self.grid.height, self.grid.width
51
+
52
+ # ๋น™ํ•˜ ์ถ•์  ์กฐ๊ฑด: ๊ธฐ์˜จ < 0ยฐC
53
+ cold_mask = temperature < self.ice_threshold
54
+
55
+ # ์ถ•์ ๋ฅ  = ๊ฐ•์ˆ˜๋Ÿ‰ * (๊ธฐ์˜จ์ด ๋‚ฎ์„์ˆ˜๋ก ๋” ๋งŽ์ด)
56
+ accumulation_rate = precipitation * np.clip(-temperature / 10.0, 0, 1) * 0.001
57
+
58
+ # ์ถ•์ 
59
+ self.ice_thickness[cold_mask] += accumulation_rate[cold_mask] * dt
60
+
61
+ # ๋น™ํ•˜ ์†Œ๋ฉธ (๊ธฐ์˜จ > 0ยฐC)
62
+ warm_mask = temperature > self.ice_threshold
63
+ melt_rate = temperature * 0.01 # ๊ธฐ์˜จ๋‹น ์œตํ•ด์œจ
64
+ self.ice_thickness[warm_mask] -= np.minimum(
65
+ melt_rate[warm_mask] * dt,
66
+ self.ice_thickness[warm_mask]
67
+ )
68
+
69
+ self.ice_thickness = np.maximum(self.ice_thickness, 0)
70
+
71
+ def flow_ice(self, dt: float = 1.0):
72
+ """
73
+ ๋น™ํ•˜ ํ๋ฆ„ (์ค‘๋ ฅ์— ์˜ํ•œ ํ•˜๊ฐ•)
74
+
75
+ ๋†’์€ ๊ณณ์—์„œ ๋‚ฎ์€ ๊ณณ์œผ๋กœ ๋น™ํ•˜ ์ด๋™
76
+ """
77
+ h, w = self.grid.height, self.grid.width
78
+
79
+ slope, _ = self.grid.get_gradient()
80
+
81
+ # ๋น™ํ•˜ ํ๋ฆ„ ์†๋„ = ๊ธฐ๋ณธ ์†๋„ * ๊ฒฝ์‚ฌ * ๋‘๊ป˜
82
+ flow_speed = self.sliding_velocity * slope * np.sqrt(self.ice_thickness + 0.1)
83
+
84
+ # D8 ๋ฐฉํ–ฅ์œผ๋กœ ๋น™ํ•˜ ์ด๋™ (๊ฐ„๋‹จํ•œ ๊ทผ์‚ฌ)
85
+ dr = np.array([-1, -1, -1, 0, 0, 1, 1, 1])
86
+ dc = np.array([-1, 0, 1, -1, 1, -1, 0, 1])
87
+
88
+ new_ice = self.ice_thickness.copy()
89
+ elev = self.grid.elevation
90
+
91
+ for r in range(1, h - 1):
92
+ for c in range(1, w - 1):
93
+ if self.ice_thickness[r, c] <= 0:
94
+ continue
95
+
96
+ # ๊ฐ€์žฅ ๋‚ฎ์€ ์ด์›ƒ ์ฐพ๊ธฐ
97
+ min_z = elev[r, c]
98
+ target = None
99
+
100
+ for k in range(8):
101
+ nr, nc = r + dr[k], c + dc[k]
102
+ if 0 <= nr < h and 0 <= nc < w:
103
+ if elev[nr, nc] < min_z:
104
+ min_z = elev[nr, nc]
105
+ target = (nr, nc)
106
+
107
+ if target:
108
+ tr, tc = target
109
+ # ์ด๋™๋Ÿ‰ (์†๋„์— ๋น„๋ก€, ์ตœ๋Œ€ 10%)
110
+ move = min(self.ice_thickness[r, c] * 0.1 * dt, self.ice_thickness[r, c])
111
+ new_ice[r, c] -= move
112
+ new_ice[tr, tc] += move
113
+
114
+ self.ice_thickness = new_ice
115
+
116
+ def erode(self, dt: float = 1.0) -> np.ndarray:
117
+ """
118
+ ๋น™ํ•˜ ์นจ์‹
119
+
120
+ U์ž๊ณก ํ˜•์„ฑ์„ ์œ„ํ•œ ์ฐจ๋ณ„ ์นจ์‹
121
+ - ์ค‘์•™(๋น™ํ•˜ ๋‘๊บผ์šด ๊ณณ): ๊ฐ•ํ•œ ์นจ์‹
122
+ - ์ธก๋ฉด: ์•ฝํ•œ ์นจ์‹
123
+
124
+ Returns:
125
+ erosion: ์นจ์‹๋Ÿ‰ ๋ฐฐ์—ด
126
+ """
127
+ h, w = self.grid.height, self.grid.width
128
+
129
+ # ๋น™ํ•˜๊ฐ€ ์žˆ๋Š” ๊ณณ๋งŒ ์นจ์‹
130
+ glacier_mask = self.ice_thickness > 0.1
131
+
132
+ erosion = np.zeros((h, w), dtype=np.float64)
133
+
134
+ if not np.any(glacier_mask):
135
+ return erosion
136
+
137
+ # ์นจ์‹๋ฅ  = K * ๋‘๊ป˜ * ์†๋„ * ๊ฒฝ์‚ฌ
138
+ slope, _ = self.grid.get_gradient()
139
+
140
+ erosion_rate = self.K * self.ice_thickness * self.sliding_velocity * slope
141
+ erosion[glacier_mask] = erosion_rate[glacier_mask] * dt
142
+
143
+ # ์นจ์‹ ์ ์šฉ
144
+ self.grid.bedrock -= erosion
145
+ self.grid.update_elevation()
146
+
147
+ return erosion
148
+
149
+ def deposit_moraine(self, dt: float = 1.0) -> np.ndarray:
150
+ """
151
+ ๋ชจ๋ ˆ์ธ ํ‡ด์ 
152
+
153
+ ๋น™ํ•˜ ๋ง๋‹จ(thickness ๊ธ‰๊ฐ ์ง€์ )์— ํ‡ด์ 
154
+
155
+ Returns:
156
+ deposition: ํ‡ด์ ๋Ÿ‰ ๋ฐฐ์—ด
157
+ """
158
+ h, w = self.grid.height, self.grid.width
159
+
160
+ deposition = np.zeros((h, w), dtype=np.float64)
161
+
162
+ # ๋น™ํ•˜ ๋‘๊ป˜ ๊ฐ์†Œ ์ง€์  = ๋ง๋‹จ
163
+ # Gradient of ice thickness
164
+ dy, dx = np.gradient(self.ice_thickness)
165
+ ice_gradient = np.sqrt(dx**2 + dy**2)
166
+
167
+ # ๋ง๋‹จ ์กฐ๊ฑด: ๋น™ํ•˜ ์žˆ๊ณ , gradient ํผ
168
+ terminal_mask = (self.ice_thickness > 0.1) & (ice_gradient > 0.1)
169
+
170
+ # ํ‡ด์ ๋Ÿ‰ = gradient์— ๋น„๋ก€
171
+ deposition[terminal_mask] = ice_gradient[terminal_mask] * 0.01 * dt
172
+
173
+ # ํ‡ด์  ์ ์šฉ
174
+ self.grid.add_sediment(deposition)
175
+
176
+ return deposition
177
+
178
+ def step(self, temperature: np.ndarray = None,
179
+ precipitation: np.ndarray = None,
180
+ dt: float = 1.0) -> dict:
181
+ """
182
+ 1๋‹จ๊ณ„ ๋น™ํ•˜ ์ž‘์šฉ ์‹คํ–‰
183
+
184
+ Args:
185
+ temperature: ๊ธฐ์˜จ ๋ฐฐ์—ด (์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ)
186
+ precipitation: ๊ฐ•์ˆ˜๋Ÿ‰ ๋ฐฐ์—ด
187
+ dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
188
+
189
+ Returns:
190
+ result: ๋น™ํ•˜ ์ƒํƒœ ๋ฐ ๋ณ€ํ™”๋Ÿ‰
191
+ """
192
+ h, w = self.grid.height, self.grid.width
193
+
194
+ # ๊ธฐ๋ณธ ๊ธฐ์˜จ/๊ฐ•์ˆ˜
195
+ if temperature is None:
196
+ # ๊ณ ๋„ ๊ธฐ๋ฐ˜ ๊ฐ„๋‹จํ•œ ๊ธฐ์˜จ ๊ณ„์‚ฐ
197
+ temperature = 15.0 - (self.grid.elevation / 1000.0) * 6.5
198
+
199
+ if precipitation is None:
200
+ precipitation = np.ones((h, w)) * 1000.0 # mm/year
201
+
202
+ # 1. ๋น™ํ•˜ ์ถ•์ /์†Œ๋ฉธ
203
+ self.accumulate_ice(temperature, precipitation, dt)
204
+
205
+ # 2. ๋น™ํ•˜ ํ๋ฆ„
206
+ self.flow_ice(dt)
207
+
208
+ # 3. ์นจ์‹
209
+ erosion = self.erode(dt)
210
+
211
+ # 4. ๋ชจ๋ ˆ์ธ ํ‡ด์ 
212
+ moraine = self.deposit_moraine(dt)
213
+
214
+ return {
215
+ 'ice_thickness': self.ice_thickness.copy(),
216
+ 'erosion': erosion,
217
+ 'moraine': moraine
218
+ }
engine/grid.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from dataclasses import dataclass, field
3
+ from typing import Tuple, Optional
4
+
5
+ @dataclass
6
+ class WorldGrid:
7
+ """
8
+ ์ง€๊ตฌ ์‹œ์Šคํ…œ ํ†ตํ•ฉ ๊ทธ๋ฆฌ๋“œ (World Grid)
9
+
10
+ ๋ชจ๋“  ๋ฌผ๋ฆฌ์  ์ƒํƒœ(๊ณ ๋„, ๋ฌผ, ํ‡ด์ ๋ฌผ)๋ฅผ ํ†ตํ•ฉ ๊ด€๋ฆฌํ•˜๋Š” ์ค‘์•™ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.
11
+ ๊ธฐ์กด์˜ ๊ฐœ๋ณ„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๊ทธ๋ฆฌ๋“œ์™€ ๋‹ฌ๋ฆฌ, ์ „์ง€๊ตฌ์  ํ•ด์ˆ˜๋ฉด(Sea Level)๊ณผ ์—ฐ๋™๋ฉ๋‹ˆ๋‹ค.
12
+ """
13
+ width: int = 100
14
+ height: int = 100
15
+ cell_size: float = 10.0 # ๋ฏธํ„ฐ (m)
16
+ sea_level: float = 0.0 # ํ•ด์ˆ˜๋ฉด ๊ณ ๋„ (m)
17
+
18
+ # --- ์ƒํƒœ ๋ ˆ์ด์–ด (State Layers) ---
19
+ # ๊ธฐ๋ฐ˜์•” ๊ณ ๋„ (Bedrock Elevation)
20
+ bedrock: np.ndarray = field(default=None)
21
+ # ํ‡ด์ ์ธต ๋‘๊ป˜ (Sediment Thickness)
22
+ sediment: np.ndarray = field(default=None)
23
+ # ์ˆ˜์‹ฌ (Water Depth) - ํ‘œ๋ฉด ์œ ์ถœ์ˆ˜
24
+ water_depth: np.ndarray = field(default=None)
25
+ # ์œ ๋Ÿ‰ (Discharge)
26
+ discharge: np.ndarray = field(default=None)
27
+ # ์œ ํ–ฅ (Flow Direction)
28
+ flow_dir: np.ndarray = field(default=None)
29
+
30
+ # --- ํŒŒ์ƒ ๋ ˆ์ด์–ด (Derived Layers) ---
31
+ # ์ง€ํ‘œ๋ฉด ๊ณ ๋„ (Topography = Bedrock + Sediment)
32
+ elevation: np.ndarray = field(default=None)
33
+
34
+ def __post_init__(self):
35
+ """๊ทธ๋ฆฌ๋“œ ์ดˆ๊ธฐํ™”"""
36
+ shape = (self.height, self.width)
37
+
38
+ if self.bedrock is None:
39
+ self.bedrock = np.zeros(shape)
40
+ if self.sediment is None:
41
+ self.sediment = np.zeros(shape)
42
+ if self.water_depth is None:
43
+ self.water_depth = np.zeros(shape)
44
+ if self.discharge is None:
45
+ self.discharge = np.zeros(shape)
46
+ if self.flow_dir is None:
47
+ self.flow_dir = np.zeros(shape, dtype=int)
48
+ if self.elevation is None:
49
+ self.update_elevation()
50
+
51
+ def update_elevation(self):
52
+ """์ง€ํ‘œ๋ฉด ๊ณ ๋„ ๊ฐฑ์‹  (๊ธฐ๋ฐ˜์•” + ํ‡ด์ ์ธต)"""
53
+ self.elevation = self.bedrock + self.sediment
54
+
55
+ def get_gradient(self) -> Tuple[np.ndarray, np.ndarray]:
56
+ """
57
+ ๊ฒฝ์‚ฌ๋„(Slope)์™€ ๊ฒฝ์‚ฌํ–ฅ(Aspect) ๊ณ„์‚ฐ
58
+ Returns:
59
+ slope (m/m): ๊ฒฝ์‚ฌ๋„
60
+ aspect (rad): ๊ฒฝ์‚ฌ ๋ฐฉํ–ฅ (0=East, pi/2=North)
61
+ """
62
+ dy, dx = np.gradient(self.elevation, self.cell_size)
63
+ slope = np.sqrt(dx**2 + dy**2)
64
+ aspect = np.arctan2(dy, dx)
65
+ return slope, aspect
66
+
67
+ def get_water_surface(self) -> np.ndarray:
68
+ """์ˆ˜๋ฉด ๊ณ ๋„ ๋ฐ˜ํ™˜ (์ง€ํ‘œ๋ฉด + ์ˆ˜์‹ฌ)"""
69
+ return self.elevation + self.water_depth
70
+
71
+ def is_underwater(self) -> np.ndarray:
72
+ """ํ•ด์ˆ˜๋ฉด ๊ธฐ์ค€ ์นจ์ˆ˜ ์—ฌ๋ถ€ ํ™•์ธ"""
73
+ # ํ•ด์ˆ˜๋ฉด๋ณด๋‹ค ๋‚ฎ๊ฑฐ๋‚˜, ์ง€ํ‘œ๋ฉด์— ๋ฌผ์ด ํ๋ฅด๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ
74
+ # ์—ฌ๊ธฐ์„œ๋Š” ๊ฐ„๋‹จํžˆ 'ํ•ด์ˆ˜๋ฉด' ๊ธฐ์ค€๊ณผ '๋‹ด์ˆ˜' ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ๋ถ„๋ฆฌํ•ด์„œ ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์Œ.
75
+ # ์ผ๋‹จ ํ•ด์ˆ˜๋ฉด(Sea Level) ๊ธฐ์ค€ ์นจ์ˆ˜ ์ง€์—ญ ๋ฐ˜ํ™˜
76
+ return self.elevation < self.sea_level
77
+
78
+ def apply_uplift(self, rate: float, dt: float = 1.0):
79
+ """์ง€๋ฐ˜ ์œต๊ธฐ ์ ์šฉ"""
80
+ self.bedrock += rate * dt
81
+ self.update_elevation()
82
+
83
+ def add_sediment(self, amount: np.ndarray):
84
+ """ํ‡ด์ ๋ฌผ ์ถ”๊ฐ€/์ œ๊ฑฐ"""
85
+ self.sediment += amount
86
+ # ํ‡ด์ ๋ฌผ์€ 0๋ณด๋‹ค ์ž‘์„ ์ˆ˜ ์—†์Œ (๊ธฐ๋ฐ˜์•” ์นจ์‹์€ ๋ณ„๋„ ๋กœ์ง)
87
+ self.sediment = np.maximum(self.sediment, 0)
88
+ self.update_elevation()
engine/ideal_landforms.py ADDED
@@ -0,0 +1,1621 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Ideal Landform Geometry Models (์ด์ƒ์  ์ง€ํ˜• ๊ธฐํ•˜ํ•™ ๋ชจ๋ธ)
3
+
4
+ ๊ต๊ณผ์„œ์ ์ธ ์ง€ํ˜• ํ˜•ํƒœ๋ฅผ ๊ธฐํ•˜ํ•™์ ์œผ๋กœ ์ƒ์„ฑ.
5
+ ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์ด ์•„๋‹Œ, ์ง์ ‘ ์ˆ˜ํ•™์ ์œผ๋กœ "์ด์ƒ์  ํ˜•ํƒœ"๋ฅผ ๊ทธ๋ฆผ.
6
+
7
+ - ์‚ผ๊ฐ์ฃผ: ๋ถ€์ฑ„๊ผด (Sector)
8
+ - ์„ ์ƒ์ง€: ์›๋ฟ” (Cone)
9
+ - ๊ณก๋ฅ˜: S์ž ๊ณก์„  (Kinoshita Curve)
10
+ - U์ž๊ณก: ํฌ๋ฌผ์„  ๋‹จ๋ฉด
11
+ - V์ž๊ณก: ์‚ผ๊ฐํ˜• ๋‹จ๋ฉด
12
+ - ํ•ด์•ˆ ์ ˆ๋ฒฝ: ๊ณ„๋‹จํ˜• ํ›„ํ‡ด
13
+ - ์‚ฌ๊ตฌ: ๋ฐ”๋ฅดํ•œ (Crescent)
14
+ """
15
+
16
+ import numpy as np
17
+ from typing import Tuple
18
+
19
+
20
+ def create_delta(grid_size: int = 100,
21
+ apex_row: float = 0.2,
22
+ spread_angle: float = 120.0,
23
+ num_channels: int = 7) -> np.ndarray:
24
+ """
25
+ ์‚ผ๊ฐ์ฃผ (Delta) - ์กฐ์กฑ์ƒ/๋ถ€์ฑ„๊ผด
26
+
27
+ Args:
28
+ grid_size: ๊ทธ๋ฆฌ๋“œ ํฌ๊ธฐ
29
+ apex_row: ์ •์ (Apex) ์œ„์น˜ (0~1, ์ƒ๋‹จ ๊ธฐ์ค€)
30
+ spread_angle: ํผ์ง ๊ฐ๋„ (๋„)
31
+ num_channels: ๋ถ„๋ฐฐ ์ˆ˜๋กœ ๊ฐœ์ˆ˜
32
+
33
+ Returns:
34
+ elevation: ๊ณ ๋„ ๋ฐฐ์—ด
35
+ """
36
+ h, w = grid_size, grid_size
37
+ elevation = np.zeros((h, w))
38
+
39
+ apex_y = int(h * apex_row)
40
+ center_x = w // 2
41
+
42
+ # ๋ฐฐ๊ฒฝ: ๋ฐ”๋‹ค (์Œ์ˆ˜)
43
+ elevation[:, :] = -5.0
44
+
45
+ # ์œก์ง€ ๋ฐฐ๊ฒฝ (์‚ผ๊ฐ์ฃผ ์ „์ฒด)
46
+ half_angle = np.radians(spread_angle / 2)
47
+
48
+ for r in range(apex_y, h):
49
+ dist = r - apex_y
50
+ if dist == 0:
51
+ continue
52
+
53
+ # ๊ฐ๋„ ๋ฒ”์œ„ ๋‚ด ์œก์ง€
54
+ for c in range(w):
55
+ dx = c - center_x
56
+ angle = np.arctan2(dx, dist) # ์ •์  ๊ธฐ์ค€ ๊ฐ๋„
57
+
58
+ if abs(angle) < half_angle:
59
+ # ์‚ผ๊ฐ์ฃผ ์œก์ง€
60
+ # ์ค‘์‹ฌ์—์„œ ๋ฉ€์ˆ˜๋ก ๋‚ฎ์•„์ง
61
+ radial_dist = np.sqrt(dx**2 + dist**2)
62
+ max_dist = h - apex_y
63
+ elevation[r, c] = 10.0 * (1 - radial_dist / max_dist)
64
+
65
+ # ๋ถ„๋ฐฐ ์ˆ˜๋กœ (Distributary Channels)
66
+ for i in range(num_channels):
67
+ channel_angle = -half_angle + (2 * half_angle) * (i / (num_channels - 1))
68
+
69
+ for r in range(apex_y, h):
70
+ dist = r - apex_y
71
+ c = int(center_x + dist * np.tan(channel_angle))
72
+
73
+ if 0 <= c < w:
74
+ # ์ˆ˜๋กœ ํŒŒ๊ธฐ (์Œ๊ฐ)
75
+ for dc in range(-2, 3):
76
+ if 0 <= c + dc < w:
77
+ depth = 2.0 * (1 - abs(dc) / 3)
78
+ elevation[r, c + dc] -= depth
79
+
80
+ return elevation
81
+
82
+
83
+ def create_alluvial_fan(grid_size: int = 100,
84
+ apex_row: float = 0.15,
85
+ cone_angle: float = 90.0,
86
+ max_height: float = 50.0) -> np.ndarray:
87
+ """
88
+ ์„ ์ƒ์ง€ (Alluvial Fan) - ์›๋ฟ”ํ˜•
89
+
90
+ Args:
91
+ grid_size: ๊ทธ๋ฆฌ๋“œ ํฌ๊ธฐ
92
+ apex_row: ์ •์  ์œ„์น˜
93
+ cone_angle: ๋ถ€์ฑ„๊ผด ๊ฐ๋„
94
+ max_height: ์ตœ๋Œ€ ๊ณ ๋„
95
+
96
+ Returns:
97
+ elevation: ๊ณ ๋„ ๋ฐฐ์—ด
98
+ """
99
+ h, w = grid_size, grid_size
100
+ elevation = np.zeros((h, w))
101
+
102
+ apex_y = int(h * apex_row)
103
+ center_x = w // 2
104
+ half_angle = np.radians(cone_angle / 2)
105
+
106
+ # ๋ฐฐ๊ฒฝ ์‚ฐ์ง€ (์ƒ๋‹จ)
107
+ for r in range(apex_y):
108
+ elevation[r, :] = max_height + (apex_y - r) * 2.0
109
+
110
+ # ์„ ์ƒ์ง€ ๋ณธ์ฒด (์›๋ฟ”)
111
+ for r in range(apex_y, h):
112
+ dist = r - apex_y
113
+ max_dist = h - apex_y
114
+
115
+ for c in range(w):
116
+ dx = c - center_x
117
+ angle = np.arctan2(abs(dx), dist) if dist > 0 else 0
118
+
119
+ if abs(np.arctan2(dx, dist)) < half_angle:
120
+ # ์›๋ฟ” ํ˜•ํƒœ: ์ค‘์‹ฌ์ด ๋†’๊ณ , ๊ฐ€์žฅ์ž๋ฆฌ๊ฐ€ ๋‚ฎ์Œ
121
+ radial = np.sqrt(dx**2 + dist**2)
122
+ # ์ •์ ์—์„œ ๋ฉ€์–ด์งˆ์ˆ˜๋ก ๋‚ฎ์•„์ง
123
+ z = max_height * (1 - radial / (max_dist * 1.5))
124
+ # ๊ฐ€์žฅ์ž๋ฆฌ๋กœ ๊ฐˆ์ˆ˜๋ก ๋” ๊ธ‰๊ฒฉํžˆ ๋‚ฎ์•„์ง
125
+ lateral_decay = 1 - abs(dx) / (w // 2)
126
+ elevation[r, c] = max(0, z * lateral_decay)
127
+ else:
128
+ elevation[r, c] = 0 # ํ‰์ง€
129
+
130
+ # ํ˜‘๊ณก (Apex์—์„œ ์‹œ์ž‘)
131
+ for r in range(0, apex_y + 5):
132
+ for dc in range(-3, 4):
133
+ c = center_x + dc
134
+ if 0 <= c < w:
135
+ depth = 10.0 * (1 - abs(dc) / 4)
136
+ elevation[r, c] -= depth
137
+
138
+ return elevation
139
+
140
+
141
+ def create_meander(grid_size: int = 100,
142
+ amplitude: float = 0.3,
143
+ wavelength: float = 0.25,
144
+ num_bends: int = 3) -> np.ndarray:
145
+ """
146
+ ๊ณก๋ฅ˜ (Meander) - S์ž ์‚ฌํ–‰ ํ•˜์ฒœ
147
+
148
+ Args:
149
+ grid_size: ๊ทธ๋ฆฌ๋“œ ํฌ๊ธฐ
150
+ amplitude: ์‚ฌํ–‰ ์ง„ํญ (๊ทธ๋ฆฌ๋“œ ๋น„์œจ)
151
+ wavelength: ํŒŒ์žฅ (๊ทธ๋ฆฌ๋“œ ๋น„์œจ)
152
+ num_bends: ๊ตฝ์ด ๊ฐœ์ˆ˜
153
+
154
+ Returns:
155
+ elevation: ๊ณ ๋„ ๋ฐฐ์—ด
156
+ """
157
+ h, w = grid_size, grid_size
158
+ elevation = np.zeros((h, w))
159
+
160
+ # ๋ฐฐ๊ฒฝ: ๋ฒ”๋žŒ์› ํ‰ํƒ„๋ฉด
161
+ elevation[:, :] = 10.0
162
+
163
+ center_x = w // 2
164
+ amp = w * amplitude
165
+ wl = h / num_bends
166
+ channel_width = max(3, w // 20)
167
+
168
+ # ์‚ฌํ–‰ ํ•˜์ฒœ ๊ฒฝ๋กœ
169
+ for r in range(h):
170
+ # Kinoshita curve (์ด์ƒํ™”๋œ ๊ณก๋ฅ˜)
171
+ theta = 2 * np.pi * r / wl
172
+ meander_x = center_x + amp * np.sin(theta)
173
+
174
+ for c in range(w):
175
+ dist = abs(c - meander_x)
176
+
177
+ if dist < channel_width:
178
+ # ํ•˜๋„ (๋‚ฎ๊ฒŒ)
179
+ elevation[r, c] = 5.0 - (channel_width - dist) * 0.3
180
+ elif dist < channel_width * 3:
181
+ # ์ž์—ฐ์ œ๋ฐฉ (์•ฝ๊ฐ„ ๋†’๊ฒŒ)
182
+ elevation[r, c] = 10.5
183
+
184
+ # ์šฐ๊ฐํ˜ธ (Oxbow Lake) ์ถ”๊ฐ€
185
+ # ์ค‘๊ฐ„์ฏค์— ์ ˆ๋‹จ๋œ ๊ณก๋ฅ˜ ํ”์ 
186
+ oxbow_y = h // 2
187
+ oxbow_amp = amp * 1.5
188
+
189
+ for dy in range(-int(wl/4), int(wl/4)):
190
+ r = oxbow_y + dy
191
+ if 0 <= r < h:
192
+ theta = 2 * np.pi * dy / (wl/2)
193
+ ox_x = center_x + oxbow_amp * np.sin(theta)
194
+
195
+ for dc in range(-channel_width, channel_width + 1):
196
+ c = int(ox_x + dc)
197
+ if 0 <= c < w:
198
+ elevation[r, c] = 4.0 # ํ˜ธ์ˆ˜ ์ˆ˜๋ฉด
199
+
200
+ return elevation
201
+
202
+
203
+ def create_u_valley(grid_size: int = 100,
204
+ valley_depth: float = 100.0,
205
+ valley_width: float = 0.4) -> np.ndarray:
206
+ """
207
+ U์ž๊ณก (U-shaped Valley) - ๋น™ํ•˜ ์นจ์‹ ์ง€ํ˜•
208
+
209
+ Args:
210
+ grid_size: ๊ทธ๋ฆฌ๋“œ ํฌ๊ธฐ
211
+ valley_depth: ๊ณก์ € ๊นŠ์ด
212
+ valley_width: ๊ณก์ € ๋„ˆ๋น„ (๋น„์œจ)
213
+
214
+ Returns:
215
+ elevation: ๊ณ ๋„ ๋ฐฐ์—ด
216
+ """
217
+ h, w = grid_size, grid_size
218
+ elevation = np.zeros((h, w))
219
+
220
+ center = w // 2
221
+ half_width = int(w * valley_width / 2)
222
+
223
+ for r in range(h):
224
+ for c in range(w):
225
+ dx = abs(c - center)
226
+
227
+ if dx < half_width:
228
+ # U์ž ๋ฐ”๋‹ฅ (ํ‰ํƒ„)
229
+ elevation[r, c] = 0
230
+ else:
231
+ # U์ž ์ธก๋ฒฝ (๊ธ‰๊ฒฝ์‚ฌ ํ›„ ์™„๋งŒ)
232
+ # y = (x/a)^4 ํ˜•ํƒœ
233
+ normalized_x = (dx - half_width) / (w // 2 - half_width)
234
+ elevation[r, c] = valley_depth * (normalized_x ** 2)
235
+
236
+ # ์ƒ๋ฅ˜๋กœ ๊ฐˆ์ˆ˜๋ก ๋†’์•„์ง
237
+ elevation[r, :] += (h - r) / h * 30.0
238
+
239
+ return elevation
240
+
241
+
242
+ def create_v_valley(grid_size: int = 100,
243
+ valley_depth: float = 80.0) -> np.ndarray:
244
+ """
245
+ V์ž๊ณก (V-shaped Valley) - ํ•˜์ฒœ ์นจ์‹ ์ง€ํ˜•
246
+
247
+ Args:
248
+ grid_size: ๊ทธ๋ฆฌ๋“œ ํฌ๊ธฐ
249
+ valley_depth: ๊ณก์ € ๊นŠ์ด
250
+
251
+ Returns:
252
+ elevation: ๊ณ ๋„ ๋ฐฐ์—ด
253
+ """
254
+ h, w = grid_size, grid_size
255
+ elevation = np.zeros((h, w))
256
+
257
+ center = w // 2
258
+
259
+ for r in range(h):
260
+ for c in range(w):
261
+ dx = abs(c - center)
262
+
263
+ # V์ž ํ˜•ํƒœ: |x| ์— ๋น„๋ก€
264
+ elevation[r, c] = valley_depth * (dx / (w // 2))
265
+
266
+ # ์ƒ๋ฅ˜๋กœ ๊ฐˆ์ˆ˜๋ก ๋†’์•„์ง
267
+ elevation[r, :] += (h - r) / h * 50.0
268
+
269
+ # ํ•˜์ฒœ (V์ž ๋ฐ”๋‹ฅ)
270
+ for r in range(h):
271
+ for dc in range(-2, 3):
272
+ c = center + dc
273
+ if 0 <= c < w:
274
+ elevation[r, c] = max(0, elevation[r, c] - 5)
275
+
276
+ return elevation
277
+
278
+
279
+ def create_barchan_dune(grid_size: int = 100,
280
+ num_dunes: int = 3) -> np.ndarray:
281
+ """
282
+ ๋ฐ”๋ฅดํ•œ ์‚ฌ๊ตฌ (Barchan Dune) - ์ดˆ์Šน๋‹ฌ ๋ชจ์–‘
283
+
284
+ Args:
285
+ grid_size: ๊ทธ๋ฆฌ๋“œ ํฌ๊ธฐ
286
+ num_dunes: ์‚ฌ๊ตฌ ๊ฐœ์ˆ˜
287
+
288
+ Returns:
289
+ elevation: ๊ณ ๋„ ๋ฐฐ์—ด
290
+ """
291
+ h, w = grid_size, grid_size
292
+ elevation = np.zeros((h, w))
293
+
294
+ # ์‚ฌ๋ง‰ ๊ธฐ๋ฐ˜๋ฉด
295
+ elevation[:, :] = 5.0
296
+
297
+ for i in range(num_dunes):
298
+ # ์‚ฌ๊ตฌ ์ค‘์‹ฌ
299
+ cy = h // 4 + i * (h // (num_dunes + 1))
300
+ cx = w // 2 + (i - num_dunes // 2) * (w // 5)
301
+
302
+ dune_height = 15.0 + np.random.rand() * 10.0
303
+ dune_length = w // 5
304
+ dune_width = w // 8
305
+
306
+ for r in range(h):
307
+ for c in range(w):
308
+ dy = r - cy
309
+ dx = c - cx
310
+
311
+ # ๋ฐ”๋ฅดํ•œ: ๋ฐ”๋žŒ๋ฐ›์ด(์•ž)๋Š” ์™„๋งŒ, ๋ฐ”๋žŒ๊ทธ๋Š˜(๋’ค)๋Š” ๊ธ‰๊ฒฝ์‚ฌ
312
+ # ์ดˆ์Šน๋‹ฌ ํ˜•ํƒœ
313
+
314
+ # ๊ฑฐ๋ฆฌ
315
+ dist = np.sqrt((dy / dune_length) ** 2 + (dx / dune_width) ** 2)
316
+
317
+ if dist < 1.0:
318
+ # ์‚ฌ๊ตฌ ๋ณธ์ฒด
319
+ # ์•ž์ชฝ(๋ฐ”๋žŒ๋ฐ›์ด): ์™„๋งŒํ•œ ๊ฒฝ์‚ฌ
320
+ # ๋’ค์ชฝ: ๊ธ‰๊ฒฝ์‚ฌ (Slip Face)
321
+
322
+ if dy < 0: # ๋ฐ”๋žŒ๋ฐ›์ด
323
+ z = dune_height * (1 - dist) * (1 - abs(dy) / dune_length)
324
+ else: # ๋ฐ”๋žŒ๊ทธ๋Š˜
325
+ z = dune_height * (1 - dist) * max(0, 1 - dy / (dune_length * 0.5))
326
+
327
+ # ์ดˆ์Šน๋‹ฌ ๋ฟ” (Horns)
328
+ horn_factor = 1 + 0.5 * abs(dx / dune_width)
329
+
330
+ elevation[r, c] = max(elevation[r, c], 5.0 + z * horn_factor)
331
+
332
+ return elevation
333
+
334
+
335
+ def create_coastal_cliff(grid_size: int = 100,
336
+ cliff_height: float = 30.0,
337
+ num_stacks: int = 2) -> np.ndarray:
338
+ """
339
+ ํ•ด์•ˆ ์ ˆ๋ฒฝ (Coastal Cliff) + ์‹œ์Šคํƒ
340
+
341
+ Args:
342
+ grid_size: ๊ทธ๋ฆฌ๋“œ ํฌ๊ธฐ
343
+ cliff_height: ์ ˆ๋ฒฝ ๋†’์ด
344
+ num_stacks: ์‹œ์Šคํƒ ๊ฐœ์ˆ˜
345
+
346
+ Returns:
347
+ elevation: ๊ณ ๋„ ๋ฐฐ์—ด
348
+ """
349
+ h, w = grid_size, grid_size
350
+ elevation = np.zeros((h, w))
351
+
352
+ # ๋ฐ”๋‹ค (ํ•˜๋‹จ)
353
+ sea_line = int(h * 0.6)
354
+ elevation[sea_line:, :] = -5.0
355
+
356
+ # ์œก์ง€ + ์ ˆ๋ฒฝ
357
+ for r in range(sea_line):
358
+ cliff_dist = sea_line - r
359
+ if cliff_dist < 5:
360
+ # ์ ˆ๋ฒฝ๋ฉด
361
+ elevation[r, :] = cliff_height * (cliff_dist / 5)
362
+ else:
363
+ # ํ‰ํƒ„ํ•œ ์œก์ง€
364
+ elevation[r, :] = cliff_height
365
+
366
+ # ํŒŒ์‹๋Œ€ (Wave-cut Platform)
367
+ for r in range(sea_line, sea_line + 10):
368
+ if r < h:
369
+ elevation[r, :] = -2.0 + (r - sea_line) * 0.2
370
+
371
+ # ์‹œ์Šคํƒ (Sea Stacks)
372
+ for i in range(num_stacks):
373
+ sx = w // 3 + i * (w // 3)
374
+ sy = sea_line + 5 + i * 3
375
+
376
+ stack_height = cliff_height * 0.7
377
+
378
+ for dr in range(-3, 4):
379
+ for dc in range(-3, 4):
380
+ r, c = sy + dr, sx + dc
381
+ if 0 <= r < h and 0 <= c < w:
382
+ dist = np.sqrt(dr**2 + dc**2)
383
+ if dist < 3:
384
+ elevation[r, c] = stack_height * (1 - dist / 4)
385
+
386
+ return elevation
387
+
388
+
389
+ # ============================================
390
+ # ์• ๋‹ˆ๋ฉ”์ด์…˜์šฉ ํ˜•์„ฑ๊ณผ์ • ํ•จ์ˆ˜ (Stage-based)
391
+ # stage: 0.0 (์‹œ์ž‘) ~ 1.0 (์™„์„ฑ)
392
+ # ============================================
393
+
394
+ def create_delta_animated(grid_size: int, stage: float,
395
+ spread_angle: float = 120.0, num_channels: int = 7) -> np.ndarray:
396
+ """์‚ผ๊ฐ์ฃผ ํ˜•์„ฑ๊ณผ์ • ์• ๋‹ˆ๋ฉ”์ด์…˜"""
397
+ h, w = grid_size, grid_size
398
+ elevation = np.zeros((h, w))
399
+
400
+ apex_y = int(h * 0.2)
401
+ center_x = w // 2
402
+
403
+ # ๋ฐฐ๊ฒฝ: ๋ฐ”๋‹ค
404
+ elevation[:, :] = -5.0
405
+
406
+ # ํ•˜์ฒœ (ํ•ญ์ƒ ์กด์žฌ)
407
+ for r in range(apex_y):
408
+ for dc in range(-3, 4):
409
+ c = center_x + dc
410
+ if 0 <= c < w:
411
+ elevation[r, c] = 5.0
412
+
413
+ # Stage์— ๋”ฐ๋ผ ์‚ผ๊ฐ์ฃผ ์„ฑ์žฅ
414
+ max_reach = int((h - apex_y) * stage)
415
+ half_angle = np.radians(spread_angle / 2) * stage # ๊ฐ๋„๋„ ์ ์ง„์  ํ™•๋Œ€
416
+
417
+ for r in range(apex_y, apex_y + max_reach):
418
+ dist = r - apex_y
419
+ if dist == 0:
420
+ continue
421
+
422
+ for c in range(w):
423
+ dx = c - center_x
424
+ angle = np.arctan2(dx, dist)
425
+
426
+ if abs(angle) < half_angle:
427
+ radial_dist = np.sqrt(dx**2 + dist**2)
428
+ max_dist = max_reach if max_reach > 0 else 1
429
+ z = 10.0 * (1 - radial_dist / max_dist) * stage
430
+ elevation[r, c] = max(elevation[r, c], z)
431
+
432
+ # ๋ถ„๋ฐฐ ์ˆ˜๋กœ (stage 0.3 ์ดํ›„)
433
+ if stage > 0.3:
434
+ active_channels = int(num_channels * min(1.0, (stage - 0.3) / 0.7))
435
+ for i in range(active_channels):
436
+ channel_angle = -half_angle + (2 * half_angle) * (i / max(active_channels - 1, 1))
437
+ for r in range(apex_y, apex_y + max_reach):
438
+ dist = r - apex_y
439
+ c = int(center_x + dist * np.tan(channel_angle))
440
+ if 0 <= c < w:
441
+ for dc in range(-2, 3):
442
+ if 0 <= c + dc < w:
443
+ elevation[r, c + dc] -= 1.5
444
+
445
+ return elevation
446
+
447
+
448
+ def create_alluvial_fan_animated(grid_size: int, stage: float,
449
+ cone_angle: float = 90.0, max_height: float = 50.0) -> np.ndarray:
450
+ """์„ ์ƒ์ง€ ํ˜•์„ฑ๊ณผ์ • ์• ๋‹ˆ๋ฉ”์ด์…˜"""
451
+ h, w = grid_size, grid_size
452
+ elevation = np.zeros((h, w))
453
+
454
+ apex_y = int(h * 0.15)
455
+ center_x = w // 2
456
+
457
+ # ๋ฐฐ๊ฒฝ ์‚ฐ์ง€ (ํ•ญ์ƒ ์กด์žฌ)
458
+ for r in range(apex_y):
459
+ elevation[r, :] = max_height + (apex_y - r) * 2.0
460
+
461
+ # ํ˜‘๊ณก
462
+ for r in range(apex_y + 5):
463
+ for dc in range(-3, 4):
464
+ c = center_x + dc
465
+ if 0 <= c < w:
466
+ elevation[r, c] -= 10.0 * (1 - abs(dc) / 4)
467
+
468
+ # Stage์— ๋”ฐ๋ผ ์„ ์ƒ์ง€ ์„ฑ์žฅ
469
+ max_reach = int((h - apex_y) * stage)
470
+ half_angle = np.radians(cone_angle / 2) * (0.5 + 0.5 * stage)
471
+
472
+ for r in range(apex_y, apex_y + max_reach):
473
+ dist = r - apex_y
474
+ for c in range(w):
475
+ dx = c - center_x
476
+ if abs(np.arctan2(dx, max(dist, 1))) < half_angle:
477
+ radial = np.sqrt(dx**2 + dist**2)
478
+ z = max_height * (1 - radial / (max_reach * 1.5)) * stage
479
+ lateral_decay = 1 - abs(dx) / (w // 2)
480
+ elevation[r, c] = max(0, z * lateral_decay)
481
+
482
+ return elevation
483
+
484
+
485
+ def create_meander_animated(grid_size: int, stage: float,
486
+ amplitude: float = 0.3, num_bends: int = 3) -> np.ndarray:
487
+ """๊ณก๋ฅ˜ ํ˜•์„ฑ๊ณผ์ • ์• ๋‹ˆ๋ฉ”์ด์…˜ (์ง์„  -> ์‚ฌํ–‰ -> ์šฐ๊ฐํ˜ธ)"""
488
+ h, w = grid_size, grid_size
489
+ elevation = np.zeros((h, w))
490
+ elevation[:, :] = 10.0 # ๋ฒ”๋žŒ์›
491
+
492
+ center_x = w // 2
493
+ channel_width = max(3, w // 20)
494
+
495
+ # Stage์— ๋”ฐ๋ฅธ ์‚ฌํ–‰ ์ง„ํญ ๋ณ€ํ™” (์ง์„  -> ๊ตฝ์Œ)
496
+ current_amp = w * amplitude * stage
497
+ wl = h / num_bends
498
+
499
+ for r in range(h):
500
+ theta = 2 * np.pi * r / wl
501
+ meander_x = center_x + current_amp * np.sin(theta)
502
+
503
+ for c in range(w):
504
+ dist = abs(c - meander_x)
505
+ if dist < channel_width:
506
+ elevation[r, c] = 5.0 - (channel_width - dist) * 0.3
507
+ elif dist < channel_width * 3:
508
+ elevation[r, c] = 10.5
509
+
510
+ # ์šฐ๊ฐํ˜ธ (stage > 0.8)
511
+ if stage > 0.8:
512
+ oxbow_intensity = (stage - 0.8) / 0.2
513
+ oxbow_y = h // 2
514
+ oxbow_amp = current_amp * 1.5
515
+
516
+ for dy in range(-int(wl/4), int(wl/4)):
517
+ r = oxbow_y + dy
518
+ if 0 <= r < h:
519
+ theta = 2 * np.pi * dy / (wl/2)
520
+ ox_x = center_x + oxbow_amp * np.sin(theta)
521
+ for dc in range(-channel_width, channel_width + 1):
522
+ c = int(ox_x + dc)
523
+ if 0 <= c < w:
524
+ elevation[r, c] = 4.0 * oxbow_intensity + elevation[r, c] * (1 - oxbow_intensity)
525
+
526
+ return elevation
527
+
528
+
529
+ def create_u_valley_animated(grid_size: int, stage: float,
530
+ valley_depth: float = 100.0, valley_width: float = 0.4) -> np.ndarray:
531
+ """U์ž๊ณก ํ˜•์„ฑ๊ณผ์ • (V์ž -> U์ž ๋ณ€ํ™˜)"""
532
+ h, w = grid_size, grid_size
533
+ elevation = np.zeros((h, w))
534
+ center = w // 2
535
+
536
+ # V์ž์—์„œ U์ž๋กœ ๋ณ€ํ™˜
537
+ # stage 0: ์™„์ „ V, stage 1: ์™„์ „ U
538
+ half_width = int(w * valley_width / 2) * stage # U ๋ฐ”๋‹ฅ ๋„ˆ๋น„
539
+
540
+ for r in range(h):
541
+ for c in range(w):
542
+ dx = abs(c - center)
543
+
544
+ if dx < half_width:
545
+ # U์ž ๋ฐ”๋‹ฅ
546
+ elevation[r, c] = 0
547
+ else:
548
+ # V์—์„œ U๋กœ ์ „ํ™˜
549
+ # V: linear, U: parabolic
550
+ normalized_x = (dx - half_width) / max(1, w // 2 - half_width)
551
+ v_height = valley_depth * normalized_x # V shape
552
+ u_height = valley_depth * (normalized_x ** 2) # U shape
553
+ elevation[r, c] = v_height * (1 - stage) + u_height * stage
554
+
555
+ elevation[r, :] += (h - r) / h * 30.0
556
+
557
+ return elevation
558
+
559
+
560
+ def create_coastal_cliff_animated(grid_size: int, stage: float,
561
+ cliff_height: float = 30.0, num_stacks: int = 2) -> np.ndarray:
562
+ """ํ•ด์•ˆ ์ ˆ๋ฒฝ ํ›„ํ‡ด ๊ณผ์ •"""
563
+ h, w = grid_size, grid_size
564
+ elevation = np.zeros((h, w))
565
+
566
+ # Stage์— ๋”ฐ๋ฅธ ํ•ด์•ˆ์„  ํ›„ํ‡ด
567
+ initial_sea_line = int(h * 0.8)
568
+ final_sea_line = int(h * 0.5)
569
+ sea_line = int(initial_sea_line - (initial_sea_line - final_sea_line) * stage)
570
+
571
+ # ๋ฐ”๋‹ค
572
+ elevation[sea_line:, :] = -5.0
573
+
574
+ # ์œก์ง€ + ์ ˆ๋ฒฝ
575
+ for r in range(sea_line):
576
+ cliff_dist = sea_line - r
577
+ if cliff_dist < 5:
578
+ elevation[r, :] = cliff_height * (cliff_dist / 5)
579
+ else:
580
+ elevation[r, :] = cliff_height
581
+
582
+ # ํŒŒ์‹๋Œ€ (stage > 0.3)
583
+ if stage > 0.3:
584
+ platform_width = int(10 * (stage - 0.3) / 0.7)
585
+ for r in range(sea_line, min(h, sea_line + platform_width)):
586
+ elevation[r, :] = -2.0 + (r - sea_line) * 0.2
587
+
588
+ # ์‹œ์Šคํƒ (stage > 0.6)
589
+ if stage > 0.6:
590
+ stack_stage = (stage - 0.6) / 0.4
591
+ for i in range(num_stacks):
592
+ sx = w // 3 + i * (w // 3)
593
+ sy = sea_line + 5 + i * 3
594
+ stack_height = cliff_height * 0.7 * stack_stage
595
+
596
+ for dr in range(-3, 4):
597
+ for dc in range(-3, 4):
598
+ r, c = sy + dr, sx + dc
599
+ if 0 <= r < h and 0 <= c < w:
600
+ dist = np.sqrt(dr**2 + dc**2)
601
+ if dist < 3:
602
+ elevation[r, c] = stack_height * (1 - dist / 4)
603
+
604
+ return elevation
605
+
606
+
607
+ def create_v_valley_animated(grid_size: int, stage: float,
608
+ valley_depth: float = 80.0) -> np.ndarray:
609
+ """V์ž๊ณก ํ˜•์„ฑ๊ณผ์ • (ํ‰ํƒ„๋ฉด -> ์นจ์‹ -> ๊นŠ์€ V์ž)"""
610
+ h, w = grid_size, grid_size
611
+ elevation = np.zeros((h, w))
612
+ center = w // 2
613
+
614
+ # Stage์— ๋”ฐ๋ฅธ ์นจ์‹ ๊นŠ์ด ์ฆ๊ฐ€
615
+ current_depth = valley_depth * stage
616
+
617
+ for r in range(h):
618
+ for c in range(w):
619
+ dx = abs(c - center)
620
+
621
+ # ์ดˆ๊ธฐ ๊ณ ์› ์ƒํƒœ์—์„œ ์ ์ง„์ ์œผ๋กœ V์ž ํ˜•์„ฑ
622
+ base_height = 50.0 # ์ดˆ๊ธฐ ๊ณ ์› ๋†’์ด
623
+ v_shape = current_depth * (dx / (w // 2))
624
+
625
+ # ์นจ์‹ ์ง„ํ–‰์— ๋”ฐ๋ผ V์ž ๊นŠ์–ด์ง
626
+ elevation[r, c] = base_height - current_depth + v_shape
627
+
628
+ # ์ƒ๋ฅ˜ ๊ฒฝ์‚ฌ
629
+ elevation[r, :] += (h - r) / h * 30.0
630
+
631
+ # ํ•˜์ฒœ (๋‹จ๊ณ„์ ์œผ๋กœ ํ˜•์„ฑ)
632
+ if stage > 0.2:
633
+ channel_intensity = min(1.0, (stage - 0.2) / 0.8)
634
+ for r in range(h):
635
+ for dc in range(-2, 3):
636
+ c = center + dc
637
+ if 0 <= c < w:
638
+ elevation[r, c] -= 5 * channel_intensity
639
+
640
+ return elevation
641
+
642
+
643
+ def create_barchan_animated(grid_size: int, stage: float,
644
+ num_dunes: int = 3) -> np.ndarray:
645
+ """๋ฐ”๋ฅดํ•œ ์‚ฌ๊ตฌ ํ˜•์„ฑ๊ณผ์ • (ํ‰ํƒ„ ์‚ฌ๋ง‰ -> ๋ชจ๋ž˜ ์ถ•์  -> ์ดˆ์Šน๋‹ฌ ํ˜•์„ฑ)"""
646
+ h, w = grid_size, grid_size
647
+ elevation = np.zeros((h, w))
648
+
649
+ # ์‚ฌ๋ง‰ ๊ธฐ๋ฐ˜๋ฉด
650
+ elevation[:, :] = 5.0
651
+
652
+ # Stage์— ๋”ฐ๋ฅธ ์‚ฌ๊ตฌ ์„ฑ์žฅ
653
+ np.random.seed(42) # ์ผ๊ด€๋œ ์œ„์น˜
654
+
655
+ for i in range(num_dunes):
656
+ cy = h // 4 + i * (h // (num_dunes + 1))
657
+ cx = w // 2 + (i - num_dunes // 2) * (w // 5)
658
+
659
+ # ์ตœ์ข… ๋†’์ด * stage
660
+ final_height = 15.0 + (i * 5.0)
661
+ dune_height = final_height * stage
662
+
663
+ # ์‚ฌ๊ตฌ ํฌ๊ธฐ๋„ stage์— ๋น„๋ก€
664
+ dune_length = int((w // 5) * (0.3 + 0.7 * stage))
665
+ dune_width = int((w // 8) * (0.3 + 0.7 * stage))
666
+
667
+ if dune_length < 1 or dune_width < 1:
668
+ continue
669
+
670
+ for r in range(h):
671
+ for c in range(w):
672
+ dy = r - cy
673
+ dx = c - cx
674
+
675
+ dist = np.sqrt((dy / max(dune_length, 1)) ** 2 + (dx / max(dune_width, 1)) ** 2)
676
+
677
+ if dist < 1.0:
678
+ if dy < 0: # ๋ฐ”๋žŒ๋ฐ›์ด
679
+ z = dune_height * (1 - dist) * (1 - abs(dy) / max(dune_length, 1))
680
+ else: # ๋ฐ”๋žŒ๊ทธ๋Š˜
681
+ z = dune_height * (1 - dist) * max(0, 1 - dy / (dune_length * 0.5))
682
+
683
+ horn_factor = 1 + 0.5 * abs(dx / max(dune_width, 1))
684
+ elevation[r, c] = max(elevation[r, c], 5.0 + z * horn_factor)
685
+
686
+ return elevation
687
+ # ============================================
688
+ # ํ™•์žฅ ์ง€ํ˜• (Extended Landforms)
689
+ # ============================================
690
+
691
+ def create_incised_meander(grid_size: int = 100, stage: float = 1.0,
692
+ valley_depth: float = 80.0, num_terraces: int = 3) -> np.ndarray:
693
+ """
694
+ ๊ฐ์ž…๊ณก๋ฅ˜ (Incised Meander) + ํ•˜์•ˆ๋‹จ๊ตฌ (River Terraces)
695
+
696
+ ์œต๊ธฐ ํ™˜๊ฒฝ์—์„œ ๊ณก๋ฅ˜๊ฐ€ ์•”๋ฐ˜์„ ํŒŒ๊ณ  ๋“ค์–ด๊ฐ€๋ฉด์„œ ํ˜•์„ฑ
697
+ """
698
+ h, w = grid_size, grid_size
699
+ elevation = np.zeros((h, w))
700
+
701
+ center_x = w // 2
702
+ amplitude = w * 0.25 * stage
703
+ wl = h / 3 # 3 bends
704
+ channel_width = max(3, w // 25)
705
+
706
+ # ๊ธฐ๋ฐ˜ ๊ณ ์›
707
+ elevation[:, :] = valley_depth
708
+
709
+ # ๊ฐ์ž… ๊ณก๋ฅ˜ ํŒŒ๊ธฐ
710
+ for r in range(h):
711
+ theta = 2 * np.pi * r / wl
712
+ meander_x = center_x + amplitude * np.sin(theta)
713
+
714
+ for c in range(w):
715
+ dist = abs(c - meander_x)
716
+
717
+ if dist < channel_width:
718
+ # ํ•˜๋„ (๊ฐ€์žฅ ๊นŠ์Œ)
719
+ elevation[r, c] = 5.0
720
+ elif dist < channel_width * 2:
721
+ # ๊ธ‰๊ฒฝ์‚ฌ ์ธก๋ฒฝ
722
+ t = (dist - channel_width) / channel_width
723
+ elevation[r, c] = 5.0 + (valley_depth - 5.0) * t
724
+
725
+ # ํ•˜์•ˆ๋‹จ๊ตฌ (๊ณ„๋‹จ)
726
+ terrace_heights = [valley_depth * (0.3 + 0.2 * i) for i in range(num_terraces)]
727
+
728
+ for terrace_h in terrace_heights:
729
+ for r in range(h):
730
+ theta = 2 * np.pi * r / wl
731
+ meander_x = center_x + amplitude * np.sin(theta) * 0.8
732
+
733
+ for c in range(w):
734
+ dist = abs(c - meander_x)
735
+ if channel_width * 3 < dist < channel_width * 4:
736
+ if elevation[r, c] > terrace_h:
737
+ elevation[r, c] = terrace_h
738
+
739
+ return elevation
740
+
741
+
742
+ def create_free_meander(grid_size: int = 100, stage: float = 1.0,
743
+ num_bends: int = 4) -> np.ndarray:
744
+ """
745
+ ์ž์œ ๊ณก๋ฅ˜ (Free Meander) + ๋ฒ”๋žŒ์› (Floodplain) + ์ž์—ฐ์ œ๋ฐฉ (Natural Levee)
746
+
747
+ ์ถฉ์  ํ‰์•ผ ์œ„๋ฅผ ์ž์œ ๋กญ๊ฒŒ ์‚ฌํ–‰
748
+ """
749
+ h, w = grid_size, grid_size
750
+ elevation = np.zeros((h, w))
751
+
752
+ # ๋ฒ”๋žŒ์› ๊ธฐ๋ฐ˜
753
+ elevation[:, :] = 10.0
754
+
755
+ center_x = w // 2
756
+ amplitude = w * 0.3 * stage
757
+ wl = h / num_bends
758
+ channel_width = max(3, w // 20)
759
+
760
+ for r in range(h):
761
+ theta = 2 * np.pi * r / wl
762
+ meander_x = center_x + amplitude * np.sin(theta)
763
+
764
+ for c in range(w):
765
+ dist = abs(c - meander_x)
766
+
767
+ if dist < channel_width:
768
+ # ํ•˜๋„
769
+ elevation[r, c] = 5.0 - (channel_width - dist) * 0.2
770
+ elif dist < channel_width * 2:
771
+ # ์ž์—ฐ์ œ๋ฐฉ (Levee) - ํ•˜๋„๋ณด๋‹ค ์•ฝ๊ฐ„ ๋†’์Œ
772
+ elevation[r, c] = 11.0
773
+ elif dist < channel_width * 4:
774
+ # ๋ฐฐํ›„์Šต์ง€ (Backswamp) - ์•ฝ๊ฐ„ ๋‚ฎ์Œ
775
+ elevation[r, c] = 9.5
776
+
777
+ # ์šฐ๊ฐํ˜ธ (Oxbow Lake)
778
+ if stage > 0.7:
779
+ oxbow_y = h // 2
780
+ for dy in range(-int(wl/4), int(wl/4)):
781
+ r = oxbow_y + dy
782
+ if 0 <= r < h:
783
+ theta = 2 * np.pi * dy / (wl/2)
784
+ ox_x = center_x + amplitude * 1.3 * np.sin(theta)
785
+ for dc in range(-channel_width, channel_width + 1):
786
+ c = int(ox_x + dc)
787
+ if 0 <= c < w:
788
+ elevation[r, c] = 4.5
789
+
790
+ return elevation
791
+
792
+
793
+ def create_bird_foot_delta(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
794
+ """์กฐ์กฑ์ƒ ์‚ผ๊ฐ์ฃผ (Bird-foot Delta) - ๋ฏธ์‹œ์‹œํ”ผ๊ฐ•ํ˜•"""
795
+ h, w = grid_size, grid_size
796
+ elevation = np.zeros((h, w))
797
+ elevation[:, :] = -5.0 # ๋ฐ”๋‹ค
798
+
799
+ apex_y = int(h * 0.15)
800
+ center_x = w // 2
801
+
802
+ # ๊ฐ€๋Š˜๊ณ  ๊ธด ๋ถ„๋ฐฐ์ˆ˜๋กœ๋“ค
803
+ num_fingers = 5
804
+ max_length = int((h - apex_y) * stage)
805
+
806
+ for i in range(num_fingers):
807
+ angle = np.radians(-30 + 15 * i) # -30 to +30 degrees
808
+
809
+ for d in range(max_length):
810
+ r = apex_y + int(d * np.cos(angle))
811
+ c = center_x + int(d * np.sin(angle))
812
+
813
+ if 0 <= r < h and 0 <= c < w:
814
+ # ์ข์€ finger ํ˜•ํƒœ
815
+ for dc in range(-3, 4):
816
+ for dr in range(-2, 3):
817
+ nr, nc = r + dr, c + dc
818
+ if 0 <= nr < h and 0 <= nc < w:
819
+ dist = np.sqrt(dr**2 + dc**2)
820
+ z = 8.0 * (1 - d / max_length) * (1 - dist / 4) * stage
821
+ elevation[nr, nc] = max(elevation[nr, nc], z)
822
+
823
+ # ํ•˜์ฒœ
824
+ for r in range(apex_y):
825
+ for dc in range(-3, 4):
826
+ if 0 <= center_x + dc < w:
827
+ elevation[r, center_x + dc] = 6.0
828
+
829
+ return elevation
830
+
831
+
832
+ def create_arcuate_delta(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
833
+ """ํ˜ธ์ƒ ์‚ผ๊ฐ์ฃผ (Arcuate Delta) - ๋‚˜์ผ๊ฐ•ํ˜•"""
834
+ h, w = grid_size, grid_size
835
+ elevation = np.zeros((h, w))
836
+ elevation[:, :] = -5.0
837
+
838
+ apex_y = int(h * 0.2)
839
+ center_x = w // 2
840
+
841
+ # ๋ถ€๋“œ๋Ÿฌ์šด ํ˜ธ ํ˜•ํƒœ
842
+ max_reach = int((h - apex_y) * stage)
843
+
844
+ for r in range(apex_y, apex_y + max_reach):
845
+ dist = r - apex_y
846
+ # Arc width increases with distance
847
+ arc_width = int(dist * 0.8)
848
+
849
+ for c in range(max(0, center_x - arc_width), min(w, center_x + arc_width)):
850
+ dx = abs(c - center_x)
851
+ radial = np.sqrt(dx**2 + dist**2)
852
+
853
+ # Smooth arc edge
854
+ edge_dist = arc_width - dx
855
+ if edge_dist > 0:
856
+ z = 10.0 * (1 - radial / (max_reach * 1.2)) * min(1, edge_dist / 10)
857
+ elevation[r, c] = max(elevation[r, c], z * stage)
858
+
859
+ # ํ•˜์ฒœ
860
+ for r in range(apex_y):
861
+ for dc in range(-4, 5):
862
+ if 0 <= center_x + dc < w:
863
+ elevation[r, center_x + dc] = 6.0
864
+
865
+ return elevation
866
+
867
+
868
+ def create_cuspate_delta(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
869
+ """์ฒจ๋‘์ƒ ์‚ผ๊ฐ์ฃผ (Cuspate Delta) - ํ‹ฐ๋ฒ ๋ฅด๊ฐ•ํ˜•"""
870
+ h, w = grid_size, grid_size
871
+ elevation = np.zeros((h, w))
872
+ elevation[:, :] = -5.0
873
+
874
+ apex_y = int(h * 0.2)
875
+ center_x = w // 2
876
+ point_y = int(apex_y + (h - apex_y) * 0.8 * stage)
877
+
878
+ # ๋พฐ์กฑํ•œ ์‚ผ๊ฐํ˜• ํ˜•ํƒœ
879
+ for r in range(apex_y, point_y):
880
+ dist = r - apex_y
881
+ total_dist = point_y - apex_y
882
+
883
+ # Width narrows toward point
884
+ width = int((w // 3) * (1 - dist / total_dist))
885
+
886
+ for c in range(max(0, center_x - width), min(w, center_x + width)):
887
+ dx = abs(c - center_x)
888
+ z = 10.0 * (1 - dist / total_dist) * (1 - dx / max(width, 1))
889
+ elevation[r, c] = max(elevation[r, c], z * stage)
890
+
891
+ # ํ•˜์ฒœ
892
+ for r in range(apex_y):
893
+ for dc in range(-3, 4):
894
+ if 0 <= center_x + dc < w:
895
+ elevation[r, center_x + dc] = 6.0
896
+
897
+ return elevation
898
+
899
+
900
+ def create_cirque(grid_size: int = 100, stage: float = 1.0,
901
+ depth: float = 50.0) -> np.ndarray:
902
+ """๊ถŒ๊ณก (Cirque) - ๋น™ํ•˜ ์‹œ์ž‘์ """
903
+ h, w = grid_size, grid_size
904
+ elevation = np.zeros((h, w))
905
+
906
+ # ์‚ฐ์•… ๋ฐฐ๊ฒฝ
907
+ elevation[:, :] = depth + 30.0
908
+
909
+ # ๊ถŒ๊ณก ์œ„์น˜ (์ƒ๋‹จ ์ค‘์•™)
910
+ cirque_y = int(h * 0.3)
911
+ cirque_x = w // 2
912
+ cirque_radius = int(w * 0.25 * (0.5 + 0.5 * stage))
913
+
914
+ for r in range(h):
915
+ for c in range(w):
916
+ dy = r - cirque_y
917
+ dx = c - cirque_x
918
+ dist = np.sqrt(dy**2 + dx**2)
919
+
920
+ if dist < cirque_radius:
921
+ # ๋ฐ˜์›ํ˜• ์›€ํ‘นํ•œ ํ˜•ํƒœ
922
+ # ๋ฐ”๋‹ฅ์€ ํ‰ํƒ„, ํ›„๋ฒฝ(headwall)์€ ๊ธ‰๊ฒฝ์‚ฌ
923
+ if dy < 0: # ํ›„๋ฒฝ
924
+ z = depth * (1 - dist / cirque_radius) * 0.3
925
+ else: # ๋ฐ”๋‹ฅ
926
+ z = depth * 0.1
927
+ elevation[r, c] = z
928
+
929
+ return elevation
930
+
931
+
932
+ def create_horn(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
933
+ """ํ˜ธ๋ฅธ (Horn) - ํ”ผ๋ผ๋ฏธ๋“œํ˜• ๋ด‰์šฐ๋ฆฌ"""
934
+ h, w = grid_size, grid_size
935
+ elevation = np.zeros((h, w))
936
+
937
+ center = (h // 2, w // 2)
938
+ peak_height = 100.0 * stage
939
+
940
+ # 4๋ฐฉํ–ฅ ๊ถŒ๊ณก์— ์˜ํ•œ ํ˜ธ๋ฅธ ํ˜•์„ฑ
941
+ num_cirques = 4
942
+ cirque_radius = int(w * 0.3)
943
+
944
+ for r in range(h):
945
+ for c in range(w):
946
+ dy = r - center[0]
947
+ dx = c - center[1]
948
+ dist = np.sqrt(dy**2 + dx**2)
949
+
950
+ # ๊ธฐ๋ณธ ํ”ผ๋ผ๋ฏธ๋“œ ํ˜•ํƒœ
951
+ elevation[r, c] = peak_height * max(0, 1 - dist / (w // 2))
952
+
953
+ # 4๋ฐฉํ–ฅ ๊ถŒ๊ณก ํŒŒ๊ธฐ
954
+ for i in range(num_cirques):
955
+ angle = i * np.pi / 2
956
+ cx = center[1] + int(cirque_radius * 0.8 * np.cos(angle))
957
+ cy = center[0] + int(cirque_radius * 0.8 * np.sin(angle))
958
+
959
+ cdist = np.sqrt((r - cy)**2 + (c - cx)**2)
960
+ if cdist < cirque_radius * 0.6:
961
+ # ๊ถŒ๊ณก ํŒŒ๊ธฐ
962
+ elevation[r, c] = min(elevation[r, c],
963
+ 20.0 + 30.0 * (cdist / (cirque_radius * 0.6)))
964
+
965
+ return elevation
966
+
967
+
968
+ def create_shield_volcano(grid_size: int = 100, stage: float = 1.0,
969
+ max_height: float = 40.0) -> np.ndarray:
970
+ """์ˆœ์ƒํ™”์‚ฐ (Shield Volcano) - ์™„๋งŒํ•œ ๊ฒฝ์‚ฌ"""
971
+ h, w = grid_size, grid_size
972
+ elevation = np.zeros((h, w))
973
+
974
+ center = (h // 2, w // 2)
975
+ radius = w // 2
976
+
977
+ for r in range(h):
978
+ for c in range(w):
979
+ dist = np.sqrt((r - center[0])**2 + (c - center[1])**2)
980
+
981
+ if dist < radius:
982
+ # ์™„๋งŒํ•œ ํฌ๋ฌผ์„  ํ˜•ํƒœ (๊ฒฝ์‚ฌ 5-10๋„)
983
+ elevation[r, c] = max_height * (1 - (dist / radius)**2) * stage
984
+
985
+ # ์ •์ƒ๋ถ€ ํ™”๊ตฌ
986
+ crater_radius = int(radius * 0.1)
987
+ for r in range(h):
988
+ for c in range(w):
989
+ dist = np.sqrt((r - center[0])**2 + (c - center[1])**2)
990
+ if dist < crater_radius:
991
+ elevation[r, c] = max_height * 0.9 * stage
992
+
993
+ return elevation
994
+
995
+
996
+ def create_stratovolcano(grid_size: int = 100, stage: float = 1.0,
997
+ max_height: float = 80.0) -> np.ndarray:
998
+ """์„ฑ์ธตํ™”์‚ฐ (Stratovolcano) - ๊ธ‰ํ•œ ์›๋ฟ”ํ˜•"""
999
+ h, w = grid_size, grid_size
1000
+ elevation = np.zeros((h, w))
1001
+
1002
+ center = (h // 2, w // 2)
1003
+ radius = int(w * 0.4)
1004
+
1005
+ for r in range(h):
1006
+ for c in range(w):
1007
+ dist = np.sqrt((r - center[0])**2 + (c - center[1])**2)
1008
+
1009
+ if dist < radius:
1010
+ # ๊ธ‰ํ•œ ์›๋ฟ” (๊ฒฝ์‚ฌ 25-35๋„)
1011
+ elevation[r, c] = max_height * (1 - dist / radius) * stage
1012
+
1013
+ # ์ •์ƒ๋ถ€ ํ™”๊ตฌ
1014
+ crater_radius = int(radius * 0.08)
1015
+ crater_depth = 10.0
1016
+ for r in range(h):
1017
+ for c in range(w):
1018
+ dist = np.sqrt((r - center[0])**2 + (c - center[1])**2)
1019
+ if dist < crater_radius:
1020
+ elevation[r, c] = max_height * stage - crater_depth
1021
+
1022
+ return elevation
1023
+
1024
+
1025
+ def create_caldera(grid_size: int = 100, stage: float = 1.0,
1026
+ rim_height: float = 50.0) -> np.ndarray:
1027
+ """์นผ๋ฐ๋ผ (Caldera) - ํ™”๊ตฌ ํ•จ๋ชฐ"""
1028
+ h, w = grid_size, grid_size
1029
+ elevation = np.zeros((h, w))
1030
+
1031
+ center = (h // 2, w // 2)
1032
+ outer_radius = int(w * 0.45)
1033
+ caldera_radius = int(w * 0.3)
1034
+
1035
+ for r in range(h):
1036
+ for c in range(w):
1037
+ dist = np.sqrt((r - center[0])**2 + (c - center[1])**2)
1038
+
1039
+ if dist < outer_radius:
1040
+ if dist < caldera_radius:
1041
+ # ์นผ๋ฐ๋ผ ๋ฐ”๋‹ฅ (ํ‰ํƒ„, ํ˜ธ์ˆ˜ ๊ฐ€๋Šฅ)
1042
+ elevation[r, c] = 5.0
1043
+ else:
1044
+ # ์นผ๋ฐ๋ผ ๋ฒฝ (๊ธ‰๊ฒฝ์‚ฌ)
1045
+ t = (dist - caldera_radius) / (outer_radius - caldera_radius)
1046
+ elevation[r, c] = 5.0 + rim_height * (1 - t) * stage
1047
+
1048
+ return elevation
1049
+
1050
+
1051
+ def create_mesa_butte(grid_size: int = 100, stage: float = 1.0,
1052
+ num_mesas: int = 2) -> np.ndarray:
1053
+ """๋ฉ”์‚ฌ/๋ทฐํŠธ (Mesa/Butte) - ํƒ์ƒ์ง€"""
1054
+ h, w = grid_size, grid_size
1055
+ elevation = np.zeros((h, w))
1056
+
1057
+ # ์‚ฌ๋ง‰ ๊ธฐ๋ฐ˜
1058
+ elevation[:, :] = 5.0
1059
+
1060
+ mesa_height = 40.0 * stage
1061
+
1062
+ # ๋ฉ”์‚ฌ ๋ฐฐ์น˜
1063
+ positions = [(h//3, w//3), (h//2, 2*w//3)]
1064
+ sizes = [(w//4, w//5), (w//6, w//6)] # ๋ฉ”์‚ฌ, ๋ทฐํŠธ
1065
+
1066
+ for i, ((my, mx), (sw, sh)) in enumerate(zip(positions[:num_mesas], sizes[:num_mesas])):
1067
+ for r in range(h):
1068
+ for c in range(w):
1069
+ if abs(r - my) < sh and abs(c - mx) < sw:
1070
+ # ํ‰ํƒ„ํ•œ ์ •์ƒ๋ถ€
1071
+ elevation[r, c] = mesa_height
1072
+ elif abs(r - my) < sh + 3 and abs(c - mx) < sw + 3:
1073
+ # ๊ธ‰๊ฒฝ์‚ฌ ์ธก๋ฒฝ
1074
+ edge_dist = min(abs(abs(r - my) - sh), abs(abs(c - mx) - sw))
1075
+ elevation[r, c] = mesa_height * (1 - edge_dist / 3)
1076
+
1077
+ return elevation
1078
+
1079
+
1080
+ def create_spit_lagoon(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
1081
+ """์‚ฌ์ทจ (Spit) + ์„ํ˜ธ (Lagoon)"""
1082
+ h, w = grid_size, grid_size
1083
+ elevation = np.zeros((h, w))
1084
+
1085
+ # ๋ฐ”๋‹ค (์˜ค๋ฅธ์ชฝ)
1086
+ sea_line = int(w * 0.6)
1087
+ elevation[:, sea_line:] = -5.0
1088
+
1089
+ # ์œก์ง€ (์™ผ์ชฝ)
1090
+ elevation[:, :sea_line] = 10.0
1091
+
1092
+ # ์‚ฌ์ทจ (์—ฐ์•ˆ๋ฅ˜ ๋ฐฉํ–ฅ์œผ๋กœ ๊ธธ๊ฒŒ)
1093
+ spit_start = int(h * 0.3)
1094
+ spit_length = int(h * 0.5 * stage)
1095
+ spit_width = 5
1096
+
1097
+ for r in range(spit_start, min(h, spit_start + spit_length)):
1098
+ # ์‚ฌ์ทจ๊ฐ€ ๋ฐ”๋‹ค ์ชฝ์œผ๋กœ ํœ˜์–ด์ง
1099
+ curve = int((r - spit_start) / spit_length * (w * 0.15))
1100
+ spit_x = sea_line + curve
1101
+
1102
+ for dc in range(-spit_width, spit_width + 1):
1103
+ c = spit_x + dc
1104
+ if 0 <= c < w:
1105
+ elevation[r, c] = 3.0 * (1 - abs(dc) / spit_width)
1106
+
1107
+ # ์„ํ˜ธ (์‚ฌ์ทจ ์•ˆ์ชฝ)
1108
+ if stage > 0.5:
1109
+ for r in range(spit_start, spit_start + int(spit_length * 0.8)):
1110
+ curve = int((r - spit_start) / spit_length * (w * 0.1))
1111
+ for c in range(sea_line - 5, sea_line + curve):
1112
+ if 0 <= c < w:
1113
+ if elevation[r, c] < 3.0:
1114
+ elevation[r, c] = -2.0 # ์–•์€ ์„ํ˜ธ
1115
+
1116
+ return elevation
1117
+
1118
+
1119
+ # ============================================
1120
+ # ์ถ”๊ฐ€ ์ง€ํ˜• (Additional Landforms)
1121
+ # ============================================
1122
+
1123
+ def create_fjord(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
1124
+ """ํ”ผ์˜ค๋ฅด๋“œ (Fjord) - ๋น™ํ•˜๊ฐ€ ํŒŒ๋‚ธ U์ž๊ณก์— ๋ฐ”๋‹ค ์œ ์ž…"""
1125
+ h, w = grid_size, grid_size
1126
+ elevation = np.zeros((h, w))
1127
+
1128
+ # ์‚ฐ์•… ์ง€ํ˜•
1129
+ elevation[:, :] = 80.0
1130
+
1131
+ center = w // 2
1132
+ valley_width = int(w * 0.2)
1133
+
1134
+ # U์ž๊ณก + ๋ฐ”๋‹ค ์œ ์ž…
1135
+ sea_line = int(h * 0.7)
1136
+
1137
+ for r in range(h):
1138
+ for c in range(w):
1139
+ dx = abs(c - center)
1140
+
1141
+ if dx < valley_width:
1142
+ # U์ž ๋ฐ”๋‹ฅ
1143
+ if r < sea_line:
1144
+ elevation[r, c] = 10.0 # ์œก์ง€ ๋ฐ”๋‹ฅ
1145
+ else:
1146
+ elevation[r, c] = -30.0 * stage # ๋ฐ”๋‹ค
1147
+ elif dx < valley_width + 10:
1148
+ # U์ž ์ธก๋ฒฝ
1149
+ t = (dx - valley_width) / 10
1150
+ base = -30.0 if r >= sea_line else 10.0
1151
+ elevation[r, c] = base + 70.0 * t
1152
+
1153
+ return elevation
1154
+
1155
+
1156
+ def create_drumlin(grid_size: int = 100, stage: float = 1.0,
1157
+ num_drumlins: int = 5) -> np.ndarray:
1158
+ """๋“œ๋Ÿผ๋ฆฐ (Drumlin) - ๋น™ํ•˜ ๋ฐฉํ–ฅ ํƒ€์›ํ˜• ์–ธ๋•"""
1159
+ h, w = grid_size, grid_size
1160
+ elevation = np.zeros((h, w))
1161
+
1162
+ elevation[:, :] = 5.0 # ๋น™ํ•˜ ํ‡ด์  ํ‰์›
1163
+
1164
+ for i in range(num_drumlins):
1165
+ # ๋“œ๋Ÿผ๋ฆฐ ์œ„์น˜ (๋น™ํ•˜ ํ๋ฆ„ ๋ฐฉํ–ฅ์œผ๋กœ ์ •๋ ฌ)
1166
+ cy = int(h * 0.2 + (i % 3) * h * 0.25)
1167
+ cx = int(w * 0.2 + (i // 3) * w * 0.3)
1168
+
1169
+ # ํƒ€์›ํ˜• (๋น™ํ•˜ ๋ฐฉํ–ฅ์œผ๋กœ ๊ธธ์ญ‰)
1170
+ length = int(w * 0.15 * stage)
1171
+ width = int(w * 0.06 * stage)
1172
+ height = 15.0 * stage
1173
+
1174
+ for r in range(h):
1175
+ for c in range(w):
1176
+ dy = (r - cy) / max(length, 1)
1177
+ dx = (c - cx) / max(width, 1)
1178
+ dist = np.sqrt(dy**2 + dx**2)
1179
+
1180
+ if dist < 1.0:
1181
+ # ๋พฐ์กฑํ•œ ๋น™ํ•˜ ์ƒ๋ฅ˜, ์™„๋งŒํ•œ ํ•˜๋ฅ˜
1182
+ asymmetry = 1.0 if dy < 0 else 0.7
1183
+ z = height * (1 - dist) * asymmetry
1184
+ elevation[r, c] = max(elevation[r, c], 5.0 + z)
1185
+
1186
+ return elevation
1187
+
1188
+
1189
+ def create_moraine(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
1190
+ """๋น™ํ‡ด์„ (Moraine) - ์ธกํ‡ด์„, ์ข…ํ‡ด์„"""
1191
+ h, w = grid_size, grid_size
1192
+ elevation = np.zeros((h, w))
1193
+
1194
+ # ๋น™ํ•˜ ๊ณ„๏ฟฝ๏ฟฝ๏ฟฝ ๋ฐฐ๊ฒฝ
1195
+ elevation[:, :] = 20.0
1196
+ center = w // 2
1197
+
1198
+ # ๋น™ํ•˜ ๋ณธ์ฒด (๊ณผ๊ฑฐ)
1199
+ glacier_width = int(w * 0.3)
1200
+ for r in range(h):
1201
+ for c in range(w):
1202
+ if abs(c - center) < glacier_width:
1203
+ elevation[r, c] = 5.0 # ๋น™ํ•˜ ๋ฐ”๋‹ฅ
1204
+
1205
+ # ์ธกํ‡ด์„ (Lateral Moraine)
1206
+ moraine_height = 15.0 * stage
1207
+ for r in range(h):
1208
+ for side in [-1, 1]:
1209
+ moraine_c = center + side * glacier_width
1210
+ for dc in range(-5, 6):
1211
+ c = moraine_c + dc
1212
+ if 0 <= c < w:
1213
+ z = moraine_height * (1 - abs(dc) / 6)
1214
+ elevation[r, c] = max(elevation[r, c], z)
1215
+
1216
+ # ์ข…ํ‡ด์„ (Terminal Moraine)
1217
+ terminal_r = int(h * 0.8)
1218
+ for r in range(terminal_r - 5, min(h, terminal_r + 5)):
1219
+ for c in range(center - glacier_width, center + glacier_width):
1220
+ if 0 <= c < w:
1221
+ dr = abs(r - terminal_r)
1222
+ z = moraine_height * 1.2 * (1 - dr / 6)
1223
+ elevation[r, c] = max(elevation[r, c], z)
1224
+
1225
+ return elevation
1226
+
1227
+
1228
+ def create_braided_river(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
1229
+ """๋ง์ƒํ•˜์ฒœ (Braided River) - ์—ฌ๋Ÿฌ ์ˆ˜๋กœ"""
1230
+ h, w = grid_size, grid_size
1231
+ elevation = np.zeros((h, w))
1232
+
1233
+ # ๋„“์€ ํ•˜์ƒ
1234
+ elevation[:, :] = 10.0
1235
+
1236
+ center = w // 2
1237
+ river_width = int(w * 0.5)
1238
+
1239
+ # ๋„“๊ณ  ์–•์€ ํ•˜์ƒ
1240
+ for c in range(center - river_width // 2, center + river_width // 2):
1241
+ if 0 <= c < w:
1242
+ elevation[:, c] = 5.0
1243
+
1244
+ # ์—ฌ๋Ÿฌ ์ˆ˜๋กœ์™€ ์‚ฌ์ฃผ (๋ชจ๋ž˜์„ฌ)
1245
+ num_channels = int(3 + 4 * stage)
1246
+ np.random.seed(42)
1247
+
1248
+ for r in range(h):
1249
+ # ํ˜„์žฌ ํ–‰์˜ ์ˆ˜๋กœ ์œ„์น˜
1250
+ for i in range(num_channels):
1251
+ channel_x = center - river_width // 3 + int((i / num_channels) * river_width * 0.7)
1252
+ channel_x += int(10 * np.sin(r / 10 + i)) # ์•ฝ๊ฐ„ ์‚ฌํ–‰
1253
+
1254
+ for dc in range(-2, 3):
1255
+ c = channel_x + dc
1256
+ if 0 <= c < w:
1257
+ elevation[r, c] = 3.0
1258
+
1259
+ # ์‚ฌ์ฃผ (๋ชจ๋ž˜์„ฌ)
1260
+ for i in range(int(5 * stage)):
1261
+ bar_r = int(h * 0.2 + i * h * 0.15)
1262
+ bar_c = center + int((i - 2) * w * 0.1)
1263
+
1264
+ for dr in range(-5, 6):
1265
+ for dc in range(-8, 9):
1266
+ r, c = bar_r + dr, bar_c + dc
1267
+ if 0 <= r < h and 0 <= c < w:
1268
+ dist = np.sqrt((dr/5)**2 + (dc/8)**2)
1269
+ if dist < 1.0:
1270
+ elevation[r, c] = max(elevation[r, c], 6.0 * (1 - dist))
1271
+
1272
+ return elevation
1273
+
1274
+
1275
+ def create_waterfall(grid_size: int = 100, stage: float = 1.0,
1276
+ drop_height: float = 50.0) -> np.ndarray:
1277
+ """ํญํฌ (Waterfall) - ์ฐจ๋ณ„์นจ์‹"""
1278
+ h, w = grid_size, grid_size
1279
+ elevation = np.zeros((h, w))
1280
+
1281
+ center = w // 2
1282
+ fall_r = int(h * 0.4)
1283
+
1284
+ # ์ƒ๋ฅ˜ (๋†’์€ ๊ฒฝ์•”์ธต)
1285
+ for r in range(fall_r):
1286
+ for c in range(w):
1287
+ elevation[r, c] = drop_height + 20.0 + (fall_r - r) * 0.5
1288
+
1289
+ # ํญํฌ (๊ธ‰๊ฒฝ์‚ฌ)
1290
+ for r in range(fall_r, fall_r + 5):
1291
+ for c in range(w):
1292
+ t = (r - fall_r) / 5
1293
+ elevation[r, c] = drop_height * (1 - t) + 20.0
1294
+
1295
+ # ํ•˜๋ฅ˜
1296
+ for r in range(fall_r + 5, h):
1297
+ for c in range(w):
1298
+ elevation[r, c] = 20.0 - (r - fall_r - 5) * 0.2
1299
+
1300
+ # ํ•˜์ฒœ ์ˆ˜๋กœ
1301
+ for r in range(h):
1302
+ for dc in range(-4, 5):
1303
+ c = center + dc
1304
+ if 0 <= c < w:
1305
+ elevation[r, c] -= 5.0
1306
+
1307
+ # ํ”Œ๋Ÿฐ์ง€ํ’€ (ํญํ˜ธ)
1308
+ pool_r = fall_r + 5
1309
+ for dr in range(-5, 6):
1310
+ for dc in range(-6, 7):
1311
+ r, c = pool_r + dr, center + dc
1312
+ if 0 <= r < h and 0 <= c < w:
1313
+ dist = np.sqrt(dr**2 + dc**2)
1314
+ if dist < 6:
1315
+ elevation[r, c] = min(elevation[r, c], 10.0)
1316
+
1317
+ return elevation
1318
+
1319
+
1320
+ def create_karst_doline(grid_size: int = 100, stage: float = 1.0,
1321
+ num_dolines: int = 5) -> np.ndarray:
1322
+ """๋Œ๋ฆฌ๋„ค (Doline/Sinkhole) - ์นด๋ฅด์ŠคํŠธ ์ง€ํ˜•"""
1323
+ h, w = grid_size, grid_size
1324
+ elevation = np.zeros((h, w))
1325
+
1326
+ # ์„ํšŒ์•” ๋Œ€์ง€
1327
+ elevation[:, :] = 30.0
1328
+
1329
+ np.random.seed(42)
1330
+ for i in range(num_dolines):
1331
+ dy = int(h * 0.2 + np.random.rand() * h * 0.6)
1332
+ dx = int(w * 0.2 + np.random.rand() * w * 0.6)
1333
+ radius = int(w * 0.08 * (0.5 + np.random.rand() * 0.5))
1334
+ depth = 20.0 * stage * (0.5 + np.random.rand() * 0.5)
1335
+
1336
+ for r in range(h):
1337
+ for c in range(w):
1338
+ dist = np.sqrt((r - dy)**2 + (c - dx)**2)
1339
+ if dist < radius:
1340
+ z = depth * (1 - (dist / radius) ** 2)
1341
+ elevation[r, c] = max(0, elevation[r, c] - z)
1342
+
1343
+ return elevation
1344
+
1345
+
1346
+ def create_ria_coast(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
1347
+ """๋ฆฌ์•„์Šค์‹ ํ•ด์•ˆ (Ria Coast) - ์นจ์ˆ˜๋œ ํ•˜๊ณก"""
1348
+ h, w = grid_size, grid_size
1349
+ elevation = np.zeros((h, w))
1350
+
1351
+ # ์‚ฐ์ง€ ๋ฐฐ๊ฒฝ
1352
+ elevation[:, :] = 40.0
1353
+
1354
+ # ์—ฌ๋Ÿฌ ๊ฐœ์˜ V์ž๊ณก (์นจ์ˆ˜๋จ)
1355
+ num_valleys = 4
1356
+ sea_level = int(h * 0.6)
1357
+
1358
+ for i in range(num_valleys):
1359
+ valley_x = int(w * 0.15 + i * w * 0.2)
1360
+
1361
+ for r in range(h):
1362
+ for c in range(w):
1363
+ dx = abs(c - valley_x)
1364
+
1365
+ if dx < 8:
1366
+ # V์ž๊ณก
1367
+ depth = 30.0 * (1 - dx / 8)
1368
+ elevation[r, c] -= depth
1369
+
1370
+ # ํ•ด์ˆ˜๋ฉด ์ดํ•˜ = ๋ฐ”๋‹ค
1371
+ for r in range(sea_level, h):
1372
+ for c in range(w):
1373
+ if elevation[r, c] < 10:
1374
+ elevation[r, c] = -5.0 * stage # ์นจ์ˆ˜
1375
+
1376
+ return elevation
1377
+
1378
+
1379
+ def create_tombolo(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
1380
+ """์œก๊ณ„์‚ฌ์ฃผ (Tombolo) - ์œก์ง€์™€ ์„ฌ์„ ์—ฐ๊ฒฐ"""
1381
+ h, w = grid_size, grid_size
1382
+ elevation = np.zeros((h, w))
1383
+
1384
+ # ๋ฐ”๋‹ค
1385
+ elevation[:, :] = -5.0
1386
+
1387
+ # ๋ณธํ†  (์™ผ์ชฝ)
1388
+ for c in range(int(w * 0.3)):
1389
+ elevation[:, c] = 15.0
1390
+
1391
+ # ์„ฌ (์˜ค๋ฅธ์ชฝ)
1392
+ island_cy = h // 2
1393
+ island_cx = int(w * 0.75)
1394
+ island_radius = int(w * 0.12)
1395
+
1396
+ for r in range(h):
1397
+ for c in range(w):
1398
+ dist = np.sqrt((r - island_cy)**2 + (c - island_cx)**2)
1399
+ if dist < island_radius:
1400
+ elevation[r, c] = 20.0 * (1 - dist / island_radius / 1.5)
1401
+
1402
+ # ์œก๊ณ„์‚ฌ์ฃผ (์—ฐ๊ฒฐ)
1403
+ tombolo_start = int(w * 0.3)
1404
+ tombolo_end = island_cx - island_radius
1405
+
1406
+ for c in range(tombolo_start, tombolo_end):
1407
+ t = (c - tombolo_start) / (tombolo_end - tombolo_start)
1408
+ width = int(5 * (1 - abs(t - 0.5) * 2) * stage)
1409
+
1410
+ for dr in range(-width, width + 1):
1411
+ r = island_cy + dr
1412
+ if 0 <= r < h:
1413
+ elevation[r, c] = 3.0 * (1 - abs(dr) / max(width, 1))
1414
+
1415
+ return elevation
1416
+
1417
+
1418
+ def create_sea_arch(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
1419
+ """ํ•ด์‹์•„์น˜ (Sea Arch) - ๋™๊ตด์ด ๊ด€ํ†ต"""
1420
+ h, w = grid_size, grid_size
1421
+ elevation = np.zeros((h, w))
1422
+
1423
+ # ๋ฐ”๋‹ค (์•„๋ž˜)
1424
+ sea_line = int(h * 0.5)
1425
+ elevation[sea_line:, :] = -5.0
1426
+
1427
+ # ์œก์ง€ ์ ˆ๋ฒฝ
1428
+ cliff_height = 30.0
1429
+ for r in range(sea_line):
1430
+ elevation[r, :] = cliff_height
1431
+
1432
+ # ๋Œ์ถœ๋ถ€ (๊ณถ)
1433
+ headland_width = int(w * 0.3)
1434
+ headland_cx = w // 2
1435
+ headland_length = int(h * 0.3)
1436
+
1437
+ for r in range(sea_line, sea_line + headland_length):
1438
+ for c in range(headland_cx - headland_width // 2, headland_cx + headland_width // 2):
1439
+ if 0 <= c < w:
1440
+ elevation[r, c] = cliff_height * (1 - (r - sea_line) / headland_length * 0.3)
1441
+
1442
+ # ์•„์น˜ (๊ด€ํ†ต)
1443
+ arch_r = sea_line + int(headland_length * 0.5)
1444
+ arch_width = int(headland_width * 0.4 * stage)
1445
+
1446
+ for dr in range(-5, 6):
1447
+ for dc in range(-arch_width // 2, arch_width // 2 + 1):
1448
+ r, c = arch_r + dr, headland_cx + dc
1449
+ if 0 <= r < h and 0 <= c < w:
1450
+ if abs(dr) < 3: # ์•„์น˜ ๋†’์ด
1451
+ elevation[r, c] = -5.0 # ๊ด€ํ†ต
1452
+
1453
+ return elevation
1454
+
1455
+
1456
+ def create_crater_lake(grid_size: int = 100, stage: float = 1.0,
1457
+ rim_height: float = 50.0) -> np.ndarray:
1458
+ """ํ™”๊ตฌํ˜ธ (Crater Lake) - ํ™”๊ตฌ์— ๋ฌผ์ด ๊ณ ์ž„"""
1459
+ h, w = grid_size, grid_size
1460
+ elevation = np.zeros((h, w))
1461
+
1462
+ center = (h // 2, w // 2)
1463
+ outer_radius = int(w * 0.4)
1464
+ crater_radius = int(w * 0.25)
1465
+
1466
+ for r in range(h):
1467
+ for c in range(w):
1468
+ dist = np.sqrt((r - center[0])**2 + (c - center[1])**2)
1469
+
1470
+ if dist > outer_radius:
1471
+ elevation[r, c] = 0
1472
+ elif dist > crater_radius:
1473
+ # ์™ธ๋ฅœ์‚ฐ
1474
+ t = (dist - crater_radius) / (outer_radius - crater_radius)
1475
+ elevation[r, c] = rim_height * (1 - t) * stage
1476
+ else:
1477
+ # ํ˜ธ์ˆ˜ (๋ฌผ)
1478
+ elevation[r, c] = -10.0 * stage
1479
+
1480
+ return elevation
1481
+
1482
+
1483
+ def create_lava_plateau(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
1484
+ """์šฉ์•”๋Œ€์ง€ (Lava Plateau) - ํ‰ํƒ„ํ•œ ์šฉ์•”"""
1485
+ h, w = grid_size, grid_size
1486
+ elevation = np.zeros((h, w))
1487
+
1488
+ # ๊ธฐ๋ฐ˜
1489
+ elevation[:, :] = 5.0
1490
+
1491
+ # ์šฉ์•”๋Œ€์ง€ ์˜์—ญ
1492
+ plateau_height = 30.0 * stage
1493
+ margin = int(w * 0.15)
1494
+
1495
+ for r in range(margin, h - margin):
1496
+ for c in range(margin, w - margin):
1497
+ # ๊ฑฐ์˜ ํ‰ํƒ„ํ•˜์ง€๋งŒ ์•ฝ๊ฐ„์˜ ๊ตด๊ณก
1498
+ noise = np.sin(r * 0.2) * np.cos(c * 0.2) * 2.0
1499
+ elevation[r, c] = plateau_height + noise
1500
+
1501
+ # ๊ฐ€์žฅ์ž๋ฆฌ ์ ˆ๋ฒฝ
1502
+ for r in range(h):
1503
+ for c in range(w):
1504
+ edge_dist = min(r, h - r - 1, c, w - c - 1)
1505
+ if edge_dist < margin:
1506
+ t = edge_dist / margin
1507
+ elevation[r, c] = 5.0 + (elevation[r, c] - 5.0) * t
1508
+
1509
+ return elevation
1510
+
1511
+
1512
+ def create_coastal_dune(grid_size: int = 100, stage: float = 1.0,
1513
+ num_dunes: int = 3) -> np.ndarray:
1514
+ """ํ•ด์•ˆ์‚ฌ๊ตฌ (Coastal Dune) - ํ•ด์•ˆ๊ฐ€ ๋ชจ๋ž˜ ์–ธ๋•"""
1515
+ h, w = grid_size, grid_size
1516
+ elevation = np.zeros((h, w))
1517
+
1518
+ # ๋ฐ”๋‹ค (์•„๋ž˜)
1519
+ beach_line = int(h * 0.7)
1520
+ elevation[beach_line:, :] = -3.0
1521
+
1522
+ # ํ•ด๋นˆ (ํ•ด๋ณ€)
1523
+ for r in range(beach_line - 5, beach_line):
1524
+ elevation[r, :] = 2.0
1525
+
1526
+ # ํ•ด์•ˆ์‚ฌ๊ตฌ (ํ•ด๋ณ€ ๋’ค)
1527
+ dune_zone_start = int(h * 0.3)
1528
+ dune_zone_end = beach_line - 5
1529
+
1530
+ for i in range(num_dunes):
1531
+ dune_r = dune_zone_start + i * (dune_zone_end - dune_zone_start) // (num_dunes + 1)
1532
+ dune_height = 15.0 * stage * (1 - i * 0.2)
1533
+
1534
+ for r in range(h):
1535
+ for c in range(w):
1536
+ dr = abs(r - dune_r)
1537
+ if dr < 10:
1538
+ # ์‚ฌ๊ตฌ ํ˜•ํƒœ (๋ฐ”๋žŒ๋ฐ›์ด ์™„๋งŒ, ๋ฐ”๋žŒ๊ทธ๋Š˜ ๊ธ‰)
1539
+ if r < dune_r:
1540
+ z = dune_height * (1 - dr / 12)
1541
+ else:
1542
+ z = dune_height * (1 - dr / 8)
1543
+ elevation[r, c] = max(elevation[r, c], z)
1544
+
1545
+ return elevation
1546
+
1547
+
1548
+ # ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ƒ์„ฑ๊ธฐ ๋งคํ•‘
1549
+ ANIMATED_LANDFORM_GENERATORS = {
1550
+ 'delta': create_delta_animated,
1551
+ 'alluvial_fan': create_alluvial_fan_animated,
1552
+ 'meander': create_meander_animated,
1553
+ 'u_valley': create_u_valley_animated,
1554
+ 'v_valley': create_v_valley_animated,
1555
+ 'barchan': create_barchan_animated,
1556
+ 'coastal_cliff': create_coastal_cliff_animated,
1557
+ # ํ™•์žฅ
1558
+ 'incised_meander': create_incised_meander,
1559
+ 'free_meander': create_free_meander,
1560
+ 'bird_foot_delta': create_bird_foot_delta,
1561
+ 'arcuate_delta': create_arcuate_delta,
1562
+ 'cuspate_delta': create_cuspate_delta,
1563
+ 'cirque': create_cirque,
1564
+ 'horn': create_horn,
1565
+ 'shield_volcano': create_shield_volcano,
1566
+ 'stratovolcano': create_stratovolcano,
1567
+ 'caldera': create_caldera,
1568
+ 'mesa_butte': create_mesa_butte,
1569
+ 'spit_lagoon': create_spit_lagoon,
1570
+ # ์ถ”๊ฐ€ ์ง€ํ˜•
1571
+ 'fjord': create_fjord,
1572
+ 'drumlin': create_drumlin,
1573
+ 'moraine': create_moraine,
1574
+ 'braided_river': create_braided_river,
1575
+ 'waterfall': create_waterfall,
1576
+ 'karst_doline': create_karst_doline,
1577
+ 'ria_coast': create_ria_coast,
1578
+ 'tombolo': create_tombolo,
1579
+ 'sea_arch': create_sea_arch,
1580
+ 'crater_lake': create_crater_lake,
1581
+ 'lava_plateau': create_lava_plateau,
1582
+ 'coastal_dune': create_coastal_dune,
1583
+ }
1584
+
1585
+ # ์ง€ํ˜• ์ƒ์„ฑ ํ•จ์ˆ˜ ๋งคํ•‘
1586
+ IDEAL_LANDFORM_GENERATORS = {
1587
+ 'delta': create_delta,
1588
+ 'alluvial_fan': create_alluvial_fan,
1589
+ 'meander': create_meander,
1590
+ 'u_valley': create_u_valley,
1591
+ 'v_valley': create_v_valley,
1592
+ 'barchan': create_barchan_dune,
1593
+ 'coastal_cliff': create_coastal_cliff,
1594
+ # ํ™•์žฅ ์ง€ํ˜•
1595
+ 'incised_meander': lambda gs: create_incised_meander(gs, 1.0),
1596
+ 'free_meander': lambda gs: create_free_meander(gs, 1.0),
1597
+ 'bird_foot_delta': lambda gs: create_bird_foot_delta(gs, 1.0),
1598
+ 'arcuate_delta': lambda gs: create_arcuate_delta(gs, 1.0),
1599
+ 'cuspate_delta': lambda gs: create_cuspate_delta(gs, 1.0),
1600
+ 'cirque': lambda gs: create_cirque(gs, 1.0),
1601
+ 'horn': lambda gs: create_horn(gs, 1.0),
1602
+ 'shield_volcano': lambda gs: create_shield_volcano(gs, 1.0),
1603
+ 'stratovolcano': lambda gs: create_stratovolcano(gs, 1.0),
1604
+ 'caldera': lambda gs: create_caldera(gs, 1.0),
1605
+ 'mesa_butte': lambda gs: create_mesa_butte(gs, 1.0),
1606
+ 'spit_lagoon': lambda gs: create_spit_lagoon(gs, 1.0),
1607
+ # ์ถ”๊ฐ€ ์ง€ํ˜•
1608
+ 'fjord': lambda gs: create_fjord(gs, 1.0),
1609
+ 'drumlin': lambda gs: create_drumlin(gs, 1.0),
1610
+ 'moraine': lambda gs: create_moraine(gs, 1.0),
1611
+ 'braided_river': lambda gs: create_braided_river(gs, 1.0),
1612
+ 'waterfall': lambda gs: create_waterfall(gs, 1.0),
1613
+ 'karst_doline': lambda gs: create_karst_doline(gs, 1.0),
1614
+ 'ria_coast': lambda gs: create_ria_coast(gs, 1.0),
1615
+ 'tombolo': lambda gs: create_tombolo(gs, 1.0),
1616
+ 'sea_arch': lambda gs: create_sea_arch(gs, 1.0),
1617
+ 'crater_lake': lambda gs: create_crater_lake(gs, 1.0),
1618
+ 'lava_plateau': lambda gs: create_lava_plateau(gs, 1.0),
1619
+ 'coastal_dune': lambda gs: create_coastal_dune(gs, 1.0),
1620
+ }
1621
+
engine/lateral_erosion.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Lateral Erosion Module (์ธก๋ฐฉ ์นจ์‹)
3
+
4
+ ํ•˜์ฒœ์˜ ๊ณก๋ฅ˜(Meander) ํ˜•์„ฑ์„ ์œ„ํ•œ ์ธก๋ฐฉ ์นจ์‹ ํ”„๋กœ์„ธ์Šค
5
+ - ๊ณก๋ฅ  ์™ธ์ธก: ์นจ์‹ (Cutbank)
6
+ - ๊ณก๋ฅ  ๋‚ด์ธก: ํ‡ด์  (Point Bar)
7
+
8
+ ํ•ต์‹ฌ ๊ณต์‹:
9
+ E_lateral = k * ฯ„ * (1/R)
10
+ - k: ์นจ์‹ ๊ณ„์ˆ˜
11
+ - ฯ„: ์ „๋‹จ์‘๋ ฅ (์œ ๋Ÿ‰ ํ•จ์ˆ˜)
12
+ - R: ๊ณก๋ฅ  ๋ฐ˜๊ฒฝ (์ž‘์„์ˆ˜๋ก ๊ธ‰ํšŒ์ „ โ†’ ์นจ์‹ ์ฆ๊ฐ€)
13
+ """
14
+
15
+ import numpy as np
16
+ from .grid import WorldGrid
17
+
18
+
19
+ def compute_flow_curvature(flow_dir: np.ndarray, elevation: np.ndarray) -> np.ndarray:
20
+ """
21
+ ์œ ํ–ฅ(Flow Direction)์œผ๋กœ๋ถ€ํ„ฐ ๊ณก๋ฅ (Curvature) ๊ณ„์‚ฐ
22
+
23
+ Args:
24
+ flow_dir: D8 ์œ ํ–ฅ ๋ฐฐ์—ด (0-7, -1 = sink)
25
+ elevation: ๊ณ ๋„ ๋ฐฐ์—ด
26
+
27
+ Returns:
28
+ curvature: ๊ณก๋ฅ  ๋ฐฐ์—ด (์–‘์ˆ˜: ์ขŒํšŒ์ „, ์Œ์ˆ˜: ์šฐํšŒ์ „)
29
+ """
30
+ h, w = flow_dir.shape
31
+ curvature = np.zeros((h, w), dtype=np.float64)
32
+
33
+ # D8 ๋ฐฉํ–ฅ ๋ฒกํ„ฐ (index 0-7)
34
+ # 0: NW, 1: N, 2: NE, 3: W, 4: E, 5: SW, 6: S, 7: SE
35
+ dir_dy = np.array([-1, -1, -1, 0, 0, 1, 1, 1])
36
+ dir_dx = np.array([-1, 0, 1, -1, 1, -1, 0, 1])
37
+
38
+ # ๋ฐฉํ–ฅ์„ ๊ฐ๋„๋กœ ๋ณ€ํ™˜ (๋ผ๋””์•ˆ)
39
+ # atan2(dy, dx)
40
+ dir_angles = np.arctan2(dir_dy, dir_dx)
41
+
42
+ for r in range(1, h - 1):
43
+ for c in range(1, w - 1):
44
+ current_dir = int(flow_dir[r, c])
45
+ if current_dir < 0 or current_dir > 7:
46
+ continue
47
+
48
+ # ์ƒ๋ฅ˜ ๋ฐฉํ–ฅ ์ฐพ๊ธฐ (๋‚˜๋ฅผ ํ–ฅํ•ด ํ๋ฅด๋Š” ์…€)
49
+ upstream_dirs = []
50
+ for k in range(8):
51
+ nr = r + dir_dy[k]
52
+ nc = c + dir_dx[k]
53
+ if 0 <= nr < h and 0 <= nc < w:
54
+ neighbor_dir = int(flow_dir[nr, nc])
55
+ if neighbor_dir >= 0 and neighbor_dir < 8:
56
+ # ์ด์›ƒ์ด ๋‚˜๋ฅผ ํ–ฅํ•ด ํ๋ฅด๋Š”๊ฐ€?
57
+ target_r = nr + dir_dy[neighbor_dir]
58
+ target_c = nc + dir_dx[neighbor_dir]
59
+ if target_r == r and target_c == c:
60
+ upstream_dirs.append(neighbor_dir)
61
+
62
+ if not upstream_dirs:
63
+ continue
64
+
65
+ # ์ƒ๋ฅ˜ ๋ฐฉํ–ฅ์˜ ํ‰๊ท  ๊ฐ๋„
66
+ upstream_angle = np.mean([dir_angles[d] for d in upstream_dirs])
67
+ current_angle = dir_angles[current_dir]
68
+
69
+ # ๊ฐ๋„ ๋ณ€ํ™” = ๊ณก๋ฅ  (์ •๊ทœํ™”๋œ ๊ฐ’)
70
+ angle_diff = current_angle - upstream_angle
71
+
72
+ # -ฯ€ ~ ฯ€ ๋ฒ”์œ„๋กœ ์ •๊ทœํ™”
73
+ while angle_diff > np.pi:
74
+ angle_diff -= 2 * np.pi
75
+ while angle_diff < -np.pi:
76
+ angle_diff += 2 * np.pi
77
+
78
+ curvature[r, c] = angle_diff
79
+
80
+ return curvature
81
+
82
+
83
+ def apply_lateral_erosion(grid: WorldGrid,
84
+ curvature: np.ndarray,
85
+ discharge: np.ndarray,
86
+ k: float = 0.01,
87
+ dt: float = 1.0) -> np.ndarray:
88
+ """
89
+ ์ธก๋ฐฉ ์นจ์‹ ์ ์šฉ
90
+
91
+ Args:
92
+ grid: WorldGrid ๊ฐ์ฒด
93
+ curvature: ๊ณก๋ฅ  ๋ฐฐ์—ด (์–‘์ˆ˜: ์ขŒํšŒ์ „, ์Œ์ˆ˜: ์šฐํšŒ์ „)
94
+ discharge: ์œ ๋Ÿ‰ ๋ฐฐ์—ด
95
+ k: ์นจ์‹ ๊ณ„์ˆ˜
96
+ dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
97
+
98
+ Returns:
99
+ change: ์ง€ํ˜• ๋ณ€ํ™”๋Ÿ‰ ๋ฐฐ์—ด
100
+ """
101
+ h, w = grid.height, grid.width
102
+ change = np.zeros((h, w), dtype=np.float64)
103
+
104
+ # D8 ๋ฐฉํ–ฅ ๋ฒกํ„ฐ
105
+ dir_dy = np.array([-1, -1, -1, 0, 0, 1, 1, 1])
106
+ dir_dx = np.array([-1, 0, 1, -1, 1, -1, 0, 1])
107
+
108
+ # ์ขŒ/์šฐ์ธก ๋ฐฉํ–ฅ (์ƒ๋Œ€์ )
109
+ # ํ˜„์žฌ ๋ฐฉํ–ฅ์ด k์ผ ๋•Œ, ์ขŒ์ธก์€ (k-2)%8, ์šฐ์ธก์€ (k+2)%8 (๋Œ€๋žต์  ๊ทผ์‚ฌ)
110
+
111
+ for r in range(1, h - 1):
112
+ for c in range(1, w - 1):
113
+ curv = curvature[r, c]
114
+ Q = discharge[r, c]
115
+
116
+ if abs(curv) < 0.01 or Q < 1.0:
117
+ continue
118
+
119
+ # ์นจ์‹๋Ÿ‰ = k * sqrt(Q) * |curvature| * dt
120
+ erosion_amount = k * np.sqrt(Q) * abs(curv) * dt
121
+
122
+ # ํ˜„์žฌ ์œ ํ–ฅ
123
+ flow_k = int(grid.flow_dir[r, c])
124
+ if flow_k < 0 or flow_k > 7:
125
+ continue
126
+
127
+ # ์ขŒ/์šฐ์ธก ์…€ ๊ฒฐ์ •
128
+ if curv > 0: # ์ขŒํšŒ์ „ โ†’ ์šฐ์ธก(์™ธ์ธก) ์นจ์‹, ์ขŒ์ธก(๋‚ด์ธก) ํ‡ด์ 
129
+ erode_k = (flow_k + 2) % 8 # ์šฐ์ธก
130
+ deposit_k = (flow_k - 2 + 8) % 8 # ์ขŒ์ธก
131
+ else: # ์šฐํšŒ์ „ โ†’ ์ขŒ์ธก(์™ธ์ธก) ์นจ์‹, ์šฐ์ธก(๋‚ด์ธก) ํ‡ด์ 
132
+ erode_k = (flow_k - 2 + 8) % 8 # ์ขŒ์ธก
133
+ deposit_k = (flow_k + 2) % 8 # ์šฐ์ธก
134
+
135
+ # ์นจ์‹ ์…€
136
+ er = r + dir_dy[erode_k]
137
+ ec = c + dir_dx[erode_k]
138
+
139
+ # ํ‡ด์  ์…€
140
+ dr = r + dir_dy[deposit_k]
141
+ dc = c + dir_dx[deposit_k]
142
+
143
+ # ๊ฒฝ๊ณ„ ์ฒดํฌ
144
+ if 0 <= er < h and 0 <= ec < w:
145
+ change[er, ec] -= erosion_amount
146
+
147
+ if 0 <= dr < h and 0 <= dc < w:
148
+ change[dr, dc] += erosion_amount * 0.8 # ์ผ๋ถ€ ์†์‹ค
149
+
150
+ return change
151
+
152
+
153
+ class LateralErosionKernel:
154
+ """
155
+ ์ธก๋ฐฉ ์นจ์‹ ์ปค๋„
156
+
157
+ EarthSystem์— ํ†ตํ•ฉํ•˜์—ฌ ์‚ฌ์šฉ
158
+ """
159
+
160
+ def __init__(self, grid: WorldGrid, k: float = 0.01):
161
+ self.grid = grid
162
+ self.k = k
163
+
164
+ def step(self, discharge: np.ndarray, dt: float = 1.0) -> np.ndarray:
165
+ """
166
+ 1๋‹จ๊ณ„ ์ธก๋ฐฉ ์นจ์‹ ์‹คํ–‰
167
+
168
+ Args:
169
+ discharge: ์œ ๋Ÿ‰ ๋ฐฐ์—ด
170
+ dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
171
+
172
+ Returns:
173
+ change: ์ง€ํ˜• ๋ณ€ํ™”๋Ÿ‰
174
+ """
175
+ # 1. ๊ณก๋ฅ  ๊ณ„์‚ฐ
176
+ curvature = compute_flow_curvature(self.grid.flow_dir, self.grid.elevation)
177
+
178
+ # 2. ์ธก๋ฐฉ ์นจ์‹ ์ ์šฉ
179
+ change = apply_lateral_erosion(self.grid, curvature, discharge, self.k, dt)
180
+
181
+ # 3. ์ง€ํ˜• ์—…๋ฐ์ดํŠธ
182
+ # ์นจ์‹๋ถ„: bedrock/sediment์—์„œ ์ œ๊ฑฐ
183
+ erosion_mask = change < 0
184
+ erosion_amount = -change[erosion_mask]
185
+
186
+ # ํ‡ด์ ์ธต ๋จผ์ €, ๋ถ€์กฑํ•˜๋ฉด ๊ธฐ๋ฐ˜์•”
187
+ sed_loss = np.minimum(erosion_amount, self.grid.sediment[erosion_mask])
188
+ rock_loss = erosion_amount - sed_loss
189
+
190
+ self.grid.sediment[erosion_mask] -= sed_loss
191
+ self.grid.bedrock[erosion_mask] -= rock_loss
192
+
193
+ # ํ‡ด์ ๋ถ„: sediment์— ์ถ”๊ฐ€
194
+ self.grid.sediment += np.maximum(change, 0)
195
+
196
+ # ๊ณ ๋„ ๋™๊ธฐํ™”
197
+ self.grid.update_elevation()
198
+
199
+ return change
engine/mass_movement.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Mass Movement Kernel (๋งค์Šค๋ฌด๋ธŒ๋จผํŠธ)
3
+
4
+ ์ค‘๋ ฅ์— ์˜ํ•œ ์‚ฌ๋ฉด ์ด๋™ ํ”„๋กœ์„ธ์Šค
5
+ - ์‚ฐ์‚ฌํƒœ (Landslide)
6
+ - ์Šฌ๋Ÿผํ”„ (Slump)
7
+ - ๋‚™์„ (Rockfall)
8
+
9
+ ํ•ต์‹ฌ ์›๋ฆฌ:
10
+ ๊ฒฝ์‚ฌ(Slope) > ์ž„๊ณ„ ๊ฒฝ์‚ฌ(Critical Angle) โ†’ ๋ฌผ์งˆ ์ด๋™
11
+ """
12
+
13
+ import numpy as np
14
+ from .grid import WorldGrid
15
+
16
+
17
+ class MassMovementKernel:
18
+ """
19
+ ๋งค์Šค๋ฌด๋ธŒ๋จผํŠธ ์ปค๋„
20
+
21
+ ๊ฒฝ์‚ฌ ์•ˆ์ •์„ฑ์„ ๊ฒ€์‚ฌํ•˜๊ณ , ๋ถˆ์•ˆ์ •ํ•œ ๊ณณ์—์„œ ๋ฌผ์งˆ ์ด๋™์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜.
22
+ """
23
+
24
+ def __init__(self, grid: WorldGrid,
25
+ friction_angle: float = 35.0, # ๋‚ด๋ถ€ ๋งˆ์ฐฐ๊ฐ (๋„)
26
+ cohesion: float = 0.0): # ์ ์ฐฉ๋ ฅ (Pa, ๊ฐ„์†Œํ™”)
27
+ self.grid = grid
28
+ self.friction_angle = friction_angle
29
+ self.cohesion = cohesion
30
+
31
+ # ์ž„๊ณ„ ๊ฒฝ์‚ฌ (ํƒ„์  ํŠธ ๊ฐ’)
32
+ self.critical_slope = np.tan(np.radians(friction_angle))
33
+
34
+ def check_stability(self) -> np.ndarray:
35
+ """
36
+ ๊ฒฝ์‚ฌ ์•ˆ์ •์„ฑ ๊ฒ€์‚ฌ
37
+
38
+ Returns:
39
+ unstable_mask: ๋ถˆ์•ˆ์ •ํ•œ ์…€ ๋งˆ์Šคํฌ (True = ๋ถˆ์•ˆ์ •)
40
+ """
41
+ slope, _ = self.grid.get_gradient()
42
+
43
+ # ๊ฒฝ์‚ฌ > ์ž„๊ณ„ ๊ฒฝ์‚ฌ โ†’ ๋ถˆ์•ˆ์ •
44
+ unstable = slope > self.critical_slope
45
+
46
+ return unstable
47
+
48
+ def trigger_landslide(self, unstable_mask: np.ndarray,
49
+ efficiency: float = 0.5) -> np.ndarray:
50
+ """
51
+ ์‚ฐ์‚ฌํƒœ ๋ฐœ์ƒ
52
+
53
+ ๋ถˆ์•ˆ์ •ํ•œ ์…€์—์„œ ๋ฌผ์งˆ์„ ๋‚ฎ์€ ๊ณณ์œผ๋กœ ์ด๋™.
54
+
55
+ Args:
56
+ unstable_mask: ๋ถˆ์•ˆ์ • ๋งˆ์Šคํฌ
57
+ efficiency: ์ด๋™ ํšจ์œจ (0.0~1.0, 1.0์ด๋ฉด ์™„์ „ ์ด๋™)
58
+
59
+ Returns:
60
+ change: ์ง€ํ˜• ๋ณ€ํ™”๋Ÿ‰
61
+ """
62
+ h, w = self.grid.height, self.grid.width
63
+ change = np.zeros((h, w), dtype=np.float64)
64
+
65
+ if not np.any(unstable_mask):
66
+ return change
67
+
68
+ # D8 ๋ฐฉํ–ฅ
69
+ dr = np.array([-1, -1, -1, 0, 0, 1, 1, 1])
70
+ dc = np.array([-1, 0, 1, -1, 1, -1, 0, 1])
71
+
72
+ elev = self.grid.elevation
73
+ slope, _ = self.grid.get_gradient()
74
+
75
+ # ๋ถˆ์•ˆ์ • ์…€ ์ขŒํ‘œ
76
+ unstable_coords = np.argwhere(unstable_mask)
77
+
78
+ for r, c in unstable_coords:
79
+ # ํ˜„์žฌ ๊ฒฝ์‚ฌ ์ดˆ๊ณผ๋ถ„ ๊ณ„์‚ฐ
80
+ excess_slope = slope[r, c] - self.critical_slope
81
+ if excess_slope <= 0:
82
+ continue
83
+
84
+ # ์ด๋™๋Ÿ‰ = ์ดˆ๊ณผ ๊ฒฝ์‚ฌ์— ๋น„๋ก€
85
+ # ํ‡ด์ ์ธต ๋จผ์ € ์ด๋™, ๋ถ€์กฑํ•˜๋ฉด ๊ธฐ๋ฐ˜์•”
86
+ available = self.grid.sediment[r, c] + self.grid.bedrock[r, c] * 0.1
87
+ move_amount = min(excess_slope * efficiency * 5.0, available)
88
+
89
+ if move_amount <= 0:
90
+ continue
91
+
92
+ # ๊ฐ€์žฅ ๋‚ฎ์€ ์ด์›ƒ ์ฐพ๊ธฐ
93
+ min_z = elev[r, c]
94
+ target = None
95
+
96
+ for k in range(8):
97
+ nr, nc = r + dr[k], c + dc[k]
98
+ if 0 <= nr < h and 0 <= nc < w:
99
+ if elev[nr, nc] < min_z:
100
+ min_z = elev[nr, nc]
101
+ target = (nr, nc)
102
+
103
+ if target is None:
104
+ continue
105
+
106
+ tr, tc = target
107
+
108
+ # ๋ฌผ์งˆ ์ด๋™
109
+ change[r, c] -= move_amount
110
+ change[tr, tc] += move_amount * 0.9 # ์ผ๋ถ€ ์†์‹ค (๋ถ„์‚ฐ)
111
+
112
+ # ์ง€ํ˜• ์—…๋ฐ์ดํŠธ
113
+ # ์†์‹ค๋ถ„: sediment์—์„œ ์ œ๊ฑฐ
114
+ loss_mask = change < 0
115
+ loss = -change[loss_mask]
116
+
117
+ sed_loss = np.minimum(loss, self.grid.sediment[loss_mask])
118
+ rock_loss = loss - sed_loss
119
+
120
+ self.grid.sediment[loss_mask] -= sed_loss
121
+ self.grid.bedrock[loss_mask] -= rock_loss
122
+
123
+ # ํ‡ด์ ๋ถ„: sediment์— ์ถ”๊ฐ€
124
+ self.grid.sediment += np.maximum(change, 0)
125
+
126
+ self.grid.update_elevation()
127
+
128
+ return change
129
+
130
+ def step(self, dt: float = 1.0) -> np.ndarray:
131
+ """
132
+ 1๋‹จ๊ณ„ ๋งค์Šค๋ฌด๋ธŒ๋จผํŠธ ์‹คํ–‰
133
+
134
+ Args:
135
+ dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ (์‚ฌ์šฉํ•˜์ง€ ์•Š์ง€๋งŒ ์ธํ„ฐํŽ˜์ด์Šค ์ผ๊ด€์„ฑ)
136
+
137
+ Returns:
138
+ change: ์ง€ํ˜• ๋ณ€ํ™”๋Ÿ‰
139
+ """
140
+ # 1. ์•ˆ์ •์„ฑ ๊ฒ€์‚ฌ
141
+ unstable = self.check_stability()
142
+
143
+ # 2. ๋ถˆ์•ˆ์ • ์ง€์ ์—์„œ ์‚ฐ์‚ฌํƒœ ๋ฐœ์ƒ
144
+ change = self.trigger_landslide(unstable)
145
+
146
+ return change
engine/meander_physics.py ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Geo-Lab AI: ๊ณก๋ฅ˜ ํ•˜์ฒœ ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
3
+ Helical Flow ๊ธฐ๋ฐ˜ ์ธก๋ฐฉ ์นจ์‹/ํ‡ด์ 
4
+ """
5
+ import numpy as np
6
+ from dataclasses import dataclass, field
7
+ from typing import List, Tuple
8
+
9
+
10
+ @dataclass
11
+ class MeanderChannel:
12
+ """๊ณก๋ฅ˜ ํ•˜์ฒœ ์ฑ„๋„ ํ‘œํ˜„
13
+
14
+ 1D ์ค‘์‹ฌ์„  ๊ธฐ๋ฐ˜์œผ๋กœ ์ฑ„๋„์„ ํ‘œํ˜„ํ•˜๊ณ ,
15
+ ๊ฐ ์ง€์ ์—์„œ์˜ ๊ณก๋ฅ , ํญ, ๊นŠ์ด๋ฅผ ์ถ”์ 
16
+ """
17
+
18
+ # ์ฑ„๋„ ์ค‘์‹ฌ์„  ์ขŒํ‘œ
19
+ x: np.ndarray = field(default=None)
20
+ y: np.ndarray = field(default=None)
21
+
22
+ # ๊ฐ ์ง€์ ์˜ ์†์„ฑ
23
+ width: np.ndarray = field(default=None) # ์ฑ„๋„ ํญ (m)
24
+ depth: np.ndarray = field(default=None) # ์ฑ„๋„ ๊นŠ์ด (m)
25
+
26
+ # ์œ ๋Ÿ‰ ๋ฐ ์œ ์†
27
+ discharge: float = 100.0 # mยณ/s
28
+ velocity: np.ndarray = field(default=None) # m/s
29
+
30
+ def __post_init__(self):
31
+ if self.x is not None and self.width is None:
32
+ n = len(self.x)
33
+ self.width = np.full(n, 20.0)
34
+ self.depth = np.full(n, 3.0)
35
+ self.velocity = np.full(n, 1.5)
36
+
37
+ @classmethod
38
+ def create_initial(cls, length: float = 1000.0,
39
+ initial_sinuosity: float = 1.2,
40
+ n_points: int = 200,
41
+ discharge: float = 100.0):
42
+ """์ดˆ๊ธฐ ๊ณก๋ฅ˜ ํ•˜์ฒœ ์ƒ์„ฑ"""
43
+ s = np.linspace(0, 1, n_points) # ์ •๊ทœํ™”๋œ ๊ฑฐ๋ฆฌ
44
+
45
+ # ์‚ฌ์ธํŒŒ ๊ธฐ๋ฐ˜ ์ดˆ๊ธฐ ๊ณก๋ฅ˜
46
+ amplitude = length * 0.1 * (initial_sinuosity - 1)
47
+ frequency = 3 # ๊ตฝ์ด ์ˆ˜
48
+
49
+ x = s * length
50
+ y = amplitude * np.sin(2 * np.pi * frequency * s)
51
+
52
+ return cls(x=x, y=y, discharge=discharge)
53
+
54
+ def calculate_curvature(self) -> np.ndarray:
55
+ """๊ณก๋ฅ  ๊ณ„์‚ฐ (1/m)
56
+ ฮบ = (x'y'' - y'x'') / (x'^2 + y'^2)^(3/2)
57
+ """
58
+ dx = np.gradient(self.x)
59
+ dy = np.gradient(self.y)
60
+ ddx = np.gradient(dx)
61
+ ddy = np.gradient(dy)
62
+
63
+ denominator = np.power(dx**2 + dy**2, 1.5) + 1e-10
64
+ curvature = (dx * ddy - dy * ddx) / denominator
65
+
66
+ return curvature
67
+
68
+ def calculate_sinuosity(self) -> float:
69
+ """๊ตด๊ณก๋„ = ํ•˜์ฒœ ๊ธธ์ด / ์ง์„  ๊ฑฐ๋ฆฌ"""
70
+ # ๊ฒฝ๋กœ ๊ธธ์ด
71
+ ds = np.sqrt(np.diff(self.x)**2 + np.diff(self.y)**2)
72
+ path_length = np.sum(ds)
73
+
74
+ # ์ง์„  ๊ฑฐ๋ฆฌ
75
+ straight_length = np.sqrt(
76
+ (self.x[-1] - self.x[0])**2 +
77
+ (self.y[-1] - self.y[0])**2
78
+ ) + 1e-10
79
+
80
+ return path_length / straight_length
81
+
82
+
83
+ class HelicalFlowErosion:
84
+ """Helical Flow ๊ธฐ๋ฐ˜ ๊ณก๋ฅ˜ ์นจ์‹/ํ‡ด์ 
85
+
86
+ ๊ณก๋ฅ˜์—์„œ ๋ฌผ์€ ๋‚˜์„ ํ˜•(helical)์œผ๋กœ ํ๋ฆ„:
87
+ - ํ‘œ๋ฉด: ๋ฐ”๊นฅ์ชฝ์œผ๋กœ (์›์‹ฌ๋ ฅ)
88
+ - ๋ฐ”๋‹ฅ: ์•ˆ์ชฝ์œผ๋กœ (์••๋ ฅ ๊ตฌ๋ฐฐ)
89
+
90
+ ๊ฒฐ๊ณผ:
91
+ - ๋ฐ”๊นฅ์ชฝ (๊ณต๊ฒฉ์‚ฌ๋ฉด): ์นจ์‹
92
+ - ์•ˆ์ชฝ (ํ‡ด์ ์‚ฌ๋ฉด): ํ‡ด์ 
93
+ """
94
+
95
+ def __init__(self,
96
+ bank_erosion_rate: float = 0.5, # m/yr per unit stress
97
+ deposition_rate: float = 0.3):
98
+ self.bank_erosion_rate = bank_erosion_rate
99
+ self.deposition_rate = deposition_rate
100
+
101
+ def calculate_bank_migration(self, channel: MeanderChannel,
102
+ dt: float = 1.0) -> Tuple[np.ndarray, np.ndarray]:
103
+ """ํ•˜์•ˆ ์ด๋™ ๊ณ„์‚ฐ
104
+
105
+ ๊ณก๋ฅ ์ด ํฐ ๊ณณ์—์„œ ๋ฐ”๊นฅ์ชฝ์œผ๋กœ ์นจ์‹ โ†’ ์ฑ„๋„ ์ด๋™
106
+ """
107
+ curvature = channel.calculate_curvature()
108
+
109
+ # ์นจ์‹/ํ‡ด์  ๋น„๋Œ€์นญ
110
+ # ๊ณก๋ฅ  > 0: ์ขŒ์ธก์ด ๋ฐ”๊นฅ (์นจ์‹)
111
+ # ๊ณก๋ฅ  < 0: ์šฐ์ธก์ด ๋ฐ”๊นฅ (์นจ์‹)
112
+
113
+ # ์ด๋™ ๋ฒกํ„ฐ (์ฑ„๋„์— ์ˆ˜์ง)
114
+ dx = np.gradient(channel.x)
115
+ dy = np.gradient(channel.y)
116
+ path_length = np.sqrt(dx**2 + dy**2) + 1e-10
117
+
118
+ # ์ˆ˜์ง ๋ฐฉํ–ฅ (์™ผ์ชฝ์œผ๋กœ 90๋„ ํšŒ์ „)
119
+ normal_x = -dy / path_length
120
+ normal_y = dx / path_length
121
+
122
+ # ์ด๋™๋Ÿ‰ = ๊ณก๋ฅ  ร— ์œ ๋Ÿ‰ ร— erosion rate
123
+ migration_rate = curvature * channel.discharge * self.bank_erosion_rate * dt
124
+
125
+ # ์ด๋™๋Ÿ‰ ์ œํ•œ
126
+ migration_rate = np.clip(migration_rate, -2.0, 2.0)
127
+
128
+ delta_x = migration_rate * normal_x
129
+ delta_y = migration_rate * normal_y
130
+
131
+ return delta_x, delta_y
132
+
133
+ def check_cutoff(self, channel: MeanderChannel,
134
+ threshold_distance: float = 30.0) -> List[Tuple[int, int]]:
135
+ """์šฐ๊ฐํ˜ธ ํ˜•์„ฑ ์กฐ๊ฑด ์ฒดํฌ (์œ ๋กœ ์ ˆ๋‹จ)"""
136
+ n = len(channel.x)
137
+ cutoffs = []
138
+
139
+ # ๊ฐ€๊นŒ์šด ๋‘ ์  ์ฐพ๊ธฐ (๊ฒฝ๋กœ์ƒ ๋ฉ€๋ฆฌ ๋–จ์–ด์กŒ์ง€๋งŒ ๊ณต๊ฐ„์ ์œผ๋กœ ๊ฐ€๊นŒ์šด)
140
+ for i in range(n):
141
+ for j in range(i + 30, n): # ์ตœ์†Œ 30์  ๊ฐ„๊ฒฉ
142
+ dist = np.sqrt(
143
+ (channel.x[i] - channel.x[j])**2 +
144
+ (channel.y[i] - channel.y[j])**2
145
+ )
146
+
147
+ if dist < threshold_distance:
148
+ cutoffs.append((i, j))
149
+ break # ์ฒซ ๋ฒˆ์งธ cutoff๋งŒ
150
+
151
+ return cutoffs
152
+
153
+
154
+ class MeanderSimulation:
155
+ """๊ณก๋ฅ˜ ํ•˜์ฒœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜"""
156
+
157
+ def __init__(self, length: float = 1000.0, initial_sinuosity: float = 1.3):
158
+ self.channel = MeanderChannel.create_initial(
159
+ length=length,
160
+ initial_sinuosity=initial_sinuosity
161
+ )
162
+ self.erosion = HelicalFlowErosion()
163
+
164
+ self.history: List[Tuple[np.ndarray, np.ndarray]] = []
165
+ self.oxbow_lakes: List[Tuple[np.ndarray, np.ndarray]] = []
166
+ self.time = 0.0
167
+
168
+ def step(self, dt: float = 1.0):
169
+ """1 ํƒ€์ž„์Šคํ…"""
170
+ # 1. ํ•˜์•ˆ ์ด๋™
171
+ dx, dy = self.erosion.calculate_bank_migration(self.channel, dt)
172
+ self.channel.x += dx
173
+ self.channel.y += dy
174
+
175
+ # 2. ์šฐ๊ฐํ˜ธ ์ฒดํฌ
176
+ cutoffs = self.erosion.check_cutoff(self.channel)
177
+ for start, end in cutoffs:
178
+ # ์šฐ๊ฐํ˜ธ ์ €์žฅ
179
+ ox = self.channel.x[start:end+1].copy()
180
+ oy = self.channel.y[start:end+1].copy()
181
+ self.oxbow_lakes.append((ox, oy))
182
+
183
+ # ์ฑ„๋„ ๋‹จ์ถ•
184
+ self.channel.x = np.concatenate([
185
+ self.channel.x[:start+1],
186
+ self.channel.x[end:]
187
+ ])
188
+ self.channel.y = np.concatenate([
189
+ self.channel.y[:start+1],
190
+ self.channel.y[end:]
191
+ ])
192
+
193
+ # ์†์„ฑ ๋ฐฐ์—ด๋„ ์กฐ์ •
194
+ n_new = len(self.channel.x)
195
+ self.channel.width = np.full(n_new, 20.0)
196
+ self.channel.depth = np.full(n_new, 3.0)
197
+ self.channel.velocity = np.full(n_new, 1.5)
198
+
199
+ self.time += dt
200
+
201
+ def run(self, total_time: float, save_interval: float = 100.0, dt: float = 1.0):
202
+ """์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹คํ–‰"""
203
+ steps = int(total_time / dt)
204
+ save_every = max(1, int(save_interval / dt))
205
+
206
+ self.history = [(self.channel.x.copy(), self.channel.y.copy())]
207
+
208
+ for i in range(steps):
209
+ self.step(dt)
210
+ if (i + 1) % save_every == 0:
211
+ self.history.append((self.channel.x.copy(), self.channel.y.copy()))
212
+
213
+ return self.history
214
+
215
+ def get_cross_section(self, position: float = 0.5) -> Tuple[np.ndarray, np.ndarray]:
216
+ """๊ณก๋ฅ˜ ๋‹จ๋ฉด (๋น„๋Œ€์นญ)
217
+
218
+ position: ๊ณก๋ฅ˜ ๋‚ด ์œ„์น˜ (0=์ง์„ ๋ถ€, 1=์ตœ๋Œ€ ๊ตฝ์ด)
219
+ """
220
+ curvature = self.channel.calculate_curvature()
221
+ max_curve = np.abs(curvature).max() + 1e-10
222
+ asymmetry = np.abs(curvature[int(len(curvature) * 0.5)]) / max_curve
223
+ asymmetry = min(1.0, asymmetry * position * 2)
224
+
225
+ # ๋‹จ๋ฉด ์ƒ์„ฑ
226
+ x = np.linspace(-30, 30, 100)
227
+
228
+ # ๋น„๋Œ€์นญ ๊ณก์„ 
229
+ left_depth = 5 + asymmetry * 3 # ๊ณต๊ฒฉ์‚ฌ๋ฉด (๊นŠ์Œ)
230
+ right_depth = 3 - asymmetry * 1 # ํ‡ด์ ์‚ฌ๋ฉด (์–•์Œ)
231
+
232
+ y = np.where(
233
+ x < 0,
234
+ -left_depth * (1 - np.power(x / -30, 2)),
235
+ -right_depth * (1 - np.power(x / 30, 2))
236
+ )
237
+
238
+ return x, y
239
+
240
+
241
+ # ํ”„๋ฆฌ์ปดํ“จํŒ…
242
+ def precompute_meander(max_time: int = 10000,
243
+ initial_sinuosity: float = 1.3,
244
+ save_every: int = 100) -> dict:
245
+ """๊ณก๋ฅ˜ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํ”„๋ฆฌ์ปดํ“จํŒ…"""
246
+ sim = MeanderSimulation(initial_sinuosity=initial_sinuosity)
247
+
248
+ history = sim.run(
249
+ total_time=max_time,
250
+ save_interval=save_every,
251
+ dt=1.0
252
+ )
253
+
254
+ return {
255
+ 'history': history,
256
+ 'oxbow_lakes': sim.oxbow_lakes,
257
+ 'final_sinuosity': sim.channel.calculate_sinuosity()
258
+ }
259
+
260
+
261
+ if __name__ == "__main__":
262
+ print("๊ณก๋ฅ˜ ํ•˜์ฒœ ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํ…Œ์ŠคํŠธ")
263
+ print("=" * 50)
264
+
265
+ sim = MeanderSimulation(initial_sinuosity=1.3)
266
+ print(f"์ดˆ๊ธฐ ๊ตด๊ณก๋„: {sim.channel.calculate_sinuosity():.2f}")
267
+
268
+ sim.run(5000, save_interval=1000)
269
+ print(f"5000๋…„ ํ›„ ๊ตด๊ณก๋„: {sim.channel.calculate_sinuosity():.2f}")
270
+ print(f"ํ˜•์„ฑ๋œ ์šฐ๊ฐํ˜ธ: {len(sim.oxbow_lakes)}๊ฐœ")
271
+
272
+ print("=" * 50)
273
+ print("ํ…Œ์ŠคํŠธ ์™„๋ฃŒ!")
engine/physics_engine.py ADDED
@@ -0,0 +1,365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Geo-Lab AI: ์ง„์งœ ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์—”์ง„
3
+ Stream Power Law ๊ธฐ๋ฐ˜ ์‹ค์ œ ์นจ์‹ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
4
+ """
5
+ import numpy as np
6
+ from dataclasses import dataclass, field
7
+ from typing import List, Tuple, Optional
8
+ from scipy.ndimage import gaussian_filter, uniform_filter
9
+
10
+
11
+ @dataclass
12
+ class TerrainGrid:
13
+ """2D ์ง€ํ˜• ๊ทธ๋ฆฌ๋“œ"""
14
+ width: int = 100
15
+ height: int = 100
16
+ cell_size: float = 10.0 # ๋ฏธํ„ฐ
17
+
18
+ elevation: np.ndarray = field(default=None)
19
+ bedrock: np.ndarray = field(default=None) # ๊ธฐ๋ฐ˜์•” (์นจ์‹ ๋ถˆ๊ฐ€ ๋ ˆ๋ฒจ)
20
+ rock_hardness: np.ndarray = field(default=None) # 0-1
21
+
22
+ def __post_init__(self):
23
+ if self.elevation is None:
24
+ self.elevation = np.zeros((self.height, self.width))
25
+ if self.bedrock is None:
26
+ self.bedrock = np.full((self.height, self.width), -100.0)
27
+ if self.rock_hardness is None:
28
+ self.rock_hardness = np.full((self.height, self.width), 0.5)
29
+
30
+ def get_slope(self) -> np.ndarray:
31
+ """๊ฒฝ์‚ฌ๋„ ๊ณ„์‚ฐ (m/m)"""
32
+ dy, dx = np.gradient(self.elevation, self.cell_size)
33
+ return np.sqrt(dx**2 + dy**2)
34
+
35
+ def get_slope_direction(self) -> Tuple[np.ndarray, np.ndarray]:
36
+ """์ตœ๋Œ€ ๊ฒฝ์‚ฌ ๋ฐฉํ–ฅ (๋‹จ์œ„ ๋ฒกํ„ฐ)"""
37
+ dy, dx = np.gradient(self.elevation, self.cell_size)
38
+ magnitude = np.sqrt(dx**2 + dy**2) + 1e-10
39
+ return -dx / magnitude, -dy / magnitude
40
+
41
+
42
+ @dataclass
43
+ class WaterFlow:
44
+ """์ˆ˜๋ฌธ ์‹œ๋ฎฌ๋ ˆ์ด์…˜"""
45
+ terrain: TerrainGrid
46
+
47
+ # ์œ ๋Ÿ‰ (mยณ/s per cell)
48
+ discharge: np.ndarray = field(default=None)
49
+ # ์œ ์† (m/s)
50
+ velocity: np.ndarray = field(default=None)
51
+ # ์ˆ˜์‹ฌ (m)
52
+ depth: np.ndarray = field(default=None)
53
+ # ์ „๋‹จ์‘๋ ฅ (Pa)
54
+ shear_stress: np.ndarray = field(default=None)
55
+
56
+ manning_n: float = 0.03 # Manning ์กฐ๋„๊ณ„์ˆ˜
57
+
58
+ def __post_init__(self):
59
+ shape = (self.terrain.height, self.terrain.width)
60
+ if self.discharge is None:
61
+ self.discharge = np.zeros(shape)
62
+ if self.velocity is None:
63
+ self.velocity = np.zeros(shape)
64
+ if self.depth is None:
65
+ self.depth = np.zeros(shape)
66
+ if self.shear_stress is None:
67
+ self.shear_stress = np.zeros(shape)
68
+
69
+ def flow_accumulation_d8(self, precipitation: float = 0.001):
70
+ """D8 ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ธฐ๋ฐ˜ ์œ ๋Ÿ‰ ๋ˆ„์ """
71
+ h, w = self.terrain.height, self.terrain.width
72
+ elev = self.terrain.elevation
73
+
74
+ # ์ดˆ๊ธฐ ๊ฐ•์ˆ˜
75
+ acc = np.full((h, w), precipitation)
76
+
77
+ # ๋†’์€ ๊ณณ์—์„œ ๋‚ฎ์€ ๊ณณ์œผ๋กœ ์ •๋ ฌ
78
+ flat_elev = elev.ravel()
79
+ sorted_indices = np.argsort(flat_elev)[::-1]
80
+
81
+ # D8 ๋ฐฉํ–ฅ (8๋ฐฉํ–ฅ ์ด์›ƒ)
82
+ neighbors = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)]
83
+
84
+ for idx in sorted_indices:
85
+ y, x = idx // w, idx % w
86
+ current_elev = elev[y, x]
87
+
88
+ # ๊ฐ€์žฅ ๋‚ฎ์€ ์ด์›ƒ ์ฐพ๊ธฐ
89
+ min_elev = current_elev
90
+ min_neighbor = None
91
+
92
+ for dy, dx in neighbors:
93
+ ny, nx = y + dy, x + dx
94
+ if 0 <= ny < h and 0 <= nx < w:
95
+ if elev[ny, nx] < min_elev:
96
+ min_elev = elev[ny, nx]
97
+ min_neighbor = (ny, nx)
98
+
99
+ # ํ•˜๋ฅ˜๋กœ ์œ ๋Ÿ‰ ์ „๋‹ฌ
100
+ if min_neighbor is not None:
101
+ acc[min_neighbor] += acc[y, x]
102
+
103
+ self.discharge = acc
104
+ return acc
105
+
106
+ def calculate_hydraulics(self):
107
+ """Manning ๋ฐฉ์ •์‹ ๊ธฐ๋ฐ˜ ์ˆ˜๋ฆฌํ•™ ๊ณ„์‚ฐ"""
108
+ slope = self.terrain.get_slope() + 0.0001 # 0 ๋ฐฉ์ง€
109
+
110
+ # ๊ฐ€์ •: ์ฑ„๋„ ํญ = ์œ ๋Ÿ‰์˜ ํ•จ์ˆ˜
111
+ channel_width = 2 * np.power(self.discharge + 0.01, 0.4)
112
+
113
+ # Manning ๋ฐฉ์ •์‹: V = (1/n) * R^(2/3) * S^(1/2)
114
+ # ๋‹จ์ˆœํ™”: R โ‰ˆ depth
115
+ # Q = V * A, A = width * depth
116
+ # depth = (Q * n / (width * S^0.5))^(3/5)
117
+
118
+ self.depth = np.power(
119
+ self.discharge * self.manning_n / (channel_width * np.sqrt(slope) + 0.01),
120
+ 0.6
121
+ )
122
+ self.depth = np.clip(self.depth, 0, 50)
123
+
124
+ # ์œ ์†
125
+ hydraulic_radius = self.depth # ๋‹จ์ˆœํ™”
126
+ self.velocity = (1 / self.manning_n) * np.power(hydraulic_radius, 2/3) * np.sqrt(slope)
127
+ self.velocity = np.clip(self.velocity, 0, 10)
128
+
129
+ # ์ „๋‹จ์‘๋ ฅ ฯ„ = ฯgRS
130
+ rho_water = 1000 # kg/mยณ
131
+ g = 9.81
132
+ self.shear_stress = rho_water * g * self.depth * slope
133
+
134
+
135
+ class StreamPowerErosion:
136
+ """Stream Power Law ๊ธฐ๋ฐ˜ ์นจ์‹
137
+
138
+ E = K * A^m * S^n
139
+ - E: ์นจ์‹๋ฅ  (m/yr)
140
+ - K: ์นจ์‹ ๊ณ„์ˆ˜ (์•”์„ ํŠน์„ฑ ๋ฐ˜์˜)
141
+ - A: ์œ ์—ญ ๋ฉด์  (โ‰ˆ ์œ ๋Ÿ‰)
142
+ - S: ๊ฒฝ์‚ฌ
143
+ - m: ๋ฉด์  ์ง€์ˆ˜ (typically 0.3-0.6)
144
+ - n: ๊ฒฝ์‚ฌ ์ง€์ˆ˜ (typically 1.0-2.0)
145
+ """
146
+
147
+ def __init__(self, K: float = 1e-5, m: float = 0.5, n: float = 1.0):
148
+ self.K = K
149
+ self.m = m
150
+ self.n = n
151
+
152
+ def calculate_erosion(self, terrain: TerrainGrid, water: WaterFlow, dt: float = 1.0) -> np.ndarray:
153
+ """์นจ์‹๋Ÿ‰ ๊ณ„์‚ฐ"""
154
+ slope = terrain.get_slope()
155
+
156
+ # Stream Power Law
157
+ # K๋Š” ์•”์„ ๊ฒฝ๋„์— ๋ฐ˜๋น„๋ก€
158
+ effective_K = self.K * (1 - terrain.rock_hardness * 0.9)
159
+
160
+ erosion_rate = effective_K * np.power(water.discharge, self.m) * np.power(slope + 0.001, self.n)
161
+
162
+ erosion = erosion_rate * dt
163
+
164
+ # ๊ธฐ๋ฐ˜์•” ์ดํ•˜๋กœ ์นจ์‹ ๋ถˆ๊ฐ€
165
+ max_erosion = terrain.elevation - terrain.bedrock
166
+ erosion = np.minimum(erosion, np.maximum(max_erosion, 0))
167
+
168
+ return np.clip(erosion, 0, 5.0) # ์—ฐ๊ฐ„ ์ตœ๋Œ€ 5m
169
+
170
+
171
+ class HillslopeProcess:
172
+ """์‚ฌ๋ฉด ํ”„๋กœ์„ธ์Šค (Mass Wasting)
173
+
174
+ V์ž๊ณก ํ˜•์„ฑ์˜ ํ•ต์‹ฌ - ํ•˜๋ฐฉ ์นจ์‹ ํ›„ ์‚ฌ๋ฉด ๋ถ•๊ดด
175
+ """
176
+
177
+ def __init__(self, critical_slope: float = 0.7, diffusion_rate: float = 0.01):
178
+ self.critical_slope = critical_slope # ์ž„๊ณ„ ๊ฒฝ์‚ฌ (tan ฮธ)
179
+ self.diffusion_rate = diffusion_rate # ํ™•์‚ฐ ๊ณ„์ˆ˜
180
+
181
+ def mass_wasting(self, terrain: TerrainGrid, dt: float = 1.0) -> np.ndarray:
182
+ """์‚ฌ๋ฉด ๋ถ•๊ดด (๊ธ‰๊ฒฝ์‚ฌ โ†’ ๋ฌผ์งˆ ์ด๋™)"""
183
+ h, w = terrain.height, terrain.width
184
+ change = np.zeros((h, w))
185
+
186
+ elev = terrain.elevation
187
+ slope = terrain.get_slope()
188
+
189
+ # ์ž„๊ณ„ ๊ฒฝ์‚ฌ ์ดˆ๊ณผ ์ง€์ 
190
+ unstable = slope > self.critical_slope
191
+
192
+ # ๋ถˆ์•ˆ์ • ์ง€์ ์—์„œ ์ด์›ƒ์œผ๋กœ ๋ฌผ์งˆ ๋ถ„๋ฐฐ
193
+ for y in range(1, h-1):
194
+ for x in range(1, w-1):
195
+ if not unstable[y, x]:
196
+ continue
197
+
198
+ current = elev[y, x]
199
+ excess = (slope[y, x] - self.critical_slope) * terrain.cell_size
200
+
201
+ # 8๋ฐฉํ–ฅ ์ด์›ƒ ์ค‘ ๋‚ฎ์€ ๊ณณ์œผ๋กœ ๋ถ„๋ฐฐ
202
+ neighbors = [(y-1,x), (y+1,x), (y,x-1), (y,x+1)]
203
+ lower_neighbors = [(ny, nx) for ny, nx in neighbors
204
+ if elev[ny, nx] < current]
205
+
206
+ if lower_neighbors:
207
+ transfer = excess * 0.2 * dt # ์ „๋‹ฌ๋Ÿ‰
208
+ change[y, x] -= transfer
209
+ per_neighbor = transfer / len(lower_neighbors)
210
+ for ny, nx in lower_neighbors:
211
+ change[ny, nx] += per_neighbor
212
+
213
+ return change
214
+
215
+ def soil_creep(self, terrain: TerrainGrid, dt: float = 1.0) -> np.ndarray:
216
+ """ํ† ์–‘ ํฌ๋ฆฌํ”„ (๋А๋ฆฐ ํ™•์‚ฐ)"""
217
+ # ๋ผํ”Œ๋ผ์‹œ์•ˆ ํ™•์‚ฐ
218
+ laplacian = (
219
+ np.roll(terrain.elevation, 1, axis=0) +
220
+ np.roll(terrain.elevation, -1, axis=0) +
221
+ np.roll(terrain.elevation, 1, axis=1) +
222
+ np.roll(terrain.elevation, -1, axis=1) -
223
+ 4 * terrain.elevation
224
+ )
225
+
226
+ return self.diffusion_rate * laplacian * dt
227
+
228
+
229
+ class VValleySimulation:
230
+ """V์ž๊ณก ์‹œ๋ฎฌ๋ ˆ์ด์…˜ - ์‹ค์ œ ๋ฌผ๋ฆฌ ๊ธฐ๋ฐ˜
231
+
232
+ ํ”„๋กœ์„ธ์Šค:
233
+ 1. ๊ฐ•์ˆ˜ โ†’ ์œ ์ถœ (D8 flow accumulation)
234
+ 2. Stream Power Law ์นจ์‹
235
+ 3. ์‚ฌ๋ฉด ๋ถ•๊ดด (Mass Wasting)
236
+ 4. ํ† ์–‘ ํฌ๋ฆฌํ”„
237
+ """
238
+
239
+ def __init__(self, width: int = 100, height: int = 100):
240
+ self.terrain = TerrainGrid(width=width, height=height)
241
+ self.water = WaterFlow(terrain=self.terrain)
242
+ self.erosion = StreamPowerErosion()
243
+ self.hillslope = HillslopeProcess()
244
+
245
+ self.history: List[np.ndarray] = []
246
+ self.time = 0.0
247
+
248
+ def initialize_terrain(self, max_elevation: float = 500.0,
249
+ initial_channel_depth: float = 10.0,
250
+ rock_hardness: float = 0.5):
251
+ """์ดˆ๊ธฐ ์ง€ํ˜• ์„ค์ •"""
252
+ h, w = self.terrain.height, self.terrain.width
253
+
254
+ # ๋ถโ†’๋‚จ ๊ฒฝ์‚ฌ
255
+ for y in range(h):
256
+ base = max_elevation * (1 - y / h)
257
+ self.terrain.elevation[y, :] = base
258
+
259
+ # ์ค‘์•™์— ์ดˆ๊ธฐ ํ•˜์ฒœ ์ฑ„๋„
260
+ center = w // 2
261
+ for x in range(center - 3, center + 4):
262
+ if 0 <= x < w:
263
+ depth = initial_channel_depth * (1 - abs(x - center) / 4)
264
+ self.terrain.elevation[:, x] -= depth
265
+
266
+ # ์•”์„ ๊ฒฝ๋„
267
+ self.terrain.rock_hardness[:] = rock_hardness
268
+
269
+ # ๊ธฐ๋ฐ˜์•”
270
+ self.terrain.bedrock[:] = self.terrain.elevation.min() - 200
271
+
272
+ self.history = [self.terrain.elevation.copy()]
273
+ self.time = 0.0
274
+
275
+ def step(self, dt: float = 1.0, precipitation: float = 0.001):
276
+ """1 ํƒ€์ž„์Šคํ… ์ง„ํ–‰"""
277
+ # 1. ์ˆ˜๋ฌธ ๊ณ„์‚ฐ
278
+ self.water.flow_accumulation_d8(precipitation)
279
+ self.water.calculate_hydraulics()
280
+
281
+ # 2. Stream Power ์นจ์‹
282
+ erosion = self.erosion.calculate_erosion(self.terrain, self.water, dt)
283
+ self.terrain.elevation -= erosion
284
+
285
+ # 3. ์‚ฌ๋ฉด ๋ถ•๊ดด
286
+ wasting = self.hillslope.mass_wasting(self.terrain, dt)
287
+ self.terrain.elevation += wasting
288
+
289
+ # 4. ํ† ์–‘ ํฌ๋ฆฌํ”„
290
+ creep = self.hillslope.soil_creep(self.terrain, dt)
291
+ self.terrain.elevation += creep
292
+
293
+ self.time += dt
294
+
295
+ def run(self, total_time: float, save_interval: float = 100.0, dt: float = 1.0):
296
+ """์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹คํ–‰ ๋ฐ ํžˆ์Šคํ† ๋ฆฌ ์ €์žฅ"""
297
+ steps = int(total_time / dt)
298
+ save_every = int(save_interval / dt)
299
+
300
+ for i in range(steps):
301
+ self.step(dt)
302
+ if (i + 1) % save_every == 0:
303
+ self.history.append(self.terrain.elevation.copy())
304
+
305
+ return self.history
306
+
307
+ def get_cross_section(self, y_position: int = None) -> Tuple[np.ndarray, np.ndarray]:
308
+ """๋‹จ๋ฉด ์ถ”์ถœ"""
309
+ if y_position is None:
310
+ y_position = self.terrain.height // 2
311
+
312
+ x = np.arange(self.terrain.width) * self.terrain.cell_size
313
+ z = self.terrain.elevation[y_position, :]
314
+
315
+ return x, z
316
+
317
+ def measure_valley_depth(self) -> float:
318
+ """V์ž๊ณก ๊นŠ์ด ์ธก์ •"""
319
+ center = self.terrain.width // 2
320
+ y_mid = self.terrain.height // 2
321
+
322
+ # ์ค‘์•™๊ณผ ์–‘์ชฝ 20์…€ ๋–จ์–ด์ง„ ๊ณณ์˜ ๊ณ ๋„ ์ฐจ์ด
323
+ left = self.terrain.elevation[y_mid, max(0, center-20)]
324
+ right = self.terrain.elevation[y_mid, min(self.terrain.width-1, center+20)]
325
+ center_elev = self.terrain.elevation[y_mid, center]
326
+
327
+ return max(0, (left + right) / 2 - center_elev)
328
+
329
+
330
+ # ํ”„๋ฆฌ์ปดํ“จํŒ… ํ•จ์ˆ˜
331
+ def precompute_v_valley(max_time: int = 10000,
332
+ rock_hardness: float = 0.5,
333
+ K: float = 1e-5,
334
+ precipitation: float = 0.001,
335
+ save_every: int = 100) -> List[np.ndarray]:
336
+ """V์ž๊ณก ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํ”„๋ฆฌ์ปดํ“จํŒ…"""
337
+ sim = VValleySimulation(width=100, height=100)
338
+ sim.erosion.K = K
339
+ sim.initialize_terrain(rock_hardness=rock_hardness)
340
+
341
+ history = sim.run(
342
+ total_time=max_time,
343
+ save_interval=save_every,
344
+ dt=1.0
345
+ )
346
+
347
+ return history
348
+
349
+
350
+ if __name__ == "__main__":
351
+ print("V์ž๊ณก ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํ…Œ์ŠคํŠธ")
352
+ print("=" * 50)
353
+
354
+ sim = VValleySimulation()
355
+ sim.initialize_terrain(rock_hardness=0.3)
356
+
357
+ print(f"์ดˆ๊ธฐ ์ƒํƒœ: ๊นŠ์ด = {sim.measure_valley_depth():.1f}m")
358
+
359
+ for year in [1000, 2000, 5000, 10000]:
360
+ sim.run(1000, save_interval=1000)
361
+ depth = sim.measure_valley_depth()
362
+ print(f"Year {year}: ๊นŠ์ด = {depth:.1f}m")
363
+
364
+ print("=" * 50)
365
+ print("ํ…Œ์ŠคํŠธ ์™„๋ฃŒ!")
engine/precompute.py ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Geo-Lab AI: ํ”„๋ฆฌ์ปดํ“จํŒ… + ์บ์‹ฑ ์‹œ์Šคํ…œ
3
+ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ๋ฏธ๋ฆฌ ๋Œ๋ ค๋†“๊ณ  ์Šฌ๋ผ์ด๋”๋กœ ํƒ์ƒ‰
4
+ """
5
+ import numpy as np
6
+ import pickle
7
+ import hashlib
8
+ import os
9
+ from pathlib import Path
10
+ from typing import Dict, Any, Optional, Callable
11
+ import threading
12
+ from concurrent.futures import ThreadPoolExecutor
13
+
14
+
15
+ class PrecomputeCache:
16
+ """ํ”„๋ฆฌ์ปดํ“จํŒ… ๊ฒฐ๊ณผ ์บ์‹œ"""
17
+
18
+ def __init__(self, cache_dir: str = None):
19
+ if cache_dir is None:
20
+ cache_dir = Path(__file__).parent.parent / "cache"
21
+ self.cache_dir = Path(cache_dir)
22
+ self.cache_dir.mkdir(exist_ok=True)
23
+
24
+ # ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ
25
+ self.memory_cache: Dict[str, Any] = {}
26
+
27
+ def _get_key(self, sim_type: str, params: dict) -> str:
28
+ """ํŒŒ๋ผ๋ฏธํ„ฐ ๊ธฐ๋ฐ˜ ์บ์‹œ ํ‚ค ์ƒ์„ฑ"""
29
+ param_str = f"{sim_type}_{sorted(params.items())}"
30
+ return hashlib.md5(param_str.encode()).hexdigest()[:16]
31
+
32
+ def get(self, sim_type: str, params: dict) -> Optional[Any]:
33
+ """์บ์‹œ์—์„œ ์กฐํšŒ"""
34
+ key = self._get_key(sim_type, params)
35
+
36
+ # ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ ํ™•์ธ
37
+ if key in self.memory_cache:
38
+ return self.memory_cache[key]
39
+
40
+ # ๋””์Šคํฌ ์บ์‹œ ํ™•์ธ
41
+ cache_file = self.cache_dir / f"{key}.pkl"
42
+ if cache_file.exists():
43
+ try:
44
+ with open(cache_file, 'rb') as f:
45
+ data = pickle.load(f)
46
+ self.memory_cache[key] = data
47
+ return data
48
+ except:
49
+ pass
50
+
51
+ return None
52
+
53
+ def set(self, sim_type: str, params: dict, data: Any):
54
+ """์บ์‹œ์— ์ €์žฅ"""
55
+ key = self._get_key(sim_type, params)
56
+
57
+ # ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ
58
+ self.memory_cache[key] = data
59
+
60
+ # ๋””์Šคํฌ ์บ์‹œ
61
+ cache_file = self.cache_dir / f"{key}.pkl"
62
+ try:
63
+ with open(cache_file, 'wb') as f:
64
+ pickle.dump(data, f)
65
+ except:
66
+ pass
67
+
68
+ def get_or_compute(self, sim_type: str, params: dict,
69
+ compute_fn: Callable, force_recompute: bool = False) -> Any:
70
+ """์บ์‹œ ๋˜๋Š” ๊ณ„์‚ฐ"""
71
+ if not force_recompute:
72
+ cached = self.get(sim_type, params)
73
+ if cached is not None:
74
+ return cached
75
+
76
+ # ๊ณ„์‚ฐ
77
+ result = compute_fn()
78
+ self.set(sim_type, params, result)
79
+ return result
80
+
81
+
82
+ class SimulationManager:
83
+ """์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋งค๋‹ˆ์ €
84
+
85
+ ํŒŒ๋ผ๋ฏธํ„ฐ๋ณ„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ํ”„๋ฆฌ์ปดํ“จํŒ…ํ•˜๊ณ 
86
+ UI์—์„œ๋Š” ์บ์‹œ๋œ ๊ฒฐ๊ณผ๋ฅผ ์กฐํšŒ
87
+ """
88
+
89
+ def __init__(self):
90
+ self.cache = PrecomputeCache()
91
+ self.executor = ThreadPoolExecutor(max_workers=2)
92
+ self.computing: Dict[str, bool] = {}
93
+
94
+ def get_v_valley(self, rock_hardness: float = 0.5,
95
+ K: float = 1e-5,
96
+ max_time: int = 10000) -> Dict:
97
+ """V์ž๊ณก ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๊ฒฐ๊ณผ"""
98
+ from engine.physics_engine import VValleySimulation
99
+
100
+ # ํŒŒ๋ผ๋ฏธํ„ฐ ์–‘์žํ™” (์บ์‹œ ํšจ์œจ)
101
+ rock_hardness = round(rock_hardness, 1)
102
+ K = round(K, 6)
103
+
104
+ params = {
105
+ 'rock_hardness': rock_hardness,
106
+ 'K': K,
107
+ 'max_time': max_time
108
+ }
109
+
110
+ def compute():
111
+ sim = VValleySimulation(width=100, height=100)
112
+ sim.erosion.K = K
113
+ sim.initialize_terrain(rock_hardness=rock_hardness)
114
+ history = sim.run(max_time, save_interval=max_time // 100)
115
+
116
+ # ๊ฐ ์Šค๋ƒ…์ƒท์˜ ๋‹จ๋ฉด๊ณผ ๊นŠ์ด ์ €์žฅ
117
+ cross_sections = []
118
+ depths = []
119
+ for elev in history:
120
+ temp_sim = VValleySimulation()
121
+ temp_sim.terrain.elevation = elev
122
+ x, z = temp_sim.get_cross_section()
123
+ depth = temp_sim.measure_valley_depth()
124
+ cross_sections.append((x, z))
125
+ depths.append(depth)
126
+
127
+ return {
128
+ 'history': history,
129
+ 'cross_sections': cross_sections,
130
+ 'depths': depths,
131
+ 'n_frames': len(history)
132
+ }
133
+
134
+ return self.cache.get_or_compute('v_valley', params, compute)
135
+
136
+ def get_meander(self, initial_sinuosity: float = 1.3,
137
+ max_time: int = 10000) -> Dict:
138
+ """๊ณก๋ฅ˜ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๊ฒฐ๊ณผ"""
139
+ from engine.meander_physics import MeanderSimulation
140
+
141
+ initial_sinuosity = round(initial_sinuosity, 1)
142
+
143
+ params = {
144
+ 'initial_sinuosity': initial_sinuosity,
145
+ 'max_time': max_time
146
+ }
147
+
148
+ def compute():
149
+ sim = MeanderSimulation(initial_sinuosity=initial_sinuosity)
150
+ history = sim.run(max_time, save_interval=max_time // 100)
151
+
152
+ # ๊ตด๊ณก๋„ ํžˆ์Šคํ† ๋ฆฌ
153
+ sinuosities = []
154
+ for x, y in history:
155
+ temp_channel = type(sim.channel)(x=x, y=y)
156
+ sinuosities.append(temp_channel.calculate_sinuosity())
157
+
158
+ return {
159
+ 'history': history,
160
+ 'oxbow_lakes': sim.oxbow_lakes,
161
+ 'sinuosities': sinuosities,
162
+ 'n_frames': len(history)
163
+ }
164
+
165
+ return self.cache.get_or_compute('meander', params, compute)
166
+
167
+ def get_delta(self, river_energy: float = 60,
168
+ wave_energy: float = 25,
169
+ tidal_energy: float = 15,
170
+ max_time: int = 10000) -> Dict:
171
+ """์‚ผ๊ฐ์ฃผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๊ฒฐ๊ณผ"""
172
+ from engine.delta_physics import DeltaSimulation
173
+
174
+ # ์ •๊ทœํ™”
175
+ total = river_energy + wave_energy + tidal_energy + 0.01
176
+ river_energy = round(river_energy / total, 2)
177
+ wave_energy = round(wave_energy / total, 2)
178
+ tidal_energy = round(tidal_energy / total, 2)
179
+
180
+ params = {
181
+ 'river_energy': river_energy,
182
+ 'wave_energy': wave_energy,
183
+ 'tidal_energy': tidal_energy,
184
+ 'max_time': max_time
185
+ }
186
+
187
+ def compute():
188
+ sim = DeltaSimulation()
189
+ sim.set_energy_balance(river_energy, wave_energy, tidal_energy)
190
+ history = sim.run(max_time, save_interval=max_time // 100)
191
+
192
+ return {
193
+ 'history': history,
194
+ 'delta_type': sim.get_delta_type().value,
195
+ 'delta_area': sim.get_delta_area(),
196
+ 'n_frames': len(history)
197
+ }
198
+
199
+ return self.cache.get_or_compute('delta', params, compute)
200
+
201
+ def precompute_common_scenarios(self):
202
+ """์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค ๋ฏธ๋ฆฌ ๊ณ„์‚ฐ"""
203
+ scenarios = [
204
+ # V์ž๊ณก
205
+ {'type': 'v_valley', 'rock_hardness': 0.3, 'K': 1e-5},
206
+ {'type': 'v_valley', 'rock_hardness': 0.5, 'K': 1e-5},
207
+ {'type': 'v_valley', 'rock_hardness': 0.7, 'K': 1e-5},
208
+ # ๊ณก๋ฅ˜
209
+ {'type': 'meander', 'initial_sinuosity': 1.2},
210
+ {'type': 'meander', 'initial_sinuosity': 1.5},
211
+ # ์‚ผ๊ฐ์ฃผ
212
+ {'type': 'delta', 'river': 0.7, 'wave': 0.2, 'tidal': 0.1},
213
+ {'type': 'delta', 'river': 0.3, 'wave': 0.5, 'tidal': 0.2},
214
+ {'type': 'delta', 'river': 0.2, 'wave': 0.2, 'tidal': 0.6},
215
+ ]
216
+
217
+ for scenario in scenarios:
218
+ if scenario['type'] == 'v_valley':
219
+ self.executor.submit(
220
+ self.get_v_valley,
221
+ rock_hardness=scenario['rock_hardness'],
222
+ K=scenario['K']
223
+ )
224
+ elif scenario['type'] == 'meander':
225
+ self.executor.submit(
226
+ self.get_meander,
227
+ initial_sinuosity=scenario['initial_sinuosity']
228
+ )
229
+ elif scenario['type'] == 'delta':
230
+ self.executor.submit(
231
+ self.get_delta,
232
+ river_energy=scenario['river'],
233
+ wave_energy=scenario['wave'],
234
+ tidal_energy=scenario['tidal']
235
+ )
236
+
237
+
238
+ # ๊ธ€๋กœ๋ฒŒ ์ธ์Šคํ„ด์Šค
239
+ _manager = None
240
+
241
+ def get_simulation_manager() -> SimulationManager:
242
+ global _manager
243
+ if _manager is None:
244
+ _manager = SimulationManager()
245
+ return _manager
engine/pyvista_render.py ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PyVista ๊ธฐ๋ฐ˜ ๊ณ ํ’ˆ์งˆ 3D ์ง€ํ˜• ๋ Œ๋”๋ง
3
+ - ์ง„์งœ 3D ๋ Œ๋”๋ง
4
+ - ํ…์Šค์ฒ˜ ๋งคํ•‘
5
+ - ์กฐ๋ช…, ๊ทธ๋ฆผ์ž ํšจ๊ณผ
6
+ """
7
+ import numpy as np
8
+
9
+ try:
10
+ import pyvista as pv
11
+ PYVISTA_AVAILABLE = True
12
+ except ImportError:
13
+ PYVISTA_AVAILABLE = False
14
+
15
+
16
+ def create_terrain_mesh(elevation: np.ndarray, x_scale: float = 1.0, y_scale: float = 1.0, z_scale: float = 1.0):
17
+ """๊ณ ๋„ ๋ฐฐ์—ด์„ PyVista ๋ฉ”์‹œ๋กœ ๋ณ€ํ™˜"""
18
+ if not PYVISTA_AVAILABLE:
19
+ raise ImportError("PyVista is not installed")
20
+
21
+ h, w = elevation.shape
22
+ x = np.arange(w) * x_scale
23
+ y = np.arange(h) * y_scale
24
+ X, Y = np.meshgrid(x, y)
25
+ Z = elevation * z_scale
26
+
27
+ grid = pv.StructuredGrid(X, Y, Z)
28
+ grid["elevation"] = Z.flatten(order="F")
29
+
30
+ return grid
31
+
32
+
33
+ def create_interactive_plotter(elevation: np.ndarray, title: str = "์ง€ํ˜•",
34
+ x_scale: float = 1.0, y_scale: float = 1.0, z_scale: float = 1.0):
35
+ """์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ํšŒ์ „ ๊ฐ€๋Šฅํ•œ PyVista ํ”Œ๋กœํ„ฐ ์ƒ์„ฑ (stpyvista์šฉ)"""
36
+ if not PYVISTA_AVAILABLE:
37
+ return None
38
+
39
+ mesh = create_terrain_mesh(elevation, x_scale, y_scale, z_scale)
40
+
41
+ plotter = pv.Plotter(window_size=[800, 600])
42
+ plotter.set_background("#1a1a2e")
43
+
44
+ # ๋‹จ์ผ ์ƒ‰์ƒ (copper)
45
+ plotter.add_mesh(
46
+ mesh,
47
+ scalars="elevation",
48
+ cmap="copper",
49
+ lighting=True,
50
+ smooth_shading=True,
51
+ show_scalar_bar=True,
52
+ scalar_bar_args={
53
+ "title": "๊ณ ๋„ (m)",
54
+ "color": "white"
55
+ },
56
+ specular=0.3,
57
+ specular_power=15
58
+ )
59
+
60
+ # ์กฐ๋ช…
61
+ plotter.remove_all_lights()
62
+ plotter.add_light(pv.Light(position=(1000, 1000, 2000), intensity=1.2))
63
+ plotter.add_light(pv.Light(position=(-500, -500, 1000), intensity=0.5))
64
+
65
+ plotter.add_text(title, font_size=14, position="upper_left", color="white")
66
+
67
+ return plotter
68
+
69
+
70
+ def render_v_valley_pyvista(elevation: np.ndarray, depth: float):
71
+ """V์ž๊ณก PyVista ๋ Œ๋”๋ง - ๋‹จ์ผ ์ƒ‰์ƒ ๋ช…๋„ ๋ณ€ํ™”"""
72
+ if not PYVISTA_AVAILABLE:
73
+ return None
74
+
75
+ # ๋ฉ”์‹œ ์ƒ์„ฑ (์ˆ˜์ง ๊ณผ์žฅ 2๋ฐฐ)
76
+ mesh = create_terrain_mesh(elevation, x_scale=12.5, y_scale=12.5, z_scale=2.0)
77
+
78
+ # ํ”Œ๋กœํ„ฐ ์„ค์ •
79
+ plotter = pv.Plotter(off_screen=True, window_size=[1200, 900])
80
+ plotter.set_background("#1a1a2e") # ์–ด๋‘์šด ๋ฐฐ๊ฒฝ
81
+
82
+ # ๋‹จ์ผ ์ƒ‰์ƒ (๊ฐˆ์ƒ‰ ๊ณ„์—ด - copper) ๋ช…๋„ ๋ณ€ํ™”
83
+ plotter.add_mesh(
84
+ mesh,
85
+ scalars="elevation",
86
+ cmap="copper", # ๊ฐˆ์ƒ‰ ๋‹จ์ผ ์ƒ‰์ƒ ๋ช…๋„ ๋ณ€ํ™”
87
+ lighting=True,
88
+ smooth_shading=True,
89
+ show_scalar_bar=True,
90
+ scalar_bar_args={
91
+ "title": "๊ณ ๋„ (m)",
92
+ "vertical": True,
93
+ "title_font_size": 14,
94
+ "label_font_size": 12
95
+ },
96
+ specular=0.3,
97
+ specular_power=15
98
+ )
99
+
100
+ # ํ•˜์ฒœ (๋” ์ง„ํ•œ ์ƒ‰์ƒ)
101
+ water_level = elevation.min() + 3
102
+ h, w = elevation.shape
103
+ x_water = np.arange(w) * 12.5
104
+ y_water = np.arange(h) * 12.5
105
+ X_w, Y_w = np.meshgrid(x_water, y_water)
106
+ Z_w = np.full_like(elevation, water_level, dtype=float)
107
+ water_mask = elevation < water_level
108
+ Z_w[~water_mask] = np.nan
109
+
110
+ if np.any(water_mask):
111
+ water_grid = pv.StructuredGrid(X_w, Y_w, Z_w)
112
+ plotter.add_mesh(water_grid, color="#2C3E50", opacity=0.9) # ์–ด๋‘์šด ๋ฌผ
113
+
114
+ # ๋“œ๋ผ๋งˆํ‹ฑํ•œ ์นด๋ฉ”๋ผ ๊ฐ๋„
115
+ plotter.camera_position = [
116
+ (w * 12.5 * 1.5, h * 12.5 * 0.2, elevation.max() * 3),
117
+ (w * 12.5 * 0.5, h * 12.5 * 0.5, elevation.min()),
118
+ (0, 0, 1)
119
+ ]
120
+
121
+ # ๊ฐ•ํ•œ ์กฐ๋ช… (๊ทธ๋ฆผ์ž ํšจ๊ณผ)
122
+ plotter.remove_all_lights()
123
+ key_light = pv.Light(position=(w*12.5*3, 0, elevation.max()*5), intensity=1.2)
124
+ fill_light = pv.Light(position=(-w*12.5, h*12.5*2, elevation.max()*2), intensity=0.4)
125
+ plotter.add_light(key_light)
126
+ plotter.add_light(fill_light)
127
+
128
+ plotter.add_text(f"V์ž๊ณก | ๊นŠ์ด: {depth:.0f}m", font_size=16,
129
+ position="upper_left", color="white")
130
+
131
+ img = plotter.screenshot(return_img=True)
132
+ plotter.close()
133
+
134
+ return img
135
+
136
+
137
+ def render_delta_pyvista(elevation: np.ndarray, delta_type: str, area: float):
138
+ """์‚ผ๊ฐ์ฃผ PyVista ๋ Œ๋”๋ง - ๋‹จ์ผ ์ƒ‰์ƒ"""
139
+ if not PYVISTA_AVAILABLE:
140
+ return None
141
+
142
+ # ๋ฉ”์‹œ ์ƒ์„ฑ (์ˆ˜์ง ๊ณผ์žฅ)
143
+ mesh = create_terrain_mesh(elevation, x_scale=50, y_scale=50, z_scale=10.0)
144
+
145
+ plotter = pv.Plotter(off_screen=True, window_size=[1200, 900])
146
+ plotter.set_background("#0f0f23") # ์–ด๋‘์šด ๋ฐฐ๊ฒฝ
147
+
148
+ # ๋‹จ์ผ ์ƒ‰์ƒ (bone - ๋ฒ ์ด์ง€/๊ฐˆ์ƒ‰ ๋ช…๋„ ๋ณ€ํ™”)
149
+ plotter.add_mesh(
150
+ mesh,
151
+ scalars="elevation",
152
+ cmap="bone", # ๋ฒ ์ด์ง€ ๋‹จ์ผ ์ƒ‰์ƒ ๋ช…๋„ ๋ณ€ํ™”
153
+ lighting=True,
154
+ smooth_shading=True,
155
+ clim=[elevation.min(), elevation.max()],
156
+ show_scalar_bar=True,
157
+ scalar_bar_args={
158
+ "title": "๊ณ ๋„ (m)",
159
+ "title_font_size": 14,
160
+ "label_font_size": 12
161
+ },
162
+ specular=0.2,
163
+ specular_power=10
164
+ )
165
+
166
+ # ํ•ด์ˆ˜๋ฉด (์ง„ํ•œ ์ƒ‰์ƒ)
167
+ h, w = elevation.shape
168
+ x = np.arange(w) * 50
169
+ y = np.arange(h) * 50
170
+ X, Y = np.meshgrid(x, y)
171
+ Z_sea = np.zeros_like(elevation, dtype=float)
172
+ sea_mask = elevation < 0
173
+ Z_sea[~sea_mask] = np.nan
174
+
175
+ if np.any(sea_mask):
176
+ sea_grid = pv.StructuredGrid(X, Y, Z_sea)
177
+ plotter.add_mesh(sea_grid, color="#1a3a5f", opacity=0.85)
178
+
179
+ # ์นด๋ฉ”๋ผ
180
+ plotter.camera_position = [
181
+ (w * 50 * 0.5, -h * 50 * 0.5, elevation.max() * 50),
182
+ (w * 50 * 0.5, h * 50 * 0.5, 0),
183
+ (0, 0, 1)
184
+ ]
185
+
186
+ # ์กฐ๋ช…
187
+ plotter.remove_all_lights()
188
+ plotter.add_light(pv.Light(position=(w*50*2, -h*50, 1000), intensity=1.3))
189
+ plotter.add_light(pv.Light(position=(-w*50, h*50*2, 500), intensity=0.5))
190
+
191
+ plotter.add_text(f"{delta_type} | ๋ฉด์ : {area:.2f} kmยฒ", font_size=16,
192
+ position="upper_left", color="white")
193
+
194
+ img = plotter.screenshot(return_img=True)
195
+ plotter.close()
196
+
197
+ return img
198
+
199
+
200
+ def render_meander_pyvista(x: np.ndarray, y: np.ndarray, sinuosity: float, oxbow_lakes: list):
201
+ """๊ณก๋ฅ˜ ํ•˜์ฒœ PyVista ๋ Œ๋”๋ง (2.5D)"""
202
+ if not PYVISTA_AVAILABLE:
203
+ return None
204
+
205
+ plotter = pv.Plotter(off_screen=True, window_size=[1400, 600])
206
+
207
+ # ๋ฒ”๋žŒ์› (ํ‰๋ฉด)
208
+ floodplain = pv.Plane(
209
+ center=(x.mean(), y.mean(), -1),
210
+ direction=(0, 0, 1),
211
+ i_size=x.max() - x.min() + 200,
212
+ j_size=max(200, (y.max() - y.min()) * 3)
213
+ )
214
+ plotter.add_mesh(floodplain, color="#8FBC8F", opacity=0.8)
215
+
216
+ # ํ•˜์ฒœ (ํŠœ๋ธŒ)
217
+ points = np.column_stack([x, y, np.zeros_like(x)])
218
+ spline = pv.Spline(points, 500)
219
+ tube = spline.tube(radius=8)
220
+ plotter.add_mesh(tube, color="#4169E1", smooth_shading=True)
221
+
222
+ # ์šฐ๊ฐํ˜ธ
223
+ for lake_x, lake_y in oxbow_lakes:
224
+ if len(lake_x) > 3:
225
+ lake_points = np.column_stack([lake_x, lake_y, np.full_like(lake_x, -0.5)])
226
+ lake_spline = pv.Spline(lake_points, 100)
227
+ lake_tube = lake_spline.tube(radius=6)
228
+ plotter.add_mesh(lake_tube, color="#87CEEB", opacity=0.8)
229
+
230
+ # ์นด๋ฉ”๋ผ (์œ„์—์„œ ์•ฝ๊ฐ„ ๊ธฐ์šธ์–ด์ง„ ์‹œ์ )
231
+ plotter.camera_position = [
232
+ (x.mean(), y.mean() - 300, 400),
233
+ (x.mean(), y.mean(), 0),
234
+ (0, 0, 1)
235
+ ]
236
+
237
+ # ์กฐ๋ช…
238
+ plotter.add_light(pv.Light(position=(x.mean(), y.mean(), 500), intensity=1.0))
239
+
240
+ plotter.add_text(f"๊ณก๋ฅ˜ ํ•˜์ฒœ (๊ตด๊ณก๋„: {sinuosity:.2f})", font_size=14, position="upper_left")
241
+
242
+ img = plotter.screenshot(return_img=True)
243
+ plotter.close()
244
+
245
+ return img
246
+
247
+
248
+ def save_pyvista_image(img: np.ndarray, filepath: str):
249
+ """PyVista ๋ Œ๋”๋ง ์ด๋ฏธ์ง€ ์ €์žฅ"""
250
+ from PIL import Image
251
+ if img is not None:
252
+ Image.fromarray(img).save(filepath)
253
+ return True
254
+ return False
engine/river/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # River Submodule
engine/river/delta.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Geo-Lab AI: ์‚ผ๊ฐ์ฃผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
3
+ ํ•˜๋ฅ˜์—์„œ 3๊ฐ€์ง€ ์—๋„ˆ์ง€ ๊ท ํ˜•์— ๋”ฐ๋ฅธ ์‚ผ๊ฐ์ฃผ ํ˜•ํƒœ ํ˜•์„ฑ
4
+ """
5
+ import numpy as np
6
+ from dataclasses import dataclass, field
7
+ from typing import List, Tuple
8
+ from enum import Enum
9
+
10
+ import sys
11
+ import os
12
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
13
+
14
+ from engine.base import Terrain, Water
15
+ from engine.deposition import delta_deposition, apply_deposition
16
+
17
+
18
+ class DeltaType(Enum):
19
+ """์‚ผ๊ฐ์ฃผ ๋ถ„๋ฅ˜ (Galloway's Classification)"""
20
+ RIVER_DOMINATED = "์กฐ์กฑ์ƒ (Bird's Foot)" # ๋ฏธ์‹œ์‹œํ”ผํ˜•
21
+ WAVE_DOMINATED = "์›ํ˜ธ์ƒ (Arcuate)" # ๋‚˜์ผํ˜•
22
+ TIDE_DOMINATED = "์ฒจ๊ฐ์ƒ (Cuspate)" # ํ‹ฐ๋ฒ ๋ฅดํ˜•
23
+
24
+
25
+ @dataclass
26
+ class DeltaSimulator:
27
+ """์‚ผ๊ฐ์ฃผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
28
+
29
+ ํ•ต์‹ฌ ์›๋ฆฌ:
30
+ ํ•˜์ฒœ ์—๋„ˆ์ง€ vs ํŒŒ๋ž‘ ์—๋„ˆ์ง€ vs ์กฐ๋ฅ˜ ์—๋„ˆ์ง€์˜ ๊ท ํ˜•
31
+
32
+ ๊ฒฐ๊ณผ:
33
+ - ํ•˜์ฒœ ์šฐ์„ธ: ๊ธธ๊ฒŒ ๋ป—์€ ์กฐ์กฑ์ƒ (๋ฏธ์‹œ์‹œํ”ผ๊ฐ•)
34
+ - ํŒŒ๋ž‘ ์šฐ์„ธ: ๋„“๊ฒŒ ํผ์ง„ ์›ํ˜ธ์ƒ (๋‚˜์ผ๊ฐ•)
35
+ - ์กฐ๋ฅ˜ ์šฐ์„ธ: ๋พฐ์กฑํ•œ ์ฒจ๊ฐ์ƒ (ํ‹ฐ๋ฒ ๋ฅด๊ฐ•)
36
+ """
37
+
38
+ # ์ง€ํ˜• ํฌ๊ธฐ
39
+ width: int = 120
40
+ height: int = 100
41
+
42
+ # ์—๋„ˆ์ง€ ํŒŒ๋ผ๋ฏธํ„ฐ (0-100)
43
+ river_energy: float = 50.0
44
+ wave_energy: float = 30.0
45
+ tidal_energy: float = 20.0
46
+
47
+ # ํ™˜๊ฒฝ
48
+ sea_level: float = 10.0
49
+ sediment_supply: float = 1.0 # ํ‡ด์ ๋ฌผ ๊ณต๊ธ‰๋Ÿ‰ ๊ณ„์ˆ˜
50
+
51
+ # ๋‚ด๋ถ€ ์ƒํƒœ
52
+ terrain: Terrain = field(default=None)
53
+ water: Water = field(default=None)
54
+ delta_mask: np.ndarray = field(default=None) # ์‚ผ๊ฐ์ฃผ ์˜์—ญ
55
+ history: List[np.ndarray] = field(default_factory=list)
56
+ current_step: int = 0
57
+
58
+ def __post_init__(self):
59
+ self.reset()
60
+
61
+ def reset(self):
62
+ """์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ดˆ๊ธฐํ™”"""
63
+ self.terrain = Terrain(width=self.width, height=self.height)
64
+
65
+ # ์ง€ํ˜• ์„ค์ •: ์ƒ๋ฅ˜(์œก์ง€) โ†’ ํ•˜๋ฅ˜(๋ฐ”๋‹ค)
66
+ for y in range(self.height):
67
+ if y < self.height * 0.4:
68
+ # ์œก์ง€ (๊ฒฝ์‚ฌ)
69
+ elev = 50 - y * 0.5
70
+ else:
71
+ # ๋ฐ”๋‹ค (ํ•ด์ˆ˜๋ฉด ์•„๋ž˜)
72
+ elev = self.sea_level - (y - self.height * 0.4) * 0.3
73
+ self.terrain.elevation[y, :] = elev
74
+
75
+ # ์ค‘์•™์— ํ•˜์ฒœ ์ฑ„๋„
76
+ center = self.width // 2
77
+ for y in range(int(self.height * 0.5)):
78
+ for dx in range(-3, 4):
79
+ x = center + dx
80
+ if 0 <= x < self.width:
81
+ self.terrain.elevation[y, x] -= 5
82
+
83
+ # ์ˆ˜๋ฌธ ์ดˆ๊ธฐํ™”
84
+ self.water = Water(terrain=self.terrain)
85
+ self.water.discharge[0, center] = 100 # ์ƒ๋ฅ˜ ์œ ์ž…
86
+ self.water.accumulate_flow()
87
+
88
+ self.delta_mask = np.zeros((self.height, self.width), dtype=bool)
89
+ self.history = [self.terrain.elevation.copy()]
90
+ self.current_step = 0
91
+
92
+ def set_energy_balance(self, river: float, wave: float, tidal: float):
93
+ """์—๋„ˆ์ง€ ๊ท ํ˜• ์„ค์ • (0-100)"""
94
+ self.river_energy = max(0, min(100, river))
95
+ self.wave_energy = max(0, min(100, wave))
96
+ self.tidal_energy = max(0, min(100, tidal))
97
+
98
+ def step(self, n_steps: int = 1) -> np.ndarray:
99
+ """์‹œ๋ฎฌ๋ ˆ์ด์…˜ n์Šคํ… ์ง„ํ–‰"""
100
+ for _ in range(n_steps):
101
+ # 1. ์œ ๋Ÿ‰ ๊ณ„์‚ฐ
102
+ self.water.accumulate_flow()
103
+
104
+ # 2. ์‚ผ๊ฐ์ฃผ ํ‡ด์ 
105
+ deposition = self._calculate_delta_deposition()
106
+ apply_deposition(self.terrain, deposition)
107
+
108
+ # 3. ํŒŒ๋ž‘/์กฐ๋ฅ˜์— ์˜ํ•œ ์žฌ๋ถ„๋ฐฐ
109
+ redistribution = self._calculate_redistribution()
110
+ self.terrain.elevation += redistribution
111
+
112
+ # 4. ์‚ผ๊ฐ์ฃผ ์˜์—ญ ์—…๋ฐ์ดํŠธ
113
+ self._update_delta_mask()
114
+
115
+ self.current_step += 1
116
+
117
+ if self.current_step % 10 == 0:
118
+ self.history.append(self.terrain.elevation.copy())
119
+
120
+ return self.terrain.elevation
121
+
122
+ def _calculate_delta_deposition(self) -> np.ndarray:
123
+ """์—๋„ˆ์ง€ ๊ท ํ˜• ๊ธฐ๋ฐ˜ ์‚ผ๊ฐ์ฃผ ํ‡ด์  ๊ณ„์‚ฐ"""
124
+ deposition = delta_deposition(
125
+ self.terrain, self.water,
126
+ river_energy=self.river_energy / 50, # ์ •๊ทœํ™”
127
+ wave_energy=self.wave_energy / 50,
128
+ tidal_energy=self.tidal_energy / 50,
129
+ sea_level=self.sea_level
130
+ )
131
+
132
+ return deposition * self.sediment_supply
133
+
134
+ def _calculate_redistribution(self) -> np.ndarray:
135
+ """ํŒŒ๋ž‘/์กฐ๋ฅ˜์— ์˜ํ•œ ํ‡ด์ ๋ฌผ ์žฌ๋ถ„๋ฐฐ"""
136
+ redistribution = np.zeros((self.height, self.width))
137
+
138
+ # ํ•˜๊ตฌ ์˜์—ญ (ํ•ด์ˆ˜๋ฉด ๊ทผ์ฒ˜)
139
+ estuary = (self.terrain.elevation > self.sea_level - 5) & \
140
+ (self.terrain.elevation < self.sea_level + 5)
141
+
142
+ if not np.any(estuary):
143
+ return redistribution
144
+
145
+ # ํŒŒ๋ž‘: ์ขŒ์šฐ๋กœ ํผ๏ฟฝ๏ฟฝ๏ฟฝ๋ฆผ
146
+ if self.wave_energy > 20:
147
+ from scipy.ndimage import uniform_filter1d
148
+ for y in range(self.height):
149
+ if np.any(estuary[y, :]):
150
+ # ์ขŒ์šฐ ๋ฐฉํ–ฅ ํ‰ํ™œํ™”
151
+ row = self.terrain.elevation[y, :].copy()
152
+ smoothed = uniform_filter1d(row, size=5)
153
+ redistribution[y, :] = (smoothed - row) * (self.wave_energy / 100) * 0.1
154
+
155
+ # ์กฐ๋ฅ˜: ๋ฐ”๋‹ค ๋ฐฉํ–ฅ์œผ๋กœ ์“ธ์–ด๋ƒ„
156
+ if self.tidal_energy > 20:
157
+ for y in range(1, self.height):
158
+ factor = self.tidal_energy / 100 * 0.05
159
+ if np.any(estuary[y, :]):
160
+ # ์•„๋ž˜๋กœ ์ด๋™
161
+ redistribution[y, :] += self.terrain.elevation[y-1, :] * factor * 0.1
162
+ redistribution[y-1, :] -= self.terrain.elevation[y-1, :] * factor * 0.1
163
+
164
+ return redistribution
165
+
166
+ def _update_delta_mask(self):
167
+ """์‚ผ๊ฐ์ฃผ ์˜์—ญ ์—…๋ฐ์ดํŠธ"""
168
+ # ํ•ด์ˆ˜๋ฉด๋ณด๋‹ค ์•ฝ๊ฐ„ ๋†’๊ณ  ํ•˜๊ตฌ ๊ทผ์ฒ˜์ธ ์˜์—ญ
169
+ self.delta_mask = (
170
+ (self.terrain.elevation > self.sea_level) &
171
+ (self.terrain.elevation < self.sea_level + 20) &
172
+ (np.arange(self.height)[:, None] > self.height * 0.4) # ํ•˜๋ฅ˜
173
+ )
174
+
175
+ def get_delta_type(self) -> DeltaType:
176
+ """ํ˜„์žฌ ์‚ผ๊ฐ์ฃผ ์œ ํ˜• ํŒ๋ณ„"""
177
+ total = self.river_energy + self.wave_energy + self.tidal_energy + 0.01
178
+
179
+ r_ratio = self.river_energy / total
180
+ w_ratio = self.wave_energy / total
181
+ t_ratio = self.tidal_energy / total
182
+
183
+ if r_ratio >= w_ratio and r_ratio >= t_ratio:
184
+ return DeltaType.RIVER_DOMINATED
185
+ elif w_ratio >= t_ratio:
186
+ return DeltaType.WAVE_DOMINATED
187
+ else:
188
+ return DeltaType.TIDE_DOMINATED
189
+
190
+ def get_delta_area(self) -> float:
191
+ """์‚ผ๊ฐ์ฃผ ๋ฉด์  (์…€ ์ˆ˜)"""
192
+ return float(np.sum(self.delta_mask))
193
+
194
+ def get_delta_extent(self) -> Tuple[float, float]:
195
+ """์‚ผ๊ฐ์ฃผ ๊ฐ€๋กœ/์„ธ๋กœ ๋ฒ”์œ„"""
196
+ if not np.any(self.delta_mask):
197
+ return 0, 0
198
+
199
+ cols = np.any(self.delta_mask, axis=0)
200
+ rows = np.any(self.delta_mask, axis=1)
201
+
202
+ width = np.sum(cols) * self.terrain.cell_size
203
+ length = np.sum(rows) * self.terrain.cell_size
204
+
205
+ return width, length
206
+
207
+ def get_info(self) -> dict:
208
+ """ํ˜„์žฌ ์ƒํƒœ ์ •๋ณด"""
209
+ delta_type = self.get_delta_type()
210
+ width, length = self.get_delta_extent()
211
+
212
+ return {
213
+ "step": self.current_step,
214
+ "delta_type": delta_type.value,
215
+ "delta_area": self.get_delta_area(),
216
+ "delta_width": width,
217
+ "delta_length": length,
218
+ "energy_balance": {
219
+ "river": self.river_energy,
220
+ "wave": self.wave_energy,
221
+ "tidal": self.tidal_energy
222
+ }
223
+ }
224
+
225
+
226
+ if __name__ == "__main__":
227
+ # ์‹œ๋‚˜๋ฆฌ์˜ค 1: ๋ฏธ์‹œ์‹œํ”ผํ˜• (ํ•˜์ฒœ ์šฐ์„ธ)
228
+ print("=" * 50)
229
+ print("์‹œ๋‚˜๋ฆฌ์˜ค 1: ์กฐ์กฑ์ƒ ์‚ผ๊ฐ์ฃผ (๋ฏธ์‹œ์‹œํ”ผํ˜•)")
230
+ sim1 = DeltaSimulator()
231
+ sim1.set_energy_balance(river=80, wave=10, tidal=10)
232
+
233
+ for i in range(10):
234
+ sim1.step(50)
235
+ info = sim1.get_info()
236
+ print(f"์œ ํ˜•: {info['delta_type']}")
237
+ print(f"๋ฉด์ : {info['delta_area']:.0f}, ํญ: {info['delta_width']:.0f}m, ๊ธธ์ด: {info['delta_length']:.0f}m")
238
+
239
+ # ์‹œ๋‚˜๋ฆฌ์˜ค 2: ๋‚˜์ผํ˜• (ํŒŒ๋ž‘ ์šฐ์„ธ)
240
+ print("\n" + "=" * 50)
241
+ print("์‹œ๋‚˜๋ฆฌ์˜ค 2: ์›ํ˜ธ์ƒ ์‚ผ๊ฐ์ฃผ (๋‚˜์ผํ˜•)")
242
+ sim2 = DeltaSimulator()
243
+ sim2.set_energy_balance(river=30, wave=60, tidal=10)
244
+
245
+ for i in range(10):
246
+ sim2.step(50)
247
+ info = sim2.get_info()
248
+ print(f"์œ ํ˜•: {info['delta_type']}")
249
+ print(f"๋ฉด์ : {info['delta_area']:.0f}, ํญ: {info['delta_width']:.0f}m, ๊ธธ์ด: {info['delta_length']:.0f}m")
250
+
251
+ print("\n์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์™„๋ฃŒ!")
engine/river/meander.py ADDED
@@ -0,0 +1,299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Geo-Lab AI: ๊ณก๋ฅ˜ & ์šฐ๊ฐํ˜ธ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
3
+ ์ค‘๋ฅ˜ ํ•˜์ฒœ์˜ ์ธก๋ฐฉ ์นจ์‹์œผ๋กœ ๊ตฝ์ด์น˜๋Š” ํ•˜์ฒœ๊ณผ ์šฐ๊ฐํ˜ธ ํ˜•์„ฑ
4
+ """
5
+ import numpy as np
6
+ from dataclasses import dataclass, field
7
+ from typing import List, Tuple, Optional
8
+
9
+ import sys
10
+ import os
11
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
12
+
13
+ from engine.base import Terrain, Water
14
+ from engine.erosion import lateral_erosion, apply_erosion
15
+ from engine.deposition import apply_deposition
16
+
17
+
18
+ @dataclass
19
+ class MeanderSimulator:
20
+ """๊ณก๋ฅ˜ ํ•˜์ฒœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
21
+
22
+ ํ•ต์‹ฌ ์›๋ฆฌ:
23
+ 1. ์ธก๋ฐฉ ์นจ์‹ (Lateral Erosion) - ๋ฐ”๊นฅ์ชฝ(๊ณต๊ฒฉ์‚ฌ๋ฉด)
24
+ 2. ์ธก๋ฐฉ ํ‡ด์  (Point Bar) - ์•ˆ์ชฝ(ํ‡ด์ ์‚ฌ๋ฉด)
25
+ 3. ์œ ๋กœ ์ ˆ๋‹จ (Cutoff) - ์šฐ๊ฐํ˜ธ ํ˜•์„ฑ
26
+
27
+ ๊ฒฐ๊ณผ: ๊ตฝ์ด์น˜๋Š” ํ•˜์ฒœ, ์šฐ๊ฐํ˜ธ(Oxbow Lake)
28
+ """
29
+
30
+ # ์ง€ํ˜• ํฌ๊ธฐ
31
+ width: int = 150
32
+ height: int = 150
33
+
34
+ # ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํŒŒ๋ผ๋ฏธํ„ฐ
35
+ initial_sinuosity: float = 1.2 # ์ดˆ๊ธฐ ๊ตด๊ณก๋„
36
+ discharge: float = 50.0 # ์œ ๋Ÿ‰
37
+
38
+ # ์นจ์‹/ํ‡ด์  ๊ณ„์ˆ˜
39
+ k_lateral: float = 0.0003
40
+ k_deposition: float = 0.0002
41
+
42
+ # ์šฐ๊ฐํ˜ธ ํ˜•์„ฑ ์กฐ๊ฑด
43
+ cutoff_threshold: float = 10.0 # ์œ ๋กœ ๊ฐ„ ๊ฑฐ๋ฆฌ๊ฐ€ ์ด ์ดํ•˜๋ฉด ์ ˆ๋‹จ
44
+
45
+ # ๋‚ด๋ถ€ ์ƒํƒœ
46
+ terrain: Terrain = field(default=None)
47
+ water: Water = field(default=None)
48
+ channel_path: List[Tuple[int, int]] = field(default_factory=list)
49
+ oxbow_lakes: List[np.ndarray] = field(default_factory=list)
50
+ history: List[np.ndarray] = field(default_factory=list)
51
+ current_step: int = 0
52
+
53
+ def __post_init__(self):
54
+ self.reset()
55
+
56
+ def reset(self):
57
+ """์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ดˆ๊ธฐํ™”"""
58
+ self.terrain = Terrain(width=self.width, height=self.height)
59
+
60
+ # ํ‰ํƒ„ํ•œ ๋ฒ”๋žŒ์› (์•ฝ๊ฐ„์˜ ๊ฒฝ์‚ฌ)
61
+ for y in range(self.height):
62
+ self.terrain.elevation[y, :] = 100 - y * 0.2 # ์™„๋งŒํ•œ ๊ฒฝ์‚ฌ
63
+
64
+ # ์ดˆ๊ธฐ ๊ณก๋ฅ˜ ํ•˜์ฒœ ๊ฒฝ๋กœ ์ƒ์„ฑ
65
+ self._create_initial_channel()
66
+
67
+ # ์ˆ˜๋ฌธ ์ดˆ๊ธฐํ™”
68
+ self.water = Water(terrain=self.terrain)
69
+ self._update_water_from_channel()
70
+
71
+ self.oxbow_lakes = []
72
+ self.history = [self.terrain.elevation.copy()]
73
+ self.current_step = 0
74
+
75
+ def _create_initial_channel(self):
76
+ """์ดˆ๊ธฐ ์‚ฌ์ธํŒŒ ํ˜•ํƒœ์˜ ๊ณก๋ฅ˜ ํ•˜์ฒœ ์ƒ์„ฑ"""
77
+ self.channel_path = []
78
+
79
+ amplitude = self.width * 0.15 * self.initial_sinuosity
80
+ frequency = 3 # ๊ตฝ์ด ์ˆ˜
81
+
82
+ center = self.width // 2
83
+
84
+ for y in range(self.height):
85
+ # ์‚ฌ์ธํŒŒ ๊ณก์„ 
86
+ x = int(center + amplitude * np.sin(2 * np.pi * frequency * y / self.height))
87
+ x = max(5, min(self.width - 5, x))
88
+ self.channel_path.append((y, x))
89
+
90
+ # ํ•˜์ฒœ ์ฑ„๋„ ํŒŒ๊ธฐ (์ฃผ๋ณ€๋„ ์•ฝ๊ฐ„)
91
+ for dx in range(-3, 4):
92
+ nx = x + dx
93
+ if 0 <= nx < self.width:
94
+ depth = 5 * (1 - abs(dx) / 4)
95
+ self.terrain.elevation[y, nx] -= depth
96
+
97
+ def _update_water_from_channel(self):
98
+ """ํ•˜์ฒœ ๊ฒฝ๋กœ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ˆ˜๋ฌธ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ"""
99
+ self.water.discharge[:] = 0
100
+ self.water.velocity[:] = 0
101
+
102
+ for y, x in self.channel_path:
103
+ self.water.discharge[y, x] = self.discharge
104
+ self.water.velocity[y, x] = 2.0 # ๊ธฐ๋ณธ ์œ ์†
105
+
106
+ # ์ฃผ๋ณ€์œผ๋กœ ํ™•์‚ฐ
107
+ from scipy.ndimage import gaussian_filter
108
+ self.water.discharge = gaussian_filter(self.water.discharge, sigma=1)
109
+ self.water.velocity = gaussian_filter(self.water.velocity, sigma=1)
110
+
111
+ def step(self, n_steps: int = 1) -> np.ndarray:
112
+ """์‹œ๋ฎฌ๋ ˆ์ด์…˜ n์Šคํ… ์ง„ํ–‰"""
113
+ for _ in range(n_steps):
114
+ # 1. ์ธก๋ฐฉ ์นจ์‹ (๋ฐ”๊นฅ์ชฝ)
115
+ erosion = self._calculate_bank_erosion()
116
+ apply_erosion(self.terrain, erosion)
117
+
118
+ # 2. Point Bar ํ‡ด์  (์•ˆ์ชฝ)
119
+ deposition = self._calculate_point_bar_deposition()
120
+ apply_deposition(self.terrain, deposition)
121
+
122
+ # 3. ํ•˜์ฒœ ๊ฒฝ๋กœ ์—…๋ฐ์ดํŠธ (๊ฐ€์žฅ ๋‚ฎ์€ ๊ณณ์œผ๋กœ ์ด๋™)
123
+ self._update_channel_path()
124
+
125
+ # 4. ์šฐ๊ฐํ˜ธ ์ฒดํฌ
126
+ self._check_cutoff()
127
+
128
+ # 5. ์ˆ˜๋ฌธ ์—…๋ฐ์ดํŠธ
129
+ self._update_water_from_channel()
130
+
131
+ self.current_step += 1
132
+
133
+ if self.current_step % 10 == 0:
134
+ self.history.append(self.terrain.elevation.copy())
135
+
136
+ return self.terrain.elevation
137
+
138
+ def _calculate_bank_erosion(self) -> np.ndarray:
139
+ """๊ณต๊ฒฉ์‚ฌ๋ฉด(๋ฐ”๊นฅ์ชฝ) ์นจ์‹ ๊ณ„์‚ฐ"""
140
+ erosion = np.zeros((self.height, self.width))
141
+
142
+ for i in range(1, len(self.channel_path) - 1):
143
+ y, x = self.channel_path[i]
144
+ y_prev, x_prev = self.channel_path[i - 1]
145
+ y_next, x_next = self.channel_path[i + 1]
146
+
147
+ # ๊ณก๋ฅ  ๊ณ„์‚ฐ (๋ฐฉํ–ฅ ๋ณ€ํ™”)
148
+ dx1, dy1 = x - x_prev, y - y_prev
149
+ dx2, dy2 = x_next - x, y_next - y
150
+
151
+ # ์™ธ์ ์œผ๋กœ ํšŒ์ „ ๋ฐฉํ–ฅ ํŒ๋‹จ
152
+ cross = dx1 * dy2 - dy1 * dx2
153
+
154
+ # ๋ฐ”๊นฅ์ชฝ ๊ฒฐ์ •
155
+ if cross > 0: # ์˜ค๋ฅธ์ชฝ์œผ๋กœ ํšŒ์ „ โ†’ ์™ผ์ชฝ์ด ๋ฐ”๊นฅ
156
+ outer_x = x - 1
157
+ else: # ์™ผ์ชฝ์œผ๋กœ ํšŒ์ „ โ†’ ์˜ค๋ฅธ์ชฝ์ด ๋ฐ”๊นฅ
158
+ outer_x = x + 1
159
+
160
+ if 0 <= outer_x < self.width:
161
+ curvature = abs(cross) / (np.sqrt(dx1**2+dy1**2+0.1) * np.sqrt(dx2**2+dy2**2+0.1) + 0.1)
162
+ erosion[y, outer_x] = self.k_lateral * self.discharge * curvature
163
+
164
+ return erosion
165
+
166
+ def _calculate_point_bar_deposition(self) -> np.ndarray:
167
+ """ํ‡ด์ ์‚ฌ๋ฉด(์•ˆ์ชฝ) ํ‡ด์  ๊ณ„์‚ฐ"""
168
+ deposition = np.zeros((self.height, self.width))
169
+
170
+ for i in range(1, len(self.channel_path) - 1):
171
+ y, x = self.channel_path[i]
172
+ y_prev, x_prev = self.channel_path[i - 1]
173
+ y_next, x_next = self.channel_path[i + 1]
174
+
175
+ dx1, dy1 = x - x_prev, y - y_prev
176
+ dx2, dy2 = x_next - x, y_next - y
177
+ cross = dx1 * dy2 - dy1 * dx2
178
+
179
+ # ์•ˆ์ชฝ (๋ฐ”๊นฅ์ชฝ ๋ฐ˜๋Œ€)
180
+ if cross > 0:
181
+ inner_x = x + 1
182
+ else:
183
+ inner_x = x - 1
184
+
185
+ if 0 <= inner_x < self.width:
186
+ curvature = abs(cross) / (np.sqrt(dx1**2+dy1**2+0.1) * np.sqrt(dx2**2+dy2**2+0.1) + 0.1)
187
+ deposition[y, inner_x] = self.k_deposition * self.discharge * curvature
188
+
189
+ return deposition
190
+
191
+ def _update_channel_path(self):
192
+ """ํ•˜์ฒœ ๊ฒฝ๋กœ๋ฅผ ๊ฐ€์žฅ ๋‚ฎ์€ ์ง€์ ์œผ๋กœ ์ด๋™"""
193
+ new_path = [self.channel_path[0]] # ์‹œ์ž‘์  ์œ ์ง€
194
+
195
+ for i in range(1, len(self.channel_path) - 1):
196
+ y, x = self.channel_path[i]
197
+
198
+ # ์ฃผ๋ณ€ ์ค‘ ๊ฐ€์žฅ ๋‚ฎ์€ ๊ณณ ํƒ์ƒ‰
199
+ min_elev = self.terrain.elevation[y, x]
200
+ best_x = x
201
+
202
+ for dx in [-1, 0, 1]:
203
+ nx = x + dx
204
+ if 0 <= nx < self.width:
205
+ if self.terrain.elevation[y, nx] < min_elev:
206
+ min_elev = self.terrain.elevation[y, nx]
207
+ best_x = nx
208
+
209
+ new_path.append((y, best_x))
210
+
211
+ new_path.append(self.channel_path[-1]) # ๋์  ์œ ์ง€
212
+ self.channel_path = new_path
213
+
214
+ def _check_cutoff(self):
215
+ """์šฐ๊ฐํ˜ธ ํ˜•์„ฑ ์กฐ๊ฑด ์ฒดํฌ"""
216
+ # ๊ฐ€๊นŒ์šด ๋‘ ์œ ๋กœ ์ง€์  ์ฐพ๊ธฐ
217
+ for i in range(len(self.channel_path)):
218
+ for j in range(i + 20, len(self.channel_path)): # ์ตœ์†Œ 20์…€ ๋–จ์–ด์ง„ ๊ฒƒ๋งŒ
219
+ y1, x1 = self.channel_path[i]
220
+ y2, x2 = self.channel_path[j]
221
+
222
+ dist = np.sqrt((x1 - x2)**2 + (y1 - y2)**2)
223
+
224
+ if dist < self.cutoff_threshold:
225
+ # Cutoff ๋ฐœ์ƒ! ์šฐ๊ฐํ˜ธ ์ƒ์„ฑ
226
+ self._create_oxbow_lake(i, j)
227
+ return
228
+
229
+ def _create_oxbow_lake(self, start_idx: int, end_idx: int):
230
+ """์šฐ๊ฐํ˜ธ ์ƒ์„ฑ"""
231
+ # ๊ณ ๋ฆฝ๋  ๊ตฌ๊ฐ„ ์ถ”์ถœ
232
+ cutoff_section = self.channel_path[start_idx:end_idx]
233
+
234
+ # ์šฐ๊ฐํ˜ธ๋กœ ์ €์žฅ
235
+ oxbow = np.zeros((self.height, self.width), dtype=bool)
236
+ for y, x in cutoff_section:
237
+ oxbow[y, x] = True
238
+ self.oxbow_lakes.append(oxbow)
239
+
240
+ # ํ•˜์ฒœ ๊ฒฝ๋กœ ๋‹จ์ถ• (์ง์„ ์œผ๋กœ)
241
+ self.channel_path = (
242
+ self.channel_path[:start_idx+1] +
243
+ self.channel_path[end_idx:]
244
+ )
245
+
246
+ print(f"๐ŸŒŠ ์šฐ๊ฐํ˜ธ ํ˜•์„ฑ! (Step {self.current_step})")
247
+
248
+ def get_cross_section(self, y_position: int = None) -> Tuple[np.ndarray, np.ndarray]:
249
+ """ํŠน์ • ์œ„์น˜์˜ ๋‹จ๋ฉด๋„"""
250
+ if y_position is None:
251
+ y_position = self.height // 2
252
+
253
+ x = np.arange(self.width) * self.terrain.cell_size
254
+ z = self.terrain.elevation[y_position, :]
255
+
256
+ return x, z
257
+
258
+ def get_sinuosity(self) -> float:
259
+ """ํ˜„์žฌ ๊ตด๊ณก๋„ ๊ณ„์‚ฐ"""
260
+ if len(self.channel_path) < 2:
261
+ return 1.0
262
+
263
+ # ์‹ค์ œ ๊ฒฝ๋กœ ๊ธธ์ด
264
+ path_length = 0
265
+ for i in range(1, len(self.channel_path)):
266
+ y1, x1 = self.channel_path[i-1]
267
+ y2, x2 = self.channel_path[i]
268
+ path_length += np.sqrt((x2-x1)**2 + (y2-y1)**2)
269
+
270
+ # ์ง์„  ๊ฑฐ๋ฆฌ
271
+ y_start, x_start = self.channel_path[0]
272
+ y_end, x_end = self.channel_path[-1]
273
+ straight_length = np.sqrt((x_end-x_start)**2 + (y_end-y_start)**2) + 0.1
274
+
275
+ return path_length / straight_length
276
+
277
+ def get_info(self) -> dict:
278
+ """ํ˜„์žฌ ์ƒํƒœ ์ •๋ณด"""
279
+ return {
280
+ "step": self.current_step,
281
+ "sinuosity": self.get_sinuosity(),
282
+ "oxbow_lakes": len(self.oxbow_lakes),
283
+ "channel_length": len(self.channel_path)
284
+ }
285
+
286
+
287
+ if __name__ == "__main__":
288
+ sim = MeanderSimulator(initial_sinuosity=1.5)
289
+
290
+ print("๊ณก๋ฅ˜ ํ•˜์ฒœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹œ์ž‘")
291
+ print(f"์ดˆ๊ธฐ ๊ตด๊ณก๋„: {sim.get_sinuosity():.2f}")
292
+
293
+ for i in range(20):
294
+ sim.step(50)
295
+ info = sim.get_info()
296
+ print(f"Step {info['step']}: ๊ตด๊ณก๋„ {info['sinuosity']:.2f}, "
297
+ f"์šฐ๊ฐํ˜ธ {info['oxbow_lakes']}๊ฐœ")
298
+
299
+ print("์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์™„๋ฃŒ!")
engine/river/v_valley.py ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Geo-Lab AI: V์ž๊ณก ์‹œ๋ฎฌ๋ ˆ์ด์…˜
3
+ ์ƒ๋ฅ˜ ํ•˜์ฒœ์˜ ํ•˜๋ฐฉ ์นจ์‹์œผ๋กœ V์ž ๋ชจ์–‘ ๊ณจ์งœ๊ธฐ ํ˜•์„ฑ
4
+ """
5
+ import numpy as np
6
+ from dataclasses import dataclass, field
7
+ from typing import List, Tuple
8
+
9
+ import sys
10
+ import os
11
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
12
+
13
+ from engine.base import Terrain, Water, SimulationState
14
+ from engine.erosion import vertical_erosion, headward_erosion, mass_wasting, apply_erosion
15
+
16
+
17
+ @dataclass
18
+ class VValleySimulator:
19
+ """V์ž๊ณก ์‹œ๋ฎฌ๋ ˆ์ด์…˜
20
+
21
+ ํ•ต์‹ฌ ์›๋ฆฌ:
22
+ 1. ํ•˜๋ฐฉ ์นจ์‹ (Stream Power Law)
23
+ 2. ์‚ฌ๋ฉด ๋ถ•๊ดด (Mass Wasting)
24
+
25
+ ๊ฒฐ๊ณผ: V์ž ํ˜•ํƒœ์˜ ๊นŠ์€ ๊ณจ์งœ๊ธฐ
26
+ """
27
+
28
+ # ์ง€ํ˜• ํฌ๊ธฐ
29
+ width: int = 100
30
+ height: int = 100
31
+
32
+ # ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํŒŒ๋ผ๋ฏธํ„ฐ
33
+ rock_hardness: float = 0.5 # 0=๋ฌด๋ฆ„, 1=๋‹จ๋‹จํ•จ
34
+ slope_gradient: float = 0.1 # ์ดˆ๊ธฐ ๊ฒฝ์‚ฌ
35
+ initial_discharge: float = 10.0 # ์ดˆ๊ธฐ ์œ ๋Ÿ‰
36
+
37
+ # ์นจ์‹ ๊ณ„์ˆ˜
38
+ k_vertical: float = 0.0005 # ํ•˜๋ฐฉ ์นจ์‹ ๊ณ„์ˆ˜
39
+ k_headward: float = 0.0003 # ๋‘๋ถ€ ์นจ์‹ ๊ณ„์ˆ˜
40
+
41
+ # ๋‚ด๋ถ€ ์ƒํƒœ
42
+ terrain: Terrain = field(default=None)
43
+ water: Water = field(default=None)
44
+ history: List[np.ndarray] = field(default_factory=list)
45
+ current_step: int = 0
46
+
47
+ def __post_init__(self):
48
+ self.reset()
49
+
50
+ def reset(self):
51
+ """์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ดˆ๊ธฐํ™”"""
52
+ # ์ดˆ๊ธฐ ์ง€ํ˜•: ๋ถ์ชฝ์ด ๋†’์€ ๊ฒฝ์‚ฌ๋ฉด
53
+ self.terrain = Terrain(width=self.width, height=self.height)
54
+
55
+ # ๊ฒฝ์‚ฌ ์„ค์ •
56
+ for y in range(self.height):
57
+ base_elevation = 500 * (1 - y / self.height)
58
+ self.terrain.elevation[y, :] = base_elevation
59
+
60
+ # ์ค‘์•™์— ์ดˆ๊ธฐ ํ•˜์ฒœ ์ฑ„๋„ (์•ฝ๊ฐ„ ๋‚ฎ๊ฒŒ)
61
+ center = self.width // 2
62
+ channel_width = 3
63
+ for x in range(center - channel_width, center + channel_width):
64
+ if 0 <= x < self.width:
65
+ self.terrain.elevation[:, x] -= 10
66
+
67
+ # ์•”์„ ๊ฒฝ๋„ ์„ค์ •
68
+ self.terrain.rock_hardness[:] = self.rock_hardness
69
+
70
+ # ์ˆ˜๋ฌธ ์ดˆ๊ธฐํ™”
71
+ self.water = Water(terrain=self.terrain)
72
+ self.water.discharge[0, center] = self.initial_discharge # ์ƒ๋ฅ˜์—์„œ ์œ ์ž…
73
+ self.water.accumulate_flow()
74
+
75
+ self.history = [self.terrain.elevation.copy()]
76
+ self.current_step = 0
77
+
78
+ def step(self, n_steps: int = 1) -> np.ndarray:
79
+ """์‹œ๋ฎฌ๋ ˆ์ด์…˜ n์Šคํ… ์ง„ํ–‰"""
80
+ for _ in range(n_steps):
81
+ # 1. ์œ ๋Ÿ‰ ๊ณ„์‚ฐ
82
+ self.water.add_precipitation(rate=0.001)
83
+ self.water.accumulate_flow()
84
+
85
+ # 2. ํ•˜๋ฐฉ ์นจ์‹
86
+ v_erosion = vertical_erosion(
87
+ self.terrain, self.water,
88
+ k_erosion=self.k_vertical * (1 - self.rock_hardness)
89
+ )
90
+ apply_erosion(self.terrain, v_erosion)
91
+
92
+ # 3. ๋‘๋ถ€ ์นจ์‹
93
+ h_erosion = headward_erosion(
94
+ self.terrain, self.water,
95
+ k_headward=self.k_headward
96
+ )
97
+ apply_erosion(self.terrain, h_erosion)
98
+
99
+ # 4. ์‚ฌ๋ฉด ๋ถ•๊ดด (V์ž ํ˜•์„ฑ์˜ ํ•ต์‹ฌ!)
100
+ mass_change = mass_wasting(self.terrain)
101
+ self.terrain.elevation += mass_change
102
+
103
+ # 5. ํ๋ฆ„ ๋ฐฉํ–ฅ ์—…๋ฐ์ดํŠธ
104
+ self.water.flow_x, self.water.flow_y = self.terrain.get_flow_direction()
105
+
106
+ self.current_step += 1
107
+
108
+ # ํžˆ์Šคํ† ๋ฆฌ ์ €์žฅ (๋งค 10์Šคํ…)
109
+ if self.current_step % 10 == 0:
110
+ self.history.append(self.terrain.elevation.copy())
111
+
112
+ return self.terrain.elevation
113
+
114
+ def get_cross_section(self, y_position: int = None) -> Tuple[np.ndarray, np.ndarray]:
115
+ """ํŠน์ • ์œ„์น˜์˜ ๋‹จ๋ฉด๋„ ๋ฐ˜ํ™˜"""
116
+ if y_position is None:
117
+ y_position = self.height // 2
118
+
119
+ x = np.arange(self.width) * self.terrain.cell_size
120
+ z = self.terrain.elevation[y_position, :]
121
+
122
+ return x, z
123
+
124
+ def get_valley_depth(self) -> float:
125
+ """ํ˜„์žฌ V์ž๊ณก ๊นŠ์ด ์ธก์ •"""
126
+ center = self.width // 2
127
+ y_mid = self.height // 2
128
+
129
+ # ์ขŒ์šฐ ๊ณ ๋„ ํ‰๊ท  vs ์ค‘์•™ ๊ณ ๋„
130
+ left_elev = self.terrain.elevation[y_mid, center - 20]
131
+ right_elev = self.terrain.elevation[y_mid, center + 20]
132
+ center_elev = self.terrain.elevation[y_mid, center]
133
+
134
+ depth = (left_elev + right_elev) / 2 - center_elev
135
+ return max(0, depth)
136
+
137
+ def get_info(self) -> dict:
138
+ """ํ˜„์žฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ƒํƒœ ์ •๋ณด"""
139
+ return {
140
+ "step": self.current_step,
141
+ "valley_depth": self.get_valley_depth(),
142
+ "max_elevation": float(self.terrain.elevation.max()),
143
+ "min_elevation": float(self.terrain.elevation.min()),
144
+ "total_erosion": float(self.history[0].sum() - self.terrain.elevation.sum())
145
+ }
146
+
147
+
148
+ # ํ…Œ์ŠคํŠธ ์ฝ”๋“œ
149
+ if __name__ == "__main__":
150
+ sim = VValleySimulator(rock_hardness=0.3)
151
+
152
+ print("V์ž๊ณก ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹œ์ž‘")
153
+ print(f"์ดˆ๊ธฐ ๊นŠ์ด: {sim.get_valley_depth():.1f}m")
154
+
155
+ for i in range(10):
156
+ sim.step(100)
157
+ info = sim.get_info()
158
+ print(f"Step {info['step']}: ๊นŠ์ด {info['valley_depth']:.1f}m, "
159
+ f"์ด ์นจ์‹๋Ÿ‰ {info['total_erosion']:.0f}mยณ")
160
+
161
+ print("์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์™„๋ฃŒ!")
engine/script_engine.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import numpy as np
3
+ import math
4
+ from .grid import WorldGrid
5
+ from .fluids import HydroKernel
6
+ from .erosion_process import ErosionProcess
7
+
8
+ class ScriptExecutor:
9
+ """
10
+ ์‚ฌ์šฉ์ž ์ •์˜ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰ ์—”์ง„
11
+ ๋ณด์•ˆ์„ ์œ„ํ•ด ์ œํ•œ๋œ ํ™˜๊ฒฝ์—์„œ Python ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
12
+ """
13
+ def __init__(self, grid: WorldGrid):
14
+ self.grid = grid
15
+ self.hydro = HydroKernel(grid)
16
+ self.erosion = ErosionProcess(grid)
17
+
18
+ def execute(self, script: str, dt: float = 1.0, allowed_modules: list = ['numpy', 'math']):
19
+ """
20
+ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰
21
+
22
+ Args:
23
+ script: ์‹คํ–‰ํ•  Python ์ฝ”๋“œ ๋ฌธ์ž์—ด
24
+ dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ (Time Step)
25
+ allowed_modules: ํ—ˆ์šฉํ•  ๋ชจ๋“ˆ ๋ฆฌ์ŠคํŠธ (๊ธฐ๋ณธ: numpy, math)
26
+
27
+ Available Variables in Context:
28
+ - grid: WorldGrid ๊ฐ์ฒด
29
+ - elevation: grid.elevation (Numpy Array)
30
+ - bedrock: grid.bedrock
31
+ - sediment: grid.sediment
32
+ - water_depth: grid.water_depth
33
+ - dt: Delta Time
34
+ - np: numpy module
35
+ - math: math module
36
+ - hydro: HydroKernel ๊ฐ์ฒด
37
+ - erosion: ErosionProcess ๊ฐ์ฒด
38
+ """
39
+
40
+ # 1. ์‹คํ–‰ ์ปจํ…์ŠคํŠธ(Namespace) ์ค€๋น„
41
+ context = {
42
+ 'grid': self.grid,
43
+ 'elevation': self.grid.elevation,
44
+ 'bedrock': self.grid.bedrock,
45
+ 'sediment': self.grid.sediment,
46
+ 'water_depth': self.grid.water_depth,
47
+ 'dt': dt,
48
+ 'np': np,
49
+ 'math': math,
50
+ 'hydro': self.hydro,
51
+ 'erosion': self.erosion,
52
+ # Helper functions
53
+ 'max': max,
54
+ 'min': min,
55
+ 'abs': abs,
56
+ 'pow': pow,
57
+ 'print': print # ๋””๋ฒ„๊น…์šฉ
58
+ }
59
+
60
+ # 2. ๊ธˆ์ง€๋œ ํ‚ค์›Œ๋“œ ์ฒดํฌ (๊ธฐ๋ณธ์ ์ธ ๋ณด์•ˆ)
61
+ # ์™„๋ฒฝํ•œ ์ƒŒ๋“œ๋ฐ•์Šค๋Š” ์•„๋‹ˆ์ง€๋งŒ, ์‹ค์ˆ˜ ๋ฐฉ์ง€์šฉ
62
+ forbidden = ['import os', 'import sys', 'open(', 'exec(', 'eval(', '__import__']
63
+ for bad in forbidden:
64
+ if bad in script:
65
+ raise ValueError(f"๋ณด์•ˆ ๊ฒฝ๊ณ : ํ—ˆ์šฉ๋˜์ง€ ์•Š๋Š” ํ‚ค์›Œ๋“œ '{bad}'๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.")
66
+
67
+ # 3. ์ฝ”๋“œ ์‹คํ–‰
68
+ try:
69
+ exec(script, {"__builtins__": {}}, context)
70
+
71
+ # 4. ๋ณ€๊ฒฝ ์‚ฌํ•ญ ๋ฐ˜์˜ (Elevation์€ derived property์ง€๋งŒ, ์ง์ ‘ ์ˆ˜์ •ํ–ˆ์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ)
72
+ # ์‚ฌ์šฉ์ž๊ฐ€ elevation์„ ์ˆ˜์ •ํ–ˆ๋‹ค๋ฉด bedrock/sediment ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ชจํ˜ธํ•ด์ง.
73
+ # Grid ํด๋ž˜์Šค์˜ update_elevation()์€ bedrock+sediment -> elevation์ด๋ฏ€๋กœ,
74
+ # ์‚ฌ์šฉ์ž๊ฐ€ elevation์„ ์ˆ˜์ •ํ•˜๋ฉด ๋ฌด์‹œ๋  ์ˆ˜ ์žˆ์Œ.
75
+ # ๊ฐ€์ด๋“œ: "bedrock"์ด๋‚˜ "sediment"๋ฅผ ์ˆ˜์ •ํ•˜์„ธ์š”.
76
+ # ํ•˜์ง€๋งŒ ํŽธ์˜๋ฅผ ์œ„ํ•ด elevation์ด ๋ฐ”๋€Œ์—ˆ์œผ๋ฉด bedrock์— ๋ฐ˜์˜ํ•˜๋Š” ๋กœ์ง ์ถ”๊ฐ€
77
+
78
+ # ๋ณ€๊ฒฝ ์ „ elevation๊ณผ ๋น„๊ตํ•ด์•ผ ํ•˜๋‚˜?
79
+ # ์ผ๋‹จ grid.update_elevation()์„ ํ˜ธ์ถœํ•˜์—ฌ ๋™๊ธฐํ™”
80
+ # (๋งŒ์•ฝ ์‚ฌ์šฉ์ž๊ฐ€ bedrock์„ ๋ฐ”๊ฟจ๋‹ค๋ฉด ๋ฐ˜์˜๋จ)
81
+ self.grid.update_elevation()
82
+
83
+ return True, "์‹คํ–‰ ์„ฑ๊ณต"
84
+
85
+ except Exception as e:
86
+ return False, f"์‹คํ–‰ ์˜ค๋ฅ˜: {str(e)}"
87
+
engine/system.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from dataclasses import dataclass
3
+ from .grid import WorldGrid
4
+ from .fluids import HydroKernel
5
+ from .erosion_process import ErosionProcess
6
+ from .lateral_erosion import LateralErosionKernel
7
+ from .mass_movement import MassMovementKernel
8
+ from .climate import ClimateKernel
9
+ from .wave import WaveKernel
10
+ from .glacier import GlacierKernel
11
+ from .wind import WindKernel
12
+
13
+ class EarthSystem:
14
+ """
15
+ Project Genesis: Unified Earth System Engine
16
+
17
+ ํ†ตํ•ฉ ์ง€๊ตฌ ์‹œ์Šคํ…œ ์—”์ง„
18
+ - ๊ธฐํ›„(Climate) -> ์ˆ˜๋ฌธ(Hydro) -> ์ง€ํ˜•(Erosion/Tectonics) ์ƒํ˜ธ์ž‘์šฉ์„
19
+ ๋‹จ์ผ ๋ฃจํ”„(step) ๋‚ด์—์„œ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
20
+ """
21
+ def __init__(self, grid: WorldGrid):
22
+ self.grid = grid
23
+ self.time = 0.0
24
+
25
+ # Core Kernels
26
+ self.hydro = HydroKernel(self.grid)
27
+ self.erosion = ErosionProcess(self.grid)
28
+ self.lateral = LateralErosionKernel(self.grid, k=0.01)
29
+ self.mass_movement = MassMovementKernel(self.grid, friction_angle=35.0)
30
+ self.climate = ClimateKernel(self.grid)
31
+
32
+ # Phase 2 Kernels (Optional, enabled via settings)
33
+ self.wave = WaveKernel(self.grid)
34
+ self.glacier = GlacierKernel(self.grid)
35
+ self.wind = WindKernel(self.grid)
36
+
37
+ # State History (Optional for analysis)
38
+ self.history = []
39
+
40
+ def step(self, dt: float = 1.0, settings: dict = None):
41
+ """
42
+ ์‹œ์Šคํ…œ 1๋‹จ๊ณ„ ์ง„ํ–‰ (Time Step)
43
+
44
+ Process Chain:
45
+ 1. Tectonics (Endogenous): Uplift, subsidence based on scenario settings
46
+ 2. Climate (Exogenous): Generate precipitation
47
+ 3. Hydro (Physics): Route flow, calculate discharge/depth
48
+ 4. Morphodynamics (Response): Erode, transport, deposit
49
+ """
50
+ if settings is None:
51
+ settings = {}
52
+
53
+ # 1. Tectonics (์ง€๊ตฌ์กฐ ์šด๋™)
54
+ # Simple vertical uplift logic if provided
55
+ uplift_rate = settings.get('uplift_rate', 0.0)
56
+ tile_uplift = settings.get('uplift_mask', None)
57
+
58
+ if uplift_rate > 0:
59
+ if tile_uplift is not None:
60
+ self.grid.apply_uplift(tile_uplift * uplift_rate, dt)
61
+ else:
62
+ self.grid.apply_uplift(uplift_rate, dt)
63
+
64
+ # 2. Climate (๊ธฐํ›„)
65
+ # Generate precipitation map
66
+ # Default: Uniform rain + randomness
67
+ base_precip = settings.get('precipitation', 0.01)
68
+ precip_map = np.ones((self.grid.height, self.grid.width)) * base_precip
69
+
70
+ # Apply rain source if specified (e.g., river mouth)
71
+ rain_source = settings.get('rain_source', None) # (y, x, radius, amount)
72
+ if rain_source:
73
+ y, x, r, amount = rain_source
74
+ # Simplified box source for speed
75
+ y_min, y_max = max(0, int(y-r)), min(self.grid.height, int(y+r+1))
76
+ x_min, x_max = max(0, int(x-r)), min(self.grid.width, int(x+r+1))
77
+ precip_map[y_min:y_max, x_min:x_max] += amount
78
+
79
+ # 3. Hydro (์ˆ˜๋ฌธ)
80
+ # Route flow based on current topography + precip
81
+ discharge = self.hydro.route_flow_d8(precipitation=precip_map)
82
+
83
+ # Update grid state
84
+ self.grid.discharge = discharge
85
+ self.grid.water_depth = self.hydro.calculate_water_depth(discharge)
86
+
87
+ # 4. Erosion / Deposition (์ง€ํ˜• ๋ณ€ํ™”)
88
+ # Using the ErosionKernel (ErosionProcess wrapper)
89
+ # Need to update K, m, n parameters dynamically?
90
+ # For now, use instance defaults or allow overrides
91
+
92
+ # Execute Transport & Deposition
93
+ sediment_influx_map = None
94
+ sediment_source = settings.get('sediment_source', None) # (y, x, radius, amount)
95
+ if sediment_source:
96
+ y, x, r, amount = sediment_source
97
+ sediment_influx_map = np.zeros((self.grid.height, self.grid.width))
98
+ y_min, y_max = max(0, int(y-r)), min(self.grid.height, int(y+r+1))
99
+ x_min, x_max = max(0, int(x-r)), min(self.grid.width, int(x+r+1))
100
+ sediment_influx_map[y_min:y_max, x_min:x_max] += amount
101
+
102
+ self.erosion.simulate_transport(discharge, dt=dt, sediment_influx_map=sediment_influx_map)
103
+
104
+ # 5. Lateral Erosion (์ธก๋ฐฉ ์นจ์‹) - ๊ณก๋ฅ˜ ํ˜•์„ฑ
105
+ lateral_enabled = settings.get('lateral_erosion', True)
106
+ if lateral_enabled:
107
+ self.lateral.step(discharge, dt=dt)
108
+
109
+ # 6. Hillslope Diffusion (์‚ฌ๋ฉด ์•ˆ์ •ํ™”)
110
+ diff_rate = settings.get('diffusion_rate', 0.01)
111
+ if diff_rate > 0:
112
+ self.erosion.hillslope_diffusion(dt=dt * diff_rate)
113
+
114
+ # 7. Mass Movement (๋งค์Šค๋ฌด๋ธŒ๋จผํŠธ) - ์‚ฐ์‚ฌํƒœ
115
+ mass_movement_enabled = settings.get('mass_movement', True)
116
+ if mass_movement_enabled:
117
+ self.mass_movement.step(dt=dt)
118
+
119
+ # Update Time
120
+ self.time += dt
121
+
122
+ def get_state(self):
123
+ """ํ˜„์žฌ ์‹œ์Šค๏ฟฝ๏ฟฝ๏ฟฝ ์ƒํƒœ ๋ฐ˜ํ™˜"""
124
+ self.grid.update_elevation()
125
+ return {
126
+ 'elevation': self.grid.elevation.copy(),
127
+ 'water_depth': self.grid.water_depth.copy(),
128
+ 'discharge': self.grid.discharge.copy(),
129
+ 'sediment': self.grid.sediment.copy(),
130
+ 'time': self.time
131
+ }
engine/wave.py ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Wave Kernel (ํŒŒ๋ž‘ ์ปค๋„)
3
+
4
+ ํ•ด์•ˆ ์ง€ํ˜• ํ˜•์„ฑ ํ”„๋กœ์„ธ์Šค
5
+ - ํŒŒ๋ž‘ ์นจ์‹ (Wave Erosion): ํ•ด์‹์ ˆ๋ฒฝ ํ›„ํ‡ด
6
+ - ์—ฐ์•ˆ ํ‡ด์  (Coastal Deposition): ์‚ฌ์ฃผ, ์‚ฌ์ทจ ํ˜•์„ฑ
7
+ - ์—ฐ์•ˆ๋ฅ˜ (Longshore Drift): ์ธก๋ฐฉ ํ‡ด์ ๋ฌผ ์ด๋™
8
+
9
+ ํ•ต์‹ฌ ๊ณต์‹:
10
+ ์นจ์‹๋ฅ  E = K * H^2 * (1/R)
11
+ - H: ํŒŒ๊ณ  (Wave Height)
12
+ - R: ์•”์„ ์ €ํ•ญ๋ ฅ (Rock Resistance)
13
+ """
14
+
15
+ import numpy as np
16
+ from .grid import WorldGrid
17
+
18
+
19
+ class WaveKernel:
20
+ """
21
+ ํŒŒ๋ž‘ ์ปค๋„
22
+
23
+ ํ•ด์•ˆ์„ ์—์„œ์˜ ํŒŒ๋ž‘ ์ž‘์šฉ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜.
24
+ ์นจ์‹๊ณผ ํ‡ด์ ์„ ๋™์‹œ์— ์ฒ˜๋ฆฌ.
25
+ """
26
+
27
+ def __init__(self, grid: WorldGrid,
28
+ wave_height: float = 2.0, # m
29
+ wave_period: float = 8.0, # s
30
+ wave_direction: float = 0.0, # degrees from N
31
+ K_erosion: float = 0.001):
32
+ self.grid = grid
33
+ self.wave_height = wave_height
34
+ self.wave_period = wave_period
35
+ self.wave_direction = np.radians(wave_direction)
36
+ self.K = K_erosion
37
+
38
+ def identify_coastline(self) -> np.ndarray:
39
+ """
40
+ ํ•ด์•ˆ์„  ์‹๋ณ„
41
+
42
+ ์œก์ง€-๋ฐ”๋‹ค ๊ฒฝ๊ณ„์„ ์„ ์ฐพ์Œ
43
+
44
+ Returns:
45
+ coastline_mask: ํ•ด์•ˆ์„  ์…€ ๋งˆ์Šคํฌ
46
+ """
47
+ underwater = self.grid.is_underwater()
48
+
49
+ # ํ•ด์•ˆ์„  = ์œก์ง€์ธ๋ฐ ์ธ์ ‘ ์…€์— ๋ฐ”๋‹ค๊ฐ€ ์žˆ๋Š” ๊ณณ
50
+ h, w = self.grid.height, self.grid.width
51
+ coastline = np.zeros((h, w), dtype=bool)
52
+
53
+ for r in range(h):
54
+ for c in range(w):
55
+ if underwater[r, c]:
56
+ continue # ๋ฐ”๋‹ค๋Š” ํ•ด์•ˆ์„  ์•„๋‹˜
57
+
58
+ # ์ธ์ ‘ ์…€ ํ™•์ธ
59
+ for dr in [-1, 0, 1]:
60
+ for dc in [-1, 0, 1]:
61
+ if dr == 0 and dc == 0:
62
+ continue
63
+ nr, nc = r + dr, c + dc
64
+ if 0 <= nr < h and 0 <= nc < w:
65
+ if underwater[nr, nc]:
66
+ coastline[r, c] = True
67
+ break
68
+ if coastline[r, c]:
69
+ break
70
+
71
+ return coastline
72
+
73
+ def calculate_wave_energy(self) -> np.ndarray:
74
+ """
75
+ ํŒŒ๋ž‘ ์—๋„ˆ์ง€ ๋ถ„ํฌ ๊ณ„์‚ฐ
76
+
77
+ Returns:
78
+ energy: ๊ฐ ์…€์˜ ํŒŒ๋ž‘ ์—๋„ˆ์ง€
79
+ """
80
+ h, w = self.grid.height, self.grid.width
81
+
82
+ # ๊ธฐ๋ณธ ์—๋„ˆ์ง€ = H^2 (ํŒŒ๊ณ  ์ œ๊ณฑ์— ๋น„๋ก€)
83
+ base_energy = self.wave_height ** 2
84
+
85
+ # ์ˆ˜์‹ฌ์— ๋”ฐ๋ฅธ ๊ฐ์‡ 
86
+ # ๊นŠ์€ ๋ฐ”๋‹ค: ์—๋„ˆ์ง€ ์œ ์ง€, ์–•์€ ๊ณณ: ์—๋„ˆ์ง€ ์ง‘์ค‘ ํ›„ ์‡„ํŒŒ
87
+ sea_depth = np.maximum(0, self.grid.sea_level - self.grid.elevation)
88
+
89
+ # ์ฒœํ•ด ํšจ๊ณผ: ์ˆ˜์‹ฌ < ํŒŒ์žฅ/2 ์ผ ๋•Œ ์—๋„ˆ์ง€ ์ฆ๊ฐ€
90
+ wavelength = 1.56 * (self.wave_period ** 2) # ์‹ฌํ•ด ํŒŒ์žฅ ๊ทผ์‚ฌ
91
+ depth_factor = np.ones((h, w))
92
+
93
+ shallow = sea_depth < wavelength / 2
94
+ depth_factor[shallow] = 1.0 + 0.5 * (1 - sea_depth[shallow] / (wavelength / 2))
95
+
96
+ energy = base_energy * depth_factor
97
+
98
+ # ์œก์ง€๋Š” ์—๋„ˆ์ง€ 0
99
+ energy[~self.grid.is_underwater()] = 0
100
+
101
+ return energy
102
+
103
+ def erode_coast(self, coastline: np.ndarray,
104
+ wave_energy: np.ndarray,
105
+ rock_resistance: np.ndarray = None,
106
+ dt: float = 1.0) -> np.ndarray:
107
+ """
108
+ ํ•ด์•ˆ ์นจ์‹
109
+
110
+ Args:
111
+ coastline: ํ•ด์•ˆ์„  ๋งˆ์Šคํฌ
112
+ wave_energy: ํŒŒ๋ž‘ ์—๋„ˆ์ง€ ๋ฐฐ์—ด
113
+ rock_resistance: ์•”์„ ์ €ํ•ญ๋ ฅ (0~1, ๋†’์„์ˆ˜๋ก ์ €ํ•ญ)
114
+ dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
115
+
116
+ Returns:
117
+ erosion: ์นจ์‹๋Ÿ‰ ๋ฐฐ์—ด
118
+ """
119
+ h, w = self.grid.height, self.grid.width
120
+
121
+ if rock_resistance is None:
122
+ rock_resistance = np.ones((h, w)) * 0.5
123
+
124
+ erosion = np.zeros((h, w), dtype=np.float64)
125
+
126
+ # ํ•ด์•ˆ์„  ์…€์— ๋Œ€ํ•ด ์นจ์‹ ๊ณ„์‚ฐ
127
+ coast_coords = np.argwhere(coastline)
128
+
129
+ for r, c in coast_coords:
130
+ # ์ธ์ ‘ ๋ฐ”๋‹ค ์…€์˜ ํ‰๊ท  ์—๋„ˆ์ง€
131
+ adjacent_energy = 0
132
+ count = 0
133
+
134
+ for dr in [-1, 0, 1]:
135
+ for dc in [-1, 0, 1]:
136
+ if dr == 0 and dc == 0:
137
+ continue
138
+ nr, nc = r + dr, c + dc
139
+ if 0 <= nr < h and 0 <= nc < w:
140
+ if self.grid.is_underwater()[nr, nc]:
141
+ adjacent_energy += wave_energy[nr, nc]
142
+ count += 1
143
+
144
+ if count > 0:
145
+ avg_energy = adjacent_energy / count
146
+ # ์นจ์‹๋ฅ  = K * Energy / Resistance
147
+ resistance = rock_resistance[r, c]
148
+ erosion[r, c] = self.K * avg_energy * (1 - resistance) * dt
149
+
150
+ return erosion
151
+
152
+ def longshore_drift(self, coastline: np.ndarray,
153
+ sediment_available: np.ndarray,
154
+ dt: float = 1.0) -> np.ndarray:
155
+ """
156
+ ์—ฐ์•ˆ๋ฅ˜์— ์˜ํ•œ ํ‡ด์ ๋ฌผ ์ด๋™
157
+
158
+ Args:
159
+ coastline: ํ•ด์•ˆ์„  ๋งˆ์Šคํฌ
160
+ sediment_available: ์ด๋™ ๊ฐ€๋Šฅํ•œ ํ‡ด์ ๋ฌผ
161
+ dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
162
+
163
+ Returns:
164
+ change: ํ‡ด์ ๋ฌผ ๋ณ€ํ™”๋Ÿ‰
165
+ """
166
+ h, w = self.grid.height, self.grid.width
167
+ change = np.zeros((h, w), dtype=np.float64)
168
+
169
+ # ํŒŒ๋ž‘ ๋ฐฉํ–ฅ์— ๋”ฐ๋ฅธ ์—ฐ์•ˆ๋ฅ˜ ๋ฐฉํ–ฅ
170
+ # 0 = N, 90 = E, etc.
171
+ drift_dx = np.sin(self.wave_direction)
172
+ drift_dy = -np.cos(self.wave_direction) # Y์ถ• ๋ฐ˜์ „
173
+
174
+ coast_coords = np.argwhere(coastline)
175
+
176
+ for r, c in coast_coords:
177
+ available = sediment_available[r, c]
178
+ if available <= 0:
179
+ continue
180
+
181
+ # ์ด๋™๋Ÿ‰
182
+ move_amount = min(available * 0.1 * dt, available)
183
+
184
+ # ๋ชฉํ‘œ ์…€ (์—ฐ์•ˆ๋ฅ˜ ๋ฐฉํ–ฅ)
185
+ tr = int(r + drift_dy)
186
+ tc = int(c + drift_dx)
187
+
188
+ if 0 <= tr < h and 0 <= tc < w:
189
+ if coastline[tr, tc] or self.grid.is_underwater()[tr, tc]:
190
+ change[r, c] -= move_amount
191
+ change[tr, tc] += move_amount
192
+
193
+ return change
194
+
195
+ def step(self, dt: float = 1.0,
196
+ rock_resistance: np.ndarray = None) -> dict:
197
+ """
198
+ 1๋‹จ๊ณ„ ํŒŒ๋ž‘ ์ž‘์šฉ ์‹คํ–‰
199
+
200
+ Args:
201
+ dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
202
+ rock_resistance: ์•”์„ ์ €ํ•ญ๋ ฅ ๋ฐฐ์—ด
203
+
204
+ Returns:
205
+ result: ์นจ์‹/ํ‡ด์  ๊ฒฐ๊ณผ
206
+ """
207
+ # 1. ํ•ด์•ˆ์„  ์‹๋ณ„
208
+ coastline = self.identify_coastline()
209
+
210
+ if not np.any(coastline):
211
+ return {'erosion': np.zeros_like(self.grid.elevation),
212
+ 'drift': np.zeros_like(self.grid.elevation)}
213
+
214
+ # 2. ํŒŒ๋ž‘ ์—๋„ˆ์ง€ ๊ณ„์‚ฐ
215
+ wave_energy = self.calculate_wave_energy()
216
+
217
+ # 3. ํ•ด์•ˆ ์นจ์‹
218
+ erosion = self.erode_coast(coastline, wave_energy, rock_resistance, dt)
219
+
220
+ # ์นจ์‹ ์ ์šฉ (bedrock ๊ฐ์†Œ)
221
+ self.grid.bedrock -= erosion
222
+
223
+ # ์นจ์‹๋œ ์–‘์€ ํ‡ด์ ๋ฌผ๋กœ ๋ณ€ํ™˜
224
+ self.grid.sediment += erosion * 0.8 # ์ผ๋ถ€ ์œ ์‹ค
225
+
226
+ # 4. ์—ฐ์•ˆ๋ฅ˜ (Longshore Drift)
227
+ drift = self.longshore_drift(coastline, self.grid.sediment, dt)
228
+ self.grid.sediment += drift
229
+
230
+ # ๊ณ ๋„ ๋™๊ธฐํ™”
231
+ self.grid.update_elevation()
232
+
233
+ return {
234
+ 'erosion': erosion,
235
+ 'drift': drift
236
+ }
engine/wind.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Wind Kernel (๋ฐ”๋žŒ ์ปค๋„)
3
+
4
+ ํ’์„ฑ ์ง€ํ˜• ํ˜•์„ฑ ํ”„๋กœ์„ธ์Šค
5
+ - ํ’์‹ (Deflation): ๋ฏธ์„ธ ์ž…์ž ์ œ๊ฑฐ
6
+ - ๋งˆ์‹ (Abrasion): ๋ชจ๋ž˜ ์ถฉ๋Œ์— ์˜ํ•œ ์นจ์‹
7
+ - ํ’์  (Aeolian Deposition): ์‚ฌ๊ตฌ ํ˜•์„ฑ
8
+
9
+ ํ•ต์‹ฌ:
10
+ - ๋ฐ”๋žŒ ์†๋„์— ๋น„๋ก€ํ•œ ์šด๋ฐ˜๋ ฅ
11
+ - ์ž…์ž ํฌ๊ธฐ์— ๋”ฐ๋ฅธ ์„ ํƒ์  ์šด๋ฐ˜
12
+ """
13
+
14
+ import numpy as np
15
+ from .grid import WorldGrid
16
+
17
+
18
+ class WindKernel:
19
+ """
20
+ ๋ฐ”๋žŒ ์ปค๋„
21
+
22
+ ๊ฑด์กฐ ์ง€์—ญ์—์„œ์˜ ํ’์‹๊ณผ ํ’์ ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜.
23
+ """
24
+
25
+ def __init__(self, grid: WorldGrid,
26
+ wind_speed: float = 10.0, # m/s
27
+ wind_direction: float = 45.0, # degrees from N
28
+ K_erosion: float = 0.0001,
29
+ sand_threshold: float = 0.1): # ์‚ฌ๊ตฌ ํ˜•์„ฑ ์ž„๊ณ„ ํ‡ด์ ๋Ÿ‰
30
+ self.grid = grid
31
+ self.wind_speed = wind_speed
32
+ self.wind_direction = np.radians(wind_direction)
33
+ self.K = K_erosion
34
+ self.sand_threshold = sand_threshold
35
+
36
+ def get_wind_vector(self) -> tuple:
37
+ """
38
+ ๋ฐ”๋žŒ ๋ฐฉํ–ฅ ๋ฒกํ„ฐ ๋ฐ˜ํ™˜
39
+
40
+ Returns:
41
+ (dy, dx): ๋ฐ”๋žŒ ๋ฐฉํ–ฅ ๋‹จ์œ„ ๋ฒกํ„ฐ
42
+ """
43
+ dx = np.sin(self.wind_direction)
44
+ dy = -np.cos(self.wind_direction) # Y์ถ• ๋ฐ˜์ „ (ํ™”๋ฉด ์ขŒํ‘œ๊ณ„)
45
+ return dy, dx
46
+
47
+ def calculate_transport_capacity(self,
48
+ vegetation_cover: np.ndarray = None) -> np.ndarray:
49
+ """
50
+ ๋ชจ๋ž˜ ์šด๋ฐ˜๋ ฅ ๊ณ„์‚ฐ
51
+
52
+ Args:
53
+ vegetation_cover: ์‹์ƒ ํ”ผ๋ณต๋ฅ  (0~1, ๋†’์œผ๋ฉด ์šด๋ฐ˜๋ ฅ ๊ฐ์†Œ)
54
+
55
+ Returns:
56
+ capacity: ์šด๋ฐ˜๋ ฅ ๋ฐฐ์—ด
57
+ """
58
+ h, w = self.grid.height, self.grid.width
59
+
60
+ # ๊ธฐ๋ณธ ์šด๋ฐ˜๋ ฅ = ํ’์†^3 (๊ฒฝํ—˜์‹)
61
+ base_capacity = (self.wind_speed ** 3) * self.K
62
+
63
+ capacity = np.ones((h, w)) * base_capacity
64
+
65
+ # ์‹์ƒ ํšจ๊ณผ (์žˆ์œผ๋ฉด ์šด๋ฐ˜๋ ฅ ๊ฐ์†Œ)
66
+ if vegetation_cover is not None:
67
+ capacity *= (1 - vegetation_cover)
68
+
69
+ # ๊ฒฝ์‚ฌ ํšจ๊ณผ (๋ฐ”๋žŒ๋ฐ›์ด vs ๋ฐ”๋žŒ๊ทธ๋Š˜)
70
+ slope, aspect = self.grid.get_gradient()
71
+
72
+ # ๋ฐ”๋žŒ๋ฐ›์ด = ํ’ํ–ฅ๊ณผ ๋ฐ˜๋Œ€ ๊ฒฝ์‚ฌ๋ฉด โ†’ ๊ฐ์† โ†’ ํ‡ด์ 
73
+ # ๋ฐ”๋žŒ๊ทธ๋Š˜ = ํ’ํ–ฅ๊ณผ ๊ฐ™์€ ๊ฒฝ์‚ฌ๋ฉด โ†’ ๊ฐ€์† โ†’ ์นจ์‹
74
+ dy, dx = self.get_wind_vector()
75
+
76
+ # ๊ฒฝ์‚ฌ๋ฉด์˜ ๋…ธ์ถœ๋„ (๋ฐ”๋žŒ๊ณผ ๊ฒฝ์‚ฌ ๋ฐฉํ–ฅ์˜ ๋‚ด์ )
77
+ exposure = dx * np.gradient(self.grid.elevation, axis=1) + \
78
+ dy * np.gradient(self.grid.elevation, axis=0)
79
+
80
+ # ๋…ธ์ถœ๋„์— ๋”ฐ๋ฅธ ์šด๋ฐ˜๋ ฅ ์กฐ์ •
81
+ capacity *= (1 + 0.5 * np.clip(exposure, -1, 1))
82
+
83
+ # ํ•ด์ˆ˜๋ฉด ์•„๋ž˜๋Š” 0
84
+ capacity[self.grid.is_underwater()] = 0
85
+
86
+ return np.maximum(capacity, 0)
87
+
88
+ def deflation(self, capacity: np.ndarray, dt: float = 1.0) -> np.ndarray:
89
+ """
90
+ ํ’์‹ (Deflation) - ๋ฏธ์„ธ ์ž…์ž ์ œ๊ฑฐ
91
+
92
+ Args:
93
+ capacity: ์šด๋ฐ˜๋ ฅ ๋ฐฐ์—ด
94
+ dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
95
+
96
+ Returns:
97
+ erosion: ์นจ์‹๋Ÿ‰ ๋ฐฐ์—ด
98
+ """
99
+ h, w = self.grid.height, self.grid.width
100
+
101
+ # ์นจ์‹๋Ÿ‰ = ์šด๋ฐ˜๋ ฅ * dt (ํ‡ด์ ์ธต์—์„œ๋งŒ)
102
+ available = self.grid.sediment
103
+ erosion = np.minimum(capacity * dt, available)
104
+
105
+ # ํ‡ด์ ์ธต ๊ฐ์†Œ
106
+ self.grid.sediment -= erosion
107
+
108
+ return erosion
109
+
110
+ def transport_and_deposit(self,
111
+ eroded_material: np.ndarray,
112
+ capacity: np.ndarray,
113
+ dt: float = 1.0) -> np.ndarray:
114
+ """
115
+ ํ’์  (Aeolian Deposition) - ์‚ฌ๊ตฌ ํ˜•์„ฑ
116
+
117
+ Args:
118
+ eroded_material: ์นจ์‹๋œ ๋ฌผ์งˆ๋Ÿ‰
119
+ capacity: ์šด๋ฐ˜๋ ฅ ๋ฐฐ์—ด
120
+ dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
121
+
122
+ Returns:
123
+ deposition: ํ‡ด์ ๋Ÿ‰ ๋ฐฐ์—ด
124
+ """
125
+ h, w = self.grid.height, self.grid.width
126
+
127
+ dy, dx = self.get_wind_vector()
128
+
129
+ # ๋ฌผ์งˆ ์ด๋™
130
+ deposition = np.zeros((h, w), dtype=np.float64)
131
+
132
+ for r in range(h):
133
+ for c in range(w):
134
+ if eroded_material[r, c] <= 0:
135
+ continue
136
+
137
+ # ๋ฐ”๋žŒ ๋ฐฉํ–ฅ์œผ๋กœ ์ด๋™
138
+ tr = int(r + dy * 2) # 2์…€ ์ด๋™
139
+ tc = int(c + dx * 2)
140
+
141
+ if not (0 <= tr < h and 0 <= tc < w):
142
+ continue
143
+
144
+ # ๋ชฉํ‘œ ์ง€์ ์˜ ์šด๋ฐ˜๋ ฅ ํ™•์ธ
145
+ if capacity[tr, tc] < capacity[r, c]:
146
+ # ์šด๋ฐ˜๋ ฅ ๊ฐ์†Œ โ†’ ํ‡ด์ 
147
+ deposit_amount = eroded_material[r, c] * (1 - capacity[tr, tc] / capacity[r, c])
148
+ deposition[tr, tc] += deposit_amount
149
+ else:
150
+ # ๊ณ„์† ์šด๋ฐ˜ (๋‹ค์Œ ์…€๋กœ)
151
+ # ๊ฐ„๋‹จํžˆ ์œ„ํ•ด ์ผ๋ถ€๋งŒ ํ‡ด์ 
152
+ deposition[tr, tc] += eroded_material[r, c] * 0.1
153
+
154
+ # ํ‡ด์  ์ ์šฉ
155
+ self.grid.add_sediment(deposition)
156
+
157
+ return deposition
158
+
159
+ def form_barchan(self, iteration: int = 5):
160
+ """
161
+ ๋ฐ”๋ฅดํ•œ ์‚ฌ๊ตฌ ํ˜•์„ฑ (๋ฐ˜๋ณต ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
162
+
163
+ ๋ฐ”๋žŒ๋ฐ›์ด: ์™„๊ฒฝ์‚ฌ, ๋ฐ”๋žŒ๊ทธ๋Š˜: ๊ธ‰๊ฒฝ์‚ฌ (Slip Face)
164
+
165
+ Args:
166
+ iteration: ํ˜•ํƒœ ๋‹ค๋“ฌ๊ธฐ ๋ฐ˜๋ณต ํšŸ์ˆ˜
167
+ """
168
+ h, w = self.grid.height, self.grid.width
169
+ dy, dx = self.get_wind_vector()
170
+
171
+ # ์‚ฌ๊ตฌ ํ›„๋ณด (ํ‡ด์ ๋ฌผ ๋งŽ์€ ๊ณณ)
172
+ dune_mask = self.grid.sediment > self.sand_threshold
173
+
174
+ for _ in range(iteration):
175
+ # ๋ฐ”๋žŒ๋ฐ›์ด ์ชฝ ์™„๋งŒํ•˜๊ฒŒ
176
+ for r in range(1, h - 1):
177
+ for c in range(1, w - 1):
178
+ if not dune_mask[r, c]:
179
+ continue
180
+
181
+ # ๋ฐ”๋žŒ๋ฐ›์ด ์ด์›ƒ
182
+ wr, wc = int(r - dy), int(c - dx)
183
+ if 0 <= wr < h and 0 <= wc < w:
184
+ # ๊ฒฝ์‚ฌ ์™„ํ™”
185
+ avg = (self.grid.sediment[r, c] + self.grid.sediment[wr, wc]) / 2
186
+ self.grid.sediment[r, c] = self.grid.sediment[r, c] * 0.9 + avg * 0.1
187
+
188
+ self.grid.update_elevation()
189
+
190
+ def step(self, vegetation_cover: np.ndarray = None,
191
+ dt: float = 1.0) -> dict:
192
+ """
193
+ 1๋‹จ๊ณ„ ๋ฐ”๋žŒ ์ž‘์šฉ ์‹คํ–‰
194
+
195
+ Args:
196
+ vegetation_cover: ์‹์ƒ ํ”ผ๋ณต๋ฅ 
197
+ dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
198
+
199
+ Returns:
200
+ result: ์นจ์‹/ํ‡ด์  ๊ฒฐ๊ณผ
201
+ """
202
+ # 1. ์šด๋ฐ˜๋ ฅ ๊ณ„์‚ฐ
203
+ capacity = self.calculate_transport_capacity(vegetation_cover)
204
+
205
+ # 2. ํ’์‹
206
+ erosion = self.deflation(capacity, dt)
207
+
208
+ # 3. ์ด๋™ ๋ฐ ํ‡ด์ 
209
+ deposition = self.transport_and_deposit(erosion, capacity, dt)
210
+
211
+ return {
212
+ 'erosion': erosion,
213
+ 'deposition': deposition,
214
+ 'capacity': capacity
215
+ }
image list.txt ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ๐ŸŒŠ [Chapter 1. ํ•˜์ฒœ ์ง€ํ˜• (River Landforms)]
2
+ ํ•„์ˆ˜ ์ด๋ฏธ์ง€ (5์žฅ)
3
+
4
+ 1. V์ž๊ณก๊ณผ ํ˜‘๊ณก (V-shaped Valley)
5
+ Filename: v_shaped_valley_diagram.jpg
6
+ Point: ๊ฐ•๋ฐ”๋‹ฅ์ด ๊นŠ๊ฒŒ ํŒŒ์ด๊ณ  ์–‘์˜†์ด ๊ฒฝ์‚ฌ์ง„ ๋‹จ๋ฉด๋„๊ฐ€ ํฌํ•จ๋œ ๊ทธ๋ฆผ.
7
+
8
+ 2. ์„ ์ƒ์ง€ (Alluvial Fan)
9
+ Filename: alluvial_fan_structure.jpg
10
+ Point: ๋ถ€์ฑ„๊ผด ๋ชจ์–‘์˜ ํ‰๋ฉด๋„ + ๋•…์†(์„ ์ •/์„ ์•™/์„ ๋‹จ) ์ž…์ž ํฌ๊ธฐ๊ฐ€ ๋ฌ˜์‚ฌ๋œ ๊ทธ๋ฆผ.
11
+
12
+ 3. ๊ณก๋ฅ˜ ํ•˜์ฒœ ๋‹จ๋ฉด (Meander Cross-section)
13
+ Filename: meander_cross_section.jpg
14
+ Point: ๊ณต๊ฒฉ์‚ฌ๋ฉด(๊นŽ์ž„)์€ ์ ˆ๋ฒฝ, ํ‡ด์ ์‚ฌ๋ฉด(์Œ“์ž„)์€ ์™„๋งŒํ•˜๊ฒŒ ๋น„๋Œ€์นญ์œผ๋กœ ๊ทธ๋ ค์ง„ ๋‹จ๋ฉด๋„. (๊ฐ€์žฅ ์ค‘์š”!)
15
+
16
+ 4. ํ•˜์•ˆ๋‹จ๊ตฌ (River Terrace)
17
+ Filename: river_terrace_formation.jpg
18
+ Point: ๊ฐ• ์˜†์— ๊ณ„๋‹จ์‹์œผ๋กœ ํ‰ํ‰ํ•œ ๋•…์ด ์ธต์ธต์ด ์žˆ๋Š” ๊ทธ๋ฆผ.
19
+
20
+ 5. ์‚ผ๊ฐ์ฃผ ์œ ํ˜• (Delta Types)
21
+ Filename: delta_classification.jpg
22
+ Point: ์ƒˆ๋ฐœ ๋ชจ์–‘(๋ฏธ์‹œ์‹œํ”ผ), ๋ถ€์ฑ„ ๋ชจ์–‘(๋‚˜์ผ), ์ฒจ๊ฐ์ƒ(ํ‹ฐ๋ฒ ๋ฅด) 3๊ฐ€์ง€๊ฐ€ ๋น„๊ต๋œ ๊ทธ๋ฆผ.
23
+
24
+ ---
25
+
26
+ ๐Ÿ–๏ธ [Chapter 2. ํ•ด์•ˆ ์ง€ํ˜• (Coastal Landforms)]
27
+ ํ•„์ˆ˜ ์ด๋ฏธ์ง€ (4์žฅ)
28
+
29
+ 1. ํ•ด์‹์• ์™€ ํŒŒ์‹๋Œ€ (Sea Cliff & Wave-cut Platform)
30
+ Filename: sea_cliff_erosion.jpg
31
+ Point: ์ ˆ๋ฒฝ ๋ฐ‘์ด ํŒŒ์—ฌ ์žˆ๊ณ (Notch), ์ ˆ๋ฒฝ ์•ž๋ฐ”๋‹ค ๋ฐ”๋‹ฅ์ด ํ‰ํ‰ํ•œ(Platform) ๊ทธ๋ฆผ.
32
+
33
+ 2. ์‹œ์Šคํƒ ํ˜•์„ฑ ๊ณผ์ • (Sea Stack Evolution)
34
+ Filename: sea_stack_formation.jpg
35
+ Point: ๋™๊ตด โ†’ ์•„์น˜ โ†’ ์‹œ์Šคํƒ โ†’ ์‹œ์Šคํ…€ํ”„ ์ˆœ์„œ๊ฐ€ ํ•œ ์žฅ์— ๊ทธ๋ ค์ง„ ๊ทธ๋ฆผ.
36
+
37
+ 3. ํ•ด์•ˆ ํ‡ด์  ์ง€ํ˜• (Coastal Deposition)
38
+ Filename: sand_spit_bar_tombolo.jpg
39
+ Point: ์‚ฌ์ทจ(Spit), ์‚ฌ์ฃผ(Bar), ์œก๊ณ„์‚ฌ์ฃผ(Tombolo), ์„ํ˜ธ(Lagoon)๊ฐ€ ๊ทธ๋ ค์ง„ ์ง€๋„ํ˜• ๊ทธ๋ฆผ.
40
+
41
+ 4. ๊ฐฏ๋ฒŒ ๋‹จ๋ฉด (Mudflat Profile)
42
+ Filename: tidal_flat_zonation.jpg
43
+ Point: ๋ฐ€๋ฌผ/์ฐ๋ฌผ ์ˆ˜์œ„ ํ‘œ์‹œ(High/Low tide)์™€ ๊ฐฏ๋ฒŒ์˜ ๊ฒฝ์‚ฌ๊ฐ€ ๊ทธ๋ ค์ง„ ๋‹จ๋ฉด๋„.
44
+
45
+ ---
46
+
47
+ ๐Ÿฆ‡ [Chapter 3. ์นด๋ฅด์ŠคํŠธ ์ง€ํ˜• (Karst Landforms)]
48
+ ํ•„์ˆ˜ ์ด๋ฏธ์ง€ (4์žฅ)
49
+
50
+ 1. ํƒ„์‚ฐ์นผ์Š˜ ์šฉ์‹ ๋ฐ˜์‘ (Chemical Weathering Reaction)
51
+ Filename: chemical_weathering_reaction.png
52
+ Point: CaCOโ‚ƒ + Hโ‚‚O + COโ‚‚ โ†” Ca(HCOโ‚ƒ)โ‚‚ ํ™”ํ•™์‹ ๋ฐ ๋ชจ์‹๋„.
53
+
54
+ 2. ๋Œ๋ฆฌ๋„ค-์šฐ๋ฐœ๋ผ-ํด๋ฆฌ์— ๋ณ€ํ™” (Doline Progression)
55
+ Filename: doline_uvala_polje_progression.jpg
56
+ Point: ๋Œ๋ฆฌ๋„ค์—์„œ ํด๋ฆฌ์—๋กœ ์ปค์ง€๋Š” ๋‹จ๊ณ„๋ณ„ ์ง€๋„.
57
+
58
+ 3. ์„ํšŒ ๋™๊ตด ๋‚ด๋ถ€ ๊ตฌ์กฐ (Cave Interior)
59
+ Filename: cave_interior_structure.jpg
60
+ Point: ์ข…์œ ์„, ์„์ˆœ, ์„์ฃผ๊ฐ€ ์žˆ๋Š” ๋™๊ตด ๋‚ด๋ถ€ ๋‹จ๋ฉด๋„.
61
+
62
+ 4. ํƒ‘ ์นด๋ฅด์ŠคํŠธ (Tower Karst)
63
+ Filename: tower_karst_landscape.jpg
64
+ Point: ํƒ‘ ์นด๋ฅด์ŠคํŠธ์˜ ์ง€ํ˜•์  ํŠน์ง• (ํ•˜๋กฑ๋ฒ ์ด, ๊ตฌ์ด๋ฆฐ ์Šคํƒ€์ผ).
65
+
66
+ ---
67
+
68
+ ๐ŸŒ‹ [Chapter 4. ํ™”์‚ฐ ์ง€ํ˜• (Volcanic Landforms)]
69
+ ํ•„์ˆ˜ ์ด๋ฏธ์ง€ (4์žฅ)
70
+
71
+ 1. ํ™”์‚ฐ ํ˜•ํƒœ ๋น„๊ต (Volcano Shape by Viscosity)
72
+ Filename: volcano_shape_viscosity.png
73
+ Point: ์ˆœ์ƒ vs ์„ฑ์ธต vs ์ข…์ƒ ํ™”์‚ฐ์˜ ๋‹จ๋ฉด ๋น„๊ต๋„.
74
+
75
+ 2. ์นผ๋ฐ๋ผ ํ˜•์„ฑ ๊ณผ์ • (Caldera Formation)
76
+ Filename: caldera_formation_steps.gif
77
+ Point: ๋งˆ๊ทธ๋งˆ ๋ฐฉ ๋น„์›€ โ†’ ๋ถ•๊ดด โ†’ ํ˜ธ์ˆ˜ ํ˜•์„ฑ 3๋‹จ๊ณ„.
78
+
79
+ 3. ์ฃผ์ƒ์ ˆ๋ฆฌ (Columnar Jointing)
80
+ Filename: columnar_jointing_hex.jpg
81
+ Point: ์ฃผ์ƒ์ ˆ๋ฆฌ์˜ ์œก๊ฐํ˜• ๋‹จ๋ฉด ๋ฐ ์ธก๋ฉด ๊ธฐ๋‘ฅ ๊ตฌ์กฐ.
82
+
83
+ 4. ํ•œ๊ตญ ํ™”์‚ฐ ์ง€ํ˜• ์ง€๋„ (Korea Volcanic Map)
84
+ Filename: korea_volcanic_map.png
85
+ Point: ๋ฐฑ๋‘์‚ฐ, ์ œ์ฃผ๋„, ์šธ๋ฆ‰๋„, ์ฒ ์› ์šฉ์•”๋Œ€์ง€ ์œ„์น˜ ์ง€๋„.
86
+
87
+ ---
88
+
89
+ โ„๏ธ [Chapter 5. ๋น™ํ•˜ ์ง€ํ˜• (Glacial Landforms)]
90
+ ํ•„์ˆ˜ ์ด๋ฏธ์ง€ (4์žฅ)
91
+
92
+ 1. V์ž๊ณก์—์„œ U์ž๊ณก์œผ๋กœ ๋ณ€ํ˜• (Valley Transformation)
93
+ Filename: valley_transformation_V_to_U.gif
94
+ Point: V์ž๊ณก์—์„œ U์ž๊ณก์œผ๋กœ ๋ณ€ํ˜•๋˜๋Š” 3D ๋ชจ๋ธ.
95
+
96
+ 2. ํ”ผ์˜ค๋ฅด ๋‹จ๋ฉด (Fjord Cross-section)
97
+ Filename: fjord_cross_section.jpg
98
+ Point: ๋ฐ”๋‹ท๋ฌผ์— ์ž ๊ธด U์ž๊ณก ๋‹จ๋ฉด๋„.
99
+
100
+ 3. ๋น™ํ‡ด์„ ๋ถ„๊ธ‰ ๋ถˆ๋Ÿ‰ (Glacial Till Unsorted)
101
+ Filename: glacial_till_unsorted.png
102
+ Point: ๋น™ํ‡ด์„์˜ ๋’ค์„ž์ธ ์ž๊ฐˆ ๋‹จ๋ฉด - ๋ถ„๊ธ‰ ๋ถˆ๋Ÿ‰ ์˜ˆ์‹œ.
103
+
104
+ 4. ๋“œ๋Ÿผ๋ฆฐ๊ณผ ๋น™ํ•˜ ์ด๋™ ๋ฐฉํ–ฅ (Drumlin Ice Flow)
105
+ Filename: drumlin_ice_flow.jpg
106
+ Point: ๋“œ๋Ÿผ๋ฆฐ์˜ ํ˜•ํƒœ์™€ ๋น™ํ•˜ ์ด๋™ ๋ฐฉํ–ฅ ๊ด€๊ณ„๋„.
107
+
108
+ ---
109
+
110
+ ๐Ÿœ๏ธ [Chapter 6. ๊ฑด์กฐ ์ง€ํ˜• (Arid Landforms)]
111
+ ํ•„์ˆ˜ ์ด๋ฏธ์ง€ (4์žฅ)
112
+
113
+ 1. ๋ฐ”๋ฅดํ•œ ์‚ฌ๊ตฌ์™€ ๋ฐ”๋žŒ ๋ฐฉํ–ฅ (Barchan Dune)
114
+ Filename: barchan_dune_wind_direction.jpg
115
+ Point: ๋ฐ”๋ฅดํ•œ ์‚ฌ๊ตฌ์˜ ํ˜•ํƒœ์™€ ๋ฐ”๋žŒ ๋ฐฉํ–ฅ ํ™”์‚ดํ‘œ.
116
+
117
+ 2. ์„ ์ƒ์ง€ ๊ตฌ์กฐ (Alluvial Fan in Arid)
118
+ Filename: alluvial_fan_structure.png
119
+ Point: ์„ ์ƒ์ง€์˜ ์„ ์ •, ์„ ์•™, ์„ ๋‹จ ๋‹จ๋ฉด๋„ ๋ฐ ์ž…์ž ํฌ๊ธฐ ๋ถ„ํฌ.
120
+
121
+ 3. ๋ฉ”์‚ฌ-๋ทฐํŠธ ์นจ์‹ ๋‹จ๊ณ„ (Mesa-Butte Evolution)
122
+ Filename: mesa_butte_evolution.jpg
123
+ Point: ๊ณ ์› โ†’ ๋ฉ”์‚ฌ โ†’ ๋ทฐํŠธ โ†’ ์ŠคํŒŒ์ด์–ด ์นจ์‹ ๋‹จ๊ณ„.
124
+
125
+ 4. ๋ฒ„์„ฏ๋ฐ”์œ„ ํ˜•์„ฑ (Mushroom Rock)
126
+ Filename: mushroom_rock_formation.gif
127
+ Point: ๋ชจ๋ž˜๋ฐ”๋žŒ์— ์˜ํ•œ ํ•˜๋‹จ๋ถ€ ์นจ์‹ ์• ๋‹ˆ๋ฉ”์ด์…˜.
128
+
129
+ ---
130
+
131
+ ๐ŸŒพ [Chapter 7. ํ‰์•ผ ์ง€ํ˜• (Plain Landforms)]
132
+ ํ•„์ˆ˜ ์ด๋ฏธ์ง€ (4์žฅ)
133
+
134
+ 1. ๊ณก๋ฅ˜ ํ•˜์ฒœ ์ง„ํ™” (Meandering River Evolution)
135
+ Filename: meandering_river_evolution.gif
136
+ Point: ๊ณก๋ฅ˜ ํ•˜์ฒœ์ด ์šฐ๊ฐํ˜ธ๋กœ ๋ณ€ํ•˜๋Š” 4๋‹จ๊ณ„ ๊ณผ์ •.
137
+
138
+ 2. ๋ฒ”๋žŒ์› ๋‹จ๋ฉด (Floodplain Cross-section)
139
+ Filename: floodplain_cross_section.png
140
+ Point: ํ•˜๋„ - ์ž์—ฐ์ œ๋ฐฉ - ๋ฐฐํ›„์Šต์ง€ ๋‹จ๋ฉด๋„ ๋ฐ ํ† ์ง€ ์ด์šฉ.
141
+
142
+ 3. ์‚ผ๊ฐ์ฃผ ์œ ํ˜• ์œ„์„ฑ ์‚ฌ์ง„ (Delta Types Satellite)
143
+ Filename: delta_types_satellite.jpg
144
+ Point: ๋‚˜์ผ๊ฐ•, ๋ฏธ์‹œ์‹œํ”ผ๊ฐ•, ๊ฐ ์ง€์Šค๊ฐ• ์‚ผ๊ฐ์ฃผ ์œ„์„ฑ ์‚ฌ์ง„ ๋น„๊ต.
145
+
146
+ 4. ์ž์—ฐ์ œ๋ฐฉ vs ๋ฐฐํ›„์Šต์ง€ ํ† ์–‘ (Levee vs Backswamp Soil)
147
+ Filename: levee_vs_backswamp_soil.jpg
148
+ Point: ์ž์—ฐ์ œ๋ฐฉ์˜ ๋ชจ๋ž˜ vs ๋ฐฐํ›„์Šต์ง€์˜ ์ ํ†  ์ž…์ž ๋น„๊ต.
149
+
150
+ ---
151
+
152
+ ๐Ÿ“Š [์ด๊ณ„]
153
+ - ํ•˜์ฒœ ์ง€ํ˜•: 5์žฅ
154
+ - ํ•ด์•ˆ ์ง€ํ˜•: 4์žฅ
155
+ - ์นด๋ฅด์ŠคํŠธ ์ง€ํ˜•: 4์žฅ
156
+ - ํ™”์‚ฐ ์ง€ํ˜•: 4์žฅ
157
+ - ๋น™ํ•˜ ์ง€ํ˜•: 4์žฅ
158
+ - ๊ฑด์กฐ ์ง€ํ˜•: 4์žฅ
159
+ - ํ‰์•ผ ์ง€ํ˜•: 4์žฅ
160
+ - **์ดํ•ฉ: 29์žฅ**
pyproject.toml ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ [tool.poetry]
2
+ name = "geo-lab-ai"
3
+ version = "1.0.0"
4
+ description = "๊ต์œก์šฉ ์ง€ํ˜• ์‹œ๋ฎฌ๋ ˆ์ด์…˜ - ํ•˜์ฒœ ์ง€ํ˜• ๋ชจ๋“ˆ"
5
+ authors = ["Geo-Lab Team"]
6
+ readme = "README.md"
7
+
8
+ [tool.poetry.dependencies]
9
+ python = "^3.9"
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ streamlit>=1.28.0
2
+ numpy>=1.24.0
3
+ plotly>=5.18.0
4
+ matplotlib>=3.7.0
5
+ scipy>=1.11.0
6
+ Pillow>=10.0.0
run_log.txt ADDED
Binary file (2.01 kB). View file
 
run_trace.txt ADDED
Binary file (3.4 kB). View file
 
run_trace_soft.txt ADDED
Binary file (1.94 kB). View file
 
test_genesis.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from engine.grid import WorldGrid
3
+ from engine.system import EarthSystem
4
+
5
+ def test_fan_mechanism():
6
+ print("Testing Project Genesis Alluvial Fan Mechanism...")
7
+
8
+ # 1. Setup Mini Grid (20x20)
9
+ grid = WorldGrid(width=20, height=20, cell_size=10.0)
10
+
11
+ # Slope N->S
12
+ for r in range(20):
13
+ grid.bedrock[r, :] = 20.0 - r * 0.5 # 20m -> 10m
14
+
15
+ grid.update_elevation()
16
+
17
+ # 2. Setup Engine
18
+ engine = EarthSystem(grid)
19
+ engine.erosion.K = 0.001 # Low K to force deposition
20
+
21
+ # 3. Step with sediment source
22
+ # Source at (2, 10)
23
+ source_y, source_x = 2, 10
24
+ amount = 100.0
25
+
26
+ settings = {
27
+ 'precipitation': 0.0,
28
+ 'rain_source': (0, 10, 2, 10.0), # Rain at top
29
+ 'sediment_source': (source_y, source_x, 1, amount) # Sediment at source
30
+ }
31
+
32
+ print(f"Initial Max Sediment: {grid.sediment.max()}")
33
+
34
+ # Run 10 steps
35
+ np.set_printoptions(precision=4, suppress=True)
36
+ for i in range(10):
37
+ engine.step(dt=1.0, settings=settings)
38
+ print(f"Step {i+1}:")
39
+ print(f" Max Sediment: {grid.sediment.max():.6f}")
40
+ print(f" Max Discharge: {grid.discharge.max():.6f}")
41
+ print(f" Max Elev: {grid.elevation.max():.6f}")
42
+
43
+ if grid.sediment.max() > 0:
44
+ print("SUCCESS: Sediment is depositing.")
45
+ print("Sample Sediment Grid (Center):")
46
+ print(grid.sediment[0:5, 8:13])
47
+ else:
48
+ print("FAILURE: No sediment deposited.")
49
+
50
+ if __name__ == "__main__":
51
+ test_fan_mechanism()
test_output.txt ADDED
Binary file (2.66 kB). View file