744 Commits

Author SHA1 Message Date
3eaa3e23bd A macro module keeps its own prelude, and --x86 gets the merged daemon
flan dev's merged build is the program and the compiler in one -rdynamic
executable, so it exports every flan.* body it has, and ELF gives it precedence
over anything dlopened afterwards. The compiler expands a macro by dlopening a
module into that same process, and the module is built by Emit.program whatever
backend the session uses -- so under --x86 the caller was LLVM's and the body it
landed in was the dev backend's, which is a crossed pair. It died with SIGSEGV
inside flan.[clamp] during the first expansion, before the program had run a
line, and Dev.start refused the combination rather than do that.

Build.macro_module now asks Emit.program for hidden visibility on the module's
own Flan definitions. There is nothing left for the host to interpose, and the
flan.macro.* thunks stay exported because dlsym is how the compiler reaches
them -- nm -D on the built module lists those three and nothing else of Flan's.
The -Wl,-Bsymbolic that had been binding everything locally since 65d14f4 goes
with it: the module links its own flan_rt.c, and binding that locally aimed its
calls at a runtime flan_rt_init never ran on, with a null flan_exit_hook, so a
trap raised inside an expansion would have exited the process instead of parking
it.

Nothing about the host moved, which is what keeps redefinition modules reaching
its cells, its globals and flan_dev_cell. hidden defaults to false, and the 540
IR files this compiler emits for the test corpus are byte-identical to the ones
before it.

test_dev.ml's assertion that the merged daemon refuses --x86 becomes the session
it was standing in for: dev-macro.flan calls a prelude macro at the top level,
so the daemon coming up at all is the old crash not happening, and one build
then carries C-x C-e, a C-c C-c whose body calls a macro again, the park and the
rerun.
2026-09-17 19:50:29 +07:00
9e80147527 What landed, and the one thing re-run cannot do 2026-09-17 19:11:08 +07:00
dd7a4eaea7 C-x C-e on a top-level form installs it
# Conflicts:
#	emacs/flan-dev.el
2026-09-17 19:10:11 +07:00
7704463ecd A closed window parks the program instead of ending the session 2026-09-17 19:08:45 +07:00
0d72835842 The escape hatch says which hatch it is when asked for a re-run
--two-process has no parked thread to wake. Its program is a child, and a
child that finishes is gone, so running main again is not something that shape
can do — and saying "the program is already running" would send somebody back
to try again after it had exited. It names itself instead.

--x86 arrives here too and is the reason this is worth a round trip rather
than none: --x86 refuses the merged daemon, for the -rdynamic reason the case
below already records, so every x86 dev loop is a two-process one and this is
the answer it gets.
2026-09-17 19:08:12 +07:00
836c23295c Two sentences a parked program was still getting wrong
basis had the same false promise the eval note did. It branches on the break
state, which answers "running" while the program is parked — the agent's
listener is alive and nothing has stopped — so disassemble reported that a body
delivered while parked installs at the next frame boundary, which is not a
boundary the program will reach until somebody runs it again.

And flan-rerun had no autoload. The keymap binds it, and flan-mode.el says in
as many words why every command there needs a real autoload rather than a
declare-function: a user who loaded only flan-mode would otherwise find the key
bound to nothing at the exact moment they most want it.
2026-09-17 19:01:49 +07:00
0c523cfe8b The program can be run again, in the process that is already there
You run a program under flan dev, it opens a raylib window, you close the
window, main returns — and there is no way to get another window short of
flan-dev-restart-program, which throws away the build, the session and every
global with it. In Common Lisp or Clojure the image outlives main, so you call
it again. The process here already outlived main: the exit hook flushed, closed
stdout and sat in for (;;) pause(). Nothing could wake it.

So main() is a loop. The hook records the status and longjmps back into a
setjmp in main() — there is no return available, since flan_exit is reached
from wherever the program happened to be — and the thread waits on a condition
variable until the new rerun op signals it. The main thread is the one that
runs main again: a window belongs to the thread that opened it, and on macOS to
the first thread of the process. A longjmp pops no frame, so the park first
empties the handler stack, the restart stack and the shadow frame chain, each
of which was a chain of allocas in stack the next run is about to write over.
Nothing else is reset; the second run reads whatever the first left in the
globals, which is the semantics that was asked for.

Closing stdout had to go with it. That was how the compiler learned the program
was done, but a pipe delivers EOF once, so the signal and the program's output
were the same resource and spending it left the second run with nowhere to
print. The descriptor hazard the old code reopened /dev/null for goes away with
the close that caused it. Liveness is asked for instead, through a weak symbol
in the same style as the agent's, and is now three states rather than two: Live,
Parked and Gone. Every guard branches on that before consulting the break
state, because the agent's listener answers "running" while the program is
parked and telling somebody whose program has finished that it is running is
worse than saying nothing. Only eval accepts a parked program — it queues and
waits for nothing, and the queued module installs at the first frame boundary
of the next run, so a body can be fixed while parked and the re-run executes
it. Everything else needs a frame boundary or a stopped stack, has neither, and
says which, naming the command that gets the program back.

A re-run while the program is running is refused rather than queued: the test
and the signal happen under one mutex, so two mains writing the same globals at
once never starts.

:parked rides on every reply beside :stopped, for the reason :stopped does —
finishing is as unannounced as stopping, more so when the way it happens is a
mouse click on a title bar. Emacs shows flan:parked in the modeline and binds
flan-rerun to C-c C-M-x.
2026-09-17 19:01:01 +07:00
ce3b62340b C-x C-e evaluates the form it is given, declaration or not
A defvar typed at the top of a file could not be evaluated with C-x C-e. The
key was wired straight to eval-expr, so the parser met a declaration where an
expression was required and said so: "defvar is a top-level declaration, not
an expression". The only way to get the var installed was C-c C-c. That split
was never a property of the compiler, which has had both evaluators side by
side in lib/dev.ml all along — one installs a body under a name, the other
wraps an expression in a thunk because there is no name to install into. It
was a property of the keymap.

So the key now dispatches on the form it would actually send. Two questions,
both of which have to answer yes before the declaration path is taken: the
depth at the form's open delimiter says whether anything encloses it, and the
head says whether it is a declaration. Requiring the first is what keeps an
inner expression inside a defn body evaluating as an expression — the
enclosing form being a declaration is not a reason to reinstall it, and the
cursor's position in the file was never the question. Requiring it also
leaves a defn written inside a let to the parser, which refuses it correctly,
rather than quietly installing it as a top-level definition.

The head list is spelled out in flan-dev.el and points at the arm of
Parse.expr that is its authority; package is deliberately left out of it,
because this is the set of heads that fail when sent to the expression
evaluator and not the set that introduces a name.

The echo area is now the only thing that says which evaluator ran, so it has
to say it. An expression still reports "=> 42". A declaration reports what
landed, and reports it by name: a defvar installs no bodies, so leading the
sentence with the generic label left it reading "form installed in 4 ms (also
ticks)" — the one name that changed, parenthesised as an afterthought. Where
there are no functions the names are what changed, so they are what the
sentence is about, and the aside is then empty rather than a repeat of it.

C-c C-c is untouched and stays the explicit "reload the definition I am
standing in", which is still the command to use from inside a body.
2026-09-17 19:00:13 +07:00
c124df36ca A move-only global is borrowed, never moved 2026-09-17 18:42:37 +07:00
69f5d8a05a A global Vec is borrowed, never moved, and outlives every entry to main
A program that wants to load its data once and keep it could not say so. Every
move-only global was refused where it was declared, on an argument about the
dead set being per function: two functions each freeing the same global would
be a double free nothing could see. The argument was sound and the conclusion
was too strong. It assumed a global has an owner. It does not.

Reading a move-only global is now always a borrow. Nothing may take ownership
of one, so nothing may free one, and with no owner to hand over there is no
double free left to catch. This is not a general ownership model for globals
and is not meant to grow into one: it is sound precisely because the lifetime
question that model would exist to answer has a constant answer here, the
process's. The refusal lands at the read, which is where a move would have been
recorded for a local -- passing the global to something that owns its
parameter, binding it to a local, returning it and freeing it all reach the
same place, and each is told to borrow instead, or to clone if it really wants
something of its own.

Such a global is mutable where it stands. push, put, reserve and set already
take their target through the borrow path, so a global (Vec u8) is filled and
grown in place, and the aliasing that raises is the one every Vec has:
spec-memory.md's explicit Zig/Odin contract, where a push that reallocates
invalidates a slice taken before it and the dev build's generation word traps
on the stale one. Globals get no borrow rule locals do not have, because the
hazard is not new and the trap lives on the Vec rather than on the binding.

What a move-only global may not do is carry a computed initialiser. A global's
initialiser is a link-time constant -- there is no init-at-startup path in the
LLVM backend by design, and the x86 backend that has one deliberately leaves it
out of a reload module, because re-running an initialiser wipes the live state
reloading exists to preserve. So the global starts zeroed, which for a Vec is
an empty Vec and therefore a value rather than a placeholder, and the load is
an ordinary assignment in whichever function loads it. That is also what makes
the data survive: nothing runs between one entry to main and the next, so a
re-entered main finds the global as it left it. A defconst cannot be one at
all, since a constant is not an assignable place and nothing could ever load
it; both refusals name the (defvar g (Vec u8)) that works.

The reload fixture gains a global Vec in the host and another that arrives at
run time, because that is where declaring instead of defining has teeth: a
module that defined the host's Vec would take a zeroed header of its own and
strand the block the process is still using, which a re-zeroed i64 cannot
demonstrate.
2026-09-17 18:42:01 +07:00
de3ad8c052 defenum members may leave their value to the previous one 2026-09-17 18:36:34 +07:00
3771f6820b An enum member's value is optional, and a value nobody chose is refused
Every defenum member had to carry a literal integer, so an enum of twenty keys
was twenty numbers typed by hand and renumbered by hand the first time a member
was inserted in the middle. A value may now be left out, and then it is the one
above it plus one, starting at 0 -- C's rule, because the enums written here are
as often a transcription of a header as they are original.

Autoincrement brings its own silent failure with it. Renumber a member, or slip
one into the middle, and the member below can land on a value some other member
already holds: two names for one number, the program still compiles, and one of
the two is now unreachable through a match on the other, with nothing in the
source saying so. So a duplicate that was *written* is kept -- a Count or a Last
pointing at an existing value is a real idiom and is somebody's decision -- and
a duplicate autoincrement walked into is refused, naming both members and the
number they collide on, and saying that writing the value out is how the alias
is declared to be intended.

The rule lives in the parser rather than beside the duplicate-name check in the
checker because it is a question about the source text. Ast.Defenum holds
resolved numbers and no per-member locations, so by the time the checker has an
enum in hand it can no longer tell which of the values were typed, nor point at
the other member. All members are resolved before any of them is checked: the
value collided with is as often below as above, and (defenum E [A B 0]) has to
refuse A.
2026-09-17 18:34:52 +07:00
798c852934 Where a program starts, how a big array clears, and which key folds
flan-dev's start command proposed the last program it had started, so
invoking it from a fresh project's buffer offered the previous project's
file. It now proposes the buffer it was called from; restarting the
previous program is what flan-dev-restart-program is for.

Zeroing a fixed array wrote one typed store per element. Above 64 bytes
that becomes a memset, which LLVM can lower as a bulk clear; below it the
inline stores are still cheaper than a call.

Outline's minor-mode map owned TAB in the lowering buffer, so the folding
keys that buffer defines never ran. A buffer-local overriding map gives
them back without touching Outline anywhere else.

FIX.org collects the rough edges found while using the dev loop.
2026-09-17 18:08:51 +07:00
65d14f42f0 A dlopened macro binds to its own copy, not the merged host's
flan dev's one-process build links the program and the compiler into one
-rdynamic executable, so it exports flan.rl/with-drawing -- the package's
defmacro compiled as an ordinary function, whose body was qualified at the
Ast level, after the quasiquote had already become a string literal. ELF
gives the executable precedence over a dlopened object, so the macro module
called the host's copy and (rl/with-drawing ...) expanded to an unqualified
begin-drawing the checker then refused. The module is self-contained, so
-Wl,-Bsymbolic is the whole fix.

merged_setup reports a Loc.Error as a diagnostic now, which is how the
failure had a location at all instead of an exception constructor after an
apparently successful build.
2026-09-14 22:26:52 +07:00
a4dce138c0 The page catches up: the backend serves the editor, and emit --x86 is annotated 2026-09-14 13:12:53 +07:00
45c5b43021 Merge branch 'worktree-agent-ae9bc74da338fa214' into dev-loop 2026-09-14 11:55:14 +07:00
238f65db59 A listing says which form it came from, and what each slot is
`flan emit --x86` printed a three-line header and then nothing but .byte
blobs. The information was all there and none of it was written down.

Each run of bytes is now headed by the Flan form that produced it, with the
position it was written at, indented by how deeply the form nests. The
headings are queued rather than written, so a form that emits nothing does
not leave its heading on the next form's bytes; atoms queue none at all,
because a literal operand would otherwise steal the heading standing above
the imul that consumes it.

Above each function is a frame map, which is the half no disassembly
recovers: every value in this backend lives in a frame temporary, so
-0x20(%rbp) is the whole vocabulary of the listing and nothing says what it
means. It is read out of what emit_fn already keeps, so it cannot drift.
Beside it, where the arguments arrived and whether there is a hidden sret.

And the bookkeeping is named where it appears -- the transfer guard, the
bounds triple, the arithmetic guards, rep movsb, the dev indirection cell --
with each explained once in a legend at the top rather than at every site.

Always on for `emit --x86`, which exists to be read, and never for a build,
whose .s is a temp file handed to clang. spike/x86/annot.sh is the check that
this costs no byte: emit both ways, assemble both, compare every section.
342 SAME / 0 DIFFER over the corpus in default, --dev and --debug. dump.sh
now shows the annotated listing beside objdump's disassembly -- why beside
what, which is the pairing that answers the mnemonics question.

survey.sh has not been run on this; see the handoff.
2026-09-14 11:54:36 +07:00
1615e3ed8b The section refresh is checked against the file it must not have touched 2026-09-14 11:29:13 +07:00
1961c7cc9d The plan for an annotated listing, and the two decisions it rests on 2026-09-14 11:28:30 +07:00
042ddd73d9 One buffer for four lowerings, and it remembers which one you were reading 2026-09-14 11:25:05 +07:00
5a2f627842 Four lowerings want a buffer, and the buffer wants to remember 2026-09-14 11:11:13 +07:00
f77216212e Every lowering of one program, side by side 2026-09-14 11:05:35 +07:00
7f44cc3c8f The backend serves the editor, which is what it was for 2026-09-14 11:01:20 +07:00
e7bb643d9d Merge branch 'worktree-agent-aaf84e55296df121c' into dev-loop 2026-09-14 10:52:13 +07:00
d0e8bdf051 Why test_emacs got no x86 case 2026-09-14 10:51:29 +07:00
ef1c2e6fa5 The survey and the three forced test runs, with what the skip count moved for 2026-09-14 10:51:03 +07:00
fd7fa78720 The two sweeps have no lane left to convert, and had stopped telling a real hit from a false one 2026-09-14 10:47:47 +07:00
391fc62cc8 The handoff, with both backends measured through a real daemon 2026-09-14 10:40:56 +07:00
ab5a381de8 A README section on what checks what, and the full account of the third rot
The third silent failure was waiting, and it was the September 12th one again:
web/index.html showed the value renderer spelling struct fields with colons, two
days after the sweep that made them dots everywhere else. The check that should
have said so was anchored in NEXT.md, which is a scratch document, so it had been
reporting 'whatever this quotes has moved' into a report nobody could read.

README gains a Checking it section: the four commands, what each one means, and
the plain statement that nothing runs @checks for you. The convention it proposes
is the one this repository already has -- a lane's handoff quotes its counts.
2026-09-14 10:39:20 +07:00
f182fb4728 flan dev --x86: the host and its modules, chosen together
Item 3, and the reason the backend was written. Until now --x86 was read only
by flan build's argument list; the daemon built both halves through LLVM, so
none of this reached the dev loop at all.

The choice is a session setting, not a per-command flag, and it is spelled
exactly as [debug] already is -- one field on Session.t, set once in Dev.start,
carried on every change the session emits. session.ml's comment on [debug]
already gives the reason and it is the same one: the modules have to match the
process they are loaded into. Session.redefinition is the single place that
picks a backend, so the six call sites cannot disagree and the refusal has one
home. Session.change carries the answer beside the text, so the builder and the
text can never come from two different decisions.

There is no fallback and there must not be one. X86.redefinition refusing a form
is reported to the editor; quietly building an LLVM module instead is precisely
the crossed pair flan.abi.x86 exists to refuse at dlopen. A refusal reaches the
editor as a diagnostic like any other -- X86.Unsupported is re-raised as a
Loc.Error at the form it is about, because every caller already handles that and
none handled the other, and a session that died on the first unsupported form
would be worse than one that says so and stays up.

flan reload got the same flag at the same time. A command that could build a
module for a host the other backend compiled is how the crossed pair was
reachable from the CLI at all; the aggregate handoff's two-line reproduction no
longer has a second half.

And the finding: flan dev --x86 refuses the merged daemon. A merged build is the
program and the compiler in one process, and the compiler expands macros by
dlopening a module Build.macro_module made through Emit.program, cached on disk
by the macro source rather than by the backend. The merged host is linked
-rdynamic so a redefinition module can reach its cells, which also exports every
flan.* body it has -- so the macro module's own copy of a prelude function is
interposed by the host's. With an LLVM host nobody notices. With an --x86 host
the caller is LLVM and the body it lands in is this backend's, and the process
dies inside flan.[clamp] during the first macro expansion, before the program
has started. flan.abi.x86 does not catch it and was never meant to: a macro
module deliberately neither defines nor requires a marker. The honest fix is
hidden visibility on a macro module's Flan bodies, which changes the cached
object for both backends and wants a lane of its own. Until then the refusal
names the mechanism and the remedy, and --two-process has no such meeting.

start_merged keeps its --x86 plumbing, unreachable for now, because it is the
half that is right and will be wanted the day the macro module is fixed.

test_dev.ml drives an --x86 daemon through C-c C-c, C-x C-e, a literal, a new
defvar with a value of its own and a new defn, and asserts (twice fresh) is 82 --
which only holds if both registry lookups resolved. The merged refusal is
asserted there too. bin/main.ml learned to print a bare Failure as a sentence
rather than an uncaught exception and its backtrace.
2026-09-14 10:34:49 +07:00
5b1567c49f The transient thunk, so an expression has something to compile to
Item 2 of HANDOFF-x86-redef.md. C-x C-e and every value a break loop computes
are not a redefinition: there is no name to install a body into, so the
expression is wrapped in a function with nowhere to be called from and the
module says "run this once". flan_reload_call is that wrapper, and the agent
dlsyms exactly that spelling.

Its shape is emit_main's rather than a body's: no caller hands it a transfer
channel, so it owns a null cell on its own frame and passes that cell's address
on. Sixteen bytes of frame rather than eight, because rsp has to be 16-aligned
at the call and that is the whole of what the ABI asks of a frame making one.

The thunk itself is excluded from everything else the module does -- no cell, no
publish, no registry slot. There are 4096 slots and an expression evaluated in a
loop would exhaust them, and a module with nothing pointing into it is what lets
the agent unload it at all.

@flan_reload_transient is that claim, under emit.ml's three conditions. The
third is about data rather than text and is the one that can be got wrong in the
dangerous direction: a string literal lives in this module's image, an
expression may store one anywhere it likes, and a global left pointing into an
unmapped image is silent garbage rather than a fault. So the count is kept where
the literals are made -- string_const bumps the same Emit.m.nstr field emit.ml
counts on, and float constants, which are loaded and never retained,
deliberately do not. The install function's own registry name strings go through
string_const too, which is right rather than incidental: a module that interned
a name left something behind.
2026-09-14 10:34:29 +07:00
aa82364066 Two aliases for the checks nobody ran, and one word that names them all
@page runs web/examples/check.sh and web/examples/quotes.sh against the compiler
dune just built. @cells runs spike/x86/cells.sh, which was a real pass/fail check
-- four builds, two backends, 22 22 against 42 42 -- that nothing in the tree ran.
@checks is @page, @x86 and @cells together, and its comment argues for where the
boundary sits: everything you can run while making coffee is in, @sanitize and
@valgrind are out because folding tens of minutes in would make the umbrella the
thing nobody has time for, which is the disease rather than the cure.

All three scripts learned to resolve FLAN to an absolute path, which is what
actually stood between them and a dune rule: %{workspace_root} expands relative to
the directory the rule is written in, and every one of these scripts cd's somewhere
before using it. The first run of @page failed with twenty diffs all saying
'../bin/main.exe: No such file or directory', which is at least a failure that says
what is wrong.

docs/BUILT.md carried the same colon-spelled renderer block index.html did, from the
same sweep. Nothing checks BUILT.md, so it is corrected here by hand.
2026-09-14 10:26:48 +07:00
c57ca6a24f The renderer's own output, spelled the way the renderer spells it
The renderer check greps a line out of NEXT.md, and NEXT.md is a scratch document
that gets rewritten. The line went, the grep went empty, and the empty-needle guard
did its job and said so -- into a report nobody was reading. Behind that noise the
page was genuinely wrong: the colon-to-dot sweep rewrote every field label in the
corpus and lib/render.ml writes .field today, so the inspector block on the page had
been showing {:x 1.5 :y 0} for a renderer that prints {.x 1.5 .y 0}. test_repl.ml's
fixtures are the authority and they are dots throughout, with an enum member still a
colon; the page now matches. The anchor moves to test/programs/raylib-imported.flan,
which dune test builds and runs, so it cannot quietly stop saying it.

The LLVM excerpt beside it moved too: a --dev main pushes a condition frame before
anything else now, which shifted the SSA numbering by one. The three quoted lines are
what flan emit --dev prints today, with the frame push marked as elided rather than
silently dropped.
2026-09-14 10:20:34 +07:00
a003073be7 Three quote probes compared a caret excerpt against a page that quotes one line
quotes.sh had been red since the compiler's diagnostics grew a source excerpt: the
needle swallowed the caret lines, so result, quoted and i64index failed against a
page that is in fact correct. It now compares the message and nothing else.

Six other probes are deleted rather than fixed. Vec, Map, Handle, an Fn-typed
parameter, a defer inside a let and break were all refused as not-implemented when
the loop was written; all six work today and the page's table lost their rows as
each landed, so the probes were the last thing in the tree asserting a claim the
page no longer makes. What made this invisible for weeks is worth recording: flan
check answers a program that compiles with its whole symbol table, so each of those
probes failed by printing eighty lines of prelude signatures. A clean exit is now
its own one-line failure saying the page's claim has gone stale.
2026-09-14 10:19:18 +07:00
5a67cf458a A name the host was never built with, compiled by the x86 backend
The first of the three things HANDOFF-x86-redef.md left: a function or a
defvar the running process has no symbol for. ELF cannot grow one, so the
address is asked for by string at install time -- flan_dev_cell for a cell,
flan_dev_global for a global's storage -- and parked in a slot this module
defines.

The reference side is one new [loc] case and nothing else. [Lslot] loads the
slot and answers [Reg (scratch, d)], which is exactly what [Lgot] already did
with [Got] where this has [Sym]; every site that reaches a cell already
double-loads, so no call site, no place expression and no [sym_loc] caller had
to learn a third case. [fnctx.slot] is a second predicate rather than a widened
[ext] because they answer different questions -- [ext] says "the host's, reach
it through the GOT", [slot] says "nobody's yet, reach it through a slot I
filled". It defaults to [fun _ -> None], so the whole-program path emits
byte-identical output and the survey goes on being a structural check.

flan_reload_install is now a function with a frame rather than a run of loads
and stores, because it makes calls and a call on an unaligned stack faults
inside glibc's movaps rather than anywhere a reader would look. Its shape is
emit_globals_init's, down to owning the null transfer cell no caller hands it.

A new global's declared value travels with it: flan_dev_global copies the image
onto the allocation the first time the name is interned and ignores it after,
which is where "a reload must not reset the state" lives. emit.ml folds that
value into an LLVM constant and this file has no folder, so the image is a
module-local buffer written by the initialiser lowered as ordinary code -- the
same bargain emit_globals_data already documents.

Republishing a defconst came free once the rest was there: one store of the new
constant into the host's global, which is what emit.ml does.

reload-v6.flan is new. v3's [extra] is declared zero, which calloc also gives,
so a run-time-new global whose initial value never arrived would still pass;
v6's [tuning] is 42 and the host prints 88.

test_reload.ml's x86 section now runs all four modules against the same
transcript the LLVM path is held to, and the refusal it used to assert is gone.
2026-09-14 10:16:42 +07:00
066322c47d The checks nobody runs, and a plan to find the rest of them 2026-09-14 10:12:05 +07:00
9ad886abe5 The plan for wiring the x86 backend to the dev loop, written before the work 2026-09-14 10:11:34 +07:00
f4593525f2 Merge branch 'worktree-agent-ae54bef3575a698ce' into dev-loop 2026-09-14 09:53:35 +07:00
3b1d3e82b5 Where this is at the end of the session, and the three things waiting on a decision 2026-09-14 09:50:42 +07:00
a9b7eb1556 The survey landed, and neither census comment states a number any more 2026-09-14 08:49:43 +07:00
0661e6042e The one verification this session could not finish, said so 2026-09-14 08:44:08 +07:00
3d668f125c A build artefact that wandered into a commit 2026-09-14 08:41:02 +07:00
7ebf7ec9c4 The counts beside the census, and the language claim the walk used to carry 2026-09-14 08:40:32 +07:00
c355739f55 The handoff says what was built, and the index knows it is there 2026-09-14 08:37:59 +07:00
155ec0ea31 The two gaps read as closed, and a census that had rotted stops being a census 2026-09-14 08:37:30 +07:00
99c5d87125 Both raylib workarounds, and where the second one went instead 2026-09-14 08:37:30 +07:00
7e59085916 Two bindings the pointer arm makes writable: a backwards codepoint read, and a void* the caller types 2026-09-14 08:37:30 +07:00
37d94ed66f The pointer arm agrees promised, and the comment stops promising it 2026-09-14 08:37:30 +07:00
bb2a9b202b The plan for the pointer arm cimport already promised 2026-09-14 08:37:30 +07:00
c9bb12e591 The two-process daemon took the same signal, and has no test that says so 2026-09-14 08:17:43 +07:00