A floating message stops short of the middle, and is read at arm's length #47

Manually merged
atanabe merged 6 commits from feat/047-a-floating-message-stops-short-of-the-middle-a into master 2026-07-08 09:36:07 +02:00
Owner

A floating message stops short of the middle, and is read at arm's length

Along the way:

  • The tick keeps its footing when a completion empties the list under it
  • Every number a floating message is made of is a setting
  • Damage falls away from your character, and a crit falls differently
  • The character sheet, rebuilt as rows behind a figure
  • The numbers come up at login rather than when you open the options

Touches 33 files: scripts/bake-round.sh, scripts/check.sh, scripts/harness/client/01-widgets.lua, scripts/harness/client/04-hands.lua, scripts/harness/client/13-character.lua, scripts/harness/client/init.lua and 27 more.

A floating message stops short of the middle, and is read at arm's length Along the way: - The tick keeps its footing when a completion empties the list under it - Every number a floating message is made of is a setting - Damage falls away from your character, and a crit falls differently - The character sheet, rebuilt as rows behind a figure - The numbers come up at login rather than when you open the options Touches 33 files: `scripts/bake-round.sh`, `scripts/check.sh`, `scripts/harness/client/01-widgets.lua`, `scripts/harness/client/04-hands.lua`, `scripts/harness/client/13-character.lua`, `scripts/harness/client/init.lua` and 27 more.
atanabe approved these changes 2026-07-07 02:00:00 +02:00
atanabe left a comment

Messages stopping short of the middle of the screen is the fix; being able to read them at arm's length is the reason. Three notes.

Messages stopping short of the middle of the screen is the fix; being able to read them at arm's length is the reason. Three notes.
@ -125,0 +134,4 @@
-- and a mask is not art anybody hides, recolours or measures: it is the shape
-- another texture is cut to. Registering it there would have every region count
-- in this harness move on the day the gear page rounded its icons.
function Region:CreateMaskTexture(name)
Owner

A mask registered outside the region count, with the reason on the line above. That is the right call and it is the sort of exemption that has to be written down or it reads as an oversight.

A mask registered outside the region count, with the reason on the line above. That is the right call and it is the sort of exemption that has to be written down or it reads as an oversight.
@ -0,0 +177,4 @@
events:RegisterEvent("PLAYER_LOGIN")
events:RegisterEvent("PLAYER_REGEN_DISABLED")
events:RegisterEvent("PLAYER_REGEN_ENABLED")
Owner

Three events and the handler branches on the first one only at login. What happens on the second PLAYER_REGEN_ENABLED of a session, does Arm run twice?

Three events and the handler branches on the first one only at login. What happens on the second PLAYER_REGEN_ENABLED of a session, does `Arm` run twice?
@ -163,0 +174,4 @@
-- icon going missing at the edges.
--------------------------------------------------------------------------
local ROUND = "Interface\\AddOns\\" .. ADDON .. "\\Media\\Round.tga"
Owner

A texture path built by concatenation at file scope. Fine, and it means a typo in the folder name is a missing texture at login rather than an error anybody sees.

A texture path built by concatenation at file scope. Fine, and it means a typo in the folder name is a missing texture at login rather than an error anybody sees.
Author
Owner

It came out of the count on the day the gear page rounded its icons and every region number in the harness moved at once.

It came out of the count on the day the gear page rounded its icons and every region number in the harness moved at once.
Author
Owner

There is a check in the release script that every path built this way resolves to a file that is in the archive.

There is a check in the release script that every path built this way resolves to a file that is in the archive.
Author
Owner

Arm is idempotent and the second call costs a comparison. I have said so above it, because reading the handler alone you cannot tell.

`Arm` is idempotent and the second call costs a comparison. I have said so above it, because reading the handler alone you cannot tell.
Three things wrong on the first screen it was seen on.

It crossed the centre. A message is pinned by the corner nearest the edge it
came from, and the rest offset was measured to that corner, so stopping it forty
pixels short of the centre put the other two hundred and twenty pixels of the
message straight across it. The offset is measured to the far edge now and the
width comes off, which is the edge the eye reads the message by. The entry
offset is still to the near corner and still needs no width: that one is a
distance to the screen's edge and is right as it was.

It was drawn at panel size. The loot feed's column is read in a window you have
opened; this is read over the world in the second before it goes. The icon is
fifty pixels rather than twenty, and the name is twenty rather than the twelve
every control in the addon uses. Neither number is UI.Metric, because those are
the size of a thing in a window.

And the count was drawn over the name. Both were pinned to the row's right edge,
so a stack of eight put its own number through the last letters of the word. The
name stops at the count now.
A message leaving stops the travel and the wait of the row it retires, from
inside the fade's own completion, so the list Animations.Beat is halfway
through indexing loses entries it has not reached. The swap-remove that is free
away from the tick is wrong under it twice over: an entry it moves down into a
slot already beaten is advanced twice in one frame, and a list that shrinks
past the walk's own bound leaves it reading a slot that is gone. Eight drops in
one second was enough, and in the game it read as
"attempt to index local 'tween' (a nil value)".

A removal under the walk now writes a hole and the walk sweeps them out when it
is done with the list. Nothing moves while it is being indexed.

The harness asks it twice: of the library directly, where the third of four
tweens lands and stops two others, and through the front door with a bag's
worth of drops in one frame.
They were literals in Feeds/Floats.lua and none of them was a fact. Where the
eye sits on a screen, how long a caption has to be up to be read and how much of
the middle a message may cross are answers about a monitor and a person, and the
numbers that shipped were one person's.

Fifteen rows on the loot page under the switch that was already there: which way
it crosses, how far in from that edge it starts, how far short of the centre it
rests, its height and the gap under it, the two alphas it fades between, the
travel, the time on screen, the beat between arrivals, how many the column
holds, and the four sizes a row is drawn at.

ns.Ck.Float is untouched. It took every one of these as a spec field from the
first day and reads no setting itself, which is what makes this file the whole
of the join between a slider on a page and a message crossing a screen.

Two things a settings page has to get right here. A lane resolves its spec when
it is built and then owns slots and a stagger clock worked out from those
numbers, so a change throws the lane away and the next drop builds one that has
heard about it; what is on screen plays out on the old numbers and hands its
rows back the ordinary way. And the rows are pooled, so every size is put on a
row as it comes out of the pool rather than once when it was built.

The height is the one number that is not a control: a row is as tall as the
tallest thing on it, because a height beside an icon size is two settings that
have to agree and the one somebody forgets to move writes over the message
underneath.

Both directions run in the harness, and the left one is not the right one with
a sign on it: the rest offset is measured to the far edge with the row's width
taken off, which is what stops a message crossing the centre instead of stopping
beside it. A scene changes a setting under a message that is still travelling,
and the defaults the section types out are asserted against the shipped ones so
that a default moved without this section reads as the name of the setting.
What lands on you falls away on your left, what you land falls away on
your right. Damage is white, healing green, and a critical or a crushing
blow gold. Every number goes large to small down a slight bow, and fades
while it falls. Three anchors you drag decide where each stream starts,
and the third one is for words rather than numbers: Execute rises above
your head the moment the target drops under a fifth.

ns.Ck.Stream is a second thing under Ck\ rather than a fourth channel on
Ck\Animations.lua, and the split is the point. A tween's hard part is
interruption: Ck\Float.lua re-aims a message that is already travelling
because the one above it expired, and Animations exists to do that from
wherever the frame currently stands. A damage number is never re-aimed.
Its place is a function of how long it has been alive, its style and the
seed it was born with, so the arc and the scale envelope are evaluated
rather than interpolated between two stored ends. Adding a scale channel,
a curved path and one easing per channel to Animations would have worked
and would have left that file carrying eight fields that are nil on every
run a message makes.

The merge is what the shape buys. Four ticks of a bleed in six seconds
are one number that grows and swells in place rather than four drawn on
top of each other, and that is the single biggest thing between this and
every other floating number in the game. A tween owns its clock and a
number merged into has to keep the clock it already has.

Three other things a control cannot say. A big hit is drawn bigger,
measured against the biggest of the fight, which resets when the fight
does. The size on the page is a multiplier on one outlined font at the
outline floor rather than a font object per size, so dragging the slider
does not leave a CreateFont per step behind. And crushing is read off
slot 20 of a swing and nowhere else, checked against Details' own parser
on the live 2.5.6 install rather than typed from memory.

Two tables found one home on the way past. ns.CombatLog.SHAPES is where a
number is on a line, and ns.Unit.Roster.Mine is whether a GUID is yours.
Feeds\Combat.lua and Breakdown\Breakdown.lua still carry their own copies
of the first and Feeds\Combat.lua of the second; moving those two readers
is the next commit and not this one.

Gated in both layers. 80-floating-numbers.lua asserts the side, the three
colours, the crushing flag against the glancing one beside it, the two
envelopes, the bow, the merge in both directions, that the tick allocates
nothing on a pass where nothing moved and gives its OnUpdate back, and
that off takes the reader off the combat log. Writing it found a real
one: Find matched on the key alone, so a tick could be refused a merge it
was entitled to when a critical of the same spell was still in the air.
The kind is in the key now.
Two commits off .worktrees/character-sheet: the round icons and their
mask, then the rows, the names, the sockets and the figure moved behind the
page. The harness split that came with the second one is in its own paragraph
there.
Nothing drew. Numbers.Apply is what subscribes to the combat log, builds
the styles and places the anchors, and the only things that called it
were the switch, the slash words and the panel's own controls. So the
part armed itself the first time you changed one of its settings and was
silent before that: a restart, a pull, and no numbers.

Every other part of this addon registers PLAYER_LOGIN and calls its own
Apply from there, and Calls.lua in this same folder already did. This is
that line, in the events frame that was already sitting at the foot of
the file resetting the fight's biggest hit.

Two assertions, because the hole was in the harness as much as in the
code. 80-floating-numbers.lua called Apply itself at the top of the file
before firing a single line, which is exactly the state a real session is
never in, so every question below it was answered about a part somebody
had already switched on by hand. It now fires a blow before it applies
anything and expects a number.

And 24-combat-log.lua turns every switch that owns a reader off and
counts what is left on the client. There were five and there are six.
That list is the one place in the suite that can see a part which arms
itself at login, because a part that does not never appears on it: the
count would have been right for the wrong reason, and it was.
atanabe manually merged commit 4153744925 into master 2026-07-08 09:36:07 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
northgale/warriorkit!47
No description provided.