1size 960 600
2background #03040b
3timeline 30 300
4
5// Poser le module sur la plateforme. Clavier : ↑ ou espace pour pousser, ← → pour pivoter, R pour
6// rejouer ; au doigt, les trois boutons du bas. La physique tourne à 60 Hz (`every frame`, DT) :
7// gravité lunaire, poussée dans l'axe du module, carburant compté. Le verdict part vers l'hôte (`send`).
8def g = 38
9def poussee = 92
10def padA = 560
11def padB = 700
12def padY = 470
13
14var mx = 220
15var my = 110
16var vx = 40
17var vy = 0
18var ang = -30
19var fuel = 1
20var pousse = 0
21var phase = 0
22var tfin = -99
23var gauche = 0
24var droite = 0
25var gaz = 0
26
27// Le relief, morceau par morceau (les mêmes points que le dessin du sol).
28fn seg(x, x0, y0, x1, y1) = lerp(y0, y1, clamp((x - x0) / (x1 - x0), 0, 1))
29fn sol(x) = x < 120 ? seg(x, 0, 500, 120, 455) : (x < 230 ? seg(x, 120, 455, 230, 525) : (x < 340 ? seg(x, 230, 525, 340, 440) : (x < 460 ? seg(x, 340, 440, 460, 505) : (x < 560 ? seg(x, 460, 505, 560, 470) : (x < 700 ? 470 : (x < 790 ? seg(x, 700, 470, 790, 410) : (x < 880 ? seg(x, 790, 410, 880, 500) : seg(x, 880, 500, 960, 465))))))))
30fn relance() {
31 mx = 220
32 my = 110
33 vx = 40
34 vy = 0
35 ang = -30
36 fuel = 1
37 phase = 0
38}
39
40scene {
41 layer "Ciel" {
42 repeat i from 0 to 70 { circle $(mod(i * 389, 960)) $(mod(i * 173, 420)) $(0.5 + mod(i, 3) * 0.55) fill #ffffff opacity $(0.2 + mod(i, 4) * 0.15) }
43 circle 790 140 36 fill radial(0.38, 0.35, 0.7, 0:#9fd0ff, 0.55:#3f7fd8, 1:#16346e)
44 path "M770 124 C782 118 796 130 808 124 C812 136 796 148 782 142 Z" fill #6fb26a opacity 0.8
45 circle 790 140 50 fill radial(0.5, 0.5, 0.5, 0:#4f8fff00, 0.7:#4f8fff22, 1:#4f8fff00)
46 }
47 layer "Sol" {
48 path "M0 500 L120 455 L230 525 L340 440 L460 505 L560 470 L700 470 L790 410 L880 500 L960 465 L960 600 L0 600 Z" fill linear(90, 0:#8a8d9a, 1:#3a3c46)
49 path "M0 500 L120 455 L230 525 L340 440 L460 505 L560 470 L700 470 L790 410 L880 500 L960 465" nofill stroke #c4c7d4 2 join round
50 circle 150 520 18 fill #6e717d opacity 0.7
51 circle 410 540 26 fill #6e717d opacity 0.6
52 circle 840 545 14 fill #6e717d opacity 0.7
53 rect $(padA) $(padY - 4) $(padB - padA) 8 2 fill #f2c94c
54 repeat i from 0 to 6 { rect $(padA + 6 + i * 20) $(padY - 4) 8 8 fill #2a2a33 }
55 circle $(padA) $(padY - 14) 4 fill #ff5a5a
56 circle $(padB) $(padY - 14) 4 fill #ff5a5a
57 rect $(padA - 1) $(padY - 14) 2 10 fill #9a9cab
58 rect $(padB - 1) $(padY - 14) 2 10 fill #9a9cab
59 }
60 layer "Module" {
61 group "Module" at 220,110 pivot 0,0 nohit {
62 layer "flamme" {
63 group "Flamme" at 0,14 pivot 0,0 {
64 layer "c" {
65 path "M-9 0 C-9 18 0 34 0 46 C0 34 9 18 9 0 Z" fill linear(90, 0:#fff4c2, 0.4:#ffb13d, 1:#ff4d1f00)
66 path "M-4 0 C-4 10 0 20 0 26 C0 20 4 10 4 0 Z" fill #ffffff
67 }
68 }
69 }
70 layer "corps" {
71 path "M-16 6 L-26 26 M16 6 L26 26 M-30 26 L-22 26 M22 26 L30 26" nofill stroke #c9ccd8 3 cap round
72 rect -15 -2 30 14 3 fill linear(90, 0:#e8b84a, 1:#a8742a)
73 path "M-14 -2 C-14 -26 14 -26 14 -2 Z" fill linear(90, 0:#f2f4fa, 1:#a9adbd)
74 circle 0 -12 6 fill radial(0.4, 0.35, 0.7, 0:#bfe6ff, 1:#2a5c9a)
75 rect -5 12 10 4 1 fill #555866
76 }
77 }
78 }
79 layer "Explosion" {
80 group "Boum" at 0,0 pivot 0,0 nohit {
81 layer "c" {
82 circle 0 0 60 fill radial(0.5, 0.5, 0.5, 0:#fff3c4, 0.4:#ff9a3d, 1:#ff3d1f00)
83 circle 0 0 80 nofill stroke #ffb13d 3
84 }
85 }
86 }
87 layer "Tableau" {
88 text "ALUNISSAGE" at 28,22 font "Geist, system-ui, sans-serif" size 24 bold align left line 1.2 color #e6e9f5 box 300 32
89 text "↑ pousser ← → pivoter R rejouer" at 28,54 font "Geist, system-ui, sans-serif" size 13 align left line 1.2 color #7a7f99 box 320 20
90 text "altitude {} m" as "Alt" at 28,86 font "Geist Mono, ui-monospace, monospace" size 15 align left line 1.2 color #c9cde0 box 220 22 bind "max(0, round((sol(mx) - my - 26) / 4))"
91 text "chute {} m/s" as "Vit" at 28,108 font "Geist Mono, ui-monospace, monospace" size 15 align left line 1.2 color #c9cde0 box 220 22 bind "round(vy / 4)"
92 text "carburant" at 28,134 font "Geist Mono, ui-monospace, monospace" size 13 align left line 1.2 color #7a7f99 box 120 20
93 rect 120 138 140 10 5 fill #1c1f2e
94 group "Jauge" at 120,138 pivot 0,0 { layer "c" { rect 0 0 140 10 5 fill linear(0, 0:#ff5a5a, 0.3:#f2c94c, 1:#5ad18a) } }
95 }
96 layer "Boutons" {
97 group "BGauche" at 70,550 hitbox 84 70 { layer "c" { circle 0 0 30 fill #ffffff14 stroke #ffffff40 2 path "M8 -12 L-10 0 L8 12 Z" fill #e6e9f5 } }
98 group "BGaz" at 170,550 hitbox 84 70 { layer "c" { circle 0 0 30 fill #ffffff14 stroke #ffffff40 2 path "M-12 8 L0 -12 L12 8 Z" fill #ffb13d } }
99 group "BDroite" at 270,550 hitbox 84 70 { layer "c" { circle 0 0 30 fill #ffffff14 stroke #ffffff40 2 path "M-8 -12 L10 0 L-8 12 Z" fill #e6e9f5 } }
100 }
101 layer "Verdict" {
102 group "Bravo" at 480,250 pivot 0,0 nohit {
103 layer "c" {
104 rect -220 -60 440 120 18 fill #05070fd8 stroke #5ad18a 2
105 text "Posé en douceur !" at -220,-40 font "Geist, system-ui, sans-serif" size 34 bold align center line 1.2 color #b6f5cf box 440 44
106 text "R ou le bouton pour rejouer" at -220,10 font "Geist, system-ui, sans-serif" size 15 align center line 1.2 color #7fa893 box 440 22
107 }
108 }
109 group "Rate" at 480,250 pivot 0,0 nohit {
110 layer "c" {
111 rect -220 -60 440 120 18 fill #05070fd8 stroke #ff5a5a 2
112 text "Raté : le module s'est écrasé." at -220,-40 font "Geist, system-ui, sans-serif" size 26 bold align center line 1.2 color #ffc2c2 box 440 40
113 text "R ou le bouton pour rejouer" at -220,10 font "Geist, system-ui, sans-serif" size 15 align center line 1.2 color #a87f7f box 440 22
114 }
115 }
116 group "Rejouer" at 860,40 hitbox 150 44 {
117 layer "c" { rect -70 -18 140 36 18 fill #ffffff12 stroke #ffffff55 1.5 text "rejouer" at -70,-18 font "Geist, system-ui, sans-serif" size 16 bold align center line 1.2 color #e6e9f5 box 140 36 valign middle }
118 }
119 }
120}
121
122object "Module" {
123 x = mx
124 y = my
125 rotationDeg = ang
126 opacity = phase == 2 ? 0 : 1
127}
128object "Flamme" {
129 scaleY = pousse * (0.75 + 0.25 * sin(clock * 47) * sin(clock * 31))
130 scaleX = pousse * (0.9 + 0.1 * sin(clock * 53))
131}
132object "Boum" {
133 x = mx
134 y = my
135 opacity = phase == 2 ? pulse(tfin, 1.4) : 0
136 scaleX = 0.4 + (clock - tfin) * 1.6
137 scaleY = 0.4 + (clock - tfin) * 1.6
138}
139object "Jauge" { scaleX = max(0.001, fuel) }
140object "Bravo" {
141 opacity = phase == 1 ? clamp((clock - tfin) * 3, 0, 1) : 0
142}
143object "Rate" {
144 opacity = phase == 2 ? clamp((clock - tfin - 0.6) * 3, 0, 1) : 0
145}
146object "BGauche" {
147 opacity = self.pressed ? 1 : 0.7
148 when pressed { gauche = 1 }
149 when released { gauche = 0 }
150}
151object "BGaz" {
152 opacity = self.pressed ? 1 : 0.7
153 when pressed { gaz = 1 }
154 when released { gaz = 0 }
155}
156object "BDroite" {
157 opacity = self.pressed ? 1 : 0.7
158 when pressed { droite = 1 }
159 when released { droite = 0 }
160}
161object "Rejouer" {
162 focusable order 1
163 feedback lift
164 when clicked { relance() }
165}
166
167every frame {
168 if keys.r == 1 {
169 relance()
170 }
171 if phase == 0 {
172 pousse = fuel > 0 ? max(keys.ArrowUp, max(keys.Space, gaz)) : 0
173 ang = clamp(ang + (max(keys.ArrowRight, droite) - max(keys.ArrowLeft, gauche)) * 140 * DT, -90, 90)
174 vx = vx + sin(rad(ang)) * $(poussee) * pousse * DT
175 vy = vy + ($(g) - cos(rad(ang)) * $(poussee) * pousse) * DT
176 mx = clamp(mx + vx * DT, 20, 940)
177 my = my + vy * DT
178 fuel = max(0, fuel - pousse * 0.06 * DT)
179 if my + 26 >= sol(mx) {
180 my = sol(mx) - 26
181 tfin = clock
182 pousse = 0
183 if mx > $(padA + 14) && mx < $(padB - 14) && vy < 60 && abs(vx) < 36 && abs(ang) < 14 {
184 phase = 1
185 send "pose", round(fuel * 100)
186 } else {
187 phase = 2
188 send "crash"
189 }
190 }
191 }
192}