Timeline: safely calculate the fraction of a very large timespan

...using a requantisation trick to cancel out some factors in the
product of two rational numbers, allowing to calculate the product
without actual multiplication of (dangerously large) numbers.

with these additional safeguards, the anchorWindowAtPosition()
succeeds without Integer-wrap, but the result is not fully correct
(some further calculation error hidden somewhere??)
This commit is contained in:
Fischlurch 2022-11-29 02:00:41 +01:00
parent b898f1514b
commit 7007101357
6 changed files with 215 additions and 16 deletions

View file

@ -139,6 +139,13 @@ namespace util {
< 63;
}
inline bool
can_represent_Product (Rat a, Rat b)
{
return can_represent_Product(a.numerator(), b.numerator())
and can_represent_Product(a.denominator(), b.denominator());
}
inline bool
can_represent_Sum (Rat a, Rat b)
{

View file

@ -283,8 +283,11 @@ namespace time {
/** constant to indicate "no duration" */
const Duration Duration::NIL (Time::ZERO);
const Duration Duration::NIL {Time::ZERO};
/** maximum possible temporal extension */
const Duration Duration::MAX {Offset{Time::MIN, Time::MAX}};
}} // namespace lib::Time

View file

@ -480,6 +480,7 @@ namespace time {
{ }
static const Duration NIL;
static const Duration MAX ;
void accept (Mutation const&);

View file

@ -111,6 +111,8 @@ namespace model {
using util::Rat;
using util::rational_cast;
using util::can_represent_Product;
using util::reQuant;
using util::min;
using util::max;
@ -166,7 +168,7 @@ namespace model {
const FSecs DEFAULT_CANVAS{23};
const Rat DEFAULT_METRIC{25};
const uint MAX_PX_WIDTH{1000000};
const FSecs MAX_TIMESPAN{_FSecs(Time::MAX-Time::MIN)};
const FSecs MAX_TIMESPAN{_FSecs(Duration::MAX)};
const FSecs MICRO_TICK{1_r/Time::SCALE};
/** Maximum quantiser to be handled in fractional arithmetics without hazard.
@ -174,15 +176,15 @@ namespace model {
* DENOMINATOR * Time::Scale has to stay below INT_MAX, with some safety margin
*/
const int64_t LIM_HAZARD{int64_t{1} << 40 };
const int64_t HAZARD_DEGREE{util::ilog2(LIM_HAZARD)};
inline int
toxicDegree (Rat poison)
toxicDegree (Rat poison, const int64_t THRESHOLD =HAZARD_DEGREE)
{
const int64_t HAZARD_DEGREE{util::ilog2(LIM_HAZARD)};
int64_t magNum = util::ilog2(abs(poison.numerator()));
int64_t magDen = util::ilog2(abs(poison.denominator()));
int64_t degree = max (magNum, magDen);
return max (0, degree - HAZARD_DEGREE);
return max (0, degree - THRESHOLD);
}
}
@ -426,7 +428,7 @@ namespace model {
setVisiblePos (Rat percentage)
{
FSecs canvasDuration{afterAll_-startAll_};
anchorWindowAtPosition (canvasDuration*percentage);
anchorWindowAtPosition (scaleSafe (canvasDuration, percentage));
fireChangeNotification();
}
@ -495,18 +497,48 @@ namespace model {
* is thus specific to the ZoomWindow implementation. To sanitise, the denominator
* is reduced logarithmically (bit-shift) sufficiently and then used as new quantiser,
* thus ensuring that both denominator (=quantiser) and numerator are below limit.
* @warning the rational number must not be to large overall; this heuristic will fail
* on fractions with very large numerator and small denominator however, for
* the ZoomWindow, this case is not relevant, since the zoom factor is limited,
* and other usages of rational numbers can be range checked explicitly.
* @note the check is based on the 2-logarithm of numerator and denominator, which is
* pretty much the fastest possibility (even a simple comparison would have
* to do the same). Values below threshold are simply passed-through.
* to do the same). Values below threshold are simply passed-through.
*/
static Rat
detox (Rat poison)
{
int toxicity = toxicDegree (poison);
return toxicity ? util::reQuant(poison, poison.denominator() >> toxicity)
return toxicity ? reQuant (poison, max (poison.denominator() >> toxicity, 64))
: poison;
}
/**
* Scale a possibly large time duration by a rational factor, while attempting to avoid
* integer wrap-around. Obviously this is only a heuristic, yet adequate within the
* framework of ZoomWindow, where the end result is pixel aligned anyway.
*/
static FSecs
scaleSafe (FSecs duration, Rat factor)
{
auto approx = [](Rat r){ return rational_cast<double> (r); };
if (not util::can_represent_Product(duration, factor))
{
if (approx(MAX_TIMESPAN) < approx(duration) * approx (factor))
return MAX_TIMESPAN; // exceeds limits of time representation => cap the result
// slightly adjust the factor so that the time-base denominator cancels out,
// allowing to calculate the product without dangerous multiplication of large numbers
factor = 1_r / reQuant (1_r/factor, duration.denominator());
return detox (duration * factor);
}
else
// just calculate ordinary numbers...
return duration * factor;
}
static Rat
establishMetric (uint pxWidth, Time startWin, Time afterWin)
{
@ -732,7 +764,7 @@ namespace model {
FSecs duration{afterWin_-startWin_};
Rat posFactor = canvasOffset / FSecs{afterAll_-startAll_};
posFactor = parabolicAnchorRule (posFactor); // also limited 0...1
FSecs partBeforeAnchor = posFactor * duration;
FSecs partBeforeAnchor = scaleSafe (duration, posFactor);
startWin_ = startAll_ + (canvasOffset - partBeforeAnchor);
establishWindowDuration (duration);
startAll_ = min (startAll_, startWin_);
@ -815,6 +847,8 @@ namespace model {
parabolicAnchorRule (Rat posFactor)
{
posFactor = util::limited (0, posFactor, 1);
if (toxicDegree(posFactor, 20)) // prevent integer wrap
posFactor = util::reQuant(posFactor, 1 << 20);
posFactor = (2*posFactor - 1); // -1 ... +1
posFactor = posFactor*posFactor*posFactor; // -1 ... +1 but accelerating towards boundaries
posFactor = (posFactor + 1) / 2; // 0 ... 1

View file

@ -399,9 +399,11 @@ namespace test {
win.setVisiblePos(0.50);
CHECK (win.visible() == TimeSpan(_t((40/2-16) -8), FSecs(16))); // window positioned to centre of canvas
CHECK (win.visible().start() == _t(-4)); // (canvas was already positioned asymmetrically)
win.setVisiblePos(-0.50);
CHECK (win.visible() == TimeSpan(_t(-16-40/2), FSecs(16))); // relative positioning not limited at lower bound
CHECK (win.visible().start() == _t(-36)); // (causing also further expansion of canvas)
win.setVisiblePos(_t(200)); // absolute positioning likewise not limited
CHECK (win.visible() == TimeSpan(_t(200-16), FSecs(16))); // but anchored according to relative anchor pos
CHECK (win.px_per_sec() == 80); // metric retained
@ -580,13 +582,16 @@ namespace test {
}
/** @test verify ZoomWindow code can handle "poisonous" Zoom-Factor parameters
/** @test verify ZoomWindow code can handle "poisonous" fractional number parameters
* - toxic zoom factor passed through ZoomWindow::setMetric(Rat)
* - toxic proportion factor passed to ZoomWindow::setVisiblePos(Rat)
* - indirectly cause toxic posFactor in ZoomWindow::anchorWindowAtPosition(FSecs)
* by providing a target position very far off current window location
*/
void
safeguard_poisonousMetric()
{
ZoomWindow win{};
ZoomWindow win{};
CHECK (win.visible() == win.overallSpan()); // by default window spans complete canvas
CHECK (win.visible().duration() == _t(23)); // ...and has just some handsome extension
CHECK (win.px_per_sec() == 25);
@ -624,6 +629,26 @@ namespace test {
CHECK(856.350708f == rational_cast<float> (_FSecs(win.visible().duration())));
CHECK (win.px_per_sec() == 575000000_r/856350691); // the new metric however is comprised of sanitised fractional numbers
CHECK (win.pxWidth() == 575); // and the existing pixel width was not changed
SHOW_EXPR(win.overallSpan());
SHOW_EXPR(_raw(win.visible().duration()));
SHOW_EXPR(win.px_per_sec());
SHOW_EXPR(win.pxWidth());
win.setVisiblePos (poison); // Yet another way to sneak in our toxic value...
SHOW_EXPR(win.overallSpan());
SHOW_EXPR(_raw(win.overallSpan()));
SHOW_EXPR(_raw(win.overallSpan().duration()));
SHOW_EXPR(_raw(win.visible().duration()));
SHOW_EXPR(win.px_per_sec());
SHOW_EXPR(win.pxWidth());
SHOW_EXPR(_raw(win.overallSpan().duration()) * rational_cast<double> (poison))
Time targetPos{TimeValue{gavl_time_t(_raw(win.overallSpan().duration()) * rational_cast<double> (poison))}};
SHOW_EXPR(targetPos);
SHOW_EXPR(_raw(targetPos));
SHOW_EXPR(_raw(win.visible().start()))
SHOW_EXPR(_raw(win.visible().end()))
SHOW_EXPR(bool(win.visible().start() < targetPos))
SHOW_EXPR(bool(win.visible().end() > targetPos))
}

View file

@ -39764,9 +39764,48 @@
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1668267251650" ID="ID_1280626173" MODIFIED="1668267290312" TEXT="potentieller TimeValue-wrap">
<icon BUILTIN="broken-line"/>
<node CREATED="1669507976363" ID="ID_1360914257" MODIFIED="1669508005798" TEXT="Duration und Canvas-size k&#xf6;nnen gro&#xdf; sein">
<icon BUILTIN="messagebox_warning"/>
</node>
<node CREATED="1669507940458" ID="ID_355998747" MODIFIED="1669507940458" TEXT="Rat posFactor = canvasOffset / FSecs{afterAll_-startAll_};"/>
<node CREATED="1669507965470" ID="ID_1858701509" MODIFIED="1669507965470" TEXT="FSecs partBeforeAnchor = posFactor * duration;"/>
</node>
</node>
<node CREATED="1668267393360" ID="ID_1700728412" MODIFIED="1668267398176" TEXT="giftige Br&#xfc;che">
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1669508070174" ID="ID_171647736" MODIFIED="1669508178509" TEXT="Aua: so ziemlich alles kann hier giftig sein">
<linktarget COLOR="#e32c65" DESTINATION="ID_171647736" ENDARROW="Default" ENDINCLINATION="8;76;" ID="Arrow_ID_1341085031" SOURCE="ID_196873456" STARTARROW="None" STARTINCLINATION="577;-36;"/>
<icon BUILTIN="broken-line"/>
<node CREATED="1669508081900" ID="ID_1740115162" MODIFIED="1669508382889" TEXT="zwar kann das Ergebnis nicht grob entgleisen">
<icon BUILTIN="button_ok"/>
</node>
<node CREATED="1669508094694" ID="ID_1971396713" MODIFIED="1669508365178" TEXT="konsistentes Verhalten ist aber schwer aufrecht zu erhalten">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
und zwar dann, wenn auch noch die Window-Parameter extrem sind &#8212; dann sieht die Lage ziemlich hoffnungslos aus
</p>
</body>
</html></richcontent>
<icon BUILTIN="messagebox_warning"/>
</node>
<node CREATED="1669508366255" ID="ID_1644190283" MODIFIED="1669682752758" TEXT="Idee: auf gleichen Nenner normalisieren">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
...denn dadurch w&#252;rde man die kreuzweise Multiplikation verhindern
</p>
</body>
</html></richcontent>
<arrowlink COLOR="#fbf8c7" DESTINATION="ID_267834802" ENDARROW="Default" ENDINCLINATION="538;-591;" ID="Arrow_ID_1957682133" STARTARROW="None" STARTINCLINATION="668;44;"/>
<icon BUILTIN="idea"/>
</node>
</node>
<node CREATED="1668267422500" ID="ID_17566048" MODIFIED="1668267468507" TEXT="parabolicAnchorRule">
<arrowlink COLOR="#e92c3f" DESTINATION="ID_1689286236" ENDARROW="Default" ENDINCLINATION="82;-4;" ID="Arrow_ID_176217068" STARTARROW="None" STARTINCLINATION="-158;21;"/>
</node>
@ -39883,8 +39922,12 @@
<node COLOR="#338800" CREATED="1669478154709" ID="ID_499422673" MODIFIED="1669488173372" TEXT="Fall-2 : hinreichend gro&#xdf;er Canvas">
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1669479930775" ID="ID_196873456" MODIFIED="1669480034941" TEXT="Fall-3 : setVisiblePos mit giftigem faktor">
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1669479930775" ID="ID_196873456" MODIFIED="1669508183093" TEXT="Fall-3 : setVisiblePos mit giftigem faktor">
<arrowlink COLOR="#e32c65" DESTINATION="ID_171647736" ENDARROW="Default" ENDINCLINATION="8;76;" ID="Arrow_ID_1341085031" STARTARROW="None" STARTINCLINATION="577;-36;"/>
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1669682888293" HGAP="67" ID="ID_1431682003" MODIFIED="1669682917272" TEXT="erweist sich als ziemlich schwer unter Kontrolle zu bekommen" VSHIFT="-1">
<icon BUILTIN="broken-line"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1669480019215" ID="ID_930072077" MODIFIED="1669480034942" TEXT="Fall-4 : setVisiblePos mit extrem entfernter Position">
<icon BUILTIN="flag-yellow"/>
@ -39904,7 +39947,7 @@
<linktarget COLOR="#e62f5c" DESTINATION="ID_651838862" ENDARROW="Default" ENDINCLINATION="115;-4;" ID="Arrow_ID_1930230162" SOURCE="ID_1257614207" STARTARROW="None" STARTINCLINATION="-189;14;"/>
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1668270062492" ID="ID_1671513743" MODIFIED="1668736241536" TEXT="entgiftungs-Methode schaffen">
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1668270062492" ID="ID_1671513743" MODIFIED="1669680251895" TEXT="entgiftungs-Methoden schaffen">
<linktarget COLOR="#bf0f5b" DESTINATION="ID_1671513743" ENDARROW="Default" ENDINCLINATION="297;-23;" ID="Arrow_ID_389982782" SOURCE="ID_1175951405" STARTARROW="None" STARTINCLINATION="-323;20;"/>
<linktarget COLOR="#9a858e" DESTINATION="ID_1671513743" ENDARROW="Default" ENDINCLINATION="297;-23;" ID="Arrow_ID_1815772846" SOURCE="ID_148236436" STARTARROW="None" STARTINCLINATION="-292;20;"/>
<linktarget COLOR="#b18586" DESTINATION="ID_1671513743" ENDARROW="Default" ENDINCLINATION="256;-19;" ID="Arrow_ID_882084800" SOURCE="ID_773636992" STARTARROW="None" STARTINCLINATION="-201;12;"/>
@ -40275,12 +40318,37 @@
<icon BUILTIN="idea"/>
<node CREATED="1668705828544" ID="ID_27322947" MODIFIED="1668705883187" TEXT="lb(z&#xe4;hler) - lb(nenner) &#x27fc; zusatz-Faktor"/>
<node CREATED="1668705906923" ID="ID_21115436" MODIFIED="1668705925067" TEXT="bei &#xdc;berschu&#xdf; den Quantiser entsprechend reduzieren"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1669508782375" ID="ID_1328009916" MODIFIED="1669673876153" TEXT="Vorsicht: Quantiser kann Null werden">
<arrowlink COLOR="#dd2238" DESTINATION="ID_507738124" ENDARROW="Default" ENDINCLINATION="93;-57;" ID="Arrow_ID_377645167" STARTARROW="None" STARTINCLINATION="-217;15;"/>
<icon BUILTIN="messagebox_warning"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1669673789135" ID="ID_846083949" MODIFIED="1669673797869" TEXT="dieser Ansatz hat seine Grenzen">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1669673800862" ID="ID_507738124" MODIFIED="1669673869569" TEXT="absolut sehr gro&#xdf;e Br&#xfc;che k&#xf6;nnen wir nicht handhaben">
<linktarget COLOR="#dd2238" DESTINATION="ID_507738124" ENDARROW="Default" ENDINCLINATION="93;-57;" ID="Arrow_ID_377645167" SOURCE="ID_1328009916" STARTARROW="None" STARTINCLINATION="-217;15;"/>
</node>
<node CREATED="1669673828418" ID="ID_1875664128" MODIFIED="1669673980107" TEXT="f&#xfc;r solche F&#xe4;lle w&#xfc;rde sogar die &quot;Bereinigung&quot; selber entgleisen">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Und zwar, wenn der Nenner viel kleiner ist als der Z&#228;hler, und der Z&#228;hler extrem gro&#223;. Dann w&#252;rde n&#228;mlich die Ganzzahl-Division keine signifikante Verringerung der Dimension bewirken, und die anschlie&#223;ende re-Quantisierung das Ergebnis (bedingt durch die Normierung auf einen gemeinsamen Nenner) sogar noch vergr&#246;&#223;ern
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1669673981445" ID="ID_1599443715" MODIFIED="1669673998427" TEXT="allerdings f&#xfc;r Zoom-Faktoren k&#xf6;nnen wir das ausschlie&#xdf;en">
<icon BUILTIN="idea"/>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1668705939639" ID="ID_393310295" MODIFIED="1668736180984" TEXT="Implementierung">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1668705939639" ID="ID_393310295" MODIFIED="1669680301519" TEXT="allgemeiner Entgiftungs-Filter">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1668705949188" ID="ID_510681740" MODIFIED="1668707393273" TEXT="2-er-Logarithmen berechnen">
<icon BUILTIN="button_ok"/>
<node CREATED="1668705960324" ID="ID_1999378206" MODIFIED="1668705972646" TEXT="ist die effizientest m&#xf6;gliche L&#xf6;sung">
@ -40300,6 +40368,26 @@
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1669680303553" ID="ID_943604336" MODIFIED="1669680329806" TEXT="Handhabung extremer Zeitspannen">
<icon BUILTIN="pencil"/>
<node CREATED="1669680334284" ID="ID_1144877898" MODIFIED="1669680352726" TEXT="Probleme">
<node CREATED="1669680353674" ID="ID_380004829" MODIFIED="1669680374859" TEXT="Summen mit Br&#xfc;chen"/>
<node CREATED="1669680375968" ID="ID_861847594" MODIFIED="1669680389409" TEXT="Verh&#xe4;ltnis-Berechnung"/>
<node CREATED="1669680390181" ID="ID_136457686" MODIFIED="1669680403199" TEXT="Anteil-Berechnung"/>
</node>
<node CREATED="1669680416417" ID="ID_264195573" MODIFIED="1669680423082" TEXT="L&#xf6;sungs-Techniken">
<icon BUILTIN="idea"/>
<node CREATED="1669680424602" ID="ID_267834802" MODIFIED="1669682824592" TEXT="K&#xfc;rzungs-Trick">
<linktarget COLOR="#fbf8c7" DESTINATION="ID_267834802" ENDARROW="Default" ENDINCLINATION="538;-591;" ID="Arrow_ID_1957682133" SOURCE="ID_1644190283" STARTARROW="None" STARTINCLINATION="668;44;"/>
<node CREATED="1669680431912" ID="ID_180315930" MODIFIED="1669680447650" TEXT="einen Partner re-Quantisieren"/>
<node CREATED="1669680448333" ID="ID_969768863" MODIFIED="1669680473406" TEXT="so da&#xdf; sich dann kreuzweise ein Z&#xe4;hler/Nenner wegk&#xfc;rzen"/>
<node CREATED="1669680479307" ID="ID_503307048" MODIFIED="1669680497907" TEXT="&#x27f9; danach k&#xf6;nnen wir Produkt oder Quotient ohne Multipliaktion berechnen"/>
</node>
<node COLOR="#338800" CREATED="1669682825470" ID="ID_906501375" MODIFIED="1669682838860" TEXT="verallgemeinert: scaleSafe()">
<icon BUILTIN="button_ok"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1668707398858" ID="ID_1529383789" MODIFIED="1668707464339" TEXT="Test">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1668707465449" ID="ID_1678870601" MODIFIED="1668736173820" TEXT="toxicDegree mit einigen Beispielen demonstrieren">
@ -40307,6 +40395,47 @@
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1668707479892" ID="ID_1964644327" MODIFIED="1668707490200" TEXT="ansonsten durch Ausreizen der Extremwerte testen">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1669682928809" ID="ID_499417740" MODIFIED="1669682938471" TEXT="setVisiblePos(gift)">
<icon BUILTIN="pencil"/>
<node CREATED="1669682950712" ID="ID_1730356591" MODIFIED="1669682974366" TEXT="macht was Anderes als nur die Zoom-Metrik anpassen"/>
<node CREATED="1669682975415" ID="ID_1386857509" MODIFIED="1669682993901" TEXT="die Verbindung mit L&#xe4;ngen/Positions-Kalkulationen ist gef&#xe4;hrlich">
<icon BUILTIN="messagebox_warning"/>
</node>
<node COLOR="#338800" CREATED="1669683004871" ID="ID_1503834170" MODIFIED="1669683025116" TEXT="wrap-around bekomme ich mit detox() und scaleSafe() in den Griff">
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1669683026004" ID="ID_1867241917" MODIFIED="1669683139814" TEXT="Ergebnis ist aber noch nicht plausibel">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
<font face="Monospaced">#--&#9670;--# _raw(win.overallSpan().duration()) ? = 307445734561825860 </font>
</p>
<p>
<font face="Monospaced">#--&#9670;--# _raw(targetPos) ?&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;= 206435633551724864 </font>
</p>
<p>
<font face="Monospaced">#--&#9670;--# _raw(win.visible().start()) ?&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;=&#160;&#160;&#160;2248731193323487 </font>
</p>
<p>
<font face="Monospaced">#--&#9670;--# _raw(win.visible().end()) ?&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;=&#160;&#160;&#160;2248732049674178 </font>
</p>
<p>
<font face="Monospaced">#--&#9670;--# bool(win.visible().start() &lt; targetPos) ? = 1 </font>
</p>
<p>
<font face="Monospaced">#--&#9670;--# bool(win.visible().end() &gt; targetPos) ? = 0 </font>
</p>
</body>
</html></richcontent>
<icon BUILTIN="broken-line"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1669683157298" ID="ID_710000990" MODIFIED="1669683199793" TEXT="Fenster liegt verd&#xe4;chtig weit daneben">
<icon BUILTIN="stop-sign"/>
</node>
</node>
</node>
</node>
</node>
</node>