<!-- canonical: https://efficientnewlanguage.org/eml-p/projects/P095-sensor-logger/ | updated: 2026-10-15 -->

# P095 Sensor logger

Temperature readings kept in a ring buffer of the last N, shown slot by slot with where the next write goes and how many were overwritten, and the average, lowest and highest over a window sliding along them, the extremes by a monotonic queue.

EML-P project `projects/sensor-logger` in the EML language repo: 2 module(s), entry `main.eml`, terminal UI. There, `eml project run projects/sensor-logger` runs it and `eml project verify projects/sensor-logger` replays every session under CPython (two hash seeds) and in the interpreter; the site build replays every session in the interpreter again and publishes a session only if its screen matches.

Built on verified corpus cases: ring-buffer-bounds-report (https://efficientnewlanguage.org/cases/196-ring-buffer-bounds-report/), sliding-window-slices (https://efficientnewlanguage.org/cases/210-sliding-window-slices/).

## Sessions

### bad-input - interpreter: byte-equal to the golden

Input:

```text
0
x
1
abc
1
21.55
1
70
1
-50.1
1

3
0
9
x

4
13
4
1
4

5
```

Screen:

```text
== Sensor logger ==
The last readings in a ring buffer, and a window sliding along them.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 0
Pick a number from 1 to 5.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> x
Pick a number from 1 to 5.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 1
readings (up to 20, like 21.5 22 -3.4)> abc
Type 1 to 20 readings from -50.0 to 60.0, at most one decimal each.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 1
readings (up to 20, like 21.5 22 -3.4)> 21.55
Type 1 to 20 readings from -50.0 to 60.0, at most one decimal each.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 1
readings (up to 20, like 21.5 22 -3.4)> 70
Type 1 to 20 readings from -50.0 to 60.0, at most one decimal each.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 1
readings (up to 20, like 21.5 22 -3.4)> -50.1
Type 1 to 20 readings from -50.0 to 60.0, at most one decimal each.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 1
readings (up to 20, like 21.5 22 -3.4)> 
Cancelled.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 3
window size (1 to 8)> 0
Type a whole number from 1 to 8.
window size (1 to 8)> 9
Type a whole number from 1 to 8.
window size (1 to 8)> x
Type a whole number from 1 to 8.
window size (1 to 8)> 
Cancelled.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 4
capacity (2 to 12)> 13
Type a whole number from 2 to 12.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 4
capacity (2 to 12)> 1
Type a whole number from 2 to 12.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 4
capacity (2 to 12)> 
Cancelled.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 5
Bye.
```

### basic - interpreter: byte-equal to the golden

Input:

```text
2
3
3
1
19.5 -2.3
2
3
8
4
4
2
1
1 2 3
1
4.5 5
2
3
2
5
```

Screen:

```text
== Sensor logger ==
The last readings in a ring buffer, and a window sliding along them.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 2
  slot   20.8  20.1  21.4  22.1  23.0  22.6  21.9  21.2
                        ^  next write
Holding 8 of 8; 2 overwritten so far.
Oldest to newest: 21.4 22.1 23.0 22.6 21.9 21.2 20.8 20.1

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 3
window size (1 to 8)> 3
  readings   average   lowest  highest
   1 to  3     22.17     21.4     23.0
   2 to  4     22.57     22.1     23.0
   3 to  5     22.50     21.9     23.0
   4 to  6     21.90     21.2     22.6
   5 to  7     21.30     20.8     21.9
   6 to  8     20.70     20.1     21.2
6 windows of 3 in 8 readings: each sum adds the reading coming in and drops the one going out.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 1
readings (up to 20, like 21.5 22 -3.4)> 19.5 -2.3
Logged 2; the 2 oldest readings were overwritten to make room.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 2
  slot   20.8  20.1  19.5  -2.3  23.0  22.6  21.9  21.2
                                    ^  next write
Holding 8 of 8; 4 overwritten so far.
Oldest to newest: 23.0 22.6 21.9 21.2 20.8 20.1 19.5 -2.3

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 3
window size (1 to 8)> 8
  readings   average   lowest  highest
   1 to  8     18.35     -2.3     23.0
1 window of 8 in 8 readings: it is all of them.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 4
capacity (2 to 12)> 4
A new, empty buffer of 4.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 2
  slot      -     -     -     -
            ^  next write
Holding 0 of 4; 0 overwritten so far.
Nothing logged yet.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 1
readings (up to 20, like 21.5 22 -3.4)> 1 2 3
Logged 3; nothing had to be overwritten.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 1
readings (up to 20, like 21.5 22 -3.4)> 4.5 5
Logged 2; the oldest reading was overwritten to make room.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 2
  slot    5.0   2.0   3.0   4.5
                  ^  next write
Holding 4 of 4; 1 overwritten so far.
Oldest to newest: 2.0 3.0 4.5 5.0

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 3
window size (1 to 4)> 2
  readings   average   lowest  highest
   1 to  2      2.50      2.0      3.0
   2 to  3      3.75      3.0      4.5
   3 to  4      4.75      4.5      5.0
3 windows of 2 in 4 readings: each sum adds the reading coming in and drops the one going out.

1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit
choice> 5
Bye.
```

## Modules

### main.eml

```eml
# P095 sensor logger: temperature readings kept in a ring buffer of the
# last N, the slots and where the next one goes, and the average, lowest
# and highest over a window sliding along what is kept.
import logger

def plain(s):
    0 => i
    len(s) => j
    while i < j and s[i] == " ":
        i + 1 => i
    while j > i and s[j - 1] == " ":
        j - 1 => j
    return s[i:j]

def tenths_of(s):
    # "21.5", "-3", "0.4" as whole tenths, or [] if not a reading from
    # -50.0 to 60.0 with at most one decimal.
    s => t
    1 => sg
    if len(t) > 1 and t[0] == "-":
        t[1:len(t)] => t
        -1 => sg
    0 => k
    while k < len(t) and t[k] != ".":
        k + 1 => k
    t[0:k] => whole
    "0" => frac
    if k < len(t):
        t[k + 1:len(t)] => frac
    if len(whole) == 0 or len(whole) > 2 or len(frac) != 1:
        return []
    0 => n
    for c in whole + frac:
        if not (c >= "0" and c <= "9"):
            return []
        n * 10 + int(c) => n
    sg * n => v
    if v < -500 or v > 600:
        return []
    return [v]

def show(v):
    if v < 0:
        return "-" + show(0 - v)
    return str(int((v - v % 10) / 10)) + "." + str(v % 10)

def average(s, k):
    # s tenths over k readings, in degrees to two places, halves away from 0.
    if s < 0:
        return "-" + average(0 - s, k)
    # hundredths: s / k tenths x 10 = 10 s / k
    20 * s + k => n
    2 * k => d
    0 => q
    while n >= d:
        n - d => n
        q + 1 => q
    "" => z
    if q % 100 < 10:
        "0" => z
    return str(int((q - q % 100) / 100)) + "." + z + str(q % 100)

def lpad(s, n):
    while len(s) < n:
        " " + s => s
    return s

def add(r):
    plain(input("readings (up to 20, like 21.5 22 -3.4)> ")) => s
    if s == "":
        "Cancelled." ^0
        return r
    [] => vs
    "" => w
    True => ok
    for c in s + " ":
        if c == " ":
            if w != "":
                tenths_of(w) => v
                if len(v) == 0:
                    False => ok
                else:
                    vs + v => vs
            "" => w
        else:
            w + c => w
    if not ok or len(vs) == 0 or len(vs) > 20:
        "Type 1 to 20 readings from -50.0 to 60.0, at most one decimal each." ^0
        return r
    r[3] => before
    for v in vs:
        logger.put(r, v) => r
    r[3] - before => gone
    if gone == 0:
        ("Logged " + str(len(vs)) + "; nothing had to be overwritten.") ^0
    elif gone == 1:
        ("Logged " + str(len(vs)) + "; the oldest reading was overwritten to make room.") ^0
    else:
        ("Logged " + str(len(vs)) + "; the " + str(gone) + " oldest readings were overwritten to make room.") ^0
    return r

def buffer(r):
    r[0] => slots
    "" => line
    for i in [0:len(slots) - 1]:
        "     -" => cell
        if i < r[2] or r[2] == len(slots):
            lpad(show(slots[i]), 6) => cell
        line + cell => line
    ("  slot " + line) ^0
    "" => arrow
    for i in [0:len(slots) - 1]:
        if i == r[1]:
            arrow + "     ^" => arrow
        else:
            arrow + "      " => arrow
    ("       " + logger_trim(arrow) + "  next write") ^0
    ("Holding " + str(r[2]) + " of " + str(len(slots)) + "; " + str(r[3]) + " overwritten so far.") ^0
    "" => seq
    for v in logger.in_order(r):
        if seq != "":
            seq + " " => seq
        seq + show(v) => seq
    if seq == "":
        "Nothing logged yet." ^0
    else:
        ("Oldest to newest: " + seq) ^0

def logger_trim(s):
    len(s) => j
    while j > 0 and s[j - 1] == " ":
        j - 1 => j
    return s[0:j]

def windows(r):
    logger.in_order(r) => xs
    if len(xs) == 0:
        "Nothing logged yet." ^0
        return
    while True:
        plain(input("window size (1 to " + str(len(xs)) + ")> ")) => s
        if s == "":
            "Cancelled." ^0
            return
        0 => k
        True => ok
        if len(s) > 2:
            False => ok
        for c in s:
            if c >= "0" and c <= "9":
                k * 10 + int(c) => k
            else:
                False => ok
        if ok and k >= 1 and k <= len(xs):
            logger.window_sums(xs, k) => sums
            logger.window_extremes(xs, k, False) => lows
            logger.window_extremes(xs, k, True) => highs
            "  readings   average   lowest  highest" ^0
            for i in [0:len(sums) - 1]:
                ("  " + lpad(str(i + 1), 2) + " to " + lpad(str(i + k), 2) + lpad(average(sums[i], k), 10) + lpad(show(lows[i]), 9) + lpad(show(highs[i]), 9)) ^0
            if len(sums) == 1:
                ("1 window of " + str(k) + " in " + str(len(xs)) + " readings: it is all of them.") ^0
            else:
                (str(len(sums)) + " windows of " + str(k) + " in " + str(len(xs)) + " readings: each sum adds the reading coming in and drops the one going out.") ^0
            return
        ("Type a whole number from 1 to " + str(len(xs)) + ".") ^0

def resize(r):
    plain(input("capacity (2 to 12)> ")) => s
    if s == "":
        "Cancelled." ^0
        return r
    if s == "2" or s == "3" or s == "4" or s == "5" or s == "6" or s == "7" or s == "8" or s == "9" or s == "10" or s == "11" or s == "12":
        ("A new, empty buffer of " + s + ".") ^0
        return logger.ring(int(s))
    "Type a whole number from 2 to 12." ^0
    return r

logger.ring(8) => r
for v in [205, 210, 214, 221, 230, 226, 219, 212, 208, 201]:
    logger.put(r, v) => r

"== Sensor logger ==" ^0
"The last readings in a ring buffer, and a window sliding along them." ^0
True => running
while running:
    "" ^0
    "1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit" ^0
    plain(input("choice> ")) => choice
    if choice == "1":
        add(r) => r
    elif choice == "2":
        buffer(r)
    elif choice == "3":
        windows(r)
    elif choice == "4":
        resize(r) => r
    elif choice == "5":
        False => running
    else:
        "Pick a number from 1 to 5." ^0
"Bye." ^0
```

Python projection of main.eml:

```python
import logger

def plain(s):
    i = 0
    j = len(s)
    while i < j and s[i] == " ":
        i = i + 1
    while j > i and s[j - 1] == " ":
        j = j - 1
    return s[i:j]

def tenths_of(s):
    t = s
    sg = 1
    if len(t) > 1 and t[0] == "-":
        t = t[1:len(t)]
        sg = -1
    k = 0
    while k < len(t) and t[k] != ".":
        k = k + 1
    whole = t[0:k]
    frac = "0"
    if k < len(t):
        frac = t[k + 1:len(t)]
    if len(whole) == 0 or len(whole) > 2 or len(frac) != 1:
        return []
    n = 0
    for c in whole + frac:
        if not (c >= "0" and c <= "9"):
            return []
        n = n * 10 + int(c)
    v = sg * n
    if v < -500 or v > 600:
        return []
    return [v]

def show(v):
    if v < 0:
        return "-" + show(0 - v)
    return str(int((v - v % 10) / 10)) + "." + str(v % 10)

def average(s, k):
    if s < 0:
        return "-" + average(0 - s, k)
    n = 20 * s + k
    d = 2 * k
    q = 0
    while n >= d:
        n = n - d
        q = q + 1
    z = ""
    if q % 100 < 10:
        z = "0"
    return str(int((q - q % 100) / 100)) + "." + z + str(q % 100)

def lpad(s, n):
    while len(s) < n:
        s = " " + s
    return s

def add(r):
    s = plain(input("readings (up to 20, like 21.5 22 -3.4)> "))
    if s == "":
        print("Cancelled.")
        return r
    vs = []
    w = ""
    ok = True
    for c in s + " ":
        if c == " ":
            if w != "":
                v = tenths_of(w)
                if len(v) == 0:
                    ok = False
                else:
                    vs = vs + v
            w = ""
        else:
            w = w + c
    if not ok or len(vs) == 0 or len(vs) > 20:
        print("Type 1 to 20 readings from -50.0 to 60.0, at most one decimal each.")
        return r
    before = r[3]
    for v in vs:
        r = logger.put(r, v)
    gone = r[3] - before
    if gone == 0:
        print("Logged " + str(len(vs)) + "; nothing had to be overwritten.")
    elif gone == 1:
        print("Logged " + str(len(vs)) + "; the oldest reading was overwritten to make room.")
    else:
        print("Logged " + str(len(vs)) + "; the " + str(gone) + " oldest readings were overwritten to make room.")
    return r

def buffer(r):
    slots = r[0]
    line = ""
    for i in range(0, len(slots)):
        cell = "     -"
        if i < r[2] or r[2] == len(slots):
            cell = lpad(show(slots[i]), 6)
        line = line + cell
    print("  slot " + line)
    arrow = ""
    for i in range(0, len(slots)):
        if i == r[1]:
            arrow = arrow + "     ^"
        else:
            arrow = arrow + "      "
    print("       " + logger_trim(arrow) + "  next write")
    print("Holding " + str(r[2]) + " of " + str(len(slots)) + "; " + str(r[3]) + " overwritten so far.")
    seq = ""
    for v in logger.in_order(r):
        if seq != "":
            seq = seq + " "
        seq = seq + show(v)
    if seq == "":
        print("Nothing logged yet.")
    else:
        print("Oldest to newest: " + seq)

def logger_trim(s):
    j = len(s)
    while j > 0 and s[j - 1] == " ":
        j = j - 1
    return s[0:j]

def windows(r):
    xs = logger.in_order(r)
    if len(xs) == 0:
        print("Nothing logged yet.")
        return
    while True:
        s = plain(input("window size (1 to " + str(len(xs)) + ")> "))
        if s == "":
            print("Cancelled.")
            return
        k = 0
        ok = True
        if len(s) > 2:
            ok = False
        for c in s:
            if c >= "0" and c <= "9":
                k = k * 10 + int(c)
            else:
                ok = False
        if ok and k >= 1 and k <= len(xs):
            sums = logger.window_sums(xs, k)
            lows = logger.window_extremes(xs, k, False)
            highs = logger.window_extremes(xs, k, True)
            print("  readings   average   lowest  highest")
            for i in range(0, len(sums)):
                print("  " + lpad(str(i + 1), 2) + " to " + lpad(str(i + k), 2) + lpad(average(sums[i], k), 10) + lpad(show(lows[i]), 9) + lpad(show(highs[i]), 9))
            if len(sums) == 1:
                print("1 window of " + str(k) + " in " + str(len(xs)) + " readings: it is all of them.")
            else:
                print(str(len(sums)) + " windows of " + str(k) + " in " + str(len(xs)) + " readings: each sum adds the reading coming in and drops the one going out.")
            return
        print("Type a whole number from 1 to " + str(len(xs)) + ".")

def resize(r):
    s = plain(input("capacity (2 to 12)> "))
    if s == "":
        print("Cancelled.")
        return r
    if s == "2" or s == "3" or s == "4" or s == "5" or s == "6" or s == "7" or s == "8" or s == "9" or s == "10" or s == "11" or s == "12":
        print("A new, empty buffer of " + s + ".")
        return logger.ring(int(s))
    print("Type a whole number from 2 to 12.")
    return r

r = logger.ring(8)
for v in [205, 210, 214, 221, 230, 226, 219, 212, 208, 201]:
    r = logger.put(r, v)
print("== Sensor logger ==")
print("The last readings in a ring buffer, and a window sliding along them.")
running = True
while running:
    print("")
    print("1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit")
    choice = plain(input("choice> "))
    if choice == "1":
        r = add(r)
    elif choice == "2":
        buffer(r)
    elif choice == "3":
        windows(r)
    elif choice == "4":
        r = resize(r)
    elif choice == "5":
        running = False
    else:
        print("Pick a number from 1 to 5.")
print("Bye.")
```

### logger.eml

```eml
# P095 sensor logger - readings in a ring buffer that keeps the last N and
# overwrites the oldest, and statistics over a window sliding along them.
# Readings are whole tenths of a degree (21.5 is 215). A buffer is
# [slots, next slot to write, how many are held, how many overwritten].

def ring(n):
    return [[0] * n, 0, 0, 0]

def put(r, value):
    # Write at the next slot and move it on, wrapping round; when the
    # buffer is full that slot held the oldest reading, which is lost. As in
    # the corpus case ring-buffer-bounds-report, every index stays inside
    # 0 .. N-1, so no read or write can go out of range.
    r[0] => slots
    len(slots) => n
    value => slots[r[1]]
    (r[1] + 1) % n => nxt
    r[2] => held
    r[3] => lost
    if held < n:
        held + 1 => held
    else:
        lost + 1 => lost
    return [slots, nxt, held, lost]

def in_order(r):
    # The held readings, oldest first: they start where the next write goes
    # once the buffer is full, at slot 0 before that.
    r[0] => slots
    len(slots) => n
    0 => start
    if r[2] == n:
        r[1] => start
    [0] * r[2] => out
    for k in [0:r[2] - 1]:
        slots[(start + k) % n] => out[k]
    return out

def window_sums(xs, k):
    # The sum of every full window of k in a row - as in the corpus case
    # sliding-window-slices, exactly len(xs) - k + 1 of them - by adding the
    # reading coming in and taking off the one going out.
    [] => out
    if k > len(xs):
        return out
    0 => s
    for i in [0:k - 1]:
        s + xs[i] => s
    out + [s] => out
    for i in [k:len(xs) - 1]:
        s + xs[i] - xs[i - k] => s
        out + [s] => out
    return out

def window_extremes(xs, k, biggest):
    # The largest (or smallest) of every full window, in one pass: a queue
    # of positions whose readings only fall (or only rise) from front to
    # back. A newcomer removes everything it beats from the back; the front
    # leaves when it slides out of the window. Each reading goes in once and
    # out at most once.
    [] => out
    [0] * len(xs) => q
    0 => head
    0 => tail
    for i in [0:len(xs) - 1]:
        True => going
        while going and tail > head:
            q[tail - 1] => j
            if (biggest and xs[j] <= xs[i]) or (not biggest and xs[j] >= xs[i]):
                tail - 1 => tail
            else:
                False => going
        i => q[tail]
        tail + 1 => tail
        if q[head] <= i - k:
            head + 1 => head
        if i >= k - 1:
            out + [xs[q[head]]] => out
    return out
```

Python projection of logger.eml:

```python
def ring(n):
    return [[0] * n, 0, 0, 0]

def put(r, value):
    slots = r[0]
    n = len(slots)
    slots[r[1]] = value
    nxt = (r[1] + 1) % n
    held = r[2]
    lost = r[3]
    if held < n:
        held = held + 1
    else:
        lost = lost + 1
    return [slots, nxt, held, lost]

def in_order(r):
    slots = r[0]
    n = len(slots)
    start = 0
    if r[2] == n:
        start = r[1]
    out = [0] * r[2]
    for k in range(0, r[2]):
        out[k] = slots[(start + k) % n]
    return out

def window_sums(xs, k):
    out = []
    if k > len(xs):
        return out
    s = 0
    for i in range(0, k):
        s = s + xs[i]
    out = out + [s]
    for i in range(k, len(xs)):
        s = s + xs[i] - xs[i - k]
        out = out + [s]
    return out

def window_extremes(xs, k, biggest):
    out = []
    q = [0] * len(xs)
    head = 0
    tail = 0
    for i in range(0, len(xs)):
        going = True
        while going and tail > head:
            j = q[tail - 1]
            if biggest and xs[j] <= xs[i] or not biggest and xs[j] >= xs[i]:
                tail = tail - 1
            else:
                going = False
        q[tail] = i
        tail = tail + 1
        if q[head] <= i - k:
            head = head + 1
        if i >= k - 1:
            out = out + [xs[q[head]]]
    return out
```

## README

# P095 - Sensor logger

Temperature readings kept in a ring buffer of the last N. The program
shows the buffer slot by slot, with where the next write goes and how
many readings have been overwritten. It also gives the average, lowest
and highest over a window sliding along the readings. It starts with ten
readings in a buffer of eight, so two are already gone.

- `main.eml` - the menu, reading temperatures, the buffer and the window
  table
- `logger.eml` - the ring buffer, its readings in order, window sums and
  window extremes

How each part works:

- Readings are whole tenths of a degree (21.5 is 215), so sums and
  averages are exact.
- The ring buffer writes at the next slot and moves on, wrapping round to
  slot 0. Once it is full, that slot held the oldest reading, which is
  overwritten. As in the corpus case `ring-buffer-bounds-report`, every
  index stays inside 0 to N - 1, so nothing can be read or written out of
  range. The oldest reading sits where the next write will go.
- Windows, as in the corpus case `sliding-window-slices`: only full
  windows, len - k + 1 of them. Each window's sum adds the reading coming
  in and takes off the one going out.
- The lowest and highest come from a queue of positions whose readings
  only rise (or only fall) from front to back. A new reading removes from
  the back everything it beats, and the front leaves when it slides out of
  the window. Each reading enters once and leaves at most once, so this is
  one pass, whatever the window size.

What is checked: menu choices 1 to 5; 1 to 20 readings at a time, each
from -50.0 to 60.0 with at most one decimal; a window from 1 to the number
of readings held; a capacity from 2 to 12. An empty answer cancels.

Sessions: `sessions/basic.in` gives:
- the starting buffer, wrapped round, and windows of 3;
- two more readings overwriting the two oldest; the one window of 8;
- a new buffer of 4: three readings, then two more, the second
  overwriting the oldest; windows of 2.

`sessions/bad-input.in` gives:
- menu choices 0 and x;
- the readings abc, 21.55, 70 and -50.1;
- window sizes 0, 9 and x; capacities 13 and 1; then cancels.

Built on the verified corpus cases `ring-buffer-bounds-report` (a fixed
buffer whose indices never leave it) and `sliding-window-slices` (full
windows, counted at both ends).
