danoniplus 52.1.0 → 52.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Source by tickle
7
7
  * Created : 2026/09/13
8
- * Revised : 2026/10/25 (v51.4.3)
8
+ * Revised : 2026/11/08 (v52.2.0)
9
9
  *
10
10
  * https://github.com/cwtickle/danoniplus
11
11
  */
@@ -51,6 +51,18 @@ const mainInit = () => {
51
51
  addTransform(`infoSprite`, `main`,
52
52
  `translateX(${wUnit(g_workObj.playingX)}) translateY(${wUnit(g_headerObj.playingY)})`, g_transPriority.base);
53
53
 
54
+ // リングノート用スプライト (mainSpriteの外: PlayWindow/Shaking等の影響を受けない)
55
+ let ringSprite = null;
56
+ if ((g_scoreObj.ringData?.length ?? 0) > 0) {
57
+ bindRingPointer(divRoot);
58
+ startRingKeepAlive();
59
+ ringSprite = createEmptySprite(divRoot, `ringSprite`, mainCommonPos);
60
+ addTransform(`ringSprite`, `main`,
61
+ `translateX(${wUnit(g_workObj.playingX)}) translateY(${wUnit(g_headerObj.playingY)})`, g_transPriority.base);
62
+ } else {
63
+ stopRingKeepAlive();
64
+ }
65
+
54
66
  // 判定系スプライトを作成(メインスプライトより上位)
55
67
  const judgeSprite = createEmptySprite(divRoot, `judgeSprite`, mainCommonPos);
56
68
  addTransform(`judgeSprite`, `main`,
@@ -1450,10 +1462,120 @@ const mainInit = () => {
1450
1462
  }
1451
1463
  };
1452
1464
 
1465
+ /**
1466
+ * リングノート
1467
+ * - 状態は mainInit ローカルのため、リトライ時は自然に初期化される
1468
+ * - cnt === 0 がガイドと一致するフレーム。判定は cnt <= 0 の間のみ行い、
1469
+ * 最初にポインタがガイド内にあったフレームの遅れ (-cnt) で判定する
1470
+ */
1471
+ const rings = [];
1472
+ let ringCnt = 0;
1473
+
1474
+ const makeRing = _data => {
1475
+ const no = ++ringCnt;
1476
+ const diameter = _data.radius * 2;
1477
+ const base = {
1478
+ x: _data.x - _data.radius, y: _data.y - _data.radius, w: diameter, h: diameter,
1479
+ borderRadius: `50%`, boxSizing: `border-box`,
1480
+ };
1481
+ const guide = createEmptySprite(ringSprite, `ringGuide${no}`, {
1482
+ ...base, border: `solid ${g_ringObj.lineWidth}px ${g_ringObj.guideColor}`,
1483
+ visibility: g_stateObj.ringGuide === `Hidden` ? `hidden` : `visible`,
1484
+ });
1485
+ const body = createEmptySprite(ringSprite, `ringBody${no}`, {
1486
+ ...base, border: `solid ${g_ringObj.lineWidth}px ${_data.color}`,
1487
+ });
1488
+ rings.push({ ..._data, cnt: _data.arrivalFrame + 1, guide, body, guideVisible: true });
1489
+ };
1490
+
1491
+ const ringDone = new Set(); // 判定済み(削除済み)リングの通し番号
1492
+ let ringNextSeq = 0; // 未判定のうち最小の通し番号
1493
+
1494
+ const removeRing = _i => {
1495
+ ringDone.add(rings[_i].seq);
1496
+ rings[_i].guide.remove();
1497
+ rings[_i].body.remove();
1498
+ rings.splice(_i, 1);
1499
+ };
1500
+
1501
+ const updateRings = () => {
1502
+ for (let i = rings.length - 1; i >= 0; i--) {
1503
+ const ring = rings[i];
1504
+ ring.cnt--;
1505
+
1506
+ // リング本体の大きさ (cnt === 0 でガイドと一致)
1507
+ const t = Math.min(Math.max(1 - ring.cnt / ring.total, 0), 1);
1508
+ ring.body.style.transform = `scale(${g_ringObj.startRate + (1 - g_ringObj.startRate) * t})`;
1509
+
1510
+ // ガイド到達前は判定しない
1511
+ if (ring.cnt > 0) {
1512
+ continue;
1513
+ }
1514
+
1515
+ // 判定範囲外は、ポインタ位置に関係なくミスにする
1516
+ if (ring.cnt < -g_judgObj.arrowJ[g_judgPosObj.shobon]) {
1517
+ judgeUwan(ring.cnt, -1);
1518
+ removeRing(i);
1519
+ continue;
1520
+ }
1521
+
1522
+ const autoPlay = g_stateObj.autoAll === C_FLG_ON;
1523
+ const hit = autoPlay ? ring.cnt === 0 : isPointerInside(ring.guide);
1524
+
1525
+ if (hit) {
1526
+ // 判定の選択には正の遅れ、フックには符号付きのカウントを渡す
1527
+ checkJudgment(-ring.cnt)[0](ring.cnt, -1);
1528
+ playRingSound(ring.sound);
1529
+ removeRing(i);
1530
+ }
1531
+ }
1532
+
1533
+ // ガイドを「次に判定するリング」だけに絞る
1534
+ // - 次 = 未判定(未出現を含む)のうち到達が最も早いリング。出現順ではなく譜面上の到達順で決める
1535
+ if (g_stateObj.ringGuide === `Next`) {
1536
+ while (ringDone.has(ringNextSeq)) {
1537
+ ringNextSeq++;
1538
+ }
1539
+ const nextArrival = g_workObj.ringArrivals[ringNextSeq];
1540
+ rings.forEach(ring => {
1541
+ const visible = ring.cnt <= 0 || ring.arrival === nextArrival;
1542
+ if (ring.guideVisible !== visible) {
1543
+ ring.guide.style.visibility = visible ? `visible` : `hidden`;
1544
+ ring.guideVisible = visible;
1545
+ }
1546
+ });
1547
+ }
1548
+ };
1549
+
1550
+ /**
1551
+ * ライフ制&ライフ0の場合の途中終了
1552
+ */
1553
+ const isLifeZero = () => g_workObj.lifeVal === 0 && g_workObj.lifeBorder === 0;
1554
+ const finishByLifeZero = () => {
1555
+ g_audio.pause();
1556
+ g_timerHandler.clearTimeout(g_timeoutEvtId);
1557
+ g_gameOverFlg = true;
1558
+ g_finishFlg = false;
1559
+ resultInit();
1560
+ };
1561
+
1453
1562
  /**
1454
1563
  * フレーム処理(譜面台)
1455
1564
  */
1456
1565
  const flowTimeline = () => {
1566
+ const terminalFrame = g_scoreObj.frameNum >= fullFrame;
1567
+ if (!terminalFrame && g_audio instanceof AudioPlayer && g_audioClockSync &&
1568
+ musicStartCtxTime !== undefined && g_audio.contextState !== `running`) {
1569
+ // 停止中でも、ライフ0の途中終了は待たずに行う
1570
+ // (最終フレームは上の terminalFrame で本体へ通し、そちらの判定を優先する)
1571
+ if (isLifeZero()) {
1572
+ finishByLifeZero();
1573
+ return;
1574
+ }
1575
+ getSharedAudioContext(); // suspended時はresumeを試行
1576
+ g_timeoutEvtId = g_timerHandler.setTimeout(flowTimeline, g_maxFrameWait);
1577
+ return;
1578
+ }
1457
1579
 
1458
1580
  const currentFrame = g_scoreObj.frameNum;
1459
1581
  lblframe.textContent = g_scoreObj.baseFrame;
@@ -1598,6 +1720,10 @@ const mainInit = () => {
1598
1720
  }
1599
1721
  }
1600
1722
 
1723
+ // リングノート生成・更新
1724
+ g_workObj.mkRing?.[currentFrame]?.forEach(data => makeRing(data));
1725
+ updateRings();
1726
+
1601
1727
  // 歌詞表示
1602
1728
  g_scoreObj.wordData[currentFrame]?.forEach(wordData => updateWord(wordData, currentFrame));
1603
1729
 
@@ -1629,14 +1755,10 @@ const mainInit = () => {
1629
1755
  g_workObj.mainEndTime = thisTime;
1630
1756
  resultInit();
1631
1757
 
1632
- } else if (g_workObj.lifeVal === 0 && g_workObj.lifeBorder === 0) {
1758
+ } else if (isLifeZero()) {
1633
1759
 
1634
1760
  // ライフ制&ライフ0の場合は途中終了
1635
- g_audio.pause();
1636
- g_timerHandler.clearTimeout(g_timeoutEvtId);
1637
- g_gameOverFlg = true;
1638
- g_finishFlg = false;
1639
- resultInit();
1761
+ finishByLifeZero();
1640
1762
 
1641
1763
  } else {
1642
1764
 
@@ -1653,27 +1775,20 @@ const mainInit = () => {
1653
1775
  // 再生開始時のオフセットずれやsuspend/resumeによるずれを吸収できない
1654
1776
  thisTime = performance.now();
1655
1777
  buffTime = 0;
1656
- let holdFrame = false;
1657
1778
 
1658
1779
  if (g_audio instanceof AudioPlayer && g_audioClockSync && musicStartCtxTime !== undefined) {
1659
- if (g_audio.contextState === `running`) {
1660
- buffTime = (g_audio.contextTime - musicStartCtxTime) * 1000
1661
- - (currentFrame - musicStartFrame) * 1000 / g_fps;
1662
- } else {
1663
- // AudioContext停止中は音源も止まっているため、フレーム進行も止めて復帰を待つ
1664
- getSharedAudioContext(); // suspended時はresumeを試行
1665
- holdFrame = true;
1666
- }
1780
+ buffTime = (g_audio.contextTime - musicStartCtxTime) * 1000
1781
+ - (currentFrame - musicStartFrame) * 1000 / g_fps;
1667
1782
  } else if (g_audio instanceof AudioPlayer || currentFrame >= musicStartFrame) {
1668
- buffTime = (thisTime - musicStartTime - (currentFrame - musicStartFrame) * 1000 / g_fps);
1783
+ buffTime = thisTime - musicStartTime - (currentFrame - musicStartFrame) * 1000 / g_fps;
1669
1784
  }
1670
1785
 
1671
- if (!holdFrame) {
1672
- g_scoreObj.frameNum++;
1673
- g_scoreObj.baseFrame++;
1674
- }
1675
- g_timeoutEvtId = g_timerHandler.setTimeout(flowTimeline, holdFrame ? g_maxFrameWait :
1676
- Math.min(Math.max(1000 / g_fps - buffTime, 0), g_maxFrameWait));
1786
+ g_scoreObj.frameNum++;
1787
+ g_scoreObj.baseFrame++;
1788
+ g_timeoutEvtId = g_timerHandler.setTimeout(
1789
+ flowTimeline,
1790
+ Math.min(Math.max(1000 / g_fps - buffTime, 0), g_maxFrameWait)
1791
+ );
1677
1792
  }
1678
1793
  };
1679
1794
 
@@ -1855,6 +1970,242 @@ const mainInit = () => {
1855
1970
  }
1856
1971
  };
1857
1972
 
1973
+ /** マウス/タッチ位置 (リングノートの判定用) */
1974
+ const g_pointer = { x: 0, y: 0, valid: false, type: null, id: null };
1975
+
1976
+ // 接触中のタッチ位置
1977
+ const g_touchPointers = new Map();
1978
+ let g_ringTouchCleanup = null;
1979
+
1980
+ /** デバイス待機防止用の維持音 */
1981
+ let g_ringKeepAlive = null;
1982
+
1983
+ /**
1984
+ * リングノートの効果音が欠けないよう、聞こえないレベルのノイズを鳴らし続ける
1985
+ * - 完全な無音だとデバイスが省電力に入り、その直後の短い効果音が欠けることがある
1986
+ * - 振幅1e-4は16bitで約3LSB(約-80dBFS)で、聞こえない
1987
+ */
1988
+ const startRingKeepAlive = () => {
1989
+ stopRingKeepAlive();
1990
+ const ctx = getSharedAudioContext();
1991
+ const buffer = ctx.createBuffer(1, ctx.sampleRate, ctx.sampleRate);
1992
+ const data = buffer.getChannelData(0);
1993
+ for (let i = 0; i < data.length; i++) {
1994
+ data[i] = (Math.random() * 2 - 1) * 1e-4;
1995
+ }
1996
+ const source = ctx.createBufferSource();
1997
+ source.buffer = buffer;
1998
+ source.loop = true;
1999
+ source.connect(ctx.destination);
2000
+ source.start();
2001
+ g_ringKeepAlive = source;
2002
+ };
2003
+
2004
+ const stopRingKeepAlive = () => {
2005
+ if (g_ringKeepAlive) {
2006
+ try {
2007
+ g_ringKeepAlive.stop();
2008
+ } catch (e) { /* 既に停止済み */ }
2009
+ g_ringKeepAlive.disconnect();
2010
+ g_ringKeepAlive = null;
2011
+ }
2012
+ };
2013
+
2014
+ // ポインタ状態をクリア
2015
+ const resetRingPointers = () => {
2016
+ g_pointer.valid = false;
2017
+ g_touchPointers.clear();
2018
+ };
2019
+
2020
+ // プレイ開始前に一度だけ登録する。
2021
+ // document のリスナーは clearWindow では解除されない。
2022
+ const bindRingMouse = () => {
2023
+ const update = evt => {
2024
+ if (evt.pointerType !== `mouse`) {
2025
+ return;
2026
+ }
2027
+ g_pointer.x = evt.clientX;
2028
+ g_pointer.y = evt.clientY;
2029
+ g_pointer.type = `mouse`;
2030
+ g_pointer.id = evt.pointerId;
2031
+ g_pointer.valid = true;
2032
+ };
2033
+
2034
+ g_handler.addListener(document, `pointermove`, update, true);
2035
+ g_handler.addListener(document, `pointerdown`, update, true);
2036
+ g_handler.addListener(document, `pointerover`, evt => {
2037
+ // 文書外から入った場合だけ更新する。
2038
+ if (evt.relatedTarget === null) {
2039
+ update(evt);
2040
+ }
2041
+ }, true);
2042
+ g_handler.addListener(document, `pointerout`, evt => {
2043
+ if (evt.pointerType === `mouse` && evt.relatedTarget === null &&
2044
+ g_pointer.type === `mouse`) {
2045
+ g_pointer.valid = false;
2046
+ }
2047
+ }, true);
2048
+ };
2049
+
2050
+ // タッチを document で追跡し、子要素のクリック先を変更しない。
2051
+ const bindRingTouchTracking = _root => {
2052
+ const ignoreSelector = [
2053
+ `.button_common`,
2054
+ `button`, `input`, `select`, `textarea`, `a[href]`,
2055
+ `[role="button"]`, `[data-ring-ignore]`,
2056
+ ].join(`,`);
2057
+
2058
+ const onDown = evt => {
2059
+ if (evt.pointerType !== `touch` ||
2060
+ !(evt.target instanceof Element) || !_root.contains(evt.target)) {
2061
+ return;
2062
+ }
2063
+
2064
+ // 除外したコントロールでも、古いマウス・ペン位置は無効にする。
2065
+ g_pointer.valid = false;
2066
+ if (evt.target.closest(ignoreSelector)) {
2067
+ return;
2068
+ }
2069
+
2070
+ g_touchPointers.set(evt.pointerId, {
2071
+ x: evt.clientX,
2072
+ y: evt.clientY,
2073
+ startTarget: evt.target,
2074
+ });
2075
+ };
2076
+
2077
+ const onMove = evt => {
2078
+ const pointer = g_touchPointers.get(evt.pointerId);
2079
+ if (!pointer) return;
2080
+ if (!_root.contains(pointer.startTarget)) {
2081
+ g_touchPointers.delete(evt.pointerId);
2082
+ return;
2083
+ }
2084
+ pointer.x = evt.clientX;
2085
+ pointer.y = evt.clientY;
2086
+ };
2087
+
2088
+ const onEnd = evt => {
2089
+ g_touchPointers.delete(evt.pointerId);
2090
+ };
2091
+
2092
+ const listenerIds = [
2093
+ g_handler.addListener(document, `pointerdown`, onDown, true),
2094
+ g_handler.addListener(document, `pointermove`, onMove, true),
2095
+ ...[`pointerup`, `pointercancel`, `lostpointercapture`].map(type =>
2096
+ g_handler.addListener(document, type, onEnd, true)),
2097
+ ];
2098
+
2099
+ return () => {
2100
+ listenerIds.forEach(id => g_handler.removeListener(id));
2101
+ g_touchPointers.clear();
2102
+ };
2103
+ };
2104
+
2105
+ // mainInit のたびに登録する。
2106
+ // clearWindow は _root のリスナーを解除する。
2107
+ const bindRingPointer = _root => {
2108
+ g_ringTouchCleanup?.();
2109
+ g_touchPointers.clear();
2110
+
2111
+ // マウスの位置だけをリトライ後も保持する。
2112
+ if (g_pointer.type !== `mouse`) {
2113
+ g_pointer.valid = false;
2114
+ }
2115
+
2116
+ g_ringTouchCleanup = bindRingTouchTracking(_root);
2117
+
2118
+ const update = evt => {
2119
+ // マウスとタッチは document 側で追跡する。
2120
+ if (evt.pointerType === `mouse` || evt.pointerType === `touch`) {
2121
+ return;
2122
+ }
2123
+ g_pointer.x = evt.clientX;
2124
+ g_pointer.y = evt.clientY;
2125
+ g_pointer.type = evt.pointerType;
2126
+ g_pointer.id = evt.pointerId;
2127
+ g_pointer.valid = true;
2128
+ };
2129
+
2130
+ const release = evt => {
2131
+ if (evt.pointerType === `touch`) {
2132
+ g_touchPointers.delete(evt.pointerId);
2133
+ } else if (evt.type === `pointercancel` &&
2134
+ g_pointer.type === evt.pointerType && g_pointer.id === evt.pointerId) {
2135
+ g_pointer.valid = false;
2136
+ }
2137
+ };
2138
+
2139
+ const leave = evt => {
2140
+ if (evt.pointerType !== `touch` && evt.type === `pointercancel` &&
2141
+ g_pointer.type === evt.pointerType && g_pointer.id === evt.pointerId) {
2142
+ g_pointer.valid = false;
2143
+ }
2144
+ };
2145
+
2146
+ g_handler.addListener(_root, `pointermove`, update);
2147
+ g_handler.addListener(_root, `pointerdown`, update);
2148
+ g_handler.addListener(_root, `pointerup`, release);
2149
+ g_handler.addListener(_root, `pointercancel`, release);
2150
+ g_handler.addListener(_root, `pointerleave`, leave);
2151
+ };
2152
+
2153
+ /**
2154
+ * リングノート用効果音の再生
2155
+ * @param {number} _no 音番号 (0: 無音)
2156
+ */
2157
+ const playRingSound = _no => {
2158
+ const buffer = g_ringSoundBuffers.get(_no);
2159
+ if (_no <= 0 || buffer === undefined) {
2160
+ return;
2161
+ }
2162
+ const ctx = getSharedAudioContext();
2163
+ const source = ctx.createBufferSource();
2164
+ const gain = ctx.createGain();
2165
+ source.buffer = buffer;
2166
+ gain.gain.value = g_stateObj.volume / 100;
2167
+ source.connect(gain);
2168
+ gain.connect(ctx.destination);
2169
+ source.onended = () => {
2170
+ source.disconnect();
2171
+ gain.disconnect();
2172
+ };
2173
+ source.start();
2174
+ };
2175
+
2176
+ /**
2177
+ * 要素(ガイド円)の内側にポインタがあるか
2178
+ * - 画面の拡縮・transform後の実座標で判定するため getBoundingClientRect を使用
2179
+ */
2180
+ const isPointerInside = _el => {
2181
+ const rect = _el.getBoundingClientRect();
2182
+ const radius = rect.width / 2;
2183
+ const centerX = rect.left + radius;
2184
+ const centerY = rect.top + rect.height / 2;
2185
+
2186
+ const inside = pointer => {
2187
+ const dx = pointer.x - centerX;
2188
+ const dy = pointer.y - centerY;
2189
+ return dx * dx + dy * dy <= radius * radius;
2190
+ };
2191
+
2192
+ if (g_pointer.valid && inside(g_pointer)) {
2193
+ return true;
2194
+ }
2195
+ const root = document.getElementById(`divRoot`);
2196
+ for (const [id, pointer] of g_touchPointers) {
2197
+ // 開始要素が削除されたタッチは、古い座標で判定しない。
2198
+ if (!root?.contains(pointer.startTarget)) {
2199
+ g_touchPointers.delete(id);
2200
+ continue;
2201
+ }
2202
+ if (inside(pointer)) {
2203
+ return true;
2204
+ }
2205
+ }
2206
+ return false;
2207
+ };
2208
+
1858
2209
  /**
1859
2210
  * 矢印・フリーズアローの初期位置
1860
2211
  * - true_true: スクロールなし/初期位置マニュアル
@@ -2609,6 +2960,11 @@ const changeLifeColor = (_state = ``) => {
2609
2960
  lifeBar.style.height = wUnit(playableHeight * intLifeVal / g_headerObj.maxLifeVal);
2610
2961
  };
2611
2962
 
2963
+ /**
2964
+ * 判定カスタムフック名 (リングノートは _j = -1 で専用フックへ)
2965
+ */
2966
+ const judgHookName = (_name, _j) => _j < 0 ? `judg_ring_${_name}` : `judg_${_name}`;
2967
+
2612
2968
  /**
2613
2969
  * ゲージ回復処理
2614
2970
  */
@@ -2693,7 +3049,7 @@ const judgeRecovery = (_name, _difFrame, _j) => {
2693
3049
  if (_name === `shakin`) {
2694
3050
  quickRetry(`Shakin(Great)`);
2695
3051
  }
2696
- safeExecuteCustomHooks(`g_customJsObj.judg_${_name}`, g_customJsObj[`judg_${_name}`], _difFrame, _j);
3052
+ safeExecuteCustomHooks(`g_customJsObj.${judgHookName(_name, _j)}`, g_customJsObj[judgHookName(_name, _j)], _difFrame, _j);
2697
3053
  };
2698
3054
 
2699
3055
  /**
@@ -2708,7 +3064,7 @@ const judgeDamage = (_name, _difFrame, _j) => {
2708
3064
  comboJ.textContent = ``;
2709
3065
  diffJ.textContent = ``;
2710
3066
  lifeDamage();
2711
- safeExecuteCustomHooks(`g_customJsObj.judg_${_name}`, g_customJsObj[`judg_${_name}`], _difFrame, _j);
3067
+ safeExecuteCustomHooks(`g_customJsObj.${judgHookName(_name, _j)}`, g_customJsObj[judgHookName(_name, _j)], _difFrame, _j);
2712
3068
  };
2713
3069
 
2714
3070
  /**
@@ -2736,7 +3092,7 @@ const judgeMatari = (_difFrame, _j) => {
2736
3092
  finishViewing();
2737
3093
  quickRetry(`Matari(Good)`);
2738
3094
 
2739
- safeExecuteCustomHooks(`g_customJsObj.judg_matari`, g_customJsObj.judg_matari, _difFrame, _j);
3095
+ safeExecuteCustomHooks(`g_customJsObj.${judgHookName(`matari`, _j)}`, g_customJsObj[judgHookName(`matari`, _j)], _difFrame, _j);
2740
3096
  };
2741
3097
 
2742
3098
  /**
package/js/lib/result.js CHANGED
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Source by tickle
7
7
  * Created : 2026/09/13
8
- * Revised : 2026/09/23 (v51.0.0)
8
+ * Revised : 2026/11/08 (v52.2.0)
9
9
  *
10
10
  * https://github.com/cwtickle/danoniplus
11
11
  */
@@ -867,6 +867,8 @@ const getSelectedSettingList = (_orgShuffleFlg) => {
867
867
  withOptions(g_stateObj.swapping, C_FLG_OFF,
868
868
  `Swap:${getStgDetailName(g_stateObj.swapping)}${!_orgShuffleFlg && !g_stateObj.swapping.endsWith(`+`) ? getStgDetailName(`(S)`) : ``}`),
869
869
  withOptions(g_stateObj.judgRange, `Normal`, `Judg:${getStgDetailName(g_stateObj.judgRange)}`),
870
+ hasRingNotes() ? withOptions(g_stateObj.ringSpeed, 1, `RingSpd:${getStgDetailName(g_stateObj.ringSpeed)}`) : ``,
871
+ hasRingNotes() ? withOptions(g_stateObj.ringGuide, `Next`, `Guide:${getStgDetailName(g_stateObj.ringGuide)}`) : ``,
870
872
  ].filter(value => value !== ``).join(`, `);
871
873
 
872
874
  // Display設定の組み立て処理 (Ex: Step : FlatBar, Judge, Life : OFF)