openlang

Coroutine proposer: asked for amber, got green, and history shows only the last tile

Building the tile picker for a palette toy (mobile app, testing headless with lua main.lua and a scripted list of decisions before the touch UI). Two things look wrong and I can't tell if it's one bug or two.

  1. A rejection with want = "amber" (third in the palette) gets me x=2 color=green next. Plain rejections are fine.
  2. The history dump at the end prints the same color and x for every entry, the last proposal's (x=3 color=green, over and over). The grid still ends up right.

The resume prints show the decisions arriving correctly, so I doubt it's an off-by-one resume (see the coroutine.resume docs). Is history reading the grid late?

The coroutine:

function Proposer.new(palette, start_x)
  local proposal = { x = start_x, color = nil }
  return coroutine.create(function()
    local x = start_x
    while true do
      local c = palette:next()
      proposal.x, proposal.color = x, c
      local decision = coroutine.yield(proposal)
      if decision.accept then
        x = x + 1
      elseif decision.want then
        palette:seek(decision.want)
      end
    end
  end)
end

And the part of Session:step that records history:

table.insert(self.history, { tile = self.pending, decision = decision })
if decision.accept then
  self.grid[self.pending.x] = self.pending.color.name
end

Where does the amber get lost?

The conversation continues 8

Follow the reasoning
  1. Permalink

    The coroutine yields the same proposal table every time, so history just stores references to one table. Yield a fresh table per iteration.

  2. Permalink

    The coroutine yields the same proposal table every time

    Yes, that's it, and it explains #2 (the history dump). A fresh table per yield fixes it. The amber is probably a separate bug, in seek.

  3. Permalink

    history just stores references to one table

    Right, that matches the dump exactly. (Same table, so every entry shows the last values.)

    Quick question on where the fix belongs. Would you copy in the coroutine, a fresh { x = x, color = c } per yield, or in Session:step when it records history? The second feels safer if someone else ever yields a reused table. Or is that overkill?

  4. Permalink

    Fix it in the coroutine. That's where the bug lives, and the producer should own what it hands out. Copying in Session:step works too, but then it hides the aliasing, and the next consumer that holds a reference gets bitten the same way.

    If you want a belt-and-braces, a shallow copy at record time costs almost nothing. Overkill, but cheap overkill.

  5. Permalink

    Can you show palette:seek and palette:next? If seek sets the index to amber and next increments before returning, you'd get the color after amber, which is green.

  6. Permalink

    Your seek theory fits, marmalade, but one detail: want is the string "amber", not 3, so seek is resolving a name to an index first. That's the second place an off-by-one can hide, besides the pre-increment in next.

    Cheap check: have the coroutine yield after seek and print the index before next runs. If it already says 3, then next is the culprit.

    On the aliasing, the part that matters for the fix is in §3.4.9. A table constructor builds a new table each time it's evaluated. So { x = x, color = c } inside the loop gives a fresh one per yield.

  7. Permalink

    Good point, I skipped the string-to-index step. In fact that's where I'd look first, since a name lookup that counts from 0 while next counts from 1 would give green by itself. Printing the index right after seek settles it, no? Then next is cleared or blamed.

Other sides of the same question

  1. A status region that reads Saved twice

    The screen reader said "Saved" twice after one save. The state behind it has a single transition into saved, and the reducer…

    Replies 3Latest activity typescriptjavascript
  2. One health bar went red and took the rest with it

    Got bitten by shared resources in Godot UI this time. Every unit in my little prototype has a health bar scene, a ProgressBar…

    Replies 4Latest activity gdscript
  3. The label changed, but did the screen show it?

    Hello ! I'm building a small sales report page (Vite dev server, TypeScript) and I'm confused about a label. Click "Load repo…

    Replies 7Latest activity javascripttypescript