This commit is contained in:
Rainer Leit
2026-09-25 17:02:24 +03:00
parent cc43ed8dc8
commit 9597629951
2149 changed files with 460234 additions and 1770 deletions
+1
View File
@@ -133,6 +133,7 @@ agree. A row is added to a spec and here in the same pull request as the emit ca
| Stats | `status_applied`, `status_blocked`, `status_removed` |
| Combat | `enemy_spawned`, `enemy_killed`, `ability_used`, `player_damaged`, `player_downed`, `player_revived`, `player_died`, `party_wiped`, `grief_action` |
| Crafting | `substance_processed`, `piece_shaped`, `piece_enchanted`, `item_assembled`, `assembly_rejected`, `activity_completed`, `characteristic_discovered`, `weapon_equipped` |
| World | `world_map_opened` (layer, metres_per_pixel, opened_by) |
| Technical | `perf_sample`, `error_logged`, `net_correction` (count of visible movement corrections per 30 s, from `p.NetShowCorrections`) |
### The rows that carry weight
+68
View File
@@ -161,12 +161,80 @@ Hooks now, content later; none of the later work touches a widget.
| Rebinding | prompts read the live binding | the rows ship in step 3 through engine settings |
| Hold-to-confirm | on irreversible verbs already | an option to extend it to every verb |
## The world map
```
[DECIDED] The map is a picture of the world with a linear transform on it. There is no capture. (D-73)
```
`L_World` is a whole planet map imported with no crop (`RawContent/World/Region.json`), so the art and the ground
are the same rectangle at two scales and the whole projection is one multiply and one add per axis. Everything
else about a map view falls out of that and most of it is absence: no scene capture, no render target, no minimap
actor, no per-tile bookkeeping. A capture would also be *wrong* rather than merely expensive — the level is
world-partitioned, so a capture only ever sees the streamed-in region, and a map is exactly the thing that must
show ground nobody is standing on.
```
Source/SaltyCore/World/
└── WorldMapProjection.h // pure: world cm <-> 0..1 across the art, the seam, distance on the ground
Source/Salty/World/
├── WorldMapDefinition.h // UPrimaryDataAsset: the projection, the layers, which level it is a map of
└── WorldMapSubsystem.h // world subsystem: finds the definition, holds the markers, emits the event
Source/Salty/UI/
├── SWorldMap.h // the map itself: drawing, panning, zooming, markers, scale bar
└── WorldMapWidget.h // UWidget wrapping it, so a Blueprint can drop one into a screen
Source/SaltyEditor/WorldMap/
└── WorldMapTab.h // the same SWorldMap in a dockable tab; a click moves the viewport camera
```
**Why Slate and not a `UUserWidget`.** The map has two hosts with nothing else in common, and the editor tab has
no `UWorld` at all — anything that needed one to exist could not be shared, and a second implementation is where
two maps quietly start disagreeing about where things are. `UWorldMapWidget` is the UMG wrapper; `WBP_WorldMap`
is what owns the frame around it, which is the usual C++/Blueprint line.
**Markers are registered, never searched for.** An actor that wants to be on the map adds one in `BeginPlay` and
removes it in `EndPlay`. The local player's own body is deliberately not a registered marker: it moves every
frame, and a registry entry rewritten every frame is a registry being used as a variable. It is queried each
paint instead, through `GetLocalPlayerMarker`, which also means it is drawn last and can never end up hidden
under a waypoint that happens to be on top of it.
**Following is sticky in both directions.** The map opens centred on the player and tracks them. Panning by hand
turns the follow off, because a map that snaps back the moment you let go cannot be read — and because that
would otherwise be a one-way door, a left double-click (or `bs.WorldMapFollow`) goes back to the player and
resumes it. `FocusOnWorld` deliberately does *not* re-arm the follow: a "go here" jump and a "go back to me"
are different intents and only one of them means "and keep up".
**The art is data.** `RawContent/World/MapArt/layers.json` says which planet images become layers; `Tools/MapArt`
renders them (including the derived shaded-relief layer) and `Scripts/Authoring/create_world_map.py` imports them
and writes the definition. The projection is copied out of `Region.json` rather than typed, because a map that
disagrees with the landscape about how big the world is is the one bug here that still looks like a plausible map.
**The key is a debug bind, deliberately.** `M` opens the map and `N` cycles its layers, both `DebugExecBindings`
in `Config/DefaultInput.ini` pointing at the `bs.*` commands. That is the engine's own mechanism for putting a
key on a console command: development builds only, no Input Action, no mapping context, no asset. It is the
right shape *because* it is not the input map — step 3 designs that, and a map key invented here would be a
guess at an `Input.*` action that step has to live with. When the map gets a HUD it gets a real action and
those two lines go away.
**Not yet:** there is no HUD, so the only ways in are that key, the console and the editor tab. It does
not read the theme asset either, because there is not one yet; its colours are local and will move to `UUITheme`
when that lands. Overlay marks — the towns, roads and forests `overlay.json` already carries in world metres —
are not drawn; they transfer by the same normalised coordinates and are the obvious next thing.
## Telemetry
`settings_changed` with `setting_id` as the dotted path (`camera.fov.first_person`, `input.bindings.interact`) and
the new value. Nothing else: prompt visibility and menu opens are high frequency and low value, and
`prop_interacted` already answers whether players find things.
`world_map_opened` with the layer, the zoom in ground metres per pixel and what opened it. One event, because
opening the map is the act worth counting and which layer and how far in say what a person wanted from it.
Every way of opening it goes through `UWorldMapSubsystem::NoteMapOpened`, so the count is of maps opened and not
of the ways of opening them.
## Open questions
- **Q1. World-space widget legibility.** A `UWidgetComponent` at bench distance in both camera modes has to be