<!-- https://minecraftdocs.dev/systems/world/points-of-interest — this page as markdown. The book's index: https://minecraftdocs.dev/llms.txt; the whole book in one file: https://minecraftdocs.dev/llms-full.txt -->

# Points of interest

> Verified against **Minecraft 26.3** · Part IV · A villager claims a bed.

A villager standing in the middle of a field at noon decides which bed is its
own. It is not near it, it is not looking at it, and it will not walk to it
for another six thousand ticks. `AcquirePoi` asked the index for the beds
with a free ticket within 48 blocks, took the five nearest, asked
`PathNavigation` for a path to all five at once, and the
moment `Path.canReach` came back true it called `PoiManager.take` and
decremented that bed's ticket. Night has nothing to do with it. Hours later
`SleepInBed` will finally put it in the bed and set
`AbstractBedBlock.OCCUPIED`,
and because both occupied variants of a bed head are in `PoiTypes.BEDS` and
map to the same `PoiTypes.HOME`, that block change does not touch the record
at all: **the claim and the *occupied* flag speak in one direction only. Going
to sleep tells the index nothing, and of the three behaviours that read the
flag back, the only one that turns it into a change in the record can do just
one thing with it — take a claim away.** Neither is what a player would call
ownership.

## The cast

| class | what it decides | thread |
|---|---|---|
| `PoiType` | how many tickets a kind of block hands out and how close is close enough — a record of `PoiType.matchingStates`, `PoiType.maxTickets`, `PoiType.validRange` | static, built at bootstrap |
| `PoiTypes` | the catalogue, and the block-state → type map `PoiTypes.TYPE_BY_STATE` that every lookup goes through | static |
| `PoiRecord` | one position's type and its `PoiRecord.freeTickets` counter | Server |
| `PoiSection` | the records in one 16³ section, by section-relative position and by type, plus the *Valid* flag that decides whether the section is rebuilt on load | Server |
| `PoiManager` | the index: a `SectionStorage` over the *poi/* region files, the whole query family, and the village distance graph | Server |
| `ServerLevel` | that a block change happened at all — `ServerLevel.updatePOIOnBlockStateChange` is the door every ordinary write goes through | Server, or the worldgen executor |
| `AcquirePoi` | which POI a mob claims, and when to stop asking about one it cannot reach | Server |
| `ValidateNearbyPoi` · `SleepInBed` | whether a remembered POI is still real, and what to do on arrival | Server |

## A ticket is a claim nothing enforces

A point of interest is a block state the game has decided is worth going to.
There are twenty-one kinds, none data-pack-driven: `PoiTypes.bootstrap`
registers them into `BuiltInRegistries.POINT_OF_INTEREST_TYPE` and files every
one of their block states into `PoiTypes.TYPE_BY_STATE`, throwing at startup
if two types claim one state. `PoiTypes.forState` and `PoiTypes.hasPoi` are
the only questions anyone asks of that map.

A ticket is the claim. A `PoiRecord` starts with `PoiType.maxTickets` free
and hands them out one at a time: `PoiRecord.acquireTicket` decrements,
`PoiRecord.releaseTicket` increments, both refuse to run past their end of the
range, and both mark the section dirty. `PoiRecord.hasSpace` asks whether any
are left, `PoiRecord.isOccupied` whether any are gone — the second question
being the one the village graph reads. Neither says anything about *who*: a
record does not know its holder, a holder is not told when its record
disappears, and no state on the block corresponds to a claim.

```mermaid
stateDiagram-v2
    [*] --> Free : block placed, PoiManager.add, PoiType.maxTickets free
    Free --> Held : PoiManager.take, then PoiRecord.acquireTicket
    Held --> Free : PoiManager.release, from one of four behaviours or the villager's end
    Held --> Gone : the block changes, so PoiManager.remove
    Free --> Gone : the block changes, so PoiManager.remove
    Gone --> [*]
    note right of Held : occupied, so its section is a village centre if the type is in PoiTypeTags.VILLAGE
    note right of Gone : nothing is released, and the claimant is not told
```

*The whole life of one ticket, and the thing to look for is what is missing.
The only way back to **Free** is a villager letting go, through a behaviour in
its brain or its end, and the two arrows into **Gone** bypass the claim
entirely — the block changed, the record went, and the note says what nobody
hears about it.*

The asymmetry in that figure is deliberate on the release side and merely
survivable on the removal side. `PoiManager.release` **throws** when the
section is not there and `PoiSection.release` throws when the record is not,
which is why four of the five that release a ticket check `PoiManager.getType`
or `PoiManager.exists` first. Those five are `ValidateNearbyPoi`,
`Villager.releaseAllPois` (through `Villager.releasePoi`,
which checks the type and then tests it against `Villager.POI_MEMORIES` before
it dares), `SetWalkTargetFromBlockMemory`, `GoToPotentialJobSite` and
`VillagerMakeLove`. The last checks nothing, and gets away with it because the
position it releases is one
`PoiManager.take` handed back a moment earlier. `PoiSection.remove` on
a missing record only logs an error, so the removal path is allowed to be
wrong and the release path is not.

Erasing a memory is not a release, which is what makes the job-site behaviours
noisy and harmless: `PoiCompetitorScan` awards a contested site to whichever
villager has the higher `Villager.getVillagerXp` and makes the loser erase
`MemoryModuleType.JOB_SITE`, `YieldJobSite` has an unemployed villager hand
its `MemoryModuleType.POTENTIAL_JOB_SITE` to a neighbour whose profession the
site belongs to, and
`AssignProfessionFromJobSite` promotes the potential site to the real one.
None of those three touches a ticket — though `GoToPotentialJobSite` does,
`GoToPotentialJobSite.stop` giving back the ticket `AcquirePoi` took on the
potential site.

## The catalogue

| types | the block | `PoiType.maxTickets` | `PoiType.validRange` |
|---|---|---:|---:|
| `PoiTypes.ARMORER` `PoiTypes.BUTCHER` `PoiTypes.CARTOGRAPHER` `PoiTypes.CLERIC` `PoiTypes.FARMER` `PoiTypes.FISHERMAN` `PoiTypes.FLETCHER` `PoiTypes.LEATHERWORKER` `PoiTypes.LIBRARIAN` `PoiTypes.MASON` `PoiTypes.SHEPHERD` `PoiTypes.TOOLSMITH` `PoiTypes.WEAPONSMITH` | one work block each, in the same order — blast furnace, smoker, cartography table, brewing stand, composter, barrel, fletching table, all four cauldrons, lectern, stonecutter, loom, smithing table, grindstone | 1 | 1 |
| `PoiTypes.HOME` | the bed's **head** half only — `PoiTypes.BEDS` filters `AbstractBedBlock.PART` to `BedPart.HEAD` | 1 | 1 |
| `PoiTypes.MEETING` | the bell | 32 | 6 |
| `PoiTypes.BEEHIVE` `PoiTypes.BEE_NEST` | hive and nest | 0 | 1 |
| `PoiTypes.NETHER_PORTAL` | the portal block | 0 | 1 |
| `PoiTypes.LODESTONE` | the lodestone | 0 | 1 |
| `PoiTypes.LIGHTNING_ROD` | the rod, every facing | 0 | 1 |
| `PoiTypes.TEST_INSTANCE` | the test instance block | 0 | 1 |

Six of the twenty-one types are locatable but unclaimable.
`PoiRecord.hasSpace` is false for them forever, so nothing can
`PoiManager.take` one, and `PoiRecord.isOccupied` — which asks whether the free
count has moved off `PoiType.maxTickets` — is false forever too. Not that it
would matter: none of the six is in `PoiTypeTags.VILLAGE`, so none was ever a
candidate for a village centre. They are indexed so something can find one
fast: the bee's hive search asks for `PoiTypeTags.BEE_HOME`
within 20 blocks and then filters through `Bee.doesHiveHaveSpace`, which asks
the `BeehiveBlockEntity` whether it is full. The index answers *where* and
something else answers *whether*. Three tags cut across the catalogue
([tags](../foundations/tags#a-tag-is-a-key-and-a-file)):
`PoiTypeTags.ACQUIRABLE_JOB_SITE` for the
thirteen professions, `PoiTypeTags.BEE_HOME` for the two hives, and
`PoiTypeTags.VILLAGE` for those thirteen plus `PoiTypes.HOME` and
`PoiTypes.MEETING` — fifteen types whose occupied records are what a village
*is*.

## Where the index lives, and how it repairs itself

`PoiManager` extends `SectionStorage`, so it is the third of the three region
stores a dimension keeps ([four folders, three of them the same
shape](chunk-storage#four-folders-three-of-them-the-same-shape)), and
the unit of storage is a chunk section and the unit of file a region:
`ChunkMap` builds it on the dimension's *poi/* folder, `PoiManager`'s own
constructor naming `DataFixTypes.POI_CHUNK`, and `PoiSection.Packed` is the
on-disk shape
— a
*Valid* boolean and a list of *Records*, each a position, a type and a
*free_tickets* count. Tickets survive a restart, and so do the villagers'
memories of them, and nothing on load reconciles the two.

`ChunkMap.tick` runs `PoiManager.tick` under the profiler's *poi*, which
writes dirty chunks for as long as the tick has time and then settles the
village graph ([the level
tick](../server/server-level-tick#the-chunk-source-does-five-things-in-one-call)
owns the
budget). Reads are the interesting half. The query family is a dozen shapes of
the same walk — `PoiManager.getInSquare` over a chunk range, `PoiManager.getInRange`
narrowing it to a sphere, and `PoiManager.findClosest`, `PoiManager.getRandom`,
`PoiManager.getCountInRange`, `PoiManager.exists` and `PoiManager.getType`
above them — and every one bottoms out in `SectionStorage.getOrLoad`, where a
section whose prefetch has not finished is read from disk **synchronously, on
the
Server thread, blocking**. That is why `ChunkMap.scheduleChunkLoad` fires
`SectionStorage.prefetch` beside the chunk's own parse and joins the two
before either is used.

The repair runs on every chunk read. `SerializableChunkData.read` calls
`PoiManager.checkConsistencyWithBlocks` once per section with block data. If
the section is in storage, `PoiSection.refresh` rebuilds it — but only if its
*Valid* flag is false, and the rebuild reuses the existing `PoiRecord` objects
for positions that still have a POI, so **a repair does not reset anybody's
tickets**. If it is not in storage, one is created and scanned. Both scans are
short-circuited by `PoiManager.mayHavePoi`, which asks
`LevelChunkSection.maybeHas` whether the palette holds any state
`PoiTypes.hasPoi` recognises ([the palette and the ladder it
climbs](chunk-anatomy#the-palette-and-the-ladder-it-climbs)), so a section
of plain stone is dismissed without one block read. The
*Valid* flag's codec defaults to **false**: anything not explicitly written as
validated gets rescanned.

## A record appears when a block changes, sometimes a task late

`Level.setBlock` calls `Level.updatePOIOnBlockStateChange` last, after the
neighbour updates, and on a `ServerLevel` that override compares
`PoiTypes.forState` of the old and the new state. Equal types mean nothing
happens — exactly the bed case, since a bed head that gains
`AbstractBedBlock.OCCUPIED` is still `PoiTypes.HOME`. Different types mean a
`PoiManager.remove` for the old and a `PoiManager.add` for the new, each
wrapped in a `BlockableEventLoop.execute` on the server.

That wrapper exists because `WorldGenRegion.setBlock` calls the same hook from the worldgen executor and the index is Server-thread-only. What it does *not* do is
defer the ordinary case: `MinecraftServer.scheduleExecutables` is false when
the caller is already the Server thread and not inside a task the loop is
running (`ReentrantBlockableEventLoop.runningTask`), so a block placed by a
player, a command or a mob during the tick body gets its record synchronously.
Deferral is the worldgen and nested-task case — there the record appears a
task later than the block it describes, and a read in between gets the old
answer.

## Noon, and a bed forty-eight blocks away

A bed is placed, a villager claims it, and thousands of ticks later it sleeps in
it. The index is written when the bed goes down and asked, and a ticket taken,
when the villager claims it — not the time you would guess — and at night
asked only whether the
record is still there.

```mermaid
sequenceDiagram
    participant SL as ServerLevel
    participant PM as PoiManager
    participant Brain as Brain
    participant AP as AcquirePoi
    participant PN as PathNavigation
    participant SIB as SleepInBed

    Note over SL,PM: any tick, Server thread
    SL->>SL: setBlock puts the bed head down, and the two states differ
    SL->>PM: add — a PoiRecord with one free ticket, the section dirty
    Note over Brain,SIB: Activity.CORE, any hour of the day, HOME absent
    Brain->>AP: at priority 10, and this evaluation is due
    AP->>PM: findAllClosestFirstWithType HOME, HAS_SPACE, 48 blocks
    PM-->>AP: the nearest five, past the retry cache
    AP->>AP: VillagerGoal<br/>Packages.<br/>validateBedPoi<br/>re-reads all five
    AP->>PN: createPath to all five at once, reach range 1
    PN-->>AP: a Path whose canReach is true, getTarget is one bed
    AP->>PM: take at that position, and one free ticket becomes zero
    AP->>Brain: MemoryModuleType.HOME set to a GlobalPos
    AP->>SL: broadcastEntityEvent 14
    Note over Brain,SIB: thousands of ticks later, tick 12000, Activity.REST
    Brain->>Brain: SetWalkTarget<br/>FromBlockMemory<br/>writes WALK_TARGET
    Brain->>Brain: ValidateNearbyPoi, within 16 blocks: is the record still HOME
    Brain->>SIB: within 2 blocks, and the bed is not OCCUPIED
    SIB->>SL: setBlock, AbstractBedBlock.OCCUPIED true
    SL->>SL: the state is HOME either way, so nothing is queued
    Note over Brain,SIB: morning, REST leaves the brain, SleepInBed.stop or WakeUp clears the flag, the ticket stays
```

*A bed placed and a bed slept in: the index is written at the top, asked, and
a ticket taken, at the claim in the middle, and at night asked only whether
the record still
exists. The last self-message is the page's hook drawn as a non-event —
sleeping wrote `AbstractBedBlock.OCCUPIED` straight to the block, and because
the before and after are both `PoiTypes.HOME` the manager was never asked
about it.*

The scan is cheap and the path is not. `PoiManager.findAllClosestFirstWithType`
turns a 48-block radius into a chunk radius of four, walks every section of
those chunks, filters by `PoiManager.Occupancy.HAS_SPACE` and sorts by
distance; `AcquirePoi` takes the first five and only then runs
`VillagerGoalPackages.validateBedPoi`, which re-reads each block to confirm it
is in `BlockTags.VILLAGERS_CAN_SLEEP_ON_BED` and not already `AbstractBedBlock.OCCUPIED`. Then
`AcquirePoi.findPathToPois` hands all five positions to
`PathNavigation.createPath` as one target set, at the reach range from
`PoiType.validRange` — one, for a bed. A villager's constructor raises
`PathNavigation.setRequiredPathLength` to 48 so that this search can span the
scan range ([pathfinding](../entities/pathfinding#the-budget-which-is-also-the-map)
owns that budget). Beds that failed before are held off by
`AcquirePoi.JitteredLinearRetry`, whose delay is **cumulative**: each attempt
adds another 40 to 79 ticks to that position's own counter, capped at
`AcquirePoi.JitteredLinearRetry.MAX_RETRY_PATHFINDING_INTERVAL`, 400. A bed
behind a wall is checked ever more rarely until the gap reaches the cap,
twenty seconds, and then the marker, untouched that long, is dropped, so the
next check starts the saw again from the beginning. A successful claim clears
the whole cache.

The claim itself is five statements. `PoiManager.take` — alone among the
radius searches in having no `PoiManager.Occupancy` parameter, because it
always means *HAS_SPACE* — is called with radius 1 around the path's target and a filter
accepting only that exact position, and calls `PoiRecord.acquireTicket` on
what it finds. Then `MemoryModuleType.HOME` is set to a `GlobalPos`, then
`ServerLevel.broadcastEntityEvent` sends event 14,
`EntityEvent.VILLAGER_HAPPY` — the green particle burst,
and the only thing an ordinary client learns of any of this. The last two
statements clear the retry cache and tell the debug channel. The memory is set
inside `PoiManager.getType`'s *ifPresent*, not inside `PoiManager.take`'s —
the take's result is never consulted.

## After the claim: the night shift

`Brain.updateActivityFromSchedule`, run from `UpdateActivityFromSchedule` at
priority 99 and only when more than twenty ticks have passed since it last did
anything, reads the villager's schedule *attribute* —
`EnvironmentAttributes.VILLAGER_ACTIVITY` for an adult,
`EnvironmentAttributes.BABY_VILLAGER_ACTIVITY` for a child — at the villager's
own position, and `Timelines.VILLAGER_SCHEDULE` puts the `Activity.REST`
keyframe at tick 12000 of a 24000-tick period
([environment attributes](environment-attributes-and-timelines#the-four-timelines)
owns the mechanism, [goals and
brains](../entities/ai-goals-and-brains#the-brains-trace-a-villagers-day)
the pull that reads it; the old *Schedule* class is gone). Only then does the bed half of
the brain exist at all: `VillagerGoalPackages.getRestPackage` is where
`SetWalkTargetFromBlockMemory`, `ValidateNearbyPoi` for `PoiTypes.HOME` and
`SleepInBed` live, while the core package validates the *job site* and not the
bed. So between dawn and dusk a villager whose bed was mined keeps pointing at
a position with no record, and nothing tells it otherwise.

### What the three of them do to the record

At night the three run in priority order, and only two of them ever touch a
ticket. `SetWalkTargetFromBlockMemory` at priority 2 is the walking half —
where it sends a villager and how it picks an intermediate target belong to
[goals and brains](../entities/ai-goals-and-brains#the-brains-trace-a-villagers-day);
what belongs here is its one effect on the index, which is that it gives up,
**releasing the ticket** and erasing the memory, once
`MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE` has stood for more than 1200
ticks, the memory points into another dimension, or, with the bed more than
150 blocks off, a thousand tries find no step toward it. `ValidateNearbyPoi`
at priority 3 does
nothing at all unless the bed is within 16 blocks and in this dimension: then
it erases the memory if `PoiManager.exists` no longer agrees on the type, and
if the bed is `AbstractBedBlock.OCCUPIED` while *this* villager is not asleep — asleep
anywhere, not asleep here — it erases the memory. Whether it also releases the
ticket turns on a second question, asked of the world rather than of the
block: is a `Villager` actually sleeping inside that block's box? If one is,
the memory goes and the ticket stays, the sleeper being presumed to hold it.
If none is, the ticket goes too. The two can disagree — a player asleep in the
bed sets the same flag — and that disagreement is the whole reason the second
question exists. `SleepInBed`, also priority 3, is the one that touches no
ticket at all. It
needs the villager within 2 blocks, the bed unoccupied and
`SleepInBed.COOLDOWN_AFTER_BEING_WOKEN` ticks since
`MemoryModuleType.LAST_WOKEN`; it calls `LivingEntity.startSleeping`, which is
what actually sets the flag. That is the second of the three reads of
`AbstractBedBlock.OCCUPIED` — a gate on entering the bed, not a change to the record —
and the third is `VillagerGoalPackages.validateBedPoi`, the filter
`AcquirePoi` runs over its best five.

Morning ends it twice over: when `Activity.REST` goes inactive,
`SleepInBed.stop` wakes the villager as the behaviour ends, and `WakeUp`, at
priority 0 in the core package, calls `LivingEntity.stopSleeping` on any
villager still asleep outside it. Either way the flag
clears and the ticket does not.

## What makes a village

`PoiManager.DistanceTracker` is a `SectionTracker` — the same
`DynamicGraphMinFixedPoint` flood the ticket system's two graphs use
([two graphs, one store](tickets-and-loading#two-graphs-one-store)), and the
only one of the five
outside `server/level` — over chunk sections instead of chunks. Its sources are the sections where
`PoiManager.isVillageCenter` holds: at least one record whose type is in
`PoiTypeTags.VILLAGE` and whose `PoiManager.Occupancy` is *IS_OCCUPIED*. They
sit at level 0 and the flood runs out to `PoiManager.MAX_VILLAGE_DISTANCE`,
six sections, past which the level is simply absent from the map.

**A village is made of the sections in memory only.** Alone in the query family,
`PoiManager.isVillageCenter` reads through `SectionStorage.get` rather than
`SectionStorage.getOrLoad` — the getter that answers null for a section not in
memory instead of pulling it off the disk — and it treats that null as *not a
centre*. So the graph is not a picture of what is in the save; it is a picture
of what has been read into memory this run, which nothing evicts. That is
deliberate: the flood is settled every tick from `PoiManager.tick`, and a
version of it that could touch the disk would turn the village graph into an
IO source in the middle of the level tick.

**An unclaimed bed makes no village.** A hundred empty beds are a hundred
records and zero sources; one villager taking one ticket lights the section up.
`PoiManager.setDirty` and `PoiManager.onSectionLoad` re-seed the source,
`PoiManager.tick` settles the flood every tick, and
`PoiManager.sectionsToVillage` settles it again before answering.
`ServerLevel.isVillage` is that distance being one section or less;
`ServerLevel.isCloseToVillage` takes the distance as an argument and refuses
anything past six.

That boolean is load-bearing far outside this system. `BadOmenMobEffect`
starts a raid only where `ServerLevel.isVillage` is true, `VillageSiege` needs
one to put zombies in, `PatrolSpawner` refuses a patrol when its chosen player
stands within two sections of one and `CatSpawner` insists on being within
two. `Raid` re-checks
it as the raid runs, and `Raids` sets the raid's centre to the average
position of the occupied `PoiTypeTags.VILLAGE` records within 64 blocks.

## Other readers of the index

| who | what it asks for | radius | `PoiManager.Occupancy` |
|---|---|---|---|
| `PortalForcer.findClosestPortalPosition` | `PoiTypes.NETHER_PORTAL`, after `PoiManager.ensureLoadedAndValid` drags the chunks in | 16 going to the Nether, 128 coming back | *ANY* |
| `Bee` | `PoiTypeTags.BEE_HOME`, then `Bee.doesHiveHaveSpace` for the real occupancy | 20 | *ANY* |
| `ServerLevel.findLightningRod` | `PoiTypes.LIGHTNING_ROD` standing at the surface height | 128 | *ANY* |
| `LodestoneTracker` | one position — is `PoiTypes.LODESTONE` still there | — | — |
| `LocateCommand` | `PoiManager.findClosestWithType` for a type or a tag | 256 | *ANY* |
| `Raids` | the records it averages into a raid's centre | 64 | *IS_OCCUPIED* |
| `CatSpawner` | more than four claimed `PoiTypes.HOME` nearby — asked only after `ServerLevel.isCloseToVillage` at two sections has already said yes | 48 | *IS_OCCUPIED* |
| `WanderingTraderSpawner` | a `PoiTypes.MEETING` near a player, to arrive at | 48 | *ANY* |
| `NearestBedSensor` | `PoiTypes.HOME` for `MemoryModuleType.NEAREST_BED`, babies only, no ticket taken | 48 | *ANY* |

`PoiManager.ensureLoadedAndValid` in the first row is the only caller that
forces the chunks themselves to load, where every other query reads the POI
file alone: for any nearby
section missing or invalid it pulls the chunk in at `ChunkStatus.EMPTY`, once
per chunk per server run since `PoiManager.loadedChunks` never forgets. The
index is on the debug channel too — `LevelDebugSynchronizers.registerPoi`,
`LevelDebugSynchronizers.updatePoi` and `LevelDebugSynchronizers.dropPoi` feed
`DebugSubscriptions.POIS` and `DebugSubscriptions.VILLAGE_SECTIONS`.

## Questions players ask

**Why is a villager sleeping in the bed I built for someone else?** Because
the claim is a number in a file and the bed is a block, and neither knows the
other. `PoiManager.take` decremented a counter 48 blocks away hours before
anyone walked anywhere, and `SleepInBed` only ever checks that the bed is *not
currently occupied* — never who holds the ticket.

**Why do villagers stop breeding when I take a bed away?**
`VillagerMakeLove.tryToGiveBirth` calls `VillagerMakeLove.takeVacantBed`
first — a `PoiManager.take` for `PoiTypes.HOME` within 48 blocks, filtered by
reachability. No free ticket, no baby, and the pair get entity event 13,
`EntityEvent.VILLAGER_ANGRY`, instead. If the birth then fails the ticket is
released; if it succeeds,
`VillagerMakeLove.giveBedToChild` writes the baby's `MemoryModuleType.HOME`
directly, with no `AcquirePoi` involved. This is the raw `PoiManager.take`,
which finds the first reachable match in section order rather than the
nearest, so the baby's bed need not be the closest.

**Why does the same bed get claimed twice after I break and replace it?**
Breaking it runs `PoiManager.remove` and the record ceases to exist with its
ticket count — no release, no notification. The villager keeps its `GlobalPos`
until `ValidateNearbyPoi` next runs, which needs `Activity.REST` active *and*
the villager within 16 blocks, or `SetWalkTargetFromBlockMemory` gives it up.
Replace the bed first and there is a brand-new
record with a full ticket: another villager can claim it, while the one whose
memory already points there keeps pointing, so two villagers hold one bed.

## Where to look

The record and the three things that happen to one:
`PoiRecord.acquireTicket` · `PoiManager.add` · `PoiManager.take` ·
`PoiManager.release`. Where they live and how they are repaired:
`PoiManager.checkConsistencyWithBlocks` · `PoiSection.refresh` ·
`ServerLevel.updatePOIOnBlockStateChange`. The claim, from the brain's side:
`AcquirePoi.create` · `ValidateNearbyPoi.create`. And the flood that turns
claimed records into a village: `PoiManager.DistanceTracker` ·
`ServerLevel.isVillage`.

The other index in this corner of the tree — the fire-and-forget broadcast
sculk sensors listen to — is
[game events and vibrations](game-events-and-vibrations). The brain belongs
to [goals and brains](../entities/ai-goals-and-brains), death and
conversion to [the entity
lifecycle](../entities/entity-lifecycle#five-reasons-one-label), the bed
block to [blocks and states](../blocks/blocks-and-states).

---

*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
