Skip to content

Integrate contributor PRs #218, #222, #223, #224, #226 - #228

Merged
eriknihlen merged 19 commits into
mainfrom
integrate/contrib-2026-10-19
Oct 19, 2026
Merged

eriknihlen merged 19 commits into
mainfrom
integrate/contrib-2026-10-19

Conversation

@eriknihlen

Copy link
Copy Markdown
Owner

Integrates five contributor PRs by @davidc-smith, each with maintainer fixes pushed to its own branch:

Merging this marks the five PRs as merged.

🤖 Generated with Claude Code

davidc-smith and others added 19 commits October 10, 2026 23:10
ACE sends a shop create-list row's authored stack size unchanged, and
world content authors many of those rows as 0 rather than -1. ACE never
limits a create-list item, but the vendor window only treated a negative
stock as unlimited, so it hid every zero-stock row and the vendor looked
empty. Component auto-fill also capped those rows at nothing.

The ApproachVendor wiring now records any non-positive stock as -1.

Claude-Session: https://claude.ai/code/session_01T9gYq4uZVPNw3DPW9cnpE9
A place walk or preview inside a dungeon kept the height it was given. A
dungeon map gives a cell by the middle of its bounds, which in a tall room
is metres above the floor, and the router only arrives within 1.2 m of a
goal's height, so such a place came back NoRoute. In 0x5B48 no cell routed
at all, and in 0x5948 only the low ones did.

RuntimeNavigationGoalSource.TryLocatePlace now stands a place in an interior
cell on the cell's own floor nearest at or below it (up to 0.5 m above), or,
where the cell has no floor there, as in the upper half of a tall room
authored as a cell on top of a cell, on the nearest floor of any cell below
it. Preview and walk both locate places through it, so they agree. Outdoor
places, places with no height, and object goals are unchanged.

Against the installed dat, from one start: 0x5948 routes 313 of 327 cell
middles (39 before); 0x5B48 routes 265 of 712 (9 before), the rest lying in
wings its cells alone do not join.

Claude-Session: https://claude.ai/code/session_01Qxrqqq5UccBfoNgzr2T84o
A `<column type="button" items="{Captions}" onclick="{Act}">` draws each
row's caption on a button with the face and border of a markup `<button>`
(the theme's colours on a themed window), lighter while hovered and darker
while pressed. It fires the column's onclick with the row index when the
left button comes up over the button it went down on, and never selects
the row. An empty caption draws no button. Optional `buttonw`/`buttonh`
size the button, centred in its cell and never larger than it; only the
button itself takes clicks.

Claude-Session: https://claude.ai/code/session_01Lm9T9oP2Hi5FEjwsLmXeyN
A coordinate pair written in any chat line except combat text ("meet at
42.1N 33.5E", "Your corpse is located at (42.1N, 33.5E).") is now a
COORD:World link, split out by the same rule PluginChatCoordinateParser
reads. A line a plugin posts has its own <COORD:World:...> markup read as
a link; server text is never read as markup, and a plugin line with no
tag keeps its angle brackets.

Clicking a coordinate in the main chat window now raises
IPluginChat.LinkClicked. The window's ChatLinkClicked hook was never
assigned, so until now no click reached a plugin.

Contract: XML docs only (LinkClicked's summary described Received).

Claude-Session: https://claude.ai/code/session_01Lm9T9oP2Hi5FEjwsLmXeyN
…ness

HasCompleteInventory wanted a contents listing for every container-slot
item, but ACE never sends one for a focus (container type 2), since it
holds nothing. Anyone carrying a focus never had a complete inventory,
so plugin Sort waited 60 s and gave up. Skip foci when walking packs.

Claude-Session: https://claude.ai/code/session_01Lm9T9oP2Hi5FEjwsLmXeyN
This reverts commit 087f346.

A listed amount of 0 is a real amount, not "unlimited": only -1 means a
vendor never runs out, and component auto-fill caps a row at its listed
amount, so rewriting 0 to -1 at the wire changes what auto-fill stages.
The empty-vendor symptom comes from the vendor window hiding rows whose
amount is 0 at open; the next commit fixes that in the window instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Servers list many vendor rows with an amount of 0. The vendor window hid
any row whose listed amount minus what was staged was 0 or less, and it
applied that test before anything was staged, so a vendor whose rows all
carry 0 opened empty.

A row now leaves the browse list only when staging has used it up: it is
staged, its amount is limited (anything but -1), and the listed amount
minus the total staged is 0 or less. A row that is not staged is always
listed. A row listed with 0 can still be staged; the first unit staged
takes it out of the list and clears the selection, the same as exhausting
any limited row, and clearing the buying list puts it back. The listed
amount stays as the server sent it, so -1 alone means unlimited and
component auto-fill keeps capping a row at its listed amount.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r text

A line with a clickable sender was parsed as tag markup whole, the
speaker's own words included. A player who typed
<COORD:World:0N, 0E>42.1N 33.5E<\COORD> got a green link showing one
place that reported another to every plugin listening on LinkClicked.

What players and the server write now looks as it did before this
branch: the speaker's name is still the tell link, plain coordinates are
not linked, and a COORD tag typed into a message keeps its caption as
plain text in the line colour with nothing behind a click. Only a line a
plugin posted carries coordinate links: its COORD tags and the pairs it
writes in plain text.

A plugin's line now reads only a well-formed <COORD:World:pair> closed by
<\COORD>; any other angle-bracket text ("Press <Ctrl:F>") is shown as
written instead of being swallowed as a tag.

The chat links pairs with the public parser's own matcher instead of a
copy of its pattern, and that pattern no longer matches inside words, so
"10n 20east" is neither read nor linked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The enum sat alone in a file named for something else. Only the list's
button cells and their tests use it, and nothing public returns it, so it
does not need to be part of the App's public surface.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…abled

A button column's onclick gets a row index, so a release could fire for a
different item than the one pressed. The press is now dropped if, before
the release, the list scrolls, its row count changes, or the pressed row's
text, caption or icon changes. The rows are re-read at the release, so a
change with no frame drawn in between still counts. Rows re-read with the
same content (bindings hand over a fresh array each read) still fire.
Disabling the list while pressed drops the press too.

A wheel scroll with no pointer move left the hover on a button that had
scrolled away; the hover is now found again from the last pointer position
after a scroll and on every draw.

A disabled list drew an enabled-looking face; face, border and caption now
fade by the alpha a disabled toggle's caption uses (named for both). A
caption wider than its button is cut at the button's edges instead of
running across the cell.

The docs now say what the button looks like on and off a theme, that its
hover and pressed looks are its own, and when a press is cancelled. New
tests go through UiRoot dispatch (press, release, capture taken away) and
cover each fix; the fix tests fail on the previous commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comparing the whole pressed row cancelled presses in lists with a live
column: a distance that updates every frame changed the row between press
and release, so the button beside it fired only now and then. The press is
now cancelled only when the list scrolls, its row count changes, or the
pressed button's own caption changes or becomes empty. Disabling the list
while the button is held still cancels it.

The docs say so, and note that a plugin which reorders rows without
changing the count should change the caption too. A new test has a
different column change between press and release and still fire; it fails
on the previous commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Standing a place in a cell on the floor under it only looked at the cell
polygons, while the dungeon route grid also stands a body on the tops of
the objects the landblock holds. A place deliberately on a bridge, a
platform or a crate (a route waypoint recorded where the character stood)
was moved down under it, so a walk that used to arrive on the object now
ended on the floor below or had no route, breaking the promise that a
place on a rock top is arrived at on the rock.

A place now stays where it is when something the route grid stands on,
a cell floor or an object's top (as the grid captures and sorts them,
through the new NavGeometry.HighestFloorUnder and the floor and cylinder
top rules shared with NavGrid), lies within the router's goal height
tolerance under it or up to 0.5 m over it; it is lifted onto that surface
when it is just under it. Only otherwise does it drop to the floor as
before.

The earlier tests went through a test double that called the floor
lookup itself, so removing the call from the client's goal source failed
nothing. A test now goes through RuntimeNavigationGoalSource.TryLocatePlace
and a preview with it. Both new tests were shown to fail with their part
of the fix removed.

The docs now say what happens to a place with a NaN height in a dungeon
(it is given the ground's height first and then dropped to a floor under
that), and that the fallback searches every cell of the landblock, so a
place over a pit drops to its bottom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…us-line captions

A plugin that reposts what a player wrote (echoing a tell, relaying a
channel) re-armed any COORD tag the player typed, because a plugin's line
is trusted as the plugin's own. PluginChatMarkup.Escape puts a backslash
before every '<'; the plugin-line reader drops that backslash and shows
the '<' as written, never as a tag. Nothing calls it implicitly; the
PostMessage/PostSystemMessage docs and the plugin guide tell plugins to
escape text they did not write.

The name a clicked coordinate is reported with was formatted with the
current culture and kept the sign: "-33,5W" on a Swedish machine. It is
now built once, invariantly, from the absolute values ("42.1N, 33.5W").

A plugin line routed to the status line, which has no links, showed the
raw tag; it now shows the caption only. The plugin surface already sends
that class to the transcript, so this guards the lower-level path.

Doc comments now say only COORD tags are read on plugin lines, the
tagged wording keeps a plugin's line as written, and ParseAll states the
word-boundary rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first fix kept a place where it was whenever something walkable lay
up to 1.2 m under it. A cell's middle, 1.5 m up in a 3 m room, then stayed
in mid-air over a table, chest or altar top: no route, or one onto the
furniture. A recorded waypoint sits at foot height, so a place now stays
only when a surface lies no more than 0.5 m under it, and is lifted when
it lies up to 0.5 m under one; anything else drops to the floor as before.

Only faces that face up count, both in that check and in the floor
lookup the drop uses, so the underside of a ceiling, an arch or something
hanging never captures or lifts a place. The installed dungeons confirm
their floors face up (694 of 712 and 325 of 327 cell middles have one
under them).

A cylinder top counts as far out as the route grid holds it, half a grid
column beyond its radius, so a waypoint recorded at a platform's edge
stays on the platform; a cylinder with no radius or height is ignored, as
the grid ignores it. The grid's reach and extent rules are now shared
helpers.

New tests, each shown to fail with its part of the fix removed: a table
in a low room (the middle drops to the floor and routes), a place just
under a ceiling, a place at a platform's edge, and a platform the server
placed, which stays a floor only because the client's goal source passes
the walk's standable objects on.

The docs no longer claim a height found is one a route can arrive on
(no headroom is checked), and say which place or surface each sentence
means.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@eriknihlen
eriknihlen merged commit 03020e6 into main Oct 19, 2026
7 checks passed
@eriknihlen
eriknihlen deleted the integrate/contrib-2026-10-19 branch October 19, 2026 17:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants