diff --git a/app/lib/core/ui/planet_25d.dart b/app/lib/core/ui/planet_25d.dart index ae31594..c74e4fd 100644 --- a/app/lib/core/ui/planet_25d.dart +++ b/app/lib/core/ui/planet_25d.dart @@ -472,6 +472,21 @@ class _Planet25DState extends State ), ), ), + // Stage 6 — Figma `2024:20821` 「자기장(Field) 라인이 행성을 감싸며 외곽으로 방사됨」. + // 본체 뒤에 깔아 구면 바깥으로 뻗는 부분만 보이게 한다. + if (stage == 6) + Positioned.fill( + child: IgnorePointer( + child: CustomPaint( + key: const ValueKey('planet-field-lines'), + painter: _FieldLinePainter( + t: t, + color: glow, + lowEffects: _lowEffects, + ), + ), + ), + ), // 6. 링 — 뒤쪽 절반. 본체 뒤에 깔린다. if (ring) Positioned.fill( @@ -902,6 +917,53 @@ class _OrbitPainter extends CustomPainter { old.lowEffects != lowEffects; } +/// Stage 6 자기장 — 양극을 잇는 고리를 좌우로 겹겹이 그리고, 밝은 띠가 안쪽에서 바깥 고리로 +/// 번져 나간다(외곽 방사). 저사양은 고리 수만 줄인다. +class _FieldLinePainter extends CustomPainter { + const _FieldLinePainter({ + required this.t, + required this.color, + required this.lowEffects, + }); + final double t; + final Color color; + final bool lowEffects; + + @override + void paint(Canvas canvas, Size size) { + final c = size.center(Offset.zero); + final r = size.shortestSide * 0.42; + final loops = lowEffects ? 2 : 4; + // 방사 위치 0~1 — 한 주기에 네 번 안쪽에서 바깥으로. + final wave = (t * 4) % 1; + for (var i = 0; i < loops; i++) { + final k = (i + 1) / loops; + final w = r * (0.55 + 0.75 * k); + final h = r * (1.05 + 0.25 * k); + final glow = 1 - ((k - wave).abs() * 2.5).clamp(0.0, 1.0); + final paint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 1.0 + glow * 1.2 + ..color = Color.lerp(color, Colors.white, 0.35)! + .withValues(alpha: (0.14 + 0.36 * glow) * (1 - k * 0.4)); + for (final side in const [-1.0, 1.0]) { + canvas.drawOval( + Rect.fromCenter( + center: c + Offset(side * w / 2, 0), + width: w, + height: h * 2, + ), + paint, + ); + } + } + } + + @override + bool shouldRepaint(_FieldLinePainter old) => + old.t != t || old.color != color || old.lowEffects != lowEffects; +} + /// Stage 6: 폭죽 대신 구면 주위에 머무는 넓고 일정한 대기광. class _SustainedAura extends CustomPainter { const _SustainedAura({required this.color, required this.lowEffects}); diff --git a/app/test/planet_give_reaction_test.dart b/app/test/planet_give_reaction_test.dart index 2d6158a..0b5197f 100644 --- a/app/test/planet_give_reaction_test.dart +++ b/app/test/planet_give_reaction_test.dart @@ -182,4 +182,22 @@ void main() { await tester.pump(const Duration(seconds: 3)); }); }); + + testWidgets('Stage 6 은 자기장 라인이 행성을 감싼다(Figma 2024:20821) · Stage 5 는 없다', (tester) async { + Future pumpStage(int stage) => tester.pumpWidget( + MaterialApp( + home: Center( + child: Planet25D( + image: 'assets/figma/planet-home.png', + size: 200, + stage: stage, + ), + ), + ), + ); + await pumpStage(6); + expect(find.byKey(const ValueKey('planet-field-lines')), findsOneWidget); + await pumpStage(5); + expect(find.byKey(const ValueKey('planet-field-lines')), findsNothing); + }); }