Class NameplateTests
- Namespace
- GrindFest
- Assembly
- GrindFest.dll
The nameplate layer: where a label is drawn relative to its item, which items share one label, and which label the pointer picks.
These are the behaviours that kept regressing by hand (2026-09-23 and 24): labels ending up hundreds of pixels from their items, heaps of the same kind never collapsing so a dozen plates fought over the same pixels, a Ctrl comparison that only appeared if Ctrl was held before the pointer arrived, and a pointer scan that picked the first label in pool order instead of the one drawn on top. Every one of those was found with a throwaway script in GrindFest.Workspace and nothing kept it fixed. This is that knowledge as tests.
What is asserted is observable: pixel distances between what is on screen and the item's own projection, how many labels a heap costs, whether the two halves of a comparison touch, and which label a point resolves to. Not asserted: the label's root RectTransform size, which is a layout artefact that reports the plate wrongly (measured (140, 10) for a full plate, and (10, 35) after a forced rebuild) - the geometry the game lays out with is PlateSize(UINameplate) and the tests read the same one.
Alt and Ctrl are forced by disabling PlayerInput, which rewrites both flags from the keyboard
every frame; that also means the pointer scan does not run, so a test that needs a hover calls
OnPlayerHover the way PlayerInput does. The input is always restored (try/finally), because the rest of
the suite needs it.
public class NameplateTests : DevTest
- Inheritance
-
objectNameplateTests
- Inherited Members
Methods
- ComparisonAppearsWhenCtrlIsPressedWhileHovering()
Pressing Ctrl while the pointer is ALREADY on an item brings up the comparison. This is the exact order the owner reported as broken - "I have to hold Ctrl first and only then hover" - and it used to be: the label already existed, and the comparison half was only ever built inside the hover path's
if (Nameplate == null).
- ComparisonHalvesAreSideBySide()
The two halves of a Ctrl comparison sit side by side: the hovered item's label exactly on its item, the half clear of it to the right, tops lined up. Broken twice on 2026-09-23 - first the half was placed by the item it compares AGAINST (189 px between the halves), then the layout pushed the hovered label away from its own half.
- DifferentKindsAreNotMerged()
Two different kinds lying together keep their own labels: a number in front of a name means "this many of THAT", so merging a bucket into a ladle's label would be a lie (the owner reported reading "3 The Alchemist's Handbook" with one handbook on the ground).
- EquippedLabelsNeedTheZoom()
Worn gear only gets a label while the camera is zoomed in far enough to take it off (the owner's rule: "we shouldnt show nameplates of equipped stuff when not zoomed enough"). The zoom is the inspect-your-gear mode, so the label belongs to it.
- GroundLootDoesNotPileWithInventoryContents()
An item in the backpack and an item on the ground are never ONE pile (owner, 2026-09-24: "we probably shouldn't merge things on the ground and things in the inventory into the same piles, that wouldn't make sense"). Before the rule, a sword in the container and a sword on the ground read "2 Broken Sword": a lie about the world, with no pair to pick up.
The two labels are not forced onto the same pixels - moving a ground item into the container's own space makes the container PICK IT UP (measured: the ground sword's
InContainerbecame true and the rule then rightly saw one place), so what is asserted instead is the rule's own two halves plus a positive control: the place really differs, FormsPile(UINameplate, UINameplate) refuses the pair, and two same-kind items in the SAME place do still collapse (so the refusal is not blanket).
- LabelSitsOnItsItem()
A label is drawn on its item when nothing else is in the way. Reported as "all those labels get terribly far from where the item actually is": measured before the fix, 23 of 23 labels were off their items by an average of 87 px, worst 213 px.
- LabelsBehindGeometryAreHidden()
A label whose object something solid stands in front of is HIDDEN - the case the owner reported on 2026-09-24: "we're missing, in the new nameplates, the hiding when the nameplates are under an open container/inventory". The old system never had it (its TODO is still in TooltipManager: "maybe we should raycast so we don't show nameplate behind terrain, floor, containers"), so while a container is open every label behind it - worn gear, loot on the ground - was drawn on top of the box.
The blocker here is a plain collider parked between the camera and the item rather than a real container: the container needs a hero, an opened inventory and its own arrangement, while what is under test is the visibility rule. Measured with the real inventory open, every camera->anchor ray in the scene is blocked the same way, by the container's body and by its Handle-layer click shield ("InventoryCollisions").
The contents of an open container are exempt from this test on purpose (they are the promotion case, and their anchors sit inside the container) - see InteractiveBehaviour's occlusion block.
- PointerHitsTheWholePlate()
The pointer hits the whole plate, not just the size the label's root RectTransform happens to report. That rect is a layout artefact (measured (140, 10) for a full plate, and (10, 35) after a forced rebuild), so a rect test alone leaves parts of the plate the player is pointing straight at invisible to the game.
- PointerPicksTheLabelOnTop()
The pointer resolves to the label DRAWN ON TOP, not to the first one in pool order. Pool order has nothing to do with what the player sees, and with plates packed edge to edge the first match is regularly a different item - "which one is extended never switches to the label I am on" (reported 2026-09-24).
- SameKindHeapCollapsesWithTheSum()
A heap of the same kind costs ONE label, reading the summed amount. Two piles of the same item that would overlap on screen have to collapse - the rule used to be a fixed 26 px radius, measured against plates ~140 px wide, so a heap never collapsed at all and the arranger spread the plates into a 200 px ladder.
Gold is what this is measured with because it is what stacks:
SpawnItem(path, count: 10)on an ingot gives one item with Amount 1, not a stack of ten (that is how the first version of this test failed).
- StaleOffsetComesBackHome()
A label that was once pushed aside comes back the moment its own spot is free. The old arrangement reused a remembered offset whenever it was still free and never asked whether the item's own spot had freed up, which is how labels stayed hundreds of pixels away for the rest of a session.